十一、指针和引用(一)1、指针

1)思考

​在计算机程序中,有一条铁律那就是万物皆内粗,而我们知道,内存就是一个个小格,存放着高电平或者低电平,也就是0或者1,我们要表达的一切都是通过这种二进制的方式放到内存中,当我们读取、写入,其实局势在对应的内存空间执行读或者写操作

​我们今天就研究研究,当我们读取和写入的时候,背后的故事,首先我们需要知道我们读取和写入的内存地址,因为内存里有很多个小格,就好比一栋楼有很多住户,你要找好朋友张三,你就要知道他家的地址,你不能挨个去敲门,因为这样会被打死…,其次来说,效率也很低。

​再者来讲,你还要知道你到底要读取多少格的内容,或者写入多少格的内容,因为不同的数据类型,占用的内存空间也是不同的

总结:操作内存,即读取和写入操作时,需要知道内存地址和内存的大小

2)内存空间模拟图

在计算机中,内存的最小单位为字节,每8个bit算一个内存地址,要操作内存,需要知道内存的地址和内存的大小

3)指针语法

​C/C++提供了让我们直接操作内存的机会,这种机会就是利用指针,利用指针操作内存需要知道两个要素:即要操作的内存地址和要操作的内存大小。

​指针的本质就是一种特殊的变量类型,指针本身就是一种变量。

​利用int类型的指针,可以操作int类型的数据,int类型占4个字节,所以int类型的指针操作的也是4个字节的内存。

//指针语法数据类型* 变量名称;           //数据类型解决指针内存大小的问题//示例int* pStudentId;
#include int main(){    int* a{ };   //声明一个int类型的指针,指针指向的是内存地址    std::cout << a;}

4)指针的其他声明方法

//指针声明数据类型 *变量名称;//示例int *pStudentId;          //*号靠近变量名int* a{},b;        //a是int类型的指针,b是int类型的变量int *a{},*b;       //a和b都是int类型的指针//指针声明建议方式:多个指针分开写int* a;int* b;

5)取址运算符(读取内存地址)

​既然指针是用来操作内存的,那么如何获得一个变量的内存地址呢?可以通过取值运算符&获取变量的地址。取值运算符&是一个一元运算符,用于获取变量的地址,也可称为引用运算符。

#include int main(){    int a = 500;    int* pa{ &a };   //声明一个int类型的指针pa,将a的地址赋值给指针pa    std::cout << pa;  //输出a内存地址00F9FA98}//注:局部变量,每一次运行程序,内存地址是会发生变化的

6)间接运算符(操作内存地址)

​通过取值运算符&可以获取到变量的内存地址,通过间接运算符*可以操作内存地址。

通过地址来操作内存空间,虽然效果一样,但是原理不一样

//间接运算符*#include int main(){    int a = 500;    int* pa{ &a };   //声明一个int类型的指针pa,将a的地址赋值给指针pa    //pa=0x5000      //表示修改a的内存地址    std::cout << "a的内存地址为:" << pa << std::endl;    *pa = 1000;      //在变量a的内存空间中写入1000(修改a的内存空间),即修改变量a的值    std::cout << "a的值为:" << a << std::endl;  //输出a的值为1000}          // *pa可以当作来用,*pa是直接操作变量的内存空间;直接修改a的值,是通过操作系统来修改a的值,本质不同。   

7)指针声明、取值及操作示例

//指针声明、取值及操作示例#include int main(){    int a{ 5000 };         //声明一个int类型的指针,并初始化为空指针    int* pa{ &a };         //pa等于a的内存地址          std::cout << "a的内存地址:" << pa << std::endl << "a的初始值:" << *pa << std::endl;    *pa = 1000;         //通过内存修改值    std::cout << "修改后a的值:" << *pa << std::endl;;    std::cout << "++++++++++++++++++++++++++++++++++++++++++++" << std::endl;    char c = 65;    char* pc = &c;    std::cout << *pc << std::endl;    (*pc)++;     //要对指针进行++,需要使用括号    std::cout << *pc << std::endl;}

2、指针数组

​要深刻理解,指针的本质起始就是一种特殊的变量类型,因此指针也可以通过数组的方式声明。对变量可以操作什么,那么对指针就能够操作什么

1)指针数组的声明

//指针数组的声明语法int* ptrArray[10];       //即什么10个int类型的指针

2)指针数组的使用

#include int main(){int studentId[5]{ 1001,1002,1003,1004,1005 };//取数数组中每个元素的内存地址,查看是否连续int* ptrStudentId[5]{};for (int i = 0; i < 5; i++){ptrStudentId[i] = &studentId[i];   //指针数组的小标即对应数组的小标std::cout << "第" << i << "个元素的内存地址为" << ptrStudentId[i] <<",值为" <<studentId[i]<< std::endl;}}

2)指针二维数组

#include int main(){int studentId[2][2]{{1001,1002},{2001,2002}};int* ptrStudent[2][2];          //声明一个二维数组指针for (int x = 0; x < 2; x++){for (int y = 0; y < 2; y++){ptrStudent[x][y] = &studentId[x][y];      #使指针获取到二维数组的地址std::cout << "内存地址:" << ptrStudent[x][y] << "  值:" << *ptrStudent[x][y] << std::endl;}}}

