C++ 面向对象进阶:继承深化与多态详解

上一篇博客我们介绍了 C++ 面向对象的基础概念,包括类与对象、封装、构造 / 析构函数及继承的基本用法。本文将深入探讨继承的高级特性,并详细讲解面向对象的另一核心特性 ------ 多态,帮助你理解复杂类层次设计和灵活的接口实现。

一、继承的深入探讨

继承作为代码复用的核心机制,在实际开发中存在诸多细节需要掌握。除了基础的继承语法,我们还需关注成员访问控制、构造函数传递及菱形继承等问题。

1.1 继承中的构造与析构顺序

当子类继承父类时,对象的创建和销毁会涉及父子类构造函数与析构函数的调用顺序,这是内存管理的关键:

  • 构造顺序:先调用父类构造函数,再调用子类构造函数(先有父再有子)

  • 析构顺序:与构造相反,先调用子类析构函数,再调用父类析构函数(先销毁子再销毁父)

    #include <iostream>
    using namespace std;

    class Parent {
    public:
    Parent() { cout << "Parent构造函数" << endl; }
    ~Parent() { cout << "Parent析构函数" << endl; }
    };

    class Child : public Parent {
    public:
    Child() { cout << "Child构造函数" << endl; }
    ~Child() { cout << "Child析构函数" << endl; }
    };

    int main() {
    Child c;
    // 输出顺序:
    // Parent构造函数 → Child构造函数
    // 程序结束时:Child析构函数 → Parent析构函数
    return 0;
    }

注意:若父类没有默认构造函数,子类必须在初始化列表中显式调用父类的有参构造:

复制代码
class Parent {
public:
    Parent(int a) { cout << "Parent有参构造:" << a << endl; }
};

class Child : public Parent {
public:
    // 必须通过初始化列表调用父类有参构造
    Child() : Parent(10) { 
        cout << "Child构造函数" << endl; 
    }
};

1.2 继承中的同名成员处理

当子类与父类存在同名成员时,需要通过作用域分辨符区分:

  • 同名成员变量:子类对象.父类名::成员 访问父类成员

  • 同名成员函数:若子类重写了父类函数,直接调用会执行子类版本;需加作用域访问父类版本

    class Parent {
    public:
    int num = 100;
    void show() { cout << "Parent show: " << num << endl; }
    };

    class Child : public Parent {
    public:
    int num = 200; // 同名成员变量
    void show() { cout << "Child show: " << num << endl; } // 同名成员函数
    };

    int main() {
    Child c;
    cout << c.num << endl; // 200(子类成员)
    cout << c.Parent::num << endl; // 100(父类成员)

    复制代码
      c.show();  // Child show: 200(子类函数)
      c.Parent::show();  // Parent show: 100(父类函数)
      return 0;

    }

1.3 菱形继承问题与虚继承

当一个子类同时继承两个父类,而这两个父类又继承自同一个基类时,会产生菱形继承问题:

  • 数据冗余:子类会保存两份基类成员

  • 二义性:访问基类成员时无法确定来自哪个父类

    // 菱形继承结构
    class Base { public: int a; };
    class Parent1 : public Base {};
    class Parent2 : public Base {};
    class Child : public Parent1, public Parent2 {};

    int main() {
    Child c;
    // c.a = 10; // 错误:二义性(Parent1::a 还是 Parent2::a?)
    c.Parent1::a = 10; // 需显式指定,仍存在数据冗余
    return 0;
    }

解决方法:虚继承(virtual) 通过virtual关键字修饰父类的继承方式,使基类成员在子类中只保留一份:

复制代码
class Base { public: int a; };
// 虚继承:Parent1和Parent2共享Base成员
class Parent1 : virtual public Base {};
class Parent2 : virtual public Base {};
class Child : public Parent1, public Parent2 {};

int main() {
    Child c;
    c.a = 10;  // 正确:仅一份Base::a
    return 0;
}

二、多态:面向对象的灵活性核心

多态是指同一接口在不同场景下表现出不同行为,分为静态多态动态多态

  • 静态多态:编译期确定(函数重载、运算符重载)
  • 动态多态:运行期确定(基于虚函数的继承体系)

2.1 动态多态的实现条件

  1. 存在继承关系

  2. 子类重写父类的虚函数(函数名、参数、返回值完全一致)

  3. 父类指针或引用指向子类对象

    #include <iostream>
    using namespace std;

    // 父类:定义虚函数
    class Animal {
    public:
    // 虚函数:用virtual修饰
    virtual void speak() {
    cout << "动物叫" << endl;
    }
    };

    // 子类:重写虚函数
    class Cat : public Animal {
    public:
    // 重写:函数签名与父类虚函数一致
    void speak() override { // override关键字可显式标识重写(C++11)
    cout << "喵喵叫" << endl;
    }
    };

    class Dog : public Animal {
    public:
    void speak() override {
    cout << "汪汪叫" << endl;
    }
    };

    // 统一接口:接收父类指针
    void doSpeak(Animal* animal) {
    animal->speak(); // 运行时根据实际对象类型调用对应函数
    }

    int main() {
    Cat cat;
    Dog dog;
    doSpeak(&cat); // 输出:喵喵叫
    doSpeak(&dog); // 输出:汪汪叫
    return 0;
    }

2.2 虚函数表与多态原理

