十一、指针和引用(一)

十一、指针和引用(一)

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;
}

热门相关:试婚老公,要给力   你好,墨先生   都市捉妖人   至尊剑皇   黜龙