十四、指针和引用(四)

十四、指针和引用(四)

1、字符处理(字符串)

1)字符串

​ 日常生活中,单个字符无法满足我们的需求,比如一个单词hello要由五个字符组成,名字张三要由两个中文字符来组成,我们把这种连续的字符称为字符串,字符串在内存中的表现就是连续的字符。比如hello在内存中是这样子的。

​ 注:字符在内存中也是数字,字符串以0结尾,即\0就是数字0

H e l l o \0
72 101 108 108 111 0

​ 连续的内存空间就是数组,因此可以用数组或者来自来声明一个字符串

//通过数组声明字符串
#include <iostream>

int main()
{
    //通过数组声明字符串
    char strA[0xFF]{ 'H','e','l','l','o' };
    char strB[0xFF]{ 0x48,0x65,0x6C,0x6C,0x6F };   //字符在内存中也是数字,所以可以使用ASCII表示字符
    char strC[0xFF]{ "Hello" };       //可直接通过双引号声明数组,相当于将字符串的每一位初始化为==>  'H','e','l','l','o'
    
    //通过指针声明字符串
    char* strD = (char*)"Hello";       //hellow默认是一个const常量类型,无法直接初始化为char类型的指针,需要强制类型转化
    const char* strE = "Hello";        //或直接通过常量指针来定义字符串,则不需要强制类型转化
    char* stdE = new char[0xFF]{ "Hello" };
    
    std::cout << strA << std::endl;
    std::cout << strB << std::endl;
    std::cout << strC << std::endl;
    std::cout << strD << std::endl;
    std::cout << strE << std::endl;
}

2)单字节字符串(char)

假设当前目标计算机是中文GBK编码,那么内存表现如下:

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

'H'占用1个字节,'张'占用2个字节,决定'张'在内存中是什么数字,由操作系统的GBK编码表表示,如果调整了内码页,就会显示其他文字

#include <iostream>

int main()
{
    char strA[0xFF]{ "Hello张三" };    //单字节字符串声明
    std::cout << strA << std::endl;

}

3)宽字节字符串(wchar_t)

​ 一个wchar_t在内存中占用2个字节,因此\0在内存中占用为0000

​ 注:采用的编码表不同,则输出的内容不同

//宽字节字符串的声明(此时假定编译器采用UTF-16实现Unicode标准)
wchar_t strA[255]{L"Hello"};
const wchar_t* strB =L"Hello";

#include <iostream>
#include <locale>
int main()
{
	wchar_t wstr[0xFF]{ L"Hello张三" };  //宽字节字符串定义
	for (int x = 0; wstr[x]; x++)
	{
		std::cout << std::hex << (short)wstr[x] << std::endl;
	}
	setlocale(LC_ALL, "chs");  //控制台需要设置编码为unicode编码,才可以显示中文
	std::wcout << wstr << std::endl;
}

2、字符处理应用

1)C语言字符串的输入输出

//C语言的输入输出
char str[0xFF];
scanf("%s",str);

whcar_t wstr[0xFF];
wscanf(L"%s",wstr);
//英文字符串的输入输出示例
#define _CRT_SECURE_NO_WARNINGS
#include <locale>
int main()
{
	char Name[0x10];
	int age;
	printf("请输入你的名字:");
	scanf("%s", Name); //因为Name是一个数组,数组本事是一个指针,所以不需要再加取值运算符了
	printf("请输入你的年龄:");
	scanf("%d",&age);  //因为age是一个int类型的变量,所以需要加取值运算符&
	printf("您的名字是%s\n", Name);
	printf("您的年龄是%d\n", age);
}

//中文字符串的输入输出示例
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <locale>
int main()
{
	setlocale(LC_ALL, "chs");    //修改控制台的编码方式为unique编码
	wchar_t wstr[0x5];                       //数组定义宽字节字符串
	//wchar_t* wstr1 = new wchar_t[0x5];     //指针定义宽字节字符串
	wprintf(L"请输入你的名字:");
	wscanf(L"%s", wstr);
	wprintf(L"你的名字是【%s】", wstr);

}

2)C语言更安全的输入输出

​ 直接使用scanf()函数有可能导致数组越界,最终造成栈溢出或堆溢出(使用new的方式申请的数组可能造成堆溢出)

//C语言更安全的输入输出
char str[0xFF];
scanf_s("%s",str,可接受最大字符值);  //限制输入的字符串的可接受长度

