面向对象编程:C++中的继承与多态

在C++中,继承与多态是实现代码复用和灵活性的重要机制。继承允许子类获取父类的属性和方法,从而重用已有代码;多态则让我们可以用统一的接口调用不同子类的实现。下面我们从基础语法、典型用法、常见陷阱以及最佳实践四个角度,系统梳理这两大概念。

一、继承的基本语法

class Base {
public:
    void show() const { std::cout << "Base::show" << std::endl; }
protected:
    int value;
};

class Derived : public Base {  // 公开继承
public:
    void show() const { std::cout << "Derived::show" << std::endl; }
    void setValue(int v) { value = v; }  // 访问受保护成员
};
  • publicprotectedprivate 控制访问权限。公开继承保持父类的公共成员不变;受保护继承将父类公共成员变为受保护;私有继承则把所有成员都变为私有。
  • 默认继承是 private,在类内部写 class Derived : Base {} 时,所有公共和受保护成员都变成私有。

二、多态的实现与使用

1. 虚函数

class Base {
public:
    virtual void speak() const { std::cout << "Base" << std::endl; }
    virtual ~Base() {}  // 虚析构,防止内存泄漏
};

class Derived : public Base {
public:
    void speak() const override { std::cout << "Derived" << std::endl; }
};
  • virtual 声明在基类中,子类使用 override 确认覆写,帮助编译器捕获错误。
  • 虚函数表(vtable)使得运行时可以决定调用哪个函数。

2. 纯虚函数与抽象类

class Shape {
public:
    virtual double area() const = 0;  // 纯虚函数
};

class Circle : public Shape {
    double r;
public:
    Circle(double rad) : r(rad) {}
    double area() const override { return 3.1415926535 * r * r; }
};
  • 纯虚函数使类成为抽象类,无法直接实例化。
  • 子类必须实现所有纯虚函数,才能成为可实例化的类。

3. 多重继承

class Flyer {
public:
    void fly() { std::cout << "Flying" << std::endl; }
};

class Swimmer {
public:
    void swim() { std::cout << "Swimming" << std::endl; }
};

class Duck : public Flyer, public Swimmer { };
  • 注意 菱形继承:若多个基类共享同一祖先,需使用虚继承 (virtual) 避免重复子对象。
class Animal { public: void breathe() {} };
class Mammal : virtual public Animal { };
class Bird : virtual public Animal { };
class Bat : public Mammal, public Bird { };  // 只包含一份 Animal

三、常见陷阱与错误

  1. 忘记虚析构
    通过基类指针删除派生对象时,若基类没有虚析构,析构函数只会调用基类析构,导致资源泄漏。

  2. 不安全的类型转换
    直接使用 static_cast<Derived*>(ptr) 从基类指针转换,若对象实际不是 Derived,行为未定义。推荐使用 dynamic_cast 并检查返回值。

  3. 多态失效

    • 对象切片:Derived d; Base b = d; 只保留基类部分,b.speak() 调用的是基类实现。
    • 非虚函数:如果忘记在基类声明为 virtual,多态不会生效。
  4. 虚函数表开销
    虚函数表在某些小型嵌入式系统会占用大量内存;如果性能极端敏感,可考虑纯函数或模板。

四、最佳实践

主题 推荐做法
继承层次 尽量保持浅层(≤2层),避免深层继承导致维护困难
多态接口 定义接口(纯虚类)时只保留必要方法,保持兼容性
命名规范 采用 I 前缀(如 IShape)表示接口,区分实现类
虚构造 所有可通过基类指针删除的类都应提供虚析构
使用override 每次覆写时加 override,让编译器检查
final 修饰符 防止类被再次继承或方法被再次覆写,提升安全性
移动语义 对于大型对象,优先使用移动构造和移动赋值,避免不必要的拷贝
模板与 CRTP 对于需要静态多态的情况,可使用模板/CRTP 替代虚函数,提升性能

五、实际案例:实现一个插件系统

class Plugin {
public:
    virtual void init() = 0;
    virtual void shutdown() = 0;
    virtual const char* name() const = 0;
    virtual ~Plugin() {}
};

class AudioPlugin : public Plugin {
public:
    void init() override { std::cout << "Audio init" << std::endl; }
    void shutdown() override { std::cout << "Audio shutdown" << std::endl; }
    const char* name() const override { return "Audio"; }
};

class VideoPlugin : public Plugin {
public:
    void init() override { std::cout << "Video init" << std::endl; }
    void shutdown() override { std::cout << "Video shutdown" << std::endl; }
    const char* name() const override { return "Video"; }
};

int main() {
    std::vector<std::unique_ptr<Plugin>> plugins;
    plugins.emplace_back(std::make_unique <AudioPlugin>());
    plugins.emplace_back(std::make_unique <VideoPlugin>());

    for (auto& p : plugins) p->init();
    // 运行时使用
    for (auto& p : plugins) p->shutdown();
}

通过统一的 Plugin 接口,主程序可以在不知道具体插件实现的情况下进行初始化、调用和关闭。继承与多态使得添加新插件时,只需实现接口即可。

结语

继承与多态是C++面向对象设计的核心,正确使用它们可以让代码更模块化、可维护、可扩展。然而,滥用或误用同样会导致层次过深、运行时开销大、代码难以理解。本文通过语法、示例、陷阱与最佳实践,帮助你在实际项目中高效、安全地运用继承与多态。祝你编码愉快!

发表评论