十四、指针和引用(四)
十四、指针和引用(四)
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;
}
}