美文网首页
C++学习笔记

C++学习笔记

作者: home2_d685 | 来源:发表于2022-11-24 14:20 被阅读0次

    初识C++

    C++标准格式

    #include <iostream>
    using namespace std;
    
    int main()
    {
        cout << "hello world!" << endl;
    
        system("pause");
        return 0;
    }
    

    变量

    作用:给一段指定的内存空间起名,方便操作这段内存。
    语法:数据类型 变量名 = 初始值;

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int a = 10;
        cout << "a的值为:" << a << endl;
    
        system("pause");
        return 0;
    }
    

    常量

    作用:用于记录程序中不可更改的数据
    C++定义常量的两种方式

    1. #define 宏常量 : #define 常量名 常量值
      • 通常在文件上方定义,表示一个常量
    2. const修饰的变量:const 数据类型 常量名 = 常量值
      • 通常在变量定义前面加关字const,修饰该变量为常量,不可修改
    #define day 7
    
    int main ()
    {
        cout << "一周共有多少天" << day << endl;
        
        const int month = 12;
        cout << "一年总共有多少个月份" << month << endl;
    }
    

    关键字

    作用:关键字是C++中预先保留的单词(标识符)</br>
    在定义变量或者常量的时候,不要用关键字

    if else while signed throw union this
    int char double unsigned const goto virtual
    for char brack auto class operator case
    do long typedef static friend template default
    new void register extern return enum inline
    try short continue sizeof switch pricate protected
    asm while catch delete public volatile struct

    标识符命名规则

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

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

    数据类型

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

    1.整型

    作用:整型变量标识的数是整型的数据</br>
    C++中能够标识整型的类型有一下几种方式,区别在于所占内存空间不同;

    数据类型 占用空间 取值范围
    short(短整型) 2字节 (-215~215-1)
    int(整型) 4字节 (-231~231-1)
    long(长整型) Windows为4字节,linux为4字节(32位),8字节(64位) (-231~231-1)
    long long(长长整型) 8字节 (-263~263-1)

    2.sizeof关键字

    作用:利用sizeof关键字可以统计数据类型所占内存大小
    语法:sizeof(数据类型 / 变量)

    3.实型(浮点型)

    作用:用于表示小数</br>
    浮点型变量分为两种:

    1. 单精度float
    2. 双精度double

    两者的区别在于标识的有效数字范围不同</br>
    默认情况下 输出一个小数会显示出6位有效数据</br>

    数据类型 占用空间 有效字符范围
    float 4字节 7位有效数字
    double 8字节 15~16位有效数字
    int main ()
    {
        float f1 = 2.14f;
        double d1 = 3.14;
        
        cout << "float sizeof = " << sizeof(f1) << endl;
        cout << "double sizeof = " << sizeof(d1) << endl;
        
        //科学计数法
        float f2 = 3e2 ; //3 * 10^2
        cout  << "f2 = " << f2 << endl;
        folat f3 = 3e-2; //3 * 0.1^2
        cout << "f3 = " << f3 << endl;
    }
    

    4.字符型

    作用:字符型变量用于显示单个字符</br>
    语法:char ch = 'a';</br>
    注意1:在显示支付变量时,用单引号将字符括起来,不要用双引号</br>
    注意2:单引号只能有一个字符,不可以是字符串</br>

    • c和c++中字符型变量只占用一个字节
    • 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放到存储单元
    int main()
    {
        char ch = 'a';
        cout << ch << endl;
        cout << sizeof(char) << endl;
        
        cout << (int)ch << endl;  //查看字符a对应的ACSII值
        ch = 97;                //可以直接用ACSII给字符型变量赋值
        cout << ch << endl;
        
    }
    

    5.转义字符

    作用:用于表示一些不能显示出来的ASCII字符</br>
    现阶段我们常用的转义字符有:\n \\ \t

    6.字符串型

    作用:用于表示一串字符

    1. C风格字符串:char 变量名[]="字符串值"
    int main()
    {
        char str1[] = "hello world";
        cout << str1 << endl;
    }
    
    1. C++风格字符串:string 变量名 = "字符串值"
    int main()
    {
        string str2 = "hello world";
        cout << str2 << endl;
    }
    

    7.布尔数据类型

    作用:布尔数据类型代表真或假的值</br>
    bool类型只有两个值

    • true --- 真(本质是1)
    • false --- 假(本质是0)
      bool类型占1个字节大小
    int main()
    {
        bool flag = true;
        cout << flag << endl; //1
        
        flag = false;
        cout << flag << endl;//0
        
        cout << "sizeof bool = " << sizeof(bool) << endl;
    }
    

    8.数据的输入

    作用:用于从键盘获取数据
    关键字:cin
    语法:cin >> 变量;

    运算符

    作用:用于执行代码的运算

    运输算符 作用
    算数运算符 用于处理四则运算
    赋值运算符 用于将表达式的值赋给变量
    比较运算符 用于表达式的比较,并返回一个真值或者假值
    逻辑运算符 用于根据表达式的值返回真值或假值

    1.算术运算符

    作用:用于处理四则运算
    算数运算符包括以下符号:

    运算符 术语 示例 结果
    + 正号 +3 3
    - 负号 -3 -3
    + 10+5 15
    - 10-5 5
    * 10*5 50
    / 10/5 2
    % 取模(求余) 10%3 1
    ++ 前置递增 a=2;b=++a; a=3;b=3;
    ++ 后置递增 a=2;b=a++; a=3;b=2;
    -- 前置递减 a=2;b=--a; a=1;b=1;
    -- 后置递减 a=2;b=a--; a=1;b=2;

    2.赋值运算符

    作用:用于将表达式的值赋给变量
    赋值运算符包括一下几个符号:

    运算符 术语 示例 结果
    = 赋值 a=2;a=3; a=2;b=3;
    += 加等于 a=0;a+=2 a=2;
    -= 减等于 a=5;a-=3 a=2;
    *= 乘等于 a=2;a*=2; a=4;
    /= 除等于 a=4;a/=2; a=2;
    %= 模等于 a=3;a%2; a=1;

    3.比较运算符

    作用:用于表达式的比较,并返回一个真值或假值
    比较运算符有以下符号:

    运算符 术语 示例 结果
    == 相等于 4 == 3 0
    != 不等于 4!=3 1
    < 小于 4<3 0
    > 大于 4>3 1
    <= 小于等于 4<=3 0
    >= 大于等于 4>=1 1

    4.逻辑运算符

    作用:用于根据表达式的值返回真值或假值
    逻辑运算符有以下符号:

    运算符 术语 示例 结果
    ! !a 如果a为假,则!a为真;如果a为真,则!a为假。(去反)
    && a && b 如果a和b都为真,则结果为真,否则为假。
    a || b 如果a和b有一个为真,则结果为真,二者都有假时,结果为假。

    程序流程结构

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

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

    1.选择结构

    1.1 if语句

    作用:执行满足条件的语句
    if语句的三种形式

    • 单行格式if语句
    • 多行格式if语句
    • 多条件的if语句

    嵌套if语句:在if语句中,可以嵌套使用if语句,达到更精确地条件判断

    案例需求:

    • 提升用户输入一个高考分数,根据分数做如下判断
    • 分数如果大于600分视为考上一本大学,大于500分考上二本,大于400考上三本,其余视为未考上本科;
    • 在一本分数中,如果大于700分,考入北大,大于650分,考入清华,大于600考入人大
    int main()
    {
        int source = 0;
        cout << "请输入您的考上分数" << endl;
        cin >> source;
        
        cout << "你输入的分数为" << source << endl;
        if (source > 600)
        {
            cout << "恭喜你考上了一本大学" << endl;
            if (source > 700)
            {
                cout << "恭喜你考上了北大" << endl;
            }
            else if (source > 650)
            {
                cout << "恭喜你考上了清华" << endl;
            }
            else {
                cout << "恭喜你考上了人大" << endl;
            }
        }
        else if (source > 500)
        {
            cout << "恭喜你考上了二本大学" << endl;
        }
        else if (source > 400)
        {
            cout << "恭喜你考上了三本大学" << endl;
        }
        else {
            cout << "你未考上本科" << endl;
        }
    }
    

    1.2三目运算符

    作用:通过三目运算符实现简单的判断
    语法:表达式1 ?表达式2 :表达式3
    解释:

    • 如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
    • 如果表达式1的值为假,执行表达式3,并返回表达式3的结构;

    示例:

    int main()
    {
        int a = 10;
        int b = 20;
        int c = 0;
        
        c = (a > b ? a : b);
        cout << "c = " << c << endl;
        
        //在c++中三目运算符返回的是变量,可以继续赋值
        (a > b ? a : b ) = 100;
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }
    

    1.3 switch语句

    作用:执行多条件分支语句
    语法:

    switch(表达式)
    {
        case 结果1 : 执行语句;
        break;
        case 结果2 : 执行语句;
        break;
        case 结果3 : 执行语句;
        break;
        
        ......
        
        default : 执行语句;
        break;
        
    }
    

    2.循环语句

    2.1 while循环语句

    作用:满足循环条件,执行循环语句
    语法:while(循环条件){ 循环语句 }
    解释:只要循环语句的结果为真,就执行循环语句

    需求:打印0-9之间的数

    int main()
    {
        int number = 0;
        
        while (number < 10)
        {
            cout << number << endl;
            number++;
        }
    }
    

    案例:0-100生成随机数,

    #include <iostream>
    #include <ctime>
    using namespace std;
    
    int main()
    {
        srand((unsigned int)time(NULL));    //添加随机种子,作用是利用系统时间生成随机数,防止每次随机数都一样;
        int num = rand() % 100 + 1;         //生成一个随机数
        
        int val = 0;
        while(1)
        {
            cin << val;
            if ( val > num)
            {
                cout >> "您输入的数过大了" >> endl;
            }
            else if (val < num)
            {
                cout >> "您输入的数过小了" >> endl;
            }
            else 
            {
                cout >> "恭喜您猜对了,你太厉害了"  >> endl;
                brank;
            }
        }
        return 0;
    }
    

    2.2 do...while循环语句

    作用:满足循环条件,执行循环语句
    语法:do { 循环语句 } while(循环条件);
    注意:与while的区别在于do...while会先执行一次循环语句,再判断循环条件

    示例:

    int main()
    {
        int num = 0;
        
        do
        {
            cout << num << endl;
            num++;
        }while(num < 10);
        
        system("pause");
        return 0;
    }
    

    案例:输出三位的水仙花数

    int main()
    {
        int num = 100;
        
        do
        {
            int a = 0
            int b = 0;
            int c = 0;
            
            a = num % 10;
            b = num / 10 % 10;
            c = num / 100;
            
            if (a*a*a + b*b*b + c*c*c = num)
            {
                cout << num <<endl;
            }
            num++;
        }while(num < 1000);
    }
    

    2.3 for循环语句

    作用:满足循环条件,执行循环语句
    语句:for ( 起始表达式;条件表达式;末尾表达式) { 循环语句; }

    int main()
    {
        for (int i = 0 ; i < 10 ;i++)
        {
            cout << i << endl;
        }
    
        system("pause");
        return 0;
    }
    

    案例:遇到个位或者十位有7的都输入敲桌子

    int main()
    {
        
        for (int num = 0; i < 100 ;i++)
        {
            if (num % 7 == 0 || num % 10 == 7 || num / 10 == 7)
            {
                cout << "敲桌子" << endl;
            }
            else
            {
                cout << i << endl;
            }
        }
    }
    

    2.4 嵌套循环

    作用:在循环体中再嵌套一层循环,解决一些实际问题
    例如:在屏幕中打印10*10的星号

    int main()
    {
        int i,j;
        for (i = 0;i<10 ;i++)
        {
            for (j = 0;j<10;j++)
            {
                cout << "* " << endl;
            }
        }
        return 0;
    }
    

    乘法口诀表

    int main()
    {
        int i , j ;
        for (i = 1 ;i < 10 ;i++)
        {
            for (j = 1 ; j< i+1;j++)
            {
                cout << i << "x" << j << "=" << i*j << " ";
            }
            cout << endl;
        }
        return 0;
    }
    

    3.跳转语句

    3.1 break语句

    作用:用于跳出选择结构或者循环结构
    break使用的时机:

    • 出现在switch条件语句中,作用是终止case并跳出switch
    • 出现在循环语句中,作用是跳出当前的循环语句
    • 出现在嵌套循环中,跳出最近的内层循环语句

    3.2 continue语句

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

    int main()
    {
        for (int i;i < 100;i++)
        {
            if(i % 2 == 0)
            {
                cotinue;
            }
            cout << i << endl;
        }
    }
    

    3.3 goto语句

    作用:可以无条件跳转语句

    语法: goto 标记;
    解释:如果标记的名称存在,执行到goto语句中,会跳转到标记的位置;

    int main()
    {
        cout << "1" << endl;
        
        goto FLAG;
        cout << "1" << endl;
        cout << "2" << endl;
        cout << "3" << endl;
        cout << "4" << endl;
        
        FLAG;
        
        cout << "5" << endl;
        
    }
    

    4.一维数组

    4.1一维数组定义方式

    一维数组定义的三种方式:

    • 数据类型 数组名[数组长度];
    • 数据类型 数组名[数组长度] = {值1,值2,值3};
    • 数据类型 数组名[] = {值1,值2 ...};
    int mian ()
    {
        //方法1
        int arr[5];
        arr[0] = 10;
        arr[1] = 20;
        arr[2] = 30;
        arr[3] = 40;
        arr[4] = 50;
        
        //方法2
        int arr2[5] = {10,20,30,40,50};
        
        //方法3
        int arr3[] = {10,20,30,40,50,60,70,80,90};
        
        cout << arr3[0] << endl;
        cout << arr3[1] << endl;
        cout << arr3[2] << endl;
        cout << arr3[3] << endl;
        cout << arr3[4] << endl;
        
        
        for (int i = 0;i <10 ;i++)
        {
            cout << arr3[i] << endl;
        }
    }
    

    总结1: 数组名的命名规则与变量名规则一致,不能和变量重名
    总结2: 数组中下标是从0开始索引

    4.2 一维数组数组名

    一维数组的用途:

    • 可以统计整个数组在内存中的长度
    • 可以获取数组在内存中的首地址
    int main ()
    {
       //数组名的用途
       //1.可以通过数组名统计整个数组占用内存大小
       int arr[10] = {1,2,3,4,5,6,7,8,9,10};
       cout << "整个数组占用内存空间为:" << sizeof(arr) <<endl;
       cont << "每个元素在数组中占用内存空间大小为:" << sizeof(arr[0]) << endl;
       cout << "数组中元素个数为:" << sizeof(arr) / sizeof(arr[0]) << endl; 
       
       //2.可以通过数组名查看数组首地址
       cout << "数组的首地址为:" << (int)arr  << endl;
       cout << "数组的首地址为:" << (int)&arr[0]  << endl;
       
       //数组名是常量,不能做赋值操作
    }
    
    案例:五只小猪称体重

    案例描述:
    在一个数组中记录了五只小猪的体重,如:int arr[5] = {300,350,200,400,250}; 找出并打印最重的小猪体重。

    int main()
    {
        int max;
        int arr[5] = {300,350,200,400,250};
        max = arr[0];
        
        for(int i = 0; i <5 ;i++)
        {
            if (arr[i] > max)
            {
                max = arr[i];
            }
            
        }
        cout << "五只小猪中最重的是:" << max << endl; 
    }
    
    案例2:数组元素逆置

    案例描述:请声明一个5个元素的数组,并且将元素逆置
    (如原数组元素为:1,3,2,5,4 逆置后输出结果为:4,5,2,3,1)

    int test()
    {
        int arr[] = { 1,3,2,5,4 };
        cout << "逆置前的数组为:"<< endl; 
        for (int i = 0; i <5 ;i++)
        {
            cout << arr[i] << endl;
        }
        
        //实现逆置
        //1.记录起始下标位置
        //2.记录结束下标位置
        //3.起始下标与结束下标的元素互换
        //4.起始位置加1,结束位置减1
        //5.循环执行1操作,直到起始位置>=结束位置
        int start = 0 ;                             //设置起始下标
        int end = sizeof(arr) / sizeof(arr[0]) - 1;  //设置结束下标
            
        while(start < end)
        {
            int temp;
            temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;
            
            start ++;
            end --;
        }
        cout << "逆置后的数组为:"<< endl; 
        for (int i = 0; i <5 ;i++)
        {
            cout << arr[i] << endl;
        }
    }
    
    int main ()
    {
        test();
        return 0;
    }
    
    冒泡排序

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

    1. 比较相邻的元素,如果第一个比第二个大,就交换他们
    2. 对每一对相邻元素做同样的工作,执行完成后,找到第一个最大值。
    3. 重复以上的步骤,每次比较次数-1,直到不需要比较
    4. 算法逻辑:两两交换,前面一个数比后面一个数大就交换,否则不交换,继续便利下两个数

    排序总轮数 = 元素个数 - 1;
    每轮对比次数 = 元素个数 - 排序轮数 - 1;

    int main()
    {
        int arr[9] = {2,4,0,5,7,1,3,8,9};
        cout << "排序前为:" << endl;
        for (int i = 0; i<9 i++)
        {
            cout << arr[i] << "  ";
        }
        cout << endl;
        
        for (int i = 0;i < 9-1 ;i++)
        {
            for (int j = 0;j<9-i-1;j++)
            {
                if(arr[j] > arr[j+1])
                {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        cout << "排序后为:" << endl;
        for (int i = 0; i<9 i++)
        {
            cout << arr[i] << "  ";
        }
        cout << endl;
    }
    

    5.二维数组

    二位数组就是在一维数组上,多加一个维度

    5.1 二维数组的定义方式

    二位数组定义的四种方式:

    1. 数据类型 数组名[行数][列数];
    2. 数据类型 数组名[行数][列数] = {{ 数据1,数据2},{数据3,数据4}};
    3. 数据类型 数组名[行数][列数] = {数据1,数据2,数据3,数据4};
    4. 数据类型 数组名[][列数] = {数据1,数据2,数据3,数据4};
    • 建议:以上4钟定义方式,利用第二种更加直观,提高代码的可读性

    5.2 二维数组数组名

    • 查看二维数组所占内存空间
    • 获取二维数组首地址

    示例

    int main()
    {
        int arr[2][3] = 
        {
            {1,2,3},
            {4,5,6}
        };
        
        cout << "二维数组大小" << sizeof(arr) << endl;
        cout << "二维数组一行大小" << sizeof(arr[0]) << endl;
        cout << "二维数组元素大小" << sizeof(arr[0][0]) << endl;
        
        cout << "二维数组的首地址:" << arr << endl;
    }
    

    5.3 二维数组应用案例

    考试成绩统计:
    案例描述:有三名同学(张三,李四,王五),在一次考试中的成绩分别如下表,请分别输出三名同学的总成绩

    语文 数学 英语
    张三 100 100 100
    李四 90 50 100
    王五 60 70 80
    int main()
    {
        int sources[3][3] = 
        {
            {100,100,100},
            {90,50,100},
            {60,70,80}
        };
        
        for(int i ;i <3;i++)
        {
            int sum;
            for(int j;j<3;j++)
            {
                sum += source[i][j];
                cout<< source[i][j] <"  ";
            }
            cout << "第"<<i+1<<"的总分为:" << sum << endl;
        }
        
    }
    

    6 函数

    6.1 概述

    作用:将一段经常使用的代码封装起来,减少重复代码。
    一个较大的程序,一般分为若干个程序模块,每个模块实现特定的功能。

    6.2 函数的定义

    函数的定义一般主要有5个步骤:

    1. 返回值类型
    2. 函数名
    3. 参数列表
    4. 函数体语句
    5. return表达式
      语法:
    返回值类型 函数名 (参数列表)
    {
        函数体语句
        
        return 表达式;
    }
    

    6.3 函数的调用

    功能:使用定义好的函数
    语法:函数名(参数)
    示例:

    int add(int num1,int num2)
    {
        int sum = sum1 + sum2;
        return sum;
    }
    int main()
    {
        int a = 10;
        int b = 20;
        
        int sum = add(a,b);
        cout << "sum = " << sum << endl;
    }
    

    总结:函数定义里小括号称为形参,函数调用时传入的参数称为实参。

    6.4 值传递

    • 所谓值传递,就是函数调用时实参将数值传入给形参
    • 值传递时,如果形参发生,并不会影响实参

    6.5 函数的常见样式

    常见的函数样式有4种

    1. 无参无返
    2. 有参无返
    3. 无参有返
    4. 有参有返

    6.6 函数的声明

    作用:告诉编译器函数名称及如何调用函数,函数的实际主体可以单独定义

    • 函数的声明可以多次,但是函数的定义只能由一次。

    6.7 函数的分文件编写

    作用:让代码结构更加清晰
    函数分文件编写一般有4个步骤

    1. 创建后缀名为.h的头文件
    2. 创建后缀为.cpp的源文件
    3. 在头文件中写函数的声明
    4. 在源文件中写函数的定义

    7 指针

    7.1 指针的基本概念

    指针的作用:可以通过指针间接访问内存

    • 内存编号是从0开始记录的,一般用十六进制数字表示
    • 可以利用指针变量保存地址

    7.2 指针变量的定义和使用

    指针变量定义语法: 数据类型 *变量名;
    示例:

    int main()
    {
        //1. 指针的定义
        int a = 10;
        //指针定义语法:数据类型 *变量名;
        int *p;
        //指针变量赋值
        p = &a; //指针指向变量a的地址;
        cout << &a <<endl;
        cout << p << endl;
        
        //2. 指针的使用
        //通过*操作指针变量指向的内存
        cout << "p = " << *p << endl;
    }
    

    7.3 指针所占内存空间

    在32位操作系统下,指针变量占用4个字节空间。64位下占8个字节。
    提问:指正也是数据类型,那么这种数据类型占用多少内存空间?
    示例:

    int main ()
    {
        int a = 10;
        
        int *p;
        p = &a ;
        
        cout << *p <<end;  //*解引用
        cout << sizeof(p) << endl;
        cout << sizeof(char *) << endl;
        cout << sizeof(float *) << endl;
        cout << sizeof(double *) << endl;
    }
    

    7.4 空指针和野指针

    空指针:指针变量执行内存中编号为0的空间。
    用途:初始化指针变量
    注意:空指针指向的内存是不可以访问的

    示例1:空指针

    int main()
    {
        //指针变量p指向内存地址编号为0的空间
        int *p = NULL;
        
        //访问空指针报错
        //内存编号0 ~ 255为系统占用内存,不允许用户访问
        cout << *p << endl;
    }
    

    野指针:指针变量指向非法的内存空间
    示例2:野指针

    int main ()
    {
        //指针变量p执行内存地址编号为0x1100的空间;
        int *p = (int *)0x1100;
        
        //访问野指针报错
        cout << *p << endl;
    }
    

    7.5 const修饰指针

    const修饰指针有三种情况

    1. const修饰指针 ---常量指针
    • const int *p = &a
    • 特点:指针的指向可以修改,但是指针指向的值不可以修改
    1. const修饰常量 ---指针常量
    • int * const p = &a
    • 特点:指针的执行不可以修改,但是指针指向的值可以修改
    1. const即修饰指针,有修饰常量
    • const int * const p = &a
    • 特点:指针的指向和指针的值都不可以修改

    7.6 指针和数组

    作用:利用指针访问数组中元素
    示例:

    int main ()
    {
        int arr[] = {1,2,3,4,5,6,7,8,9,10};
        
        int *p = arr;
        
        cout << "第一个元素:" << arr[0] << endl;
        cout << "指针访问第一个元素:" << *p << endl;
        
        for (int i = 0;i <10;i++)
        {
            cout << *p << endl;
            p++;
        }
    }
    
    

    7.7 指针和函数

    作用:利用指针作函数参数,可以修改实参的值
    示例:

    int swap01(int a ,int b )
    {
        int temp;
        temp = a;
        b = a;
        a = temp;
    }
    int swap02(int *a, int *b)
    {
        int temp;
        temp = *a;
        *a = *b;
        *b = temp;
    }
    int main()
    {
        int a =10;
        int b =20;
        
        swap01(a,b);
        
        swap01(&a,&b);
    }
    

    7.8 指针、数组、函数

    案例描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排序
    例如数组:int arr[10] = {4,3,6,9,1,2,10,8,7,5};

    void add (int *arr ,int len)
    {
        for(int i=0;i<len-1;i++)
        {
            for(int j=0;j<len-i-1;j++)
            {
                if(arr[j] > arr[j+1])
                {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] =temp;
                }
            }
        }
    }
    void pro(int *arr,int len)
    {
        for(int i=0;i<len;i++)
        {
            cout << arr[i] << "  ";
        }
        cout << endl;
    }
    int main()
    {
        int arr[10] = {4,3,6,9,1,2,10,8,7,5};
        int lan = sizeof(arr) / sizeof(arr[0]);
        add(arr,len);
        pro(arr,len);
    }
    

    8 结构体

    8.1 结构体基本概念

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

    8.2 结构体定义和使用

    语法: struct 结构体名 { 结构体成员列表 };
    通过结构体创建变量的方式有三种:

    • struct 结构体 变量名
    • struct 结构体 变量名 = { 成员1值 ,成员2值 ...}
    • 定义结构体时顺便创建变量
      示例:
    //结构体定义
    struct student
    {
        string name;
        int age;
        int score;
    }s3;
    
    int main()
    {
        //赋值方式
        //1.直接赋值
        struct student s1;
        s1.name = "张三";
        s1.age = 18;
        s1.score = 100;
        cout << "姓名: " << s1.name << "年龄: " << s1.age << "分数: " << s1.score << endl;
        
        //2. struct 结构体 变量名 = { 成员1值 ,成员2值 ...}
        struct student s2 = { "李四",19,80 };
        cout << "姓名: " << s2.name << "年龄: " << s2.age << "分数: " << s2.score << endl;
        
        //3.定义结构体时顺便创建变量
        s3.name = "王五";
        s3.age = 20;
        s3.score = 90;
        cout << "姓名: " << s3.name << "年龄: " << s3.age << "分数: " << s3.score << endl;
    }
    
    
    • 总结1:定义结构体时的关键字是struct,不可省略
    • 总结2:创建结构体变量时,关键字struct可以省略
    • 总结3:结构体变量利用操作符"."访问成员

    8.3 结构体数组

    作用:将自定义的结构体放入到数组中方便维护
    语法:struct 结构体名 数组名[元素个数] = { {},{}, ...{} };
    示例:

    struct Student
    {
      string name;
      int age;
      int score;
    };
    int main ()
    {
        struct Student Arr[3] = 
        {
            {"张三",18,90},
            {"李四",20,80},
            {"王五",25,90}
        };
        
        Arr[2].name = "赵六";
        Arr[2].age = 50;
        Arr[2].score = 60;
        
        for(int i;i<3;++)
        {
            cout << "姓名:" << Arr[i].name << "年龄:" << Arr[i].age << "成绩:" << Arr[i].score << endl;
        }
        
    }
    

    8.4 结构体指针

    作用:通过指针访问结构体中的成员

    • 利用操作符 -> 可以通过结构体指针访问结构体属性
      示例:
    struct Student 
    {
      string name;
      int age;
      int score;
    };
    int main()
    {
        Student s = {"张三",18,90};
        Student *p = &s;
        cout << "姓名:" << p->name << "年龄:" << p->age << "成绩:" << p->score << endl;
        
    }
    
    • 结构体可以使用箭头来访问属性。

    8.5 结构体嵌套结构体

    作用:结构体中的成员可以是另一个结构体
    例如:每个老师辅导一个学员,一个老师的结构体重,记录一个学生的结构体

    struct Student
    {
      string name;
      int age;
      int score;
    };
    struct teacher
    {
      int id;
      string name;
      int age;
      struct Student stu;
    };
    
    int main().
    {
        teacher t;
        t.id = 1000;
        t.name = "老王";
        t.age = 60;
        t.stu.name = "小王";
        t.stu.age = 18;
        t.stu.score = 100;
        //输出他们...
    }
    
    

    8.6 结构体做函数参数

    作用:将结构体作为参数向函数中传递
    传递方式有两种

    • 值传递
    • 地址传递

    示例:

    struct Student 
    {
        string name;
        int age;
        int score;
    };
    //值传递
    void printStudent1(struct Student s)
    {
        s.age = 100;
        cout << "值传递函数中打印  姓名:" << s.name << "年龄:" << s.age << "成绩:" << s.score << endl; 
    }
    //地址传递
    void printStudent2(struct Student *p)
    {
        p->age = 200;
        cout << "地址传递函数中打印  姓名:" << p->name << "年龄:" << p->age << "成绩:" << p->score << endl;
    }
    int main()
    {
        struct Student s;
        s.name = "张三";
        s.age = 19;
        s.score = 85;
        
        printStudent(s);
        printStudent(&s);
        cout << "main函数中打印  姓名:" << s.name << "年龄:" << s.age << "成绩:" << s.score << endl; 
    }
    
    • 总结:如果不想修改主函数重的数据,用值传递,反之用地址传递

    8.7 结构体重const使用场景

    作用:用const来防止误操作
    示例:

    struct student
    {
        string name;
        int age;
        int score;
    }
    void printStudent (const student *stu )
    {
        //stu->age = 100;    //操作失败,因为加了const修饰
        cout << "姓名:" << p->name << "年龄:" << p->age << "成绩:" << p->score << endl; 
    }
    int main()
    {
        student stu = {"张三",18,90};
        printStudent(&stu);
        
        return 0;
    }
    

    8.8 结构体案例

    8.81 案例一

    案例描述:
    学校找找做毕业设计项目,每个老师带领5个学生,总共又3名老师,需求如下
    设计学生和老师的结构体,其中在老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员
    学生的成员有姓名、考试分数,常见数组存放3名老师,通过函数给每个老师及所带的学生赋值
    最综打印出老师数据以及老师所带的学生数据。
    实例:

    #include <iostream>
    using namespace std;
    #include <string>
    #include <ctime>
    
    struct Student
    {
        string name;
        int score;
    };
    struct Teacher
    {
        string name;
        struct Student Arr[5];
    };
    
    void cindata(struct Teacher tArr[], int len)
    {
        string arr = "abcde";
        for(int i=0 ;i<len;i++)
        {
            tArr[i].name = "Teacher_";
            tArr[i].name += arr[i];
            for(int j =0;j<5;j++)
            {
                tArr[i].Arr[j].name = "Student_";
                tArr[i].Arr[j].name += arr[j];
    
                int ren = rand() % 61 + 40;
                tArr[i].Arr[j].score = ren;
            }
        }
    }
    
    void prodata(struct Teacher tArr[], int len)
    {
        for(int i =0;i<len;i++)
        {
            cout << "老师姓名:" << tArr[i].name << endl;
            for (int j = 0;j<5;j++)
            {
                cout << "\t学生姓名:" << tArr[i].Arr[j].name << "  " << 
                "学生成绩:" << tArr[i].Arr[j].score << endl;
            }
        }
    }
    
    int main()
    {
        //随机数种子,根据系统时间来取随机数
        srand((unsigned int)time(NULL));
    
        //定义结构体三个老师
        struct Teacher tArr[3];
        
        int len = sizeof(tArr) / sizeof(tArr[0]);
        cindata(tArr,len);
        prodata(tArr,len);
    
        return 0;
    }
    
    8.8.2 案例2

    案例描述:
    设计一个英雄的结构体,包括成员姓名,年龄,性别;
    创建结构体数组,数组中存放5名英雄。
    通过冒泡排序的算法,将数组中的英雄按照年龄进行升序排序,最终打印排序后的结果。

    #include <iostream>
    using namespace std;
    #include <string>
    
    struct Superman
    {
        string name;
        int age;
        string sex;
    };
    
    void BubbleSort(struct Superman arr[],int len)
    {
        for (int i = 0;i<len-1;i++)
        {
            for(int j = 0;j<len-i-1;j++)
            {
                if(arr[j].age > arr[j+1].age)
                {
                    struct Superman temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
    
    void printsort(struct Superman arr[],int len)
    {
        for(int i=0;i<len;i++)
        {
            cout << "姓名:" << arr[i].name << "  " << "年龄:" <<  arr[i].age << "  " << "性别:" << arr[i].sex <<endl;
        }
    }
    int main()
    {
        struct Superman arr[5] = 
        {
            {"刘备",23,"男"},
            {"关羽",22,"男"},
            {"张飞",20,"男"},
            {"赵云",21,"男"},
            {"貂蝉",19,"女"}
        };
        int len = sizeof(arr) / sizeof(arr[0]);
        cout << "排序前:"  << endl;
        printsort(arr,len);
        
        cout << "排序后:"  << endl;
        BubbleSort(arr,len);
        printsort(arr,len);
    
        return 0;
        
    }
    

    通讯录管理系统

    1.系统需求

    通讯录是一个可以记录亲人、好友信息的工具。
    本教程主要利用C++来实现一个通讯录管理系统
    系统中需要实现的功能如下:

    • 添加联系人: 向通讯录中添加新人,信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人
    • 显示联系人: 显示通讯录中所有联系人信息
    • 删除联系人: 按照姓名进行删除指定联系人
    • 查找联系人: 按照姓名查看指定联系人信息
    • 修改联系人: 按照姓名修改指定联系人
    • 清空联系人: 清空通讯录中所有信息
    • 退出通讯录: 推出当前使用的通讯录

    2.添加联系人

    功能描述:
    实现添加联系人功能,联系人上限为1000人,联系人信息包括(姓名、性别、年龄、联系电话、家庭住址)

    添加联系人实现步骤:

    • 设计联系人结构体
    • 设计通讯录结构体
    • main函数中创建通讯录
    • 封装添加联系人函数
    • 测试添加联系人功能

    C++核心编程

    1.内存分区模型

    C++程序在执行时,将内存大方向划分为4个区域

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

    内存四区的意义:

    • 不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

    1.1 程序运行前

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

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

    总结:

    • C++中在程序运行前分别为全局区和代码区
    • 代码区特点是共享和只读
    • 全局区中存放全局变量、静态变量、常量
    • 常量区中存放const修饰的全局常量和字符串常量

    1.2 程序运行后

    栈区:
    由编译器自动分配释放,存放函数的参数值,局部变量等
    注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

    堆区:
    由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
    在C++中主要利用new在堆区开辟内存

    1.3 new操作符

    C++中利用new操作符在堆区开辟数据
    堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
    语法:new 数据类型
    利用new创建的数据,会返回该数据对应的类型的指针

    示例1: 基本用法

    new一个变量
    
    int * func()
    {
        int *a = new int(10);
        return a;
    }
    int main()
    {
        int *p = func();
        cout << *p << endl;
        
        //释放堆区p
        delete p;
        
        return 0;
    }
    
    new一个指针
    
    int * func()
    {
        int * arr = new int[10];
        return arr;
    }
    
    int main()
    {
        int *arr =  func();
        for (int i =0;i<10;i++)
        {
            arr[i] = i+100;
        }
        for(int j=0;j<10;j++)
        {
            cout << arr[i] << endl;
        }
        delete[] arr;
    }
    

    2.引用

    2.1 引用的基本使用

    作用:给变量起名
    语法:数据类型 &别名 = 原名
    示例:

    int mian()
    {
        int a = 10;
        int &b = a;
        
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
        
        b = 100;
        
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
        system("pause");
    }
    

    2.2 引用的注意事项

    • 引用必须初始化
    • 引用在初始化后,不可以改变
      示例:
    int main()
    {
        int a = 10;
        int b = 20;
        
        //int &c;  //错误,引用必须初始化
        int &c = a ; //一旦初始化后,就不可以更改
        c = b; // 这是赋值操作,不是更改引用
        
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
        cout << "c = " << c << endl;
        
        system("pause");
        return 0;
    }
    
    

    2.2 引用做函数参数

    作用:函数传参时,可以利用引用的技术让形参修饰实参
    优点:可以简化指针修改实参

    示例:

    //1.值传递
    void mySwap01(int a,int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
    //2.地址传递
    void mySwap02(int *a ,int *b)
    {
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    
    //3.引用传递
    void mySwap01(int &a,int &b)
    {
        int temp = a;
        a = b;
        b = temp;
    }
    int main()
    {
        int a = 10;
        int b = 20;
        
        mySwap01(a,b);
        cout << "a = "<< a << endl;
        cout << "b = " << b << endl;
        mySwap02(&a,&b);
        cout << "a = "<< a << endl;
        cout << "b = " << b << endl;
        mySwap01(a,b);
        cout << "a = "<< a << endl;
        cout << "b = " << b << endl;
    }
    

    2.4 引用做函数返回值

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

    注意:不要返回局部变量引用
    用法:函数调用作为左值

    示例:

    //1.不要返回局部变量的引用
    int & test01()
    {   
        int a = 10;
        return a;
    }
    //2.函数的调用可以作为左值
    int& test02()
    {
        static int a = 10; // 静态变量,存放在全局区,全局区上的数据在程序结束后才会释放
        return a;
    }
    
    int main()
    {
        int &ref = test01(); 
        cout << " ref = "  << ref <<endl;   //第一次结果正确,是因为编译器做了保留
        cout << " ref = "  << ref <<endl;   //第二次结果错误,是因为a的内存已经释放
        
        int &ref2 = test02()
        cout << " ref2 = "  << ref2 <<endl;  
        cout << " ref2 = "  << ref2 <<endl;
    }
    
    

    2.6常量引用

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

    在函数形参列表中,可以加const修饰形参,防止形参改变实参

    示例:

    void showValue(const int &v)
    {
        v += 10;
        cout << v<< endl;
    }
    int main()
    {
        //int &ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
        //加入const就可以了,编译器优化代码,int temp =10;const int &ref = temp;
        const int& ref = 10;
        
        //ref = 100; //加入const后不可以修改变量
        cout << ref << endl;
        
        //函数中利用常量引用防止操作修改实参
        int a = 10;
        showValue(a);
        
        return 0;
    }
    

    3 函数的提高

    3.1 函数默认参数

    在C++中,函数的形参列表中的形参是可以有默认值得。
    语法:返回值类型 函数名 (参数 = 默认值) {}

    示例:

    int func(int a = 10,int b = 20,int c = 30)
    {
        return a +b+c;
    }
    int main()
    {
        cout << func() << endl;
        
        return 0;
    }
    

    3.2 函数占位参数

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

    语法:返回值类型 函数名(数据类型) {}

    示例:

    int func(int a ,int)
    {
        cout << "this is func" << endl;
    }
    int main()
    {
        func(10,10);
        
        return 0;
    }
    

    3.3 函数重载

    3.31 函数重载概念

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

    函数重载满足条件:

    • 同一个作用域下
    • 函数名相同
    • 函数参数类型不用 或者 个数不用 或者 顺序不用

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

    示例:

    void func()
    {
        cout << "this is func" <<endl;
    }
    void func(int a)
    {
        cout << "this is (int)func" << endl;
    }
    void func(double a)
    {
        cout << "this is (doule)func" << endl;
    }
    int main()
    {
        func();
        func(10);
        func(3.14);
        
        return 0;
    }
    
    3.3.2 函数重载注意事项
    • 引用作为重载条件
    • 函数重载碰到函数默认参数

    示例:

    //函数重载注意事项
    //1.引用作为函数重载的条件
    void func(int &a)
    {
        cout << "func(int &a)调用" << endl;
    }
    void func(const int &a)
    {
        cout << "func(const int &a)调用" << endl;
    }
    
    //2.函数重载碰到默认参数
    void func02(int a ,int b = 10)
    {
        cout << "func(int a ,int b = 10)调用" << endl;
    }
    void func02(int a)
    {
        cout << "func02(int a)调用" << endl;
    }
    int mian ()
    {
        int a = 10;
        func(a);
        
        func(10);
        
        func02(10);  // 存在二义性不用执行
        func02(10,20);
        return 0;
    }
    

    4 类和对象

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

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

    例如:
    人可以作为对象,属性有名字、年龄、身高、体重.... ,行为有走、跑、跳、吃饭、唱歌....
    车也可以作为对象,属性有轮胎、方向盘、车灯....,行为有载人、放音乐、放空调....
    具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类

    4.1 封装

    4.1.1 封装的意义

    封装是C++面向对象三大特性之一
    封装的含义:

    • 将属性和行为作为一个整体,表现生活中的事物
    • 将属性和行为加以权限控制
      封装的意义一:
      在设计类的时候,属性和行为写在一起,表现事物
      语法:class 类名{ 访问权限: 属性 / 行为 };

    示例1:设计一个圆类,求圆的周长

    const double PI = 3.14;
    class Circle
    {
        public:
            double perimeter();
            int width;
    };
    void Circle:: double perimeter
    {
        cout << (2*PI*width) << endl;;
    }
    int main ()
    {
        Circle c1;
        c1.width = 5;
        cout <<  c1.perimeter() << endl;
        
        return 0;
    }
    
    
    

    示例2:设计一个学生类,属性有名字和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

    class Student
    {
        public:
            Student(string a,int b):name(a),id(b){};
            void showStudent();
        private:
            string name;
            int id;
    };
    void Student :: showStudent()
    {
        cout << "姓名:" << name << "学号:" << id << endl;
    }
    
    int main()
    {
        Student s1;
        s1("黄龙",5210789);
        s1.showStudent();
        
        return 0;
    }
    

    封装的含义二:
    类在设计时,可以把属性和行为放在不用的权限下,加以控制
    访问权限有三种:

    1. public 公共权限
    2. protected 保护权限
    3. private 私有权限

    示例:

    class Person 
    {
        public:
            string m_name;
        protected:
            string m_Car;
        private:
            int m_Password;
        
        public:
            void func()
            {
                m_name = "张三";
                m_Car = "拖拉机";
                m_Password = 123456;
            }
    }
    int main()
    {
        Person p;
        p.m_name = "李四";
        //p.m_Car = "汽车";   //错误,保护权限类外无法访问
        //p.m_Password = 123; //错误,私有权限类外无法访问
        
        func();
        return 0;
    } 
    
    
    
    4.1.2 struct 和 class区别

    在C++中 struct和class唯一的区别就在于默认的访问权限不用
    区别:

    • struct 默认权限为公共
    • class 默认权限为私有
    class C1
    {
        int m_a;
    }
    struct C2
    {
        int m_a;
    }
    int main()
    {
        C1 c1;
        //c1.m_a = 2;   //错误   私有变量在外部无法访问
        
        C2 c2;
        c2.m_a = 2;
        
        return 0;
    }
    
    4.1.3 成员属性设置为私有

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

    封装联系案例

    练习案例1:设计立方体类
    设计立方体类(Cube)
    求出立方体的面积和体积

    代码:

    class Cube
    {
        public:
            Cube(int a = 0,int b = 0,int c = 0):m_L(a),m_H(b),m_W(c){};
            void Area();
            void Volume();
        private:
            int m_L;
            int m_H;
            int m_W;
    }
    
    void Cube :: Area()
    {
        int area = ((m_L * m_H + m_H * m_W + m_L *m_W)*2);
        cout << area<< endl;
    }
    void Cube :: Volume()
    {
        int volume = m_L*m_H*m_W;
        cout << volume << endl;
    }
    
    
    int main()
    {
        Cube c1(10,10,10);
        c1.Area();
        c1.Volume();
        
        return 0;
    }
    

    练习案例2:点和圆的关系
    设计一个圆形类(Circle) ,和一个点类(Point),计算点和圆的关系

    代码:

    
    

    4.2 对象的初始化和清理

    • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
    • C++中的面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前的清理数据的设置
    4.2.1 构造函数和析构函数

    对象的初始化和清理也是两个非常重要的安全问题
    一个对象或者变量没有初始化状态,队其使用后果是未知
    同样的使用完一个对象或者变量,没有及时清理,也会造成安全问题

    C++利用了构造函数和析构函数解决上述的问题,这两个函数将对对编译器自动调用,完成对象初始化和清理工作。
    对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。

    • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
    • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

    构造函数语法:类名(){}

    1. 构造函数,没有返回值也不写void
    2. 函数名称于类名相同
    3. 构造函数可以有参数,因此可以发生重载
    4. 程序在调用对象时候自动用构造,无需手动调用,而且只会调用一次

    析构函数:~类名(){}

    1. 没有返回值也不写void
    2. 函数名称于类名相同,在名称前加上符号~
    3. 析构函数不可以有参数,因此不可以发生重载
    4. 程序在对象销毁钱会制动调用析构,无须手动执行,而且只会调用一次
    class Person
    {
    public:
        Person(){
            cout << "Person 构造函数的调用" << endl;
        }
        ~Person()
        {
            cout << "Person 析构函数的调用" << endl;
        }
        
    };
    
    void test01()
    {
        Person p;
    }
    int main()
    {
        test01();
    
        return 0;
    }
    
    4.2.2 构造函数的分类及调用

    两种分类方式:
    按参数分为:有参数构造和无参构造
    按类型分为:普通析构和拷贝析构
    三种调用方法:
    括号法
    显示法
    隐式转换法

    示例:

    class Person()
    {
    public:
        //无参构造函数
        Person(){
            cout << "Person 无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a){
            cout << "Person 有参构造函数的调用" << endl;
            age = a;
        }
        //拷贝构造函数
        Person( const Person &p ){
            //将传入得人身上的所有属性,拷贝到我身上
            cout << "Person 拷贝构造函数的调用" << endl;
            age = p.age;
        }
        ~Person()
        {
            cout << "Person 析构函数的调用" << endl;
        }
        
        int age = 0;
    };
    
    
    //调用
    void test01()
    {
        //1.括号法
        Person p1;      //默认构造函数的调用
        Person p2(10);  //有参构造函数
        Person p3(p2);  //拷贝构造函数
        
        
        
        //2.显示法
        Person p1;          //无参构造
        Person p2 = Person(10);     //有参构造
        Person p3 = Person(p2);     //拷贝构造
        Person (10);   //匿名对象  特点:当前行执行结束,系统会立即回收掉匿名对象
        
        //3.隐式转换法
        Person p4 = 10;  //相当于  写了 Person p4 = Person(10);
        
    }
    
    int main()
    {
        test01(); 
    
        return 0;
    }
    
    4.2.3 拷贝构造函数调用时机

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

    • 使用一个已经创建完毕的对象来初始化一个新对象
    • 值传递的方式给函数参数传值
    • 已值方式返回局部对象

    示例:

    class Person
    {
        Person()
        {
            cout<< "This is 无参构造函数!" << endl;
        }
        Person(int a)
        {
            Age =a;
            cout << "This is 有参构造函数!" << endl;
        }
        Person( const Person &p )
        {
            Age  = p.Age;
            cout << "This is 拷贝构造函数!" << endl;
        }
        ~Person()
        {
            cout << "This is 析构函数!" << endl;
        }
        int Age;
    };
    //使用一个已经创建完毕的对象来初始化一个新对象
    void test01()
    {
        Person p1(10);
        Person p2(p1);
        
        cout << "p2的年龄" << p2.Age << endl;
    }
    //值传递的方式给函数参数传值
    void doword(Person p)
    {
        
    }
    void test02()
    {
        Person p;
        doword(p);
    }
    
    //已值方式返回局部对象
    Person doword2()
    {
        Person p1;
        return p1;
    }
    void test03()
    {
        Person p = doword2();
    }
    
    int main()
    {
        test01();
        test02();
        tset03();
        
        return 0;
    }
    
    4.2.4 构造函数调用规则

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

    1. 默认构造函数(无参,函数体为空)
    2. 默认析构函数(无参,函数体为空)
    3. 默认拷贝函数,对属性进行值拷贝

    构造函数调用规则然然下:

    • 如果用户定义有参构造函数,C++不在提供默认无参构造函数,但是会提供默认拷贝函数
    • 如果用户定义拷贝构造函数,C++不会在提供其他构造函数
    4.2.5 深拷贝和浅拷贝

    深浅拷贝是面试经典问题,也是常见的一个坑

    浅拷贝:简单的赋值拷贝操作
    深拷贝:在堆区重新申请空间,进行拷贝操作

    • 浅拷贝的问题 堆区的内存重复释放,解决方法:在拷贝函数时候定义一个新的堆区来存放拷贝过来的数据

    示例:

    class Person
    {
       Person()
        {
            cout<< "This is 无参构造函数!" << endl;
        }
        Person(int a)
        {
            Age =a;
            Height = new int()
            cout << "This is 有参构造函数!" << endl;
        }
        Person( const Person &p )
        {
            Age  = p.Age;
            cout << "This is 拷贝构造函数!" << endl;
        }
        ~Person()
        {
            if(Height != NULL)
            {
                delete Height;
                m_Height = NULL;
            }
            cout << "This is 析构函数!" << endl;
        }
        int Age; 
        int *Height;
    };
    
    4.2.6 初始化列表

    作用:C++提供了初始化列表语法,用来初始化属性
    语法:构造函数():属性1(值1),属性2(值2) ... {}

    4.2.7 类对象作为类成员

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

    例如:

    class A {}
    class B
    {
        A a;
    }
    

    B类中有对象A作为成员,A为对象成员

    那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
    当其他类对象作为本类成员,构造时候先构造类对象,再构造自身
    析构的顺序和构造的顺序是相反的

    示例:

    class Phone 
    {
    punlic:
        Phone(string name):m_PhoneName(name){}
        ~Phone(){cout << "Phone析构" <<endl; }
        string m_PhoneName;
    };
    class Person
    {
    public:
        Person(string name,string pName):m_name(name),m_Phone(pName){}
        ~Person(){ cout << "Person析构" << endl; }
        void playGame()
        {
            cout << "m_name" << "使用" << m_Phone.m_PhoneName << "牌手机" <<endl;
        }
    }
    void test01()
    {
        Person p("张三","水果11")
        p.playGame();
    }
    int main()
    {
        test01();
        
        return 0;
    }
    
    4.2.8 静态成员

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

    • 静态成员变量
      • 所有对象共享同一份数据
      • 在编译阶段分配内存
      • 类内声明,类外初始化
    • 静态成员函数
      • 所有成员共享同一个函数
      • 静态成员函数只能访问静态成员变量

    示例:

    class Person
    {
    public:
        Person(){}
        static void func()
        { 
            m_A = 100;
            //m_b = 100;  //错误  无法调用非静态成员变量,无法区分到底是哪个对象的m_b属性
            cout << "This is 静态成员函数" << endl; 
        }
        static int m_A; //静态成员变量
        int m_b;
    };
    
    int Person::m_A = 0;
    
    viod test01()
    {
        //1.通过对象访问
        Person P;
        P.func();
        
        //2.通过类名访问
        Person::func();
    }
    int main()
    {
        test01();
        return 0;
    }
    

    4.3 C++对象模型和this指针

    4.3.1 成员变量和成员函数分开存储

    在C++中,类内的成员变量和成员函数分开存储

    只有非静态成员变量才属于类的对象上

    class Person
    {
    public:
        Person(){ mA = 0; }
        
        //非静态成员变量占对象空间
        int mA;
        //静态成员变量不占对象空间
        static int mB;
        //函数也不占对象空间,所有函数共享一个函数实例
        void func()
        {
            cout << "mA:" << this->mA << endl;
        }
    };
    
    int Person::mB = 100;
    
    void test01()
    {
        //空函数占用内存空间为1个字节,
        //因为他会给每个空对象也分配一个字节空间,是为了区分空对象在内存中的位置
        //每个空对象也应该有一个独一无二的内存地址
        Person p;
        cout << "size of p = " << sizeof(p) << endl; 
    }
    void test02()
    {
        //占用4个空间的内存大小
        Person p;
        cout << "size of p = " << sizeof(p) << endl; 
    }
    
    int main()
    {
        test01();
        return 0;
    }
    
    4.3.2 this指针概念

    通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的
    每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
    那么问题是:这一块代码是如何区分那个对象调用自己的呢?

    C++通过提供特殊的对象指针,this指针,解决上述问题。this指针执行被调用的成员 函数所属的对象

    this指针是隐含每一个非静态成员函数内的一种指针
    this指针不需要定义,直接使用即可

    this指针的用途:

    • 当形参和成员变量同名时,可用this指针来区分
    • 在类的非静态成员函数中返回对象本身,可使用return *this
    class Person
    {
     public:
        Person(int age)
        {
            //1.当形参和成员变量同名时,可以this指针来区分
            this -> age = age;
        }
        Person& PersonAddPerson(Person p)
        {
            this ->age += p.age;
            //返回对象本身
            return *this;
        }
        int age;
    };
    void test01()
    {
        Person p1(10);
        cout << "p1.age = " << p1.age << endl;
        Person p2(10);
        p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
        cout << "p2.age = " << p2.age << endl;
    }
    
    int main()
    {
        test01();
        
        return 0;
    }
    
    4.3.3 空指针访问成员函数

    C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

    如果用到this指针,需要加以判断保证代码的健壮性

    示例:

    //空指针调用成员函数
    class Person
    {
    public:
        void showClassName()
        {
            cout << "this is Person class" << endl;
        }
        void showPersonAge()
        {
            //防止代码被传入空指针而崩溃掉
            if(this == NULL)
            {
                return;
            }
            cout << "age = " << age << endl;
        }
        int m_age;
    };
    void test01()
    {
        Person *p = NULL;
        p->showClassName();
        p->showPersonAge();
    }
    int main ()
    {
        test01();
        return 0;
    }
    
    4.3.4 const修饰成员函数

    常函数:

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

    常对象:

    • 声明对象前加const称该对象为常对象
    • 常对象只能调用常函数

    示例:

    class Person
    {
    public:
        // this 指针的本质 是指针常量 指针的指向是不可以修改的
        // const Person * const this;
        // 在成员函数后面加const, 修饰的是this指向, 让指针指向的值也不可以修改
        void showPerson() const 
        {
            //m_A = 100; //错误无法修改因为加了const
            this->m_B = 100;
            
        }
        int m_A;
        mutable int m_B;    //特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable
    };
    
    //常对象
    void test02()
    {
        const Person P;  // 在对象前加const,变为常对象
        //p.m_A = 100;  //错误  无法修改常对象中无法修改普通变量
        p.m_B = 100;    // m_B是特殊值,在常对象下也可以修改
        //常对象只能调用常函数,不可以调用普通函数
    }
    

    4.4 有元

    生活中你的家有餐厅(Public),有你的卧室(Private)
    客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
    但是呢,你也可以允许你的好闺蜜好基友进去。

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

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

    友元的关键字为friend

    有元的三种实现

    • 全局函数做友元
    • 类做友元
    • 成员函数做友元
    全局函做友元
    class Building
    {
        friend void goodGay(Building * building)
    public:
        Building()
        {
            this -> m_SittingRoom = "客厅";
            this -> m_RedRoom = "卧室";
        }
    public:
        string m_SittingRoom ;
    private:
        string m_RedRoom;
    };
    
    void goodGay(Building * building)
    {
        cout << "好基友正在访问: " << building ->m_SittingRoom <<endl;
        cout << "好基友正在访问: " << building ->m_RedRoom <<endl;
    }
    
    void test01()
    {
        Building b;
        goodGay(&b);
    }
    
    int main()
    {
        test01();
        return 0;
    }
    
    //类做友元
    class Building;
    class goodGay
    {
    public:
        goodGay();
        void visit();
    private:
        Building *building;
    };
    
    class Building 
    {
        //告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
        friend class goodGay;
    public:
        Building();
    public:
        string m_SittingRoom;
    private:
        string m_BedRoom;
    };
    
    Building::Building()
    {
        this->m_SittingRoom = "客厅";
        this->m_BedRoom = "卧室";
    }
    
    
    goodGay::goodGay()
    {
        building = new Building;
    }
    
    void goodGay:: visit()
    {
        cout << "好基友正在访问: " << building ->m_SittingRoom <<endl;
        cout << "好基友正在访问: " << building ->m_RedRoom <<endl;
    }
    
    //成员函数做友元
    class Building;
    class goodGay
    {
    public:
        goodGay();
        void visit1();
        void visit2()
    private:
        Building *building;
    };
    
    class Building 
    {
        //添加成员函数做友元
        friend void goodGay::visit1();
    public:
        Building();
    public:
        string m_SittingRoom;
    private:
        string m_BedRoom;
    };
    
    Building::Building()
    {
        this->m_SittingRoom = "客厅";
        this->m_BedRoom = "卧室";
    }
    
    
    goodGay::goodGay()
    {
        building = new Building;
    }
    
    void goodGay:: visit1()
    {
        cout << "好基友正在访问: " << building ->m_SittingRoom <<endl;
        cout << "好基友正在访问: " << building ->m_RedRoom <<endl;
    }
    
    void goodGay:: visit2()
    {
        cout << "好基友2正在访问: " << building ->m_SittingRoom <<endl;
        //cout << "好基友2正在访问: " << building ->m_RedRoom <<endl;   //无法访问
    }
    void test01()
    {
        goodGay gg;
        gg.visit1();
        gg.visit2();
    }
    

    4.5 运算符重载

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

    4.5.1 加号运算符重载

    作用: 实现两个自定义数据类型相加的运算

    //加号运算符重载
    class Person
    {
    public:
        //1.成员函数重载+号运算符
        Pereson 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;
    }
    
    void test01()
    {
        Person p1;
        p1.m_A = 10;
        p1.m_A = 10;
        Person p2;
        p2.m_A = 10;
        p2.m_A = 10;
        
        Perspm p3 = p1+p2;
        cout << "p3.m_A = " << p3.m_A << "p3.m_B = " << p3.m_b << endl;
    }
    
    • 总结1:对于内置的数据类型的表达式的运算符是不可以改变的
    • 总结2:不要滥用运算符重载
    4.5.2 左移运算符重载

    作用:可以输出自定义的数据类型

    class Person
    {
    public:
    
        friend ostream & operator<<(ostream &output ,Person &p);
        
        int m_A;
        int m_B;
    };
    
    ostream & operator<<(ostream &output ,Person &p)
    {
        output << "m_A = " << m_A << "  "<< "m_B = " << m_B;
        return output;
    }
    
    void test01()
    {
        Person p;
        p.m_A = 10;
        p.m_B = 10;
        
        cout << p << endl;
    }
    
    int main()
    {
        test01();
        
        return 0;
    }
    
    • 总结:重载左移运算符配合友元可以实现输出自定义数据类型
    4.5.3 递增运算符重载

    作用:通过重载递增运算符,实现自己的整型数据

    class print
    {
    public:
        print(int a = 0):m_Num(a){}
        friend ostream & oparetor<<(ostream &output,print &p);
        
        //重载前置++运算符  返回引用是为了对一个数据继续进行递增操作
        print& operator++()
        {
            ++m_Num;
            return *this;
        }
        //重载后置++运算符
        print& operator++(int)
        {
            print temp = *this 
            m_Num++;
            return temp;
        }
    private:
        int m_Num;
    };
    ostream & oparetor<<(ostream &output,print &p)
    {
        output << m_Num;
        return output;
    }
    
    void tset01()
    {
        print P;
        
        cout << ++P << endl;
        cout << P << endl;
        cout << P++ << endl;
    }
    
    4.5.4 赋值运算符重载

    C++编译器至少给一个类添加4个函数

    1. 默认构造函数(无参,函数体为空)
    2. 默认析构函数(无参,函数体为空)
    3. 默认拷贝构造函数,对属性进行值拷贝
    4. 赋值运算符operator=,对属性进行值拷贝

    如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

    class Person
    {
    public:
        Person(int age){ m_Age = new int(age); }
        
        //重载赋值运算符
        Person & operator=(Person &p)
        {
            if(m_Age != NULL)
            {
                delete m_Age;
                m_Age = NULL;
            }
            //编译器提供的是浅拷贝,深拷贝解决内存重复释放的问题
            m_Age = new int(*p.m_age);
            return *this;
        }
        ~Person()
        {
            if(m_Age != Null)
            {
                delete m_Age;
                m_Age = NUll;
            }
        }
    private:
        int *m_age;
    };
    
    void test01()
    {
        Person p1(18);
        Person p2(20);
        Person p3(30);
        
        p3 =p2 =p1;
        cout << "p1的年龄为: "<< *p1.m_Age << endl;
        cout << "p2的年龄为: "<< *p2.m_Age << endl;
        cout << "p3的年龄为: "<< *p3.m_Age << endl;
        
    }
    
    int main()
    {
        test01();
        return 0;
    }
    
    4.5.5 关系运算符重载

    作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

    class Person
    {
    public:
        Person(string name,int age)
        {
            this -> m_name = name;
            this -> m_age = age;
        }
        
        bool operator==(Person &p)
        {
            if(this -> m_name == p.name && this -> m_age == p.age)
            {
                return false;
            }
            else return trun;
        }
        
    private:
        string m_name;
        int m_age;
    };
    
    void test01()
    {
        Person p1("Tom",18);
        Person p2("Tom",18);
        
        if(p1 == p2)
        {
            cout << "p1和p2相等" << endl;
        }
        else cotu << "p1和p2不相等" << endl;
    }
    int main()
    {
        test01();
        return 0;
    }
    
    4.5.6 函数调用运算符重载
    • 函数调用运算符()也可以重载
    • 由于重载后使用的方式非常像函数的调用,因此称为伪函数
    • 伪函数没有固定写法,非常灵活
    
    

    4.6 继承

    相关文章

      网友评论

          本文标题:C++学习笔记

          本文链接:https://www.haomeiwen.com/subject/tkhxfdtx.html