wchar_t wstr[0xFF];
wscanf_s(L"%s",wstr,可接受的最大字符值)
//C语言更安全的输入应用
#include <iostream>
#include <locale>
int main()
{
	setlocale(LC_ALL, "chs");    //修改控制台的编码方式为unique编码
	wchar_t wstr[0x5];                     
	wprintf(L"请输入你的名字:");
	wscanf_s(L"%s", wstr,5);          //使用wscanf_s限定字符串输入
	wprintf(L"你的名字是【%s】", wstr);
}
//当输入的字符超过了接受的字符长度,程序不会报错,但不会输出内容

3)C++的输入

补充:std::cout遇到char类型指针时,会优先当作字符串处理,不显示地址

//std::cout遇到char类型指针时,会优先当作字符串处理
#include <iostream>

int main()
{
	int NameA[0x10]{};
	char NameB[0x10]{"张三"};
	std::cout << "NameA的地址:" << NameA << std::endl; //直接输入数组名,输出的为数组的地址
	std::cout << "NameB的地址:" << NameB << std::endl;//将字符串数组优先当作了字符串处理
	printf("%s",NameB);
}

//C++的输入输出语法(和字符的输入一样)
char str[0xFF];
std::cin>>str;

wchar_t wstr[0xFF];
std::cin>>wstr;

注:使用wchar时,在控制台输出内容,一定要先设置字符集
//C++的输入输出案例演示
#include <iostream>
#include <locale>
int main()
{
	setlocale(LC_ALL, "chs");  //使用wchar时,在控制台输出内容,一定要先设置字符集
	wchar_t wstr[0xFF];
	std::wcout << L"请输入你的名字:";
	std::wcin >> wstr;
	std::wcout << wstr << std::endl;
	std::cout << "输入的字符串长度为:" << wcslen(wstr) << std::endl;  //计算字符串的长度
}

4)计算字符串的长度(wcslen)

//计算字符串的长度
char str[0x10];
std::cout<<strlen(str);

wchar_t wstr[0xFF]; 
std::cout<<wcslen(str);

注:在char类型中输入中文,无法通过strlen计算出准确长度,但是wchar可以计算中英文混合的长度
#include <iostream>
#include <locale>
int main()
{
	setlocale(LC_ALL, "chs");  //使用wchar时,在控制台输出内容,一定要先设置字符集
	wchar_t wstr[0xFF];
	std::wcout << L"请输入你的名字:";
	std::wcin >> wstr;
	std::wcout << wstr << std::endl;
	std::cout << "名字长度为:" << wcslen(wstr) << std::endl;  //计算字符串的长度

	char str[0x10]{};
	std::cout << "请输入你的性别:";
	std::cin >> str;
	std::cout << str << std::endl;
	std::cout << "性别长度为:" << strlen(str) << std::endl;  //计算字符串的长度
}

5)字符串练习题一

​ 设计一个程序,接受用户输入的字符串,并且求这个字符串的长度,使用char类型来接受用户输入,且仅能输入数字和英文,禁止使用strlen,比如用户输入123abc,计算结果为6。

6)字符串练习题二

​ 设计一个程序,接受用户输入的字符串,并且求这个字符串的长度,使用char类型来接受用户输入,用户可以输入中文和英文以及数字。假设用户输入"你好123",输出计算结果为5。

3、指针和结构体

1)通过指针访问自定义数据类型

​ 将结构体声明和给变量改名融合到了一起,定义一个指针类型,更改名字为PRole,PRole就相当于Role的指针,可以通过typedef定义多个不同的名字,但是必须加*号。

​ 在访问结构体的成员变量时,可通过偏移符号(->)进行访问。

#include <iostream>

typedef struct Role  //自定义一个结构体,结构体为Role类型
{
	int Hp;
	int Mp;
}*PRole,*XRole,*_Role;   //可以通过typedef定义多个不同的名字,但是必须加*,不加*就表示给Role修改了名字,加*表示创建了一个Role类型指针的名字

