C++笔记

黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili

12.4  : 学到了多态就没有往下面学了, 慢慢学吧

 

 

 

 

 

#include <iostream>
namespace namespaceName //我们使用了 namespace 定义了一个命名空间 namespaceName。
	using namespace...; //命名空间引入
using namespace std;	// std命名空间引入.我们常用的输入和输出 函数 都是定义在 std 命名空间中的,因此,我们需要使用输入和输出,必须要引入 std 命名空间
using std::cout;		//使用 using 限定符,引入 std 命名空间指定内容cout

#include <string> //c++中没有自带string数据类型,因此如果要用string数据类型,要用头文件. 后续就可以string name,否则用string会报错

//普及章节
// 0.1
// C++ 其实是在 C 语言的基础上增加了新特性,因此,取名为 C++。 用 C++ 编写的程序可读性好,生成的代码质量高,运行效率仅比汇编语言慢 10%~20%。
// 区分 C 语言,如内联函数、函数重载、名字空间、更灵活、方便的内存管理(new、delete)、引用。
// C++应用领域:服务 器端开发, 游戏, 虚拟现实, 数字图像处理, 科学计算, 网络软件, 操作系统,嵌入式系统等

// 0.2 C++语言编译链接教程
//  C++语言开发环境:Windows 本身就自带 C++ 语言的运行环境,因此,为了开发 C++ 语言,我们只需要安装一个 C++ 语言的开发工具即可。
//  我们编写好 C++ 语言 的程序后,需要运行 C++ 程序,必须要经过编译与链接的过程。=在编辑器上点击 编译并运行 按钮
//  将 C++ 代码转换成 CPU 能够识别的二进制指令,也就是将代码加工成 .exe 程序的格式。这个工具是一个特殊的软件,叫做编译器(Compiler)。编译也可以理解为 “翻译”
//  C++ 的编译器有很多种:  Windows 下是Visual C++,它被集成在 Visual Studio 中. Linux 自带 G++.
//  					Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中.

// .c 	源代码文件
// .C 	.cc 或 .cxx 为后缀的文件,源代码文件另有 .cpp、 和 .c++。
// .a 	由目标文件构成的档案库文件
// .h 	程序所包含的头文件.  .h 里面只有声明,没有实现. 有using namespace std.
// .hpp 程序所包含的头文件.  .hpp 里声明实现都有,可以减少 .cpp 的数量. 无using namespace std.
// .i 	已经预处理过的 C 源代码文件
// .ii 	已经预处理过的 C++ 源代码文件
// .m 	Objective-C 源代码文件
// .o 	编译后的目标文件   对于 Visual C++,目标文件的后缀是 .obj,对于 G++,目标文件的后缀是 .o
// .s 	汇编语言源代码文件

// C++五大标准:98,11,14,17,20. 不同标准表示不同年份

//静态库和静态库
// 在 Linux 中,库文件分成静态库和共享库两种。静态库以.a 作为后缀名,共享库以.so 结尾。所有库都是一些函数打包后的集合,
// 差别在于静态库每次被调用都会生成一个副本,而共享库则只有一个副本,更省空间。(通过l -lh查看)
// 静态库用的时候方便,动态库省内存.
// 静态库最后都会被集成到可执行文件中,动态库 不会
// 类比windows下的 dll 与lib

// g++ main.cpp -Iinclude -lswap -Lsrc -o dyna_or_static, 这条命令到低调用的是动态库?还是静态库呢?
// 应该是同时有动态和静态库存在时优先链接动态库
// 这里的例子做的不太好,动态静态库做好以后,应该单独放出来编译,在原位置编译可能编译的是源码
// 静态库,程序编译后,直接链接,生成最终文件,如果改了一部分主文件源码,需要重新链接。
// 动态库,程序运行在内存后,再链接库文件,如果修改了源码,重新编译链接部分不涉及动态库文件,所以更加方便。
// 相对而言,静态库链接的文件也更大一些,毕竟是运行前就链接了,相当于提前拷贝了源码到主程序。

// vs才是ide,vscode其实算不上IDE
//  vscode就是个编辑器,加了各种插件才是IDE

/* 终端命令
g++ helloworld.cpp        打开helloworld
g++ helloworld.cpp -o h   o新建一个可编辑的cpp文件,命名为h
./helloworld                执行helloworld
*/

//第一章 C++ 变量类型:
// 变量声明.
extern int a, b;
extern int c;
extern char f; //得变量可以跨文件被访问
int j;		   // 全局变量声明.全局变量一旦声明,在整个程序中都是可用的。定义全局变量时,系统会自动初始化为0 (区分局部变量)

// 常量声明.在 C++ 中,有两种简单的定义常量的方式:#define,或const. 请注意,把常量定义为大写字母形式
85								   // 十进制.不带前缀则默认表示十进制。
	0213						   // 八进制 .0 表示八进制
	0x4b						   // 十六进制 .0x 或 0X 表示十六进制
	30							   // 整数
	30u							   // 无符号整数 .U 表示无符号整数
	30l							   // 长整数 .L 表示长整数
	30ul						   // 无符号长整数
	314159e-5 = 314159 * 10 ^ (-5) // 指数
				314159E-5L		   // 合法的
				510E			   // 非法的:不完整的指数
				210f			   // 非法的:没有小数或指数
				21.0f			   // 合法的

#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'
				const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
int area;
area = LENGTH * WIDTH;

int i = -1000;
double d = 200.374;
float f = 230.47;
cout << "abs(i)  :" << abs(i) << endl;		 // 1000  绝对值
cout << "floor(d) :" << floor(d) << endl;	 // 200. 向下取整
cout << "sqrt(f) :" << sqrt(f) << endl;		 // 15.1812  . 该函数返回平方根
cout << "pow( d, 2) :" << pow(d, 2) << endl; // 40149.7 . 该函数返回 d的 2 次方。

// C++ 随机数: rand(),该函数只返回一个伪随机数。生成随机数之前必须先调用 srand() 函数。
int main()
{
	int i, j;

	// 实例中使用了 time() 函数来获取系统时间的秒数,作为参数传入srand
	srand((unsigned)time(NULL)); //调用 srand() 函数

	/* 生成 10 个随机数 */
	for (i = 0; i < 10; i++)
	{
		j = rand(); // 调用 rand() 函数,生成实际的随机数
		cout << "随机数: " << j << endl;
	}
}
// 随机数: 1748144778
// 随机数: 630873888
// 随机数: 2134540646

//--------------------------------------------------------

int func(); // 函数声明

int main() // main() 是程序开始执行的地方
/*
这是多行注释 */

{									 // 语句块是一组使用大括号括起来的按逻辑连接的语句
	cout << "Hello, world!" << endl; //你可以用 "\n" 代替 endl
	return 0;						 //为了增强可读性,您可以根据需要适当增加一些空格

	// enum color { red, green, blue } c;
	// c = blue;
	// cout << c

	int a = 3, b = 5;  // 定义并初始化 a 和 b
	int c;			   // 定义 c
	c = a + b;		   // 初始化 c
	char f = 'f';	   // 变量 f 的值为 'f'
	cout << c << endl; // 8

	f = '70.0/3.0'; // 70.0/3.0'
	cout << f << endl;
	int j; // 在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。在函数内,局部变量的值会覆盖相同名称的全局变量的值
		   // 当局部变量被定义时,系统不会对其初始化.

	int i = func(); // 函数调用

	cout << "Hello\tWorld, \
                       runoob\n\n"; // Hello   World,runoob.   \t空格,\把一个很长的字符串常量进行分行,\n换行

	short int i;		  // 有符号短整数
	short unsigned int j; // 无符号短整数
	j = 50000;
	i = j;
	cout << i << " " << j;

	return 0;

	// 函数定义.在函数声明时,提供一个函数名,而函数的实际定义则可以在任何地方进行。
	int func()
	{
		return 0;
	}
}
//--------------------------------------------------------
// C++ 程序中可用的存储类/关键字:static, extern, mutable, thread_local (C++11)

//一,static和extern:见https://www.jianshu.com/p/a7a346408d4a
//内部变量=局部变量. 外部变量=全局变量.
// static能够声明和定义一个内部函数;static能够定义一个内部变量,并将其延长至程序结束. extern能定义和声明一个外部函数,extern只起到声明一个外部变量的作用.
//大工程下我们会碰到很多源文档。下面假设static出现在a文档:
static int i;	   // i变量只在a文档中用(static能够定义一个内部变量)
int j;			   // j变量,在工程里用
static void init() // init方法只在a文档中用 (static能够声明和定义一个内部函数)
{
}
void callme() // callme方法,在工程里用
{
	static int sum; //变量sum的作用域只在callme里 (static能够定义一个内部变量)
}
// extern告诉编译器这个变量或函数在其他文档里已被定义了.下面假设在文档b中:
extern int j;		  //调用a文档里的( extern只起到声明一个外部变量的作用.声明后要另外定义,如下面的main函数)
extern void callme(); //调用a文档里的外部函数( extern能定义和声明一个外部函数(extern可以省略,本来函数就默认外部))
int main()
{
	j = 10;
	return 0;
}