3、指针补充

1)指针的大小

​指针也是一种特别的数据类型,也是一个变量,因此也需要内存空间来存放指针。

​指针的本质是一个内存地址,而内存地址的本质是一个整数。为了能够完美表达内存地址,不管是什么类型的指针,在32位操作系统下指针的大小都为4个字节,64位操作系统下为8字节,即需要4个字节来存放指针

//可以通过sizeof()计算指针的大小#include int main(){int a{ 100 };int* ptr{ &a };char ch{ 65 };char* ctr{ &ch };std::cout << sizeof(ptr) << "\n";   //输出4std::cout << sizeof(ctr) << "\n";//输出4}//在x86和x64操作系统下,指针的大小不同

2)指针的类型转化

​不能将一个类型的地址赋值给另外一个类型的指针。只要是指针,就说明是一个内存地址,就可以进行类型转化

![1700208369312](D:\2023年学习\逆向\笔记\12 【CC++ 基础语法】指针和引用(一)\image\1700208369312.png)

类型的意义在于告诉编译器,同样的一个地址,显示的结果不同,在于变量的类型,如果是int类型的指针,显示内容时,按照int的规则进行处理

#include int main(){ int      a{ 9999 }; unsigned b{ 9999 };int* ptra{ &a };//ptra = &b;     //&b是一个地址,说明就是一个整数,整数就可进行数据类型转化ptra =(int*)&b; //对&b进行地址转化,转化为int类型的指针std::cout <<"b的初始值为:" << b << std::endl;*ptra = 5200;std::cout << "通过间接运算符修改后,b的值为:" << b << std::endl;std::cout << "通过间接运算符修改后,b的值为:" << *ptra << std::endl;std::cout << std::endl;*ptra = -1;std::cout << "通过间接运算符修改后,b的值为:" << b << std::endl;  //b的类型为unsigned,所以输出的为正数std::cout << "通过间接运算符修改后,b的值为:" << *ptra << std::endl; //*ptra的类型的int型指针,所以输入为-1//同一个内存地址,但是因为数据类型的不同,输出的值也不同char* ctr{};ctr = (char*)ptra;//A的16进制为41,而char类型,只能够修改指针中的一个字节,其他字节的值无法修改,即0xFFFFFF41,转化为10进制为4294967105*ctr = 'A';std::cout << "转化为char类型指针后b的值为:" << b << std::endl;  }

4、指针的计算

//指针的计算int a[]{1001,1002,1003,1004,1005};int* ptr{&a[0]};计算(*ptr)++和*ptr++的结果?    //*ptr++相当于*(ptr++)  (*ptr)++ 相当于a[0]++,即1001+1==1002
#include int main(){int a[]{ 1001,1002,1003,1004,1005 };int* ptr{ &a[0] };std::cout << ptr << std::endl;std::cout << *ptr << std::endl;(*ptr)++;                       //*ptr即a的值,即将a的值+1,即1002std::cout << ptr << std::endl;std::cout << *ptr << std::endl;*ptr++;          //++的优先级高,即先ptr++,即地址进行++,地址++,一次增加数据类型的长度std::cout << ptr << std::endl;std::cout << &a[1] << std::endl;     //ptr+1指向了数组的下一个元素的起始地址std::cout << *ptr << std::endl;//指针+1的时候,数值的变化是+1*指针类型的大小}

5、指针的指针

//指针的指针int a[]{1001,1002,1003,1004,1005};int* ptr{&a[0]};如何表示ptr指针的内存?注:ptr是一个int类型的指针,因此ptr是一个变量,在32位操作系统下占用4个字节内存,因此ptr也有地址
//指针的指针示例#include int main(){int a[]{ 1001,1002,1003,1004,1005 };int* ptr{ &a[0] };int** pptr{ &ptr };   //指针的指针,取指针ptr的地址std::cout <<"a的地址:" << ptr << std::endl;std::cout  <<"通过地址取a的值:" << *ptr << std::endl;std::cout <<"取a的指针的地址:" << pptr << std::endl;std::cout <<"取a的指针的值:" << *pptr << std::endl;   //是一个地址std::cout <<"取a指针的值(地址)的值:" << **pptr << std::endl;std::cout << "++++++++++++++++++++++++++++++++\n";*pptr = &a[1];std::cout << *ptr << std::endl;}

{{uploading-image-986560.png(uploading…)}

多级指针

代码内存地址
int a{500};5000x50000
int* ptr{&a};0x500000x50100
int** pptr(&ptr)0x501000x50200
int*** ppptr0x502000x50300

6、常量指针、指针常量、指向常量对象的常量指针

1)常量指针

​所谓的常量指针,即这个指针指向一个常量的内存地址,常量指针中,不能对其指向的内存地址进行改变,但是指针指向的地址可以改变

