• c++ 类的继承(二)


    1. 初始化列表

    初始化列表在三种情况下必须使用:

    1. 继承关系下,父类没有无参构造函数情况
    #include 
    
    using namespace std;
    
    
    class Base{
    public:
        string name;
        int num;
        Base(){
            cout<< "基类的无参构造..." <<endl;
        }
        Base(string name){
            cout<< "基类的有参构造1..." <<endl;
        }
        Base(string name,int num){
            cout<< "基类的有参构造2..." <<endl;
        }
    
    };
    
    
    class Derived : public Base {
    public:
    
        Derived(){
            cout<< "派生类的无参构造..." <<endl;
        }
        Derived(string name):Base(name){
            cout<< "派生类的有参构造1..." <<endl;
        }
        Derived(string name,int num):Base(name,num){
            cout<< "派生类的有参构造2..." <<endl;
        }
    };
    
    int main() {
    
    
    
        Derived d;
    
        cout << "------------------------" << endl;
        Derived d0("zhangfei");
        Derived d1("libai",24);
    
    
        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

    运行结果:

    基类的无参构造...
    派生类的无参构造...
    ------------------------
    基类的有参构造1...
    派生类的有参构造1...
    基类的有参构造2...
    派生类的有参构造2...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 总结
    1. 创建子类对象的时候,不会执行父类对象的创建工作
    2. 但是创建子类对象会先执行父类的构造函数,以便初始化父类的属性(成员变量)
    3. 如果不创建父类的对象,那么父类里面的成员变量存放在子类对象中
    4. 如果创建子类对象时同时创建父类对象,这就浪费内存空间。
    1. 需要初始化const修饰的类成员或初始化引用成员数据
    #include 
    
    
    using namespace std;
    
    class person{
    
    public:
        const int ID;
        int & age;
    
        person(int no , int age):ID(no),age(age){
            cout << "执行构造函数..." <<endl;
        }
    };
    
    int main(){
        
    
        person p(78 , 28);
        cout << p.ID << " , " << p.age << endl;
    
        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
    1. 需要初始化的数据成员是对象,并且对应的类没有无参构造函数
    #include 
    
    using namespace std;
    
    
    class Base{
    public:
        string name;
        int num;
        Base(){
            cout<< "基类的无参构造..." <<endl;
        }
        Base(string name){
            cout<< "基类的有参构造1..." <<endl;
        }
        Base(string name,int num){
            cout<< "基类的有参构造2..." <<endl;
        }
    
    };
    
    
    class Derived{
    public:
        int no;
        Base b;
    //    Derived(){
    //        cout<< "派生类的无参构造..." <
    //    }
        Derived(string name):b(name){
            cout<< "派生类的有参构造1..." <<endl;
        }
        Derived(string name,int num):b(name,num){
            cout<< "派生类的有参构造2..." <<endl;
        }
    };
    
    int main() {
    
    
        cout << "------------------------" << endl;
        Derived d0("zhangfei");
        cout << "------------------------" << endl;
        Derived d1("libai",24);
    
    
        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

    运行结果:

    ------------------------
    基类的有参构造1...
    派生类的有参构造1...
    ------------------------
    基类的有参构造2...
    派生类的有参构造2...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    or

    #include 
    
    using namespace std;
    
    
    class Base{
    public:
        string name;
        int num;
        Base(){
            cout<< "基类的无参构造..." <<endl;
        }
        Base(string name){
            cout<< "基类的有参构造1..." <<endl;
        }
        Base(string name,int num){
            cout<< "基类的有参构造2..." <<endl;
        }
    
    };
    
    
    class Derived{
    public:
        int no;
        Base b;
        Derived():b(){
            cout<< "派生类的无参构造..." <<endl;
        }
        Derived(string name):b(name){
            cout<< "派生类的有参构造1..." <<endl;
        }
        Derived(string name,int num):b(name,num){
            cout<< "派生类的有参构造2..." <<endl;
        }
    };
    
    int main() {
    
        Derived d;
        cout << "------------------------" << endl;
        Derived d0("zhangfei");
        cout << "------------------------" << endl;
        Derived d1("libai",24);
    
    
        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

    运行结果:

    基类的无参构造...
    派生类的无参构造...
    ------------------------
    基类的有参构造1...
    派生类的有参构造1...
    ------------------------
    基类的有参构造2...
    派生类的有参构造2...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2. 重写父类同名函数

    在C++中,子类可以重写(override)父类的同名函数。这被称为函数的覆盖(function overriding)。当子类重写父类的函数时,它必须具有相同的名称、参数列表和返回类型。

    • 特点
    1. 多态性:通过重写父类函数,子类对象可以根据实际类型来调用不同的函数实现,实现多态性。
    2. 继承性:子类继承了父类的函数,并且可以对其进行更改或增加新的功能。
    • 使用场景
    1. 扩展功能:子类可以通过重写父类函数来添加额外的行为或修改原有行为,从而实现功能扩展。
    2. 自定义实现:子类可以根据自己的需求提供不同于父类的实现逻辑。
    3. 适应特定情境:根据特定场景需要,在子类中针对某些特殊情况重新定义父类方法。
      代码:
    #include 
    
    // 父类
    class Shape {
    public:
        virtual void draw() {
            std::cout << "绘制形状" << std::endl;
        }
    };
    
    // 子类 Circle
    class Circle : public Shape {
    public:
        void draw() override { // 使用 override 关键字表示重写
            std::cout << "绘制圆形" << std::endl;
        }
    };
    
    // 子类 Rectangle
    class Rectangle : public Shape {
    public:
        void draw() override {
            std::cout << "绘制矩形" << std::endl;
        }
    };
    
    int main() {
        Shape* shape1 = new Circle();
        shape1->draw(); // 输出:绘制圆形
    
        Shape* shape2 = new Rectangle();
        shape2->draw(); // 输出:绘制矩形
    
        delete shape1;
        delete shape2;
    
        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

    3. 多继承

    在C++中,子类多继承是指一个派生类从多个基类继承特性和行为。这意味着一个子类可以同时拥有多个父类的成员和方法。

    • 特点
    1. 子类可以获得多个基类的属性和方法,增强了代码复用性。
    2. 多继承可以构建更复杂的继承关系,允许在一个子类中结合不同的功能。
    3. 多继承提供了更大的灵活性,使得对象之间可以共享接口和实现。
    • 使用场景
    1. 当存在一种逻辑上属于不同概念、但具有共同行为的情况时,可以使用多继承来实现。
    2. 当需要通过组合不同功能或角色来创建一个新的对象时,也可以考虑使用多继承。
    • 代码实现
    #include 
    
    // 基类A
    class A {
    public:
        void funcA() {
            std::cout << "This is function A." << std::endl;
        }
    };
    
    // 基类B
    class B {
    public:
        void funcB() {
            std::cout << "This is function B." << std::endl;
        }
    };
    
    // 子类C从基类A和基类B进行多继承
    class C : public A, public B {
    public:
        void funcC() {
            std::cout << "This is function C." << std::endl;
        }
    };
    
    int main() {
    
        C c;
        c.funcA();  // 调用基类A的函数
        c.funcB();  // 调用基类B的函数
        c.funcC();  // 调用子类C自身的函数
    
        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

    3.1 多继承的构造函数

    多继承形式下的构造函数和单继承形式基本相同,只是要在子类的构造函数中调用多个父类的构造函数 。 他们调用的顺序由定义子类时,继承顺序 决定。

    #include 
    
    using namespace std;
    
    // 基类A
    class A {
    public:
        A(){
            cout<< "A类的无参构造..." <<endl;
        }
        ~A(){
            cout<< "A类的析构构造..." <<endl;
        }
    };
    
    // 基类B
    class B {
    public:
        B(){
            cout<< "B类的无参构造..." <<endl;
        }
        ~B(){
            cout<< "B类的析构构造..." <<endl;
        }
    };
    
    // 子类C从基类A和基类B进行多继承
    class C : public A, public B {
    public:
        C(){
            cout<< "C类的无参构造..." <<endl;
        }
        ~C(){
            cout<< "C类的析构构造..." <<endl;
        }
    };
    
    int main() {
    
        C c;
        
        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

    运行结果:

    A类的无参构造...
    B类的无参构造...
    C类的无参构造...
    C类的析构构造...
    B类的析构构造...
    A类的析构构造...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4. 类的前置声明

    C++中的类前置声明是指在使用类之前提前声明类的存在,而不需要完整地定义类。它可以在某些场景下用于解决循环依赖或减少编译时间的问题。

    • 特点
    1. 允许在程序中引用尚未完整定义的类。
    2. 只需要提供类名和分号即可进行声明,无需包含类的详细实现。
    3. 前置声明通常用于解决循环依赖或减少编译时间。
    • 使用场景
    1. 解决循环依赖
      当两个或多个类相互引用时,可以使用前置声明来解决循环依赖的问题。通过提前声明类的存在,可以在类定义之前使用该类的指针或引用。
    • 注意

      B b // 报错,是因为不知道前面的声明的B类有没有无参构造
      B* b; // 这是指针,它只是一个地址而已,不会执行无参构造。
      B& b; // 这是别名,不创建对象,所以不会执行无参构造。

    #include 
    
    class B; // 前置声明
    
    class A {
    private:
        B* b;
    public:
        void setB(B* obj) {
            b = obj;
        }
        void doSomething();
    };
    
    class B {
    private:
        A* a;
    public:
        void setA(A* obj) {
            a = obj;
        }
        void doSomething();
    };
    
    void A::doSomething() {
        std::cout << "a->doSomething()" << std::endl;
        if (b) {
            b->doSomething();
        }
    }
    
    void B::doSomething() {
        std::cout << "b->doSomething()" << std::endl;
        if (a) {
            a->doSomething();
        }
    }
    
    int main() {
        A a;
        B b;
    
    
        a.setB(&b);
        b.setA(&a);
    
        std::cout << "-----------------------------" << std::endl;
    
    
        a.doSomething();
        b.doSomething();
    
        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

    类A和类B相互引用,并使用了前置声明。在类A中,成员变量B* b是一个指针类型,不会执行B类的无参构造函数,因此不需要知道B类是否有无参构造函数。而如果将成员变量声明为B& b,则是一个引用类型,也不会执行B类的无参构造函数。
    在类定义之后,我们定义了类A和类B的成员函数doSomething()。在A::doSomething()中,调用了b->doSomething(),即调用了B类的成员函数。同样,在B::doSomething()中,调用了a->doSomething(),即调用了A类的成员函数。
    在main()函数中,我们创建了类A和类B的对象,并通过setB()和setA()方法设置了它们之间的循环依赖关系。最后,调用了a.doSomething()和b.doSomething()来触发成员函数的调用。

    1. 提高编译速度
      在一些情况下,完整的类定义可能不是必需的,例如在函数声明中只需要使用类的指针或引用,而不需要访问类的成员。这时,使用前置声明可以减少编译时间,因为编译器不需要包含和处理完整的类定义。
    #include 
    
    // 前置声明
    class SomeClass;
    
    void useSomeClass(SomeClass* obj);
    
    int main() {
        SomeClass* obj = new SomeClass();
        useSomeClass(obj);
        delete obj;
    
        return 0;
    }
    
    // 完整类定义
    class SomeClass {
    public:
        void doSomething();
    };
    
    void useSomeClass(SomeClass* obj) {
        obj->doSomething();
    }
    
    void SomeClass::doSomething() {
        std::cout << "Doing something..." << std::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

    5. 继承的使用场景

    1. 代码重用和封装:当多个类具有相似的属性和行为时,可以将这些共同特征提取到一个基类中,派生类继承基类以获得这些共同特征,并在派生类中添加额外的特定功能。
    class Animal {
    public:
        void eat() {
            std::cout << "Animal is eating." << std::endl;
        }
    };
    
    class Dog : public Animal {
    public:
        void bark() {
            std::cout << "Dog is barking." << std::endl;
        }
    };
    
    int main() {
        Dog dog;
        dog.eat();  // 继承自Animal类
        dog.bark(); // Dog类自身的函数
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 实现多态性:通过基类的指针或引用,可以以统一的方式操作不同的派生类对象,实现多态性。这样可以在运行时动态地选择调用不同派生类的特定函数。
    class Shape {
    public:
        virtual void draw() {
            std::cout << "Drawing a shape." << std::endl;
        }
    };
    
    class Circle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a circle." << std::endl;
        }
    };
    
    class Rectangle : public Shape {
    public:
        void draw() override {
            std::cout << "Drawing a rectangle." << std::endl;
        }
    };
    
    int main() {
        Shape* shape1 = new Circle();
        Shape* shape2 = new Rectangle();
    
        shape1->draw(); // 动态调用Circle类的draw函数
        shape2->draw(); // 动态调用Rectangle类的draw函数
    
        delete shape1;
        delete shape2;
    
        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
    1. 扩展和特化功能:通过继承,可以在派生类中添加额外的成员变量和成员函数,以实现对基类功能的扩展和特化。
    class Vehicle {
    protected:
        int wheels;
    
    public:
        Vehicle(int numWheels) : wheels(numWheels) {}
    
        void printWheels() {
            std::cout << "Number of wheels: " << wheels << std::endl;
        }
    };
    
    class Car : public Vehicle {
    public:
        Car() : Vehicle(4) {}
    
        void startEngine() {
            std::cout << "Engine started." << std::endl;
        }
    };
    
    int main() {
        Car car;
        car.printWheels();  // 继承自Vehicle类
        car.startEngine();  // Car类自身的函数
    
        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
  • 相关阅读:
    什么是机器学习中的目标函数和优化算法,列举几种常见的优化算法
    【Spring(六)】使用篇:AOP在开发中的使用
    【Python】读取显示pgm图像文件
    Windows迁移文件的快速方法
    Linux-gdb调试方式二(gdb和gdbserver)
    项目架构:eslint 代码检测、提交代码审查
    A Philosophy of Software Design读书笔记——模块的接口要通用,实现要深入
    论文阅读 - Hidden messages: mapping nations’ media campaigns
    list根据对象中某个字段属性去重Java流实现
    ppt聚光灯效果
  • 原文地址:https://blog.csdn.net/weixin_40378209/article/details/133891183