C++笔记

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

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

 

 

 

 

 

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

#include  //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"<No matching PlatformTransactionManager bean found for qualifier 'add' - neither
                                    林鹤霄

                                    
java.lang.IllegalStateException: No matching PlatformTransactionManager bean found for qualifier 'add' - neither qualifier match nor bean name match!   网上找了好多的资料没能解决,后来发现:项目中使用的是xml配置的方式配置事务,但是
  • Row size too large (> 8126). Changing some columns to TEXT or BLOB aigo column
    原文:http://stackoverflow.com/questions/15585602/change-limit-for-mysql-row-size-too-large   异常信息: Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAM
  • JS 格式化时间 alxw4616 JavaScript
    /** * 格式化时间 2013/6/13 by 半仙 [email protected] * 需要 pad 函数 * 接收可用的时间值. * 返回替换时间占位符后的字符串 * * 时间占位符:年 Y 月 M 日 D 小时 h 分 m 秒 s 重复次数表示占位数 * 如 YYYY 4占4位 YY 占2位<p></p> * MM DD hh mm
  • 队列中数据的移除问题 百合不是茶 队列移除
         队列的移除一般都是使用的remov();都可以移除的,但是在昨天做线程移除的时候出现了点问题,没有将遍历出来的全部移除,  代码如下;      // package com.Thread0715.com; import java.util.ArrayList; public class Threa
  • Runnable接口使用实例 bijian1013 javathreadRunnablejava多线程
    Runnable接口 a.       该接口只有一个方法:public void run(); b.       实现该接口的类必须覆盖该run方法 c.       实现了Runnable接口的类并不具有任何天
  • oracle里的extend详解 bijian1013 oracle数据库extend
    扩展已知的数组空间,例: DECLARE TYPE CourseList IS TABLE OF VARCHAR2(10); courses CourseList; BEGIN -- 初始化数组元素,大小为3 courses := CourseList('Biol 4412 ', 'Psyc 3112 ', 'Anth 3001 '); --
  • 【httpclient】httpclient发送表单POST请求 bit1129 httpclient
    浏览器Form Post请求 浏览器可以通过提交表单的方式向服务器发起POST请求,这种形式的POST请求不同于一般的POST请求 1. 一般的POST请求,将请求数据放置于请求体中,服务器端以二进制流的方式读取数据,HttpServletRequest.getInputStream()。这种方式的请求可以处理任意数据形式的POST请求,比如请求数据是字符串或者是二进制数据 2. Form
  • 【Hive十三】Hive读写Avro格式的数据 bit1129 hive
     1. 原始数据 hive> select * from word; OK 1 MSN 10 QQ 100 Gtalk 1000 Skype      2. 创建avro格式的数据表   hive> CREATE TABLE avro_table(age INT, name STRING)STORE
  • nginx+lua+redis自动识别封解禁频繁访问IP ronin47
    在站点遇到攻击且无明显攻击特征,造成站点访问慢,nginx不断返回502等错误时,可利用nginx+lua+redis实现在指定的时间段 内,若单IP的请求量达到指定的数量后对该IP进行封禁,nginx返回403禁止访问。利用redis的expire命令设置封禁IP的过期时间达到在 指定的封禁时间后实行自动解封的目的。 一、安装环境: CentOS x64 release 6.4(Fin
  • java-二叉树的遍历-先序、中序、后序(递归和非递归)、层次遍历 bylijinnan java
    import java.util.LinkedList; import java.util.List; import java.util.Stack; public class BinTreeTraverse { //private int[] array={ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; private int[] array={ 10,6,
  • Spring源码学习-XML 配置方式的IoC容器启动过程分析 bylijinnan javaspringIOC
    以FileSystemXmlApplicationContext为例,把Spring IoC容器的初始化流程走一遍: ApplicationContext context = new FileSystemXmlApplicationContext ("C:/Users/ZARA/workspace/HelloSpring/src/Beans.xml&q
  • [科研与项目]民营企业请慎重参与军事科技工程 comsci 企业
         军事科研工程和项目 并非要用最先进,最时髦的技术,而是要做到“万无一失”    而民营科技企业在搞科技创新工程的时候,往往考虑的是技术的先进性,而对先进技术带来的风险考虑得不够,在今天提倡军民融合发展的大环境下,这种“万无一失”和“时髦性”的矛盾会日益凸显。。。。。。所以请大家在参与任何重大的军事和政府项目之前,对
  • spring 定时器-两种方式 cuityang springquartz定时器
    方式一: 间隔一定时间 运行 <bean id="updateSessionIdTask" class="com.yang.iprms.common.UpdateSessionTask" autowire="byName" /> <bean id="updateSessionIdSchedule
  • 简述一下关于BroadView站点的相关设计 damoqiongqiu view
    终于弄上线了,累趴,戳这里http://www.broadview.com.cn   简述一下相关的技术点   前端:jQuery+BootStrap3.2+HandleBars,全站Ajax(貌似对SEO的影响很大啊!怎么破?),用Grunt对全部JS做了压缩处理,对部分JS和CSS做了合并(模块间存在很多依赖,全部合并比较繁琐,待完善)。   后端:U
  • 运维 PHP问题汇总 dcj3sjt126com windows2003
    1、Dede(织梦)发表文章时,内容自动添加关键字显示空白页 解决方法: 后台>系统>系统基本参数>核心设置>关键字替换(是/否),这里选择“是”。 后台>系统>系统基本参数>其他选项>自动提取关键字,这里选择“是”。   2、解决PHP168超级管理员上传图片提示你的空间不足 网站是用PHP168做的,反映使用管理员在后台无法
  • mac 下 安装php扩展 - mcrypt dcj3sjt126com PHP
    MCrypt是一个功能强大的加密算法扩展库,它包括有22种算法,phpMyAdmin依赖这个PHP扩展,具体如下: 下载并解压libmcrypt-2.5.8.tar.gz。 在终端执行如下命令: tar zxvf libmcrypt-2.5.8.tar.gz cd libmcrypt-2.5.8/ ./configure --disable-posix-threads --
  • MongoDB更新文档 [四] eksliang mongodbMongodb更新文档
    MongoDB更新文档 转载请出自出处:http://eksliang.iteye.com/blog/2174104 MongoDB对文档的CURD,前面的博客简单介绍了,但是对文档更新篇幅比较大,所以这里单独拿出来。 语法结构如下: db.collection.update( criteria, objNew, upsert, multi) 参数含义 参数   
  • Linux下的解压,移除,复制,查看tomcat命令 y806839048 tomcat
    重复myeclipse生成webservice有问题删除以前的,干净 1、先切换到:cd usr/local/tomcat5/logs 2、tail -f catalina.out 3、这样运行时就可以实时查看运行日志了 Ctrl+c 是退出tail命令。 有问题不明的先注掉   cp /opt/tomcat-6.0.44/webapps/g
  • Spring之使用事务缘由(3-XML实现) ihuning spring
      用事务通知声明式地管理事务   事务管理是一种横切关注点。为了在 Spring 2.x 中启用声明式事务管理,可以通过 tx Schema 中定义的 <tx:advice> 元素声明事务通知,为此必须事先将这个 Schema 定义添加到 <beans> 根元素中去。声明了事务通知后,就需要将它与切入点关联起来。由于事务通知是在 <aop:
  • GCD使用经验与技巧浅谈 啸笑天 GC
    前言 GCD(Grand Central Dispatch)可以说是Mac、iOS开发中的一大“利器”,本文就总结一些有关使用GCD的经验与技巧。 dispatch_once_t必须是全局或static变量 这一条算是“老生常谈”了,但我认为还是有必要强调一次,毕竟非全局或非static的dispatch_once_t变量在使用时会导致非常不好排查的bug,正确的如下: 1
  • linux(Ubuntu)下常用命令备忘录1 macroli linux工作ubuntu
    在使用下面的命令是可以通过--help来获取更多的信息1,查询当前目录文件列表:ls ls命令默认状态下将按首字母升序列出你当前文件夹下面的所有内容,但这样直接运行所得到的信息也是比较少的,通常它可以结合以下这些参数运行以查询更多的信息:  ls / 显示/.下的所有文件和目录  ls -l 给出文件或者文件夹的详细信息 ls -a 显示所有文件,包括隐藏文
  • nodejs同步操作mysql qiaolevip 学习永无止境每天进步一点点mysqlnodejs
    // db-util.js var mysql = require('mysql'); var pool = mysql.createPool({ connectionLimit : 10, host: 'localhost', user: 'root', password: '', database: 'test', port: 3306 });
  • 一起学Hive系列文章 superlxw1234 hiveHive入门
      [一起学Hive]系列文章 目录贴,入门Hive,持续更新中。   [一起学Hive]之一—Hive概述,Hive是什么 [一起学Hive]之二—Hive函数大全-完整版 [一起学Hive]之三—Hive中的数据库(Database)和表(Table) [一起学Hive]之四-Hive的安装配置 [一起学Hive]之五-Hive的视图和分区 [一起学Hive
  • Spring开发利器:Spring Tool Suite 3.7.0 发布 wiselyman spring
    Spring Tool Suite(简称STS)是基于Eclipse,专门针对Spring开发者提供大量的便捷功能的优秀开发工具。   在3.7.0版本主要做了如下的更新:   将eclipse版本更新至Eclipse Mars 4.5 GA Spring Boot(JavaEE开发的颠覆者集大成者,推荐大家学习)的配置语言YAML编辑器的支持(包含自动提示,