十一、指针和引用(一)
十一、指针和引用(一)
1、指针
1)思考
在计算机程序中,有一条铁律那就是万物皆内粗,而我们知道,内存就是一个个小格,存放着高电平或者低电平,也就是0或者1,我们要表达的一切都是通过这种二进制的方式放到内存中,当我们读取、写入,其实局势在对应的内存空间执行读或者写操作
我们今天就研究研究,当我们读取和写入的时候,背后的故事,首先我们需要知道我们读取和写入的内存地址,因为内存里有很多个小格,就好比一栋楼有很多住户,你要找好朋友张三,你就要知道他家的地址,你不能挨个去敲门,因为这样会被打死...,其次来说,效率也很低。
再者来讲,你还要知道你到底要读取多少格的内容,或者写入多少格的内容,因为不同的数据类型,占用的内存空间也是不同的
总结:操作内存,即读取和写入操作时,需要知道内存地址和内存的大小
2)内存空间模拟图
在计算机中,内存的最小单位为字节,每8个bit算一个内存地址,要操作内存,需要知道内存的地址和内存的大小
3)指针语法
C/C++提供了让我们直接操作内存的机会,这种机会就是利用指针,利用指针操作内存需要知道两个要素:即要操作的内存地址和要操作的内存大小。
指针的本质就是一种特殊的变量类型,指针本身就是一种变量。
利用int类型的指针,可以操作int类型的数据,int类型占4个字节,所以int类型的指针操作的也是4个字节的内存。
//指针语法
数据类型* 变量名称; //数据类型解决指针内存大小的问题
//示例
int* pStudentId;
#include <iostream>
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 <iostream>
int main()
{
int a = 500;
int* pa{ &a }; //声明一个int类型的指针pa,将a的地址赋值给指针pa
std::cout << pa; //输出a内存地址00F9FA98
}
//注:局部变量,每一次运行程序,内存地址是会发生变化的
6)间接运算符(操作内存地址)
通过取值运算符&可以获取到变量的内存地址,通过间接运算符*可以操作内存地址。
通过地址来操作内存空间,虽然效果一样,但是原理不一样
//间接运算符*
#include <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
int main()
{
int a{ 100 };
int* ptr{ &a };
char ch{ 65 };
char* ctr{ &ch };
std::cout << sizeof(ptr) << "\n"; //输出4
std::cout << sizeof(ctr) << "\n"; //输出4
}
//在x86和x64操作系统下,指针的大小不同
2)指针的类型转化
不能将一个类型的地址赋值给另外一个类型的指针。只要是指针,就说明是一个内存地址,就可以进行类型转化
![1700208369312](D:\2023年学习\逆向\笔记\12 【CC++ 基础语法】指针和引用(一)\image\1700208369312.png)
类型的意义在于告诉编译器,同样的一个地址,显示的结果不同,在于变量的类型,如果是int类型的指针,显示内容时,按照int的规则进行处理
#include <iostream>
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 <iostream>
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,即1002
std::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 <iostream>
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}; | 500 | 0x50000 |
int* ptr{&a}; | 0x50000 | 0x50100 |
int** pptr(&ptr) | 0x50100 | 0x50200 |
int*** ppptr | 0x50200 | 0x50300 |
6、常量指针、指针常量、指向常量对象的常量指针
1)常量指针
所谓的常量指针,即这个指针指向一个常量的内存地址,常量指针中,不能对其指向的内存地址进行改变,但是指针指向的地址可以改变
//常量指针语法
const 变量类型* 变量名{&常量名}
//示例
int const a{1000};
int const b{1500};
const int* ptrA{&a};
ptrA = &b; //正确,可以修改常量指针的指向
*ptrA = 500; //错误,不可以修改常量指针内存中的地址
//常量指针示例
#include <iostream>
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 <iostream>
int main()
{
int a{ 1000 };
int b{ 2000 };
int* const ptr{ &a }; //指针常量,const修饰的是ptr
std::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 <iostream>
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 <iostream>
#include <conio.h>
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;
}