4.3继承与多态

94 阅读3分钟

继承与多态:构建类族关系

一、继承体系基础

1.1 派生类语法

class Animal {        // 基类
protected:
    string name;
public:
    void breathe() { 
        cout << "呼吸中..." << endl; 
    }
};
​
class Dog : public Animal {  // 公有继承
public:
    void bark() {
        cout << name << ": 汪汪!" << endl;  // 可访问protected成员
    }
};

1.2 访问控制矩阵

基类成员访问权限继承方式派生类访问权限
publicpublicpublic
protectedpublicprotected
privatepublic不可访问
publicprotectedprotected
protectedprotectedprotected
privateprotected不可访问
publicprivateprivate
protectedprivateprivate
privateprivate不可访问

1.3 构造链式调用

class Vehicle {
protected:
    int wheels;
public:
    Vehicle(int w) : wheels(w) {}
};
​
class Car : public Vehicle {
    string brand;
public:
    Car(string b, int w) : Vehicle(w), brand(b) {}  // 先初始化基类
};

🔍 内存布局示例

Dog对象内存结构:
[Animal子对象][name]
[Dog扩展部分][bark()等]

二、多态实现机制

2.1 虚函数与动态绑定

class Shape {
public:
    virtual double area() const {  // 虚函数声明
        return 0;
    }
    virtual ~Shape() {}  // 虚析构函数!
};
​
class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override {  // 重写虚函数
        return 3.14 * radius * radius;
    }
};
​
void printArea(const Shape& shape) {
    cout << "面积: " << shape.area() << endl;  // 动态绑定
}

2.2 虚函数表(vTable)原理

对象内存布局:
[虚表指针][虚函数地址表]
[成员变量...]

调用过程:
1. 通过对象找到虚表指针
2. 查表获得函数实际地址
3. 执行目标函数

📊 多态性能对比

特性静态绑定动态绑定
决议时机编译期运行期
性能开销查表开销
灵活性
适用场景非虚函数需要多态的行为

三、现代C++特性

3.1 override关键字

class Base {
public:
    virtual void show(int) const;
};

class Derived : public Base {
public:
    void show(int) const override;  // 正确重写
    // void show() override;        // 错误!签名不匹配
};

3.2 final限定符

class NoExtend final {  // 禁止继承
    //...
};

class Base {
public:
    virtual void lock() final;  // 禁止重写
};

// class Derived : public NoExtend {};  // 错误!

四、综合应用案例

class PaymentMethod {
public:
    virtual ~PaymentMethod() = default;
    virtual void process(double amount) = 0;  // 纯虚函数
};

class Alipay : public PaymentMethod {
public:
    void process(double amount) override {
        cout << "支付宝支付:" << amount << "元" << endl;
    }
};

class WechatPay : public PaymentMethod {
public:
    void process(double amount) override {
        cout << "微信支付:" << amount << "元" << endl;
    }
};

void processPayment(PaymentMethod& method, double amt) {
    method.process(amt);  // 多态调用
}

五、关键原则与陷阱

5.1 重要规则

  1. 切片问题:基类对象接收派生类对象时丢失扩展成员

    Derived d;
    Base b = d;  // 发生对象切片
    
  2. 虚析构函数:基类必须声明虚析构函数

  3. 纯虚函数:包含纯虚函数的类成为抽象基类

    virtual void func() = 0;  // 纯虚函数声明
    

5.2 类型转换

转换方式安全性使用场景
static_cast编译期检查明确类型关系的向下转型
dynamic_cast运行时检查安全的多态类型转换
reinterpret_cast无检查低级二进制转换

六、总结与提升

课后思考

  1. 为什么构造函数不能是虚函数?
  2. 如何通过基类指针正确释放派生类对象?

实践建议

  1. 实现图形系统的类层次:Shape→Circle/Rectangle/Triangle
  2. 添加序列化功能,验证多态行为
  3. 使用typeid和dynamic_cast实现类型安全检测

设计原则

  • 遵循里氏替换原则:派生类应完全替代基类
  • 优先使用组合而非继承
  • 对多态基类声明虚析构函数
  • 使用智能指针管理多态对象

通过合理运用继承与多态,可以创建出扩展性强、维护方便的类层次结构。建议结合UML类图进行系统设计,并使用gdb等工具观察虚表指针的变化,深入理解多态的实现机制。