//常量指针语法const 变量类型* 变量名{&常量名}//示例int const a{1000};int const b{1500};const int*  ptrA{&a};ptrA = &b;          //正确,可以修改常量指针的指向*ptrA = 500;        //错误,不可以修改常量指针内存中的地址
//常量指针示例#include int main(){const int a{ 1000 };const int b{ 2000 };const int* ptr{ &a };   //常量指针 std::cout << ptr << std::endl;;//*ptr = 9000;   //错误,当指针指向常量时,不可以修改内存地址里的值ptr = &b;      //但是可以修改常量指针的指向 std::cout << ptr << std::endl;}//注:常量指针可以修改指向,但是不可以修改内存里的值

2)指针常量

​所谓的指针常量,即这个指针变量是一个常量,一旦初始化就不可以再指向其它内存地址,但是内存地址里的数据可以读写。(即指针是一个常量)

//指针常量语法变量类型* const//示例int const a{1000};int const b{1500};int* const ptrA{&a};ptrA = &b;          //错误,不可以修改常量指针的指向*ptrA = 500;        //正确,可以修改常量指针内存中的值
//指针常量示例#include int main(){int a{ 1000 };int b{ 2000 };int* const ptr{ &a };   //指针常量,const修饰的是ptrstd::cout << *ptr << std::endl;;//ptr = &b;   //错误,指针指向的内存空间不可以修改*ptr = 9000;  //正确,可以修改内存空间的值std::cout << a << std::endl;  }

3)指向常量的常量指针

​指向常量的常量指针,即这个指针变量是一个常量,一旦初始化就不可以再指向其他内存地址,因为其本事就是一个指向常量的指针,所以其执行的内存区域也不可以修改。

//指向常量的常量指针语法const 变量类型* const//示例int const a{1000};int const b{1500};const int*  ptrA{&a};ptrA = &b;          //错误,不可以修改常量指针的指向*ptrA = 500;        //错误,不可以修改常量指针内存中的值
//指向常量的常量指针示例#include int main(){const int a{ 1000 };const int b{ 2000 };const int* const ptr{ &a };//ptr = &b;           //错误,不允许修改内存地址的指向//*ptr = 9999;        //错误,不允许修改内存地址的值std::cout << *ptr << std::endl;}

7、项目:通过指针实现游戏技能

需求:设计麟江湖的技能释放模型,要求用户按下相应技能快捷键后开始释放技能,技能数据如下,假设角色的当前等级下最高内力为1000,最高生命为3000,基础攻击力为50

快捷键技能名称技能效果
1治愈消耗100内力,生命值恢复最大生命值的10%
2金刚掌消耗50内力,对远程目标造成基础攻击+50点伤害
3麻痹数消耗50内力,禁止目标攻击三个回合
4鹰抓功10个回合内,对目标造成伤害将恢复伤害量20%的内力伤害量60%的生命
5绝处逢生消耗100内力,对目标造成基础攻击+已损失血量的伤害
6易筋经消耗300内力,将内力和生命值进行互换,攻击力提高1000%
#include #include struct Role{int Hp;int maxHp;int Mp;int maxMp;int act; //攻击力int cantact; //禁止攻击int bufcount; //回合bool cant;};int main(){int inkey, damage;Role user{ 3000,3000,1000,1000,50,0,false };Role boss{ 30000,30000,1000,1000,190,0,false };int* pUserHp = &user.Hp;           //使用指针取值代替人物血量int* pBossHp = &boss.Hp;   //使用指针取值代替boss血量lfight:system("cls");printf("生命[%d/%d]  BOSS生命[%d/%d]\n", *pUserHp, user.maxHp, *pBossHp, boss.maxHp);printf("内力[%d/%d]  攻击力[%d]\n", *pUserHp, user.maxMp, user.act);printf("请输入你的技能:");inkey = _getch();damage = 0;switch (inkey){case 49:if (*pUserHp > 99){*pUserHp -= 100;user.Hp += 300;user.Hp = user.Hp > user.maxHp ? user.maxHp : user.Hp;}break;case 50:if (*pUserHp >= 50){*pUserHp -= 50;user.Hp -= 50 + user.act;}break;case 51:if (*pUserHp >= 50){*pUserHp -= 50;boss.cantact = 3;}break;case 52:user.bufcount = 10;break;case 53:if (*pUserHp >= 100){pUserHp -= 100;damage = user.maxHp - user.Hp + user.act;}break;case 54:if ((*pUserHp >= 300) && (!user.cant)){int ls = user.maxHp;user.maxHp = user.maxMp;user.maxMp = ls;ls = user.Hp;user.Hp = *pUserHp;*pUserHp = ls;user.act *= 10;user.cant = true;}break;}if (boss.cantact > 0){boss.cantact--;}else user.Hp -= boss.act;*pBossHp -= damage;if (user.bufcount > 0){user.bufcount--;user.Hp += damage * 0.6;*pUserHp += damage * 0.2;user.Hp = user.Hp > user.maxHp ? user.maxHp : user.Hp;*pUserHp = *pUserHp > user.maxMp ? user.maxMp : *pUserHp;}if (user.Hp < 1){system("cls");printf("你死了,游戏结束!!");}else if (*pBossHp < 1){system("cls");printf("击败BOSS,游戏结束!!");}else goto lfight;}