[2] C++复合数据类型编程
week10
入栈出栈示意图
for循环出入栈
Day1
函数的基本使用
函数作用 :
- 把功能进行拆分,保证main函数有可读性
- 函数把代码进行封装,保证重用,减少写代码的难度
函数注意点 : 函数不允许嵌套 , 函数的定义需要在main外边
函数定义的语法 : 返回值 函数名(函数参数){函数体} void FunName (FunParams) {FunMain}
函数调用 : 函数名(参数) FunName(Params)
函数返回值 :
- 如果函数有返回值,可以用一个相同类型的变量接收 。 不使用函数的返回值,可以不用变量去接收。
- 如果函数的返回值是void(没有返回值) 不需要用变量接收。
函数参数 : 形式参数(形参) 实际参数(实参)
形式参数
- 在函数的声明或者定义(实现)小括号中出现的都是形式参数,在函数调用的小括号中出现的都是实际参数
- 函数调用中,把实参的值传给形参,再去函数体中执行逻辑(值传递)
默认参数
void fun(int i = 10;); 函数声明
fun(int i){}; 函数定义 默认写在函数声明的时候
值传递 在函数内部对变量进行更改之后,不会影响函数外部的变量(简单类型【int float】和字符串)
如果是数组,那么使用的就不是值传递,在函数内部更改之后会影响函数外部的值,数组使用的是地址传递
函数重载 Overload 函数重写 Override
函数重载的定义 :
- 函数名相同,函数参数的个数,类型或者顺序不同
- 函数返回值并不是函数重载的判断标准
- 当默认参数和函数重载同时存在的时候避免二义性
- 函数重载的本质 命名倾轧
Day2
文件读写
// 把内存中的数据保存到硬盘中的某个文件中
// 包含头文件(<fstream>) file stream
// 定义变量 ofstream output file stream
// 打开文件 bianliang.open()
// 写内容 << 把内容写到ofstream变量中
// 关闭文件 close()
//ofstream Out;
//// 打开文件,如果文件不存在会自动创建
//// 文件的路径 1)如果从VS中运行,文件在工程的根目录 2)单独跑exe,那么文件会生成在exe的同级目录中
//// D:\CPP\Week2\Day1\Info.txt 绝对路径当工程换了路径之后可能会出问题,推荐只写文件名
////
//// 默认值 是 out 不保留原来文件中的内容
//// app 表示 append 追加
// Out.open("Info.txt", ios_base::out | ios_base::binary);
//
//FStudent Student{ 10010, "张三", 185.f };
//// 结构体的保存
// // 1)明文 结构体不能直接保存 按照一定的顺序保存结构体中的属性(成员)
//// Out << Student.ID << " " << Student.Name << " " << Student.Height << endl;
// // 2)密文 二进制的方式去写入结构体(打开文件看不懂)
// // 打开文件时需要告诉系统按照二进制的方式打开 ios_base::out | ios_base::binary
// // write((char*)&Var, sizeof(Var))
//Out.write((char*)&Student, sizeof(Student));
//Out.close();
#pragma region 文件写复习
//Students[0] = { 10010, "张三", true };
//Students[3] = { 10011, "李四", true };
//cout << "输入需要保存的文件名:";
//string FileName;
//cin >> FileName;
//SaveFile(FileName); // SaveFile("Info.txt");
#pragma endregion
#pragma region 文件读
// // 包含头文件 定义变量 打开文件 读内容 关闭文件
// ifstream In; // input file stream
// In.open("Info.txt");
// string Str;
//
//#pragma region 不同的读取方式
// // In >> Str; // 遇到空格自动停止
// /* while (In >> Str)
// {
// cout << Str << endl;
// }*/
//
// // getline(In, Str); // 读取一行
// /*while (getline(In, Str))
// {
// cout << Str << endl;
// }*/
//
// //// 其他读取方式1
// //char Content[1024];
// //while (In >> Content)
// //{
// // cout << Content << endl;
// //}
//
// // 其他读取方式2
// // In.getline()
// // 第二个参数,读取长度如果设置的不对,读不出来内容,sizeof(变量)
// /*char Content[1024];
// while (In.getline(Content, sizeof(Content)))
// {
// cout << Content << endl;
// }*/
//
// //// 其他读取方式3 一个字符一个字符的读
// //char C = In.get(); // C默认值一般是先读文件的一个字符
// //// EOF // 表示End Of File
// //while (C != EOF)
// //{
// // cout << C;
// // C = In.get(); // 读下一个字符,赋值给C
// //}
//#pragma endregion
//
//
//
// In.close();
#pragma endregion
#pragma region 文件读写案例
// 读写结构体
//FStudent Student1 = { 10010, "王柳", true };
//ofstream Out;
//Out.open("Stu1.txt");
//Out << Student1.ID << " " << Student1.Name << " " << Student1.bIsMale << endl;
//Out.close();
//ifstream In;
//In.open("Stu1.txt");
//// 10010 张三 1
//FStudent Student2;
//In >> Student2.ID >> Student2.Name >> Student2.bIsMale;
//In.close();
//cout << Student2.ID << ":" << Student2.Name << " " << Student2.bIsMale << endl;
// 读写字符串数组
//string Names[5]{ "张三", "里斯1", "王 五", "赵六", "朱琪" };
//ofstream Out;
//Out.open("Names.txt");
//for (int i = 0; i < size(Names); ++i)
//{
// Out << Names[i] << endl;
//}
//Out.close();
//string Names1[5]{};
//ifstream In;
//In.open("Names.txt");
//for (int i = 0; i < size(Names1); ++i)
//{
// // In >> Names1[i]; // 如果字符串本身带空格,会读乱
// getline(In, Names1[i]);
//}
//In.close();
//for (int i = 0; i < size(Names1); ++i)
//{
// cout << Names1[i] << endl;
//}
// 读写结构体数组
//Students[0] = { 10010, "张三", true };
//Students[1] = { 10015, "赵六", false };
//Students[3] = { 10011, "朱琪", true };
//ofstream Out;
//Out.open("Structs.txt");
//int Count = 0;
//for (int i = 0; i < size(Students); ++i)
//{
// if (Students[i].ID != 0)
// {
// Out << Students[i].ID << " " << Students[i].Name << " " << (Students[i].bIsMale ? "男" : "女") << endl;
// Count++; // 每成功存一条数据,计数+1
// }
//}
//Out.close();
//ifstream In;
//In.open("Structs.txt");
//FStudent Students1[10]{};
///*for (int i = 0; i < Count; ++i)
//{
// In >> Students1[i].ID >> Students1[i].Name >> Students1[i].bIsMale;
//}*/
//// 不知道有多少条数据
//int Index = 0;
//string Sex = "";
//while (In >> Students1[Index].ID >> Students1[Index].Name >> Sex)
//{
// Students1[Index].bIsMale = (Sex == "男" ? true : false);
// // Students1[Index].bIsMale = Sex == "男";
// Index++; // 每成功读取一条,索引向后偏移一个单位
//}
//In.close();
// 读写数组(存储个数)
//Students[0] = { 10010, "张三", true };
//Students[1] = { 10015, "赵六", false };
//Students[2] = { 10018, "赵六w", false };
//Students[3] = { 10011, "朱琪", true };
//ofstream Out;
//Out.open("Structs.txt");
//int CountOfValidIndex = 0;
//// 存文件的时候第一行应该存有效个数
//for (int i = 0; i < size(Students); ++i)
//{
// if (Students[i].ID != 0)
// {
// CountOfValidIndex++;
// }
//}
//Out << CountOfValidIndex << endl;
//for (int i = 0; i < size(Students); ++i)
//{
// if (Students[i].ID != 0)
// {
// Out << Students[i].ID << " " << Students[i].Name << " " << (Students[i].bIsMale ? "男" : "女") << endl;
// }
//}
//Out.close();
//ifstream In;
//In.open("Structs.txt");
//// 首先读取存储的有效个数
//int Count1 = 0;
//In >> Count1;
//
//FStudent Students1[10]{};
//if (Count1 > 0)
//{
// for (int i = 0; i < Count1; ++i)
// {
// string Str = "";
// In >> Students1[i].ID >> Students1[i].Name >> Str;
// Students1[i].bIsMale = Str == "男";
// }
//}
//In.close();
//cout << "Hello" << endl;
#pragma endregion
#pragma region 二进制文件的读
//FStudent Student{ 10018, "", false};
//// Student.Name = "三点水";
//// 如果结构体中使用了char[],赋值的时候需要使用特殊的形式
//strcpy_s(Student.Name, "三点水");
//ofstream Out;
//Out.open("Student.sav", ios_base::out | ios_base::binary);
//Out.write((char*)&Student, sizeof(Student));
//Out.close();
//ifstream In;
//In.open("Student.sav", ios_base::in | ios_base::binary);
//FStudent Student1;
//In.read((char*)&Student1, sizeof(Student1));
//In.close();
// cout << Student1.ID << " " << Student1.Name << " " << Student1.bIsMale << endl;
// 使用二进制进行读写的时候 ,如果有string类型,F5运行会崩溃,推荐使用Ctrl+F5
// 使用二进制读写保存一个数组
/*int Nums[]{ 1,2,3,4,5,6 };
ofstream Out;
Out.open("Nums.sav", ios_base::out | ios_base::binary);
Out.write((char*)Nums, sizeof(Nums));
Out.close();
ifstream In;
In.open("Nums.sav", ios_base::in | ios_base::binary);
int Nums1[6]{};
In.read((char*)Nums1, sizeof(Nums1));
In.close();
for (int i = 0; i < size(Nums1); ++i)
{
cout << Nums1[i] << endl;
}*/
#pragma endregion
#pragma region 二进制文件读写案例
//// 使用二进制读写结构体数组
//FStudent Students[]{ {10010, "龘龘", true}, {10011, "三点水", false}, {10015, "Lucy", false} };
//ofstream Out;
//Out.open("Info.sav", ios_base::out | ios_base::binary);
//for (int i = 0; i < size(Students); ++i)
//{
// Out.write((char*)&Students[i], sizeof(Students[i]));
//}
//Out.close();
//ifstream In;
//In.open("Info.sav", ios_base::in | ios_base::binary);
//FStudent Students1[3]{};
//for (int i = 0; i < size(Students1); ++i)
//{
// In.read((char*)&Students1[i], sizeof(Students1[i]));
//}
//In.close();
//cout << "Hello" << endl;
#pragma endregion
多文件编程
一般情况下文件是分组 .h .cpp Save.h Save.cpp 有的时候只有.h,没有.cpp
.h中放结构体定义,枚举定义,变量的声明,函数的声明
.cpp中一般放实现文件
Day4
指针
二级指针
指针解引用
值传递解析 :
函数指针
指针基本语法及使用注意点
int Num = 100;
int* pNum = &Num;
cout << sizeof(Num) << " " << Num << endl;
cout << sizeof(pNum) << " " << pNum << endl;
// 同一个程序中的同一个变量,在每次启动的时候,地址99.99%是不一样的
// 同一个程序,同一次启动未关闭之前,同一个变量的内存地址是一样
// 同一个程序的同一个变量,在不同的机器上内存地址也是不一样的
// 存储变量的时候,不能存储地址,存储的值(???SaveGame类能不能存储对象【Object Actor Component】)
//// 指针类型和原始值的类型需要匹配(严格匹配)
//char Ch = '9';
//// pNum = &Ch;
//float Num1 = Num;
//// float* pNum1 = &Num;
//// 关于指针书写的格式
//int* pNum1 = &Num; // 推荐第一种格式
//int *pNum2 = &Num;
//int * pNum3 = &Num;
//// 同一个符号可能会有不同的运算 & (按位与 逻辑与 取地址) *(指针 乘法 解引用) << >> ->运算符重载
//string Str = "Hello";
//string* pStr = &Str;
//cout << Str << " " << pStr << endl;
//// 在32位系统下string的大小是28个字节,在64位系统下大小40个字节
//cout << sizeof(Str) << " " << sizeof(pStr) << endl;
//int Num = 100;
//int* pNum = &Num;
//// Num += 100;
//// 指针可以通过解引用操作对应的值(在指针变量前+*,取到内存对应的值)
//*pNum = 200;
//cout << Num << endl;
无效指针的几种情况
#pragma region 无效指针的几种情况
// 空指针 nullptr
//int* pNum = nullptr; // 定义指针变量时不确定是什么默认值的时候直接给空指针
//if (pNum != nullptr) // 如果pNum不是空指针 if(pNum)
//{
// cout << *pNum << endl;
//}
/*string* pStr = nullptr;
cout << *pStr << endl;*/
// 现象1、指针如果是Nullptr,直接打印指针没有问题(不同系统可能会有不同的结果),nullptr解引用 百分之百崩溃
// 结论1、使用指针之前需要先判断是不是空指针,如果是就停止运行,如果不是再继续
//
//
// 野指针(使用没有初始化的指针 使用赋值错误的指针 new delete)
// 使用没有初始化的指针
//MyStruct MS;
//cout << MS.pNum << endl; // CCCCCC
// cout << *MS.pNum << endl;
// 现象1、使用未经初始化的指针,解引用百分之百崩溃
// 结论1、指针必须初始化,如果没有合适的值,初始化为nullptr
// 使用赋值错误的指针
/*int* pTemp = (int*)0x00000066A852F8C4;
cout << pTemp << endl;
cout << *pTemp << endl;*/
// 现象2、指针赋值的时候给一个真实存在的地址,解引用99.99999%会奔溃
// 结论2、给指针赋值的时候不要直接赋值地址
// 动态内存分配 new delete
// 在堆区开辟4个字节的空间,存储100,然后把这块空间的首地址给到pNum
// 使用注意点 new 和 delete必须成对出现 delete之后指针置空
// 现象3、delete之后再去使用指针会出现奇怪的现象(可能会无效,可能会崩溃)
// 揭露3、delete之后指针必须立刻置空
//int* pNum = new int(100);
//string* pStr = new string("火星时代");
//
//delete pNum; // 告诉系统,申请4个字节的空间可以回收
//pNum = nullptr;
//delete pStr; //
//pStr = nullptr;
//if (pStr != nullptr)
//{
// cout << *pStr << endl;
//}
#pragma endregion
指针使用规范
#pragma region 指针使用规范
// 使用指针之前需要先判断是不是空指针,如果是就停止运行,如果不是再继续
// 指针必须初始化,如果没有合适的值,初始化为nullptr
// 给指针赋值的时候不要直接赋值地址
// delete之后指针必须立刻置空
// 指针使用规范1、指针在使用之前必须赋值(不确定可以赋值nullptr),必须判断是否为空指针
// 指针使用规范2、new和delete必须成对出现,delete之后指针必须立刻置空
// 没有new直接delete
/*int Num = 100;
int* pNum = &Num;
delete pNum;
pNum = nullptr;*/
// new 和 delete不是成对出现
/*int* pNum = new int(50);
delete pNum;
pNum = nullptr;*/
// delete pNum;
// nullptr NULL // 在Cpp程序中使用nullptr,不要使用NULL
// new/delete malloc free // 在Cpp程序中使用前者,不要使用后者
#pragma endregion
指针作为函数参数
#pragma region 指针作为函数参数
//int WNum1 = 10;
//int WNum2 = 50;
//// 写法2(简单推荐)
//// pNum1 = &WNum1 // 把实参的地址传递给形参,函数体中直接使用实参的地址
//Swap(&WNum1, &WNum2);
//cout << WNum1 << " " << WNum2 << endl;
/*string Str1 = "Hello";
string Str2 = "World";
Swap(&Str1, &Str2);
cout << Str1 << " " << Str2 << endl;*/
#pragma endregion
指针作为函数返回值
#pragma region 指针作为函数返回值
/*string* pTemp = AppendStr1();
cout << *pTemp << endl;
delete pTemp;
pTemp = nullptr;*/
/*string Str = "火星时代";
string Str1 = AppendStr1(Str);
cout << Str1 << endl;*/
// 函数返回值是指针,不允许返回局部变量
#pragma endregion
指针作为全局变量的注意点
#pragma region 指针作为全局变量的注意点
// 如果指针变量是一个全局变量,禁止在某一个时间段指向局部变量(局部变量销毁后,全局变量可能存在不确定行)
/*pStr = new string("World");
cout << "函数外部" << pStr << endl;
cout << *pStr << endl;
Test();
cout << "函数外部" << pStr << endl;
cout << *pStr << endl;*/
#pragma endregion
结构体指针
#pragma region 结构体指针
// 结构体指针的基本使用
/*FStudent Stu1{ "张三", 10010 };
TestStudent(&Stu1);
cout << Stu1.ID << endl;*/
//FStudent* pStu1 = &Stu1;
//// -> 指针类型(结构体或者类)访问成员特殊符号
//cout << pStu1->Name << " " << pStu1->ID << endl;
// 结构体作为函数参数,在函数体内部更改,不会影响外面,如果外面想要受影响需要使用指针类型
// 结构体中包含指针类型
//int Num = 100;
//FStudent Stu1{ "张三", 10010, &Num};
//cout << Stu1.pID << endl;
//cout << *Stu1.pID << endl;
//FStudent* pStu1 = &Stu1;
//cout << *(pStu1->pID) << endl; // 第一种写法 指针结构体通过->访问成员
//cout << *(*pStu1).pID << endl; // 第二种写法 通过解引用把结构体指针变成结构体,再通过.访问成员,因为成员是指针类型,所以需要解引用货值值
// 结构体计算大小
cout << sizeof(MyStruct2)<< endl;
// 1、结构体空间不够的时候按照多少申请新的空间(所有基础属性中占用最大的)
// 2、当空间够用的时候先不申请,不够存储新属性的时候再申请
// 3、同一种类型(包含数组)可以进行空间挤压
// 4、如果结构体中有string类型,不是按照40去申请 按照8申请
// 推荐 无论是不是数组,想同类型写一起
#pragma endregion
Day5
指针数组补充
数组补充(内存结构和数组名解析)
#pragma region 数组补充(内存结构和数组名解析)
//int Nums[]{ 1,2,3,4,5,6 };
// cout << Nums << endl; // 数组名是一个内存地址
// 结论1 数组名是一个内存地址,指向索引为0的元素
// 数组 一开始定义的时候需要知道整个的空间的大小(个数),数组不能删除和增加(一整块内存)
// 数组通过索引访问 Nums[4] = 数组名 + 4 * 索引 数组访问越界(越界的地址可能不属于你的程序)
//for (int i = 0; i < size(Nums); ++i)
//{
// cout << &Nums[i] << " ";
//}
// TestArray(Nums);
// 结论 普通类型的数组当成函数参数传递的时候,地址传递
// 结论 数组作为函数参数的时候本质上就是一个指针 int Nums[] 和 int* Nums 不构成重载,因为本质上都是int*
/*int* pNums = Nums;*/
// 指针支持 ++ -- = += -=
//cout << &Nums[2] << " " << pNums + 2 << endl; // 地址
//cout << *(pNums + 2) << endl; // 值
//
//
//pNums--; // 指针向前偏移
//cout << Nums << " " << pNums << endl; // Nums的地址会比后面的地址大4个字节
//cout << *pNums << endl; // 垃圾值,相当于数组向前偏移4个字节,这块内存不一定属于程序,所以是个垃圾值
// 如果用指针存储数组,对数组进行 加法和减法的运算,运算数就是索引
/*pNums += 5;
pNums--;
cout << *pNums << endl;*/
/*for (int i = 0; i < size(Nums); ++i)
{
cout << *(pNums + i) << endl;
}*/
cout << endl;
#pragma endregion
指针数组
#pragma region 指针数组
// 数组指针指的是 数组中的元素是指针
//int Nums[]{ 1, 2, 3 };
//int Num = 50;
//int* pNums[2]{Nums, &Num};
////
//cout << *pNums[0] << endl;
//cout << *pNums[1] << endl;
//cout << pNums[0][1] << endl; // Nums[1]
// 指针数组本质上是一个普通类型的二维数组
//// 不规则二维数组
/*int Nums0[]{ 10, 20 };
int Nums1[]{ 100 };
int Nums2[]{ 1000, 2000, 3000 };
int Nums3[]{ 10000,20000,30000,40000 };
int Nums4[]{ 100000,200000 };
int* Nums[]{ Nums0, Nums1, Nums2, Nums3, Nums4 };*/
//size 只能判断数组 , 不能判断指针
// 指针数组能判断size , 但是指针不行
// 单独存储每一行有几个元素
/*int Lengths[]{ size(Nums0), size(Nums1) , size(Nums2) ,size(Nums3) ,size(Nums4) };
for (int i = 0; i < size(Nums); ++i)
{
for (int j = 0; j < Lengths[i]; ++j)
{
cout << Nums[i][j] << ",";
}
cout << endl;*/
}
#pragma endregion
半动态数组
#pragma region 半动态数组
// 数组长度不能是变量,需要是一个常量
/*const int Length = 10;
int Nums[Length]{};*/
//// 数组的new和delete
//int Length = 10;
//int* Nums = new int[Length] {};
//delete[] Nums;
//Nums = nullptr;
// 从命令行输入一个数决定数组的长度
// 挨个输入数组中每个元素的值
// 打印数组中的值
/*cout << "请输入数组元素的个数:";
int Count = 0;
cin >> Count;
if (Count > 0)
{
int* Nums = new int[Count] {};
for (int i = 0; i < Count; ++i)
{
cout << "请输入第" << (i + 1) << "个元素的值:";
cin >> Nums[i];
}
for (int i = 0; i < Count; i++)
{
cout << Nums[i] << " ";
}
delete[] Nums;
Nums = nullptr;
}*/
//// 模拟半动态数组
//int Count1 = 3;
//int* Nums = new int[Count1] {};
//for (int i = 0; i < Count1; ++i)
//{
// Nums[i] = i * 10;
//}
//int* Nums2 = new int[6]{};
//// Nums2 = Nums; // 只是赋值地址,等NUms销毁后,Nums2会有问题
//// 将原来数组的值挨个拷贝过来
//for (int i = 0; i < Count1; ++i)
//{
// Nums2[i] = Nums[i];
//}
//// 删除原来的数组的内存
//delete[] Nums;
//Nums = nullptr;
//// 继续赋值剩下的部分(Count1 - 1, 6)
//for (int i = Count1; i < 6; ++i)
//{
// Nums2[i] = i * 100;
//}
//// 打印新数组的值
//for (int i = 0; i < 6; ++i)
//{
// cout << Nums2[i] << " ";
//}
//delete[] Nums2;
//Nums2 = nullptr;
#pragma endregion
函数指针
int Add(int Num1, int Num2) { return Num1 + Num2; }
int Subtract(int Num1, int Num2) { return Num1 - Num2; }
int Multiply(int Num1, int Num2) { return Num1 * Num2; }
int Divide(int Num1, int Num2) { return Num1 / Num2; }
int Addd(int Num1, int Num2) {
return Num1 += Num2;
}
//事件分发器用了类似概念
int Add() { return 50; }
// pCalc两个数做运算的函数
int TestCalc(int Num1, int Num2, int (*pCalc)(int, int))
{
return pCalc(Num1, Num2);
}
int TestCalc1(int Num1, int Num2, int (*pCalc1)(int, int)) {
return pCalc1(Num1, Num2);
}
//声明
#pragma region 函数指针
// 函数指针 指向函数的指针
// auto 会根据变量的值(10)自动推导出变量的类型
// 用在 1)不知道类型的地方 2)类型名特别长的地方
//auto Num = Add; // int(*)(int, int)
//auto Num = 10;
// 结论 函数名就是一个函数指针,指向自己
// 写函数指针的步骤 函数声明拿过来 去掉; 将函数名替换成 (*VarName) 去掉形参名字
// int (*pAdd)() = Add; // = 后面是函数名,不要写成函数调用
// 函数指针的调用
/*cout << pAdd() << endl;
cout << pAdd1(10, 100) << endl;*/
// void (*pVoid)()
// pAdd = TestVoid; // 函数指针赋值的时候需要函数签名完全一致
/*int (*pAdd1)(int, int) = Add;
cout << pAdd1(10, 100) << endl;
pAdd1 = Multiply;
cout << pAdd1(10, 100) << endl;*/
//cout << TestCalc(100, 500, Add) << endl;
//cout << TestCalc(100, 500, Subtract) << endl;
//cout << TestCalc(100, 500, Multiply) << endl;
//cout << TestCalc(100, 500, Divide) << endl;
cout << TestCalc1(1, 2, Addd);
#pragma endregion