• C++回顾从入门开始


    前言

    新手搭配视频
    回顾复习直接看

    有问题请评论提出看到会及时修改

    include、iostream、main()

    #include 
    using namespace std;
    int main()
    {
        cout << "Hello World!" << endl;
        return 0;
    }
    
    
    ========== 解释 ==========
    int表示函数的返回值类型,表示该主函数的返回值是一个int类型的值;
    
    main()是C++的主函数(也称入口函数)
    它是C++程序开始执行的地方
    一个完整的C++程序(或工程)必须的有一个main()且只能有一个
    
    include<iostream>意思是引入iostream库,即输入输出流库。
    
    iostream库的基础是两种命名为istream和ostream的类型,分别表示输入流和输出流。#include<iostream>是标准的C++头文件,任何符合标准的C++开发环境都有这个头文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    变量、常量、关键字、标识符命名规则

    变量

    用于记录程序中可以更改的数据
    给一段指定的内存空间起名,方便操作这段内存
    数据类型 变量名 = 初始值;

    int a = 10;
    cout << "a = "<< a << endl;
    
    • 1
    • 2
    常量

    用于记录程序中不可更改的数据

    1.#define 常量名 常量值(通常在文件上方定义,表示一个常量。)
    eg:#define day 7//是不可修改的值,一旦修改就会报错
    
    
    2.const 数据类型 常量名 = 常量值(通常在变量定义之前加关键字const,修饰该变量为常量,不可修改。)
    eg:const int month = 30;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    关键字

    关键字是C++中预先保留的单词(标识符)
    在定义变量或常量的时候不要使用关键字。

    在这里插入图片描述

    标识符命名规则

    作用:C++规定给标识符(变量、常量)命名时,有一套自己的规则

    • 标识符不能是关键字
    • 标识符只能由字母、数字、下划线组成
    • 第一个字符必须为字母或者下划线
    • 标识符中字母区分大小写

    数据类型

    C++规定在创建一个变量或者常量的时候,必须要指定出相应的数据类型,否则无法给该变量分配内存空间。

    整型

    作用:整型变量表示的是整型类型的数据。
    C++中能够表示整型的类型有以下几种方式,区别在于占用的内存空间不同。
    在这里插入图片描述

    sizeof关键字
    #include 
    using namespace std;
    int main(void)
    {
    	// sizeof(数据类型/变量);
    	// 统计数据类型所占空间的大小。
    	cout << "int类型所占空间的大小是:" <<sizeof(int)<< endl;
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    实型(浮点型)

    作用:用于表示小数。
    浮点型分为两种-单精度float-双精度double。
    两者的区别在于有效数字的表示范围不一样。
    在这里插入图片描述

    float f1 = 3.14f;
    //编译器会默认把一个小数当做双精度//默认情况下输出一个小数会显示出6位有效数字//例如:下面这个f1只输出到6float f1 = 3.1234567f;
    
    • 1
    • 2
    字符型

    作用:字符型变量用于显示单个字符。

    char a = 'a';
    /*注意:显示字符型变量时用单引号括起来,不是双引号。单引号内只能有一个字符,不可以是字符串。*/
    
    C和C++中字符型变量只占1个字节。
    字符型变量并不是把所有的字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元中。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    转义字符

    作用:用于表示一些不能显示出来的ASCII字符。
    在这里插入图片描述

    字符串

    作用:用于表示一串字符串。

    c 形式字符串(要用双引号括起来)

    语法:char 变量名[] = "字符串值";
    
    char str1[] = "hello world";
    
    • 1
    • 2
    • 3

    c++ 形式字符串(需要加入头文件#include)

    #include
    
    语法:string 变量名 = "字符串值";
    
    string st2 = "hellow world";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    布尔类型bool

    作用:布尔类型数据代表真或假的值。

    bool类型只有两个值:
    true——真(1false——假(0bool类型占1个字节大小
    
    bool flag = true;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输入、运算符

    数据 输入

    作用:用于从键盘获取数据

    语法:cin >> 变量
    int a = 0;cin >>a;
    
    • 1
    • 2
    运算符

    作用:用于代码的运算。

    • 算数运算符(用于处理四则运算)
      在这里插入图片描述

    • 赋值运算符(用于将表达式的值赋给变量)
      在这里插入图片描述

    • 比较运算符(用于表达式的比较,并返回一个真值或假值)
      在这里插入图片描述

    • 逻辑运算符(用于根据表达式的值返回真值或假值)
      在这里插入图片描述

    程序流程结构

    C/C++支持最基本的三大基本程序运算结构:顺序结构、选择结构、循环结构。

    • 顺序结构:程序按顺序执行,不发生跳转。
    • 选择结构:依据条件是否满足,有选择的执行相应代码。
    • 循环结构:依据条件是否满足,循环多次指定某段代码。
    选择结构

    if语句

    if(条件1)
    {
        //条件1满足执行的语句
        //嵌套if语句
        if() //单行格式if语句
    }
    else if(条件2)
    {
        //条件2满足执行的语句
    }
    ......
    else
    {
        //都不满足执行的语句
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    三目运算符(通过三目运算符实现简单的判断)

    表达式1?表达式2:表达式3
    表达式1(是否为真)?表达式2(为真结果):表达式3(为假结果)
    
    eg:
    int a = 9;
    int b = 10;
    int c = 0;
    c = a > b ? a : b;  // 10
    
    
    int a = 10;
    int b = 9;
    int c = 0;
    c = a > b ? a : b;  // 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    switch语句(执行多条件分支语句)

    switch(表达式)
    {
            case 结果1:
                执行语句;
                break;
            ......
            default:
                执行语句;
            	break;
    }
    
    switch('0'){
        case '0' :
            cout << "星期一" << endl;
        break;
        case '1' :
            cout << "星期二" << endl;
        break;
        default :
            cout << "不知道星期几" << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    循环结构

    while循环语句

    // 语法
    while(循环条件){    循环语句} (只要满足循环条件的结果为真,就执行循环语句。)
    
    eg:猜数字
    #include 
    #include
    using namespace std;
    int main(void)
    {
    	int num = rand() % 100;
    	cout << num << endl;
    	int puT = 0;
    	cout << "请你猜一下这个数是多少\n" << endl;
    	while ((cin >> puT))
    	{
    		if (puT > num)
    		{
    			cout << "猜大了\n" << endl;
    		}
    		else if (puT <= num / 2)
    		{
    			cout << "太小了\n" << endl;
    		}
    		else if (puT >= num / 2 && puT < num)
    		{
    			cout << "再大一点\n" << endl;
    		}
    		else if (num == puT)
    		{
    			cout << "猜对了\n" << endl;
    			break;
    		}
    	}
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    do-while循环语句(注意:与while的区别在于do-while会先执行一次循环语句,再判断循环条件。)

    // 语法
    do{    
    	循环语句
    }while(循环条件)
    
    eg:水仙花数
    #include 
    using namespace std;
    int main(void)
    {
    	int ge = 0;
    	int shi = 0;
    	int bai = 0;
    	int i = 100;
    	do
    	{
    		ge = i % 10;
    		shi = (i / 10) % 10;
    		bai = i / 100;
    		if (i == ge * ge * ge + shi * shi * shi + bai * bai * bai)
    		{
    			cout << i << endl;
    		}
    		i++;
    	} while (i < 1000);
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    for循环语句

    // 语法
    for(起始条件;条件表达式;末尾循环体){    循环语句}
    
    eg:敲桌子 是7的倍数、各位有7、十位有7
    #include
    using namespace std;
    int main(void)
    {	
        for (int i = 1; i < 100; i++)	
        {		
            int ge = i % 10;		
            int shi = (i /10)% 10;		
            if (i % 7 == 0 || ge == 7 || shi == 7)		
            {			
                cout << i << endl;		
            }	
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    跳转语句

    break语句

    作用:用于跳出选择结构或者循环结构。

    break使用的时机:

    1、出现在switch语句中,作用是终止case并跳出swtich 出现在循环语句中,作用是跳出当前的循环语句
    2、出现在嵌套语句中,跳出最近的内层循环语句

    continue语句

    作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。

    goto语句

    如果标记的名称存在,执行到goto语句的时候,会跳转到标记的位置。

    goto语句也称为无条件转移语句;goto语句的语义是改变程序流向,转去执行语句标号所标识的语句;
    goto语句通常与条件语句配合使用。可用来实现条件转移,构成循环,跳出循环体等功能。
    
    void  main()
    {
        int i;
        switch(i)
        {
            case 0:
                  break;
            case 1:
                   goto stop;
            default:
                break;
        }
        stop:  printf (  "Jumped to stop. i = %d\n" , i );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    数组

    所谓数组就是一个集合,里面存放了相同类型的数据元素

    数组的特点

    • 数组中的每个数据元素都是相同的数据类型。
    • 数组是由连续的内存位置组成的。
    一维数组
    定义:
    	数据类型 数组名[数组长度];
    	数据类型 数组名[数组长度] = {1,值2......};
    	数据类型 数组名[] = {1,值2......};;
    
    
    eg:输出最重的一只小猪的体重
    #include
    using namespace std;
    int main(void)
    {	
        int temp = 0;	
        int Weight[5] = { 300,250,200,400,450 };	
        for (int i = 0; i < 5; i++)	
        {		
            if (Weight[i] > temp)		
            {			
                temp = Weight[i];		
            }	
        }	
        cout << "最重的小猪是" << temp << "kg";	
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    数组元素逆置
    #include
    using namespace std;
    int main(void)
    { 	
        int temp = 0;	
        nt nums[5] = { 1,2,3,4,5};	
        int start = 0;	
        int end = sizeof(nums)/sizeof(nums[0]) -1 ;	
        while (start < end)	
        {		
            temp = nums[start];		
            nums[start] = nums[end];		
            nums[end] = temp;		
            end--;		
            start++;	
        }	
        for (int i = 0; i < 5; i++)
        {		
            cout << nums[i];	
        }	
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    冒泡排序

    最常用的排序算法,对数组内元素进行排序

    比较相邻两个元素,如果第一个比第二个大就交换他们的位置
    每一对相邻元素做同样的工作,整型完毕后,找到第一个最大值。
    重复以上的步骤,每次比较次数-1,知道不需要比较
    
    #include
    using namespace std;
    int main(void)
    { 
    	//排序的总轮数=元素个数-1
    	//每轮对比的次数 = 元素个数- 排序轮数 
    	int nums[9] = { 7,5,2,4,9,8,6,7,1 };
    	for (int i = 0; i < 8 ; i++)
    	{
    		//内层循环对比
    		for (int j = 0; j < 9 - i-1; j++)
    		{
    			//第一个数比第二个数大就交换他们两个的位置
    			int temp = 0;
    			if (nums[j] > nums[j + 1])
    			{
    				temp = nums[j+1];
    				nums[j + 1] = nums[j];
    				nums[j] = temp;
    			}
    		}
    	}
    	for (int i = 0; i < 9; i++)
    	{
    		cout << nums[i];
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    二维数组

    二维数组就是在一维数组的基础上多加一个维度,就是在一维数组里面存储一维数组。

    定义:
    	数据类型 数组名[][];
    	数据类型 数组名[][] = {{数据1,数据2}{数据3,数据4}};
    	数据类型 数组名[][] = {数据1,数据2,数据3,数据4};
    	数据类型 数组名[][] = {数据1,数据2,数据3,数据4};
    	
    以上4种定义方式,利用第二种更加直观,提高代码的可读性。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    考试成绩统计练习(分别输出三个人的总成绩)
    语文数学英语
    504060
    201030
    708090
    #include
    using namespace std;
    int main(void)
    {	
        int score[3][3] = { {60,50,40},{10,20,30},{70,80,90} };	
        //嵌套循环解决	
        for (int i = 0; i < 3; i++)
        {			
            int temp = 0;	
            for (int j = 0; j < 3; j++)		
            {			
                temp += score[i][j];			
            }		
            cout << temp << endl;	
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    函数

    将一段经常使用的代码封装起来,减少重复代码。

    定义:
    	函数的几个要素:
    	
    	返回值类型 函数名(参数列表)
    	{    
    		函数语句;    
    		return 表达式;
    	}
    
    函数的调用:
    	功能:使用定义好的函数
    	语法:函数名(参数)
    
    值传递:
    	就是函数调用时将参数值传给形参
    	值传递时,如果形参发生变化,并不会影响到实参
    
    函数的常见样式:
    	无参无返、有参无返、无参有返、有参有返
    
    函数的声明
    	作用:告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
    	函数的声明可以有很多次,定义只能有一次。
    
    	//声明
    	int max(int a,int b);
    	//定义
    	int max(int a ,int b)
    	{
    	    return a+b;
    	}
    
    函数的分文件编写(让代码结构更加清晰)
    就是在.h的头文件里面放函数声明,函数的定义放到.c文件里
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    指针

    指针(Pointer),从其英文字面上来理解就是一个指向某一物件的东西,在程序中就是指向数据的地址(Address)。计算机的内存可以看作是一个紧密排列的数据序列,每一小块数据序列,每一小块数据(也就是字节)的旁边都有一个编号代表数据地址。这在现实中可以用房屋的地址来理解,我们可以说这一栋房子是小李家,也可以说一栋房子是xx路xxx号(指针表示)。

    内存编号是从0开始记录的,一般用16进制数字标识。
    可以利用指针变量保存地址。
    
    • 1
    • 2
    空指针
    空指针:指针变量指向内存中编号为0的空间
    
    用途:初始化指针变量
    
    注意:空指针指向的内存空间是不可以访问的
    
    int* p = NULL;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    野指针

    野指针(wild pointer),简单讲是指指向不可用的内存区域的指针。需要注意的一点是,野指针与NULL空指针是不同的。NULL指针一般比较好判断,直接用if (p==NULL)语句判断即可。但是野指针指向的是垃圾内存区域的指针,一旦使用往往会造成不可预测的结果,这种随机不可预测的结果才是最可怕的。

    const修饰指针
    const修饰指针有3种情况
    	const修饰指针—常量指针
    	const修饰常量—指针常量
    	const既修饰指针,又修饰常量
    
    	const修饰的是指针,指针指向可以改,指针指向的值不可以改
    		const int *p=&a;
    		*p = 20; //错误,p指向的内存中的值不能修改
    		p = &b; //正确,p可以改变指向
    		
    	const修饰的是常量,指针指向不可以改,指针指向的值可以更改
    		int * const p=&a;
    		*p = 20; //正确,p指向的值可以修改
    		p = &b; //错误,p不可以改变指向
    		
    	const既修饰指针,又修饰常量,指针的指向和指针指向的值都不可以改变
    	    const int* const p = &a;
    
    指针和数组
    作用:利用指针访问数组元素
    int arr[] = {1,2,3,4};
    int* p = arr;
    
    
    指针和函数
    作用:利用指针作函数的参数,可以修改实参的值。  ——传()引用
    
    eg:封装一个函数,利用冒泡排序,实现对整型数组的升序排列
    #include
    using namespace std;
    void PopSort(int* a,int len)
    {
    	for (int i = 0; i < len - 1; i++)
    	{
    		for (int j = 0; j < len-i - 1; j++)
    		{
    			int temp = 0;
    			if (a[j] > a[j + 1])
    			{
    				temp = a[j];
    				a[j] = a[j + 1];
    				a[j + 1] = temp;
    			}
    		}
    	}
    }
    int main(void)
    {	
    	int arry[5] = { 6,2,4,8,5 };
    	PopSort(arry, 5);
    	for (int i = 0; i < 5; i++)
    	{
    		cout << arry[i];
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    结构体

    结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。

    语法:
    	struct 结构体名称{    结构体成员列表};
    
    通过结构体创建变量的方式有三种:
    	1struct 结构体名 变量名
    	2struct 结构体名 变量名 = (成员1值,成员2值…)
    	3、定义结构体时顺便创建变量
    
    eg:
    struct Student
    {  
    	string name;  
    	int age;  
    	int score;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    结构体数组

    将自定义的结构头放入到数组中方便维护
    每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。

    语法:
    	struct 结构体名 数组名[元素个数]=  {{}{}...{}};
    
    eg:
    #include//预处理
    using namespace std;//命名空间 
    int main()//主函数 
    {
      struct Student{ //自定义结构体变量 
        int num;//学号 
        char sex;//性别 
        int age;//年龄 
      }stu[3]={{1001,'M',21},{1002,'F',18},{1003,'M',19}};
      for(int i=0;i<3;i++)//循环输出结构体数组信息 
      {
        cout<<stu[i].num<<endl;//输出学号 
        cout<<stu[i].sex<<endl;//输出性别 
        cout<<stu[i].age<<endl;//输出年龄 
        cout<<"---------"<<endl;//隔开 
      }
      return 0; //函数返回值为0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    结构体指针

    通过指针访问结构体中的成员
    利用操作符->可以通过结构体指针访问结构体属性

    struct Student s1;
    struct Student* p = &s1;
    p->age = 10;
    
    • 1
    • 2
    • 3
    结构体嵌套结构体
    作用:结构体中的成员可以是另一个结构体
    例如:每个老师辅导一个学员,一个老师的结构体中,记录一个学生的的结构体
    
    eg:
    
    #include
    using namespace std;
     
    //创建结构体
    struct student
    {
    	string name;
    	int age;
    	int score;
    };
     
    struct teacher
    {
    	int id;
    	string name;
    	int age;
    	struct student stu;
    };
     
     
    int main()
    {
    	//结构体嵌套结构体
    	teacher t;
    	t.stu.name = "李华";
    	t.stu.age = 23;
    	t.stu.score = 80;
    	t.name = "刘建";
    	t.age = 36;
    	t.id = 10000;
    	
    	cout << "老师姓名:" << t.name << " 老师年龄:" << t.age << " 老师的ID:" << t.id << " 老师所教的学生姓名:" << t.stu.name
    		<< " 学生年龄:" << t.stu.age << " 学生姓名:" << t.stu.name << " 学生分数:" << t.stu.score << endl;
     
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    结构体做函数参数

    将结构体作为参数向函数中传递

    //传递结构体成员
    //值传递-无法改变实参
    struct book
    {
    	float price;
    	int page;
    	char title[50];
    	char author[50];
    }Shot;
    
    void modify(float  stdata);
    
    modify(Shot.price);
    
    //地址传递-可以改变实参
    modify(&(Shot.price));  传递成员地址
    
    // 传递结构体
    struct book
    {
    	float price;
    	int page;
    	char title[50];
    	char author[50];
    }Shot;
    
    void modify(struct book* stdata );
    
    modify(&Shot);
    
    or
    
    struct book
    {
    	float price;
    	int page;
    	char title[50];
    	char author[50];
    }Shot;
    
    struct book* bk_point =&Shot;
    
    void modify(struct book* stdata );
    
    modify(bk_point);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    内存分区模型

    • 代码区:存放函数的二级制代码,由操作系统进行管理的
    • 全局区:存放全局变量和静态变量以及常量
    • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
    • 堆区: 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
    程序运行前

    在程序编译后,生成了exe可执行文件,未执行该程序前分为两个区域。

    代码区
    • 存放cpu执行的机器指令
    • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份打码即可
    • 代码区是只读的,使其只读的原因是防止程序意外的修改了它的指令
    全局区
    • 全局变量和静态变量存放于此
    • 全局区还包含了常量区,字符串常量和其他常量也存放于此
    • 该区域的数据在程序结束之后由操作系统释放
    程序运行后

    在程序编译后,生成了exe可执行文件,未执行该程序前分为两个区域。

    栈区
    • 由编译器自动分配释放,存放函数的参数值,局部变量等。
    • 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
    堆区
    • 有程序员分配释放,若程序员不释放,程序结束之后有操作系统回收
    • 在C++中主要利用new在堆区中开辟内存

    new运算符

    在堆区开辟数据

    堆区开辟的数据,由程序员手动开辟,手动释放,释放用delete
    
    语法:
    	new 数据类型
    
    利用new创建的数据,会返回该数据对应类型的指针
    	int* p = new int(10);//分配一个整型,值为10,p指向它
    	int* arry = new int[10];//分配一个人42个int的数组;p指向第一个int
    
    	delete p;			//p必须指向一个动态分配的对象或为空
    	delete[] arry;		//arry必须指向一个动态分配的数组或为空
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    引用

    基本使用(给变量起别名)
    语法:数据类型 &别名 = 原名
    
    int a = 0;
    int &b = a;
    //a和b操作的是同一块内存
    
    注意:
    	1、引用必须初始化,告诉它它是谁的别名
    	int a = 10;
    	int &b; // 报错
    	int &b = a;
    	2、引用在初始化之后,不可以改变
    	int c = 20;
    	
    	b = c; //赋值操作,而不是更改引用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    做函数参数
    作用:函数传参时,可以利用引用让形参修饰实参
    优点:可以简化指针修改实参(传址)。
    
    eg:
    #include
    using namespace std;
    
    void my(int a,int b){
    	int temp = a;
    	a = b;
    	b = temp;
    }
    
    void my1(int *a,int *b){
    	int temp = *a;
    	*a = *b;
    	*b = temp;
    }
    
    void my2(int &a,int &b){
    	int temp = a;
    	a = b;
    	b = temp;
    }
    
    
    int main(){
    	int a = 10;
    	int b = 20;
    	my(a,b);	// 值传递,形参不会修饰实参
    	my1(&a,&b);	// 地址传递,形参会修饰实参
    	my2(a,b);	// 引用传递,形参会修饰实参
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    做函数的返回值

    作用:引用是可以做函数返回值存在的

    #include
    using namespace std;
    //不要返回局部变量引用(非法操作)
    int& test01(){
    	int a = 10;	// 局部变量存放在 栈区 函数执行完自动释放
    	return a;
    }
    
    //函数调用作为左值
    int& test02(){
    	static int a = 10;	//	静态变量,存放在全局区,在程序执行完后自动释放。
    	return a;
    }
    
    int main(){
    	int &ref = test01(); // 这个时候 a 已经释放了,我们没有权利去操作他。(非法操作)
    	return 0;
    	
    	int &ref2 = test02();
    	
    	// 如果函数是一个引用,这个函数调用可以作为左值。
    	test02() = 1000;	// 返回的是引用,所以相当于做了一个a = 1000的操作。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    引用本质

    本质:引用的本质在c++内部实现是一个指针常量,引用一旦被初始化之后就不能更改。

    void func(int& ref)
    {
        ref = 100;//ref是引用,转换为*ref = 100
    }
    int main(void)
    {
        int a  = 10;
        
        //自动转化int* const ref = &a;//指针常量是指针指向不可改,也说明为什么引用不可更改
    	int &ref = a;
    	
    	//自动发现ref是引用,自动转换为*ref = 20;
    	ref =20;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但所有的指针操作编译器都帮我们做了。

    常量引用

    作用:常量引用主要用来修饰形参,防止误操作

    //打印数据的函数
    //void showvaL(int& ref){  
    //	ref = 1000
    //}
    
    //在函数形参列表中,可以加const修饰形参,防止形参改变实参
    void showvaL(const int& ref){  
    	ref = 1000;(修改会报错)
    }
    
    int main(){
    	//引用必须引用一块合法的内存空间
    	int &ref = 10; (错误用法)
    	
    	//加上const之后,编译器将代码修改为int temp =10; int& ref = temp; 
    	const int& ref = 10;
    	ref = 20; (error) //加入const之后变为只读不可以修改
    	
    	int b = 100;
    	showvaL(b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    函数提高

    函数默认参数

    在c++中函数形参列表中的形参是可以有默认值的。

    语法:返回值类型 函数名(参数=默认值)
    //函数的哪个参数被声明默认了,下面函数调用的时候就可以少传哪个参数,如果有默认值还传了参数,用的就是函数调用传递的参数
    int func(int a,int b =10,int c =23)
    {
        return a+b+c;
    }
    int main(void)
    {		
        int ref = func(10);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意事项:如果某个位置已经有了默认参数,那么从这个位置往后都要有默认参数

    //从b开始往后一的参数都有默认参数
    int fun2(int a,int b= 10;int c =20)
    {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果函数的声明有默认参数,函数的实现就不能有默认参数了。
    声明和实现只能有一个有默认参数。(二义性)

    // 运行会报错
    int fun3(int a = 10;int b = 20);
    int fun3(int a,int b)
    {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    函数占位参数(函数重载使用)

    C++中函数的形参列表里可以有占位参数,用来占位,调用函数的时候必须填补该位置。

    语法:返回值类型 函数名 (数据类型){}void func(int a, int)
    {
    
    }
    int main(void)
    {
    	func(10,1);//这个1传进去是拿不到的,目前阶段的占位参数用不到,但在后面是会用到的。
    	return 0;
    }
    
    占位参数还可以有默认参数
    void func(int a, int =10)
    {
    
    }
    int main(void)
    {
    	func(10);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    函数重载

    函数名可以相同,提高复用性

    函数重载满足条件:(函数的返回值不可以作为函数重载的条件)

    • 同一个作用域下
    • 函数名相同
    • 函数参数类型不同或者个数不同或者顺序不同
    void func()
    {
    	cout << "无参数" << endl;
    }
    void func(int a)
    {
    	cout << a;
    }
    int main(void)
    {
        根据函数传递参数的不同调用不同的代码
    	func();
    	func(10);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    函数重载的注意事项

    • 引用作为函数重载条件
    #include
    using namespace std;
    void func(int &a)
    {
    	//int a = 10;  a 是个变量,他可读可写,所以加const不行
    	//func(a);	
    	
    	//当func(10);
    	//int& a =10;不合法
    }
    void func(const int &a)
    {
    	//当func(10);
    	//const int& a =10;合法——编译器自动优化
    }
    
    int main(void)
    {
    	int a = 10;
    	//func(a);	调用的是 void func(int &a)
    	//func(10); 调用的是 void func(const int &a)
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 函数重载碰到函数默认参数
    #include
    using namespace std;
    void func(int a ,int b = 10)
    {
    	
    }
    void func(int a)
    {
    	
    }
    
    int main(void)
    {
    	func(10);
    	/*当函数重载碰到默认参数
    	编译器傻了,不知道该调用哪个了
    	出现二义性
    	写函数重载就不要加默认参数,避免这种情况的出现*/
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    类和对象

    C++面向对象的三大特性为:封装、继承、多态。

    C++认为万事万物皆为对象,对象上有其属性和行为

    例如:

    人可以作为对象,属性有姓名、年龄、身高、体重…行为有唱、跳、跑…

    车也可以作为对象,属性有轮胎、方向盘、大灯…行为有载人、放音乐、开空调…

    具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类…

    封装

    封装的意义:

    • 将属性和行为作为一个整体,表现生活中的事物
    • 将属性和行为加以权限控制

    在设计类的时候,属性和行为写在一起,表现事物

    语法:
    class 类名{访问权限: 属性 / 行为};
    
    eg:创建一个圆类,求圆的周长
    #include
    using namespace std;
    double pi = 3.14;
    //class 代表设计一个类,类后面紧跟着的就是类名称
    class Circle
    {
    	//访问权限
    	//公共权限
    public:
    	//属性 
    	//半径
    	int c_r;
    	//行为
    	//获取圆的周长
    	double calculateZC()
    	{
    		return 2 * pi * c_r;
    	}
    };
    int main(void)
    {
    	//通过圆类创建具体的圆(对象)
    	//实例化——通过一个类创建一个对象的过程
    	Circle c1;
    	//给圆对象的属性进行赋值
    	c1.c_r = 10;
    	cout << "圆的周长为" << c1.calculateZC() << endl;
    
    	return 0;
    }
    
    eg:创建一个学生类
    #include
    #include
    using namespace std;
    class Student
    {
    
    public:
    	string s_Name;
    	int s_Id;
    	void showStudent()
    	{
    		cout << "姓名: " << s_Name << "ID:" << s_Id << endl;
    	}
    	//赋值
    	void inputName(string name)
    	{
    		s_Name = name;
    	}
    };
    
    int main(void)
    {
    	Student s1;
    	//s1.s_Name = "张三";
    	s1.inputName("赵六");
    	s1.s_Id = 123456;
    	s1.showStudent();
    	return 0;
    }
    
    类中的属性和行为,我们统称为成员
    属性-成员属性-成员变量
    行为-成员函数-成员方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    类在设计时,可以把属性和行为放在不同的权限下,加以控制

    访问权限有三种:

    public		公共权限——成员类内可以访问,类外可以访问
    protected	保护权限——成员类内可以访问,类外不可以访问
    private		私有权限——成员类内可以访问,类外不可以访问
    
    eg:
    #include
    #include
    using namespace std;
    class Person
    {
    public:
    	string p_name;
    protected:
    	string p_car;
    private:
    	int p_password;
    public:
    	void funcshow()
    	{
    		p_name = "张三";
    		p_car = "拖拉机";
    		p_password = 123456;
    	}
    };
    
    int main(void)
    {
    	Person p1;
    	p1.p_name = "王五";
    	//p1.p_car = "GTR";protected类外无法访问
    	//p1.p_password = 123;private类外无法访问
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    struct和class

    在C++中struct和class的唯一区别就是默认的访问权限不同。

    struct	默认权限为公共	public
    class	默认权限为私有	private
    
    • 1
    • 2

    成员属性设置为私有:
    优点1:将所有成员属性设置为私有,可以自己控制读写权限。
    优点2:对于写权限,我们可以检测数据的有效性。

    #include
    #include
    using namespace std;
    class Person
    {
    public:
    	//设置姓名
    	void setName(string name)
    	{
    		p_name = name;
    	}
    	//获取姓名
    	string getName()
    	{
    		return p_name;
    	}
    	//获取年龄
    	int getAge()
    	{
    
    		return p_age;
    	}
    	//设置年龄
    	void setAge(int age)
    	{
    
    		p_age = age;
    		if (age < 0 || age >150)
    		{
    			p_age = 0;
    			cout << "什么鬼" << endl;
    			return;
    		}
    	}
    	//设置伙伴
    	void setLover(string lname)
    	{
    		lover = lname;
    	}
    private:
    	//姓名 可读可写
    	string p_name;
    	//年龄 可读可写加个范围
    	int p_age;
    	//伙伴  只写
    	string lover;
    };
    
    int main(void)
    {
    	Person p1;
    	p1.setName("张三");
    	cout << "姓名:" << p1.getName() << endl;
    	p1.setAge(18);
    	cout << "年龄:" << p1.getAge() << endl;
    	p1.setLover("赵四");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    将一个类拆分成两个文件

    point.h

    #pragma once
    #include
    using namespace std;
    class Point
    {
    public:
    	void setx(int x);
    	int getx();
    	void sety(int y);
    	int gety();
    private:
    	int c_x;
    	int c_y;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    point.cpp

    #include"point.h"
    //Point::告诉编译器这是Point作用域下面的一个成员函数
    void Point::setx(int x)
    {
    	c_x = x;
    }
    int Point::getx()
    {
    	return c_x;
    }
    void Point::sety(int y)
    {
    	Point::c_y = y;
    }
    int Point::gety()
    {
    	return c_y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    对象的初始化清理
    • 在生活中我们所购买的点子产品大多都有恢复出厂设置,在某一天我们不使用的时候清除自己的数据来保证自己信息的安全。
    • C++中的面向对象来源生活,每个对象也会有初识设置以及对象销毁前的清理数据的设置。
    构造函数和析构函数
    对象的初始化和清理也是两个非常重要的安全问题。
    
    一个对象或者变量没有初识状态,对其使用后的后果是未知的。
    
    同样的使用完一个对象或者变量,没有及时进行清理,也会造成一定的安全问题。
    
    C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供,但是编译器提供的构造函数和析构函数是空实现。
    
    构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
    
    析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    构造函数语法:
    	类名(){}
    	
    	构造函数没有返回值也不写void
    	函数名称与类名相同
    	构造函数可以有参数,因此可以发生重载
    	程序在调用对象的时候会自动调用构造,无须手动调用,而且只会调用一次
    
    eg:
    #include
    using namespace std;
    class Person
    {
    public:
    	Person()
    	{
            //不写的也会自动创建一个,只不过里面是空的
    		cout << "构造函数的调用" << endl;
    	}
    };
    void test01()
    {
    	Person p;//创建了一个对象但是没有调用这个函数
    }
    int main(void)
    {
    	test01();
        system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述

    析构函数语法:
    	~类名(){}
    	
    	析构函数没有返回值也不写void
    	函数名称与类名相同,在名称前加上~
    	析构函数不可以有参数,因此不可以发生重载
    	程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
    
    eg:
    #include
    using namespace std;
    class Person
    {
    public:
    	Person()
    	{
    		cout << "构造函数的调用" << endl;
    	}
    	~Person()
    	{
    		cout << "析构函数的调用" << endl;
    	}
    	//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
    };
    void test01()
    {	
    	Person p;//在栈上的数据,test01执行完之后会释放这个对象
    }
    int main(void)
    {
    	test01();
    	//Person p;在main函数中析构函数也会被调用在按完任意键之后
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述

    构造函数的分类及调用

    两种分类方式:

    • 按参数分为:有参构造和无参构造
    • 按类型分为:普通构造和拷贝构造

    三种调用方式:

    • 括号法
    • 显示法
    • 隐式转换法
    #include
    using namespace std;
    class  Person
    {	
    public:
    	//================================ 分类
    	//构造函数-无参构造-编译器提供的就是无参的(普通构造)
    	Person()
    	{
    		cout << "Person的无参构造函数调用" << endl;
    	}
    	//构造函数-有参构造(普通构造)
    	Person(int a)
    	{
    		age = a;
    		cout << "Person的有参构造函数调用" << endl;
    	}
    	
    	//拷贝构造函数(不能修改本身)
    	Person(const Person	&p)
    	{
    		//将传入的人身上的所有属性,拷贝到我身上。
    		age = p.age;
    		cout << "拷贝构造函数调用" << endl; 
    	}
    	//================================
    
    
    	~Person()
    	{
    		cout << "Person的析构函数调用" << endl;
    	}
    	
    	int age;
    };
    void test (){
    	//1.括号法
    	Person p;//默认构造函数调用
    	//	注意:使用默认构造函数的时候,不要加()
    	//	编译器会认为这是一个函数的声明
    	//	例如:Person p1();不会认为在创建对象
    	Person p2(10);//有参构造函数调用
    	Person p3(p2);//拷贝构造函数调用
    	cout << "p2的年龄为" << p2.age << endl;
    	cout << "p3的年龄为" << p3.age << endl;
    	
    
    	//2.显示法
    	Person p1;//无参
    	Person p2 = Person(10);//有参
    	//如果把等号右边的式子单独拿出来
    	//Person(10)这是一个匿名对象	
    	//特点:当前行执行结束后,系统会立即回收掉匿名对象
    	Person p3 = Person(p2);//拷贝
    	
    	//注意:不要利用拷贝函数初始化匿名对象-编译器会认为Person(p3) == Person p3 编译器会认为是对象的声明
    	//Person(p3)
    	
    	
    	//3.隐式转换法
    	Person p4 = 10;//相当与Person p4 = Person(10);
    	Person p5 = p4;//拷贝构造
    	
    }
    int main(void)
    {	
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    拷贝构造函数调用时机

    C++中拷贝构造函数调用时机通常有三种情况

    • 使用一个已经创建完毕的对象来初始化一个新对象
    • 值传递的方式给函数参数传值
    • 以值方式返回局部对象
    #include
    using namespace std;
    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;	
    };
    //使用一个已经创建完毕的对象来初始化一个对象
    void test01()
    {
    	Person p1(20);
    	Person p2(p1);
    	cout << "p2的年龄为" << p2.m_Age << endl;
    }
    
    void dowork(Person p)
    {
    	
    }
    
    void test02()
    {
    	Person p;
    	dowork(p);
    }
    
    Person dowork2()
    {
    	Person p1;
    	cout << (int*)&p1 << endl;
    	return p1;
    }
    void test03()
    {
    	Person p = dowork2();
    	cout << (int*)&p << endl;
    }
    
    int main(void)
    {
    	//使用一个已经创建完毕的对象来初始化一个新对象
    	test01();
    	//值传递的方式给函数参数传值
    	test02();
    	//以值方式返回局部对象
    	test03();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    构造函数的调用规则

    默认情况下,C++编译器至少给一个类添加三个函数

    • 默认构造函数(无参、函数体为空)
    • 默认析构函数(无参、函数体为空)
    • 默认拷贝函数构造函数,对属性值拷贝

    构造函数调用规则如下:

    • 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
    • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
    #include
    using namespace std;
    class Person
    {
    public:
    	Person()
    	{
    		cout << "Person的默认构造函数调用" << endl;
    	}
    	Person(int age)
    	{
    		m_Age = age;
    		cout << "Person的有参构造函数调用" << endl;
    	}
    	Person(const Person& p)
    	{
    		m_Age = p.m_Age;
    		cout << "Person的拷贝构造函数调用" << endl;
    	}
    	~Person()
    	{
    		cout << "Person的默认析构函数调用" << endl;
    	}
    	int m_Age;
    	
    };
    void test()
    {
    	Person p;
    	p.m_Age = 18;
    	Person p2(p);
    	cout << "p2的年龄为" << p2.m_Age << endl;
    }
    void test02()
    {
    	Person p; // error Person没有合适的默认构造函数
    	
    	// 可正常使用
    	Person p(21);
    	Person p1(p);
    }
    
    void test03()
    {
    	Person p; // error Person没有合适的默认构造函数
    }
    int main(void)
    {
    	//构造函数的调用规则
    	//只要创建一个类,c++编译器会默认给每个类都添加至少3个函数
    	/*
    		默认构造(空实现)
    		析构函数(空实现)
    		拷贝函数
    	*/
    	test01();
    	
    
    
    	//如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
    	// 注释掉自己写的(上面)
    	//Person()
    	//{
    	//	cout << "Person的默认构造函数调用" << endl;
    	//}
    	
    	//Person(const Person& p)
    	//{
    	//	m_Age = p.m_Age;
    	//	cout << "Person的拷贝构造函数调用" << endl;
    	//}
    	test02();
    
    
    	//如果用户定义拷贝构造函数,C++不会再提供其他构造函数
    	// 注释掉自己写的(上面)
    	//Person()
    	//{
    	//	cout << "Person的默认构造函数调用" << endl;
    	//}
    	//Person(int age)
    	//{
    	//	m_Age = age;
    	//	cout << "Person的有参构造函数调用" << endl;
    	//}
    	test03()
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    深拷贝与浅拷贝

    浅拷贝:简单的赋值拷贝操作。

    深拷贝:在堆区中重新申请空间,进行拷贝操作。

    浅拷贝带来的问题——内存重复释放。

    #include
    using namespace std;
    //深拷贝与浅拷贝问题
    class Person
    {
    public:
    	Person()
    	{
    		cout << "Person的默认构造函数调用" << endl;
    	}
    	Person(int age,int height)
    	{
    		m_Height = new int(height);
    		m_Age = age;
    		cout << "Person的有参构造函数调用" << endl;
    	}
        Person(const Person& p)
    	{
    		cout << "Person的拷贝构造函数调用" << endl;
    		m_Age = p.m_Age;
    		m_Height = p.m_Height;编译器默认实现的就是这行代码
    		
    	}
    	~Person()
    	{
    		//将堆区开辟的数据进行释放
    		if (m_Height !=NULL)
    		{
    			delete m_Height;
    			m_Height = NULL;
    		}
    		cout << "Person的析构构造函数调用" << endl;
    	}
     
    	int m_Age;
    	int* m_Height;
    };
    void test()
    {
    	Person p1(18,166);
    	cout << p1.m_Age<<"\t" << *p1.m_Height << endl;
    	Person p2(p1);
    	cout << p2.m_Age<<"\t" <<*p2.m_Height<< endl;
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    在这里插入图片描述

    在这里插入图片描述
    浅拷贝的这个问题需要用深拷贝来解决

    重新在堆区找一块内存来存放他。

    自己实现拷贝构造函数来解决浅拷贝带来的问题

    解决:

    深拷贝——手动创建拷贝构造函数。

    Person(const Person& p)
    {
    	cout << "Person的拷贝构造函数调用" << endl;
    	m_Age = p.m_Age;
    	//m_Height = p.m_Height;编译器默认实现的就是这行代码
    	//深拷贝操作
    	m_Height = new int(*p.m_Height);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果有属性在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

    初识化列表

    C++提供了初始化列表语法,用来初始化对象。

    语法:
    	构造函数():属性1(值1),属性2(值2)…{}
    
    	#include
    using namespace std;
    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)
    	{
    
    	}
    	int m_A;
    	int m_B;
    	int m_C;
    };
    void test()
    {
    	//Person p(10,20,30);
    	Person p(30,20,10);
    	cout << p.m_A << endl;
    	cout << p.m_B << endl;
    	cout << p.m_C << endl;
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    类对象作为类成员

    C++中类的成员可以是另一个类的对象,我们称该成员为对象成员。

    class A{}
    class B{    
    	A a;
    }
    B类中有对象A作为成员,A为对象成员。
    
    那么当创建B对时,A与B的构造和析构的顺序是怎么样的?析构呢?
    
    #include
    #include
    using namespace std;
    class Phone
    {
    public:
    	Phone(string  p)
    	{
    		Phonename = p;
    		cout << "Phone的构造函数调用" << endl;
    	}
    	~Phone()
    	{
    		cout << "Phone的析构函数调用" << endl;
    	}
    	string Phonename;
    };
    
    class Person
    {
    public:
    	//Phone Personphone = pname 隐式转换法
    	Person(string name, string pname):Personname(name), Personphone(pname)
    	{
    		cout << "Person的构造函数调用" << endl;
    	}
    	~Person()
    	{
    		//自身的析构函数先进行,之后其它类再进行。
    		cout << "Person的析构函数调用" << endl;
    	}
    	string Personname;
    	//当其他类的对象作为本类的成员时,构造时先构造其他类的对象,再构造自身。
    	Phone Personphone;
    };
    void test()
    {
    	Person p("张三", "华为"); 
    	cout << p.Personname<< endl;
    	cout << p.Personphone.Phonename<< endl;
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    静态成员

    静态成员就是在成员变量和成员函数前面加上关键字啊static,称为静态成员。
    静态成员分为:

    • 静态成员变量
    所有对象共享同一份数据
    在编译阶段分配内存
    类内声明,类外初始化
    
    • 1
    • 2
    • 3
    • 静态成员函数
    所有成员共享同一个函数
    静态成员函数只能访问静态成员变量
    
    
    eg:
    #include
    using namespace std;
    class Person
    {
    public:
    	//静态成员变量特点:
    	//1.在编译阶段分配内存 全局区
    	//2.所有对象共享同一份数据
    	//3.类内声明,类外初始化
    	static int m_A; //加static 静态成员变量
    	int m_C;//非静态成员变量
    
    
    	//静态成员函数特点:
    	//所有成员共享同一个函数
    	//静态成员函数只能访问静态成员变量
    	static void func()
    	{
    		m_A = 100;	//静态成员函数 可以访问 静态成员变量
    		m_C = 100;	//静态成员函数 不可以访问 非静态成员变量,无法区分是哪个对象的
    		cout << static void func << endl;
    	}
    
    //静态成员变量也有访问权限
    private:
    	// 静态成员变量和静态成员函数都有访问权限
    	static int m_B; //加static 静态成员变量
    	
    	static void funaa()
    	{
    		
    	}
    };
    
    //类外初始化
    int Person::m_A = 100;
    int Person::m_A = 200;
    void test01()
    {
    	Person p;
    	// int Person::m_A = 100; 不在类外初始化会报错
    	cout << p.m_A << endl;
    	
    	Person p2;
    	//所有对象共享同一份数据,所以输出200
    	p2.m_A = 200;
    	cout << p.m_A << endl;
    }
    
    void test02()
    {
    	// 静态成员变量 不属于某个对象上,所有的对象都共享同一份数据
    	// 因此静态成员变量有两种访问方式
    
    	//1、通过对象进行访问
    	Person p;
    	cout << p.m_A << endl;
    	
    	//2、通过类名进行访问
    	cout << Person::m_A << endl;
    	
    	//error private 私有权限访问不到
    	cout << Person::m_B << endl;
    }
    
    void test03()
    {
    	//1.通过对象访问
    	Person p;
    	p.func();
    	//2.通过类名访问
    	Person::func();
    	
    	//error private 私有权限访问不到
    	Person::funaa();
    }
    
    int main(void)
    {
    	//所有对象共享同一份数据
    	test01();
    	//静态成员变量两种访问方式
    	test02();
    	
    	test03();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    C++对象模型和this指针
    成员变量和成员函数分开存储

    在C++中,类内的成员变量和成员函数分开存储
    只有非静态成员变量才属于类的对象上

    #include
    using namespace std;
    class Person
    {
    	int m_A;//非静态成员属于类对象上的。 
    	static int m_B;//静态的成员变量不属于类的对象上。
    	void func() {}//非静态成员函数不属于类的对象上
    	static void func2(){}//静态成员函数不属于类的对象上
    };
    
    int Person::m_B = 10;
    
    void test01()
    {
    	Person p;
    	//空对象占用内存空间为1
    	/*C++编译器给每个空对象也分配一个字节的空间,为的是区分空对象在占内存的位置,
    	没一个空对象也应该有一个独一无二的内存地址*/
    	//class Person
    	//{
    	//};
    	cout << sizeof(p) << endl;
    }
    
    void test02()
    {	
    	//int m_A;//非静态成员属于类对象上的。 
    	Person p;
    	cout << sizeof(p) << endl; // 4
    
    	//static int m_B;//静态的成员变量不属于类的对象上。
    	cout << sizeof(p) << endl; // 4
    
    	//void func() {}//非静态成员函数不属于类的对象上
    	cout << sizeof(p) << endl; // 4
    
    	//static void func2(){}//静态成员函数不属于类的对象上
    	cout << sizeof(p) << endl; // 4
    }
    
    int main(void)
    {
    	//空对象占用内存空间
    	test01();
    	test02();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    this指针的概念
    通过上一个知识点《成员变量和成员函数是分开存储的》我们知道C++中成员变量和成员函数是分开存储的。
    
    每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码。
    
    那么问题是:这一块代码是如何区分是哪个对象调用自己的呢?
    
    C++通过提供特殊的对象指针,this指针,解决上述问题。
    
    this指针指向被调用的成员函数所属的对象。
    
    (谁调的,this就指向谁)
    
    this指针是隐含每个非静态成员函数内的一种指针。
    
    this指针不需要定义,直接使用即可。
    
    this指针的用途
    
    	当形参和成员变量同名时,可用this指针来区分
    	在类的非静态成员函数中返回对象本身,可使用return *this
    
    eg:
    	
    返回对象本身用*this
    #include
    using namespace std;
    class Person
    {
    public:
    	Person(int age)
    	{
    		age = age; //error
    		
    		//解决名称冲突
    		//this指针指向的是被调函数的成员函数所属的对象
    		//这里指向的就是p
    		this->age = age;
    	}
    	
    	//返回本体要用应用的方式进行返回
    	//这里返回值如果是Person,就创建了一个新的对象
    	Person& PersonAddPerson(Person &p)
    	{
    		this->age += p.age;
    		return *this;
    	}
    	
    	int age;//注意起名规范也可以解决名字冲突的问题
    	//eg: int m_Age
    };
    
    void test()
    {
    	Person p(18);
    	cout << p.age << endl;
    }
    //返回对象本身用*this
    void test01()
    {
    	Person p1(10);
    	Person p2(10);
    	p2.PersonAddPerson(p1);//将p1和p2的加在一起
    	//多次追加,return *this;
    	//链式编程思想
    	p2.PersonAddPerson(p1).PersonAddPerson(p1);
    	cout << p2.age << endl; // 30
    }
    int main(void)
    {
    	//解决名称冲突
    	test()
    	//返回对象本身用*this
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    空指针返回成员函数

    C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针,如果用到this指针,需要加以判断来保证代码的健壮性。

    #include
    using namespace std;
    class Person
    {
    public:
    	void ShowClassName()
    	{
    		cout << "this is Person class" << endl;
    	}
    	void ShowPersonAge()
    	{	
    		//没有下面内容会报错
    		//报错原因是因为传入的指针是NULL——无中生有,用一个空指针访问里面的属性 
    		cout << m_Age << endl; // == cout << this->m_Age << endl;
    
    		//提高健壮性,空的就直接返回,防止代码崩溃
    		if (this == NULL)
    		{
    			return;
    		}
    		cout << this->m_Age << endl;
    	}
    	int m_Age;
    };
    void test()
    {
    	Person* p = NULL;
    	p->ShowClassName();
    	p->ShowPersonAge();
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    const修饰成员函数

    常函数:

    • 成员函数后加const后我们称这个函数为常函数
    • 常函数不可以修改成员属性
    • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

    常对象:

    • 声明对象前const称该对象为常对象。
    • 常对象只能调用常函数。
    #include
    using namespace std;
    //常函数
    class Person
    {
    public:
    	//this指针的本质是指针常量,指针的指向是不可以修改的
    	//就相当于Person *const this;
    	//在成员函数后面加const修饰的是this指向,让指针指向的值也不可以修改
    	void showPerson() const
    	{
    		//加个const就不允许修改了 
    		//就相当于const Person *const this;
    		this->m_a = 100; // error
    		//this = NULL;this指针是不可以修改指针的指向的
    
    		this->m_b = 100; // mutable int m_b 可以修改
    	}
    	int m_a;
    	mutable int m_b;//加了mutable修饰的特殊变量,即使在常函数,常对象中,也可以修改这个值
    
    	void func()
    	{
    		m_a = 100;//在普通成员函数中是可以修改的
    	}
    };
    void test()
    {
    	Person P;
    	P.showPerson();
    }
    
    void test1()
    {
    	const Person p;//在对象前加const,变为常对象
    	//p.m_a = 100; error 不能修改
    	p.m_b = 100;
    	//常对象只能调用常函数 
    	p.showPerson();
    	//p.func();常对象不能调用普通成员函数,因为普通成员函数可以修改属性。
    	
    }
    int main(void)
    {
    	//常函数
    	test();
    	//常对象
    	test1();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    友元

    客厅就是Public,你的卧室就是Private

    客厅所有人都可以进去,但是你的卧室只有和你亲密的人可以进。

    在程序中,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术。

    友元的目的就是让一个函数或者类 访问另一个类中的私有元素。

    友元的关键字friend

    友元的三种实现:
    • 全局函数做友元
    就是将此函数在类的最上面写一个声明,前面加一个friend。
    eg:
    #include
    #include
    using namespace std;
    
    class Building 
    {
    	//goodgay全局函数是Building类的一个好朋友,可以访问你家的卧室(私有成员)
    	friend void goodgay(Building* building);
    	
    public:
    	Building()
    	{
    		m_SittingRoom = "客厅";
    		m_BedRoom = "卧室";
    	}
    public:
    	string m_SittingRoom;
    private:
    	string m_BedRoom;
    };
    
    //全局函数
    void goodgay(Building* building)
    {
    	cout << "好基友全局函数正在访问你的" << building->m_SittingRoom << endl;
    	
    	cout << "好基友全局函数正在访问你的" << building->m_BedRoom << endl;
    }
    void test()
    {
    	Building building;
    	goodgay(&building);
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 类做友元
    一个类在另一个中friend class xx。
    eg:
    #include
    #include
    using namespace std;
    //在前面先声明一下
    class Building;
    
    class GoodGay
    {
    public:
    	GoodGay();
    public:
    	void visit();//参观函数 访问Building中的属性
    	Building* building;
    };
    
    
    class Building
    {
    	//GoodGay是Building类的好朋友,可以访问其私有属性
    	friend class GoodGay;
    public:
    	Building();
    public:
    	string m_SittingRoom;
    private:
    	string m_BedRoom;
    };
    //在类外写成员函数
    Building::Building()
    {
    	m_SittingRoom = "客厅";
    	m_BedRoom = "卧室";
    }
    GoodGay::GoodGay()
    {
    	//创建一个Building对象
    	building = new Building;
    }
    void GoodGay::visit()
    {
    	cout << "好基友正在访问你的" << building->m_SittingRoom << endl;
    	cout << "好基友正在访问你的" << building->m_BedRoom << endl;
    }
    
    void test()
    {
    	GoodGay gy;
    	gy.visit();
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 成员函数做友元
    告诉编译器 另一个类中的xx成员函数作为本类的好朋友,可以访问私有函数。
    
    eg:
    #include
    #include
    using namespace std;
    
    class Building;
    class GoodGay
    {
    public:
    	GoodGay();
    	void visit();//可以访问Building中私有成员
    	void visit1();//不可以访问Building中私有成员
    	Building* builidng;	
    };
    class Building
    {
    	//告诉编译器 GoodGay类中的visit成员函数作为本类的好朋友,可以访问私有函数
    	friend void GoodGay::visit();
    public:
    	Building(); 
    public:
    	string m_SittingRoom;
    private:
    	string m_BedRoom;
    };
    
    Building::Building()
    {
    	m_SittingRoom = "客厅";
    	m_BedRoom = "卧室";
    }
    
    GoodGay::GoodGay()
    {
    
    	builidng = new Building;
    }
    void GoodGay::visit()
    {
    	cout << "visit正在访问" << builidng->m_SittingRoom << endl;
    	cout << "visit正在访问" << builidng->m_BedRoom << endl;
    }
    void GoodGay::visit1()
    {
    	cout << "visit1正在访问" << builidng->m_SittingRoom << endl;
    
    }
    void test()
    {
    	GoodGay gg;
    	gg.visit();
    	gg.visit1();
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    运算符重载

    运算符重载的概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

    加号运算符重载

    作用:实现两个自定义数据类型相加的运算。
    
    例如:两个整型相加编译器知道该怎么进行运算,如果是两个自定义出来的类型,两个Person想加,编译器就不知道该怎么运算了。
    
    eg :
    #include
    #include
    using namespace std;
    //加号运算符重载
    
    class Person
    {
    public:
    	//1.成员函数重载+
    	/*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;
    };
    
    
    //2.全局函数重载+
    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 test01()
    {
    	Person p1;
    	p1.m_A = 10;
    	p1.m_B = 10;
    	Person p2;
    	p2.m_A = 10;
    	p2.m_B = 10;
    	//成员函数重载本质调用
    	//Person p3 = p1.operator+(p2);
    	//Person p3 = p1 + p2;//可以简化成这种形式
    	
    	//全局函数重载的本质调用
    	//Person p3 = operator+(p1,p2);
    	/*cout << p3.m_A << endl;
    	cout << p3.m_B << endl;*/
    	
    	//运算符重载也可以发生函数重载
    	Person p3 = p1 + 10;
    	cout << p3.m_A << endl;
    	cout << p3.m_B << endl;
    }
    int main(void)
    {
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    总结:

    • 对于内置的数据类型的表达式的运算符是不可能改变的
    • 不要滥用运算符重载

    左移运算符重载

    作用:可以输出自定义的类型
    
    #include
    using namespace std;
    class Person
    {
    	friend ostream& operator<<(ostream& cout, Person& p);
    public:
    	Person(int a, int b)
    	{
    		m_A = a;
    		m_B = b;
    	}
    	//利用成员函数重载左移运算符p.operator<<(cout)简化版本p<
    	//一般我们不会利用成员函数来重载<<运算符,以为无法实现cout在左边
    	/*void operator<<(ostream &cout,Person &p)
    	{
    		cout << p.m_A << endl;
    		cout << p.m_B << endl;
    	}*/
    private:
    	int m_A;
    	int m_B;
    };
    //只能利用全局函数来重载左移运算符
    ostream& operator<<(ostream &cout, Person &p) //这样写的本质就是operator<<(cout,p)简化版本就是cout<
    {
    	cout << p.m_A << endl;
    	cout << p.m_B << endl;
    	return cout;
    }
    void test()
    {
    	Person p(10,10);
    	cout << p << "hello world" << endl;
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    总结:重载左移运算符配合友元可以实现输出自定义数据类型。

    递增运算符重载

    作用:通过重载递增运算符,实现自己的整型数据。
    
    eg:
    #include
    using namespace std;
    //重载递增运算符
    class MyInteger
    {
    	friend ostream& operator<<(ostream& cout, MyInteger myint);
    public:
    	MyInteger()
    	{
    		m_Num = 0;
    	}
    	//重载++运算符——前置
    	//返回引用是为了一直对一个数据进行递增操作
    	MyInteger& operator++()
    	{
    		++m_Num;
    		return *this;
    	}
    	
    	//重载++运算符——后置
    	MyInteger operator++(int)//这个int在这里作为占位参数,用来区分前置递增和后置递增
    	{
    		MyInteger temp = *this;
    		m_Num++;
    		return temp;
    		//后置递增要返回值,因为如果返回引用,这里相当于返回的是一个局部对象的引用。
    		//局部对象在当前函数执行完毕之后就被释放掉了,还要返回引用就是非法操作。
    	}
    private:
    	int m_Num;
    };
    //全局函数重载左移运算符
    ostream& operator<<(ostream& cout, MyInteger myint)
    {
    	cout << myint.m_Num << endl;
    	return cout;
     }
    void test()
    {
    	MyInteger myint;
    	cout << ++(++myint);
    	cout <<myint;
    }
    void test02()
    {
    	MyInteger myint;
    	cout << myint++ << endl;
    	cout << myint << endl;
    }
    int main(void)
    {
    	//test();
    	test02();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    总结:前置递增返回引用,后置递增返回值。

    赋值运算符重载

    C++编译器至少给一个类添加4个函数
    	默认构造函数(无参,函数体为空)
    	默认析构函数(无参,函数体为空)
    	默认拷贝构造函数,对属性进行值拷贝
    	赋值运算符operator=,对属性进行值拷贝
    	
    	如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
    
    eg:
    #include
    using namespace std;
    class Person
    {
    public:
    	Person(int age)
    	{
    		m_Age = new int(age);
    	}
    	~Person()
    	{
    		if (m_Age != NULL)
    		{
    			delete m_Age;
    			m_Age = NULL;
    		}
    	}
    	//重载赋值运算符
    	Person& operator=(Person &p)
    	{
    		//编译器默认提供的是浅拷贝操作
    		//m_Age = p.m_Age;
    		//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝。
    		if (m_Age != NULL)
    		{
    			delete m_Age;
    			m_Age = NULL;
    		}
    		//深拷贝操作
    		m_Age = new int(*p.m_Age);
    		return *this;
    	}
    	int *m_Age;
    };
    void test1()
    {
    	Person p1(18);
    	Person p2(20);
    	Person p3(30);
    	p3 = p2 = p1;
    	cout << *(p1.m_Age) << endl;
    	cout << *(p2.m_Age) << endl;
    	cout << *(p3.m_Age) << endl;
    }
    int main(void)
    {
    	test1();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    关系运算符重载

    作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
    
    eg:
    #include
    #include
    using namespace std;
    class Person
    {
    public:
    	//重载==
    	bool operator==(Person &p)
    	{
    		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
    		{
    			return true;
    		}
    		else
    		{
    			return false;
    		}
    	}
    	bool operator!=(Person &p)
    	{
    		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
    		{
    			return false;
    		}
    		else
    		{
    			return true;
    		}
    	}
    	Person(string name, int age)
    	{
    		m_Name = name;
    		m_Age = age;
    	}
    	string m_Name;
    	int m_Age;
    };
    void test()
    {
    	Person p1("张三", 20);
    	Person p2("张三", 20);
    	if (p1 == p2)
    	{
    		cout << "p1和p2是相等的" << endl;
    	}
    	else
    	{
    		cout << "p1和p2是不相等的" << endl;
    	}
    	if (p1 != p2)
    	{
    		cout << "p1和p2是不相等的" << endl;
    	}
    	else
    	{
    		cout << "p1和p2是相等的" << endl;
    	}
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    函数调用运算符重载

    函数调用运算符()也可以重载
    由于重载后使用的方式非常像函数的调用,因此称为仿函数
    仿函数没有固定写法,非常灵活
    
    eg:
    #include
    #include
    using namespace std;
    //函数调用运算符重载
    class MyPrint
    {
    public:
    	//重载函数调用运算符
    	void operator()(string text)
    	{
    		cout << text << endl;
    	}
    	
    };
    class MyAdd
    {
    public:
    	int operator()(int a, int b)
    	{
    		return a + b;
    	}
    };
    void test()
    {
    	MyPrint myprint;
    	myprint("hello world");
    	MyAdd myadd;
    	cout << myadd(1, 2) << endl;
    	//匿名函数对象——特点:当前行被执行完立即释放
    	cout << MyAdd()(100,100) << endl;
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    继承

    继承是面向对象三大特性之一
    有些类与类之间存在特殊的关系,例如下图中:
    在这里插入图片描述
    我们发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性。
    这时候我们就可以考虑利用继承的技术,减少重复代码量。

    继承的基本语法
    继承的语法——class 子类 :继承方式 父类
    
    继承的好处:减少重复代码
    
    子类也称派生类
    父类也称基类
    
    
    派生类中的成员,包含两大部分:
    	一类是从基类继承过来的,一类是自己增加的成员。
    	从基类继承过来的表现其共性,而新增加的成员体现其个性。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 普通实现(未使用继承,做对比):
    #include
    #include
    using namespace std;
    
    //普通实现页面
    
    //java页面
    class Java
    {
    public:
    	void header()
    	{
    		cout << "首页、登录注册" << endl;
    	}
    	void footer()
    	{
    		cout << "帮助中心、交流合作" << endl;
    	}
    	void left()
    	{
    		cout << "java、python、c++" << endl;
    
    	}
    	void contenet()
    	{
    		cout << "java学科视频" << endl;
    	}
    };
    class Python
    {
    public:
    	void header()
    	{
    		cout << "首页、登录注册" << endl;
    	}
    	void footer()
    	{
    		cout << "帮助中心、交流合作" << endl;
    	}
    	void left()
    	{
    		cout << "java、python、c++" << endl;
    
    	}
    	void contenet()
    	{
    		cout << "python学科视频" << endl;
    	}
    };
    class Cpp
    {
    public:
    	void header()
    	{
    		cout << "首页、登录注册" << endl;
    	}
    	void footer()
    	{
    		cout << "帮助中心、交流合作" << endl;
    	}
    	void left()
    	{
    		cout << "java、python、c++" << endl;
    
    	}
    	void contenet()
    	{
    		cout << "c++学科视频" << endl;
    	}
    };
    void test()
    {
    	cout << "java" << endl;
    	Java java;
    	java.header();
    	java.footer();
    	java.left();
    	java.contenet();
    
    	cout << endl;
    
    	cout << "python" << endl;
    	Python python;
    	python.header();
    	python.footer();
    	python.left();
    	python.contenet();
    
    	cout << endl;
    
    	cout << "cpp" << endl;
    	Cpp cpp;
    	cpp.header();
    	cpp.footer();
    	cpp.left();
    	cpp.contenet();
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 继承方法实现
    #include
    #include
    using namespace std;
    
    //公共页面
    class BasePage
    {
    public:
    	void header()
    	{
    		cout << "首页、登录注册" << endl;
    	}
    	void footer()
    	{
    		cout << "帮助中心、交流合作" << endl;
    	}
    	void left()
    	{
    		cout << "java、python、c++" << endl;
    
    	}
    };
    
    
    //java页面
    class Java : public BasePage
    {
    public:
    	void contenet()
    	{
    		cout << "java学科视频" << endl;
    	}
    };
    class Python : public BasePage
    {
    public:
    	void contenet()
    	{
    		cout << "python学科视频" << endl;
    	}
    };
    class Cpp : public BasePage
    {
    public:
    	
    	void contenet()
    	{
    		cout << "c++学科视频" << endl;
    	}
    };
    void test()
    {
    	cout << "java" << endl;
    	Java java;
    	java.header();
    	java.footer();
    	java.left();
    	java.contenet();
    
    	cout << endl;
    
    	cout << "python" << endl;
    	Python python;
    	python.header();
    	python.footer();
    	python.left();
    	python.contenet();
    
    	cout << endl;
    
    	cout << "cpp" << endl;
    	Cpp cpp;
    	cpp.header();
    	cpp.footer();
    	cpp.left();
    	cpp.contenet();
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    继承方式
    • 公共继承
    • 保护继承
    • 私有继承
      在这里插入图片描述
    #include
    using namespace std;
    
    //公共继承
    class Base1
    {
    public:
    	int m_A;
    protected:
    	int m_B;
    private:
    	int m_C;
    };
    
    class Son1 :public Base1
    {
    public:
    	void func()
    	{
    		m_A = 10;//父类中的公共权限成员,到了子类中依然是公共权限
    		m_B = 20;//父类中的保护权限成员,到了子类中依然是保护权限
    		//m_C = 10;父类中的隐私权限成员,子类访问不到
    	}
    };
    void test01()
    {
    	Son1 son1;
    	son1.m_A = 100;
    	//son1.m_B = 100;保护权限的内容到了类外就无法访问了
    };
    //保护继承
    class Base2
    {
    public:
    	int m_A;
    protected:
    	int m_B;
    private:
    	int m_C;
    };
    class Son2 :protected Base2
    {
    	void func()
    	{
    		m_A = 100;//父类中公共权限的成员,因为是保护继承,到子类中变为保护权限
    		m_B = 100;//父类中保护权限的成员,保护继承后到了子类还是保护权限。
    		//m_C = 100;父类中的私有成员子类访问不到
    	}
    };
    void test02()
    {
    	Son2 son2;
    	//保护权限类外访问不到,所以在son2中m_A也访问不到了
    }
    //私有继承
    class Base3
    {
    public:
    	int m_A;
    protected:
    	int m_B;
    private:
    	int m_C;
    };
    class Son3:private Base3
    {
    	void func()
    	{
    		m_A = 100;//父类中公共成员,私有继承后,到了子类变为私有成员
    		m_B = 100;//父类中保护成员,私有继承后,到了子类变为私有成员
    		//m_C = 100;父类的私有权限成员仍然访问不到
    	}
    };
    void test03()
    {
    	Son3 son3;
    	//私有成员类外访问不到
    }
    //验证Son3私有继承后成员是否变成了私有属性
    class GrandSon3 :public Son3
    {
    	void func()
    	{
    		//访问不到父类的私有成员
    		//到了Son3中m_A,m_B,m_C全是私有成员,子类无法访问
    	}
    };
    int main(void)
    {
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    继承中的对象模型
    问题:从父类继承过来的对象,哪些属于子类对象?
    
    eg:
    #include
    using namespace std;
    //继承中的对象模型
    class Base
    {
    public:
    	int m_A;
    protected:
    	int m_B;
    private:
    	int m_C;
    };
    class Son:public Base 
    {
    public:
    	int m_D;
    };
    void test01()
    {
    	//父类中所有的非静态成员属性都会被子类继承下去
    	//父类中私有的成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
    	cout << "sizeof of son:" << sizeof(Son) << endl;//结果是16 = 12 + 4
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    继承中构造和析构的顺序
    子类继承父类后,当创建子类时,也会调用父类的构造函数。
    
    问题:父类和子类的构造函数和析构顺序怎么样的呢?
    #include
    using namespace std;
    class Base
    {
    public:
    	Base()
    	{
    		cout << "父类的构造函数" << endl;
    	}
    	~Base()
    	{
    		cout << "父类的析构函数" << endl;
    	}
    };
    class Son:public Base 
    {
    public:
    	Son()
    	{
    		cout << "子类的构造函数" << endl;
    	}
    	~Son()
    	{
    		cout << "子类的析构函数" << endl;
    	}
    };
    void test01()
    {
    	Son son;
    }
    int main(void)
    {
    	//继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    继承同名成员处理方式
    问题:当子类与父类出现同名的成员。如何通过子类对象,访问到子类或父类中同名的数据呢?
    
    eg:
    #include
    using namespace std;
    class Base
    {
    public:
    	Base()
    	{
    		m_A = 100;
    	}
    	void func()
    	{
    		cout << "父类同名成员函数调用" << endl;
    	}
    	void func(int a)
    	{
    		cout << "父类同名重载成员函数调用" << endl;
    	}
    	int m_A;
    };
    class Son:public Base 
    {
    public:
    	Son()
    	{
    		m_A = 200;
    	}
    	void func()
    	{
    		cout << "子类同名成员函数调用" << endl;
    	}
    	int m_A;
    };
    //同名成员属性处理方式
    void test01()
    {
    	Son son;
    	cout <<son.m_A<< endl;
    	cout <<son.Base::m_A<< endl;
    }
    
    //同名成员函数处理方式
    void test02()
    {
    	Son son1; 
    	son1.func();//子
    	son1.Base::func();//父
    	son1.Base::func(10);
    }
    int main(void)
    {
    	//子类对象可以直接访问到子类中同名成员
    	//子类对象加作用域可以访问到父类同名成员
    	test01()
    	//当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类同名函数。
    	test02();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    继承同名静态成员处理方式
    问题:继承中同名的静态成员在子类对象上是如何进行访问的呢?
    
    eg:
    #include
    using namespace std;
    class Base
    {
    public:
    	static void func()
    	{
    		cout << "父类静态成员函数调用" << endl;
    	}
    	static void func(int a)
    	{
    		cout << "父类静态成员重载函数调用" << endl;
    	}
    	static int m_A;
    };
    int Base::m_A = 100;
    class Son :public Base
    {
    public:
    	static void func()
    	{
    		cout << "子类静态成员函数调用" << endl;
    	}
    	static int m_A;
    };
    int Son::m_A = 200;
    //同名静态成员
    void test()
    {
    	//通过对象访问
    	Son son1;
    	cout << "通过对象访问" << endl;
    	cout << son1.m_A << endl;
    	cout << son1.Base::m_A << endl;
    	//通过类名访问
    	cout << "通过类名访问" << endl;
    	cout << Son::m_A << endl;
    	//第一个::代表通过类名方式访问,第二个::代表访问父类作用域下
    	cout << Son::Base::m_A << endl;
    }
    //同名静态函数
    void test01()
    {
    	//通过对象访问
    	Son son2;
    	cout << "通过对象访问" << endl;
    	son2.func();
    	son2.Base::func();  
    	//通过类名访问
    	cout << "通过类名访问" << endl;
    	Son::func();
    	Son::Base::func();
    
    	//父类同名重载成员函数调用
    	//子类出现和父类同名的静态成员函数,也会隐藏掉父类中所有同名成员函数(重载)
    	//如果想访问父类中被隐藏的同名成员,需要加作用域
    	Son::Base::func(100);
    }
    int main(void)
    {
    	//同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和类名)。
    	
    	test();
    	cout << "我是分割线------" << endl;
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    多继承语法

    C++允许一个类继承多个类

    语法:
    	class 子类:继承方式 父类1,继承方式 父类2
    
    多继承可能会引发父类中有同名成员出现,需要加作用域区分
    
    C++实际开发中不建议使用多继承
    
    eg:
     #include
    using namespace std;
    //多继承语法
    class Base1
    {
    public:
    	Base1()
    	{
    		m_A = 100;
    	}
    	int m_A;
    };
    class Base2
    {
    public:
    	Base2()
    	{
    		m_A = 200;
    	}
    	int m_A;
    };
    //子类需要继承base1和base2
    class Son:public Base1,public Base2
    {
    public:
    	Son()
    	{
    		m_C = 300;
    		m_D = 400;
    	}
    	int m_C;
    	int m_D;
    };
    void test01()
    {	
    	Son son1;
    	cout << sizeof(son1) << endl;//16
    	cout << "第一个父类的m_A:" << son1.Base1::m_A<<endl;
    	cout << "第二个父类的m_A:" << son1.Base2::m_A<<endl;
    }
    int main(void)
    {
    	//多继承中如果父类中出现了同名情况,子类使用时要加作用域。
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    菱形继承

    两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承称为菱形继承,或者钻石继承。

    典型的菱形继承案例
    在这里插入图片描述

    菱形继承问题:

    羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
    草泥马继承动物的数据继承了两份,其实这份数据只需要一份就可以。

    vbptr——虚基类

    继承了两个指针,两个指针通过偏移量找到了唯一的数据。
    在这里插入图片描述

    #include
    using namespace std;
    class Animal
    {
    public:
    	int m_Age;
    };
    //利用虚继承可以解决菱形继承问题
    //在继承之前加上关键字virtual变为虚继承
    // Animal类称为虚基类
    //羊
    class Sheep:virtual public Animal
    {
    		
    };
    //驼
    class Tuo:virtual public Animal
    {
    
    };
    //羊驼
    class SheepTuo :public Sheep,public Tuo
    {
    
    };
    void test01()
    {
    	SheepTuo st;
    	st.Sheep::m_Age = 18;
    	st.Tuo::m_Age = 28;
    	//当菱形继承,当两个父类拥有相同的数据,需要加作用域来区分
    	cout << st.Sheep::m_Age << endl;
    	cout << st.Tuo::m_Age << endl;
    	
    	//这份数据我们知道,只有一份就可以了,菱形继承导致数据有两份,资源浪费
    	cout << st.m_Age << endl;
    	
    }
    int main(void)
    {
    	//菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。
    	//利用虚继承可以解决菱形继承问题——virtual
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    多态

    多态是C++面向对象三大特性之一

    多态分为两种

    • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
    • 动态多态:派生类和虚函数实现运行时多态

    静态多态和动态多态的区别

    • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
    • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

    多态的优点:

    • 代码组织结构清晰
    • 可读性强
    • 利于前期和后期的扩展以及维护
    #include
    using namespace std;
    class Animal
    {
    public:
    	//加上virtual变成虚函数,实现地址晚绑定
    	virtual void speak()
    	{
    		cout << "动物在说话"<< endl;
    	}
    };
    
    class Cat :public Animal
    {
    public:
    	//子类重写父类的虚函数
    	void speak()
    	{
    		cout << "小猫在说话" << endl;
    	}
    };
    
    class Dog : public Animal
    {
    public:
    	//子类重写父类的虚函数
    	void speak()
    	{
    		cout << "小狗在说话" << endl;
    	}
    };
    
    //执行说话的函数
    //地址早绑定,在编译阶段就确定函数地址
    //如果想让猫说话,那么这个函数的地址就不能提前绑定,需要在运行阶段进行绑定(加上virtual变成虚函数,实现地址晚绑定)
    void doSpeak(Animal &animal)//Animal &animal = cat;
    {
    	animal.speak();
    }
    void test01()
    {
    	Cat cat;
    	doSpeak(cat);
    	Dog dog;
    	doSpeak(dog);
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    动态多条满足条件
    	1.有继承关系
    	2.子类重写父类的虚函数
    	重写要求:函数返回值类型 函数名 参数列表 完全相同 
    
    动态多态的使用
    	父类的指针或者引用 指向子类的对象
    	//Animal &animal = cat;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    多态的原理剖析

    当子类重写父类的虚函数后,子类中的虚函数表内部会替换成子类的虚函数地址。(本身继承的是父类的虚函数地址)

    在这里插入图片描述
    虚函数(表)指针

    vfptr
        v - virtual
        f - function
        prt - pointer
    
    • 1
    • 2
    • 3
    • 4

    虚函数表

    表内记录一个虚函数的地址

    vftable
        v - virtual
        f - function
    
    • 1
    • 2
    • 3

    父类
    在这里插入图片描述
    子类重写前(子类中的虚函数表内部为父类的虚函数地址。)
    在这里插入图片描述
    子类重写前(子类中的虚函数表内部会替换成子类的虚函数地址。)
    在这里插入图片描述

    多态案例(计算器类)

    分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类。

    普通写法
    #include
    #include
    using namespace std;
    class Calculator
    {
    public:
    	int getResult(string oper)
    	{
    		if (oper == "+")
    		{
    			return m_Num1 + m_Num2;
    		}
    		else if (oper == "-")
    		{
    			return m_Num1 - m_Num2;
    		}
    		else if (oper == "*")
    		{
    			return m_Num1 * m_Num2;
    		}
    		//如果想扩展新的功能,需要修改原码
    		//在真实的开发中,实行开闭原则,对扩展进行开放,对修改进行关闭
    	}
    	int m_Num1;
    	int m_Num2;
    };
    void test()
    {
    	Calculator c;
    	c.m_Num1 = 10;
    	c.m_Num2 = 10;
    	cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    
    ---------------------------   vs  --------------------------- 
    
    
    多态写法
    
    #include
    #include
    using namespace std;
    //利用多态实现计算器
    //实现计算器抽象类
    class AbstractCalculator
    {
    public:
    	virtual int getResult()
    	{
    
    		return 0;
    	}
    	int m_Num1;
    	int m_Num2;
    };
    //加法计算器类
    class AddCalculator :public AbstractCalculator
    {
    public:
    	int getResult()
    	{
    		return m_Num1 + m_Num2;
    	}
    };
    //减法计算器类
    class SubCalculator :public AbstractCalculator
    {
    public:
    	int getResult()
    	{
    		return m_Num1 - m_Num2;
    	}
    };
    //乘法计算器类
    class MulCalculator :public AbstractCalculator
    {
    public:
    	int getResult()
    	{
    		return m_Num1 * m_Num2;
    	}
    };
    void test()
    {	
    	//加法
    	AbstractCalculator* abc = new AddCalculator;//父类指针指向子类对象
    	abc->m_Num1 = 10;
    	abc->m_Num2 = 10;
    	cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
    	//堆区数据,手动开辟手动释放
    	delete abc;//堆区的数据被销毁了,但是指针的类型没有变
    	
    	// 减法
    	abc = new SubCalculator;
    	abc->m_Num1 = 10;
    	abc->m_Num2 = 10;
    	cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
    	delete abc;
    }
    int main(void)
    {
    	//多态使用条件
    	//父类指针或者引用指向子类对象
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    C++开发提倡利用多态设计程序框架,因为多态优点很多。

    纯虚函数和抽象类

    在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。

    因此可以将虚函数改为纯虚函数。

    纯虚函数语法
    virtual 返回值类型 函数名 (参数列表) = 0;
    
    当类中有了纯虚函数,这个类也称为抽象类。
    
    抽象类特点:
    	无法实例化对象
    	子类必须重写抽象类中的纯虚函数,否则也属于抽象类
    
    eg:
    #include
    using namespace std;
    //纯虚函数和抽象类
    class Base
    {
    public:
    	//只要有一个纯虚函数,这个类称为抽象类
    	//特点;无法实例化对象
    	virtual void func() = 0;//注意:不要忘掉virtual!
    	//抽象类的子类必须要重写父类中的纯虚函数,否则也属于抽象类
    };
    class Son :public Base
    {
    public:
    	void func()
    	{
    		cout << "func函数调用" << endl;
    	}
    };
    void test()
    {
    	//Base b1; 抽象类无法实例化对象
    	//new Base;抽象类无法实例化对象
    	//Son s1;//子类必须重写父类的虚函数,否则无法实例化对象
    	Base* abc = new Son;
    	abc->func();
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    多态案例(制作饮品)

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

     #include
    using namespace std;
    class AbstractDrinking
    {
    public:
    	//煮水
    	virtual void Boil() = 0;
    	//冲泡 
    	virtual void Brew() = 0;
    	//倒入杯中
    	virtual void Pour() = 0;	
    	//加入辅料
    	virtual void PutSomething() = 0;
    	//制作饮品
    	void makeDrink()
    	{
    		Boil();
    		Brew();
    		Pour();
    		PutSomething();
    	}
    };
    //制作咖啡
    class Coffee :public AbstractDrinking
    {
    public:
    	//煮水
    	virtual void Boil()
    	{
    		cout << "把水煮开" << endl;
    	}
    	//冲泡 
    	virtual void Brew()
    	{
    		cout << "冲泡咖啡" << endl;
    	}
    	//倒入杯中
    	virtual void Pour()
    	{
    		cout << "倒入杯中" << endl;
    	}
    	//加入辅料
    	virtual void PutSomething()
    	{
    		cout << "加入糖和牛奶" << endl;
    	}
    };
    //制作茶水
    class Tea :public AbstractDrinking
    {
    public:
    	//煮水
    	virtual void Boil()
    	{
    		cout << "把矿泉水煮开" << endl;
    	}
    	//冲泡 
    	virtual void Brew()
    	{
    		cout << "冲泡茶叶" << endl;
    	}
    	//倒入杯中
    	virtual void Pour()
    	{
    		cout << "倒入杯中" << endl;
    	}
    	//加入辅料
    	virtual void PutSomething()
    	{ 
    		cout << "加入柠檬" << endl;
    	}
    };
    //制作函数
    void DoWork(AbstractDrinking* abs)//父类指针指向子类对象AbstractDrinking* abs = new Coffee;
    {
    	abs->makeDrink();
    	delete abs;//手动释放
    	//堆区的数据被销毁了但是指针的类型没变
    }
    //制作
    void test()
    {
    	DoWork(new Coffee);
    	cout << "------我是分割线------" << endl;
    	DoWork(new Tea);
    }
    int main(void)
    {
    	test();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    虚析构和纯虚析构
    多态使用的时候,如果子类中有属性开辟到堆区,那么父类指针在释放的时无法调用到子类的析构代码。
    
    解决方法:将父类中的析构函数改为虚析构或者纯虚析构
    
    虚析构和纯析构共性:
    	可以解决父类指针释放子类对象,
    	都需要有具体的含函数实现
    
    虚析构和纯虚构的区别:
    	如果是纯虚析构,该类属于抽象类,无法实例化对象
    
    虚析构语法
    	virtual ~类名(){}
    
    纯虚析构语法:
    	virtual ~类名() = 0;//声明
    	类名::~类名(){}
    
    #include
    #include
    using namespace std;
    //虚析构和纯虚析构
    class Animal
    {
    public:
    	Animal()
    	{
    		cout << "Animal的构造函数调用" << endl;
    	}
    	
    	//利用虚析构可以解决父类指针释放对象时不干净的问题
    	/*virtual ~Animal()
    	{
    		cout << "Animal的析构函数调用" << endl;
    	}*/
    	
    	//纯虚析构,需要声明也需要实现
    	//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象	
    	virtual ~Animal() = 0;
    	
    	//纯虚函数,不需要实现
    	virtual void speak() = 0;
    };
    //纯虚析构函数
    Animal::~Animal()
    {
    	cout << "Animal纯析构函数调用" << endl;
    }
    class Cat :public Animal
    {
    public:
    	Cat(string name)
    	{
    		m_Name = new string(name);
    	}
    	virtual void speak()
    	{
    		cout << "Cat的构造函数调用" << endl;
    		cout << *m_Name << "小猫在说话" << endl;
    	}
    	~Cat()
    	{
    		if (m_Name != NULL)
    		{
    			cout << "Cat的析构函数调用" << endl;
    			delete m_Name;
    			m_Name = NULL;
    		}
    	}
    	string* m_Name;
    };
    void test01()
    {
    	Animal* animal = new Cat("Tom");
    	animal->speak();
    	/*
    	父类的指针在析构的时候,不会调用子类中的析构函数,
    	导致子类如果有堆区属性,会出现内存的泄漏情况。
    	解决:将父类的析构函数改为虚析构
    	*/
    	delete animal;
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    总结:

    1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象问题
    2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
    3. 拥有纯虚析构函数的类也属于抽象类
    多态案例(电脑组装)

    电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储),将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三台不同的电脑进行工作.

    #include
    using namespace std;
    //抽象不同零件类
    //抽象cpu
    class CPU
    {
    public:
    	//抽象的计算函数
    	virtual void calculate() = 0;
    };
    //抽象显卡类
    class VideoCard
    {
    public:
    	//抽象的显示函数
    	virtual void display() = 0;
    };
    //抽象内存条类
    class Memory
    {
    public:
    	//抽象的存储函数
    	virtual void storage() = 0;
    };
    //电脑类
    class Computer
    {
    public:
    	Computer(CPU* cpu, VideoCard* vc, Memory* mem)
    	{
    		m_cpu = cpu;
    		m_vc = vc;
    		m_mem = mem;
    	}
    	//提供一个工作的函数
    	void work()
    	{
    		//让零件工作起来,调用他的接口
    		m_cpu->calculate();
    		m_vc->display();
    		m_mem->storage();
    	}
    	//提供析构函数释放3个电脑零件
    	~Computer()
    	{
    		//释放CPU零件
    		if (m_cpu != NULL)
    		{
    			delete m_cpu;
    			m_cpu = NULL;
    		}
    		//释放显卡零件
    		if (m_vc != NULL)
    		{
    			delete m_vc;
    			m_vc = NULL;
    		}
    		//释放内存条零件指针
    		if (m_mem != NULL)
    		{
    			delete m_mem;
    			m_mem = NULL;
    		}
    	}
    private:
    	CPU* m_cpu;//CPU零件指针
    	VideoCard* m_vc;//显卡零件指针
    	Memory* m_mem;//内存条零件指针
    };
    //具体的厂商
    //Intel
    class IntelCPU :public CPU
    {
    public:
    	virtual	 void calculate()
    	{
    		cout<<"Intel的CPU开始计算了"<<endl;
    	}
    };
    class IntelVideoCard :public VideoCard
    {
    public:
    	virtual	 void display ()
    	{
    		cout << "Intel的显卡开始显示了" << endl;
    	}
    };
    class IntelMemory :public Memory
    {
    public:
    	virtual	 void storage()
    	{
    		cout << "Intel的内存条开始存储了" << endl;
    	}
    };
    //具体的厂商
    //Lenovo
    class LenovoCPU :public CPU
    {
    public:
    	virtual	 void calculate()
    	{
    		cout << "Lenovo的CPU开始计算了" << endl;
    	}
    };
    class LenovoVideoCard :public VideoCard
    {
    public:
    	virtual	 void display()
    	{
    		cout << "Lenovo的显卡开始显示了" << endl;
    	}
    };
    class LenovoMemory :public Memory
    {
    public:
    	virtual	 void storage()
    	{
    		cout << "Lenovo的内存条开始存储了" << endl;
    	}
    };
    //组装电脑
    void test01()
    {
    	//一台电脑零件
    	CPU* intelcpu = new IntelCPU;
    	VideoCard* videocard = new IntelVideoCard;
    	Memory* memory = new IntelMemory;
    	//创建第一台电脑
    	Computer* computer1 = new Computer(intelcpu, videocard, memory);
    	computer1->work();
    	delete computer1;
    	cout << "------我是分割线------" << endl;
    	//组装第二台电脑
    	Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
    	computer2->work();
    	delete computer2;
    	cout << "------我是分割线------" << endl;
    	//组装第三台电脑
    	Computer* computer3 = new Computer(new LenovoCPU,new IntelVideoCard,new LenovoMemory);
    	computer3->work();
    	delete computer3;
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }log.csdn.net/qq_51604330/article/details/118607922
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    文件操作

    程序运行时,产生的数据都属于临时数据,程序一旦运行结束就会被释放。

    通过文件可以将数据持久化。

    C++中对文件进行操作需要包含头文件< Fstream>

    文件类型分为两种:

    • 文本文件-文件以文本的ASCII码形式存储在计算机中
    • 二进制文件-文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

    操作文件的三大类:

    ofstream:	写操作
    ifstream	读操作
    fstream:	读写操作
    
    • 1
    • 2
    • 3
    文本文件

    写文件

    • 包含头文件——#include< fstream>
    • 创建流对象——ofstream ofs;
    • 打开文件——ofs.open(“文件路径”,打开方式)
    • 写数据——ofs<<“写入的数据”;
    • 关闭文件——ofs.close();

    文件打开方式:
    在这里插入图片描述
    注意:文件打开方式可以配合使用,利用|操作符

    例如:用二进制方式写文件

    ios::binary | ios::out
    
    • 1
    #include
    #include
    using namespace std;
    //文本文件写文件
    void test01()
    {
    	//1.包含头文件
    	//2.创建流对象
    	ofstream ofs;
    	//3.指定打开方式
    	ofs.open("test.txt", ios::out);//如果不指定文件路径,默认和你项目的文件路径一样
    	//4.写内容
    	ofs << "姓名:张三" << endl;
    	ofs << "性别:男" << endl;
    	ofs << "年龄:18" << endl;
    	//5.关闭文件
    	ofs.close();
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    读文件

    • 包含头文件——#include< fstream>
    • 创建流对象——ifstream ifs;
    • 打开文件并判断文件是否打开成功——ifs.open(“文件路径”,打开方式);
    • 读数据——四种方式读取
    • 关闭文件——ifs.close();
    #include
    #include
    #include
    using namespace std;
    void test01()
    {
    	//1.包含头文件
    	//2.创建流对象
    	ifstream ifs;
    	//3.打开文件,并且判断是否打开成功
    	ifs.open("test.txt",ios::in);
    	if (!ifs.is_open())
    	{
    		cout << "文件打开失败了" << endl;
    		return;
    	}
    	//4.读数据
    	//第一种
    	/*char buf[1024] = { 0 };
    	while (ifs>>buf)
    	{
    		cout << buf << endl;
    	}*/
    	
    	//第二种
    	/*char buf[1024] = { 0 };
    	while (ifs.getline(buf,sizeof(buf)))
    	{
    		cout << buf << endl;
    	}*/
    	
    	//第三种
    	/*string buf;
    	while (getline(ifs,buf))
    	{
    		cout << buf << endl;
    	}*/
    	
    	//第四种-不推荐
    	char c;
    	while ((c = ifs.get()) != EOF)//EOF——end of file
    	{
    		cout << c;
    	}
    	ifs.close();
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    二进制文件

    以二进制的方式对文件进行读写操作

    打开方式主要为ios::binary

    写文件

    二进制方式写文件主要利用流对象调用成员函数write

    函数原型:

    ostream& wirte(const char* buffer,int len);
    
    • 1

    参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

    #include
    #include
    using namespace std;
    //二进制写文件
    class Person
    {
    public:
    	char m_Name[64];
    	int m_Age;
    };
    void test01()
    {
    	//1.包含头文件
    	//2.创建头文件
    	ofstream ofs("person.txt", ios::out | ios::binary);
    	//3.打开文件
    	//ofs.open("person.txt",ios::out | ios::binary);
    	//4.写文件
    	Person p = { "张三",18 };
    	ofs.write((const char*)&p,sizeof(Person));
    	//5.关闭文件
    	ofs.close();
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    读文件

    二进制方式读文件主要利用流对象调用成员函数read

    函数原型:

    istream& read(char * buffer,int len);
    
    • 1

    参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

    #include
    #include
    using namespace std;
    //二进制读文件
    class Person
    {
    public:
    	char m_Name[64];
    	int m_Age;
    };
    void test01()
    {
    	//1.包含头文件
    	//2.创建流对象
    	ifstream ifs;
    	//3.打开文件&判读文件是否打开成功
    	ifs.open("person.txt", ios::in | ios::binary);
    	if (!(ifs.is_open()))
    	{
    		cout<<"打开失败"<<endl;
    		return;
    	}
    	//4.读文件
    	Person p;
    	ifs.read((char*)&p, sizeof(Person));
    	cout << "姓名:" << p.m_Name<<" " << "年龄:" << p.m_Age << endl;
    	//5.关闭文件
    	ifs.close();
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    模板

    模板就会通用的模具,大大提高复用性。

    例如生活中的一寸照片、PPT模板。

    模板的特点:

    • 模板不可以直接使用,它只是一个框架
    • 模板的通用并不是万能的
    函数模板
    • C++另一种编程思想称为泛型编程,主要利用的技术就是模板
    • C++提供两种模板机制,函数模板和类模板。

    函数模板语法

    template<typename T>
    函数声明或定义
    
    • 1
    • 2

    作用

    建立一个通用的函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
    
    • 1
    #include
    using namespace std;
    
    //函数模板
    //两个整型交换
    void  SwapInt(int &a, int &b)
    {
    	int temp = b;
    	b = a;
    	a = temp;
    }
    //两个浮点型交换
    void SwapDouble(double &a,double & b)
    {
    	double temp = a;
    	a = b;
    	b = temp;
    }
    
    
    //函数模板
    //声明一个模板,告诉编译器后面的代码中紧跟着的T不要报错,T是一个通用数据类型
    template<typename T>
    void MySwap(T& a, T& b)
    {
    	T Temp = a;
    	a = b;
    	b = Temp;
    }
    
    void test01()
    {
    	int a = 10;
    	int b = 20;
    	//利用函数模板进行交换
    	//1.自动类型推导
    	MySwap(a, b);
    
    	cout << a  << endl;
    	cout << b << endl;
    
    	double c = 11.1;
    	double d = 12.2;
    	//显示指定类型
    	MySwap<double>(c, d);
    	cout << c << endl;
    	cout << d << endl;
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    总结:

    • 函数模板利用关键字template
    • 使用函数类型模板有两种方式:自动类型推导、显示指定类型
    • 模板的目的是为了提高复用性,将类型参数化
    函数模板注意事项

    注意事项:

    • 自动类型推导,必须推导出一致的数据类型T才能使用
    • 模板必须要确定出T的数据类型,才可以使用
    函数模板案例
    利用函数模板封装一个排序的函数,可以对不用数据类型数组进行排序
    排序规则从大到小,排序算法为选择排序
    分别利用char数组和int数组进行测试
    
    
    eg:
    #include
    using namespace std;
    //实现通用 对数组进行排序的函数
    //规则 从大到小
    //算法 选择 
    //测试 char 数组 int 数组
    //交换的函数模板
    template<class T>
    void mySwap(T& a, T& b)
    {
    	T temp = a;
    	a = b;
    	b = temp;
    }
    
    template<class T>
    void mySort(T arr[],int len)
    {
    	for (int i =  0; i < len; i++)
    	{
    		int max = i;//认定最大值的下标
    		for (int j = i + 1; j < len; j++)
    		{
    			//认定的最大值比遍历出的数值要小,说明j下标的元素才是真正的最大值
    			if (arr[max] < arr[j])
    			{
    				max = j;
    			}
    		}
    		if (max != i)
    		{
    			//交换max和i元素
    			mySwap(arr[max], arr[i]);
    		}
    	}
    }
    
    //打印数组模板
    template<class T>
    void myPrint(T arr[], int len)
    {
    	for (int i = 0; i < len; i++)
    	{
    		cout << arr[i]<< endl;
    	}
    }
    
    void test01()
    {
    	char charArr[] = "badcfe";
    	int num = sizeof(charArr) / sizeof(char);
    	mySort(charArr, num);
    	myPrint(charArr, num);
    }
    void test02()
    {
    	int intArr[] = {2,3,78,9,7};
    	int num = sizeof(intArr) / sizeof(int);
    	mySort(intArr, num);
    	myPrint(intArr, num);
    }
    int main(void)
    {
    	test01(); 
    	test02();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    普通函数与函数模板的区别
    普通函数调用时可以发生自动类型转换(隐式类型推导)
    函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
    如果利用显式指定类型的方式,可以发生隐式类型转换
    
    #include
    using namespace std;
    
    //普通函数隐式类型转换
    int myAdd01(int a, int b)
    {
    	return a + b;
    }
    
    
    //函数模板
    template<class T>
    T myAdd02(T a, T b)
    {
    	return a + b;
    }
    
    
    
    void test01()
    {
    	int a = 10;
    	int b = 20;
    	char c = 'c';
    	cout << myAdd01(a, c) << endl;
    
    	//自动类型推导不行
    	//cout << myAdd02(a, c) << endl;
    
    	//显式指定类型行
    	cout << myAdd02<int>(a, c) << endl;
    }
    
    
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    总结:建议使用显式指定类型的方式,调用函数模板,因为可以自己确定通用类型T

    普通函数与函数模板的调用规则
    如果函数模板和普通函数都可以实现,优先调用普通函数
    可以通过空模板参数列表来强制调用函数模板
    函数模板也可以发生重载
    如果函数模板可以产生更好的匹配,优先调用函数模板
    
    #include
    using namespace std;
    void myPrint(int a, int b)
    {
    	cout << "调用普通函数" << endl;
    }
    template<class T>
    void myPrint(T a, T b)
    {
    	cout << "调用模板" << endl;
    }
    
    template<class T>
    void myPrint(T a, T b,T c)
    {
    	cout << "调用重载模板" << endl;
    }
    void test01()
    {
    	int a = 10;
    	int b = 20;
    	//如果函数模板和普通函数都可以调用。优先调用普通函数。普通函数只有声明会报错
    	myPrint(a, b);
    
    	//通过空模板的参数列表强制调用函数模板
    	myPrint<>(a,b);
    
    	//调用重载模板
    	myPrint(a, b, 100);
    
    	//如果函数模板产生更好的匹配,优先调用函数模板
    	char c1 = 'a';
    	char c2 = 'b';
    
    	myPrint(c1, c2);
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。

    模板的局限性

    局限性:

    模板的通用性并不是万能的。

    template<class T>
    void f(T a,T b)
    {
        a = b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了。

    template<class T>
    void f(T a,T b)
    {
        if(a>b)
        {......}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在上述代码中,如果T的数据类型传入的是像Person这样的自定义类型,也无法正常运行。

    因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

    #include
    #include
    using namespace std;
    
    class Person
    {
    public:
    	Person(string name,int age)
    	{
    		this->m_Name = name;
    		this->m_Age = age;
    	}
    
    	string m_Name;
    	int m_Age;
    };
    
    //模板并不是万能的,有些特定的数据类型,需要用具体化方式做特殊实现
    //对比两个数据是否相等
    template<class 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_Name == p2.m_Name && p1.m_Age == p2.m_Age)
    	{
    		return true;
    	}
    	else
    	{
    		return false;
    	}
    }
    void test01()
    {
    	int a = 10;
    	int b = 20;
    	bool ret = myCompare(a, b);
    	if (ret)
    	{
    		cout << "相等" << endl;
    	}
    	else
    	{
    		cout << "不相等" << endl;
    	}
    }
    
    void test02()
    {
    
    	Person p1("Tom", 10);
    	Person p2("Tom", 10);
    
    	bool ret = myCompare(p1, p2);
    	if (ret)
    	{
    		cout << "相等" << endl;
    	}
    	else
    	{
    		cout << "不相等" << endl;
    	}
    }
    int main(void)
    {
    	test01();
    	test02();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    总结:

    • 利用具体化的模板,可以解决自定义类型的通用化
    • 学习模板并不是为了写模板,而是在STL能够运用系统系统的模板
    类模板

    建立一个通用类, 类中成员数据可以不具体指定,用一个虚拟的类型来代表

    语法:
    template<typename T>#include
    #include
    using namespace std;
    //类模板
    template<class NameType,class AgeType>
    class Person
    {
    public:
    	Person(NameType name, AgeType age)
    	{
    		this->m_Age = age;
    		this->m_Name = name;
    	}
    	void showPerson()
    	{
    		cout << this->m_Name << this->m_Age << endl;
    	}
    	NameType m_Name;
    	AgeType m_Age;
    };
    void test01()
    {
    	// <>中是模板的参数列表
    	Person<string, int>p1("张三",10);
    	p1.showPerson();
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    总结:类模板和函数模板语法相似,在声明模板template后面加类,次类称为类模板。

    类模板与函数模板的区别
    类模板没有自动类型推导的使用方式
    类模板在模板参数列表中可以有默认参数
    
    eg:
    #include
    #include
    using namespace std;
    
    
    template<class NameType, class AgeType = int>//默认参数
    class Person
    {
    public:
    	Person(NameType name,AgeType age)
    	{
    		this->m_Name = name;
    		this->m_Age = age;
    	}
    	void ShowPerson()
    	{
    		cout << this->m_Name << this->m_Age << endl;
    	}
    	NameType m_Name;
    	AgeType m_Age;
    };
    
    //类模板没有自动类型推导的使用方式
    void test01()
    {
    	//Person p("李四", 13);
    	Person<string,int>p("李四", 13);//只能用显示指定类型
    	p.ShowPerson();
    }
    
    //类模板在模板参数列表中可以有默认参数
    void test02()
    {
    	Person<string>p2("张三", 13);
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    总结:

    • 类模板使用只能用显式指定类型方式
    • 类模板中的模板参数列表可以有默认参数
    类模板中的成员函数创建实际

    普通类中的成员函数一开始就可以创建
    类模板中的成员函数在调用时才创建

    #include
    #include
    using namespace std;
    
    //类模板中的成员函数在调用时才去创建
    class Person1 
    {
    public:
    	void showPerson1()
    	{
    		cout << "Person show1" << endl;
    	}
    };
    
    class Person2
    {
    public:
    	void showPerson2()
    	{
    		cout << "Person show2" << endl;
    	}
    };
    template<class T>
    class MyClass
    {
    public:
    	T obj;
    	//类模板中的成员函数
    	void func1()
    	{
    		obj.showPerson1();
    	}
    	void func2()
    	{
    		obj.showPerson2();
    	}
    };
    
    void test01()
    {
    	MyClass<Person2>m;
    	m.func1();
    	//m.func2();
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建。

    类模板对象做函数参数
    #include
    using namespace std;
    
    //类模板对象做函数参数
    template<class T1,class T2>
    class Person
    {
    public:
    	Person(T1 name,T2 age)
    	{
    		this->m_Name = name;
    		this->m_Age = age;
    	}
    
    	void showPerson()
    	{
    		cout << this->m_Name << this->m_Age << endl;
    	}
    	T1 m_Name;
    	T2 m_Age;
    };
    //1.指定传入类型
    void PrintPerson1(Person<string,int>&p)
    {
    	p.showPerson();
    }
    void test01()
    {
    	Person<string, int>p1("张三",11);
    	PrintPerson1(p1);
    }
    //2.参数模板化
    template<class T1,class T2>
    void PrintPerson2(Person<T1, T2>&p)
    {
    	p.showPerson();
    	cout << "T1的数据类型为" << typeid(T1).name() << endl;
    	cout << "T2的数据类型为" << typeid(T2).name() << endl;
    }
    
    void test02()
    {
    	Person<string, int>p2("李四", 12);
    	PrintPerson2(p2);
    }
    //3.整个类模板化
    template<class T>
    void PrintPerson3(T &p)
    {
    	p.showPerson();
    	cout << "T的数据类型为" << typeid(T).name() << endl;
    	
    }
    void test03()
    {
    	Person<string, int>p3("赵四", 14);
    	PrintPerson3(p3);
    }
    int main(void)
    {
    	test01();
    	test02();
    	test03();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    总结:

    • 通过类模板创建的对象,可以有三种方式向函数中进行传参
    • 使用比较广泛得是第一种,指定传入的类型
    类模板与继承
    当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
    如果不想指定,编译器无法给子类分配内存
    如果想灵活指定出父类中T的类型,子类也需要变为类模板
    
    include<iostream>
    using namespace std;
    
    //类模板与继承
    template<class T>
    class Base
    {
    	T m;
    };
    class Son :public Base<int>//必须要知道父类中T的数据类型才能继承给子类
    {
    
    };
    void test01()
    {
    	Son s1;
    }
    
    //如果想灵活指定父类中T类型,子类也需要变类模板
    template<class T1,class T2>
    class Son2 :public Base<T2>
    {
    public:
    	Son2()
    	{
    		cout << typeid(T1).name()<< endl;
    		cout << typeid(T2).name()<< endl;
    	}
    	T1 obj;
    };
    void test02()
    {
    	Son2<int, char>s2;
    }
    int main(void)
    {
    	test02();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    总结:如果父类是类模板,子类需要指定出父类中T的数据类型。

    类模板成员函数类外实现
    #include
    #include
    using namespace std;
    //类模板成员函数类外实现
    template<class T1,class T2 >
    class Person
    {
    public:
    	Person(T1 name, T2 age);
    
    	void showPerson();
    
    	T1 m_Name;
    	T2 m_Age;
    };
    //构造函数类外实现
    template<class T1,class T2>
    Person<T1,T2>::Person(T1 name, T2 age)
    {
    	this->m_Name = name;
    	this->m_Age = age;
    }
    
    //成员函数类外实现
    template<class T1,class T2>
    void Person<T1,T2>::showPerson()
    {
    	cout << this->m_Name << this->m_Age << endl;
    }
    void test01()
    {
    	Person<string, int>p1("新二", 13);
    	p1.showPerson();
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    总结:类模板中成员函数类外实现时,需要加上模板参数列表。

    类模板分文件编写

    掌握类模板成员函数分文件编写产生的问题以及解决方式

    问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

    • 解决方式1:直接包含.cpp源文件
    • 解决方式2: 将声明.h和实现.cpp在到同一个文件中,并更改后缀名为.hpp,hpp是约定俗成的名称,并不是强制
    #pragma once
    #include
    using namespace std;
    
    template<class T1, class T2>
    class Person
    {
    public:
    	Person(T1 name, T2 age);
    	void showPerson();
    	T1 m_Name;
    	T2 m_Age;
    };
    
    template<class T1, class T2>
    Person<T1, T2>::Person(T1 name, T2 age)
    {
    	this->m_Name = name;
    	this->m_Age = age;
    }
    template<class T1, class T2>
    void Person<T1, T2>::showPerson()
    {
    	cout << this->m_Name << this->m_Age << endl;
    }
    
    
    
    .cpp文件
    #include
    #include"Person.hpp"
    using namespace std;
    #include
    
    void test01()
    {
    	Person<string, int>p1("伞兵", 18);
    	p1.showPerson();
    }
    int main(void)
    {
    	test01();
    	system("pause"); 
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

    类模板与友元

    掌握类模板配合友元函数的类内和类外实现

    全局函数类内实现,直接在类内声明友元即可
    全局函数类外实现,需要提前让编译器知道全局函数的存在
    eg:
    #include
    #include
    using namespace std;
    //通过全局函数打印Person的信息
    
    //提前让编译器知道Person类的存在
    template<class T1, class T2 >
    class Person;
    //类外实现
    template<class T1, class T2>
    void PrintPerosn2(Person<T1, T2>p)
    {
    	cout << "类外实现" << p.m_Name << p.m_Age < endl;
    }
    
    
    template<class T1,class T2 >
    class Person
    {
    	//全局函数类内实现
    	friend void PrintPerosn(Person<T1,T2>p)
    	{
    		cout << p.m_Name << p.m_Age << endl;
    	}
    
    	//全局函数类外实现
    	//加空模板参数列表
    	//如果全局函数 是类外实现 需要让编译器提前知道这个函数的存在
    	friend void PrintPerosn2<>(Person<T1, T2>p);
    
    
    public:
    	Person(T1 name,T2 age)
    	{
    		this->m_Name = name;
    		this->m_Age = age;
    		
    	}
    private:
    	T1 m_Name;
    	T2 m_Age;
    };
    
    void test01()
    {
    	Person<string, int>p("zbc", 12);
    	PrintPerosn(p);
    }
    void test02()
    {
    	Person<string, int>p2("年轻人", 18);
    }
    int main(void)
    {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    建议全局函数做类内实现,用法简单,而且编译器可以直接识别。

  • 相关阅读:
    VSCODE 配置远程调试环境
    经典图割算法中图的构建及实现:Graph-Cut
    汽车OTA
    今天的码农女孩学习了关于ajax技术
    基于 QUIC 协议的 HTTP/3 正式发布!
    设计模式详解之结构型设计模式——适配器、装饰器
    Springboot快速搭建Web API项目
    Scala 高阶(七):集合内容汇总(上篇)
    LeetCode 6. Z 字形变换 (N字形变换)
    开始在 Windows 上将 Python 用于 Web 开发
  • 原文地址:https://blog.csdn.net/qq_52151772/article/details/128022538