当前位置: 首页 > news >正文

设计定制型网站建设沈阳妇科医院排名最好的医院

设计定制型网站建设,沈阳妇科医院排名最好的医院,五 网站开发总体进度安排,有哪些外贸公司网站做的比较好1. 简介 1、面向对象程序设计 面向对象的四大特性 1#xff09;封装 2#xff09;继承 3#xff09;多态 4#xff09;抽象 2、标准库 标准C由三个部分组成 1#xff09;核心语言#xff1a;提供了所有的构件块 2#xff09;C标准库#xff1a;提供了大量的函…1. 简介 1、面向对象程序设计 面向对象的四大特性 1封装 2继承 3多态 4抽象 2、标准库 标准C由三个部分组成 1核心语言提供了所有的构件块 2C标准库提供了大量的函数 3标准模版库STL提供了大量的方法 2. 基本语法 2.1 变量 变量的作用变量存在的意义方便操作内存中的数据。 1内存中的数据可以通过 内存地址编号 得到2给内存起名称变量方便管理这段内存。变量的定义、变量的声明 变量可以多次声明但是只能被定义一次。 可以使用extern关键字在任何地方声明一个变量。 #include iostreamusing namespace std;// 变量声明 extern int a; extern double b;int main() {// 变量定义int a;double b;// 实际初始化a 10;b 20.0;cout a endl;cout b endl;return 0; }2.2 常量 常量记录程序中不可修改的数据。 定义常量的两种方式 1#define 常量名 常量值 // 通过宏进行定义。2const 数据类型 常量名 常量值 // const修饰的变量2.3 关键字 关键字标识符 标识符命令规则 1不能是关键字 2只能是数字、字母、下划线 3第一个字符必须为字母或者下划线 4标识符中字母区分大小写 2.4 数据类型 数据类型存在的意义给变量分配一个合适的内存空间。 2.4.1 类型修饰符 signed unsigned short long long long 2.4.2 基本内置类型 bool 1 char 1 int 4 float 4 double 8 void 无类型 wchar_t 宽字符型 注意⚠️使用endl在每一行后会插入一个换行符用于向屏幕传递多个值 1、 整型 2、 实型浮点型 默认情况下输出一个小数会显示6位有效数字。 1单精度float 4。 float f1 3.14f; // 这里的**f代表前面的float**。默认情况下编译器把小数当成双精度会去进行类型转换2双精度double 8 3、 字符型 ‘a’ 对应的ASCII码是97 ‘A’ 对应的ASCII码是65 char 1 字符型变量并不是把字符本身存储到内存中存储而是将对应的ASCII编码放入到存储单元中。 作用用于表示单个字符 注意 1使用单引号括起来2单引号里只能有一个字符不可以是字符串。查看字符型变量对应的ASCII码 ch a; cout (int)ch endl; // 974、 字符串类型使用双引号 C语言风格的字符串char 变量名[] “字符串值” C语言风格的字符串string 变量名 “字符串值”。需要一个头文件 #include 5、 布尔类型bool bool 1 6、 转义字符 \n \t \v \c \r 2.4.3 typedef 声明 为已有的类型int/bool/char等定义一个新的名称。 typedef type newname; 例如typedef int feet; // feet是int类型的一个新名称 2.4.4 枚举enumeration类型 一个变量只有几种可能的值则可以定义为枚举类型。即将变量的值一一列举出来变量的值只能在列举出的值的范围内 例子 enum color { red, green 5, blue } c; c blue; 说明创建枚举类型color的变量c并为c赋值为blue。 2.4.5 类型转换 将一个类型转换为另一个类型 四种类型转换 1静态转换Static Cast 2动态转换Dynamic Cast 3常量转换Constant Cast 4重新解释转换Reinterpret Cast 确实C提供了四种类型转换运算符每种都有其特定的用途。下面简要介绍这四种转换以及它们的应用场景 1. 静态转换Static Cast: 语法static_castTargetType(expression) 使用场景它是最通用的转换形式用于将一种类型转换为另一种类型例如基本数据类型的转换如 float 转 int指向基类的指针或引用转为指向派生类的指针或引用等。 注意此转换在编译时进行检查但不执行运行时类型检查。 float f 3.14; int i static_castint(f); // i will be 32. 动态转换Dynamic Cast: 语法dynamic_castTargetType(expression) 使用场景主要用于处理多态时的指针和引用转换。它在运行时进行检查以确保所请求的转换是安全的和有效的。 注意此转换通常用于将基类指针转换为派生类指针。 class Base {}; class Derived : public Base {}; Base *b new Derived; Derived *d dynamic_castDerived *(b); // Safe conversion3. 常量转换Constant Cast: 语法const_castTargetType(expression) 使用场景用于修改表达式的常量性例如从const类型转换为非const类型或从volatile类型转换为非volatile类型。 注意这只改变类型的const或volatile属性并不改变实际数据。 const int ci 10; int *nonConstIntPtr const_castint *(ci);4. 重新解释转换Reinterpret Cast: 语法reinterpret_cast(expression) 使用场景对任何指针或整数类型进行低级别的强制类型转换。它的用途通常是将一种类型的指针转换为另一种类型的指针。 注意此转换可能是不安全的因为它不进行任何类型检查或转换只是简单地告诉编译器将数据重新解释为另一种类型。 int i 10; void* ptr reinterpret_castvoid *(i);请注意尽管C提供了这些强制转换运算符但最佳实践是尽量避免使用它们除非在某些必要的情况下。当可能的时候总是首选C提供的安全和自动的类型转换。 5. 数据输入/输出 cin a; cout Hello endl;2.5 运算符 2.5.1 算数运算符 两个整数相除结果依然是整数 只有整型int变量可以进行取模运算 /–区别 前置递增/后置递增 – 前置递减/后置递减 前置后置的区别 前置先进行变量的加1减1操作然后进行表达式的计算。 后置先进性表达式的计算然后进行变量的加1减1操作。 2.5.2 关系运算符 2.5.3 逻辑运算符 与 || 或 ! 非 注意⚠️在C中除了0都为真。 2.5.4 位运算符 位运算符作用于位并逐位执行操作。 、|、^按位与运算符、按位或运算符、按位异或运算符 ~、、取反运算符、二进制左移运算符、二进制右移运算符 2.5.5 杂项运算符 1sizeofsizeof运算符 返回变量的大小 2Condition ? X : Y 条件运算符三目运算符 3.点和-箭头成员运算符用于引用类、结构和共用体的成员。【访问结构的成员时使用点运算符通过指针访问结构的成员时使用箭头运算符】 4Cast强制转换运算符 5指针运算符 返回变量的地址 6*指针运算符* 指向一个变量 3. 程序流程结构 顺序结构 选择结构 循环结构 3.1 选择结构 1.1 三目运算符 表达式1 ? 表达式2 : 表达式3 1.2 if 1.3 switch… case… 3.2 循环结构 3.2.1 while #include iostream using namespace std;// time系统时间头文件 #include ctimeint main(){// 1、生成一个随机数// 添加随机数种子 作用利用当前系统时间生成随机数防止每次随机数一样的问题出现伪随机数的出现srand((unsigned int)time(NULL));int num rand() % 100 1; // 这样生成的是伪随机数。需要添加随机数种子进行生成可以防止生成伪随机数。while (1){int val 0;cin val;if (val num){cout 猜测的数字过大 endl;}else if (val num){cout 猜测的数字过小 endl;}else if (val num){cout 恭喜您猜对了 endl;// 猜对之后退出猜测游戏。退出当前循环。break;} }return 0; }3.2.2 do…while do…while与while的区别是do…while会先执行一次循环语句再判断循环条件。 #include iostream using namespace std;int main(){// 输出09数字int num 0;do{cout num endl; // 先执行一次循环输出0再判断循环条件。num ;}while (num 10);return 0; }案例 水仙花数 水仙花数是三位数。每个位上的数字求三次幂之后相加等于这个三位数。 例如1^3 5^3 3^3 153 利用do…while求出所有3位数中的水仙花数。 #include iostream using namespace std;/* 水仙花数 水仙花数是三位数。每个位上的数字求三次幂之后相加等于这个三位数。 例如1^3 5^3 3^3 153 利用do...while求出所有3位数中的水仙花数。 */int main(){/*1、打印所有三位数2、从所有三位数中找到水仙花数*/int num 100;do{// 2.1 获取三位数的个位、十位、百位上的数字。int a 0;int b 0;int c 0;a num % 10; // 获取三位数的个位上数字b num / 10 % 10; // 获取三位数的十位上数字。整数相除得到的也是整数。c num / 100; // 获取三位数的百位上数字// 2.2 判断是否是水仙花数是则输出这个数字。if ((a * a * a b * b * b c * c * c)num){cout num endl;}num;} while (num 1000);return 0;}3.2.3 for循环 案例 敲桌子 #includeiostream using namespace std;/* 敲桌子 0100数字。个位、十位有7或者是7的倍数敲桌子。 如果不是输出这个数字。 */int main(){// 1、输出0100的这些数字。for (int i 0; i 100; i){// 2、找出特殊数字输出“敲桌子”if (i % 7 0 || i % 10 7 || i / 10 7){cout 敲桌子 endl;}else{cout i endl; }}return 0; }3.2.4 嵌套循环 实例 打印乘法口诀表 #include iostream using namespace std;int main(){for (int i 1; i 9; i){for (int j 1; j i; j){int eq i * j;cout j * i eq ;}cout endl; }return 0; }3.3 跳转语句break/continue 3.3.1 break语句 作用跳出整个选择结构或者整个循环结构 break使用的时机 1、出现在switch循环中。作用是终止case并跳出switch 2、出现在for循环中。作用是跳出当前循环 3、出现在嵌套循环语句中。跳出最近的内部循环语句 案例 嵌套循环 switch语句 break #include iostream using namespace std;int main(){cout 请选择难易程度 endl;cout 1、简单程度 endl;cout 2、中等程度 endl;cout 3、困难程度 endl;int select 0;cin select;switch (select){case 1:std::cout 简单 std::endl;break;case 2:std::cout 中等 std::endl;break;case 3:std::cout 困难 std::endl;break;default:break;}return 0; }案例 break的作用 c #include iostream using namespace std;int main(){for (int i 1; i 10; i){for (int j 1; j 10; j){if (j 5){break;}cout * ;}cout endl; }return 0; }3.3.2 continue 跳出循环中的本次循环不再向下执行本次循环执行下一次循环。 #includeiostream using namespace std;int main(){for (int i 0; i 100; i){if (i % 2 0) // 对2取模为0表示这个数是偶数{continue; // 跳出本次循环进行下一次的循环}cout i endl;}return 0;}3.3.3 goto 跳转到另一部分代码进行执行。 4. 一维数组和指针 数组名含义指向数据首地址的指针 4.1 指针的算数运算 取地址符 获得是一个变量的地址返回的是指向这个变量的指针。 #include iostream using namespace std;int main(){char a;double b;cout a的地址是 (long long)a endl; // 获得指向该变量的指针存储的是该变量的首地址cout a的地址1值是 (long long)(a 1) endl; //cout b的地址值是 (long long)b endl; // 首地址cout b的地址1值是 (long long)(b 1) endl; // 首地址return 0; }/* a的地址是6128757771 a的地址1值是6128757772 b的地址值是6128757760 b的地址1值是6128757768 */4.2 数组的地址 #include iostream using namespace std;int main(){int arr[5];cout arr的首地址 (long long)arr endl;cout a[0]的地址是 (long long)arr[0] endl;cout a[1]的地址是 (long long)arr[1] endl;cout a[2]的地址是 (long long)arr[2] endl;cout a[3]的地址是 (long long)arr[3] endl;cout a[4]的地址是 (long long)arr[4] endl;cout a[0] 1的值是 (long long)(arr[0] 1) endl;// 对指针进行算数运算int * p arr;cout p 0 endl;cout p 1 endl;cout p 0的值 (long long)(p 0) endl;cout p 1的值 (long long)(p 1) endl;cout p 2的值 (long long)(p 2) endl;return 0; }/* arr的首地址6165801972 a[0]的地址是6165801972 a[1]的地址是6165801976 a[2]的地址是6165801980 a[3]的地址是6165801984 a[4]的地址是6165801988 a[0] 1的值是61658019760x16f82abf40x16f82abf8 p 0的值6165801972 p 1的值6165801976 p 2的值6165801980 */注意的点 1、 数组的特点 1存放具有相同数据类型的元素 2数组元素存放在连续的内存空间中。 2、 一维数组的三种定义方式 3、 通过下标访问数组中的数据 4、 利用循环的方式输出数组中的元素。 int arr[5] {val1, val2, val3}; 输出后的arr[3] 0 , arr[4] 0。 5、 一维数组名的两种用途 1统计数组在内存中的长度 sizeof(arr); 2获取数组在内存中的首地址。 cout arr endl; 练习1: 寻找小猪体重最大的值 #include iostreamusing namespace std;int main(){int max 0;int arr[5] {300, 350, 500, 400, 250};for (int i 0; i 5; i){if (arr[i] max){max arr[i];} }cout 最重的小猪体重为 max endl;return 0;}练习2: 数组元素逆置首尾元素互换 #includeiostreamusing namespace std;int main(){int arr[] {21, 78, 11, 54, 12};int start 0;int end sizeof(arr) / sizeof(arr[0]) - 1;while (start end){int temp arr[start];arr[start] arr[end];arr[end] temp;start;end--;}for (int i 0; i 5; i){cout arr[i] endl;}return 0;}4.3 数组和指针的关系 数组名相当于指向数组首元素的指针 #include iostreamint main() {int arr[5] {1, 2, 3, 4, 5};// 使用数组名来访问数组元素std::cout Array elements using array name: std::endl;for (int i 0; i 5; i) {std::cout arr[i] ;}std::cout std::endl;// 使用指针来访问数组元素int *ptr arr; // 将数组名赋给指针std::cout Array elements using a pointer: std::endl;for (int i 0; i 5; i) {std::cout ptr[i] ;}std::cout std::endl;return 0;}/*数组名可以视为指向数组首元素的指针。*/5. 二维数组 5.1 二维数组的定义/输出 #includeiostreamusing namespace std;int main(){// 二维数组的定义// 1、第一种定义方式int arr01[2][3] {{1, 4, 2}, {7, 10, 3}};// 2、第二种定义方式int arr02[2][3];arr02[0][0] 1;arr02[0][1] 4;// 3、第三种定义方式int arr03[2][3] {1, 4, 2, 7, 10, 3};// 4、第四种定义方式int arr04[][3] {1, 4, 2, 7, 10, 3}; // 可省略行数但是列数不能省略// 二维数组的输出for (int i 0; i 2; i){for (int j 0; j 3; j){cout arr01[i][j] ;}}}5.2 二维数组的数组名的作用 1二维数组占用的内存空间 sizeof(arr) 2二维数组的首地址 cout arr endl; #includeiostreamusing namespace std;int main(){int arr[2][3] {{1, 4, 2}, {7, 10, 3}};// 数组名的作用// 1. 数组占用的内存空间cout 二维数组占用的内存空间 sizeof(arr) endl;cout 二维数组第一行元素占用的内存空间 sizeof(arr[0]) endl;cout 二维数组第一个元素占用的内存空间 sizeof(arr[0][0]) endl;cout 二维数组的行数 sizeof(arr) / sizeof(arr[0]) endl;// 2. 查看二维数组的首地址cout 二维数组的首地址 (long)arr endl;cout 二维数组的第一行的首地址 (long)arr[0] endl;cout 二维数组的第二行的首地址 (long)arr[1] endl;cout 二维数组的第一个元素的首地址 (long)arr[0][0] endl; // 查看一个数的首地址需要加取地址符cout 二维数组的第二个元素的首地址 (long)arr[0][1] endl;return 0;}实例 成绩的统计 #includeiostreamusing namespace std;#includestringint main(){// 考试成绩的统计int scores[3][3] {{100, 100, 100}, {90, 50, 100}, {60, 70, 80}};string names[3] {张三, 李四, 王五};for (int i 0; i 3; i){int sum 0;for (int j 0; j 3; j){sum scores[i][j];} cout names[i] 的总分 sum endl; }return 0;}6. 函数 作用将一段经常使用的代码进行封装减少重复代码 6.1 函数的定义/调用 实现一个加法函数功能传入两个整型数据计算数据相加的结果并且返回。 // 定义加法函数 // num1和num2为形参 int add(int num1, int num2) // 返回值类型 函数名参数列表{函数体 return表达式} {int sum num1 num2;return sum; }#includeiostream using namespace std;int main(){int a 7;int b 8;// a,b为实参int sum add(a, b);cout sum endl;return 0;}6.2 值传递 值传递就是函数调用时实参将数值传入给形参 值传递时如果形参发生变化并不影响实参 #includeiostream using namespace std;// 函数之“值传递” void swap(int num1, int num2){cout 交换前的值;cout num1的值: num1 endl;cout num2的值: num2 endl;cout 交换后的值;int temp num1;num1 num2;num2 temp;cout num1的值: num1 endl;cout num2的值: num2 endl;return; // 当函数返回值类型为void时 可以不写也可这样写}int main(){int a 10;int b 22;swap(a, b);cout 值传递时如果形参发生任何改变并不影响实参 endl;cout a的值: a endl;cout b的值: b endl;return 0;}6.3 函数的样式 1. 无参无返 2. 无参有返 3. 有参无返 4. 有参有返 6.4 函数的声明 可以将函数往主函数后面写提前使用函数声明告诉编译器这个函数是存在的。 #include iostreamusing namespace std;// 函数声明 // 提前告诉编译器函数的存在可以利用函数的声明 int max(int num1, int num2);int main(){int a 10;int b 20;cout max(a, b); return 0;}int max(int num1, int num2) {return num1 num2 ? num1 : num2; // 三目运算符 }6.5 函数分文件编写 作用让代码结构更加清晰 函数分文件编写步骤 1创建.h头文件 2创建.cpp源文件 3在.h头文件中写函数的声明 4在.cpp源文件中写函数的定义 swap.h #include iostream using namespace std; void swap(int a, int b);swap.cpp #include “swap.h”void swap(int a, int b) {int tmp a;int a b;int b tmp;cout “a ” a endl;cout “b ” b endl;}func.cpp #include “swap.h”int main() {int a 10;int b 12;swap(a, b);return 0; }7. 指针 指针的作用通过指针间接访问内存 指针存储的是内存地址 内存编号从0开始记录一般使用十六进制数字表示 利用指针变量保存地址 7.1 指针变量的定义/使用 #include iostreamusing namespace std;int main() {// 1、指针的定义int a 10;// 指针定义的语法数据类型 *指针变量名int *p;// 让指针指向变量a的地址p a;cout a的地址为: a endl;cout 指针p为: p endl;// 2、使用指针// 通过解引用的方式找到指针指向的内存// 指针前加 * 代表解引用找到指针指向的内存中的数据*p 1000;cout a a endl;cout *p *p endl;return 0; }/* a的地址为:0x16b3b2ba8 指针p为:0x16b3b2ba8 a 1000 *p 1000 */7.2 指针占用的内存空间 指针也是一种数据类型占用内存空间多大指针这种数据类型占用的内存大小 在32位操作系统下占用4个字节 在64位操作系统下占用8个字节 #include iostream using namespace std;int main() {int a 10;int *p a;// 查看指针数据类型占用的内存空间注意在64/32位操作系统下占用内存空间不同cout sizeof(int *) sizeof(int *) endl;cout sizeof(double *) sizeof(double *) endl;return 0; }7.3 空指针和野指针 7.3.1 空指针 空指针指针变量指向内存编号为0的空间 用途初始化指针变量 int *p NULL; 注意空指针指向的内存不能访问 // 指针变量p指向内存地址编号为0的空间 **int *p NULL;**// 访问空指针报错 // 内存编号0255为系统占用内存不允许用户访问 7.3.2 野指针/悬空指针 野指针指针变量指向非法内存空间。不确定指针具体指向。 悬空指针指针变量最初指向的内存已经被释放的指针。 注意野指针/空指针都不是我们申请内存空间因此不要访问。 7.4 const修饰指针 const修饰指针的3种情况 1. char *const cp; const修饰的cp在里面所以cp指向的地址是不能改变的但它所指向的内容是可以改变的指针常量 2. char const *pc1; *pc1被const所修饰也就是指针所指向的对象所以它指向的对象是不能改变的但是它指向的地址是可以改变的常量指针 3. const * const ptr 特点指针指向不可以修改值也不可以修改 7.5 指针和数组 作用指针访问数组元素 注意ptr; // 操作 这是因为定义的数组申请的是连续的内存空间当ptrptr是int类型的指针变量地址空间向后偏移是以int类型的大小为单位进行偏移的相当于int类型数组元素的向后偏移 #include iostreamusing namespace std;int main() {int arr[10] {11, 92, 24, 51, 78, 19, 20, 45, 61, 78};int *ptr arr; // 指向数组的指针即指针指向数组首元素的的首地址cout arr endl;cout arr[1] endl;cout sizeof(int *) endl;for (int i 0; i 10; i){cout *ptr endl;ptr; // 操作的是指针指向的数据}}7.6 指针和函数 参数传递的两种方式 1、值传递 2、地址传递 #include iostream using namespace std;void swap01(int a, int b); void swap02(int *p1, int *p2);int main() {int a 20;int b 31;// 1、值传递swap01(a, b);cout a a endl;cout b b endl;// 2、地址传递// 如果是地址传递可以修饰实参swap02(a, b);cout a a endl;cout b b endl; }void swap01(int a, int b) {int temp a;a b;b temp;cout swap01 a a endl;cout swap01 b b endl; }void swap02(int *p1, int *p2) {int temp *p1;*p1 *p2;*p2 temp;cout swap02 *p1 *p1 endl;cout swap02 *p2 *p2 endl; }7.7 指针、数组、函数 数组名可以表示数组的首地址即int *arr arr 实例 实例描述封装一个函数利用冒泡排序实现对整型数组的升序排序 #include iostream using namespace std;void bubblesort(int *arr, int len); void printArray(int *arr, int len);int main() {int arr[5] {1, 10, 42, 3, 0};int len sizeof(arr) / sizeof(arr[0]);bubblesort(arr, len); // 数组名可以表示数组的首地址即int *arr arr;printArray(arr, len); }void bubblesort(int *arr, int len) {for (int i 0; i len - 1; i){for (int j 0; j len - i - 1; j){if (arr[j] arr[j 1]){int temp arr[j];arr[j] arr[j 1];arr[j 1] temp;}}} }void printArray(int *arr, int len) {for (int i 0; i 5; i){cout arr[i] endl;} }7.8 多级指针 int a 10; // 定义一个整型变量 aint *p a; // 定义一个指针 p它指向 a 的地址int **q p; // 定义一个二级指针 q它指向 p 的地址cout a endl; // 输出 a 的值为 10cout *p endl; // 输出 p 所指向的变量的值为 10cout **q endl; // 输出 q 所指向的指针所指向的变量的值为 107.9 智能指针 自动垃圾回收自动管理生存期防止内存泄漏 智能指针的分类和用法: 在C中智能指针是一种对象它表现得像指针但有自动内存管理的功能。当智能指针被销毁或超出作用范围时它指向的内存会被自动释放。下面是C中最常见的三种智能指针及其用法示例 1. std::unique_ptr std::unique_ptr 是一种独占式智能指针它指向的对象在任何时刻都是唯一的没有其他的智能指针可以指向相同的对象。当 unique_ptr 超出作用范围时它会自动销毁所指向的对象。 示例 #include memory #include iostreamclass MyClass { public:void classMethod() {std::cout MyClass method called! std::endl;} };int main() {std::unique_ptrMyClass uniquePtr std::make_uniqueMyClass();uniquePtr-classMethod(); // 调用方法return 0; } // uniquePtr 超出作用范围其指向的内存被自动释放2. std::shared_ptr std::shared_ptr 是一种共享式智能指针多个 shared_ptr 可以指向同一个对象该对象和其相关资源会在最后一个指向它的 shared_ptr 被销毁时释放。 示例 #include memory #include iostreamclass MyClass { public:~MyClass() {std::cout MyClass destroyed! std::endl;} };int main() {std::shared_ptrMyClass sharedPtr1 std::make_sharedMyClass();{std::shared_ptrMyClass sharedPtr2 sharedPtr1;// 此时sharedPtr1 和 sharedPtr2 都指向同一个对象} // sharedPtr2 超出作用范围但由于 sharedPtr1 仍然存在所以对象不会被销毁return 0; } // sharedPtr1 超出作用范围它指向的对象现在被销毁3. std::weak_ptr std::weak_ptr 是一种非拥有性智能指针它指向一个由 std::shared_ptr 管理的对象但不增加该对象的引用计数。weak_ptr 主要用来解决 shared_ptr 之间的循环引用问题。 示例 #include memory #include iostreamclass MyClass { public:~MyClass() {std::cout MyClass destroyed! std::endl;} };int main() {std::weak_ptrMyClass weakPtr;{std::shared_ptrMyClass sharedPtr std::make_sharedMyClass();weakPtr sharedPtr;// 在这里由于 weakPtr 不增加引用计数所以即使 weakPtr 存在// 当 sharedPtr 超出作用范围时它指向的对象也会被销毁。} // sharedPtr 超出作用范围// 在这里由于对象已被销毁因此 weakPtr 现在为空。if (weakPtr.expired()) {std::cout Object pointed to by weakPtr has been destroyed. std::endl;}return 0; }可以看到不同类型的智能指针是如何在不同情况下管理内存的。使用智能指针可以自动管理内存确保再合适的时间释放对象占用的内存减少内存泄漏已分配内存未进行释放且程序已经失去对这部分内存的控制或引用无法使用或释放它 的风险。 4. autoC11已经弃用 7.9.1 智能指针的底层实现 智能指针在 C 中是一个类其目的是模仿原生指针的行为同时提供自动化的内存管理。智能指针的底层实现依赖于 C 的对象和内存管理特性包括构造函数、析构函数、重载的操作符等等。这里我们以两种最常见的智能指针为例详细说明其底层是如何实现的 1. std::unique_ptr: std::unique_ptr 实现了独占所有权的概念意味着同一时间只有一个 unique_ptr 可以指向某个内存区域。当这个 unique_ptr 被销毁时它所指向的内存也会被释放。 核心实现机制 - 构造函数用于绑定到原始指针获取对应内存的所有权。 - 移动构造函数和移动赋值操作符unique_ptr 不能被复制但可以移动意味着它们的所有权可以转移。 - 析构函数当 unique_ptr 的生命周期结束时析构函数会自动被调用释放其指向的内存。 - 重载的箭头和解引用操作符使 unique_ptr 在使用时更接近原始指针。 2. std::shared_ptr: std::shared_ptr 实现了共享所有权的概念允许多个 shared_ptr 指向同一个内存区域。shared_ptr 使用引用计数来跟踪指向某块内存的智能指针数量。只有当最后一个 shared_ptr 不再指向该内存时该内存才会被释放。 核心实现机制 - 构造函数类似于 unique_ptr用于绑定到原始指针。 - 拷贝复制构造函数和赋值操作符允许指针所有权的共享。每次复制时引用计数会增加。 - 析构函数减少引用计数并在引用计数变为零时释放内存。 - 重载的箭头和解引用操作符使其行为类似原始指针。 除此之外shared_ptr 通常会使用一个控制块来存储引用计数和其他管理信息。这个控制块是在堆上分配的额外内存用来跟踪有多少个 shared_ptr 和 weak_ptr另一种相关的智能指针不增加引用计数但可以观察对象指向同一个资源。 这两种智能指针都是通过 RAII (Resource Acquisition Is Initialization) 模式来管理资源即在构造时获取资源在析构时释放资源。通过这种方式智能指针能有效预防内存泄露确保在出现异常时资源能被正确释放。 7.9.2 智能指针特性的例子 1、unique_ptr #include memoryclass MyClass {int data; public:MyClass(int d) : data(d) {} };int main() {std::unique_ptrMyClass ptr1 std::make_uniqueMyClass(10); // 创建一个指向MyClass的unique_ptr// std::unique_ptrMyClass ptr2 ptr1; // 编译错误因为不能复制unique_ptrstd::unique_ptrMyClass ptr3 std::move(ptr1); // 现在ptr3拥有对象ptr1不再指向对象// 当ptr3离开作用域时对象会被自动删除return 0; }2、shared_ptr #include memoryint main() {std::shared_ptrint ptr1 std::make_sharedint(20); // 引用计数为1std::shared_ptrint ptr2 ptr1; // 引用计数增加到2ptr2.reset(); // 引用计数减少到1// 当ptr1离开作用域时引用计数变为0对象被删除return 0; }3、weak_ptr #include memoryint main() {std::shared_ptrint shared std::make_sharedint(30);std::weak_ptrint weak shared;std::shared_ptrint shared2 weak.lock(); // 从weak_ptr创建shared_ptrshared.reset(); // 释放对象的所有权if(weak.expired()) { // 检查对象是否还活着std::cout Object has been deleted! std::endl;}return 0; } 8. 引用 8.1 指针和引用的不同 引用 r是 year 的一个别名在内存中 r 和 year 占有同一个存储单元指针不同指针有自己的内存空间。 1不存在空引用。引用必须连接到一块合法的内存存在空指针。 int x 42;int ref x; // 合法的引用连接到 x// int ref2; // 错误引用必须在声明时初始化2一旦引用被初始化为一个对象就不能被指向到另一个对象了。指针可以在任何时候指向到另一个对象 int a 10, b 20;int ref a; // 引用 ref 连接到 a// ref b; // 错误不能改变引用 ref 连接的对象int *ptr a; // 指针 ptr 存储 a 的地址ptr b; // 合法指针 ptr 指向了 b3引用必须在声明时被初始化。指针可以在任何时间被初始化。 int x 42;int ref x; // 创建引用并初始化// int ref; // 错误引用必须在创建时初始化int *ptr; // 创建指针ptr x; // 指针在稍后初始化为 x 的地址4引用本身没有地址只是对象的一个别名。 指针是附属在内存位置上的标签引用是原变量的别名。可以通过原始变量名称或引用访问变量的内容 通过原始变量名访问变量名称int i 7;通过引用 int r i; // 将i声明为引用变量 double s d; // 初始化为d的double类型的引用s。 8.2 实例 #include iostream using namespace std;int main() {int i;int j;int r i;int s d;i 7;cout i i endl;cout r r endl;j 119;cout j j endl;cout s s endl;return 0; }8.3 引用使用 作用给变量起别名 数据类型 别名 原名; 无论操作原名还是别名都是操作的同一块内存空间。 #include iostream using namespace std;int main() {int i;int j;int r i;int s j;// 无论对原名还是引用名操作都是操作的同一块内存i 7;cout i i endl;cout r r endl;j 119;cout j j endl;cout s s endl;return 0;}/* i 7 r 7 j 119 s 119 */8.4 引用注意事项 1引用必须初始化 int a 10;int b a;2引用初始化后不能再改变 int a 10;int b 12;int c a;int c b; // 这是错误的引用初始化后不能再进行改变8.5 引用作函数参数 作用函数传参时利用引用让形参修饰实参。 三种参数传递方式 数值传递 不能修饰实参 地址传递 可以修饰实参 1传入函数的指针是原指针的一个拷贝此时存在两个指针两个指针指向一个内存空间同时指向原对象 2在函数中不改变拷贝指针的指向时修改拷贝指针指向的值相当于修改原指针指向的值 3在函数中改变拷贝指针的指向时只是改变了拷贝指针的指向不改变原指针的指向所以不改变原指针指向的对象。 引用传递 可以修饰实参 需要初始化才可以使用初始化后不能更改 1引用作为函数参数传递时实质上传递的是实参本身 #include iostream using namespace std;// 1、数值传递 void swap01(int a, int b) {int temp a;a b;b temp;}// 2、地址传递 void swap02(int *a, int *b) {int temp *a;*a *b;*b temp;}// 3、引用传递 void swap03(int a, int b) {int temp a;a b;b temp;}int main() {int a 10;int b 20;/* // 数值传递swap01(a, b);cout a endl;cout b endl;1020*//* // 地址传递swap02(a, b);cout a endl;cout b endl;2010*//* // 引用传递swap03(a, b);cout a endl;cout b endl;2010*/}8.6 引用做函数的返回值 注意 不要返回局部变量因为局部变量存储在栈区函数运行结束后编译器释放掉这部分内存的引用。可以通过给局部变量加static的方式变成全局变量全局变量存放在全局区全局区是在程序运行结束后释放就可以返回了。函数调用可以作为左值。 #include iostream using namespace std;// 不要返回局部变量的引用 int func() {int a 10;return a;}int func02() {// 通过加static使其存在于全局区函数执行结束后不会被释放掉这块内存static int a 10;return a;}int main() {// int ref func();// cout ref endl;int ref func02();cout ref endl; // 10// 作为左值func02() 1000; // a 1000cout ref endl; // 1000return 0;}8.7 引用的本质 引用本质上是 指针常量指针常量的特点指向不可以改值可以改 8.8 常量引用 作用常量引用主要用于修饰形参防止误操作。 引用必须引到一块合法的内存空间 const int ref 10; // 编译器将代码修改为 int temp 10; const int ref temp; int ref 10; // 这样写是错的 9. 结构体 用户自定义的数据类型允许用户存储不同的数据类型 #include iostreamusing namespace std;#include string// 结构体的定义 struct Student {string name;int age;int score;} stu3;int main() {// 通过结构体创建结构体变量的三种方式// 1、struct 结构体名 变量名;// 2、struct 结构体名 变量名 {成员1值成员2值};// 3、定义结构体时顺便创建变量struct Student stu1; // struct 关键字可以省略stu1.name 张三;stu1.age 12;stu1.score 100;struct Student stu2 {李四, 42, 98};stu3.name 王五;stu3.age 8;stu3.score 99;return 0;}9.1 结构体数组 将自定义的结构体放入到数组中方便维护 #include iostreamusing namespace std;#include string// 结构体 struct student {string name;int age;int score;};int main() {// 2、创建结构体数组struct student arr[3] {{张三, 12, 99},{王五, 54, 100},{李四, 66, 79}};// 3、给结构体数组中的元素赋值arr[2].name 赵六;arr[2].age 60;arr[2].score 59;// 4、遍历结构体数组for (int i 0; i 3; i){cout name: arr[i].name age: arr[i].age score: arr[i].score endl;}return 0;}9.2 结构体指针 通过“-”访问结构体变量中的成员属性 #include iostreamusing namespace std;// 通过指针访问结构体变量的成员属性值 // 定义结构体 struct student {string name;int age;int score;};int main() {// 创建结构体变量并赋值struct student stu {张三, 12, 88};// 通过指针访问结构体变量的成员属性值struct student *p stu;cout name: p-name age: p-age score: p-score endl; return 0;}9.3 结构体嵌套结构体 结构体中的成员是另一个结构体 #include iostreamusing namespace std;#include stringstruct student {string name;int age;int score;};struct teacher {int id;string name;int age;struct student stu;};int main() {struct teacher t1;t1.id 11;t1.name 王老师;t1.age 32;t1.stu.name 李学生;t1.stu.age 22;t1.stu.score 88;cout 老师id t1.id 老师姓名 t1.name 老师年龄 t1.age 学生姓名 t1.stu.name 学生年龄 t1.stu.age 学生分数 t1.stu.score endl;return 0;}9.4 结构体做函数参数 9.4.1 结构体作为 值传递 9.4.2 结构体作为 地址传递 #include iostreamusing namespace std;#include stringvoid printArray01(struct student stu);void printArray02(struct student *p);// 通过指针访问结构体变量的成员属性值 // 定义结构体 struct student {string name;int age;int score;};int main() {// 创建结构体变量并赋值struct student stu {张三, 12, 88};// 1、值传递printArray01(stu);// 2、地址传递printArray02(stu);return 0;}void printArray01(struct student stu) {cout name: stu.name age: stu.age score: stu.score endl;return; }void printArray02(struct student *p) {cout name: p-name age: p-age score: p-score endl;return; }9.5 结构体中const使用场景 作用使用const防止误操作 #include iostream using namespace std; #include stringvoid printArray(struct student *p);struct student {string name;int age;int score;};int main() {struct student stu;stu.name 张三;stu.age 28;stu.score 100;printArray(stu);return 0; }void printArray(const struct student *p) // 参数是地址传递(提高代码性能值传递是对值的复制如果是指针的话只需要占用4/8个字节)如果修改就会修改原始数据加const防止误修改** {cout name: p-name age: p-age score: p-score endl;return;}案例1 #include iostream using namespace std; #include string #include ctimevoid allocateSpace(struct Teacher t[], int len);void printInfo(struct Teacher t[], int len);struct Student {string sName;int score;};struct Teacher {string tName;struct Student sArray[5]; };int main() {// 随机数种子使生成随机数的时候真正随机起来srand((unsigned int)time(NULL));struct Teacher t[3];int len sizeof(t) / sizeof(t[0]);allocateSpace(t, len);printInfo(t, len);}void allocateSpace(struct Teacher t[], int len) {string nameSeed ABCDE;for (int i 0; i len; i){t[i].tName Teacher_;t[i].tName nameSeed[i];for (int n 0; n 5; n){t[i].sArray[n].sName Student_;t[i].sArray[n].sName nameSeed[n];t[i].sArray[n].score rand() % 61 40; // 生成060之间的随机数分数在40100之间}}}void printInfo(struct Teacher t[], int len) {for (int i 0; i len; i){cout t[i].tName endl;for (int j 0; j 5; j){cout sName: t[i].sArray[j].sName score: t[i].sArray[j].score endl;}}}案例2 创建结构体数组并按照age进行冒泡升序排序打印结果 #include iostream using namespace std; #include stringvoid printInfo(struct hero hArray[], int len); void sortArray(struct hero hArray[], int len);struct hero {string name;int age;string gender;};int main() {struct hero hArray[5] {{刘备, 23, 男},{关羽, 22, 男},{张飞, 20, 男},{赵云, 21, 男},{貂蝉, 19, 女}};int len sizeof(hArray) / sizeof(hArray[0]);printInfo(hArray, len);sortArray(hArray, len);printInfo(hArray, len);}void printInfo(struct hero hArray[], int len) {for (int i 0; i len; i){cout name: hArray[i].name age: hArray[i].age gender: hArray[i].gender endl;}}void sortArray(struct hero hArray[], int len) {for (int i 0; i len - 1; i){for (int j 0; j len - i - 1; j){if (hArray[j].age hArray[j 1].age){// 注意这一块可以直接写成交换数组元素位置的方式struct hero temp hArray[j];hArray[j] hArray[j 1];hArray[j 1] temp;}}} }C核心编程 1. 内存分区模型 1.1 程序运行前、运行后 C程序执行时将内存划分为4个区域 1、代码区 1存放CPU执行的指令 2共享需要频繁执行的程序只需要在内存中保留一份即可 3只读防止程序意外修改指令。 2、全局区存放全局变量、静态变量以及常量 全局区还包含常量区字符串常量和其他常量const修饰的一些变量也存放在此处 该区域的数据在程序结束后由操作系统释放。 程序运行前存在的区域代码区、全局区 程序运行后的区域栈区、堆区 3、栈区编译器自动分配释放存放局部变量、函数的参数值等。 1函数运行结束后编译器将局部变量自动释放 2不要返回局部变量栈区的数据的地址因为函数运行结束后变量内存栈区的数据被释放再使用指针进行操作就是非法操作了。 #include iostreamusing namespace std;// 栈区存放的数据 以及栈区需要注意的点 int *fun(int b) {int a 10; // 局部变量 存放在栈区栈区的数据在**函数执行结束之后**由编译器自动释放return a; // 返回局部变量的地址 }int main() {int b 65;int *p fun(b);cout *p endl; // 第一次可以打印出 10 是因为编译器做了保留cout *p endl; // 第二次打印的时候乱码 编译器是放掉了这块内存return 0; }4、堆区程序员分配释放程序员不释放程序结束时由操作系统回收。 在C中主要利用new在堆区中开辟内存。对象销毁前使用delete释放 #include iostream using namespace std;int *fun() {// 使用new关键字 将数据开辟到堆区// 指针 本质上也是局部变量放在栈上指针保存的数据是放在堆区int *p new int(10);return p; }int main() {// 在堆区开辟数据int *p fun();cout *p endl; cout *p endl;cout *p endl;delete p;return 0;}内存分区的意义 赋予不同区域存储的数据不同的生命周期。 1.2 new操作符 在堆区使用new开辟的内存在对象销毁前使用delete释放。 new创建的数据会返回该数据对应的类型的指针。 创建一个变量 new int(10); 创建一个数组 new int[10]; #include iostream using namespace std;int *func01() {// 在堆区创建整型数据// new 返回的是该类型对应的类型的指针int *p new int(10); // 开辟内存return p;}void test01() {int *p func01();cout *p endl;delete p; // 程序员释放内存}void func02() {// 数组int *arr_p new int[10]; // 10代表数组有10个元素for (int i 0; i 10; i){arr_p[i] i;}for (int j 0; j 10; j){cout arr_p[j] endl;}// 释放堆区的数组delete[] arr_p; }int main() {test01();func02();return 0; }1.3 new和malloc new 和 malloc() 是 C 和 C 语言中用于申请动态内存的两种方法。尽管它们都提供了从堆中分配内存的机制但在使用和功能上有一些重要区别 1. 构造函数和析构函数: new: 不仅分配内存还会调用对象的构造函数这意味着分配后立即初始化了对象。当使用 delete 释放内存时会自动调用析构函数。 malloc(): 只分配所需内存大小的字节不调用构造函数分配的内存用于原始字节数据。使用 free() 释放内存时不会调用析构函数。 2. 内存分配失败时的行为: new: 如果无法分配内存会抛出一个 std::bad_alloc 异常。 malloc(): 分配失败时返回 NULL。 3. 内存分配的灵活性: new: 是 C 操作符支持自定义的分配器可以重载以提供特殊行为。 malloc(): 是 C 语言的标准库函数没有这种灵活性但由于其简单性在需要与 C 语言库或旧代码兼容时非常有用。 4. 类型安全: new: 为类型安全返回正确类型的指针无需类型转换。 malloc(): 返回 void*通常需要强制类型转换以匹配指针类型。 5. 内存大小: new: 自动计算要分配的对象的大小。 malloc(): 需要程序员显式提供字节数。 6. 用法: new: 直接用于对象的创建。 MyClass* obj new MyClass(); malloc(): 需要在分配内存后可能还需要进行类型转换。 MyClass* obj (MyClass*)malloc(sizeof(MyClass)); 7. 释放内存: 对于 new应使用 delete 来释放内存。 对于 malloc()应使用 free() 来释放内存。 1.4 C的对象和内存管理 特性 C 是一种面向对象的语言它提供了一系列特性来实现对象的封装、继承和多态。同时C 也提供了灵活而复杂的内存管理功能。以下是一些关键的对象和内存管理特性 1. 构造函数和析构函数 构造函数当一个对象被创建时构造函数用来初始化该对象的状态。用户可以重载构造函数提供不同方式的初始化。 析构函数当一个对象不再使用时析构函数被调用用来执行任何必要的清理工作比如释放分配的内存等。这是 RAII资源获取即初始化的基础确保在分配资源后资源在不再需要时能被正确释放。 2. 动态内存管理 C 提供了 new 和 delete 操作符来分配和释放对象的内存。与 C 语言的 malloc 和 free 不同new 和 delete 会调用对象的构造函数和析构函数。 智能指针如 std::unique_ptr、std::shared_ptr 和 std::weak_ptr是现代 CC11 及以后中推荐的管理动态分配内存的方式可以减少内存泄漏和管理责任。 3. 拷贝构造函数和拷贝赋值操作符 用于控制对象的拷贝行为特别是涉及到动态内存分配的对象。用户可以定义自己的拷贝构造函数和拷贝赋值操作符以实现深拷贝或禁止对象拷贝等行为。 4. 移动构造函数和移动赋值操作符**C11 及以后** 这是现代 C 中的新特性允许程序“移动”资源而非传统的拷贝可以提高资源管理的效率特别是对于那些分配了大量内存或占用了其他重要资源的对象。 5. 继承和多态 继承允许创建基于现有类的新类继承现有类的属性和方法。 多态允许以统一的方式处理不同类的对象。在 C 中多态通过虚函数virtual functions实现允许在基类的层面定义接口在派生类中实现具体行为。 6. 封装 封装是面向对象编程的核心概念之一允许将数据属性和用于操作这些数据的代码方法绑定到一起作为一个单元对象并控制对这些数据的访问。 7. 异常处理 C 提供了异常处理机制允许程序在发生错误时进行有效的资源清理。这在内存和资源管理中尤为重要因为在没有异常处理的情况下发生错误可能会导致资源泄漏。 2. 函数提高 2.1 函数的默认参数 int fuc(int a 10, int b 20, int c 1) {}注意 函数声明中有默认参数函数中就不能有了默认参数前面的形参有了后面的形参就必须有 2.2 函数的占位参数 函数中有占位参数在调用的时候必须填补该位置 void func(int a, int) { }int main() {int a 10, b 20;func(a, b); }2.3 函数重载 1、重载需要满足的三个条件 同一作用域函数名称相同函数的参数类型参数个数顺序不同 注意返回值不可以作为函数重载的条件 void func() {cout “无参数” endl; }void func(int a) {cout “有参数” endl; }int main() {func();int a 10;func(a);}注意事项引用作为函数重载的条件 #include iostream using namespace std;// 引用作为 函数重载 的条件 void test01(int a) {cout int a endl; }void test01(const int a) // 这块相当于 const int a 12; // 这句代码是合法的。 {cout const int a endl; }int main() {// int a 10;// test01(a); // 调用的是 void test01(int a)test01(12); // 调用的是 void test01(const int a)return 0; }2)函数重载遇到默认参数时会出现二义性。报错 void func(int a, int b 10){} void func(int a){}小结 C中在程序运行前分为全局区和代码区 代码区的特点是共享和只读 全局区中存放全局变量、静态变量、常量 常量区中存放const修饰的全局常量和字符串常量 3. 类对象 1、类的定义 class classname {Access specifiers: // 访问修饰符private/public/protected// 成员变量属性// 成员方法行为}; // 使用“;”分号结束一个类2、对象的定义 classname Box1;classname Box2; // 声明Box2类型为classname3、访问数据 使用.直接成员访问运算符来访问 #include iostream using namespace std;class Student { public:// 成员属性 成员变量string name;int num;public:// 成员方法 成员函数void fuzhi(string stu_name, int stu_num){name stu_name;num stu_num;}void printInfo(){cout name endl;cout num endl;}};int main() {Student stu1;stu1.fuzhi(张三, 2032);stu1.printInfo(); }3.1 封装类 3.1.1 封装的意义 1将属性和行为作为一个整体表现生活中的事物2将属性和行为加以权限控制封装的意义一 设计类的时候属性和行为写在一起表现事物封装的意义二 三种访问权限类访问修饰符 1、pubic 公共权限 类内可以访问 类外可以访问 2、protected 保护权限 类内可以访问 类外不可以访问 3、private 私有权限 类内可以访问 类外不可以访问 protected和private的区别在继承上 protected修饰父类的属性和方法 子类可以继承父类的所有属性和方法 private修饰父类的属性和方法 子类不可以继承父类的private修饰的属性和方法 #include iostream using namespace std; #include string// 访问权限 class Persion {public:string f_name;protected:string f_Car;private:int f_Password;public:void func(){f_name 张三;f_Car 拖拉机;f_Password 123455;}};int main() {Persion p1;p1.f_name 王五;// p1.f_Car 宝马; // 保护权限的属性在所声明类外不能访问// p1.fPa_ssword 314124; // 私有属性在所声明类外不能访问 }3.1.2 struct和class的区别 struct结构体默认访问权限是 公共public class 类默认访问权限是 私有private 3.1.3 成员属性设置为私有 优点1将所有成员属性设置为私有可以自己控制读写权限 优点2对于写权限可以检测数据的有效性。 #include iostreamusing namespace std;#include stringclass Person {public:void setName(string name) // 设置m\_name的可写权限{m_name name;}string getName() // 可读F{return m_name;}int getAge() // 可读{return m_age;}void setLover(string lover){m_Lover lover;}private:string m_name; // 可读可写int m_age; // 只读权限string m_Lover; // 只写权限 };int main() {Person p1;p1.setName(张三);cout m_name p1.getName() endl;cout m_age p1.getAge() endl;p1.setLover(王女士); }3.2 对象的初始化和清理 当我们没有定义任何构造函数时编译器会自动定义一个隐含的默认构造函数这个默认构造有时候负责基类的构造和成员对象的构造在组合和继承中有体现 3.2.1 构造函数析构函数 类似于生活中的初始化构造函数和清理析构函数 构造函数和析构函数由编译器自动调用无需手动调用。 作用 构造函数创建对象时为对象的成员属性赋值构造函数由编译器自动调用无需手动调用。 析构函数在对象销毁前系统自动调用执行一些清理工作。 1. 构造函数 特点 1构造函数名和类名相同。 2没有返回值也不会返回void。 3构造函数可以有参数因此可以发生重载。 4程序在调用对象时会自动调用构造无需手动调用而且只会调用一次。 #include iostreamusing namespace std;class Line { private:double length;public:// 成员函数声明void setLength(double len);double getLength();Line(double len); // 带参数的构造函数};void Line::setLength(double len) {length len; }double Line::getLength() {return length; }// 构造函数 Line::Line(double len) {cout 对象被创建时线的长度为 len endl;length len; }int main() {Line l1(10.0);cout length的值为 l1.getLength() endl; // 默认值大小l1.setLength(12.0);cout length的值为 l1.getLength() endl; // 再次设置后的大小return 0;}2. 析构函数 作用将我们堆区开辟的内存释放掉 特点 1析构函数名与类名相同前加“~”作为前缀。 2没有返回值也不带参数。 3不可以有参数因此不能发生重载。 4程序在对象销毁前会自动调用析构 无须手动调用而且只会调用一次。 #include iostreamusing namespace std;class Line {private:double length;public:void setLength(double len);double getLength();Line(); // 声明构造函数~Line(); // 声明析构函数};void Line::setLength(double len) {length len; }double Line::getLength() {return length; }// 定义构造函数 Line::Line() {length 10.0;cout 创建对象时初始化的 length length endl; }// 定义析构函数 Line::~Line() {cout 函数运行结束前执行函数析构函数 endl; }int main() {Line l1;cout 构造函数初始化的成员属性length值: l1.getLength() endl;l1.setLength(11.0);cout 重新设定的成员属性length值: l1.getLength() endl; }3.2.2 构造函数的分类及调用拷贝构造 1. 构造函数按照类型分为 普通构造函数 默认构造函数 委托构造函数 Clock(int newH, int newM, int news) // 构造函数 {hour newH;minute newM;second news; } Clock():Clock(0, 0, 0){} // 委托构造函数拷贝复制构造函数 将一个对象的所有属性拷贝到另一个相同类的对象身上。 用一个已经存在的对象复制构造函数的参数确定去初始化同类的一个新对象。 如果没有定义类的复制构造函数系统会在必要的时候自动生成一个隐含的复制构造函数。 #include iostream using namespace std;/* 拷贝构造函数 */class Point { public:Point(int xx, int yy){x xx;y yy;}Point(Point p) // 把a对象赋值给p对象{x p.x;y p.y;std::cout Calling the copy constructor std::endl;}int getX() { return x; }int getY() { return y; }private:int x, y; };void func1(Point p) // 把b对象赋值给p对象 {cout p.getX() endl; }Point func2() {Point a(1, 2);return a; // 局部对象赋值给全局对象 }int main(int argc, char const *argv[]) {Point a(1, 20);Point b(a); // 1. 用a初始化b调用拷贝构造函数 Point c a;func1(b); // 2. 对象b作为func1()的实参b func2(); // 3. func2()返回类对象cout b.getX() ; b.getY() endl;return 0;}移动构造函数 左值对象的身份 右值对象的值。 左值持久右值短暂右值要么是字面常量要么是表达式求值过程中创建的临时变量 左值引用通过获得左值引用 右值引用必须绑定到右值的引用只能绑定到一个将要销毁的对象实际上就是某个对象的另一个名字。通过获得右值引用。 int i 20;int r i; // r引用ir是左值引用int rr i * 40; // rr引用i \* 40rr是右值引用const int r2 i * 40; // 可以将一个const引用绑定到一个右值上将r2绑定到i \* 40上note! 因为变量是左值所以不能将一个右值引用直接绑定到一个变量上即使这个变量是右值引用也不行。 2. 构造函数按照参数分类 有参构造无参构造默认的构造默认构造 3. 三种调用方法 括号法显示法隐式转换法 #include iostreamusing namespace std;class Person {public:Person() // 构造函数 无参构造{cout 类的 无参构造 函数创建对象时执行 endl;}Person(int a) // 有参构造{age a;cout 类的 有参构造 函数 endl;}Person(const Person p) // 拷贝构造** 使用const限定不能把本体给修改了{// 将传入的p对象人身上的所有属性拷贝到我身上age p.age;}~Person() // 析构函数{cout 类的析构函数在程序运行结束前执行 endl;}int age; };// 调用 void test01() {// 1、括号法Person p1; // 无参构造调用Person p2(10);Person p3(p2); // 拷贝构造函数的调用cout p2.age p2.age endl;cout p3.age p3.age endl;// 2、显示法Person p6 Person();Person p4 Person(12);Person p5 Person(p4);// 3、隐式转换Person p7 13;Person p8 p7; }int main() {test01();return 0; }3.2.3 拷贝构造函数/调用时机 使用同一类中之前创建的对象来初始化新对象。 拷贝构造函数的定义 className(const className obj){}C中拷贝构造函数调用的时机通常有三种情况 使用一个已经创建完毕的对象来初始化一个新对象值传递的方式给函数参数传值以值方式返回局部对象 3.2.4 深拷贝与浅拷贝 经典的面试问题 浅拷贝简单的赋值拷贝操作 深拷贝在堆区重新申请空间进行拷贝操作。 3.2.5 使用初始化列表初始化字段 假设一个类C有多个字段XYZ等需要进行初始化 class C { public:double X, Y, Z;C(double X, doubleY, double Z); };C::C(double X, double Y, double Z): X(a), Y(b), Z(c) {}3.2.6 类对象作为类成员组合类 C类中的成员可以是另一个类的对象称该成员为 对象成员。 class A {};class B {A a; // A是B类中的对象成员。 }; 问题当创建B对象的时候A/B的构造和析构顺序是怎样的 #include iostream #include string using namespace std;class Phone {public:string m_PName;Phone(string pName){m_PName pName;cout 调用Phone类的构造函数 endl;}~Phone(){cout 调用Phone类的析构函数 endl;}};class Person { public:string m_Name;Phone m_Phone;// Phone m_Phone pName; 隐式转换法Person(string name, string pName) : m_Name(name), m_Phone(pName){cout 调用Person类的构造函数 endl;}~Person(){cout 调用Person类的析构函数 endl;} };int main(int argc, char const *argv[]) { // 当其他类对象作为本类成员构造的时候先构造类对象再构造自身// 构造的顺序和析构的顺序是相反的 Person p(张三, 苹果MAX);return 0; }3.2.7 静态成员 静态成员属于类本身不属于某个实例。 “成员”出现在类中在类中无论是函数还是变量都称为成员——成员函数/成员变量 静态成员在成员变量/成员函数前加上关键字static称为静态成员。 静态成员分为 1. 静态成员变量 特点 所有对象共享同一份数据 在编译阶段分配内存 类内声明类外初始化 #include iostream using namespace std; class Person {public:// 1. 所有对象都共享同一份数据// 2. 编译阶段就分配内// 3. 类内声明类外初始化static int m_A;Person(/* args */);~Person();private:// 静态成员变量也有访问权限 私有权限在类外访问不到// 类内声明static int m_B;};// 类外初始化 int Person::m_A 100; // 意思是 Person这个类的作用域下的m\_A静态成员 int Person::m_B 400; Person::Person(/* args */) // 意思是 Person这个类的作用域下的Person构造函数 { }Person::~Person() // 意思是 Person这个类的作用域下的Person析构函数 { }// 所有对象共享同一份数据 void test01() {Person p;cout p.m_A endl; // 100Person p2;p2.m_A 200;cout p.m_A endl; // 200 }void test02() {// 静态成员变量 不属于某个对象上多有对象共享同一份数据// 因此静态成员变量有两种访问方式// 1. 通过对象进行访问Person p;cout 通过对象访问静态变量 p.m\_A endl; // 100// 2. 通过类名进行访问cout 通过类名访问静态变量 Person::m\_A endl; // 100 }int main(int argc, char const *argv[]) {// test01();test02(); }2. 静态成员函数 特点 所有对象共享同一个函数 静态成员函数只能访问静态成员变量 需要注意的 静态成员函数的两种调用方式 静态成员函数的访问权限 #include iostreamusing namespace std;class Person {public:static void func(){m_A 200; // 静态成员函数可以访问 静态成员变量// m_B 100; // 静态成员函数不可以访问非静态成员变量静态成员变量不属于某一个对象非静态成员变量属于某个对象 无法区分是哪个对象的m\_B 因此不能访问cout 静态成员函数 endl;}static int m_A; // 静态成员变量int m_B; // 非静态成员变量Person(/* args */){};~Person(){};// 静态成员函数也是有访问权限的 private:static void func1(){cout 私有的静态成员函数 endl;}};int Person::m_A 0;void test01() {// 静态成员函数的2种访问方式// 1.通过对象访问Person p;p.func();// 2.通过类名访问因为不属于某个对象因此可以通过类名访问Person::func();// 静态成员函数也是有访问权限的// Person::func1(); // 类外不能访问私有静态成员函数}int main(int argc, char const *argv[]) {test01();return 0; }3.3 C对象模型和this指针 3.3.1 成员变量和成员函数分开存储 在C中类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 #include iostream using namespace std;class Person {public:int m_A; // 非静态成员变量 属于类的对象上。static double m_B; // 静态成员变量不属于类的某一个对象。void func(){}; // 非静态成员函数 不属于类的对象上static void func2(){}; // 静态成员函数 不属于类的对象上};double Person::m_B 0;void test01() {Person p;// 空对象占用内存空间为// C编译器会给每个空对象也分配一个字节空间是为了区分空对象占用内存的位置// 每个空对象也应该有一个独一无二的位置cout Person p这个空对象占用内存空间 sizeof(p) endl; // 1}void test02() {Person p;// 因为非静态成员变量属于类的某一个对象而静态成员变量不属于某一个对象因此在输入对象的大小的时候只有非静态成员变量的大小。// 只有非静态成员变量属于类上其他都不属于类上。cout sizeof : sizeof(p) endl; // 4}int main(int argc, char const *argv[]) {// test01();test02();return 0; }3.3.2 this指针的概念 每一个非静态成员函数只会诞生一份函数实例即多个不同类型的对象会共用一块代码。 这一块代码如何区分是哪个对象调用自己的 通过this指针。this指针 指向被调用的成员函数 所属的对象。 this指针 是 隐含在每一个非静态成员函数内 的指针。 this指针 不需要定义直接使用即可。 this指针的用途 当形参和成员函数内的变量同名时可以使用this区分 在类的非静态成员函数中返回对象本身可使用return *this; #include iostreamusing namespace std;class Person { public:int age;void func(int age){// age age; // 这里第一个age和第二个age未进行区分// this指针 指向 p1 这个对象调用这个函数的对象this-age age;}Person PersonAddAge(Person p){this-age p.age; // this是指向 p2 对象的一个指针return *this; // * 解引用。即代表p2对象。}};// 1. 解决名称冲突void test() {Person p1;p1.func(18);cout p1.age : p1.age endl;}// 2. 返回对象本身void test02() {Person p1;Person p2;p1.age 11;p2.age 10;// 链式编程思想 p2.PersonAddAge(p1).PersonAddAge(p1);cout p2的年龄是: p2.age endl;}int main(int argc, char const *argv[]) {// test();test02();return 0; }3.3.3 空指针访问成员函数 C中空指针可以调用成员函数但是需要注意是否用到this指针 如果用到this指针需要判断保证代码的健壮性。 #include iostreamusing namespace std;class Person { public:void showClassName(){cout Person 类 endl;}void showPersonAge(){// 报错是因为传入的指针为空。this对象为空不存在m_Age成员属性。if (this NULL){return;}cout age this-m_Age endl; // this代表当前对象。通过当前对象访问属性。}int m_Age;};void test() {Person *p NULL;// 空指针是可以访问成员的p-showClassName();p-showPersonAge();}int main(int argc, char const *argv[]) {test();return 0; }3.3.4 const修饰成员函数 const修饰后限制为只读状态 常函数 成员函数加const 常函数常函数中内不可以修改成员属性成员属性声明时加上mutable后在常函数中依然可以修改 常对象 声明对象前加const 常对象常对象只能调用常函数 #include iostream using namespace std; class Person { public:// this指针的本质 是指针常量 指针的指向不可以修改// Person *const this; // this指向了一个Personvoid showPerson() const // 这里的const修饰的是this指针// 这里的const相当于 const Person *this; 使得指针指向的值不可以修改。// 两者综合起来就是const Person *const this;{// this-m_A 100; // 不可以修改指针指向的值因为函数名后的const修饰了this指针。// this NULL; // this指针不可以修改指针的指向this-m_B 100; // 加mutable关键字后可以修改。}void func(){m_A 100;}Person();int m_A;mutable int m_B;};Person::Person() {cout Person类的构造函数 endl; }// 常成员函数void test01() {Person p;p.showPerson(); }// 常对象void test02() {const Person p; // 对象前加const 常对象// p.m_A 100;p.m_B 100; // m_B是特殊值在常对象下也可以修改// 常对象只能调用常函数p.showPerson();// p.func();}int main(int argc, char const *argv[]) {test02();return 0; }3.4 友元 在程序里有些私有属性 也想让类外特殊的一些函数或者类进行访问就需要使用友元技术 友元的目的让一个函数或者类访问另一个类中的私有成员 友元的关键字friend 友元的三种实现 全局函数做友元类做友元成员函数做友元 3.4.1 全局函数做友元 #include iostream using namespace std; #include string// 建筑物类 class Building {// 说明全局函数goodGay是Building类的友元可以对类中的私有成员访问friend void goodGay(Building *buiding);public:Building(){m_SittingRoom 客厅;m_BedRoom 卧室;}string m_SittingRoom; // 客厅private:string m_BedRoom; // 卧室};// 全局函数 void goodGay(Building *buiding) {cout 好朋友正在访问 buiding-m_SittingRoom endl;cout 好朋友正在访问 buiding-m_BedRoom endl; }void test01() {Building b;goodGay(b); }int main(int argc, char const *argv[]) {test01();return 0; }3.4.2 类做友元 #include iostream #include string using namespace std;class Buiding;class GoodGay {public:GoodGay();void visit(); // 参观函数 访问buiding中属性Buiding *buiding; };class Buiding {// 告诉编译器 GoodGay是Buiding的友元可以访问Buiding的私有成员属性friend class GoodGay;public:Buiding();string m_SittingRoom;private:string m_BedRoom;};Buiding::Buiding() {m_SittingRoom 客厅;m_BedRoom 卧室; }GoodGay::GoodGay() {buiding new Buiding; }void GoodGay::visit() {cout 好朋友正在访问 buiding-m_SittingRoom endl;cout 好朋友正在访问 buiding-m_BedRoom endl; }void test01() {GoodGay gg;gg.visit(); }int main(int argc, char const *argv[]) {test01();return 0; }3.4.3 成员函数做友元 #include iostream #include stringusing namespace std;class Buiding;class GoodGay { public:GoodGay();Buiding *buiding;void visit01(); // 成员函数做友元void visit02(); };class Buiding {// 告诉编译器GoodGay::visit01()是Buiding的友元可以访问私有成员属性friend void GoodGay::visit01();public:Buiding();string m_SittingRoom;private:string m_BedRoom;};GoodGay::GoodGay() {buiding new Buiding; }Buiding::Buiding() {m_SittingRoom 客厅;m_BedRoom 卧室; }void GoodGay::visit01() {cout visit01 成员函数正在访问 buiding-m_SittingRoom endl;cout visit01 成员函数正在访问 buiding-m_BedRoom endl; }void GoodGay::visit02() {cout visit02 成员函数正在访问 buiding-m_SittingRoom endl; }void test01() {GoodGay gg;gg.visit01();gg.visit02(); }int main(int argc, char const *argv[]) {test01();return 0; }3.4 运算符重载 对已有的运算符重新定义赋予其另一种功能以适应不同的数据类型。 3.4.1 加号运算符重载 作用实现两个自定义数据类型相加的运算 通过自己写成员函数实现两个对象属性相加后返回新的对象。 #include iostream using namespace std;class Person { public:int m_A;int m_B;/*// 自己写成员函数实现两个对象属性相加后返回新对象Person PersonAddPerson(Person p){Person temp;temp.m_A this-m_A p.m_A;temp.m_B this-m_B p.m_B;return temp;}*/// 通过成员函数重载号运算符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;}};void test() {Person p1;p1.m_A 10;p1.m_B 11;Person p2;p2.m_A 12;p2.m_B 13;// 1. 通过调用自己写的成员函数计算// Person p3 p1.PersonAddPerson(p2);// 2. 通过成员函数重载号运算符Person p3 p1.operator(p2); // 未进行简化的写法// Person p3 p1 p2; // 简化后的写法printf(m_A: %d, m_B: %d, p3.m_A, p3.m_B); }int main(int argc, char const *argv[]) {test();return 0; }例二 #include iostream using namespace std;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; }// 函数重载版本 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 test() {Person p1;p1.m_A 10;p1.m_B 11;Person p2;p2.m_A 12;p2.m_B 13;// 通过 全局函数重载号运算符// 1. 全局函数重载本质调用// Person p3 operator(p1, p2);// 2. 简化后的写法// Person p3 p1 p2;// 运算符重载 也可以发生函数重载// Person p3 operator(p1, 10);Person p3 p1 10;printf(m_A: %d, m_B: %d, p3.m_A, p3.m_B);}int main(int argc, char const *argv[]) {test();return 0; }3.4.2 左移运算符重载 重载的两种形式1通过成员函数重载2通过全局函数重载。 链式编程思想调用完成之后返回cout再次调用完成返回cout #include iostream using namespace std;class Person { public:int m_A;int m_B;// 利用成员函数重载 左移运算符 p.operator(ostream cout) 简化版本p cout// 不能利用成员函数重载 运算符因为无法实现 cout在左侧// void operator(cout){}};// 只能使用全局函数进行重载 运算符ostream operator(ostream cout, Person p) // 本质 operator(cout, p) 简化 coutp; {cout m_A: p.m_A m_B: p.m_B;// 这种可以无限的往后追加输出,是因为其有一种链式编程思想return cout; }void test(){Person p;p.m_A 10;p.m_B 22;// 现在可以正常的输出p这个对象了 这里不能// operator(cout, p); // 本质// cout p; // 简化版// 换行 如何需要换行,前面的输出应该返回cout类型(ostream )cout p Hello World! endl; // 链式编程思想可以无限的往后追加输出/*cout p调用完成之后,返回cout类型; cout p Hello World!调用完成之后,返回cout; 然后后面再追加endl*/}int main(int argc, char const *argv[]) {test();return 0; }3.4.3 递增运算符重载 3.4.4 赋值运算符重载 3.4.5 关系运算符重载 3.4.6 函数调用运算符重载 类成员函数 类成员函数是类的一个成员可以操作类中的所有成员。使用成员函数来访问类的成员而不是直接访问这些类的成员。 成员函数可以定义在 1、类的内部 class Box { public:double length;double breadth;double height;// 定义在类内部的成员函数double getVolume(){return length * breadth * height; }};2、类的外部即单独定义 使用范围解析运算符“::”定义 #include iostream using namespace std;class Box {public:double length;double breadth;double height;// 声明成员函数 需要在类的内部声明定义在类的外部的成员函数double getVolume();void setLength(double len);void setBreadth(double bre);void setHeight(double hei); };// 定义在类外部的 成员函数 // 需要使用范围解析运算符“::” double Box::getVolume(void) // 即在Box类的作用域下的getVolume函数 {return length * breadth * height; }void Box::setLength(double len) {length len; }void Box::setBreadth(double bre) {breadth bre; }void Box::setHeight(double hei) {height hei; }int main() {Box b1;b1.setBreadth(12.0);b1.setHeight(32.0);b1.setLength(78.0);cout b1 volume: b1.getVolume() endl;return 0; }二、继承 基类派生类 基类父类 派生类子类 继承是 is a的关系。 继承类型继承方式当一个类派生自基类这个基类可以被修饰为public/protected/private 当这个基类被 public修饰时 protected修饰时当一个类派生自protected基类时这个基类的public/protected成员将成为这个派生类的protected成员。 private修饰时派生类继承自基类的所有属性和方法都成为派生类的private属性和方法成员变量/成员方法 多继承 虚函数和纯虚函数 虚函数 引入原因允许使用基类的指针调用子类的这个函数。 定义一个函数为虚函数不代表这个函数没有被实现可以有实现。 #include iostreamusing namespace std;class A { public:virtual void foo(){cout 基类的虚函数 endl;}};class B : public A { public:void foo(){cout 派生类 B类 endl;} };int main() {A *a new B();a-foo(); // 这里的指针指向的是基类A调用的成员函数foo()是派生类B的成员函数return 0; }/* 派生类 B类 */ 纯虚函数 纯虚函数是在基类中被声明的虚函数没有实现 但是要求所有派生类都需要有自己的实现方法。 在基类中实现纯虚函数即函数原型后加0 virtual void function()0; 特点 函数没有被实现。 引入原因 方便使用多态特性 基类本身生成对象是不合理的。 需要在基类中定义虚函数然后在派生类中重新定义这个函数但是在基类中又不能给出有意义的实现这个时候可以用到纯虚函数。 virtual int a() 0; 定义一个函数为纯虚函数是为了实现一个接口起到规范作用。 #include iostream using namespace std;class Animal { protected:int age 0;int height 0;int weight 0;public:Animal(){cout 构造函数 endl;}virtual ~Animal() // 防止通过基类的指针删除派生类对象时派生类的析构函数不能被正确的调用{cout 析构函数 endl;}virtual void Walk() 0;virtual void Eat() 0;virtual void Sleep() 0;};class Dog : public Animal { public:void Walk(){cout Dog Walk()方法 endl;}void Eat(){cout Dog Eat()方法 endl;}void Sleep(){cout Dog Sleep()方法 endl;} };class Cat : public Animal { public:void Walk(){cout Cat Walk()方法 endl;}void Eat(){cout Cat Eat()方法 endl;}void Sleep(){cout Cat Sleep()方法 endl;}};int main() {Animal *dog1 new Dog();Animal *cat1 new Cat();dog1-Eat();dog1-Sleep();dog1-Walk();cat1-Eat();cat1-Sleep();cat1-Walk();delete cat1;delete dog1;/*当两个指针被delete时首先调用的是Dog/Cat类的析构函数然后是Base类Animal的析构函数。这是因为基类的析构函数被声明为虚。*/return 0; }抽象类 带有纯虚函数的类叫做抽象类。 设计抽象类的目的给其他类提供一个适当的可以继承的基类。 三、多态 父类中同一个方法,在继承的子类中表现出不同的形式。重写和重载 C提高编程 C中除了有面向对象的编程思想还有泛型编程的思想 1. 模板 学习模板是为了使用STL中的模板会使用其中的模板 1.1 函数模板 C中的另一种编程思想泛型编程主要利用的技术是模板 C中的两种模板机制函数模板 类模板 1.1.1 函数模板语法 函数模板作用 建立一个通用函数其函数返回值类型和形参类型可以不具体指定用一个虚拟的类型来代表。在使用的时候确定。 语法 templatetypename T函数声明和定义 解释 template 声明创建模板 typename 表明其后面的符号是一种数据类型可以用class代替。不管是函数模板还是类模板都可以使用class/typename都可以 T 通用的数据类型名称可以替换通常为大写字母 #include iostream using namespace std;void swapInt(int a, int b) {int tmp a;a b;b tmp;}void swapDouble(double a, double b) {double tmp a;a b;b tmp;}// 函数模板 template typename T // 声明一个模板告诉编译器后面代码中紧跟着的T不要报错T是一个通用数据类型void mySwap(T a, T b) {T tmp a;a b;b tmp;}void test01() {int a 10;int b 20;// swapInt(a, b);double c 1.1;double d 2.2;// swapDouble(c, d);// 使用函数模板交换// 两种方式使用函数模板// 1. 自动类型推导// mySwap(a, b);// mySwap(c, d);// 2. 显示指定类型mySwapint(a, b);mySwapdouble(c, d);cout a a endl;cout b b endl;cout c c endl;cout d d endl;}int main(int argc, char const *argv[]) {test01();return 0; }函数模板 template typename T T Add(T a, T b) {return a b; }int main(){cout Addint(1, 2) endl;cout Adddouble(1.5, 2.5) endl;cout Addstring(“Hello, ”, “World!”)endl; } // 定义了一个模板函数“Add”可以接受任何类型“T”的参数。 template typename T inline T const Max (T const a, T const b) { return a b ? b:a; } // 定义了一个模板函数“Max”可以接受两个T类型任意类型的常量引用参数。 // inline表示是一个内联函数。内联函数的代码在调用时会被直接嵌入到调用的代码中1.1.2 函数模板注意事项 注意事项 · 自动类型推导必须推导出一致的数据类型T才可以使用 · 模板必须要确定出T的数据类型才可以使用。 #include iostream using namespace std;template typename T void mySwap(T a, T b) {T tmp a;a b;b tmp;}void test() {int a 10;int b 20;char c c;// 注意事项1. 自动类型推导必须推导出一致的数据类型T才可以使用mySwap(a, b); // 正确// mySwap(a, c); // 错误cout a a endl;cout b b endl;}template class T void func() {cout func调用 endl; }void test02() {// 注意事项2. 模板必须要确定出T的数据类型才可以使用。// func();funcint(); }int main(int argc, char const *argv[]) {test();// test02();return 0; }1.1.3 函数模板案例----数组排序 #include iostreamusing namespace std;// 实现通用 对数组进行排序的函数// 规则 从大到小// 算法 选择// 测试 char数组、int数组// 交换函数模板template typename T void mySwap(T a, T b) {T temp a;a b;b temp; }// 排序算法 template typename T void mySort(T arry[], int len) {for (int i 0; i len; i){int max i; // 认定的最大值下标for (int j i 1; j len; j){// 认定的最大值 比 遍历出的数值 要小说明j下表的元素才是真正的最大值if (arry[max] arry[j]){max j; // 更新下标}}if (max ! i) // 认定的最大值i和计算出的最大值max不相等{// 交换max和i下标的元素mySwap(arry[max], arry[i]);}} }// 打印数组模板函数 template typename T void printArray(T arr[], int len) {for (int i 0; i len; i){cout arr[i] ;}cout endl; }void test01() {char arr[] badcfe;int len sizeof(arr) / sizeof(char);mySort(arr, len);printArray(arr, len); }void test02() {int arr[] {8, 2, 3, 1, 9, 10};int len sizeof(arr) / sizeof(int);mySort(arr, len);printArray(arr, len); }int main() {test01();test02();return 0; }1.1.4 普通函数与函数模板的区别 普通函数与函数模板的区别 普通函数调用时可以发生自动类型转换隐式类型转换函数模板调用时如果利用自动类型推导不会发生隐式类型转换如果利用显示指定类型的方式可以发生隐式类型转换 #include iostreamusing namespace std;// 普通函数与函数模板的区别 // 普通函数 int myAdd01(int a, int b) {return a b;}// 函数模板 template typename T T myAdd02(T a, T b) {return a b; }void test01() {int a 10;int b 20;char c c;cout myAdd01(a, b) endl; // 30cout myAdd01(a, c) endl; // 隐式类型转换 109// 自动类型推导 调用函数模板cout myAdd02(a, b) endl;// cout myAdd02(a, c) endl; // 报错不能进行隐式类型转换// 显示指定类型 调用函数模板cout myAdd02int(a, c) endl; // 可以进行隐式类型转换}int main(int argc, char const *argv[]) {test01();return 0;}1.1.5 普通函数与函数模板的调用规则 调用规则 1. 如果函数模板和普通函数都可以实现优先调用普通函数 2. 可以通过空模板参数列表来强制调用函数模板 3. 函数模板也可以发生重载 4. 如果函数模板可以产生更好的匹配优先调用函数模板。 #include iostream using namespace std;void myPrint(int a, int b) {cout 调用普通函数 endl; }template typename T void myPrint(T a, T b) {cout 调用函数模板 endl; }template typename T void myPrint(T a, T b, T c) {cout 调用重载的函数模板 endl; }void test01() {int a 10;int b 20;int c 30;// myPrint(a, b); // 这里调用的是普通函数// 通过空模板参数列表强制调用函数模板// myPrint(a, b); //// 函数重载// myPrint(a, b, c);// 匹配最优char c1 a;char c2 b;myPrint(c1, c2);}int main(int argc, char const *argv[]) {test01();return 0; }1.1.6 模板的局限性 template typename T void f(T a, T b) {a b; }如果传入的a和b是数组则不成立 template typename T void f(T a, T b) {if(a b){….} }如果T类型传入的是Person这样的自定义数据类型编译器不能辨别这种类型无法正常运行 因此提供模板的重载为这些特定的类型提供具体化的模板 #include iostream using namespace std; #include stringclass Person { public:Person(string name, int age){(*this).m_Name name;this-m_Age age;}string m_Name;int m_Age;};// 对比两个数据是否相等函数 template typename T bool myCompare(T a, T b) {if (a b){return true;}else{return false;} }// 利用具体化的Person版本实现代码具体化优先调用 template bool myCompare(Person p1, Person p2) {if (p1.m_Age p2.m_Age p1.m_Name p2.m_Name){return true;}else{return false;} }void test01() {int a 10;int b 20;bool ret myCompare(a, b);if (ret){cout a b endl;}else{cout a ! b endl;}}// 解决方法 // 1、运算符重载 // 2、提供具体的Person版本实现比较 void test02() {// 1、括号法调用构造函数// Person p1(Tom, 10);// Person p2(Tom, 10);// 2、显式法调用构造函数Person p1 Person(Tom, 10);Person p2 Person(Tom, 10);bool ret myCompare(p1, p2);if (ret){cout p1 p2 endl;}else{cout p1 ! p2 endl;} }int main(int argc, char const *argv[]) {// test01();test02();return 0; }1.2 类模板 1.2.1 类模板语法 类模板的作用 建立一个通用类类中的成员 数据类型可以不具体指定用一个虚拟的类型代表。 template class T // 或者 template typename T 类 template 声明创建模板 typename/class 表明其后面的符号是一种数据类型 T 通用的数据类型名称可以替换通常使用大写字母 #include iostream using namespace std; #include stringtemplate class AgeType, class NameType class Person { public:Person(AgeType age, NameType name){this-m_Age age;this-m_Name name;}void showPerson(){cout name : this-m_Name ; age : this-m_Age endl;}AgeType m_Age;NameType m_Name; };void test01() {// Personint, string p1 Personint, string(10, Tom);Personint, string p1(10, Tom); // 模板的参数列表p1.showPerson(); }int main(int argc, char const \*argv[]) {test01();return 0; }1.2.2 类模板与函数模板区别 区别有两点 1. 类模板没有自动类型推导的使用方式 2. 类模板在模板参数列表中可以有默认参数。 #include iostream using namespace std; #include stringtemplate class NameType, class AgeType int // 类模板中参数列表可以有默认参数 class Person { public:Person(NameType name, AgeType age){this-m_Name name;this-m_Age age;}void showPerson(){cout name this-m_Name ; age this-m_Age endl;}NameType m_Name;AgeType m_Age; };void test01() {Personstring p1(Tom, 10); // 注意p1.showPerson(); }int main(int argc, char const *argv[]) {test01();return 0; }1.2.3 类模板中成员函数创建时机 类模板中成员函数和普通类中的成员函数创建时机不同 · 普通类中的成员函数一开始就可以创建 · 类模板中的成员函数在调用时才创建因为在创建对象时才能确定数据类型。 1.2.4 类模板对象做函数参数 三种传入方式 1. 指定传入的类型 ---- 直接显示对象的数据类型 2. 参数模板化 ---- 将对象中的参数变为模板进行传递 3. 整个类模板化 ---- 将这个对象类型模板化进行传递 #include iostream using namespace std; #include stringtemplate class T1, class T2 class Person { public:Person(T1 age, T2 name){this-m_Age age;this-m_Name name;}void showPerson(){cout age this-m_Age ; name this-m_Name endl;}T1 m_Age;T2 m_Name; };// 1、指定传入类型 void printPerson1(Personint, string p) {p.showPerson(); }void test01() {Personint, string p1(10, Tom);printPerson1(p1); }// 2、参数模板化 template class T1, class T2 void printPerson2(PersonT1, T2 p) {p.showPerson();cout T1 类型为 typeid(T1).name() endl; // 查看类型是什么cout T2 类型为 typeid(T2).name() endl; }void test02() {Personint, string p2(20, Jim);printPerson2(p2); }// 3、整个类模板化 template class T void printPerson3(T p) {p.showPerson();cout T 类型为 typeid(T).name() endl; }void test03() {Personint, string p3(20, Blue);printPerson3(p3); }int main(int argc, char const \*argv[]) {// test01();// test02();test03();return 0; }1.2.5 类模板与继承 1.2.6 类模板成员函数类外实现 1.2.7 类模板分文件编写 1.2.8 类模板与友元 1.2.9 类模板案例 类模板 template class T class Stack { private:vectorT elems; public:void push(T const ); // 这里后面的参数名称可以省略void pop();T top() const;bool empty() const{return elems.empty();}Stack(); // 声明构造函数~Stack(); // 声明析构函数 };template class T void StackT::push(T const elem) {elems.push_back(elem); }template class T void StackT::pop() {if (elems.empty()){throw out_of_range(Stack::pop(): empty stack());}elems.pop_back(); }template class T T StackT::top() const // const声明常量成员函数表明top函数不能修改对象的成员变量即不能修改Stack类中的成员变量。 {if (elems.empty()){throw out_of_range(Stack::top(): empty stack);}return elems.back();}template class T StackT::Stack() // 构造函数 {printf(构造函数); }template class T StackT::~Stack() // 析构函数 {printf(析构函数); }int main(int argc, char const \*argv[]) {try{Stackint intStack; // 实例化一个Stack类intStackStackstring stringStack;// 操作int类型的StackintStack.push(7);cout intStack.top() endl;cout \n endl;// 操作string类型的栈stringStack.push(hello);cout stringStack.top() endl;stringStack.pop();stringStack.pop();}catch (const std::exception e){std::cerr Exception: e.what() \n;} return 0; }2. STL 2.1 STL诞生 · 希望可以建立可重复使用的东西 · C的面向对象和泛型编程思想目的就是提升复用性 · 大多数情况下数据结构和算法没有一套标准导致大量重复工作 · 为了建立数据结构和算法的一套标准诞生了STL。 2.2 STL基本概念 ·STLStandard Template Library标准模板库 ·STL从广义上分为容器container、算法algorithm、迭代器iterator ·容器和算法直接通过迭代器进行无缝衔接 ·STL几乎所有的代码都采用了模板类或者模板函数。 2.3 STL六大组件 STL大体分为六大组件分别是容器、算法、迭代器、仿函数、适配器配接器、空间配置器 1、容器各种数据结构如vector、list、deque、set、map等用来存储数据。 2、算法各种常用的算法如sort、find、copy、for_each等。 3、迭代器扮演容器和算法之间的胶合剂。 4、仿函数行为类似函数可作为算法的某种策略。 5、适配器一种用来修饰容器或者仿函数或者迭代器接口的东西。 6、空间配置器负责空间的配置与管理。 2.4 STL中容器、算法、迭代器 容器数据存放的地方 STL容器是将运用最广泛的一些数据结构实现出来 常用的数据结构有数组、链表、树、栈、队列、集合、映射表等 这些容器分为序列式容器和关联式容器两种 序列式容器强调值的排序序列式容器中的每个元素均有固定的位置关联式容器二叉树结构各元素之间没有严格的屋里上的顺序关系算法问题的解决办法 算法分为**质变算法和非质变算法** **质变算法**指运算过程中会改变区间内的元素的内容。例如拷贝替换删除等 **非质变算法**运算过程中不会更改区间内的元素的内容。例如查找计数遍历寻找极值等 迭代器容器和算法之间的粘合剂 提供一种方法使其能够依序寻访某个容器所含的各个元素而又无需暴露该容器的内部表示方式。 每个容器都有自己专属的迭代器。 迭代器使用非常类似于指针。 迭代器的种类 种类功能支持运算输入迭代器对数据的只读访问只读支持、、!输出迭代器对数据的只写访问只写支持前向迭代器读写操作并能向前推进迭代器读写支持、、!双向迭代器读写操作并能向前和向后操作读写支持、–随机迭代器读写操作可以以跳跃的方式访问任意数据功能最强的迭代器读写支持、–、[n]、-n、、、、 2.5 容器算法迭代器初识 STL中最常用的容器为vector可以理解为数组 2.5.1 vector存放内置数据类型 容器vector 算法for_each 迭代器vector::iterator #include iostream using namespace std; #include vector #include algorithm // 标准算法头文件// vector容器存放内置数据类型 void printFunc(int val) {cout val endl; }void test() {// 创建一个vector容器数组vectorint v;// 向容器中插入数据v.push_back(10);v.push_back(20);v.push_back(30);v.push_back(40);v.push_back(50);// // 通过迭代器访问容器中的数据// vectorint::iterator itBegin v.begin(); // 起始迭代器 指向容器中第一个元素// vectorint::iterator itEnd v.end(); // 结束迭代器 指向容器中最后一个元素的下一个位置// // 第一种遍历方式// while (itBegin ! itEnd)// {// cout \*itBegin endl;// itBegin;// }// // 第二种遍历方式// for (vectorint::iterator it v.begin(); it ! v.end(); it)// {// cout \*it endl;// }// 第三种遍历方式 利用STL提供的遍历算法for_each(v.begin(), v.end(), printFunc); }int main(int argc, char const \*argv[]) {test();return 0; }2.5.2 vector存放自定义的数据类型 #include iostream #include string // #include algorithm #include vector using namespace std;// vector容器存放自定义数据类型 class Person { public:Person(string name, int age){this-m_Name name;this-m_Age age;}string m_Name;int m_Age; };void test01() {// 创建一个vector容器vectorPerson v;Person p1(aaa, 10);Person p2(bbb, 20);Person p3(ccc, 30);Person p4(ddd, 40);Person p5(eee, 50);// 存放自定义数据类型v.push\_back(p1);v.push\_back(p2);v.push\_back(p3);v.push\_back(p4);v.push\_back(p5);// 遍历容器中的数据for (vectorPerson::iterator it v.begin(); it ! v.end(); it){// cout name : (\*it).m\_Name ; age : (\*it).m\_Age endl;cout name : it-m\_Name ; age : it-m\_Age endl;} }// 存放自定义的数据类型 指针void test02() {// 创建一个vector容器vectorPerson * v;Person p1(aaa, 10);Person p2(bbb, 20);Person p3(ccc, 30);Person p4(ddd, 40);Person p5(eee, 50);// 存放自定义数据类型// push_back尾插法v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);v.push_back(p5);// 遍历容器中的数据for (vectorPerson *::iterator it v.begin(); it ! v.end(); it){// vector中存放的是地址cout name : (*it)-m_Name ; age : (*it)-m_Age endl;} }int main(int argc, char const *argv[]) {// test01();test02();return 0; }2.5.3 vector容器嵌套容器 vector容器类似一个数组数组中嵌套数据》二维数组。 #include iostream #include vector using namespace std;void test() {// 创建大容器vectorvectorint v;// 创建小容器vectorint v1;vectorint v2;vectorint v3;vectorint v4;// 向小容器中存放数据for (int i 0; i 4; i){v1.push_back(i 1);v2.push_back(i 5);v3.push_back(i 9);v4.push_back(i 13);}// 将小容器存入大容器v.push_back(v1);v.push_back(v2);v.push_back(v3);v.push_back(v4);// 通过大容器把所有数据遍历一遍for (vectorvectorint::iterator it v.begin(); it ! v.end(); it){// *it是小容器vectorintfor (vectorint::iterator vit (*it).begin(); vit ! (*it).end(); vit){cout *vit ;}cout endl;}}int main(int argc, char const *argv[]) {test();return 0; }3. STL-常用容器 3.1 string容器 3.1.1 string基本概念 本质string是C风格的字符串string本质是一个类 string和char *区别 ·char *是一个指针C语言风格的字符串本质 ·string是一个类类内部封装了char *管理这个字符串是一个char*型的容器。 特点 string类内部封装了很多成员方法 例如查找find拷贝copy删除delete替换replace插入insert string管理char *所分配的内存不用担心复制越界和取值越界等由类内部进行负责。 3.1.2 string构造函数 1. string(); // 创建一个空字符串 2. string(const char *s) // 使用字符串s初始化 3. string(const string str) // 使用一个string对象初始化另一个string对象 4. string(int n, char c) // 使用n个字符c初始化 #include iostreamusing namespace std;// string的构造函数/*1. string(); // 创建一个空字符串2. string(const char \*s) // 使用字符串s初始化3. string(const string str) // 使用一个string对象初始化另一个string对象4. string(int n, char c) // 使用n个字符c初始化 */void test01() {// 1.string s1; // string的无参构造// 构造函数的三种调用方式string s2(hello world2); // 括号法string s3 string(hello world3); // 显示法string s4 hello world4; // 隐式转换法const char \*str hello world;// 2.string s5(str);cout s5: s5 endl;// 3.string s6(s5); // 拷贝构造cout s6: s6 endl;// 4.string s7(10, a);cout s7: s7 endl;}int main(int argc, char const *argv[]) {test01();return 0;}3.1.3 string赋值操作 string operator (const char *s); // char *类型字符串赋值给当前字符串string operator (const string s); // 把字符串s赋给当前字符串string operator (char c); // 字符赋值给当前字符串string assign(const char *s); // 把字符串s赋给当前的字符串string assign(const char *s, int n); // 把字符串s的前n个字符赋给当前的字符串string assign(const string s); // 把字符串s赋给当前的字符串string assign(int n, char c); // 用n个字符c赋给当前字符串#include iostreamusing namespace std;/*string operator(const char *s); // char *类型字符串赋值给当前字符串string operator(const string s); // 把字符串s赋给当前字符串string operator(char c); // 字符赋值给当前字符串string assign(const char *s); // 把字符串s赋给当前的字符串string assign(const char *s, int n); // 把字符串s的前n个字符赋给当前的字符串string assign(const string s); // 把字符串s赋给当前的字符串string assign(int n, char c); // 用n个字符c赋给当前字符串*/void test01() {string str1;str1 hello world;cout str1 endl;string str2;str2 str1;cout str2 endl;string str3;str3 a;cout str3 endl;string str4;str4.assign(hello C);cout str4 endl;string str5;str5.assign(hello C, 4);cout str5 endl;string str6;str6.assign(5, c);cout str6 endl;}int main(int argc, char const *argv[]) {test01();return 0; }3.1.4 string字符串拼接 功能实现在字符串末尾拼接字符串 函数原型 string operator **** (const char *str) // 重载操作符string operator (const char c); // 重载操作符string operator (const string str); // 重载操作符string **append**(const char *s); // 把字符串s连接到当前字符串结尾string append (const char *s, int n); // 把字符串s的前n个字符连接到当前的字符串结尾string append (const string s); // 同string operator (const string str);string append (const string s, int pos, int n);// 字符串s中从pos开始的n个字符连接到字符串结尾3.1.5 string查找和替换 3.1.6 string字符串比较 3.1.7 string字符串存取 3.1.8 string插入和删除 3.1.9 string子串 3.2 vector容器 3.3 deque容器 3.4 案例 3.5 stack容器 3.6 queue容器 3.7 list容器 3.8 set/multiset容器 3.9 map/multimap容器 4. STL-函数对象 5. STL-常用算法 6. gdb g -g test.cpp -o test编译时需要添加-g选项生成带有包含调试信息包含源代码的行号变量名等的可执行文件 hive 7. 流类库与输入输出 C/C都没有输入/输出语句C标准库中有输入输出的软件包iostream这就是I/O流类库。 将数据从一个对象到另一个对象的流动叫做流。 8. 单例模式 记忆知识点 atoi()将字符串转成整型数的函数 main(int argc, char **argv)其中argc是命令行参数argv是包含所有命令行参数的一个数组 fprintf(stderr, “Usage: %s N\n”, argv[0]);将一个错误消息打印到标准错误输出stderr. argv[0]程序的名字argv[1]命令行中的第一个参数。 rand()生成0到RAND_MAX之间的伪随机数 浮点数 内存对齐 作用提高内存访问速度的策略 适用于不仅有结构体类还有各种数组等 内存对齐遵循规则: 1. 对于结构体的各个成员除了第一个成员的偏移量为 0 外其余成员的偏移量是 其实际长度 的整数倍如果不是则在前一个成员后面补充字节。 2. 结构体内所有数据成员各自内存对齐后结构体本身还要进行一次内存对齐保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍。 3. 如程序中有 #pragma pack(n) 预编译指令则所有成员对齐以 n字节 为准即偏移量是n的整数倍不再考虑当前类型以及最大结构体内类型。 struct Test01 {char c;short s;int i;double d; }t1;struct Test02 {char c;double d;int i;short s; }t2;第一个成员 c 的偏移量为 0所以成员 c 的内存空间的首地址为 0 第二个成员 d 的内存空间的首地址为 8 号地址偏移量为 8 - 0 8double 类型的整倍数 第三个成员 i 的内存空间的首地址为 16 号地址偏移量为 16 - 0 16int 类型的整倍数 第三个成员 s 的内存空间的首地址为 20 号地址偏移量为 20 - 0 20short 类型的整倍数 Test02 所占内存大小为 24 个字节结构体占用内存大小是结构体内最大数据成员 double 的最小整数倍24 / 8 4 #include iostreamusing namespace std;struct X {char a; // 1double b; // 8int c; // 4 } SRT;struct zk02 {/* data */char a; // 1int b; // 4double c; // 8 } SRT2;struct Example2 {double a;int b;char c; } SRT3;struct Example3 {char a;char b;int c; } SRT4;struct Example4 {double a;double b;char c; } SRT5;struct Example5 {char a;char b;char c;double d; } SRT6;int main() {cout sizeof(SRT) endl; // 内存对齐 24cout sizeof(SRT.a) endl; // 1cout sizeof(SRT.b) endl; // 8cout sizeof(SRT.c) endl; // 4cout sizeof(SRT2) endl; // 16cout sizeof(SRT2.a) endl; // 1cout sizeof(SRT2.b) endl; // 4cout sizeof(SRT2.c) endl; // 8cout sizeof(SRT3) endl; // 16cout sizeof(SRT4) endl; // 8cout sizeof(SRT5) endl; // 24cout sizeof(SRT6) endl; // 16return 1;}箭头(-)和点(.)的区别 箭头-左边必须为指针 点号.左边必须为实体 struct MyStruct {int member_a; };变量MyStruct s; 访问其中元素 s.member_a 1;采用指针访问MyStruct *ps; 访问其中元素 (*ps).member_a 1;或者使用 ps-member_a 1;原码反码补码 补码计算机中使用补码表示整数负数正数 十进制3正数-3负数原码0000 00111000 0011反码0000 00111111 1100补码0000 00111111 1101 补码首位的权是负数 八位二进制数各位的权是 128 64 32 16 8 4 2 1 例如一个补码1110 0000 他代表的十进制是 -128 64 32 32 补码0110 0000 十进制0 64 32 96 任意负数的补码-X都是0 - X static const volatile关键字 static、volatile 和 const 关键字的含义和用途 1. static 关键字 static被用来控制变量和函数的存储方式生存期和可见性作用域 1.1 静态全局变量普通全局变量 全局变量本身就是静态存储方式可以被其他文件访问 加上static静态全局变量改变**作用域为定义它的文件**隐藏它不被其他文件访问。 1.2 静态局部变量普通局部变量 加static将局部变量将其改为静态存储方式。 - 修饰局部变量使局部变量在函数调用之间保持其值即静态存储期。**静态局部变量在第一次初始化后保持其值而不像普通局部变量每次函数调用都会重新初始化**。1.3 static函数和普通函数 static函数在内存中只有一份普通函数在每个被调用的源文件中维持一份拷贝。 - 修饰全局函数将全局函**数的可见性限制在定义它的文件中****即使其他文件中使用相同名称的全局函数也不会引起冲突。**- 修饰类成员变量和函数使得它们**属于类本身而不是类的实例****这些成员变量或函数可以通过类名访问**而不需要创建类的实例。2. volatile 关键字 因为访问寄存器比访问内存单元快得多因此编译器一般会减少内存存取内存存取优化但有可能读取脏数据。 volatile 用于告诉编译器被修饰的变量在编译器优化时不被优化 不可被省略、重排或缓存。通常用于以下情况 用于描述硬件寄存器或内存映射的变量因为这些变量的值可能在程序之外被修改。 用于多线程环境中用于描述被多个线程访问的变量以避免编译器优化引起的问题。 在信号处理中用于保证信号处理函数中对变量的访问不会被优化掉。 3. const 关键字 简单来说就是“readonly” const 用于创建不可变的常量可以修饰变量、指针、引用以及类成员函数。 修饰变量创建不可变的变量一旦初始化后其值不能再次修改。 修饰指针使指针指向的数据不可修改。 修饰引用创建只读引用不允许通过引用修改所引用的变量。 修饰类成员函数表示该成员函数不会修改对象的状态不会修改成员变量。 动态存储/静态存储方式生存期 静态存储方式程序在编译期分配固定的存储空间。在变量定义时就分配存储单元直到程序结束生存期。全局变量静态变量都是这种。 动态存储方式程序运行期间分配存储空间。使用时才分配存储空间使用结束立即释放。例子函数的形参函数定义时并不会分配存储单元只是在函数被调用时才分配。函数调用完毕立即释放。如果一个函数被多次调用则反复分配、释放形参变量的存储单元。 重载和重写的区别 重载不考虑返回值类型只考虑参数列表参数的类型个数顺序。根据参数列表调用不同的函数。 示例 重写派生类中重新定义的函数派生类中函数的返回值/参数列表都和基类的相同只有函数的实现不同花括号中的内容不同。在派生类被调用时会调用派生类中的函数不会调用基类中的被重写的函数基类中的函数需要加上virtual才可以即基类中的函数是虚函数。 构造析构函数的调用顺序 其中注意点 拷贝构造的调用 组合类中 参数的传递是按照引用传递的还是值传递的。值传递需要调用拷贝构造函数引用传递不需要。 #include iostream #include cmath using namespace std;class Point {public:Point(int xx 0, int yy 0){x xx;y yy;}Point(Point p);int getX() { return x; }int getY() { return y; }private:int x, y;};Point::Point(Point p) {x p.x;y p.y;cout Calling the copy constructor of Point endl;}class Line {public:Line(Point xp1, Point xp2); // 组合类的构造函数Line(Line l); // 组合类的拷贝构造函数double getLen() { return len; }private:Point p1, p2;double len;};// 组合类的构造函数 Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) // 如果是按引用传递参数的话是这样的 Line::Line(const Point xp1, const Point xp2) // 这里加const的作用是防止xp1,xp2对象在Line函数中被修改。 // 按照引用传递会避免不必要对象复制。 {// this-p1 xp1;// this-p2 xp2;cout Calling constructor of Line endl;double x static_castdouble(p1.getX() - p2.getX());double y static_castdouble(p1.getY() - p2.getY());len sqrt(x * x y * y);}// 组合类的拷贝构造函数 Line::Line(Line l) : p1(l.p1), p2(l.p2) {cout Calling the copy constructor of Line endl;len l.len; }int main() {Point myp1(1, 1), myp2(4, 5); // 创建myp1,myp2对象的时候没有输出结果。/*传入对象的值调用拷贝构造。拷贝对象副本。传入对象的引用调用对象本身1. 因为这里myp1, myp2是按值传递的所以会“创建myp1,myp2对象的副本”这个副本是通过调用Point的拷贝构造函数创建的。 // 调用2次2. 在构造函数内部使用xp1,xp2初始化成员变量p1,p2。因为p1和p2属于Point类型初始化过程又分别调用Point类的拷贝构造函数。“分别用于从xp1创建p1的副本从xp2创建p2的副本”。// 调用2次Point myp1(1, 1), myp2(4, 5);创建了myp1myp2两个Point对象Line line(myp1, myp2); 其中myp1,myp2通过值传递拷贝了myp1,myp2的两个副本在Line类的构造函数中。又赋初始值从xp1创建p1的副本从xp2创建p2的副本创建了两个副本。*/Line line(myp1, myp2);Line line2(line);cout The length of the line is: ;cout line.getLen() endl;cout The length of the line2 is: ;cout line2.getLen() endl;return 0;}运行结果 Calling the copy constructor of Point Calling the copy constructor of Point Calling the copy constructor of Point Calling the copy constructor of Point Calling constructor of Line Calling the copy constructor of Point Calling the copy constructor of Point Calling the copy constructor of Line Thelength of the line is:5 The length of the line2 is:5
http://www.hkea.cn/news/14407493/

