0%

C++类和对象笔记

面向对象的三大特性:封装,继承,多态

4.1 封装

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

    访问权限

  1. public 内外都可以访问
  2. protected 类内可以访问,类外不行
  3. private 类内可以访问,类外不行
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

#include<iostream>
using namespace std;
//访问权限
//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 = "Eric";
        m_Car = "Benz";
        m_Password = 123456;
    }
};
int main() {
    
    Person p1;
    p1.m_Name = "EricD";
    //m_Car = "Bmw";//不可访问
    
    p1.func();
    system("pause");
    return 0;
}


4.1.2 struct和class曲别

默认的访问权限不同

  • struct默认权限为公有
  • class默认权限为私有

4.1.3 成员属性设置为私有

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

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

#include<iostream>
using namespace std;
//成员属性设置为私有
class Person
{
public:
    void setName(string name)//赋予写权限
    {
        m_Name = name;
    }
    void setLover(string name)
    {
        m_Lover = name;
    }
    string getName()
    {
        return m_Name;
    }
    int getAge()
    {
        return m_Age;
    }
    void setAge(int age)
    {
        if(age < 0 ||age > 150)//检测数据的有效性
        {
            m_Age = 0;
            cout << "赋值有误" << endl;
            return;
        }
        m_Age = age;
    }
    //string getLover()
    //{
    //    return m_Lover;
    //}//没有这个函数,Lover就变成只写变量了
private:
//对读写权限的控制
    string m_Name;
    int m_Age;
    string m_Lover;
};
int main() {
    
    Person p;
    p.setName("Eric");
    cout << "姓名:" << p.getName() << endl;
    p.setAge(1000);
    cout << "年龄:" << p.getAge() << endl;
    p.setLover("Ding");
    //cout << "Lover:" << p.getLover() << endl;
    system("pause");
    return 0;
}


4.2 对象的初始化和清理

4.2.1 构造函数和析构函数

构造函数: 创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
类名(){}
没有返回值,与类名相同,可以有参数,无需手动调用,系统会调用一次
析构函数: 对象销毁前系统自动调用,执行一些清理工作。
~类名(){}
与类名相同,在名称前加~。不能有参数,无返回值,无需手动调用,系统会调用一次

4.2.2 构造函数的分类及调用

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