int main()
{
	Role user;    //创建了Role类型的结构体变量user
	PRole puser = &user;  //创建了Role类型的指针puser,指向了user

	user.Hp = 50;
	user.Mp = 50;
	std::cout << "初始用户血量为:" << user.Hp << std::endl;
	std::cout << "初始用户蓝量为:" << user.Hp << std::endl;
	//(*puser).Hp=500   //通过实体访问成员变量
	puser->Hp = 500;   //通过指针方式访问结构体的成员变量:通过偏移符号
	puser->Mp = 500;

	std::cout << "修改后用户血量为:" << user.Hp << std::endl;
	std::cout << "修改后用户蓝量为:" << user.Hp << std::endl;
}

2)通过汇编分析通过指针访问结构体

puser->HP是计算HP和&user地址的起始差距是多少

#include <iostream>

typedef struct Role    //发现在定义结构体时,没有汇编代码。说明程序编译完成后,在内存中是不存在的
{
	int Hp;
	int Mp;
}*PRole,*XRole,*_Role;  

int main()
{
00C42540  push        ebp  
00C42541  mov         ebp,esp  
00C42543  sub         esp,0E0h  
00C42549  push        ebx  
00C4254A  push        esi  
00C4254B  push        edi  
00C4254C  lea         edi,[ebp-20h]  
00C4254F  mov         ecx,8  
00C42554  mov         eax,0CCCCCCCCh  
00C42559  rep stos    dword ptr es:[edi]  
00C4255B  mov         eax,dword ptr [__security_cookie (0C4C004h)]  
00C42560  xor         eax,ebp  
00C42562  mov         dword ptr [ebp-4],eax  
00C42565  mov         ecx,offset _65DADCAD_通过指针访问结构体@cpp (0C4F029h)  
00C4256A  call        @__CheckForDebuggerJustMyCode@4 (0C41384h)  
	Role user;    
	PRole puser = &user;  
00C4256F  lea         eax,[user]    //eax=user地址,即eax=[user]
00C42572  mov         dword ptr [puser],eax  //puser的内存地址=user的地址,即[puser]=[user]

	user.Hp = 50;
00C42575  mov         dword ptr [user],32h    //是直接向user的内存地址中写入32h,即10进制的50
	user.Mp = 50;
00C4257C  mov         dword ptr [ebp-0Ch],32h  
	std::cout << "初始用户血量为:" << user.Hp << std::endl;
00C42583  mov         esi,esp  
00C42585  push        offset std::endl<char,std::char_traits<char> > (0C4103Ch)  
00C4258A  mov         edi,esp  
00C4258C  mov         eax,dword ptr [user]  
00C4258F  push        eax  
00C42590  push        offset string "\xb3\xf5\xca\xbc\xd3\xc3\xbb\xa7\xd1\xaa\xc1\xbf\xce\xaa\xa3\xba" (0C49B30h)  
00C42595  mov         ecx,dword ptr [__imp_std::cout (0C4D0D8h)]  
00C4259B  push        ecx  
00C4259C  call        std::operator<<<std::char_traits<char> > (0C411AEh)  
00C425A1  add         esp,8  
00C425A4  mov         ecx,eax  
00C425A6  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A0h)]  
00C425AC  cmp         edi,esp  
00C425AE  call        __RTC_CheckEsp (0C41294h)  
00C425B3  mov         ecx,eax  
00C425B5  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A4h)]  
00C425BB  cmp         esi,esp  
00C425BD  call        __RTC_CheckEsp (0C41294h)  
	std::cout << "初始用户蓝量为:" << user.Hp << std::endl;
00C425C2  mov         esi,esp  
00C425C4  push        offset std::endl<char,std::char_traits<char> > (0C4103Ch)  
00C425C9  mov         edi,esp  
00C425CB  mov         eax,dword ptr [user]  
00C425CE  push        eax  
00C425CF  push        offset string "\xb3\xf5\xca\xbc\xd3\xc3\xbb\xa7\xc0\xb6\xc1\xbf\xce\xaa\xa3\xba" (0C49B44h)  
00C425D4  mov         ecx,dword ptr [__imp_std::cout (0C4D0D8h)]  
00C425DA  push        ecx  
00C425DB  call        std::operator<<<std::char_traits<char> > (0C411AEh)  
00C425E0  add         esp,8  
00C425E3  mov         ecx,eax  
00C425E5  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A0h)]  
00C425EB  cmp         edi,esp  
00C425ED  call        __RTC_CheckEsp (0C41294h)  
00C425F2  mov         ecx,eax  
00C425F4  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A4h)]  
00C425FA  cmp         esi,esp  
00C425FC  call        __RTC_CheckEsp (0C41294h)  

	puser->Hp = 500;   