C++ 通过虚函数表(vtable) 实现动态多态:

  • 含有虚函数的类会生成一个虚函数表,存储虚函数地址
  • 类的每个对象会包含一个虚表指针(vptr),指向该类的虚函数表
  • 子类重写虚函数时,会替换虚表中对应函数的地址
  • 调用虚函数时,通过 vptr 找到虚表,再调用实际函数地址(运行期确定)

2.3 纯虚函数与抽象类

当父类的虚函数无需实现(仅作为接口)时,可声明为纯虚函数 ,包含纯虚函数的类称为抽象类

  • 纯虚函数语法:virtual 返回类型 函数名(参数) = 0;
  • 抽象类特点:
    • 不能实例化对象

    • 子类必须重写所有纯虚函数才能实例化,否则仍是抽象类

      // 抽象类(接口)
      class Shape {
      public:
      // 纯虚函数:仅声明,无实现
      virtual double calculateArea() = 0;
      virtual double calculatePerimeter() = 0;
      };

      // 子类实现接口
      class Circle : public Shape {
      private:
      double radius;
      public:
      Circle(double r) : radius(r) {}
      // 必须重写所有纯虚函数
      double calculateArea() override {
      return 3.14 * radius * radius;
      }
      double calculatePerimeter() override {
      return 2 * 3.14 * radius;
      }
      };

      int main() {
      // Shape s; // 错误:抽象类不能实例化
      Shape* circle = new Circle(5);
      cout << "面积:" << circle->calculateArea() << endl; // 78.5
      delete circle;
      return 0;
      }

2.4 多态的应用场景

多态是框架设计的核心思想,典型应用包括:

  1. 接口统一:用父类指针 / 引用接收不同子类对象,简化调用
  2. 扩展方便:新增子类无需修改原有接口代码(开闭原则)
  3. 回调机制:通过虚函数实现事件响应的灵活绑定

例如图形绘制框架:

复制代码
// 框架代码(无需修改)
void drawShapes(Shape* shapes[], int count) {
    for (int i = 0; i < count; i++) {
        cout << "面积:" << shapes[i]->calculateArea() << endl;
    }
}

// 扩展新图形(只需新增子类)
class Rectangle : public Shape {
    // ...实现纯虚函数
};

int main() {
    Shape* shapes[] = {new Circle(5), new Rectangle(3,4)};
    drawShapes(shapes, 2);  // 统一调用,自动适配不同图形
    return 0;
}

三、继承与多态的常见问题

3.1 虚析构函数

当父类指针指向子类对象时,若父类析构函数不是虚函数,删除指针只会调用父类析构,导致子类资源泄漏:

复制代码
class Parent {
public:
    // 虚析构:确保子类析构被调用
    virtual ~Parent() { cout << "Parent析构" << endl; }
};

class Child : public Parent {
private:
    int* data;
public:
    Child() { data = new int; }
    ~Child() { 
        delete data; 
        cout << "Child析构" << endl; 
    }
};

int main() {
    Parent* p = new Child;
    delete p;  // 若Parent析构非虚函数,仅调用Parent析构
    // 虚析构时输出:Child析构 → Parent析构
    return 0;
}

结论:基类析构函数应声明为虚函数。

3.2 不能被重写的函数

  • 静态成员函数:属于类,无 this 指针,无法放入虚函数表
  • 构造函数:对象未完全创建,无法多态调用
  • 友元函数:不是类成员函数,不存在重写概念

四、总结

本文深入讲解了 C++ 继承的高级特性(构造顺序、同名成员、菱形继承)和多态的核心机制(虚函数、纯虚函数、抽象类),主要知识点包括:

  • 继承中构造与析构的调用顺序及参数传递
  • 虚继承解决菱形继承的数据冗余和二义性
  • 动态多态的实现条件与虚函数表原理
  • 纯虚函数与抽象类在接口设计中的应用
  • 虚析构函数的重要性

多态是面向对象编程灵活性的核心,掌握它能让你设计出更具扩展性和复用性的代码。下一篇将介绍运算符重载、模板等 C++ 高级特性,敬请关注!

如果本文对你有帮助,欢迎点赞收藏,有任何疑问或补充欢迎在评论区交流~

相关推荐
冷崖10 分钟前
const 与 constexpr
c++·学习
枫叶丹437 分钟前
【Qt开发】多元素类控件(三)-> QTreeWidget
开发语言·数据库·c++·qt
晨非辰41 分钟前
【数据结构入坑指南】--《层序分明:堆的实现、排序与TOP-K问题一站式攻克(源码实战)》
c语言·开发语言·数据结构·算法·面试
hansang_IR1 小时前
【题解】P2217 [HAOI2007] 分割矩阵 [记忆化搜索]
c++·数学·算法·记忆化搜索·深搜
洲覆1 小时前
Redis 驱动适配 Reactor 模式
开发语言·网络·数据库·redis
fl1768311 小时前
基于matlab实现的DnCNN网络
开发语言·matlab
第二层皮-合肥1 小时前
如何设置等长的最大走线长度
服务器·开发语言·php
掘根1 小时前
【Protobuf】proto3语法详解1
开发语言·前端·javascript
Lee_yayayayaya1 小时前
《通信之道—从微积分到5G》阅读笔记
开发语言·matlab
普密斯科技1 小时前
图像尺寸测量仪应用Type-C接口:精准检测,赋能科技
c语言·开发语言·科技