//------------------对于内部变量:static延长局部变量的生命周期:程序结束的时候,局部变量才会被销毁
void test()
{
	static int b = 0; // static延长局部变量的生命周期,程序结束的时候,局部变量才会被销毁.所有的test函数都共享着一个变量b.
	b++;
	printf("b的值是%d\n", b); // 3      //如果没有static,代码块test每次被调用后变量b就自动销毁,下次调用的时候又重新分配内存空间.此处b的值是0
}
int main()
{
	test(); // b的值是1   //如果没有static,此处b的值是0
	test(); // b的值是2   //如果没有static,此处b的值是0
	test(); // b的值是3   //如果没有static,此处b的值是0
	return 0;

	//第二章 C++运算符.  &&与,||或.   //&与,|或,^异或,~取反
	// &与,都为1才是1,否则为0. |或,都为0才是0,否则为1.  ^异或,都为0或都为1,即为0.不同则为1.   ~取反.针对二进制取反,因此要先转换成二级制, ~1=-2;

	int d = 10; //  测试自增、自减
	c = d++;	// d++时语句中d的值为10,语句执行完后d的值为11.
			 //区分++d,++d时语句中d的值为11,语句执行完后d的值仍为11.
	cout << "Line 6 - c 的值是 " << c << endl;

	d = 10; // 重新赋值
	c = d--;
	cout << "Line 7 - c 的值是 " << c << endl; // d的值为9

	// 运算符是二进制之间的比较,要先将十进制改为二进制
	unsigned int a = 60; // 60 = 0011 1100
	unsigned int b = 13; // 13 = 0000 1101
	int c = 0;
	c = a & b;	// c= 12 = 0000 1100
	c = a | b;	// 61 = 0011 1101
	c = a ^ b;	// 49 = 0011 0001
	c = ~a;		// -61 = 1100 0011
	c = a << 2; // 240 = 1111 0000
	c = a >> 2; // 15 = 0000 1111

	// &(按位与)和&&(逻辑与): 相同:当与号两边为0的时候,结果均为假=0
	//              区别:  &&是短路运算符,即如果&左边判断为假,&右边就不会执行了,直接判断为假.
	//                    &: 如果&左边判断为假,&右边仍然会执行
	//                     eg: a=5   if(0&(a--)){...}  //判断为假,但a变成了4
	//                               if(0&&(a--)){...}  //判断为假,但a还是5

	a = 0;	// a=0000
	b = 10; // b=1010
	//(a,b虽然是4位,但是int都是32位,这里是把前面18个0都省略了.为什么可以省略?可以看到,哪怕省略了0,值仍然是等价的. 1010=00000000...1010)
	if (a && b) // a && b结果为false. 0000和1010相与的结果为0000, 此处的if ( a && b ) 等价为if (false )
	{
		cout << "Line 3 - 条件为真" << endl;
	}
	else //此处等价于 if (true )
	{
		cout << "Line 4 - 条件不为真" << endl;
	}
	if (!(a && b)) // !(a && b)>> !(false)>>true , 此处的if ( !(a && b) ) 等价为if (true )
	{
		cout << "Line 5 - 条件为真" << endl; // Line 4 - 条件不为真    Line 5 - 条件为真

		unsigned int a = 60; // 60 = 0011 1100
		unsigned int b = 13; // 13 = 0000 1101
		int c = 0;
		c = ~a;		// -61 = 1100 0011  ~和反码的区别: 取反包括符号位. 这里的1100 0011其实是1000....0100 0011的
		c = a << 2; // 240 = 1111 0000   二进制左移2位.符号位不变,数值位左边丢弃(不管是0还是1都丢弃),右边补0
		c = a >> 2; // 15 = 0000 1111   二进制右移2位.符号位不变,数值位右边丢弃(不管是0还是1都丢弃),左边补0(负数左补1)
					//右移1位>>除以2, 右移2位>>除以4
					//取反的结果是补码

		//第三章 循环
		// while 循环   while(条件){循环体}
		int a = 10; //局部变量声明
		while (a < 20)
		{
			cout << "a 的值:" << a << endl;
			a++;
		}

		// for 循环执行 for(起点;条件;步骤){循环体},  等效于while循环. 构成循环的三个表达式中任何一个都不是必需的。for( ; ; )无限循环. Ctrl + C 键终止一个无限循环
		for (int a = 10; a < 20; a = a + 1)
		{
			cout << "a 的值:" << a << endl;
		}

		// for 循环小范围迭代 for(变量:迭代范围){循环体}
		int my_array[5] = {1, 2, 3, 4, 5};
		for (auto &x : my_array)
		{ // 冒号前为迭代的变量,冒号后为被迭代的范围. auto 类型也是 C++11 新标准中的,用来自动获取变量的类型
			x *= 2;
			cout << x << endl;

			// do...while 循环 . do{循环体}while(条件);  区分while: 条件出现在循环的尾部,所以循环体至少会执行一次。
			int a = 10;
			do
			{
				cout << "a 的值:" << a << endl;
				a = a + 1;
				if (a > 15)
				{
					break; // break跳出while循环.  区分continue:跳出本次while循环,进入下一轮判断.
				}
			} while (a < 20);

			//第四章 判断.
			// if(bool判断){条件为真的执行语句}
			int a = 10;
			if (a == 10)
			{
				cout << "a 的值是 10" << endl;
			}
			else if (a == 20)
			{
				cout << "a 的值是 20" << endl;
			}
			else if (a == 30)
			{
				cout << "a 的值是 30" << endl;
			}
			else
			{
				cout << "没有匹配的值" << endl;
			}
			cout << "a 的准确值是 " << a << endl;
			//没有匹配的值   a 的准确值是 100

			// switch-case判断.是对定值的判断。如评级是A/B/C,工资明年到底是+100还是+200还是+300。
			// switch(grade){case'A':} 相当于 if(grade=A){}.  if else-if 处理的是带范围的判断(if grade>A)。
			int salary = 1000;
			switch (grade)
			{
			case 'A':
				salary += 100;
				break;
			case 'B':
				salary += 200;
				break;
			case 'C': 如果冒号后面的语句和下面一样,可以省略。一直到不一样为止
				break;
			case 'D':
				salary += 300;
				break;
			default: //跟以上的case都不符合,则执行default.  switch中的default相当于if中的else
				cout << "无效的成绩" << endl;
			}
			cout << "您的工资是 " << salary << endl;

			/*三元表达式:能用if-else表示的,都可以考虑用三元表达式
				表达式1?表达式2:表达式3;
				表达式1一般为一个关系表达式。
				如果表达式1的值为true,那么表达式2的值就是整个三元表达式的值。
				如果表达式1的值为false,那么表达式3的值就是整个三元表达式的值。
				注意:表达式2的结果类型必须跟表达式3的结果类型一致,并且也要跟整个三元表达式的结果类型一致。*/

			int x, y = 10;
			x = (y < 10) ? 30 : 40;
			cout << "value of x: " << x << endl; // value of x: 40

			//第五章 函数.  又叫方法、子例程或程序
			//每个 C++ 程序都至少有一个函数,即主函数 main() .函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
			//内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。
			// 1.函数的声明: 在函数定义前告诉编辑器有这个函数的存在.有了声明就可以先调用再定义函数,即定义写在调用后面
			//  函数声明可以有多次,但是定义只能有一次
			//  格式:  函数名(形参);
			//在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:int max(int, int);

			// 2.函数的定义
			// 返回类型 函数名称(形参)
			// {
			//    函数体
			//    renturn 返回值
			// }
			//    如果函数不需要返回值,声明的时候可以写void,然后函数体中直接写"return;"即可,或根本不写return也行.
			// 如果没有指定返回类型,则默认为int

			// 3.函数的调用:  函数名(实参)

			int max(int num1, int num2); // 函数声明

			int main()
			{
				int a = 100; // 局部变量声明
				int b = 200;
				int ret;

				ret = max(a, b); // 调用max函数来获取最大值,传入实参. ret来接受max函数的返还结果
				cout << "Max value is : " << ret << endl;
				return 0;
			}

			int max(int num1, int num2) //定义函数    //形参. 值传递:函数调用时,实参将数值传递给形参
			{
				int result; // 局部变量声明

				if (num1 > num2)
					result = num1;
				else
					result = num2;

				return result;
			}

			// 4.函数传递参数的方式有三种:
			//传值调用:把参数的'实际值'赋值给形参。形参不改变实参.
			//指针调用:把参数的'地址'赋值给形参。形参改变实参.
			//引用调用:把参数的'引用的地址'赋值给形参。形参改变实参.

			//传值调用:
			void swap(int x, int y);
			int main()
			{
				int a = 100;
				int b = 200;

				cout << "交换前,a 的值:" << a << endl;
				cout << "交换前,b 的值:" << b << endl;

				swap(a, b); // 调用函数来交换值,换的是值,不是地址

				cout << "交换后,a 的值:" << a << endl;
				cout << "交换后,b 的值:" << b << endl;
				return 0;
			}
			// 交换前,a 的值: 100
			// 交换前,b 的值: 200
			// 交换后,a 的值: 100
			// 交换后,b 的值: 200

			//指针调用:
			void swap(int *x, int *y);

			int main()
			{
				int a = 100;
				int b = 200;

				cout << "交换前,a 的值:" << a << endl;
				cout << "交换前,b 的值:" << b << endl;

				swap(&a, &b); //&a 表示指向 a 的指针,即变量 a 的地址 . &b 表示指向 b 的指针,即变量 b 的地址
							  // &a是a的内存地址的二进制表示

				cout << "交换后,a 的值:" << a << endl;
				cout << "交换后,b 的值:" << b << endl;

				// 交换前,a 的值: 100
				// 交换前,b 的值: 200
				// 交换后,a 的值: 200
				// 交换后,b 的值: 100

				//引用调用:
				void swap(int &x, int &y); //在 swap()函数的声明和定义 中,您需要声明函数参数为引用类型

				int main()
				{
					int a = 100;
					int b = 200;

					cout << "交换前,a 的值:" << a << endl;
					cout << "交换前,b 的值:" << b << endl;

					swap(a, b);

					cout << "交换后,a 的值:" << a << endl;
					cout << "交换后,b 的值:" << b << endl;

					return 0;
				}
				// 交换前,a 的值: 100
				// 交换前,b 的值: 200
				// 交换后,a 的值: 200
				// 交换后,b 的值: 100

				// 5.匿名函数 Lambda 函数(也叫 Lambda 表达式)

				// 6.函数高级-- 函数默认参数
				//在C++中,函数的形参列表中的形参是可以有默认值的
				//语法: 返回值类型 函数名 (参数= 默认值){}
				//如果我们自己传入了数据,就用自己的数据,如果没有,就用默认值
				//  函数声明和实现只能有一个可以有默认参数

				// 6.1 正常方式,参数无默认值的情况
				int func1(int a, int b, int c)
				{
					return a + b + c;
				}

				// 6.2 参数有默认值的情况:
				//如果某个位置已经有了默认参数,那么从这个位置以后,从左到右都必须有默认值 .如果b有默认参数,则c也必须有默认参数

				int func2(int a, int b = 20, int c = 30) //如果b有默认参数,则c也必须有默认参数
				{
					return a + b + c;
				}

				//如果函数的声明有默认参数,函数实现就不能有默认参数. 声明和实现只能有一个有默认参数
				int func3(int a = 10, int b = 10); //函数的声明

				int func3(int a, int b) //函数的实现(函数体)
				// int func2(int a = 10, int b = 10) //报错. 声明了有默认参数,函数实现就不能有默认参数
				{
					return a + b;
				}

				int main()
				{
					cout << "func1 = " << func3(20, 30, 30) << endl; // 80
					cout << "func2 = " << func1(10) << endl;		 // 60  只传了1个参数a
					cout << "func2 = " << func1(10, 30) << endl;	 // 70  传了参数a和b

					cout << "func3 = " << func2(20) << endl; // 30 优先使用自己传入的数据
					cout << "func3 = " << func2() << endl;	 // 20
				}

				// 7. 函数高级-- 函数占位参数
				//  C++中函数的形参列表里可以有占位参数,用来做占位. 调用函数时必须填补该位置
				//  语法: 返回值类型 函数名 (参数数据类型){}

				void func(int a, int) //第二个int就是占用参数.
				// void func(int a, int)  //占位参数还可以有默认参数
				{
					cout << "this is func " << endl;
				}

				int main()
				{
					func(10, 20); //占位参数必须填补, 20必须传入
				}

				// 8.函数高级-- 函数重载
				//  函数重载:函数名可以相同,提高复用性
				//  函数重载满足三个条件:
				//  同一个作用域下:如都在全局作用域下
				//  函数名称相同:void func()
				//  函数参数不同: 类型不同 或者 个数不同 或者 顺序不同

				// 注意: 函数的返回值不可以作为函数重载的条件

				void func()
				{
					cout << "func的调用" << endl;
				}

				void func(int a)
				{
					cout << "func(int a)的调用" << endl;
				}

				void func(int a, double b)
				{
					cout << "func(int a,double b)的调用" << endl;
				}

				void func(double a, int b) //参数顺序不同, 也是重载
				{
					cout << "func(double a, int b)的调用" << endl;
				}

				// int func(double a, int b)  //报错 . 函数的返回值不可以作为函数重载的条件.返回类型由void改为int,不是重载
				// {

				//     cout << "func(double a, int b)的调用" << endl;
				// }

				int main()
				{
					func();			//调用的是func()
					func(10);		//调用的是func(int a)
					func(10, 3.14); //调用的是func(int a, double b)
					func(3.14, 10); //调用的是func(double a, int b)
				}

				// 3.4 函数重载注意事项
				// 引用作为重载参数, 变量传入int& a ,常量传入const int& a
				// 函数重载碰到参数有默认值, 容易报错

				// 3.4.1 引用作为重载参数, 变量传入int& a ,常量传入const int& a
				// int和const int参数属于 类型不同的参数,可以构成重载.
				void func(int &a) //如果传进a, 则int &a = 10;不合法
				{
					cout << "func(int &a)的调用" << endl;
				}

				void func(const int &a) // const只读,const int &a = 10;合法
				{
					cout << "func(const int &a)的调用" << endl;
				}

				int main()
				{
					int a = 10;
					func(a);  //调用的是第一个func. 因为a是可读可写的变量,如果传进第二个func,就只能读,不能写
					func(10); //调用的是第二个func.
				}

				// 3.4.2 函数重载碰到参数有默认值, 会报错
				void func2(int a, int b = 10) //参数b有默认值
				{
					cout << "func2(int a, int b)的调用" << endl;
				}

				void func2(int a)
				{
					cout << "func2(int a)的调用" << endl;
				}

				int main()
				{
					int a = 10;
					func2(10); // 传入两个函数都可以, 有歧义, 会报错
				}

				//第六章 数组.  数组都是由连续的内存空间组成。
				// 1.定义一维数组 :有三种方式
				// 数据类型 数组名称[元素个数];  然后再赋值,如arr[0]=50
				// 数据类型 数组名称[元素个数] = {...};   eg.double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
				// 数据类型 数组名称[] = {...};    eg.int arr[]={}

				// 1.1打印内存地址
				// sizeof(arr)<< 求数组占用的内存空间,如40=40字节=10个int(每个int占4个字节)
				// arr <<打印数组名称可以直接打印数组的首内存地址,如0x0000
				// arr[0] <<打印数组中第一个元素的内存地址,打印效果与上面打印arr相同
				//(int)&arr[0] <<打印数组中第一个元素的内存地址,并令其变为int格式

				// eg.float a=8.0    int(a)=8
				//(int)&a实际是将二进制的地址转化成int型;   &a则是'浮点数'a的内存地址的二进制表示;
				//  (int&)a则是告诉编译器将a当作'整数'看(并没有做任何实质上的转换)。
				//以整数形式存放和以浮点形式存放其内存数据是不一样的,因此(int)&a,  (int&)a两者不等。

				// 2.定义二维数组.多行的一维数组,行*列矩阵. 有四种方式:
				//  数据类型 数组名称[行数][列数];  然后再赋值,如arr[0]=50
				//  数据类型 数组名称[行数][列数] = {{...},{...},...};   int arr[2][3]={{1,2,3},{4,5,6}}
				//  数据类型 数组名称[行数][列数] = {...};               int arr[2][3]={1,2,3,4,5,6}
				//  数据类型 数组名称[][列数] = {...};   //可以省略行数,不能省略列数.  {{...}}内嵌则行列数都不能省略

				// int arr[2][3]={{1},{2}}
				// 1 0 0
				// 2 0 0
				// int arr[2][3]={1,2,3}
				// 1 2 3
				// 0 0 0

				//第七章 字符串.  c++的字符串有两种表示形式
				// 1.以 null 结尾的字符串. 本质上是使用 null 字符 \0 终止的一维字符数组. \0可省略,编译器会自动加上
				char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
				cout << site << endl; // RUNOOB

				char str1[13] = "runoob";
				char str2[13] = "google";
				char str3[13];
				int len;

				strcpy(str3, str1);								  // 复制 str1 到 str3
				cout << "strcpy( str3, str1) : " << str3 << endl; // runoob

				strcat(str1, str2);								 // 拼接 str1 和 str2
				cout << "strcat( str1, str2): " << str1 << endl; // runoobgoogle

				len = strlen(str1);						  // 连接后,str1 的总长度
				cout << "strlen(str1) : " << len << endl; // 12

				// 2. string
				//区分char是字符型,2个字节.
				// string字符串在C语言中没有这种类型,是python中的基础数据类型.C++中的string表示类,没有固定内存大小,大小由内部含有多少字符决定
				string str1 = "runoob";
				string str2 = "google";
				string str3;
				int len;

				str3 = str1;					   // 复制 str1 到 str3
				cout << "str3 : " << str3 << endl; // runoob

				str3 = str1 + str2;						  // 连接 str1 和 str2
				cout << "str1 + str2 : " << str3 << endl; // runoobgoogle

				len = str3.size();						  // 连接后,str3 的总长度
				cout << "str3.size() :  " << len << endl; // 12

				//第八章 指针
				//什么是内存位置?  每一个变量都有一个内存位置, 用十六进制数表示
				//如何访问内存位置?  取址符号. &var1:0xbfebd5c0
				//指针,全称指针变量. 指针即地址!
				// 1.定义指针:数据类型 *指针变量名.  指针可以是int类型,double,float,char类型
				// eg. int *p.   *说明是p指针
				// 1.1 让指针记录变量a的地址
				int a = 10 int *p
					p = &a
							cout
						<< p << endl; // 0x0000
				//上面的集合写法
				int a = 10 int *p = &a
									//强制转换
									int *p = (int *)0x1100 //强制转换成int类型的指针

					// 1.2 可以通过解引用(*)指针的方式来找到指针指向的内存,并对内存中存放的数据进行读写操作  p>>0x0000>>10
					*p = 1000;
				cout << a << endl;	// 1000
				cout << *p << endl; // 1000

				// eg.
				int var = 20; // 实际变量的声明
				int *p;		  // 指针变量的声明,一般用p表示
				ip = &var;	  // 在指针变量中存储 var 的地址

				cout << var << endl; // 20
				cout << ip << endl;	 // 输出在指针变量中存储的地址:0xbfc601ac
				cout << *ip << endl; // 访问指针中地址的值:20

				// 1.3 指针所占的内存空间:在32位操作系统(操作系统基本都是32位),占4个字节空间. (在64位系统中占8个字节)
				cout << sizeof(int *) << endl; // 4
				cout << sizeof(*) << endl;	   // 4
				cout << sizeof(p) << endl;	   // 4

				// 2.空指针
				//空指针指向内存编号为0的内存空间.
				// 2.1 用于初始化指针:新建的指针一开始不知道指向哪里好,就指向0编号的内存.0=NULL
				int *p = NULL;
				// 2.2 空指针指向的内存是不可以访问的. 0-255之间编号的内存是系统占用的,不可以访问
				*p = 100 //空指针不能赋值

					 // 3.野指针
					 //野指针指向非法的内存空间. 该内存空间没有经过申请
					 // 3.1 在程序中,尽量避免出现野指针
					 int *
					 p = (int *)0x1100 //在系统没有分配0x1100这个空间的情况下,让指针指向这个空间,会报错:访问权限冲突
					//空指针和野指针都不是我们申请的空间,因此不要访问,访问都会出错

					system('pause')

					// 4.const修饰指针.
					//快速记忆:const叫常量,*叫指针
					//  4.1 const修饰指针时,为常量指针
					//  特点:指针的指向可以修改,但指针指向的值不可以改
					int a = 10 int b = 10 int * p = &a const int * p = &a // const叫常量,*叫指针,>>常量指针
																		  // const后面跟着*,那么取*的操作就不能做,如*p=20
																	   * p = 20 //错误.指针指向的值不可以改
					p = &b														//正确.针的指向可以修改

						// 4.2 const修饰常量时,为指针常量
						// 特点:指针的指向不可以改,但是指针指向的值可以改
						int *const p = &a; // const后面跟着p,那么变p的操作就不能做,如p=&b
				*p = 20					   //正确.指针指向的值可以修改
					p = &b				   //错误.指针指向不可以改

						// 4.3  const修饰指针和常量时,为修饰常量
						//特点:指针的指向, 指针指向的值 都不可以改
						const int *const p = &a;
				*p = 20	   //错误.指针指向的值不可以改
					p = &b //错误.针的指向也不可以修改

						// 5.指针和数组配合: 利用指针访问数组中的元素
						int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
				cout << "第一个元素为:" << arr[0] << endl;
				int *p = arr;									 // arr就是数组首地址
				cout << '利用指针访问第一个元素:' << *p << endl; // 1
				p++;											 //指针向后移动4个字节,每个int是4个字节大小
				cout << '利用指针访问第二个元素:' << *p << endl; // 2
				//遍历数组方法一
				for (int i = 0; i < 10; i++)
				{
					cout << arr[i] << endl;
				}
				//遍历数组方法二:指针法
				int *p2 = arr for (int i = 0; i < 10; i++)
				{
					cout << *p2 << endl;
					p2++;
				}

				// 6.指针和函数:  指针作为函数形参,可以改变实参的值

				//值传递
				//值传递时,形参中接收的是a和b的值 .形参改变,不改变实参.见下方main函数
				void swap1(int a, int b)
				{
					int temp = a;
					a = b;
					b = temp;
					//函数内部成功交换: a=20,b=10
				}
				//地址传递
				//地址传递时,实参中传递的是'a的&取址',形参接收的时候又进行了'解引用*p指针' .
				//地址中交换的是地址.形参改变,会改变实参.
				void swap2(int *p1, int *p2)
				{
					int temp = *p1;
					*p1 = *p2;
					*p2 = temp;
					//函数内部成功交换: a=20,b=10.
				}

				int main()
				{
					int a = 10;
					int b = 20;
					swap1(a, b);   //值传递不会改变实参
					swap2(&a, &b); //地址传递会改变实参.
					cout << "a=" << a << endl;
					cout << "b=" << b << endl;
					//值传递函数外部,不交换: a=10,b=20
					//地址传递函数外部,实参数据也会交换: a=20,b=10
				}

				//第九章 结构体
				// 1. 结构体:用户自定义的数据类型. 将好多种数据类型(如下面student中有string,int,int三种类型)集中到一起,组装成一个数据类型.
				// 定义结构体: struct 结构体名{成员列表}  //结构体成员列表=属性
				// 结构体变量创建方式1:
				// struct 结构体名 变量名;
				// 变量名.成员=成员值;
				// 结构体变量创建方式2: struct 结构体名 变量名={成员1值,成员2值...}
				// 结构体变量创建方式3: 在定义结构体时,顺便创建变量

				//如自创"学生"的数据类型,之后可以快速创建"学生"变量,学生包括三个属性:name,age,score
				struct student
				{
					string name; //属性=成员列表
					int age;
					int score;
					// };  //定义student结构体数据类型
				} stu3; //结构体变量创建方式3

				int main()
				{
					struct student stu1; //结构体变量创建方式1
					stu1.name = "张三";
					stu1.age = 18 stu1.score = 100;

					cout << "姓名:" << stu1.name << "年龄:" << stu1.age << "分数:" << stu1.score

																					  struct student stu2 = {"李四", 19, 60}; //结构体变量创建方式2
					cout << "姓名:" << stu2.name << "年龄:" << stu2.age << "分数:" << stu2.score
				}

				// 2.结构体数据
				//结构体数组, 数组中每个元素都是自定义的结构体
				//  结构体数组创建方式1:  struct 结构体名 数组名[元素个数] = { {成员1值,成员2值...},{成员1值,成员2值...},{成员1值,成员2值...}...};
				//  结构体数组创建方式2:
				//  struct 结构体名 数组名[元素个数];
				//  struct 结构体名 数组名[元素索引].成员=成员值;
				//  可以在定义数组时给元素赋值,也可以先定义数组之后再给数组中的元素赋值,这也是数组的修改方式

				struct Student
				{
					string name;
					int age;
					int score;
				}; // 定义student结构体数据类型

				int main()
				{
					struct Student stuArray[3] = //结构体数组创建方式2
					{
						{"张三", 18, 60};
					{"李四", 19, 60};
					{"王五", 38, 66};
				};
				//结构体数组创建方式1 .也是数组的修改方式
				stuArray[2].name = "赵六";
				stu1.age = 18 stu1.score = 100;

				//遍历结构体数组
				for (int i = 0; i < 3; i++)
				{
					cout << " 姓名:" << stuArray[i].name
						 << " 年龄:" << stuArray[i].age
						 << " 分数:" << stuArray[i].score << endl; //这三行是一句话,因此前两行没有分号;
				}
				system("pause");
				return 0;
			}

			// 3.结构体指针
			//  通过指针来访问结构体中的成员。指针->成员
			//  利用运算符 -> 来访问结构体的属性,也就是将 . 换成 -> , 就可以通过指针来访问。-和>拼接

			struct Student //定义结构体
			{
				string name;
				int age = 0;
				int score = 0;
			};

			int main()
			{
				struct Student s = {"张三", 18, 91}; //定义结构体变量,并赋值. 这里的struct可以省略.
				struct Student *p = &s;				 //定义指针,并指向结构体变量. 注意:接收的指针类型要和结构体类型保持一致,Student.  这里的struct可以省略.
				cout << " 姓名:" << p->name		 //利用 -> 来访问结构体变量的属性,并输出相应内容
					 << " 年龄:" << p->age
					 << " 分数:" << p->score << endl;
				system("pause");
				return 0;
			}

			// 4.结构体嵌套结构体
			//  结构体中的成员可以是另外一个结构体。
			//  例:老师一对一辅导学生.老师这个结构体中,记录自己带的是哪个学生:
			//  首先定义一个老师的结构体,结构体成员有:老师的姓名、年龄、职工号、辅导的学生,
			//  此时这个辅导的学生就可以是另外一个结构体. 学生的结构体成员有:学生的姓名、年龄、成绩等....

			struct Student //定义学生的结构体
			{
				string name;   //学生姓名
				int age = 0;   //学生年龄
				int score = 0; //学生成绩
			};

			struct Teacher //定义老师的结构体
			{
				string name;	  //老师姓名
				int age = 0;	  //老师年龄
				int id = 0;		  //老师工号
				struct Student s; //辅导的学生.  如果先定义老师的结构体,再定义学生的结构体,写到这句话可能会报错,因为此时没有学生的结构体
				//因为系统不认识学生的结构体,所以不管什么版本,最好先定义出现的子结构体。
			};

			int main()
			{
				struct Teacher t; //创建结构体变量
				t.name = "万华";
				t.age = 48;
				t.id = 336;
				t.s.name = "张三"; //第一个 . 是访问t中的属性,第二个 . 是访问s中的属性
				t.s.age = 16;	   //因为我们老师的结构体中,还嵌套有一个学生的结构体。
				t.s.score = 96;
				cout << "老师的姓名:" << t.name
					 << " 老师的职工号:" << t.id
					 << " 老师的年龄:" << t.age << endl
					 << "老师辅导的学生姓名:" << t.s.name
					 << " 老师辅导的学生年龄:" << t.s.age
					 << " 老师辅导的学生成绩:" << t.s.score << endl;
				system("pause");
				return 0;
			}

			// 5.结构体作为函数参数
			//结构体作为函数的参数向函数中传递.有值传递和地址传递。
			//值传递:形参改变 不改实参.  地址传递:形参改变 改变实参.
			//为什么说将函数的形参改为指针,可以节省内存空间? 答:
			//值传递传递参数s的时候,是将s拷贝了一份放入函数中,
			//因此函数内部的s 与函数外部的s不同. 拷贝的s是占了内存空间的,内存空间*2.
			//如果传入的是*s,即指针,就不用拷贝粘贴.无论s多大,一个指针的大小是固定的,如int*p就是4个字节,

			struct Student
			{
				string name;
				int age = 0;   // int age; 也可
				int score = 0; // int score; 也可
			};

			//值传递.
			void printStudent1(struct Student s) //定义打印学生信息的方法
			{
				s.age = 100;
				cout << "在print1函数中打印的结果:" << endl
					 << "姓名:" << s.name
					 << " 年龄:" << s.age // 100
					 << " 成绩:" << s.score << endl;
			}

			//地址传递
			void printStudent2(struct Student * p) //指针的数据类型要和传入的s保持一致,Student
			{
				p->age = 200;
				cout << "在print2函数中打印的结果:" << endl
					 << "姓名:" << p->name //指针用 -> 访问
					 << " 年龄:" << p->age
					 << " 成绩:" << p->score << endl;
			}

			//地址传递 +const防止误修改
			//为什么值传递中不加const修饰? 因为值传递中的s变化本身就不会影响函数外的s
			void printStudent2(const Student *p) //数据类型Student前面加入const
			{
				p->age = 200; //会报错.因为加了const修饰后,就不可以修改了.只可以访问.根据需要加.
				cout << "在print2函数中打印的结果:" << endl
					 << "姓名:" << p->name //指针用 -> 访问
					 << " 年龄:" << p->age
					 << " 成绩:" << p->score << endl;
			}

			int main()
			{
				struct Student s; //创建结构体变量
				s.name = "张三";
				s.age = 16;
				s.score = 76;
				//创建结构体变量方法二: struct Student s = {"张三",16,76};

				//调用打印学生信息的方法
				printStudent1(s);  //值传递
				printStudent2(&s); //地址传递
				cout << "在main函数中打印的结果:" << endl
					 << "姓名:" << s.name
					 << " 年龄:" << s.age
					 //值传递中,结果为16.没变
					 //地址传递中,结果为200.变了

					 << " 成绩:" << s.score << endl;
				system("pause");
				return 0;
			}

			//第十章 程序的内存模型
			// 内存四区的意义:不同区域存放的数据,有不同的生命周期
			// C++中在'程序运行前'分为全局区和代码区. '程序运行前':双击运行exe程序前

			//【代码区】:
			// 存放程序中所有的二进制代码.会先将代码转成二进制, 再存放
			// 特点是共享和只读.
			// 共享:哪怕有的程序可以多次运行(多次双击exe运行),代码区中也只存放一份代码
			// 只读: 用户只能运行代码,不能修改代码.如果可以修改,那直接在微信程序代码中将自己的零钱改到10000就好了

			// 【全局区】:
			// 存放 –全局变量、静态变量、常量区(常量区中存放: const修饰的全局常量 和 字符串常量)-
			// 全局区的数据在程序结束后由操作系统释放.由操作系统来管理其死亡

			// 【栈区】:
			//【栈区】存放函数的参数(形参), 局部变量等
			// 栈区的数据由编译器自动分配释放. 即由编译器来管理其生存和死亡
			// 注意事项:不要返回局部变量的地址.

			// 【堆区】:
			// 在程序运行后,由程序员分配释放, 若程序员不释放, 程序结束时由操作系统回收. 即由程序员来管理其生存和死亡,有最迟死亡时间
			// 在C++中主要利用new在堆区开辟内存. ​语法:new 数据类型.
			// 可一直存放.

			// 【new 操作符】
			// ​堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

			// 10.1 全局区
			//程序输出:全局区1518开头,常量区1517开头,栈区1631(和前两个区比较远)

			int g_a = 10;		  //全局变量	->全局区    地址为: 15187968
			const int c_g_a = 10; //全局常量	->常量区    地址为: 15178544

			int main()
			{
				static int s_a = 10; //静态变量	->全局区    地址为: 15187976
				(int)&"hello world"; //字符串常量	->常量区 (字符串常量:用双引号引起来的)  地址为: 15178740

				int a = 10;			  //局部变量	->栈区      地址为: 16317200
				const int c_l_a = 10; //局部常量    ->栈区      地址为: 16317176
			}

			// 10.2 栈区
			//局部变量存放在栈区,栈区的数据在函数执行完后由编译器自动释放(释放数据=释放内存).因此不要返回局部变量的地址!
			//形参 也会放在栈区
			int *func(int b) //形参b 也会放在栈区
			{
				b = 100;
				int a = 10; //局部变量 .
				return &a;	//返回局部变量的地址
			}
			int main()
			{
				//利用指针来接受func函数的返回值.func函数的返回值为:局部变量a的地址
				int *p = func(1);
				cout << *p << endl; // 10.  第一次可以打印正确的数字,是因为编译器做了保留
				cout << *p << endl; // 26799368(乱码).  第二次这个数据就已经被释放了,因此会乱码

				system("pause");
				return 0;
			}

			// 10.3 堆区
			// new可将数据开辟到堆区: new 数据类型(初始值), 这个创建方式返回的是内存地址,因此要用指针来接收

			int *func()
			{
				int *a = new int(10);
				// new返回的是内存地址,因此要用指针来接收
				//区分上个例子中的'int a = 10'创建的是局部变量
				//指针本质上也是局部变量,放在栈区上. 指针保存的数据放在堆区.
				return a;
			}
			int main()
			{
				int *p = func();
				cout << *p << endl; // 10.  这个10会一直存活,除非按叉退出程序.或delete删除干净.
			}

			// 10.3.1 new的用法

			//案例一:在堆区区利用new来建立整型数据
			int *func()
			{
				int *p = new int(10); //在堆区建立整型数据.指针放在栈上
				return p;
			}

			void test01() //有cout但是没有return,仍然用void
			{
				int *p = func();
				cout << *p << endl; // 10. 存放在堆区

				delete p; //堆区数据可通过delete释放
						  // cout << *p << endl;  //报错
			}

			//案例二:在堆区利用new来创建数组
			void test02()
			{
				int *arr = new int[10]; //返回的是连续内存空间组成的数组, 指针也可以是数组
				for (int i = 0; i < 10; i++)
				{
					arr[i] = i + 100; //给指针数组中的10个指针赋值为100-109
				}
				for (int i = 0; i < 10; i++)
				{
					cout << arr[i] << endl; //打印100-109
				}
				//释放堆区数组: 释放数组的时候, 要加[]来告诉编译器是对'整个数组'做释放
				delete[] arr;
			}

			int main()
			{
				test01();
				test02();

				int *p = func();
				cout << *p << endl;
			}

			//第十一章 引用
			//引用: 给一个变量起一个别名. 给变量a取一个别名b,令变量a和b都能操控同一块内存.

			// 11.1 引用的使用
			//语法:数据类型 &别名=原名  .数据类型要和原来一样,不能变.
			int main(int argc, char **argv)
			{

				int a = 10;
				int &b = a;					//创建引用
				cout << "a= " << a << endl; // 10
				cout << "b= " << b << endl; // 10

				b = 100;
				cout << "a= " << a << endl; // 100
				cout << "b= " << b << endl; //输出都是100,因为a和b指向同一块内存
			}

			// 11.2 引用的注意事项
			//  引用必须初始化
			//  引用在初始化后不可改变
			int main(int argc, char **argv)
			{
				int a = 10;
				// int& b;  //错误 ,引用创建的时候就必须赋值,进行初始化
				int &b = a;

				int c = 20;
				// int& b = c;   //错误.  引用初始化后不可以更改.b是a的别名,就不能改为是c的别名
				b = c; //不能更改引用操作,可以进行赋值操作

				cout << "a= " << a << endl; // 20
				cout << "b= " << b << endl; // 20
				cout << "c= " << c << endl; // 20
			}

			// 11.3 引用作为函数参数
			//  作用:函数传参时,可以利用'引用'让形参修饰实参.(不需要指针,也可以达到地址传递的效果)
			//  优点:简化指针修改实参

			// 11.3.1 值传递
			//形参不会修饰实参 = 形参改变,实参不发生改变
			void Swap(int a, int b)
			{
				int temp = a;
				a = b;
				b = temp;

				cout << "值传递中的a= " << a << endl; // 20
				cout << "值传递中的b= " << b << endl; // 10
			}

			// 11.3.2 地址传递
			//形参会修饰实参 = 形参改变,实参也发生改变
			void Swap01(int *a, int *b)
			{
				int temp = *a;
				*a = *b;
				*b = temp;

				cout << "地址传递中的a= " << *a << endl; // 20
				cout << "地址传递中的b= " << *b << endl; // 10
			}

			// 11.3.3 引用传递
			// main函数中,调用的Swap02(a, b)将a传入函数,为其通过形参列表中的'int& a'取了别名. 原名和别名都是a,指向的是同一块内存
			void Swap02(int &a, int &b)
			{
				int temp = a;
				a = b;
				b = temp;

				cout << "引用传递中的a= " << a << endl; // 20
				cout << "引用传递中的b= " << b << endl; // 10
			}

			int main(int argc, char **argv)
			{

				int a = 10;
				int b = 20;
				// Swap(a, b);     //值传递  a=10  b=20
				// Swap01(&a, &b); //地址传递  a=20  b=10
				Swap02(a, b); //地址传递  a=20  b=10

				cout << "a= " << a << endl;
				cout << "b= " << b << endl;
			}

			// 11.4 引用作为函数返回值
			//  作用:引用可以作为函数的返回值存在的
			//  注意:不要返回局部变量引用
			//  用法:函数调用作为左值

			// 1.不要return 局部变量的引用,会报错
			int &test01()
			{				//将局部变量a,以引用(int&)的形式,返还到外面
				int a = 10; //创建局部变量,存放在栈区
				return a;
			}

			// 2.函数调用可以作为左值. 左值是等号的左边, 即 函数名()=值
			int &test02()
			{
				static int a = 10; //静态变量存放在全局区,程序结束后由系统释放
				return a;
			}

			int main(int argc, char **argv)
			{

				// int& ref = test01();  // test01()函数返还出来的是 int&a,  此时相当于ref=a,打印ref就是打印a局部变量
				//局部变量当函数执行完就自动释放了.
				// cout << "ref = " << ref << endl;   10. 打印ref就是打印a局部变量.第一次能打出来,是因为编译器做了保留
				// cout << "ref = " << ref << endl;   报错.a的内存已经被释放了

				int &ref2 = test02();			   // ref2=a
				cout << "ref2 = " << ref2 << endl; // 10
				cout << "ref2 = " << ref2 << endl; // 10

				test02() = 1000; //如果函数的返回值是引用,这个函数调用可以作为左值
								 // int& ref2=值,是对引用赋值.即令ref2 =1000

				cout << "ref2 = " << ref2 << endl;
				cout << "ref2 = " << ref2 << endl;
			}

			// 11.5 引用的本质(了解即可,面试八股而已)
			//  引用的本质在c++的内部实现是一个指针常量. 指针常量是指针指向不可改,这也说明为什么引用不可更改
			//  推荐使用引用,语法方便
			//  引用本质是指针常量,但是指针的操作编译器都自动帮我们转换了

			void func(int &ref)
			{			   //引用会被自动转换为:int* const ref = &a;  ref只能指向a的地址(0x0011),不能指向其他地址(如0x0022)
				ref = 100; //引用会被自动转换为:*ref = 100;          ref的值可以变
			}
			int main()
			{
				int a = 10;

				int &ref = a; //引用会被自动转换为指针常量:int* const ref = &a;
				ref = 20;	  //引用会被自动转换为:*ref = 20;

				cout << "a:" << a << endl;	   // 20
				cout << "ref:" << ref << endl; // 20 .这里的ref是指针,会被自动转换为:*ref

				func(a);
				cout << "a:" << a << endl;	   // 100
				cout << "ref:" << ref << endl; // 100 .这里的ref是指针,会被自动转换为:*ref
			}

			// 11.6 常量引用
			//  常量引用主要用来修饰形参,防止误操作
			//  在函数形参列表中,可以加const修饰形参,防止形参改变实参

			// 11.6.1 加const后编译器会自动对代码进行优化, 可对引用进行赋值
			int main(int argc, char **argv)
			{

				int a = 10;
				int &ref = a; //引用
				// int &ref=10 报错 必须初始化

				// const int& ref = 10; 正确
				// 加了const相当于编译器将代码修改为 int temp=10; const int &ref=temp;
				// ref = 20;     //报错. 加了const之后变为只读,不可以修改
			}

			// 11.6.2  加const修饰形参,防止参数修改
			void show(const int &val)
			{
				val = 1000;						//报错,防止误操作
				cout << "val= " << val << endl; // 100
			}

			int main(int argc, char **argv)
			{
				int a = 10;
				show(a);
				cout << "a= " << a << endl; // 100
			}

			// 第十二章 面向对象
			// 面向对象三大特性: 封装, 继承, 多态.
			// 万事万物皆对象: 人为类, 姓名年龄是属性, 跑吃饭唱歌是行为

			// 12.1 封装
			// 封装是C++面向对象三大特性之一
			// 封装的意义:
			//     将属性和行为作为一个整体,表现生活中的事物
			//     将属性和行为加以权限控制

			// 类中的属性和行为统一称为“成员”
			//     属性 成员属性(成员变量)
			//     行为 成员函数(成员方法)

			// 12.1.1 意义一 将属性和行为作为一个整体,表现生活中的事物

			// 语法: class 类名{ 访问权限: 属性 / 行为 };
			//类中的成员属性和成员行为统一称为“成员”
			//属性 = 成员属性 = 成员变量
			//行为 = 成员函数 = 成员方法
			// 实例化 : 通过一个类创建一个对象的过程

			// 示例1:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号(两个行为)

			class Student //设计类. 类后面紧跟着就是类的名称
			{
			public: //访问权限设为公共权限
					//属性 —— 变量 —— 姓名,学号
				string m_name;
				int m_id;

				//行为 —— 函数 —— 给姓名赋值, 给学号赋值 ,显示姓名和学号
				void setName(string name)
				{
					m_name = name; //给姓名赋值
				}

				void setId(int id)
				{
					m_id = id; //给学号赋值
				}

				void showStudent()
				{
					cout << "姓名:" << m_name << "学号" << m_id << endl;
				}
			};

			int main()
			{
				Student s1;		   //实例化——通过一个类创建一个对象的过程
				s1.m_name = "张三" //给s1对象的属性进行赋值 方式一
							s1.m_id = "1"

									  s1.setName("张三"); //给s1对象的属性进行赋值 方式二
				s1.setId(1);

				s1.showStudent();									//显示学生信息 方式一
				cout << "学生信息为:" << s1.showStudent() << endl; //显示学生信息 方式二
			}

			// 12.1.2 意义二 访问权限

			//     公共权限 public    类内可以访问 类外可以访问
			//     保护权限 protected 类内可以访问 类外不可以访问 儿子可以访问父亲中的保护内容(如父亲的工作)
			//     私有权限 private   类内可以访问 类外不可以访问 儿子不可以访问父亲中的保护内容(如父亲的银行卡)
			//保护和私有权限的区别在继承中体现
			// 没有设置访问权限的时候,则默认是私有private

			class Person
			{
			public: //公共权限 - 姓名
				string m_Name;

			protected: //保护权限 - 汽车
				string m_Car;

			private: //私有权限 - 密码
				int m_Password;

			public: //公共函数 下面三个的赋值都属于类内,可以访问
				void func1()
				{
					m_Name = "张三"; // m_Name是类内变量,可以访问
					m_Car = "拖拉机";
					m_Password = 123456;
				}

			private: //私有函数,类内可以访问,类外不可以访问
				void func2()
				{
					m_Name = "张三";
					m_Car = "拖拉机";
					m_Password = 123456;
				}
			}; //到此为止都是类内内容

			int main()
			{
				Person p1; //实例化具体对象

				//类外访问类内的变量和方法
				p1.m_Name = "李四"; //类外可以访问,可以进行修改
				// p1.m_Car = "奔驰";     //类外访问不到.  保护权限内容
				// p1.m_Password = 123;  //类外访问不到.  私有权限内容

				p1.func1(); // public可以类外访问函数
				// p1.func2();           //报错. private不可以类外访问
			}

			// 12.1.2.1  struct 与 class 的区别
			// 在C++中 结构体struct和类class唯一的区别就在于 : 默认的访问权限不同
			// struct 默认权限为公共
			// class  默认权限为私有

			class C1 //默认权限是私有private
			{
				int m_A;
			};

			struct C2 //默认权限是公共public
			{
				int m_A;
			};

			int main()
			{
				C1 c1;
				// c1.m_A = 10;  //无法访问,class默认权限是私有

				C2 c2;
				c2.m_A = 10; //可以访问,struct默认权限是公共
			}

			// 12.1.3 属性设置为私有,行为设置为公共!!
			// 优点1: 将所有成员属性设置为私有,可以自己控制读写权限
			// 优点2: 对于写权限,我们可以检测数据的有效性
			// 分类: 又读又写; 只读; 只写

			//     写:void set(参数类型 xxx) {m_x = xx;}
			//     读:参数类型 get() {return m_x;}
			//附加:在类中可以让另一个类 作为本类的成员. 见下方
			// Person::setName()可表示 setName()是Person中的成员函数

			//案例一: 设计人类
			class Person
			{
			public:
				void setName(string name) //设置姓名  写姓名
				{
					m_Name = name;
				}

				string getName() //获取姓名  读姓名
				{
					return m_Name; //返回string类型的字符串
				}

				//------------------------------------------------

				int getAge() //获取年龄 只读
				{
					m_age = 20;	  // 初始化为20岁,这样的话最终的年龄还是20岁这样就是只读
					return m_age; //返回int类型
				}

				//------------------------------------------------
				void setLover(string lover) //设置女朋友  只写 —— 外界访问不到该数据
				{
					// if (m_Lover==none)
					// {
					// 	cout << "你这个单身狗,必须要有个对象!" << endl;
					// 	return;
					// } //优点2: 对于写权限,我们可以判断用户输入的数据有没有问题.通过if.
					m_Lover = lover;
				}

				//将所有成员属性设置为私有,然后自己再控制读写权限
			private:
				string m_Name;	//姓名	可读可写
				int m_age;		//年龄	   只读    没有写内部写入函数
				string m_Lover; //女朋友    只写   没有写内部读取函数
				Student five;	//在类中可以让另一个类 作为本类的成员. 这里是假设之前可能有一个class Student
			};

			int main()
			{
				Person p;

				p.setName("张三");						  //输入写姓名
				cout << "姓名: " << p.getName() << endl; //输出读姓名

				p.setAge(18);							 //报错. 年龄只能读,不能设置
				cout << "年龄: " << p.getAge() << endl; //输出读年龄

				p.setLover("刘亦菲");					   //输入写女朋友
				cout << "女朋友: " << p.getAge() << endl; //报错. 女朋友只能写,不能读
				//本质上是'年龄'没有写内部写入函数;  '女朋友'没有写内部读取函数
			}

			//案例二:设计立方体类(Cube),求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等
			//步骤:
			// 1.创建立方体的类
			// 2.设计属性(私有权限)   三个属性的设置(void set)与获取(int return)
			// 3.设计行为	获取立方体面积和行为
			// 4.判断两个立方体是否相等
			// 方法一:成员函数 两个一比一
			// 方法二:全局函数 一比二

			//工程中经常使用头文件和源文件分写.
			// 头文件.h   负责声明函数和变量.
			// 源文件.cpp 负责实现函数&main程序.

			class Cube
			{
			public:				 //行为
				void setL(int l) //设置长
				{
					m_L = l;
				}
				int getL() //获取长
				{
					return m_L;
				}

				void setW(int w) //设置宽
				{
					m_W = w;
				}

				int getW() //获取宽
				{
					return m_W;
				}

				void setH(int h) //设置高
				{
					m_H = h;
				}

				int getH() //获取高
				{
					return m_H;
				}

				int calculateS() //获取立方体的面积
				{
					return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H;
				}

				int calculateV() //获取立方体的体积
				{
					return m_H * m_L * m_W;
				}

				//方法一: 利用成员函数判断两个立方体是否相等
				// bool isSameByClass(Cube& c) 	//利用引用传递的方式传递. 就不会再拷贝一份数据,且函数内外一致
				// {
				// 	//判断自身(c1)的长和传进来的长(c2,c3)进行比较
				// 	if (m_L == c.getL() && m_H == c.getH() && m_W == c.getW())
				// 	{
				// 		return true;
				// 	}
				// 	return false;
			}

			private :		   //属性
					  int m_L; //长
			int m_W;		   //宽
			int m_H;		   //高
		};					   //以上是类内

		//方法二: 定义全局函数.  利用全局函数判断 两个立方体是否相等
		bool isSame(Cube & c1, Cube & c2)
		{
			if (c1.getL() == c2.getL() && c1.getH() == c2.getH() && c1.getW() == c2.getW()) //长宽高 均相等
			{
				return true;
			}
			return false;
		}

		int main()
		{
			Cube c1; //创建立方体对象
			c1.setL(10);
			c1.setH(10);
			c1.setW(10);

			Cube c2; //创建第二个立方体. 这里设置与c1完全相等
			c2.setL(10);
			c2.setH(10);
			c2.setW(10);

			Cube c3; //创建第三个立方体
			c3.setL(20);
			c3.setH(30);
			c3.setW(40);

			cout << "c1 的面积为:" << c1.calculateS() << endl;
			cout << "c1 的体积为:" << c1.calculateV() << endl;

			// 判断方法一: 利用成员函数判断 两个立方体是否相等
			// bool ret2 = c1.isSameByClass(c2);//c1为已知立方体,再传入调用c2,进行对比
			// if (ret2)
			// {
			// 	cout << "成员函数 c1 和 c2 相等" << endl;
			// } else {
			// 	cout << "成员函数 c1 和 c2 不相等" << endl;
			// }

			// bool ret3 = c1.isSameByClass(c3);//c1为已知立方体,再传入调用c3,进行对比
			// if(ret3)
			// {
			// 	cout << "成员函数 c1 和 c3 相等" << endl;
			// } else {
			// 	cout << "成员函数 c1 和 c3 不相等" << endl;
			// }

			//判断方法二: 使用全局函数判断 两个立方体是否相等
			bool ret1 = isSame(c1, c2);
			if (ret1)
			{
				cout << "全局函数 c1 和 c2 相等" << endl;
			}
			else
			{
				cout << "全局函数 c1 和 c2 不相等" << endl;
			}
		}

		// 12.2.  对象的初始化和清理

		// 12.2.1 构造函数(初始化)和析构函数(清理)
		// 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
		// C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置
		// 构造函数和析构函数如果你自己不写,编译器就会自动调用一个空实现的构造和析构函数(没有代码). 你写了就用你的,你没写就用编译器的

		// 1. 构造函数(写作用域),进行初始化操作
		//语法:类名(){}
		//函数名与类名相同
		//有返回值,不用写void
		//构造函数可以有参数,可以发生重载
		//创建对象时,构造函数会自动调用,而且只调用一次

		// 2. 析构函数  进行清理操作
		//语法: ~类名(){}
		//函数名和类名相同,在名称前加~
		//没有返回值 不写void
		//析构函数不可以有参数,不可以发生重载
		//对象在销毁前,会自动调用析构函数,而且只会调用一次

		class Person
		{
		public:		 //成员函数
			Person() // 1. 构造函数(写作用域),进行初始化操作.由于我们自己写了构造函数,当实例化对象的时候,执行的是我们这个构造函数
			{
				cout << "Person 构造函数的调用" << endl;
			}

			~Person() // 2. 析构函数  进行清理操作.释放对象前就会自动调用这个我们写好的析构函数.
			{
				cout << "Person 析构函数调用" << endl;
			}
		}; //以上是类内

		void test01()
		{
			Person p1; //实例化person时会调用我们写好的构造函数.
					   // p1是局部变量,在栈上的数据,test01执行完毕后自动释放这个对象. 释放对象前就会自动调用我们写好的析构函数.
		}

		int main()
		{
			//在test01()函数内实例化了一个对象p1
			test01(); //结果: "Person 构造函数的调用"  "Person 析构函数调用".
			cout << endl;

			//又在全局实例化了一个对象p2是全局变量, main程序执行完,按0退出main程序时才会 自动调用我们写好的析构函数
			Person p2; //结果: "Person 构造函数的调用"  .  实例化person时会调用我们写好的构造函数.
			system("pause");
			return 0
		}

		// 12.2.2 构造函数的分类及调用
		//构造函数有两种分类:
		// 1 按构造函数是否有参数 - 无参构造, 有参构造
		// 2 按类型 - 普通构造, 拷贝构造!!! 加引用

		//构造函数有三种调用方式:
		// 3 括号法(推荐)
		// 4 显示法
		// 5 隐式转换法

		class Person
		{
		public:
			Person()
			{
				cout << "Person 无参构造函数的调用" << endl; //无参构造函数
			}												 //系统自带的是无参的空构造函数

			Person(int a)
			{
				age = a;
				cout << "Person 有参构造函数的调用" << endl; //有参构造函数
			}

			Person(const Person &p) //拷贝构造函数   //拷贝一模一样的数据出来
			{
				//将传入的人身上所有属性,拷贝到我身上,并且之后不能对原人物信息进行二次修改,因此是const
				age = p.age;
			}

			~Person() //析构函数调用
			{
				cout << "Person 析构函数调用" << endl;
			}

			int age;
		}; //类内

		//调用(三种方法)
		void test01()
		{
			// 1.括号法
			//注意:调用默认构造函数时,不要加小括号().如果写成Person p(),编译器会认为是函数的声明,如void func().不会认识是在创建对象,不运行构造函数
			Person p;	   //默认构造函数 调用
			Person p0(10); //有参构造函数
			Person p1(p0); //拷贝构造函数. 将p0的所有属性拷贝给p1.

			cout << "p0 年龄为: " << p0.age << endl; //显示为10
			cout << "p1 年龄为: " << p1.age << endl; //显示为10
			cout << endl;

			// 2.显示法
			Person p6;								  //默认构造
			Person p2 = Person(20);					  //有参构造.  对象名称写在'等号右边'
			Person p3 = Person(p2);					  //拷贝构造
			cout << "p2 年龄为: " << p2.age << endl; //显示为20
			cout << "p3 年龄为: " << p3.age << endl; //显示为20
			cout << endl;

			// Person(10);      //这一行执行完毕,会立刻析构对象.然后才执行下一行. 运行结果见下方
			// cout << "aaaaa" << endl;
			//  "Person 有参构造函数的调用"
			//  "Person 析构函数调用"
			//  "aaaaa"
			//匿名对象特点:当前行执行结束后,系统会立即回收匿名对象. 匿名对象指经过实例化创建,但是不在'等号右边',因此该对象没有名字.

			// Person(p3);       //会报错,重复定义.
			//不要利用拷贝函数初始化匿名对象,编译器会将Person (p3) 认为是 Person  p3 (隐式转换). 括号被自动去掉后,就是对象声明了

			// 3.隐式转换法
			Person p7;								  // 默认构造
			Person p4 = 30;							  //相当于	Person p4 = Person(10);有参构造
			Person p5 = p4;							  //拷贝构造
			cout << "p4 年龄为: " << p4.age << endl; //显示为30
			cout << "p5 年龄为: " << p5.age << endl; //显示为30
			cout << endl;
		} //函数内

		int main()
		{
			test01();
		}

		// 12.2.3 什么时候会调用拷贝构造函数? (3种情况)
		// 1 使用已经创建完毕的对象来初始化一个新对象
		// 2 值传递的方式给函数参数传值
		// 3 以值方式返回局部对象(难点)

		class Person
		{
		public:
			Person() //无参构造函数
			{
				cout << "Person 默认构造函数调用" << endl;
			}

			Person(int age) //有参构造函数
			{
				cout << "Person 有参构造函数调用" << endl;
				m_age = age;
			}

			Person(const Person &p) //拷贝构造函数
			{
				cout << "Person 拷贝构造函数调用" << endl;
				m_age = p.m_age;
			}

			~Person() //析构函数
			{
				cout << "Person 析构函数调用" << endl;
			}

			int m_age;
		};

		// 1.使用一个已经创建完毕的对象来初始化一个新对象 Person p2(p1);
		void test01()
		{
			Person p1(20); // 有参构造
			Person p2(p1); // 拷贝构造函数调用

			cout << "p2 年龄为:" << p2.m_age << endl; // 假设下面的代码都注释化了,即程序运行到此为止.则:
													   // "Person 默认构造函数调用"
													   // "Person 默认构造函数调用"
													   // 20
													   // "Person 析构函数调用"
													   // "Person 析构函数调用"
		}

		// 2.值传递的方式给函数参数传值 doWork(p3);
		void doWork(Person p)
		{
			p.age = 1000; // 值传递, 拷贝出临时的副本,不会影响下面的数据
		}

		void test02()
		{
			Person p3;	// 无参构造函数调用
			doWork(p3); // 实参p3传给doWork函数的形参Person p时, 会调用拷贝构造函数
		}

		// 3.值传递的方式返回局部对象. Person p = doWork2();
		Person doWork2() // doWork2函数返还出去的p1是Person类型. 因为进行的是值传递, p1会拷贝一个新的对象,返回给test03
		{
			Person p1; // 局部对象,调用默认构造函数.局部对象 函数执行完之后,就会被释放掉
			return p1;
			cout << (int *)&p1 << endl;
		}

		void test03()
		{
			Person p = doWork2();	   // 创建局部对象
			cout << (int *)&p << endl; // p 和 p1不是一个
		}

		int main()
		{
			test01();
			cout << endl;
			test02();
			cout << endl;
			test03();
		}

		// 12.2.4 构造函数调用规则
		// 1. 创建一个类,C++编译器会给每个类都添加至少3个函数
		//①默认构造 (空实现)  < 有参构造函数
		//②拷贝函数 (值拷贝).
		//③析构函数 (空实现)

		// 2. 如果我们写了有参构造函数,编译器就不再提供默认无参构造.'有高就无低':  默认构造 (空实现)  < 有参构造函数''
		//  如果我们没写拷贝构造函数,编译器也会自动地帮我们写一个仅是值拷贝的拷贝构造函数.有①仍有②
		class Person
		{
		public:
			Person(int age) //有参构造函数
			{
				m_age = age;
				cout << "Person 有参构造函数调用" << endl;
			}

			int main()
			{
				// Person p1;      //报错 . 无默认构造函数

				Person p1(18);							   //有参构造函数
				Person p2(p1);							   //成功. 拷贝构造函数调用. 如果我们没写拷贝构造函数,编译器也会自动地帮我们写一个仅是值拷贝的拷贝构造函数
				cout << "p2 年龄为: " << p2.m_age << endl; // 18
			}
		}

		// 3. 如果我们只写了拷贝构造函数,但是没写其他普通(无参+有参)构造函数,编译器也不会再提供其他普通(无参+有参)构造函数了.
		//  只要用户写了“高级”的构造函数,“低级”的就不再提供了.'有高就无低':有②则无①
		class Person
		{
		public:
			Person(const Person &p) //拷贝构造函数
			{
				m_age = p.m_age;
				cout << "Person 拷贝构造函数调用" << endl;
			}

			int main()
			{
				// Person p1;          //报错 . 无默认构造函数
				// Person p1(18);      //报错 . 无有参构造函数
			}
		}

		// 12.2.5 深拷贝和浅拷贝(难点)
		// 浅拷贝:简单的赋值拷贝操作.
		// 浅拷贝场景:自己没有写拷贝构造函数,编译器会自动提供一个拷贝构造函数,做浅拷贝操作。就是程序提供的等号赋值工作
		// 浅拷贝带来的问题是:堆区的内存重复释放.先被p1释放,再被p2释放.
		// 浅拷贝问题用深拷贝解决

		// 深拷贝:在堆区重新申请空间,进行拷贝操作.深拷贝指针指向的堆区是不同的,在做释放时就不会出现错误。
		// 如果属性有在堆区开辟的(new),一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

		//浅拷贝
		class Person
		{
		public:
			Person(int age, int height)
			{
				cout << "Person 有参构造函数调用" << endl;

				m_age = age;
				m_height = new int(height);
				// 利用new把身高创建在堆区,用指针接收堆区的数据. 堆区数据由程序员用delete可将其释放,delete写在下方析构函数中
				// m_height是指针,里面存放的值是一串内存地址
				// p1和p2两个对象由于是浅拷贝,对象属性的值和内存地址都一样.在释放的时候,先释放p2,再释放p1的时候,会遇到同一块内存地址重复释放的问题.会报错!!
			}

			// 析构函数 —— 作用是将堆区开辟的数据做释放操作
			~Person()
			{
				cout << "析构函数调用" << endl;
				if (m_height != NULL)
				{
					delete m_height; // 利用delete释放干净
					m_height = NULL; // 防止野指针出现,置空操作. 可省略这一步?
				}
			}

			int m_age;	   // 年龄
			int *m_height; // 身高用指针,目的是把身高的数据开辟到堆区
		};

		void test01() // 栈的规则是先进后出,所以先释放p2,再释放p1.在释放p1的时候,会遇到同一块内存地址重复释放的问题.
		{
			Person p1(18, 180);
			cout << "p1 年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl; // m_height是个指针,因此打印要解引用

			Person p2(p1); // 由于没有自己写的拷贝构造函数,这里会自动用编译器提供的拷贝构造函数,会做浅拷贝操作(值传递)
			cout << "p2 年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;

			// p1和p2由于是浅拷贝,值和内存地址都一样.在释放的时候,先释放p2,再释放p1的时候,会遇到同一块内存地址重复释放的问题.
		}

		int main()
		{
			test01(); // test01()执行完了,就可以释放p2,p1,销毁堆区数据

			system("pause");
			return 0;
		}

		//深拷贝: 写自己的拷贝构造函数即可.
		class Person
		{
		public:
			Person(int age, int height) // 参数构造函数不变
			{
				cout << "Person 有参构造函数调用" << endl;

				m_age = age;
				m_height = new int(height);
			}

			// 自己实现拷贝构造函数,解决浅拷贝带来的问题
			Person(const Person &p)
			{
				cout << "Person 拷贝构造函数调用 " << endl;

				m_age = p.m_age;
				// m_height = p.m_height;  //这是浅拷贝代码,编译器默认实现的代码就是这个
				m_height = new int(*p.m_height); // 解引用为18, 重新在堆区为这个18申请一块内存,返还新的内存地址
			}

			~Person() // 析构函数不变
			{
				cout << "析构函数调用" << endl;
				if (m_height != NULL)
				{
					delete m_height;
					m_height = NULL;
				}
			}

			int m_age;
			int *m_height;
		};

		void test01()
		{
			Person p1(18, 180);
			cout << "p1 年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl; // m_height是个指针,因此打印要解引用

			Person p2(p1); // 用自己写的拷贝构造函数,做深拷贝操作
			cout << "p2 年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
		}

		int main()
		{
			test01(); // test01()函数执行完,p1走p1的析构,p2走p2的析构,没有重复释放的问题

			system("pause");
			return 0;
		}

		// 12.2.6 初始化列表
		// C++提供了初始化列表语法,用来初始化属性
		// 语法:构造函数():属性m_A(值a),属性m_B(值b),属性m_C(值c)... {}
		// Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}

		class Person
		{
		public:
			// 传统方式初始化 利用构造函数初始化
			// Person(int a, int b, int c) {
			//	m_A = a;
			//	m_B = b;
			//	m_C = c;
			//}

			//初始化列表方式初始化
			Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {}
			void PrintPerson()
			{
				cout << "m_A = " << m_A << endl;
				cout << "m_B = " << m_B << endl;
				cout << "m_C = " << m_C << endl;
			}

		private:
			int m_A;
			int m_B;
			int m_C;
		};

		int main()
		{
			Person p(1, 2, 3);
			p.PrintPerson();
		}

		// 12.2.7 类对象作为类成员
		// C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员.
		//语法:
		// class A {}
		// class B  // B类中有对象A作为成员
		// {
		//     A a;  //A为对象成员, a是成员属性
		// }

		//构造的顺序是 :先调用对象成员的构造,再调用本类构造
		//析构顺序与构造相反.原因: 栈区数据 先进后出.

		class Phone // 手机类 —— 人这个类的成员
		{
		public:
			Phone(string pName) // 有参构造函数
			{
				cout << "Phone 有参构造函数调用" << endl;
				m_PName = pName;
			}

			~Phone()
			{
				cout << "Phone 析构函数调用" << endl;
			}

			string m_PName; // 手机品牌名称
		};

		class Person // 人类
		{
		public:
			Person(string name, string pName) : m_Name(name), m_Phone(pName)
			{
				cout << "Person 构造函数调用" << endl;
			}

			~Person()
			{
				cout << "Person 析构函数调用" << endl;
			}

			string m_Name; // 姓名
			Phone m_Phone; // Phone是成员类=对象成员.  m_Phone是成员变量.
						   // 上面的初始化列表实际上就是让这里的 Phone m_Phone = pName, 属于用隐士转换的方法调用构造函数.
		};

		void test01()
		{
			Person p("张三", "苹果MAX");							 //将"张三"传入name,将"苹果MAX"传入pName.  给属性赋值: m_Name="张三", m_Phone="苹果MAX".
			cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl; // m_Phone属性是成员类Phone的实例化. 成员类Phone中另有m_PName属性.

			// 当其他类对象作为本类成员,构造时先构造其他类对象(先phone再person),再构造自身。析构的顺序与构造相反
		}

		int main()
		{
			test01();
			// Phone的构造函数调用
			// Person的构造函数调用
			//张三拿着: 苹果MAX
			// Person的析构函数调用
			// Phone的析构函数调用
		}

		// 12.2.8 静态成员(成员变量和函数加static,重点难点)

		// 12.2.8.1 静态成员变量
		// 静态成员变量特点:
		// 1 在编译阶段分配内存(全局区)(编译阶段:双击运行程序前)
		// 2 类内声明,类外初始化. 类外调用的时候必须为其设置初始值.
		// 3 所有对象共享同一份数据. 所有Person的静态变量信息都是相同的.

		// 静态成员变量两种访问方式
		// 1、通过对象 访问静态成员变量. p.m_A
		// 2、通过类名 访问静态成员变量. Person::m_A

		class Person
		{
		public:
			static int m_A; // 静态成员变量的类内声明.

		private:
			static int m_B; // 静态成员变量也是有访问权限的,设置为private的static,类外不可以访问
		};					//类内

		int Person::m_A = 100; // 静态变量的类外初始化
		int Person::m_B = 300; // 报错. 设置为private的静态变量类外不可以访问

		void test01()
		{
			// 1、通过对象 访问静态成员变量
			Person p;
			cout << p.m_A << endl; // 100

			Person p2;
			p2.m_A = 200;
			cout << p.m_A << endl; // 200 .因为所有对象共享同一份数据

			// 2、通过类名 访问静态成员变量
			cout << "m_A = " << Person::m_A << endl; // 200
													 // cout << "m_B = " << Person::m_B << endl; // 私有权限访问不到
		}

		int main()
		{

			test01();

			system("pause");
			return 0;
		}

		// 12.2.8.1 静态成员函数

		// 静态成员函数特点
		// 1 所有对象共享同一个函数
		// 2 静态成员函数只能访问静态成员变量(要改就全改), 非静态成员变量无法访问(不能针对某一个对象改).

		// 静态成员函数两种访问方式
		// 1. 通过对象进行访问. p.func();
		// 2. 通过类名进行访问. Person::func();  这种方式不用实例化对象

		class Person
		{
		public:
			static void func() // 静态成员函数
			{
				m_A = 100; // 静态成员函数可以访问 静态成员变量(共享). (要改就全改)
				// m_B = 200;    // 静态成员函数 不可以访问 非静态成员变量,无法区分到底是哪个对象的m_B的属性.(不能针对某一个对象改)
				cout << "static void func 调用" << endl;
			}

			static int m_A; // 静态成员变量
			int m_B;		// 非静态成员变量

		private: // 静态成员函数也是有访问权限的,私有不可访问
			static void func2()
			{
				cout << "static void func2 调用" << endl;
			}
		}; //类内

		int Person::m_A = 10; // 静态成员变量的类外初始化. 这里是变量!不是函数! 函数不需要初始化.
		// int Person::m_B = 10;

		void test01()
		{
			// 静态成员函数两种访问方式
			// 1. 通过对象进行访问
			Person p;
			p.func();

			// 2. 通过类名进行访问
			Person::func();
			// Person::func2();//类外访问不到私有的静态成员函数
		}

		int main()
		{
			test01();

			system("pause");
			return 0;
		}

		// 第十三章 对象模型和this指针
		// 13.1 成员变量和成员函数分开存储
		//空对象占用的内存空间为: 1;  只有非静态成员变量才属于类的对象上,其他的大家共享的是一份数据

		// 13.1.1 空对象
		//     C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
		//     每个空对象也应该有个独一无二的内存地址(占坑行为)
		//     空对象占用的内存空间为: 1字节
		//     空对象:类里面是空的,没有属性和成员.此时类实例化的对象就是空对象

		// 空对象.	// 空对象占用的内存空间为: 1
		class Person
		{
		}; //类里面是空的,没有属性和成员.此时类实例化的对象就是空对象

		void test01()
		{
			Person p;
			// 空对象占用的内存空间为: 1
			cout << "size of p=" << sizeof(p) << endl;
		}

		int main()
		{
			test01(); // 1个字节

			system("pause");
			return 0;
		}

		// 13.1.2 非空对象.  成员变量和成员函数分开存储.只有非静态成员变量才属于类的对象上,其他的大家共享的是一份数据

		class Person
		{
			int m_A;			   // 非静态成员变量,属于类的对象上
			static int m_B;		   // 静态成员变量 —— 需要类内声明,类外初始化,不属于类的对象上. 大家共享的是一份数据
			void func() {}		   // 非静态成员函数,不属于类的对象上. 大家共享的是一份数据,只是函数里面有方式能确认是谁在调用该非静态函数-this指针.
			static void func2() {} // 静态成员函数,  不属于类的对象上,大家共享的是一份数据
		};						   //类非空

		int Person::m_B = 10;

		void test02()
		{
			Person p1; // 整个类中只有非静态成员变量int m_A在类的对象上,int空间大小为4个字节,因此实例化的时候,只开辟4个字节的空间.
			cout << "size of p1 =" << sizeof(p1) << endl;
		}

		int main()
		{
			test02(); // 4个字节

			system("pause");
			return 0;
		}

		// 13.2 this指针

		// 在C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象p1,p2,p3会共用一块代码。
		// 问题是:这一块代码是如何区分那个对象调用自己的呢?就是说 假设只有一个函数,对象p1,p2,p3都在使用这个函数,在函数体内部如何区分是p1调的还是p2调的?应该相应修改p1还是p2的属性呢?

		// this指针[指向]被调用的成员函数所属的[对象]. p1调用成员函数,this就指向p1;p2调用成员函数,this就指向p2,以此类推.
		// this指针是包含在每一个非静态成员函数内部的一种指针.
		// this指针不需要定义,直接使用即可

		// this指针的用途:
		//     当形参和成员变量同名时,可用this指针来区分
		//     在类的非静态成员函数中返回对象本身,可使用return *this. 因为this本质上是个指针, *this是在解引用.

		// this指针的本质 是指针常量 指针的指向是不可以修改的

		// 13.2.1 解决冲突问题
		//名称冲突,会报错:
		class Person
		{
		public:
			Person(int age) //这是形参age
			{
				age = age; // 报错.如果成员变量和传入形参的名称一致,编译器会觉得这是一个age,即会将四个age看做一个age
			}
			int age;

			// 解决冲突问题方法一 :使用m_,区分和形参的名称
		public:
			Person(int age) //这是形参age
			{
				m_age = age; // 成员英文member,m_age代表是个成员变量.  //形参age来给成员变量m_age赋值.
			}
			int m_age;

			// 解决冲突问题方法二 :使用this指针(推荐)
			//     p1 在调用有参构造函数,所以this指向被调用的成员函数所属的对象

		public:
			Person(int age) //这是形参age
			{
				this->age = age;
				// this指针指向 被调用成员函数 所属的对象. p1调用成员函数,this就指向p1;p2调用成员函数,this就指向p2,以此类推.
				// this指的是成员属性age,后面是形参age
			}
			int age;
		} //类内

		void
		test01()
		{
			Person p1(10);
			cout << "p1的年龄为:" << p1.age << endl;
		}

		// 13.2.2 返回对象本身用*this
		class Person
		{
		public:
			// 如果开头Person&变成Person的话,会变成值传递,Person就会创建和本体不一样的新数据,调用拷贝构造函数(用值得方式返回,会复制一份新的数据,Person和*this自身是不一样的数据)。通过一次次链式传递,新生成不同于p2的新的对象,每次返回的都是新的对象。
			Person &PersonAddAge2(Person &p) // 第2个Person&表示以引用的方式传入  // 第1个Person&表示以引用的方式返回.
			{
				this->age += p.age; // 将别人的年龄加到自身上面
				return *this;		// this指向p2的指针,*this指向的就是p2这个对象的本体
									// 要返回本体,要用引用的方式返回,不能只用值返回Person
			}
		} //类内

		void
		test01()
		{
			Person p1(10);
			Person p2(10);
			Person p2.PersonAddAge2(p1);							  // 20.  将p1的年龄加到p1身上.
			p2.PersonAddAge2(p1).PersonAddAge2(p1).PersonAddAge2(p1); // 40 . 将p1的年龄加三次到p1身上.
			//用Person& 引用返回,则Person p2.PersonAddAge2(p1)返回的结果是p2;然后p2又可以进一步调用第二个PersonAddAge2(p1),又返回p2出来;然后p2又可以进一步调用第三个PersonAddAge2(p1),又返回p2出来
			// 如果开头Person&变成Person的话,会变成值传递,Person就会创建和本体不一样的新数据.结果是p2只会变成20,即只会加一次,剩下的两个10加到了副本身上
			cout << "p2的年龄为:" << p2.age << endl; // 40
		}

		// 13.3 空指针访问成员函数
		// C++中空指针也是可以调用成员函数的,但是也要注意成员函数中有没有用到this指针. 如果用到this指针,需要加以判断保证代码的健壮性,使用下面程序
		//函数中的this指针不能指向空指针,否则会报错.

		//报错情形:
		class Person
		{
		public:
			void showPersonName()
			{
				cout << "age = " << this->m_Age << endl;
			}
			int m_Age;
		};

		void test01()
		{
			Person *p = NULL;	 //创建空指针
			p->showPersonName(); //空指针调用成员函数.  报错.因为函数中的this此时指向了一个空指针,'this->m_Age '

			Person p1;
			p1.showPersonName();
		}

		int main()
		{
			test01();
		}

		//改错:	修改类中void showPersonName()函数
		class Person
		{
		public:
			void showPersonName()
			{
				// 改错. 报错的原因是传入的指针为空NULL,加入if后没法无中生有
				if (this == NULL)
				{
					return;
				}

				cout << "age = " << this->m_Age << endl;
			}
			int m_Age;
		};

		void test01()
		{
			Person *p = NULL;	 //创建空指针
			p->showPersonName(); //空指针调用成员函数.  报错.因为函数中的this此时指向了一个空指针,'this->m_Age '

			Person p1;
			p1.showPersonName();
		}

		int main()
		{
			test01();
		}

		// 13.4 const 修饰成员函数(常函数 常对象)

		// 13.4.1 常函数:
		// 成员函数后加const的函数为常函数,常函数内不可以修改成员属性      void showPerson() const;  this->m_A = 100;
		// 成员属性声明时加关键字mutable后,在'const常函数'中仍然可以修改值    mutable int m_B; this->m_B = 100;

		class Person
		{
		public:
			void showPerson() const //将showPerson()函数变为常函数
			{
				// this指针的本质 是指针常量 指针的指向是不可以修改的 但指针指向的值可以修改
				// 在成员函数后面加const,修饰的是this指针,让指针指向的值从可以修改 变为不可以修改

				// this = NULL;  //报错. 指针常量 指针的指向是不可以修改的,p调用的,就指向p
				this->m_A = 100; //报错.  //成员函数后加const的函数为常函数,属性值就不可以修改
				this->m_B = 100; //不报错.  //成员属性声明时加关键字mutable后,在const常函数中仍然可以修改值
			}
			void func()
			{
				m_A = 500
			}
			int m_A;
			mutable int m_B; //将m_B变量变为 可变变量
		};

		void test01()
		{
			Person p;
			p.showPerson();
		}

		// 13.4.2 常对象:
		// 声明对象前加const,为常对象. 常对象的属性也不可以修改     const Person p; p.m_A = 100;
		// 成员属性声明时加关键字mutable后,在'const常对象'中仍然可以修改值    mutable int m_B; p.m_B = 100;

		// 常对象只能调用常函数                                 void showPerson() const	; p.showPerson();
		// 常对象不能调用普通函数,因为普通成员函数可以修改属性.       void showPerson() const	; p.func();

		void test02()
		{
			const Person p; // 将p变为常对象
			// p.m_A = 100;// 报错. 对象属性不可以修改
			p.m_B = 100; // m_B是可变变量,在常对象下也可以修改(加上mutable)

			p.showPerson(); // 常对象只能调用常函数
							// p.func();// 常对象不可以调用普通的成员函数,因为普通成员函数可以修改属性,如m_A = 500, 这就侧面地把不能修改的属性修改了
		}

		int main()
		{
			system("pause");
			return 0;
		}

		// 13.5 友元(便于访问私有函数)

		// 生活中你的家(Building)有客厅(Public),有你的卧室(Private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是你也可以允许你的好闺蜜好基友进去。
		// 在程序里,有些私有属性,想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术,友元的目的就是让一个函数或者类 访问另一个类中私有成员,友元的关键字为 friend

		// 13.5.1 全局函数做友元(一个全局函数可以访问另一个类的私有成员)
		//方式:在被访问类中的开头加入一条friend+全局函数声明后,该全局函数就成了类(Building)的好朋友. 该全局函数可以访问该类的实例(building1)的私有属性.
		//	friend void goodGay(Building* building);

		//案例:让一个全局函数goodGay可以访问另一个类Building的私有成员
		// 类(Building)
		class Building
		{
			//方式:在类开头中加入一条friend+全局函数声明后,该全局函数就成了类(Building)的好朋友. 该全局函数可以访问该类的实例(building1)的私有属性.
			friend void goodGay(Building *building);

		public:
			Building() //默认构造函数
			{
				m_SittingRoom = "客厅"; //为属性赋予初值
				m_BedRoom = "卧室";
			}
			string m_SittingRoom; // 客厅
		private:
			string m_BedRoom; // 卧室.  设置为私有属性.
		};

		// 全局函数
		void goodGay(Building * building) // 传进一个实例化对象.  这里是传入指针Building(引用也可以)
		{
			cout << "好基友的全局函数 正在访问:" << building->m_SittingRoom << endl; // 公共属性

			cout << "好基友的全局函数 正在访问:" << building->m_BedRoom << endl; // 私有属性.  //好基友的全局函数 正在访问:卧室
		}

		void test01()
		{
			Building building1;	 // 实例化Building对象
			goodGay(&building1); // 以指针的形式(传入地址)将对象传入全局函数
		}

		int main()
		{
			test01();

			system("pause");
			return 0;
		}

		// 13.5.2 类做友元(一个类的实例化对象可以访问另一个类的私有成员)
		// 案例:让一个类(goodGay)的实例化对象可以访问另一个类(Building)的私有成员m_settingRoom.
		//方式:
		//先分清 被访问类,访问类
		// 在被访问类Building中的开头加入一条friend+访问类声明  friend class GoodGay;

		// 程序运行步骤:
		//     创建goodGay对象gg,创造对象时会调用goodGay的构造函数
		//     GoodGay::GoodGay()写在类外,使用new Builing 创建一个building对象,创造对象时会调用building的构造函数
		//     Building::Building(),里面已经赋好初值了。
		//     gg.visit() 调用visit函数,可以访问building内部维护的m_settingRoom
		//     friend class goodGay:可以让goodGay实例化对象访问Building里面的私有属性

		class Building; // 创建building,告诉编译器一会会写这个类

		class GoodGay //   创建goodgay类
		{
		public:
			GoodGay();	  // 无参构造函数.   //类外写构造函数,见下方.
			void visit(); // 参观函数  用于访问Building中的属性 公共和私有属性.// 类外写成员函数,见下方.

		private:
			Building *building; // 指针类型
		};

		class Building
		{
			friend class GoodGay; //可以让goodGay实例化对象访问Building里面的私有属性

		public:
			Building(); // 无参构造函数,构造函数用于 赋初值. // 类外写构造函数,见下方.

		public:
			string m_SittingRoom; // 客厅

		private:
			string m_BedRoom; // 卧室
		};

		// 类外写Building成员函数
		Building::Building() // 类中的成员函数,Building下面的构造函数Building,赋初值
		{
			m_SittingRoom = "客厅";
			m_BedRoom = "卧室";
		}

		// 类外写GoodGay成员函数1
		GoodGay::GoodGay()
		{
			building = new Building; // 创建建筑物对象
									 // new表示在堆区创建出一个对象,new什么样的类型,就返回什么样的指针
		}

		// 类外写GoodGay成员函数2
		void GoodGay::visit()
		{
			cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
			cout << "好基友类正在访问:" << building->m_BedRoom << endl; //
		}

		void test01()
		{
			GoodGay gg; // 创建goodGay对象,调用goodGay的构造函数
			gg.visit();
		}

		int main()
		{
			test01();

			system("pause");
			return 0;
		}

		// 13.5.2 成员函数做友元
		//让GoodGay类中的成员函数visit1() 可以访问Building中私有成员m_settingRoom

		//方式:
		//先分清 被访问类,访问类
		// 在被访问类Building中的开头加入一条friend+访问类成员函数声明  friend void GoodGay::visit1();

		// 程序运行步骤:
		//     创建goodGay对象gg,创造对象时会调用goodGay的构造函数
		//     GoodGay::GoodGay()写在类外,使用new Builing 创建一个building对象,创造对象时会调用building的构造函数
		//     Building::Building(),里面已经赋好初值了。
		//     gg.visit1() 调用visit1函数,可以访问building内部私有的m_settingRoom
		//     gg.visit2() 调用visit2函数,不可以访问building内部私有的m_settingRoom

		class Building; // 首先声明Building类
		class GoodGay
		{
		public:
			GoodGay();	   // 无参构造函数     // 用类外实现函数
			void visit1(); // 让visit1函数可以访问Building中私有成员
			void visit2(); // 让visit2函数不可以访问Building中私有成员

			Building *building1;
		};

		class Building
		{
			// 告诉编译器,GoodGay类下的visit1成员函数作为本类的好朋友,可以访问私有成员
			friend void GoodGay::visit1();
			// friend void GoodGay::visit2();// 没有这句的话visit2不可以访问私有成员

		public:
			Building(); // 构造函数的声明

		public:
			string m_SittingRoom; // 客厅

		private:
			string m_BedRoom; // 客厅
		};

		// 类外实现成员函数
		Building::Building()
		{
			m_SittingRoom = "客厅";
			m_BedRoom = "卧室";
		}

		GoodGay::GoodGay() // 类外实现
		{
			building1 = new Building;
			// new一个Building对象,返回的是building1指针
		}

		void GoodGay::visit1()
		{
			cout << "visit1 函数正在访问:" << building1->m_SittingRoom << endl;

			cout << "visit1 函数正在访问:" << building1->m_BedRoom << endl;
		}

		void GoodGay::visit2()
		{
			cout << "visit2 函数正在访问:" << building1->m_SittingRoom << endl;

			// cout << "visit2函数正在访问:" << building1->m_BedRoom << endl;  //报错.  成员函数不做友元,不能访问私有成员
		}

		void test01()
		{
			GoodGay gg;
			gg.visit1();
			gg.visit2();
		}

		int main()
		{
			test01();
			system("pause");
			return 0;
		}

		// 第十四章 运算符重载

		// 运算符重载包括 成员函数重载 或者全局函数重载 两种方式.
		// 将系统预设的运算符,用于用户自定义的数据类型,就是运算符重载.
		// 如系统有加号,可以让两个数值相加,但不能让两个对象相加. 运算符重载 可以让 3=0+1 ->> Person p3 = p0 + p1

		//语法: 返回类型 operator重载符号(参数表){重载函数体}
		// Person operator+ (Person& p)	{...}

		// 运算符重载的实质是函数重载.
		// 函数重载满足条件:
		//     同一个作用域(比如都是成员函数)
		//     函数名一样operator
		//     参数不一样,个数,类型,顺序不一样
		//     返回值不作为重载条件

		// 14.1 加号运算符重载
		// 总结1:对于内置的数据类型的表达式的的运算符是不可能改变的
		// 总结2:不要滥用运算符重载
		// 就是相当于重新定义了一个“+”,你可以用加号来做你想做的事,比如在里面写一个交换函数

		// 14.1.1 成员函数重载
		class Person
		{
		public:
			// 方式1、成员函数重载+号, this指的是正在调用的对象,这里this指向的就是p0,p1
			Person operator+(Person &p)
			{
				Person temp;
				temp.m_A = this->m_A + p.m_A;
				temp.m_B = this->m_B + p.m_B;
				return temp;
			}
			int m_A;
			int m_B;
		}; //类内

		void main()
		{
			Person p0;
			p0.m_A = 10;
			p0.m_B = 10;
			Person p1;
			p1.m_A = 20;
			p1.m_B = 20;

			Person p2 = p0.operator+(p1); // 调用方式一 . 成员函数重载的本质
										  // Person p3 = p0 + p1;  // 调用方式二. 全局函数重载和成员函数重载均可以使用这种方式

			cout << "p2.m_A = " << p2.m_A << endl;
			cout << "p2.m_B = " << p2.m_B << endl;
		}

		// 14.1.2 全员函数重载

		class Person
		{
		public:
			int m_a;
			int m_b;
		};

		Person operator+(Person &p1, Person &p2)
		{
			Person temp;
			temp.m_a = p1.m_a + p2.m_a;
			temp.m_b = p1.m_b + p2.m_b;
			return temp;
		}

		void main()

		{
			Person p0;
			p0.m_A = 10;
			p0.m_B = 10;

			Person p1;
			p1.m_A = 20;
			p1.m_B = 20;

			Person p2;
			p2 = operator+(p0, p1); // 全局函数重载的本质
			// p2 = p0 + p1;      // 全局函数重载和成员函数重载均可以使用这种方式

			cout << "p2.m_a:" << p2.m_a << endl;
			cout << "p2.m_b:" << p2.m_b << endl;
		}

		int main()
		{
			test01();
			system("pause");
			return 0;
		}

		// //14.1.3 函数重载+
		// 运算符重载的实质是函数重载.

		class Person
		{
		public:
			int m_A;
			int m_B;
		}; //类内

		Person operator+(Person &p1, int num)
		{
			Person temp;
			temp.m_A = p1.m_A + num;
			temp.m_B = p1.m_B + num;
			return temp;
		}

		void test01()
		{
			Person p0;
			p0.m_A = 10;
			p0.m_B = 10;
			Person p1;
			p1.m_A = 20;
			p1.m_B = 20;

			Person p4 = p0 + 100; // Person + int
			cout << "p4.m_A = " << p4.m_A << endl;
			cout << "p4.m_B = " << p4.m_B << endl;
		}

		int main()
		{
			test01();
			system("pause");
			return 0;
		}

		// 14.2 左移<<运算符重载(可输出自定义数据类型)
		//左移运算符重载 配合友元可以实现输出自定义数据类型

		// 14.2.1 利用成员函数重载左移运算符
		//  结论:成员函数重载的结果为p.operator<<(cout) 简化版本 p << cout,无法实现 cout 在左侧. 因此只能利用全局函数重载左移运算符

		class Person
		{
		public:
			//利用成员函数重载左移运算符
			void operator<<(Person &p)
				//报错.  p.operator<<(p)不成立
				//尝试改为   p.operator<<(cout), 借鉴上一节讲的,简化版的相当于把.operator和<<(需要重载的运算符)省略掉.
				//由于cout本质上是ostream类的对象, 就会变成 ' p << cout ', 也不符合要求.无法实现 cout 在左侧
				//根据观察,发现上一节中的全局函数重载,更能够将两个对象按顺序用重载运算符连接起来,故决定用全局函数重载.全局函数重载的实现方式见下方.

				int m_A;
			int m_B;
		};

		void test01()
		{
			// 方式1
			Person p;
			p.m_A = 10;
			p.m_B = 10;
			cout << p.m_A << endl;
			cout << p.m_B << endl;

			cout << p; // 要通过打印p 能够完整打印出p.m_A 和p.m_B,即上两行一样的效果
		}

		int main()
		{
			test01();

			system("pause");
			return 0;
		}

		// 14.2.2 利用全局函数重载左移运算符

		// 14.2.2.1 利用全局函数打印类中的公共属性
		// cout 是类ostream(输出流)的对象,且全局只能有一个,用引用的方式传递
		//全局函数重载,更能够将两个对象按顺序用重载运算符连接起来

		class Person
		{
		public:
			int m_A;
			int m_B;
		};

		// void operator<<(cout, Person &p)    //报错.  cout 是类ostream(输出流)的对象,对象要用引用传递. 下行为正确代码
		ostream &operator<<(ostream &cout, Person &p)
		{
			cout << "m_A = " << p.m_A << " m_B = " << p.m_B << endl; //将'<<' 运算符重载为 执行该函数体(打印a属性和打印b属性)
			return cout;											 // 解引用ostream,返回cout,可以无限往后追加输入.  cout << p  << "hello "<< endl<<...<<...<<..  ,需注意<<左右两边连接的是对象
		}

		void test01()
		{
			// 方式1
			Person p;
			p.m_A = 10;
			p.m_B = 10;
			cout << p.m_A << endl;
			cout << p.m_B << endl;

			cout << p;		   //能够成功输出"m_A = 10  p.m_B = 10"
			cout << p << endl; // 该行能够成功运行的前提,是cout << p 返回的是一个对象,这个新的对象又重新调用了endl
		}

		int main()
		{
			test01();

			system("pause");
			return 0;
		}

		// 14.2.2.2 利用全局函数打印类中的私有属性(引入友元)

		class Person
		{
			friend ostream &operator<<(ostream &cout, Person &p); //被访问类中加入友元函数声明
		private:
			int m_A; //本例中是私有属性了
			int m_B;
		};

		// void operator<<(cout, Person &p)    //报错.  cout 是类ostream(输出流)的对象,对象要用引用传递. 下行为正确代码
		ostream &operator<<(ostream &cout, Person &p)
		{
			cout << "m_A = " << p.m_A << " m_B = " << p.m_B << endl; //将'<<' 运算符重载为 执行该函数体(打印a属性和打印b属性)
			return cout;											 // 解引用ostream,返回cout,可以无限往后追加输入. cout << p  << "hello "<< endl<<...<<...<<..  ,需注意<<左右两边连接的是变量
		}

		void test01()
		{
			// 方式1, 会报错:
			// Person p;
			// p.m_A = 10;   //会报错. 因为p.m_A是私有属性,p.m_A = 10不在友元函数里, 没法赋初值.
			// p.m_B = 10;
			// cout << p.m_A << endl;
			// cout << p.m_B << endl;

			// cout << p << "hello worle"<<endl;

			// 方式2, 不会报错:
			Person p(10, 10);
			cout << p << "hello worle" << endl; //成功打印 "p.m_A = 10  p.m_B = 10helloworld"
		}

		int main()
		{
			test01();

			system("pause");
			return 0;
		}

		// 14.3 递增运算符++重载(自己定义自增自减)
		// 作用: 通过重载递增运算符,实现自己的整型数据
		// 14.3.1 重载前置 ++ 运算符(返回的是引用)
		// 14.3.2 重载后置 ++ 运算符(返回的是值) !!!重点

		// 14.3.1 首先要理解前置递增和后置递增的区别: 前置递增,++a会先对a进行运算,再返回a. 后置递增,a++会先返回a,返回a后再对a+1
		int a = 10;
		cout << ++a << endl; // 11
		cout << a << endl;	 // 11

		int b = 10;
		cout << b++ << endl; // 10
		cout << b << endl;	 // 11

		//把MyInteger
		class MyInteger
		{
		public:
			MyInteger()
			{
				m_Num = 0;
			}

		private:
			int m_Num;
		}

			MyInteger myint : //相当于int a , 而非Person p.
							  cout
							  << myint
							  << endl; // 0   // a初始值为0
		cout << ++myint << endl;	   // 1
		cout << myint++ << endl;	   // 1
		cout << myint << endl;		   // 2

		// 14.3.2 重载递增运算符

		//先按照上一节内容仿写
		class MyInteger
		{
			friend ostream &operator<<(ostream &cout, MyInteger myint); //友元函数

		public:
			MyInteger()
			{
				m_Num = 0;
			}

		private:
			int m_Num;
		};

		ostream &operator<<(ostream &cout, MyInteger myint)
		{
			out << myint.m_Num;
			return cout;
		}

		int main()
		{
			MyInteger myint;
			cout << myint << endl;	 // 0   //"<<"被重载,是打印"myint.m_Num"的意思
			cout << ++myint << endl; //报错. 上一行能将myint打印为0, 但没法识别++. 需要我们重载++运算符,见下方.

			system("pause");
			return 0;
		}

		// 14.3.2.1 前置递增重载

		class MyInteger
		{
			friend ostream &operator<<(ostream &cout, MyInteger myint); //友元函数

		public:
			MyInteger()
			{
				m_Num = 0;
			}

			//前置递增重载
			MyInteger &operator++()
			{
				m_Num++;
				return *this; //返回自身引用.能够一直对一个数据无限递增. cout << ++(++myint) << endl
			}

		private:
			int m_Num;
		};

		ostream &operator<<(ostream &cout, MyInteger myint)
		{
			cout << myint.m_Num;
			return cout;
		}

		int main()
		{
			MyInteger myint;
			cout << myint << endl;		 // 0
			cout << ++myint << endl;	 // 1
			cout << ++(++myint) << endl; // 2

			system("pause");
			return 0;
		}

		// 14.3.2.2 后置递增重载
		class MyInteger
		{
			friend ostream &operator<<(ostream &cout, MyInteger myint); //友元函数

		public:
			MyInteger()
			{
				m_Num = 0;
			};

			//前置递增重载.  //这里保留前置递增重载函数,是为了介绍下面的int占位参数,有用
			MyInteger &operator++()
			{
				m_Num++;
				return *this; //返回自身引用.能够一直对一个数据无限递增. cout << ++(++myint) << endl
			}

			// 后置递增重载(返回的是值)
			//后置递增a++是先返还a,再让a+1.
			MyInteger operator++(int) // 返回值不可以作为重载的条件,因此如果这里写operator++(),会因为和前置递增的函数定义一样而报错. 这里int代表占位参数,用于区分前置和后置递增.哪怕这个int没有参数进去也可以
									  //为什么这里不返还 MyInteger& , 注意看函数体中的temp,temp局部变量,局部对象在当前函数结束后就被释放,因此局部对象返回引用是非法操作
			{
				MyInteger temp = *this; //这里先记录myint但是不返回. 因为这里不能写return,不然下面代码就不执行了.
				m_Num++;
				return temp; // 最后将记录结果做返回. 注意这里是值返回(temp=1),而不是引用返回.
			}

		}

		private : int m_Num;
	};

	ostream &operator<<(ostream &cout, MyInteger myint)
	{
		cout << myint.m_Num;
		return cout;
	}

	int main()
	{
		MyInteger myint;			 //创建实例化对象 ,Person p
		cout << myint << endl;		 // 0
		cout << myint++ << endl;	 // 0  //myint++ 相当于myint调用operator++函数
		cout << (myint++)++ << endl; // 1

		system("pause");
		return 0;
	}

	// 14.4 赋值运算符=
	// C++编译器至少给一个类添加4个函数:    //之前我们说编译器会自动配置3个函数,现在补充第四个,赋值运算符
	//     默认构造函数(无参,函数体为空)
	//     默认析构函数(无参,函数体为空)
	//     默认拷贝构造函数,对属性进行值拷贝
	//     赋值运算符 operator=, 对属性进行值拷贝.

	//赋值运算符 operator=:
	//系统默认的赋值操作p3 = p2 = p1是浅拷贝(值拷贝),要通过重载变为深拷贝. 一般只要有值拷贝,都会有深浅拷贝的问题.

	//浅拷贝为什么会报错? 释放堆区内存时,p2先执行右边的析构代码,先判断是否为空,不为空,释放内存. p1再执行析构代码,释放相同的堆区内存,
	//处理方法:
	//使用深拷贝,使p2和p1分别指向不同的堆区内存.  要保证p2待指向的新内存是空的,否则会报错: 深拷贝前先判断指针是否为空.
	//不能通过构析函数释放空内存,否则会报错. 防止方法:释放内存前先判断指针是否为空.

	//深浅拷贝问题总结:
	// 如果类中有属性指向堆区(new),做赋值操作时也会出现深浅拷贝问题.
	// 编译系统提供的等号赋值操作,是浅拷贝.
	// 拷贝构造函数 构造另一个对象时,也会出现深浅拷贝问题.

	class Person
	{
	public:
		Person(int age)
		{
			m_Age = new int(age); // new int 返回的是int*,则m_Age是指针.这个在下面成员变量声明时也反映出来了.
								  // 传进参数,如18岁,将年龄数据开辟到堆区. 堆区的数据要手动释放 ,通过析构函数释放
		}

		// 重载赋值运算符=
		Person &operator=(Person &p) //如果这里写为void operator=(Person& p),就不能用'='进行连续赋值.p3 = p2 = p1会报错.
		{
			// 编译器提供的代码是浅拷贝. 如果不改为深拷贝会报错.
			// m_Age = p.m_Age;

			// 先判断是否有属性在堆区,如果有,先释放干净,然后再深拷贝
			if (m_Age != NULL)
			{
				delete m_Age;
				m_Age = NULL;
			}

			// 深拷贝 解决浅拷贝的问题
			m_Age = new int(*p.m_Age); // new一个int类型空间. 如传入p.m_Age值为20,先解析,再用指针接收

			return *this; // 返回自身
		}				  //重载函数

		// 析构函数 堆区的数据要手动释放 —— 有数字就释放
		~Person()
		{
			if (m_Age != NULL)
			{
				delete m_Age;
				m_Age = NULL;
			}
		}
		int *m_Age; // 年龄的指针
	};				//类内

	void test01()
	{
		Person p1(18);
		Person p2(20);
		Person p3(30);

		p3 = p2 = p1; // 赋值操作.将p1的数据赋值传给p2和p3.  系统默认的赋值操作是浅拷贝,要通过重载变为深拷贝,实现方式见上方.

		cout << "p1的年龄为:" << *p1.m_Age << endl; // 18  //p1.m_Age是一个指针
		cout << "p2的年龄为:" << *p2.m_Age << endl; // 18
		cout << "p3的年龄为:" << *p3.m_Age << endl; // 18
	}

	int main()
	{

		test01();
		system("pause");
		return 0;
	}

	// 14.5 关系运算符>,<,==重载
	// 重载关系运算符(>,<,==,!=),可以让两个自定义类型对象进行对比操作.
	// ==可以判断数字1和2是否相等.能否让==来判断对象p1和对象p2是否相等?:
	// Person p1;
	// Person p2;
	// if(p1==p2){
	//     cout<<"p1和p2相等"<<endl;
	// }

	class Person
	{
	public:
		Person(string name, int age)
		{
			m_Name = name;
			m_Age = age;
		};

		// 重载 == 号
		bool operator==(Person &p)
		{
			if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) //自身的名字==传入p的名字 并且 自身的年龄==传入p的 年龄
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		// 重载 != 号
		bool operator!=(Person &p)
		{
			if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		string m_Name;
		int m_Age;
	};

	void test01()
	{
		Person a("孙悟空", 18);
		Person b("孙悟空", 18);

		if (a == b)
		{
			cout << "a 和 b 相等" << endl;
		}
		else
		{
			cout << "a 和 b 不相等" << endl;
		}

		Person c("孙悟空", 18);
		Person d("孙悟空", 20);

		if (c != d)
		{
			cout << "c 和 d 不相等" << endl;
		}
		else
		{
			cout << "c 和 d 相等" << endl;
		}
	}

	int main()
	{
		test01();

		system("pause");
		return 0;
	}

	// 14.6 函数调用运算符 () 重载(仿函数)
	// 由于重载后使用的方式非常像函数的调用,因此称为仿函数
	// 仿函数没有固定写法,非常灵活
	//语法:void operator()(参数列表){ 函数体}  //有两个()()

	// 14.6.1 仿函数和普通函数
	//  仿函数使用起来和函数十分相似.以下是区分:
	//  1.仿函数定义:class 类名 {  void operator()(参数) { } }
	//      函数定义:void 函数名(参数) { }
	//  2.仿函数调用:实例对象(参数);
	//    函数调用:  函数名(参数)

	class MyPrint // 定义一个打印类
	{
	public:
		void operator()(string text) // 用于重载()操作符的仿函数.  string text是参数
		{
			cout << text << endl;
		}
	}; //类内

	//函数和仿函数定义的方式不同
	void MyPrint02(string test) //写另一个打印函数. 这个函数的功能就是打印, 和仿函数做区分, 二者调用的方式很像,但定义的方式不同
	{
		cout << test << endl;
	}

	void test01()
	{
		MyPrint myFunc;
		myFunc("hello world"); //重载函数调用.  // 由于使用起来非常类似于函数调用,因此称为仿函数

		MyPrint02("hello world"); // 普通函数调用. 函数和仿函数调用的方式不同. 二者结果一样
	}

	int main()
	{

		test01();

		system("pause");
		return 0;
	}

	// 14.6.2 仿函数非常灵活;仿函数结合匿名对象
	//仿函数非常灵活,没有固定的手法. 可以传一个参数,也可以传两个参数. 可以返回&,也可以返回值int.  int operator()(int v1, int v2)
	//仿函数结合匿名对象: 先显式法MyAdd()创建匿名对象,然后第二个括号是仿函数.

	class MyAdd // 加法类
	{
	public:
		int operator()(int v1, int v2)
		{
			return v1 + v2;
		}
	};
	void test02()
	{
		MyAdd add;
		int ret = add(10, 10);
		cout << "ret = " << ret << endl; // 20

		//仿函数结合匿名对象
		//先显式法MyAdd()创建匿名对象,然后第二个括号是仿函数
		cout << "MyAdd()(10,10) = " << MyAdd()(10, 10) << endl; // 20
	}

	int main()
	{
		test02();

		system("pause");
		return 0;
	}

	//第十五章 继承
	// 15.1 继承
	//继承是面向对象三大特性之一. 继承的好处:可以减少重复的代码
	// 语法:class 子类: 继承方式 父类
	// class A : public B;
	// A 类称为 子类 或 派生类
	// B 类称为 父类 或 基类

	// 派生类中的成员,包含两大部分:
	//     一类是从基类继承过来的,一类是自己增加的成员
	//     从基类继承过过来的表现其共性,而新增的成员体现了其个性

	class BasePage //公共页面类(父类)
	{
	public:
		void header()
		{
			cout << "首页、公开课、登录、注册...(公共头部)" << endl;
		}
		void footer()
		{
			cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
		}
	};

	class CPP : public BasePage // C++页面类(子类一)
	{
	public:
		void content()
		{
			cout << "C++ 学科视频" << endl;
		}
	};

	class Python : public BasePage // Python页面类(子类二)
	{
	public:
		void content()
		{
			cout << "Python 学科视频" << endl;
		}
	};

	int main()
	{
		// C++页面
		cout << "C++ 下载视频页面如下: " << endl;
		CPP cp;
		cp.header();
		cp.footer();
		cp.content();

		// Python页面
		cout << "Python 下载视频页面如下: " << endl;
		Python py;
		py.header();
		py.footer();
		py.content();

		system("pause");
		return 0;
	}

	// 15.2 继承方式
	// 继承方式一共有三种:
	// 公共继承,   class Son1 :public Base1 {...}   ; 按原权限继承
	// 保护继承,   class Son2 :protected Base1 {...};  父类中比protected更高的权限都变为子类中的protected权限
	// 私有继承,   class Son3 :private Base1 {...}  ;  父类中比private更高的权限都变为子类中的private权限

	//含义:
	// 父类中可能会有公共内容(public), 保护内容(protected), 私有内容(private)
	// 公共成员:类内和类外都可以访问.  子类怎么继承,就变成什么成员
	// 保护成员:类内可以访问.类外不可以访问.
	// 对于父类中私有成员,无论哪种继承方式,子类都无法访问.

	//     子类|公共继承|保护继承|私有继承|
	// |父类   |
	// |公共成员|公共成员|保护成员|私有成员|
	// |保护成员|保护成员|保护成员|私有成员|
	// |私有成员|不可访问|不可访问|不可访问|

	//先定义一个父类,再分几种情况分析
	class Base1
	{
	public:
		int m_A;

	protected:
		int m_B;

	private:
		int m_C;
	};

	// 15.2.1 公共继承
	class Son1 : public Base1
	{
	public:
		void func()
		{
			m_A = 10; // 可访问 public权限,父类中的公共权限成员,到子类中依然是公共权限
			m_B = 10; // 可访问 protected权限,父类中的保护权限成员,到子类中依然是保护权限
					  // m_C = 10; // 不可访问,父类中的私有权限成员,子类访问不到
		}
	}; //类内

	void test01()
	{
		Son1 s1;
		s1.m_A = 100; //其他类只能访问到公共权限
					  // s1.m_B = 100;    //报错. 到了son1中m_B是保护权限,类外访问不到
	}

	// 15.2.2 保护继承
	class Son2 : protected Base1
	{
	public:
		void func()
		{
			m_A = 100; // 可访问 protected权限,父类中的公共成员,到子类中变为保护权限
			m_B = 100; // 可访问 protected权限,父类中保护权限成员,到子类中变为保护权限
					   // m_C = 100; // 父类中的私有成员子类不可访问
		}
	}; //类内

	void test02()
	{
		Son2 s;
		// s.m_A = 100;//在Son2中m_A变为保护权限,因此类外不可访问
		// s.m_B = 100;//在Son2中m_B变为保护权限,因此类外不可访问
	}

	// 15.2.3 私有继承
	class Son3 : private Base1
	{
	public:
		void func()
		{
			m_A = 100; // 可访问 private权限,父类中公共成员,在子类中变为 私有成员
			m_B = 100; // 可访问 private权限,父类中保护成员,在子类中变为 私有成员
					   // m_C;     // 父类中私有成员子类不可访问
		}
	}; //类内

	void test03()
	{
		Son2 s;
		// s.m_A = 100;// 在Son3中m_A变为私有权限,因此类外不可访问
		// s.m_B = 100;// 在Son3中m_B变为私有权限,因此类外不可访问     //结果和保护继承一样
	}

	// 15.2.3.1 孙子继承儿子
	class GrandSon3 : public Son3
	{
	public:
		void func()
		{
			// Son3的私有属性在GrandSon3中都无法访问到
			// m_A;// 都报错
			// m_B;
			// m_C;
		}
	};

	int main()
	{
		system("pause");
		return 0;
	}

	// 15.3 继承中的对象模型

	// 问题:从父类继承过来的成员,哪些属于子类对象中?
	// 结论
	//     父类中所有非静态成员属性都会被子类继承下去
	//     父类中私有成员属性子类只是访问不到,被隐藏了,但是确实被继承下去

	// 拓展:利用开发人员命令提示工具查看对象模型
	// 跳到文件所在盘 D:
	// 跳转到文件路径 cd 具体路径下
	// 查看单个类布局  cl /d1 reportSingleClassLayouts类名 文件名
	//可以看到Son类中有哪些属性

	class Base
	{
	public:
		int m_A;

	protected:
		int m_B;

	private:
		int m_C; // 私有成员只是被隐藏了,但是还是会继承下去. 只是子类没法访问而已.
	};

	// 公共继承
	class Son : public Base
	{
	public:
		int m_D; //子类的自身属性D
	};

	void test01()
	{
		cout << "sizeof Son = " << sizeof(Son) << endl; // 16
		// 父类中所有非静态成员属性都会被子类继承下去
		// 父类中私有成员属性子类只是访问不到,被隐藏了,但是确实被继承下去
	}

	int main()
	{
		test01();

		system("pause");
		return 0;
	}

	// 15.4 继承中构造与析构顺序

	//问题:子类继承父类后,当创建子类对象,也会调用父类的构造函数,父类和子类的构造和析构顺序是谁先谁后?
	//总结:继承中 先调用父类构造函数,再调用子类构造函数.
	//析构顺序与构造相反,先析构子类,再析构父类。

	class Base //父类
	{
	public:
		Base()
		{
			cout << "Base 构造函数!" << endl;
		}
		~Base()
		{
			cout << "Base 析构函数!" << endl;
		}
	};

	class Son : public Base //子类
	{
	public:
		Son()
		{
			cout << "Son  构造函数!" << endl;
		}
		~Son()
		{
			cout << "Son  析构函数!" << endl;
		}
	};

	void test01()
	{
		Son s;
		// Base 构造函数!
		//  Son  构造函数!
		//  Son  析构函数!
		//  Base 析构函数!
	}

	int main()
	{
		test01();
		system("pause");
		return 0;
	}

	// 15.5 继承同名成员处理方法

	// 问题:当子类与父类出现同名的成员(成员函数或成员属性),如何通过子类对象,访问到子类或父类中同名的数据呢?
	//     通过子类对象,访问子类同名成员 直接访问即可                                  s.m_A   s.func();
	//     通过子类对象,访问父类中间同名成员,会优先调用的是子类中的同名成员,除非加作用域    s.Base::m_A    s.Base::func();
	//     父类中重载的成员函数由于函数名也与子类相同,一起被屏蔽了. 除非加作用域            s.Base::func(100);

	class Base
	{
	public:
		Base()
		{
			m_A = 100;
		}

		void func() // 成员函数
		{
			cout << "Base - func() 调用" << endl;
		}

		void func(int a) // 成员函数 的重载函数
		{
			cout << "Base - func(int a) 调用" << endl;
		}

		int m_A;
	}; //类内

	class Son : public Base
	{
	public:
		Son()
		{
			m_A = 200;
		}

		void func() // 子类中有同名成员函数
		{
			cout << "Son - func() 调用" << endl;
		}

		int m_A; //子类中有同名属性
	};			 //类内

	// 15.5.1 同名成员属性处理方式: 如果通过子类对象,访问到父类中间同名成员,需要加作用域
	void test01()
	{
		Son s;
		cout << "Son  下 m_A: " << s.m_A << endl;
		cout << "Base 下 m_A: " << s.Base::m_A << endl; // 通过子类对象,访问到父类中间同名成员
	}

	// 15.5.2 同名成员函数处理方式
	void test02()
	{
		Son s;
		s.func();		// 直接调用的是子类中的同名成员
		s.Base::func(); // 加作用域调用到父类中同名成员函数

		//当子类出现和父类同名的成员函数时,会优先调用子类成员.同时会屏蔽父类中所有的同名成员函数,不管是有参的还是无参的.因此该重载的成员函数由于函数名与子类相同,一起被屏蔽了
		s.Base::func(100); // 如果想访问父类中被隐藏的同名重载成员,需要加作用域
	}

	int main()
	{
		test01();
		test02();

		system("pause");
		return 0;
	}

	// 15.6 继承同名静态成员处理方式
	// 静态成员的特点: 1.编译阶段分配内存。2.所有对象共享同一份数据。3,类内声明 类外初始化
	// 静态成员可以通过对象访问 或 通过类名访问
	// 总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名

	// 15.6.1 同名静态成员属性:
	// 通过对象访问    s.m_A;         s.Base::m_A;    (子类直接访问; 子类通过父类访问;)
	// 通过类名访问    Son::m_A;   Son::Base::m_A;(第一个::代表通过类名方式访问 第二个::代表访问父类的作用域下)

	// 15.6.2 同名静态成员函数:
	// 通过对象访问   s.func();        	s.Base::func();
	// 通过类名访问   Son::func();;   Son::Base::func();

	// 子类出现和父类同名的静态成员函数,也会隐藏父类中所有的同名成员函数.如果想访问父类中被隐藏同名成员,需要加作用域

	class Base
	{
	public:
		static int m_A;

		static void func() // 成员函数
		{
			cout << "Base - static void func()" << endl;
		}

		static void func(int a) // 成员函数 的重载函数
		{
			cout << "Base - static void func(int a)" << endl;
		}

	};					 //类内
	int Base::m_A = 100; //静态成员, 类内声明 类外初始化

	class Son : public Base
	{
	public:
		static int m_A; // 需要赋初值

		static void func() // 子类中有同名成员函数
		{
			cout << "Son - static void func()" << endl;
		}
	};					//类内
	int Son::m_A = 200; //子类中有同名属性

	// 15.6.1同名静态成员属性
	void test01()
	{
		// 1. 通过对象来访问
		cout << "通过对象访问:" << endl;
		Son s;
		cout << "Son 下 m_A = " << s.m_A << endl;		 // 200
		cout << "Base 下 m_A = " << s.Base::m_A << endl; // 100
		cout << endl;

		// 2. 通过类名访问
		cout << "通过类名访问:" << endl;
		cout << "Son 下 m_A = " << Son::m_A << endl;
		cout << "Base 下 m_A = " << Son::Base::m_A << endl; // 第一个::代表通过类名方式访问  第二个::代表访问父类的作用域下

		cout << endl;
	}

	// 15.6.2同名静态成员函数
	void test02()
	{
		// 1.通过对象访问
		cout << "通过对象访问:" << endl;
		Son s;
		s.func();
		s.Base::func();
		cout << endl;

		// 2.通过类名访问
		cout << "通过类名访问:" << endl;
		Son::func();
		Son::Base::func();

		// Son::func(100);  //报错
		// 2.1 直接调用父类中成员函数的(重载),会报错.因为同名的函数也被屏蔽了
		//当子类出现和父类同名的静态成员函数时,会优先调用子类成员.同时会屏蔽父类中所有的同名成员函数,不管是有参的还是无参的.因此该重载的成员函数由于函数名与子类相同,和上面第一个的函数一起被屏蔽了

		Son::Base::func(100); //成功调用父类中成员函数的重载函数   //如果想访问父类中被隐藏同名成员,需要加作用域
		cout << endl;
	}

	int main()
	{
		test01();
		test02();

		system("pause");
		return 0;
	}

	// 15.7 多继承语法(不建议用)

	//     C++允许一个类继承多个类.  一个孩子可以继承多个父亲,吕布语法
	//     语法: class 子类 :继承方式 父类1 , 继承方式 父类2...
	//如 class 吕布 : public 丁原,public 董卓 , public 王允

	//     多继承可能会引发父类中有同名成员出现,需要加作用域区分
	//     C++实际开发中不建议用多继承

	// 多继承语法(不建议使用)
	class Base1
	{
	public:
		Base1() // 无参构造函数
		{
			m_A = 100;
		}
		int m_A;
	}; //父类1

	class Base2
	{
	public:
		Base2() // 无参构造函数
		{
			m_A = 200;
		}
		int m_A;
	}; //父类2

	// 子类 需要继承Base1和Base2
	class Son : public Base1, public Base2
	{
	public:
		Son()
		{
			m_C = 300;
			m_D = 400;
		}

		int m_C;
		int m_D;
	};

	void test01()
	{
		Son s;
		cout << "sizeof Son = " << sizeof(s) << endl; // 16,包括继承父类的两个

		// 当父类中出现了重名的成员,需要加作用域区分.  如本例中,Base1和Base2都有m_A成员属性
		cout << "Base1::m_A = " << s.Base1::m_A << endl; // 100
		cout << "Base2::m_A = " << s.Base2::m_A << endl; // 200
	}

	int main()
	{
		test01();
		system("pause");
		return 0;
	}

	// 15.8 菱形继承

	// 菱形继承概念:
	//     ​两个派生类继承同一个基类
	//     又有某个类同时继承者两个派生类
	//     ​ 这种继承被称为菱形继承,或者钻石继承

	// 菱形继承问题:
	//     羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性.  这个根据我们前面所学,加入作用域即可区分.此时两份数据都保存了
	//     草泥马继承了羊的m_Age,也继承了驼的m_Age.其实我们应该清楚,这份数据我们只需要一份就可以. 虚继承:是谁最后修改谁为准, 只保存一份数据

	// 解决方法:
	//     利用虚继承可以解决菱形继承的问题
	//     在继承之前加上关键字 virtual 变为虚继承
	//     Animal类 称为 虚基类

	// 通过虚继承可以保存最后的数据: 底层实现原理:
	//通过终端cd进入该程序路径, 然后输入cl /d1 reportSingleClassLayoutSheepTuo查询类结构,可看到在类下面,子类继承的并非数据,而是vbptr 虚基类指针
	// vbptr 虚基类指针: v-virtual ,b-base, ptr - pointer
	// 指针中存储一个地址的偏移量,加上就可以指向一个新地址.不同的子类,自父类中继承的虚基类指针,最后都指向同一个地址,因此虚基类最后只会保留一个数据

	class Animal //动物类(虚基类)
	{
	public:
		int m_Age;
	};

	// 羊类
	class Sheep : virtual public Animal //虚继承
	{
	};

	// 驼类
	class Tuo : virtual public Animal //虚继承
	{
	};

	//羊驼类
	class SheepTuo : public Sheep, public Tuo
	{
	};

	void test01()
	{
		SheepTuo st;

		st.Sheep::m_Age = 18;
		st.Tuo::m_Age = 28;

		// 当出现菱形继承的时候,两个父类拥有相同数据,通过虚继承保存最后的数据,即以28为准
		cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; // 28
		cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;	 // 28
	}

	int main()
	{
		system("pause");
		return 0;
	}

	//第十六章 多态
	//多态: 让子类重写父类中的虚函数.

	// 多态是C++面向对象三大特性之一
	//     静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名. 上面我们学到的都是静态多态.
	//     动态多态: 派生类和虚函数实现运行时多态. 一般我们说的多态都是动态多态,本章介绍的也都是动态多态.

	// 静态多态和动态多态区别:静早动晚
	//     静态多态的函数 地址早绑定 - 编译阶段确定函数地址
	//     动态多态的函数 地址晚绑定 - 运行阶段确定函数地址.  执行让猫,狗说话

	// // 16.1 多态基本语法
	// 动态多态满足条件:
	//     有继承关系
	//     子类重写父类中的虚函数.重写: 函数名相同+形参列表中东西相同+函数返回值类型相同(重载不要求返回值相同)。
	//     重写时父类的 virtual 必写,子类的 virtual 可写可不写
	//     用父类指针或引用 接收子类对象  void dospeak(Animal& animal);  dospeak(cat);

	class Animal // 动物类
	{
		// public:
		// 	void speak()	// 原父类成员函数
		// 	{
		// 		cout << "动物在说话" << endl;
		// 	}

	public:
		virtual void speak() // 改为 虚函数 virtual
		{
			cout << "动物在说话" << endl;
		}
	};

	class Cat : public Animal // 猫类
	{
	public:
		void speak() //子类需重写父类中的虚函数; 重写时父类的 virtual 必写,子类的 virtual 可写可不写
		{
			cout << "小猫在说话" << endl;
		}
	};

	//狗类
	class Dog : public Animal
	{
	public:
		void speak() //重写
		{
			cout << "小狗在说话" << endl;
		}
	};

	void dospeak(Animal & animal)
	//动态多态中父类引用可接收 子类的传递对象,允许父子间类型转换,无需强制转换  dospeak(cat);
	//假设你有多个类同时继承一个类,而这个几个类中(包括父类)都有同名方法那么参数直接写父类类名就可以
	{
		animal.speak(); // 加了virtual后,由于加入的对象不同,确定不同函数地址
	}

	void test01()
	{
		Cat cat;
		dospeak(cat); // Animal &animal=cat    //动物在说话(如果父类前不加入virtual)
					  //本意是想让猫来说话->将父类变为虚函数

		Dog dog;
		dospeak(dog); //动物在说话(如果父类前不加入virtual)
		//本意是想让狗来说话->将父类变为虚函数

		// 地址早绑定-动物说话, 地址晚绑定-猫说话,狗说话
		//早绑定:animal类里面的说话函数编译完后已经在内存里的某个地方存在了,不管传入参数是哪个子类,都只执行父类中的函数
		//晚绑定:将 “父类”变成虚职(父类前加入virtual关键字),函数也就不访问它,而访问子类了,
	}
	void test02()
	{
		cout << "sizeof Animal = " << sizeof(Animal) << endl;
		//父类变为虚函数前,sizeof(Animal) = 1 .空类
		//父类变为虚函数后,sizeof(Animal) = 4 .原因:变虚函数后,存储的东西变成了指针,无论什么类型的指针,都占4个字节.
		//这种现象会引出下一节的内容: 多态原理剖析
	}

	int main()
	{
		test01();
		test02();

		system("pause");
		return 0;
	}

	// 16.2 多态原理剖析

	//父类变为虚函数后,sizeof(Animal) =4 .原因:变虚函数后,存储的东西变成了指针(vfptr),无论什么类型的指针,都占4个字节.
	// vfptr虚函数(表)指针,virtual function pointer ,指针会指向一个虚函数表vftable.
	// vftable虚函数表,virtual function table 里面存放虚函数表.
	// vftable虚函数表内部记录一个虚函数的地址.&Animal::speak()
	// vfptr, 区分vbptr虚基类指针

	// Animal类内结构:vfptr -> vftable: &Animal::speak()
	// 子类Cat类内结构:子类继承父类中所有的内容, vfptr -> vftable: &Animal::speak()
	// 但当子类重写父类虚函数,子类中的虚函数表内部,会替换成子类的虚函数地址.vfptr -> vftable: &Cat::speak()
	// 当父类的指针或引用指向子类对象时,发生多态. Animal &animal=cat ,会从cat的虚函数表中找Cat::speak() 并执行猫在叫

	// 多态本质上就是用virtual关键字在底层增加了一层指针,根据传入不同的子类对象,
	// 来指向对应的子类虚函数表.从cat的虚函数表中调用相关的虚函数.

	// 16.3 多态案例1 —— 计算机类
	// 案例描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

	// 多态的优点:
	//     代码组织结构清晰
	//     可读性强
	//     利于前期和后期的扩展以及维护
	//总结:C++开发提倡利用多态设计程序架构,因为多态优点很多

	// 普通写法
	//     如果想扩展新的功能,需要修改源码
	//     在真实开发中,提倡 开闭原则
	//     开闭原则:对扩展进行开放,对修改进行关闭

	// 普通写法
	class Calculator
	{
	public:
		int getResult(string oper) // 传入操作符
		{
			if (oper == "+")
			{
				return m_Num1 + m_Num2;
			}
			else if (oper == "-")
			{
				return m_Num1 - m_Num2;
			}
			else if (oper == "*")
			{
				return m_Num1 * m_Num2;
			}
			// 如果想扩展新的功能,需要修改源码.如想要增加一个指数运算的功能,要修改源码
			// 在真实开发中,提倡 开闭原则.
			// 开闭原则:对扩展进行开放,对修改进行关闭.不让修改,只让扩展.增强原有的功能,而不去修改源代码.
			// 可以学习《设计模式》充分理解开闭原则,单一职责,里氏替换,迪米特法则,依赖倒置 5个基础原则,后续的设计模式都是基于这五个基础原则的演化。
		}

		int m_Num1;
		int m_Num2;
	};

	void test01()
	{
		Calculator c;
		c.m_Num1 = 10;
		c.m_Num2 = 10;

		cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
		cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
		cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
	}

	// 利用多态实现计算器--------------------------------------
	// 多态:父类里面只有虚函数,为抽象类. 子类继承父类并重写同名函数.调用的时候用父类指针接收子类对象,调用子类同名函数.
	// 虽然从代码量来看,多态代码量更多,但是多态:
	//     代码组织结构清晰: 子类单独写,
	//     可读性强
	//     利于前期和后期的扩展以及维护

	class AbstractCalculator // 计算器的抽象类.
	// 里面只有虚函数的类,叫抽象类. 抽象类中什么功能都不写,只有一个getResult()虚函数和运算用的变量.

	{
	public:
		virtual int getResult() // 写成虚函数
		{
			return 0;
		}
		int m_Num1;
		int m_Num2;
	};

	//加法计算器类
	class AddCalculator : public AbstractCalculator
	{
	public:
		int getResult()
		{
			return m_Num1 + m_Num2;
		}
	};

	//减法计算器类
	class SubCalculator : public AbstractCalculator
	{
	public:
		int getResult()
		{
			return m_Num1 - m_Num2;
		}
	};

	//乘法计算器类
	class MulCalculator : public AbstractCalculator
	{
	public:
		int getResult()
		{
			return m_Num1 * m_Num2;
		}
	};

	void test02()
	{
		// 加法运算
		// 创建一个加法计算器的对象. 用父类的指针指向子类的对象,此时发生多态
		//* abc 是父类的指针, new AddCalculator是创建了一个子类的对象
		AbstractCalculator *abc = new AddCalculator; // 用父类的指针指向子类的对象,此时发生多态
		abc->m_Num1 = 100;
		abc->m_Num2 = 100;

		cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
		delete abc; //用完后手动释放堆区数据. 堆区数据除非自己释放,否则堆区只会在整个程序运行完后释放

		//减法运算
		abc = new SubCalculator;
		abc->m_Num1 = 100;
		abc->m_Num2 = 100;

		cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
		delete abc;

		//乘法运算
		abc = new MulCalculator;
		abc->m_Num1 = 100;
		abc->m_Num2 = 100;

		cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
		delete abc;
	}

	int main()
	{
		test01();
		cout << endl;
		test02();

		system("pause");
		return 0;
	}

	// 16.4 纯虚函数和抽象类
	// 在多态中,通常父类中虚函数是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
	// 纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;     //virtual void func() = 0;

	// 抽象类: 只要类中有一个纯虚函数,这个类称为抽象类. 抽象类=接口
	// 抽象类特点:
	// 1.无法实例化对象 (像一个无法下蛋的母鸡)
	// 2.抽象类的子类 必须重写父类中的纯虚函数,否则这个子类也无法实例化对象 Son s;  因为这个子类也属于抽象类

	class Base
	{
	public:
		virtual void func() = 0; // 纯虚函数
	};

	class Son : public Base // 继承
	{
	public:
		virtual void func() // 重写父类中的纯虚函数
		{
			cout << "func 函数调用" << endl;
		};
	};

	void test01()
	{
		// Base b;// 抽象类Base是无法实例化对象,在栈区
		// new Base;// 抽象类是无法实例化对象,在堆区也不行.
		//一句话记住了,只要够虚,就没有对象,大师我悟了

		Son s; //检验:不报错.  子类重写父类中的纯虚函数后(子类加virtual),才能实例化对象

		//调用多态,用父类指针接收子类对象
		Base *base = new Son; // new 什么对象,就调用什么对象 func 函数
		base->func();		  // 调用子类 func 函数
	}

	int main()
	{
		test01();
		system("pause");
		return 0;
	}

	// 16.5 多态案例2 —— 制作饮品
	// 案例描述:
	// 制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料
	// 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

	class AbstractDrinking //基类
	{
	public:
		// 基类里面都写成纯虚函数,不做任何处理
		// 煮水
		virtual void Boil() = 0;
		// 冲泡
		virtual void Brew() = 0;
		// 倒入杯中
		virtual void PourInCup() = 0;
		// 加入辅料
		virtual void PutSomething() = 0;
		// 制作饮品
		void makeDrink() // makeDrink函数是公共的, 子类可以调用父类的公共成员
		{
			Boil();
			Brew();
			PourInCup();
			PutSomething();
		}
	};

	// 制作咖啡:子类
	class Coffee : public AbstractDrinking
	{
	public:
		// 煮水
		virtual void Boil()
		{
			cout << "煮农夫山泉" << endl;
		}
		// 冲泡
		virtual void Brew()
		{
			cout << "冲泡咖啡" << endl;
		}
		// 倒入杯中
		virtual void PourInCup()
		{
			cout << "倒入杯中" << endl;
		}
		// 加入辅料
		virtual void PutSomething()
		{
			cout << "加入糖和牛奶" << endl;
		}
	};

	// 制作茶叶:子类
	class Tea : public AbstractDrinking
	{
	public:
		// 煮水
		virtual void Boil()
		{
			cout << "煮农夫山泉" << endl;
		}
		// 冲泡
		virtual void Brew()
		{
			cout << "冲泡茶叶" << endl;
		}
		// 倒入杯中
		virtual void PourInCup()
		{
			cout << "倒入杯中" << endl;
		}
		// 加入辅料
		virtual void PutSomething()
		{
			cout << "加入枸杞" << endl;
		}
	};

	// 制作函数
	void doWork(AbstractDrinking * abs) // 父类的指针
	{
		abs->makeDrink();
		// 制作完delete
		delete abs; // 堆区数据手动开辟,手动释放
	}

	void test01()
	{
		// 制作咖啡
		doWork(new Coffee); // AbstractDrinking *abs = new Coffee

		cout << " --------------- " << endl;

		// 制作茶叶
		doWork(new Tea); // AbstractDrinking *abs = new Tea

		//不同的子类,可以通过多态共用父类中makeDrink()的接口,一个接口有多种形态,同一个接口可以制作不同的饮品.
	}

	int main()
	{
		test01();

		system("pause");
		return 0;
	}

	// 16.6 虚析构和纯虚析构

	// 使用场景:多态使用时,父类指针无法释放子类对象.
	//        如果子类中有属性开辟到堆区,那么父类指针在释放时只会调用父类自己的析构函数,而无法调用到子类的析构代码,此时堆内存中的子类数据就无法被清除,造成内存泄露

	// 解决方式:将父类中的析构函数改为虚析构或者纯虚析构
	//          本质和虚函数是一样的,在父类调用自己的析构函数时通过vfptr调用实际子类的析构函数

	// 虚析构和纯虚析构共性:
	//     都可以解决父类指针释放子类对象,如果父类没有写成虚析构或者纯虚析构,就不会走子类的虚构代码。
	//     都需要有具体的函数实现,就是虚析构、纯虚析构要进行函数实现。
	// 虚析构和纯虚析构区别:
	//     如果是纯虚析构,该类属于抽象类,无法实例化对象
	//     纯虚析构, 与虚析构只能有一个存在, 需要声明也需要实现
	//     纯虚函数,与纯虚析构不同,只要声明不需要实现

	// 虚析构语法:virtual ~类名(){}
	// 纯虚析构语法:类内声明 —— virtual ~类名() = 0;
	//             类外实现 —— 类名::~类名(){}

	// 总结:
	// ​虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
	// ​如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。有指针情况就会有堆区
	// ​拥有纯虚析构函数的类也属于抽象类

	// 纯虚函数和纯虚析构的区别
	//     纯虚析构, 与虚析构只能有一个存在, 需要声明也需要实现
	//     有了纯虚析构之后,这个类也属于抽象类,无法实例化对象

	// 虚析构和纯虚析构 —— 解决子类中的代码调用不到的问题,存在指针类型,在堆区

	// 16.6.1 父类指针释放子类对象不干净的问题
	class Animal
	{
	public:
		Animal() // 无参构造函数调用
		{
			cout << "Animal 构造函数调用" << endl;
		}

		~Animal() // 析构函数
		{
			cout << "Animal 析构函数调用" << endl;
		}

		// 纯虚函数 speak() .纯虚函数,与后面学的纯虚析构不同,纯虚函数只要声明不需要实现
		virtual void speak() = 0;
	} //类内

	class Cat : public Animal
	{
	public:
		Cat(string name) // 有参构造函数
		{
			cout << "Cat 构造函数调用" << endl;
			m_Name = new string(name); // new 一个string,返回的是string指针,m_Name是一个指针
		}

		virtual void speak() // 子类重写父类纯虚函数,多态的条件
		{
			cout << *m_Name << " 小猫在说话" << endl;
		}

	public:
		string *m_Name; // 让小猫的名称创建在堆区
		//注意一下,这里老师是为了推进下面的课程才这么写的,属性不是必要开辟在堆区

		//加入Cat析构函数. 使得当函数执行完,自动释放存放在堆区的数据m_Name,
		~Cat()
		{
			if (m_Name != NULL)
			{
				cout << "Cat析构函数调用" << endl;
				delete m_Name;
				m_Name = NULL;
				// delete是清空指针区域的内容,并不清空指针(m_Name),NULL是把指针替换为NULL地址
			}
		}

	}; //类内

	void test01()
	{
		Animal *animal = new Cat("Tom"); // new一个对象  会自动调用构造函数.  //Tom小猫在说话
		animal->speak();
		delete animal; //使用delete释放animal这个指针的时候,会自动调用animal析构函数。
					   // Animal 构造函数调用
					   // Cat 构造函数调用
					   // Tom小猫在说话
					   // Animal 析构函数调用

		//发现没走"Cat 析构函数调用",则没走delete m_Name;,堆区数据m_Name没有被释放干净. 本来应该有两个指针要释放,现在释放了animal,没有释放m_Name
		//原因:	父类指针在析构时,不会调用子类中析构函数,导致子类如果有堆区的属性,出现内存的泄露情况
		//内存泄漏(Memory Leak): 是指程序中已动态分配的[堆内存]由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
		//=斩草不除根
		//利用虚析构可以解决父类指针释放子类对象不干净的问题
	}

	int main()
	{
		test01();
		system("pause");
		return 0;
	}

	// 16.6.2  16.6.3 利用虚析构 或 纯虚析构可以解决父类指针释放子类对象不干净的问题
	// 优化思路: 要通过履行"Cat 析构函数调用",将cat类中创建的堆区数据m_Name释放干净.
	// 方式:将Animal类中的"析构"改为"虚析构/纯虚析构"可以解决父类指针释放子类对象不干净的问题. virtual 可以强制调看到子类.

	class Animal
	{
	public:
		Animal() // 无参构造函数调用
		{
			cout << "Animal 构造函数调用" << endl;
		}

		virtual void speak() = 0; // 纯虚函数speak(),与纯虚析构不同,纯虚函数只要声明不需要实现

		// 16.6.2 虚析构 可以解决 父类指针释放子类对象时不干净的问题
		virtual ~Animal() // 析构函数 变为 虚析构,这样Cat析构函数就会调用出来
		{
			cout << "Animal 虚析构函数调用" << endl;
		}

		// 16.6.3  纯虚析构, 与虚析构只能有一个存在, 需要声明也需要实现(实现见类外).函数实现通常写在"{ }"里面
		virtual ~Animal() = 0;
	}; //类内

	// 纯虚析构的实现.纯虚析构必须要实现,而且实现必须写在类外面.
	Animal::~Animal() // Animal作用域 下的纯虚析构函数~Animal()
	{
		cout << "Animal 纯虚析构函数调用" << endl;
	}

	class Cat : public Animal
	{
	public:
		Cat(string name) // 有参构造函数
		{
			cout << "Cat 构造函数调用" << endl;
			m_Name = new string(name); // new 一个string,返回的是string指针,m_Name是一个指针
		}

		virtual void speak() // 子类重写父类
		{
			cout << *m_Name << " 小猫在说话" << endl; // Tom小猫在说话
		}

		~Cat() // 析构函数,用于释放堆区数据
		{
			if (m_Name != NULL)
			{
				cout << "Cat 析构函数调用" << endl;
				delete m_Name;
				m_Name == NULL;
			}
		}

	public:
		string *m_Name;
	};

	void test01()
	{
		Animal *animal = new Cat("Tom"); // new一个对象  会自动调用构造函数
		animal->speak();
		delete animal; //使用delete释放animal这个父类指针的时候,会自动调用父类析构函数。
					   // Animal 构造函数调用
					   // Cat 构造函数调用
					   // Tom小猫在说话
					   // Cat 析构函数调用
					   // Animal 析构函数调用
	}

	int main()
	{
		test01();
		system("pause");
		return 0;
	}

	// 16.7 多态案例3 —— 电脑组装
	// 16.7.1 抽象每个零件的类.CPU, 显卡, 内存条三个抽象父类.
	// 16.7.2 电脑类.电脑的类中有三个指针维护三个零件:CPU, 显卡, 内存条
	// 16.7.3 具体零件厂商:Inter厂商, Lenovo厂商.
	// 16.7.4 创建电脑

	// 16.7.1 抽象出每个零件的类
	// 1.抽象CPU类
	class CPU // 抽象类
	{
	public:
		// 抽象计算函数
		virtual void calculate() = 0;
	};

	// 2.抽象显卡类
	class VideoCard // 抽象类
	{
	public:
		// 抽象显示函数
		virtual void display() = 0;
	};

	// 3.抽象内存条类
	class Memory // 抽象类
	{
	public:
		// 抽象存储函数
		virtual void storage() = 0;
	};

	// 16.7.2 电脑类
	class Computer
	{
		// 构造函数中传入三个零件指针
	public:
		Computer(CPU *cpu, VideoCard *vc, Memory *mem)
		{
			m_cpu = cpu;
			m_vc = vc;
			m_mem = mem;
		}

		// 提供工作函数
		void work()
		{
			// 让零件工作起来,调用接口
			m_cpu->calculate();
			m_vc->display();
			m_mem->storage();
		}

		// 提供析构函数 释放3个电脑零件
		~Computer()
		{
			// 释放CPU零件
			if (m_cpu != NULL)
			{
				delete m_cpu;
				m_cpu = NULL;
			}
			// 释放显卡零件
			if (m_vc != NULL)
			{
				delete m_vc;
				m_vc = NULL;
			}
			// 释放内存条零件
			if (m_mem != NULL)
			{
				delete m_mem;
				m_mem = NULL;
			}
		}

	private:
		// 电脑的类中有三个指针维护三个零件
		CPU *m_cpu;		 // CPU的零件指针
		VideoCard *m_vc; // 显卡的零件指针
		Memory *m_mem;	 // 内存条零件指针
	};

	// 16.7.3 具体零件厂商
	// Inter厂商
	class InterCpu : public CPU
	{
	public:
		virtual void calculate()
		{
			cout << "Inter CPU开始计算" << endl;
		}
	};

	class InterVideoCard : public VideoCard
	{
	public:
		virtual void display()
		{
			cout << "Inter 显卡开始显示" << endl;
		}
	};

	class InterMemory : public Memory
	{
	public:
		virtual void storage()
		{
			cout << "Inter 内存条开始存储" << endl;
		}
	};

	// 联想Lenovo厂商
	class LenovoCpu : public CPU
	{
	public:
		virtual void calculate()
		{
			cout << "Lenovo CPU开始计算" << endl;
		}
	};

	class LenovoVideoCard : public VideoCard
	{
	public:
		virtual void display()
		{
			cout << "Lenovo 显卡开始显示" << endl;
		}
	};

	class LenovoMemory : public Memory
	{
	public:
		virtual void storage()
		{
			cout << "Lenovo 内存条开始存储" << endl;
		}
	};

	// 16.7.4 组装不同的电脑
	void test01()
	{
		//第一台电脑组装,CPU为父类,InterCpu是子类
		CPU *intelCpu = new InterCpu; // 多态技术,用父类的指针指向子类对象
		VideoCard *intelCard = new InterVideoCard;
		Memory *intelMem = new InterMemory;
		cout << "第一台电脑开始工作:" << endl;

		//创建第一台电脑
		Computer *computer1 = new Computer(intelCpu, intelCard, intelMem);
		computer1->work();
		delete computer1;
		cout << "-----------------------" << endl;

		cout << "第二台电脑开始工作:" << endl;
		//第二台电脑组装
		Computer *computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
		;
		computer2->work();
		delete computer2;
		cout << "-----------------------" << endl;

		cout << "第三台电脑开始工作:" << endl;
		//第三台电脑组装
		Computer *computer3 = new Computer(new LenovoCpu, new InterVideoCard, new LenovoMemory);
		;
		computer3->work();
		delete computer3;
	}

	int main()
	{
		test01();
		system("pause");
		return 0;
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值