4.2.3 拷贝构造函数的调用时机

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

    4.2.4构造函数调用规则

    默认情况下,C++至少给一个类添加三个函数:
  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数
  • 如果用户定义有参构造函数,C++不再提供默认无参构造
  • 如果用户定义拷贝构造函数,C++不再提供其他构造函数

    4.2.5 深拷贝与浅拷贝

    浅拷贝:简单的赋值拷贝操作
    深拷贝:在堆区重新申请空间进行拷贝操作有指针的时候要注意
    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

    #include<iostream>
    using namespace std;
    //构造函数的调用规则
    class Person
    {
    public:
        Person()
        {
            cout << "Person的默认构造函数调用" << endl;
        }
        Person(int a,int height)
        {
            m_Age = a;
            m_Height = new int(height);
            cout << "Person的有参构造函数调用" << endl;
        }
        //自己实现一个拷贝构造函数来解决浅拷贝带来的问题
        Person(const Person &p)
        {
            cout << "Person的拷贝构造函数调用" << endl;
            m_Age= p.m_Age;
            //m_Height = p.m_Height;编译器默认实现就是这行代码
            //深拷贝操作
            m_Height = new int(*p.m_Height);
        }
        ~Person()
        {
            //通常将堆区开辟的数据做释放操作
            //浅拷贝带来的问题就是,堆区内存的重复释放
            if(m_Height != NULL)
            {
                 delete m_Height;
                m_Height = NULL;
            }
            cout << "Person的析构函数调用" << endl;
        }
        int m_Age;
        int *m_Height;
    };
    void test01()
    {
        Person p1(18,160);
        cout << "p1的年龄为 " << p1.m_Age <<  " 身高为: " << *p1.m_Height << endl;
        Person p2(p1);
         cout << "p2的年龄为" << p2.m_Age <<  " 身高为: " << *p2.m_Height << endl;
    }
    int main(){
        test01();
        
        
        system("pause");
        return 0;
    }


    4.2.6 初始化列表

    语法: 构造函数():属性1(值1),属性2(值2)…
    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

    #include<iostream>
    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 test01()
    {
        Person p(1,2,3);
        cout << "m_A = " << p.m_A << endl;
        cout << "m_B = " << p.m_B << endl;
        cout << "m_C = " << p.m_C << endl;
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    4.2.7 类对象作为类成员

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

    #include<iostream>
    using namespace std;
    #include<string>
    //类对象作为类成员,
    class Phone
    {
    public:
        Phone (string pName)
        {
            cout << "Phone构造函数调用" << endl;
            m_pName = pName;
        }
        //手机品牌名称
        ~Phone()
        {
            cout << "Phone析构函数调用" << endl;
        }
        string m_pName;
    };
    class Person
    {
    public:
        Person(string name,string pName):m_Name(name) ,m_Phone(pName)//隐式转换法
        {
            cout << "Person构造函数调用" << endl;
        }
        ~Person()
        {
            cout << "Person析构函数调用" << endl;
        }
        string m_Name;
        Phone m_Phone;
    };
    void test01()
    {
        Person p("张三","iphone max");
        cout << p.m_Name << " 拿着: " << p.m_Phone.m_pName << endl;
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    1
    2
    3
    4
    5
    6
    Phone构造函数调用
    Person构造函数调用
    张三 拿着: iphone max
    Person析构函数调用
    Phone析构函数调用
    请按任意键继续. . .

    4.2.8 静态成员

    就是在成员变量和成员函数前加上关键字static ,称为静态成员
  1. 静态成员变量
  • 所有对象共享同一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化
  1. 静态成员函数
  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量

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

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

    4.2.3 this指针概念

    this指针指向被调用成员函数所属的对象
    是隐含在每一个 非静态成员函数 体内的一种指针,不需要定义,直接使用就行,返回对象本身可以用return *this;
    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

    #include<iostream>
    using namespace std;
    class Person
    {
    public:
        Person(int age)
        {
            //this指针指向的是被调用的成员函数 所属的对象
            this->age = age;
        }
        
        //如果这里返回的是Person不是Person&,就会创建一个新的对象
        Person& PersonAddAge(Person &p)
        {
            this->age += p.age;
            
            //this是指向p2的指针,而*this是p2本体
            return *this;
        }
        int age;
    };
    //解决名称冲突
    void test01()
    {
        Person p1(18);
        cout << "p1的年龄为:" << p1.age << endl;
    }
    void test02()
    {
        Person p1(10);
        Person p2(10);
        //链式编程思想
        p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
        cout << "p2的年龄为: " << p2.age << endl;
    }
    int main(){
        test02();
        
        system("pause");
        return 0;
    }


    4.3.3 空指针访问成员函数

    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<iostream>
    using namespace std;
    //空指针调用成员函数
    class Person
    {
    public:
        void showClassName()
        {
            cout << "This is Person class!" << endl;
        }
        void showPersonAge()
        {
            if (this == NULL)//提高代码的健壮性
            {
                return;
            }
            cout << "age = " << m_Age << endl;//其实这里的m_Age是this->m_Age的一种表达形式
        }
        int m_Age;
    };
    void test01()
    {
        Person * p = NULL;
        p->showClassName();
        p->showPersonAge();
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    4.3.4 const修饰成员函数

常函数:

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

常对象:

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

    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<iostream>
    using namespace std;
    class Person
    {
    public:
        //this指针的本质 是指针常量,指针的指向是不可以修改的
        //下面const类似于 const Person * const this;
        //在成员函数后加const,修饰的是this指向,让指针指向的对象包含的成员值也不可修改
        void showPerson() const
        {
            this->m_B = 10;
            //this->m_A = 10;//左值不可修改
            //this = NULL;
        }
        void func()
        {
            m_A = 10;
        }
        int m_A;
        mutable int m_B;//特殊变量,在常函数中也可以修改这个值,加关键字mutable
    };
    void test01()
    {
        Person p;
        p.showPerson();
    }
    //常对象
    void test02()
    {
        const Person p;//在对象前加上const变为常对象
        //p.m_A = 100;//不可修改
        p.m_B = 100;//m_B是特殊值,在常对象下也可以修改
        //常对象只能调用常函数(const只能调用const)
        p.showPerson();
        //p.func();//常对象不能调用普通成员函数,因为这样就可能修改成员属性了
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    4.4 友元

    有些私有属性想让类外一些特殊的函数或者类访问
    三种实现:

  • 全局函数做友元

  • 类做友元
  • 成员函数做友元

    4.4.1 全局函数做友元

    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

    #include<iostream>
    #include<string>
    using namespace std;
    //建筑物类
    class Building
    {
        //goodGay全局函数是Building好朋友,可以访问私有成员
        friend void goodGay(Building *building);
    public:
        Building()
        {
            m_SittingRoom = "客厅";
            m_BedRoom = "卧室";
        }
        string m_SittingRoom;
    private:
        string m_BedRoom;
    };
    void goodGay(Building *building)
    {
        cout << "friend is visiting : " << building->m_SittingRoom << endl;
        cout << "friend is visiting : " << building->m_BedRoom << endl;
    }
    void test01()
    {
        Building building;
        goodGay(&building);
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    4.4.2 类做友元

    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

    #include<iostream>
    using namespace std;
    #include<string>
    class Building;
    class GoodGay
    {
    public:
        GoodGay();
        void visit();//参观函数,访问Building中的属性
        
        Building * building;
        int m_Age;
    };
    class Building
    {
        //告诉编译器GoodGay可以访问Building中的私有内容
    friend class GoodGay;
    public:
        Building();
        string m_SittingRoom;
    private:
        string m_BedRoom;
    };
    Building::Building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    GoodGay::GoodGay()
    {
        //创建一个建筑物对象
        building = new Building;
    }
    void GoodGay::visit()
    {
        cout << "friend is visiting : " << building->m_SittingRoom << endl;
        cout << "friend is visiting : " << building->m_BedRoom << endl;
    }
    void test01()
    {
        GoodGay gg;
        gg.visit();
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    4.4.3成员函数做友元

    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

    #include<iostream>
    using namespace std;
    #include<string>
    class Building;
    class GoodGay
    {
    public:
        GoodGay();
        void visit();//参观函数,访问Building中的属性
        void visit2();//不可访问Building中的属性
        
        Building * building;
        int m_Age;
    };
    class Building
    {
        //告诉编译器GoodGay类中的visit()可以访问Building中的私有内容
        friend void GoodGay::visit();
    public:
        Building();
        string m_SittingRoom;
    private:
        string m_BedRoom;
    };
    Building::Building()
    {
        m_SittingRoom = "客厅";
        m_BedRoom = "卧室";
    }
    GoodGay::GoodGay()
    {
        //创建一个建筑物对象
        building = new Building;
    }
    void GoodGay::visit()
    {
        cout << "visit is visiting : " << building->m_SittingRoom << endl;
        cout << "visit is visiting : " << building->m_BedRoom << endl;
    }
    void GoodGay::visit2()
    {
        cout << "visit2 is visiting : " << building->m_SittingRoom << endl;
        //cout << "friend is visiting : " << building->m_BedRoom << endl;
    }
    void test01()
    {
        GoodGay gg;
        gg.visit();
        gg.visit2();
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    4.5 运算符重载

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

    4.5.1 加号运算符重载+

    实现两个自定义数据类型的相加运算
    内置的数据类型的表达式是不能改变的
    不要滥用
    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

    #include<iostream>
    using namespace std;
    //加号运算符重载
    class Person
    {
    public:
        //1、成员函数实现重载
        // Person operator+(Person &p)
        // {
        //     Person temp;
        //     temp.m_A = this->m_A + p.m_A;
        //     temp.m_A = 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_A = 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_A = 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 = operator+(p1,p2);
        Person p3 = p1 + p2;
        //运算符重载也可以发生函数重载
        Person p4 = p1 + 100;
        cout << "p3.m_A = " << p3.m_A << endl;
        cout << "p3.m_A = " << p3.m_A << endl;
        cout << "p4.m_A = " << p4.m_A << endl;
        cout << "p4.m_A = " << p4.m_A << endl;
    }
    int main(){
        test01();
        
        system("pause");
        return 0;
    }


    4.5.2 左移运算符重载<<

    作用 输出自定义数据类型
    Person p;
    cout << p << endl; 怎么定义?
    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 <iostream>
    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
        // //不会利用成员函数重载<<运算符,因为无法实现cout在左侧
        //     void operator<<(cout)
        //     {
        //     }
    private:
        int m_A;
        int m_B;
    };
    //只能用全局函数重载左移运算符
    ostream & operator<<(ostream &cout, Person &p) //本质 opertor<<(cout,p) 简化cout << p
    {
        cout << "m_A = " << p.m_A << " m_B = " << p.m_B << endl;
        return cout;
    }
    void test01()
    {
        Person p(10,10);
        // p.m_A = 10;
        // p.m_B = 10;
        cout << p << "hello world!" << endl;
    }
    int main()
    {
        test01();
        system("pause");
        return 0;
    }


    4.5.3 递增运算符重载++

    作用: 通过重载递增运算符,实现自己的整型数据
    前置递增返回引用,后置递增返回值
    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

    #include<iostream>
    using namespace std;
    //递增运算符重载
    class MyInteger
    {
        friend ostream & operator<<(ostream &cout,MyInteger myint);
    public:
        MyInteger()
        {
            m_Num = 0;
        }
        //重载前置++运算符
       //为了一直对一个数据进行递增操作
        MyInteger &  operator++()
        {
            m_Num++;
            //将自身返回
            return *this;
        }
        //重载后置++运算符
        //int代表占位参数,可以用于区分前置和后置
        MyInteger operator++(int)
        {
            //先 记录当时结果
            MyInteger temp = *this;
            //后 递增
            m_Num++;
            return temp;
        }
    private:
        int m_Num;
    };
    //重载<<运算符
    ostream & operator<<(ostream &cout,MyInteger myint)
    {
        cout << myint.m_Num;
        return cout;
    }
    void test01()
    {
        MyInteger myint;
        cout << ++(++myint) << endl;
        cout << myint << endl;
    }
    void test02()
    {
        MyInteger myint;
        cout << myint++ << endl;
        cout << myint << endl;
    }
    int main(){
        test02();
        
        system("pause");
        return 0;
    }


    4.5.4 赋值运算符重载=

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

4.5.5关系运算符重载 == ,!=

4.5.6函数调用运算符重载

函数调用运算符()也可以重载

4.6 继承

继承实现页面
减少重复代码
语法: class 子类 : 继承方式 父类
子类 也称为 派生类
父类,也称为 基类
公共页面类

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

#include<iostream>
using namespace std;
//普通实现页面
//Java页面
class BasePage
{
public:
    void header()
    {
        cout << "首页、公开课、登录。。。。(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心,交流合作,站内地图。。。(公共尾部)" << endl;
    }
    void left()
    {
        cout << "Java、python、c++...(公共分类列表)" << endl;
    }
    
};
// class Python
// {
// public:
//     void header()
//     {
//         cout << "首页、公开课、登录。。。。(公共头部)" << endl;
//     }
//     void footer()
//     {
//         cout << "帮助中心,交流合作,站内地图。。。(公共尾部)" << endl;
//     }
//     void left()
//     {
//         cout << "Java、python、c++...(公共分类列表)" << endl;
//     }
//     void content()
//     {
//         cout << "Python学科视频"<< endl;
//     }
// };
// class C
// {
// public:
//     void header()
//     {
//         cout << "首页、公开课、登录。。。。(公共头部)" << endl;
//     }
//     void footer()
//     {
//         cout << "帮助中心,交流合作,站内地图。。。(公共尾部)" << endl;
//     }
//     void left()
//     {
//         cout << "Java、python、c++...(公共分类列表)" << endl;
//     }
//     void content()
//     {
//         cout << "C++学科视频"<< endl;
//     }
// };
// 继承实现页面
// 减少重复代码
// 语法: class 子类 : 继承方式 父类
// 子类 也称为 派生类
// 父类,也称为 基类
// 公共页面类
class Java : public BasePage
{
public:
    void content()
    {
        cout << "Java学科视频"<< endl;
    }
};
class Python : public BasePage
{
public:
    void content()
    {
        cout << "Python学科视频"<< endl;
    }
};
class C : public BasePage
{
public:
    void content()
    {
        cout << "C++学科视频"<< endl;
    }
};
void test01()
{
    cout << "Java下载视频页面如下" << endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();
    cout << "----------------------" << endl;
    cout << "Python下载视频页面如下" << endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();
    cout << "----------------------" << endl;
    cout << "C++下载视频页面如下" << endl;
    C c;
    c.header();
    c.footer();
    c.left();
    c.content();
}
int main(){
    test01();
    system("pause");
    return 0;
}

4.6.2继承方式

即从父类继承的东西是子类的什么部分

  • 公共继承
    private里面的子类都继承不到,public和protected都直接继承
  • 保护继承
    private里面的子类都继承不到,public和protected都继承为子类的protected
  • 私有继承
    private里面的子类都继承不到,public和protected都继承为子类的private
    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

    class Base1
    {
    public:
        int m_A;
    protected:
        int m_B;
    private:
        int m_C;
        
    };
    //公共继承
    class Son1 : public Base1
    {
    public:
        void func()
        {
            m_A = 10;//父类中的public,在子类中为public
            m_B = 10;//父类中的protected,在子类中为protected
            //m_C = 10;//父类中的private 子类访问不到
        }
    };
    //保护继承
    class Son2 : protected Base1
    {
    public:
        void func()
        {
            m_A = 100;//父类中的public,在子类中为protected
            m_B = 100;//父类中的protected,在子类中为protected
            //m_C = 10;//父类中的private 子类访问不到
        }
    };
    //私有继承
    class Son3 : private Base1
    {
    public:
        void func()
        {
            m_A = 100;//父类中的public,在子类中为private
            m_B = 100;//父类中的protected,在子类中为private
            //m_C = 10;//父类中的private 子类访问不到
        }
    };


    4.6.3 继承中的对象模型

利用开发人员命令提示工具查看对象模型

image-20220306131148803

其实父类中所有的非静态成员属性都会被子类继承,私有成员属性被编译器隐藏了,因此访问不到,但是确实被继承了。

4.6.4 继承中构造和析构顺序

子类继承父类后,当子类创建对象,也会调用父类的构造函数,到底先调用哪一个?
先父类的构造,再子类构造,然后析构子类,再析构父类,一个对应一个,一层一层来。

4.6.5 继承同名成员的处理方式

  • 访问子类同名成员,直接访问就行
  • 访问父类同名成员,需要加作用域
    image-20220306131211640
    子类中出现和父类同名的成员函数,子类成员会屏蔽所有父类的同名函数,如果想访问到被隐藏的,加作用域
    image-20220306131229790

4.6.6 继承 同名静态成员的处理方式

同上
image-20220306131243461

4.6.7 继承 多继承语法

相当于允许一个儿子有多个爹
实际开发中不建议用多继承,容易有二义性
image-20220306131300369
子类对象的size
image-20220306131313569

4.6.7 继承 菱形继承语法

两个派生类继承自同一个基类;又有一个子类继承两个派生类
image-20220306131331048
解决方案:虚继承
image-20220306131342232
这份数据就只有一份了
原理:虚继承把原本继承的各个基类的内部的变量值变成vbptr虚基类指针,指向唯一真实值
image-20220306131355936

4.7 多态

4.7.1 多态的基本概念

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

#include<iostream>
using namespace std;
class Animal
{
public:
    //虚函数
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};
class Cat : public Animal
{
public:
    
    void speak()
    {
    cout << "小猫在说话" << endl;
    }
};
class Dog : public Animal {
    public:
    
    void speak()
    {
    cout << "小狗在说话" << endl;
    }
};
//父类的引用接受子类的对象
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,则不能提前绑定地址,地址晚绑定
//满足条件:1、继承关系
//2、子类要重写父类的虚函数(重载是函数名相同,参数不一样)
//重写:函数返回值的类型,参数名称,参数列表完全相同
//动态多态使用
//父类指针或者引用,执行子类对象
void doSpeak(Animal &animal)
{
    animal.speak();
}
void test01()
{
    Cat cat;
    doSpeak(cat);
    Dog dog;
    doSpeak(dog);
}
int main()
{
    test01();
    system("pause");
    return 0;
}


内部这样写,存储的会是虚函数(表)指针

image-20220306131413183
vfptr:virtual function pointer
指向一个
vftable虚函数表
表内部记录虚函数的地址

子类重写的父类虚函数
子类中的虚函数表 内部 会替换成 子类重写的虚函数的地址

当父类的指针或引用指向子类对象时,发生多态。
image-20220306131432922

用猫类的虚函数表替换继承自父类的虚函数表
image-20220306131444142

4.7.2 多态案例-计算机类

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

如果想扩展新的功能,需求修改源码
在真实的开发中,提倡开闭原则
对扩展进行开发,对修改进行关闭

多态的好处:

  1. 组织结构清晰,哪个功能出错了马上就可以定位到
  2. 可读性强
  3. 前期和后期的扩展和维护性高:如果想扩展新的功能,需求修改源码,在真实的开发中,提倡开闭原则,对扩展进行开发,对修改进行关闭.
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

#include<iostream>
#include <string>
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;
};
//利用多态实现计算器
//实现计算器抽象类
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 test01()
{
   Calculator c;
   c.m_Num1 = 10;
   c.m_Num2 = 10;
   cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
   cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
   cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
void test02()
{
    //多态使用条件
    //父类指针或者引用指向子类对象
    //加法运算
    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;
    abc = new MulCalculator;
    abc->m_Num1 = 10;
    abc->m_Num2 = 10;
    cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
    delete abc;
}
int main()
{
    test02();
    system("pause");
    return 0;
}


4.7.3 纯虚函数和抽象类

一般在使用多态时,父类虚函数无意义,都是用子类调用,重写内容

因此可以将父类改成 纯虚函数

virtual 返回值类型 函数名 (参数列表)= 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<iostream>
using namespace std;
class Base
{
public:
    //纯虚函数
    //只要有一个纯虚函数,这个类称为抽象类
    //抽象类特点:
    //1、无法实例化对象
    //2、子类必须重写父类中的纯虚函数,否则也属于抽象类
    virtual void func() = 0;
};
class Son : public Base
{
public:
    virtual void func() 
    {
        cout << "func" << endl;
    }
}; 
void test01()
{
    //Base b;   //无法实例化对象
    Base * a = new Son;
    a->func();
}
int main()
{
    test01();
    system("pause");
    return 0;
}


4.7.4 多态案例:制作饮品

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

#include<iostream>
#include<string>
using namespace std;
//多态案例2 制作饮品
class AbstractDrinking
{
public:
    virtual void Boil() = 0;
    //冲泡
    virtual void Brew() = 0;
    //倒入杯中
    virtual void PourInCup() = 0;
    //导入辅料
    virtual void PutSomething() = 0;
    //制作饮品
    void makeDrink()
    {
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};
//制作咖啡
class Coffee :public AbstractDrinking
{
    public:
    virtual void Boil()
    {
        cout << "煮农夫山泉" << endl;
    }
    //冲泡
    virtual void Brew()
    {
        cout << "冲泡咖啡" << endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout << "倒入杯中" << endl;
    }
    //导入辅料
    virtual void PutSomething()
    {
        cout << "加入糖和牛奶" << endl;
    }
};
//泡茶
class Tea :public AbstractDrinking
{
    public:
    virtual void Boil()
    {
        cout << "矿泉水" << endl;
    }
    //冲泡
    virtual void Brew()
    {
        cout << "冲泡茶叶" << endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout << "倒入杯中" << endl;
    }
    //导入辅料
    virtual void PutSomething()
    {
        cout << "不加辅料" << endl;
    }
};
void doWork(AbstractDrinking * abs)
{
    abs->makeDrink();
    delete abs;
}
void test01()
{
    doWork(new Coffee);
    cout << "-----------------------" << endl;
    doWork(new Tea);
}
int main()
{
    test01();
    system("pause");
    return 0;
}


4.7.5 虚析构和纯虚析构

多态使用时,如果子类中有一些属性放在堆区,父类指针在释放时无法调用子类的析构代码
image-20220306131611778
如图,没有调用Cat的析构函数,原因在于用父类指针指向的子类对象,父类指针在析构的时候不会调用子类中的析构函数

解决:将父类中的析构函数改为虚析构或纯虚析构

共性:

  • 都可以解决父类指针释放子类对象
  • 都需要有具体的函数实现
    区别:
  • 如果是纯虚构,该类属于抽象类,无法实例化对象
    虚析构语法:
    virtual ~类名(){}
    纯虚构语法:
    virtual ~类名 () = 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

#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
    Animal()
    {
        cout << "Animal构造函数调用" << endl;
    }
    //纯虚函数
    virtual void speak() = 0;
    //利用虚析构可以解决 父类指针释放子类对象时不干净的问题
    // virtual ~Animal()
    // {
    //     cout << "Animal虚析构函数调用" << endl;
    // }
    //纯虚析构 需要声明,也需要实现
    //有了纯虚析构之后,这个类也属于抽象类,无法实例化对象。
    virtual ~Animal() = 0;
};
Animal::~Animal()
{
    cout << "Animal纯虚析构函数调用" << endl;
}
class Cat : public Animal
{
public:
    Cat(string name)
    {
        cout << "Cat构造函数调用" << endl;
        m_Name = new string(name);
    }
    virtual void speak()
    {
    cout  << *m_Name << "小猫在说话" << endl;
    }
    ~Cat()
    {
        if (m_Name != NULL)
        {
            cout << "Cat析构函数调用" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }
    //创建在堆区,用指针区维护
    string* m_Name;
};
class Dog : public Animal {
    public:
    
    void speak()
    {
    cout << "小狗在说话" << endl;
    }
};
void doSpeak(Animal &animal)
{
    animal.speak();
}
void test01()
{
    Animal* animal = new Cat("Tom");
    animal->speak();
    delete animal;
}
int main()
{
    test01();
    system("pause");
    return 0;
}