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

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

一、继承的深入探讨

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

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

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

  • 构造顺序:先调用父类构造函数,再调用子类构造函数(先有父再有子)
  • 析构顺序:与构造相反,先调用子类析构函数,再调用父类析构函数(先销毁子再销毁父)
#include 
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 
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++ 高级特性,敬请关注!

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

posted @ 2025-10-26 10:13  yjbjingcha  阅读(1)  评论(0)    收藏  举报