00C42601  mov         eax,dword ptr [puser]  //eax=puser的地址,即eax=[puser],即eax=[user]
00C42604  mov         dword ptr [eax],1F4h  //eax的地址=1F4h,即[user]=1F4h,把user的地址中放入50即[user]=500
	puser->Mp = 500;
00C4260A  mov         eax,dword ptr [puser]  
00C4260D  mov         dword ptr [eax+4],1F4h  

	std::cout << "修改后用户血量为:" << user.Hp << std::endl;
00C42614  mov         esi,esp  
00C42616  push        offset std::endl<char,std::char_traits<char> > (0C4103Ch)  
00C4261B  mov         edi,esp  
00C4261D  mov         eax,dword ptr [user]  
00C42620  push        eax  
00C42621  push        offset string "\xd0\xde\xb8\xc4\xba\xf3\xd3\xc3\xbb\xa7\xd1\xaa\xc1\xbf\xce\xaa\xa3\xba" (0C49B58h)  
00C42626  mov         ecx,dword ptr [__imp_std::cout (0C4D0D8h)]  
00C4262C  push        ecx  
00C4262D  call        std::operator<<<std::char_traits<char> > (0C411AEh)  
00C42632  add         esp,8  
00C42635  mov         ecx,eax  
00C42637  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A0h)]  
00C4263D  cmp         edi,esp  
00C4263F  call        __RTC_CheckEsp (0C41294h)  
00C42644  mov         ecx,eax  
00C42646  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A4h)]  
00C4264C  cmp         esi,esp  
00C4264E  call        __RTC_CheckEsp (0C41294h)  
	std::cout << "修改后用户蓝量为:" << user.Hp << std::endl;
00C42653  mov         esi,esp  
00C42655  push        offset std::endl<char,std::char_traits<char> > (0C4103Ch)  
00C4265A  mov         edi,esp  
00C4265C  mov         eax,dword ptr [user]  
00C4265F  push        eax  
00C42660  push        offset string "\xd0\xde\xb8\xc4\xba\xf3\xd3\xc3\xbb\xa7\xc0\xb6\xc1\xbf\xce\xaa\xa3\xba" (0C49B70h)  
00C42665  mov         ecx,dword ptr [__imp_std::cout (0C4D0D8h)]  
00C4266B  push        ecx  
00C4266C  call        std::operator<<<std::char_traits<char> > (0C411AEh)  
00C42671  add         esp,8  
00C42674  mov         ecx,eax  
00C42676  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A0h)]  
00C4267C  cmp         edi,esp  
00C4267E  call        __RTC_CheckEsp (0C41294h)  
00C42683  mov         ecx,eax  
00C42685  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0C4D0A4h)]  
00C4268B  cmp         esi,esp  
00C4268D  call        __RTC_CheckEsp (0C41294h)  
}

因内存对其问题,通过sizeof()计算结构体大小时,结果不一定等于每个变量的类型大小之和

#include <iostream>

typedef struct Role  ////结构体中定义了3个成员变量
{
	short sex;      //进行了内存对其
	int Hp;
	int Mp;
}*PRole;  

typedef struct Weapon  //结构体中定义了4个成员变量
{
	short A;
	short B;
	int act;
	int sword;
}*PWeapon;

int main()
{
	std::cout << "结构体Role的大小为:" << sizeof(Role) << std::endl;
	std::cout << "结构体Role的大小为:" << sizeof(Weapon) << std::endl;
}

4、指针安全
//野指针问题
#include <iostream>

int main()
{
	int* p;
	{
		int* a = new int[50];  //a是局部变量,
		p = a;
		p[2] = 255;            //相当于a[2]=255,但是a的内存空间没有释放
	}
	p[0] = 255;  //所以还可以读出数据,这就是野指针
	std::cout << p[0] << std::endl;
}

通过智能指针解决上述问题:

//唯一智能指针解决野指针问题
#include <iostream>

int main()
{
	int* p;
	{
		std::unique_ptr<int[]>ptrA{std::make_unique<int[]>(50)};
		ptrA[10] = 100;
		p = ptrA.get();
		std::cout << p[10] << std::endl;           //相当于输入a[10]=255,此时ptrA还没有释放内存地址,可以正常输出
	}
	std::cout << p[10] << std::endl;  //此时ptrA以及成功释放,无法正常输出a[10]的值
}