相关文章:

  • 装修网站模板源码wordpress改主题幻灯片尺寸
  • 网站可以更更换空间吗wordpress英文版安装
  • 网站图片如何优化山东通app官网下载二维码
  • 专业做冻货的网站深圳网站建设网络
  • 深圳网站开发外包wordpress页面构建
  • 企业移动网站品牌c2c网站功能
  • 免费做二建题的网站计算机哪方面技术吃香
  • 苏州网络推广苏州网站建设网站建设项目报价
  • 网站制作建立速橙科技有限公司网站建设
  • 做视频编辑哪个网站素材比较好装修设计公司网站排名
  • 做地方网站需要什么部门批准网站的公司
  • 全国网站排名新材建设局网站
  • 中国建设银行的网站特色双wan路由器做网站接入
  • 做肯德基玻璃门网站兰州网站设计
  • 杭州市上城区建设局网站wordpress rclean
  • 可以发布广告的网站郑州企业管理培训课程
  • 灰色网站欣赏英雄联盟最新赛事
  • 网站建设网站推广买服务器做网站
  • 如何做产品展示网站中国建筑网官网手机版
  • 千图主站的功能介绍动态可视化wps图表制作
  • 医疗器械查询官网南京网络推广优化哪家好
  • 2016企业网站建设合同wordpress双语安装
  • 怎么关键词优化网站山西孝义网站开发
  • 建设电商网站的个人心得网站建设与管理教学大纲
  • 为什么买的网站模版不好用网红营销套路
  • 郑州云拓网站建设公司wordpress钩子自定义钩子
  • 个人网站免费的吗网页设计图片的代码
  • 广州广州网站建设公司网站域名到期查询
  • 网站推广的主要方法重庆网站建设去迅法网
  • 怎么管理网站添加代码威海制作网站