5、项目设计:完善麟江湖技能系统

1)技能系统

​ 需求:本次重新设计麟江湖的技能系统,技能是在游戏设计之处已经设计好的,游戏设计了十一种技能,属性如下:

技能 消耗内力 消耗怒气值 攻击力 冷却
普通工具 0 0 10+基础攻击 0
大力金刚指 10 0 50+基础攻击 1
云龙三观 10 0 60+基础攻击 1
一阳指 30 0 2*基础攻击 3
迎风破浪 30 0 300 3
八卦掌 50 0 5*基础攻击 4
六合八荒 50 0 500 4
仙人指路 100 0 10*基础攻击 6
横扫千军 100 0 50+2*基础攻击 6
气吞山河 0 100 500+5*基础攻击 0
秋风到法 0 100 200+10*基础攻击 0

2)项目设计

​ 设计需求:每个角色随着等级不同,最少拥有一个技能,即普通攻击;最多拥有5个技能,技能拥有等级,技能等级每提升1级,角色的最终伤害提升15%,设计角色的属性面板,能够显示角色的属性和技能,角色属性根据需求自己发挥。要求面板上能够显示技能的名称,尝试设计释放技能,释放技能显示技能名称

#include <iostream>

struct Skill                //定义技能结构体
{
	char Name[48]; //技能名称
	int Mpp; //内力消耗
	int Spp; //怒气
	int Act; //攻击力
	int ActB; //翻倍攻击
	int CoolDown; //冷却时间
};

struct USkill        //定义用户当前的技能
{
	Skill* skill{};
	int lv;
	int cooldown;
	bool buse{};
};

typedef struct Role
{
	char Name[48];
	int HP;
	int MaxHp;
	int MP;
	int MaxMp;
	int Sp;     //怒气值
	int MaxSp;
	int Act;    //普通攻击力
	USkill skills[5];   //每个角色最多5技能
}*PROLE;

int main()
{
	Skill AllSkills[11]
	{
		{"普通攻击",0,0,10,1,0},
		{"大力金刚指",10,0,50,1,1},
		{"云龙三观",10,0,60,1,1},
		{"一阳指",30,0,0,2,3},
		{"迎风破浪",30,0,300,0,3},
		{"八卦掌",50,0,0,5,4},
		{"六合八荒",50,0,500,0,4},
		{"仙人指路",100,0,0,10,6},
		{"横扫千军",100,0,50,2,6},
		{"气吞山河",0,100,500,5,0},
		{"秋分刀法",0,100,200,0,0}
	};

	PROLE User = new Role
	{
		"郝英俊",
		1000,1000,1000,1000,0,100,100,
		{
			{&AllSkills[0],0,0,true},
			{&AllSkills[1],0,0,true},
			{&AllSkills[2],0,0,false},
			{&AllSkills[3],0,0,false},
			{&AllSkills[10],0,0,true}
		}
	};

	PROLE Monster = new Role
	{
		"凹凸曼",
		1000,1000,1000,1000,0,100,100,
		{
			{&AllSkills[0],0,0,true},
			{&AllSkills[1],0,0,true},
			{&AllSkills[2],0,0,true},
			{&AllSkills[4],0,0,true},
			{&AllSkills[10],0,0,true}
		}
	};

	std::cout << "角色姓名:"<<User->Name << std::endl;
	std::cout << "生命:" << User->HP << "/" << User->MaxHp << std::endl;
	std::cout << "内力:" << User->MP << "/" << User->MaxMp << std::endl;
	std::cout << "怒气:" << User->Sp << "/" << User->MaxSp << std::endl;
	std::cout << "基本攻击:" << User->Act << std::endl;

	for (auto skill : User->skills)
	{
		if (skill.buse)
			std::cout
			<<"技能名称 ["<<skill.skill->Name<<"]"
			<< "消耗MP " << skill.skill->Mpp
			<< "消耗SP " << skill.skill->Spp
			<< "附加攻击 " << skill.skill->Act
			<< "翻倍攻击 " << skill.skill->ActB
			<< "冷却 " << skill.skill->CoolDown
			<< "技能等级 " << skill.lv
			<< std::endl;

	}
}

热门相关:跟总裁假结婚的日子   青莲剑说   龙印战神   龙印战神   厨道仙途