当前位置: 首页 > news >正文

C++ 继承完全指南

1. 概述

继承(Inheritance)是面向对象编程的三大特性之一(封装、继承、多态)。在 C++ 中,继承允许我们创建一个新类(派生类, derived class)基于另一个已有的类(基类, base class),从而重用、扩展或修改基类的行为。

继承的主要作用:

  • 代码复用:派生类自动拥有基类的成员(数据和方法)。

  • 建立层次结构:表达“is-a”关系,如DerivedBase的一种。

  • 支持多态:通过虚函数实现运行时多态。

2. 继承的基本语法

cpp

class Base { // ... }; class Derived : access-specifier Base { // ... };

其中access-specifier可以是publicprotectedprivate,决定了基类成员在派生类中的访问级别。

3. 继承方式与成员访问控制

继承方式直接影响基类publicprotectedprivate成员在派生类中的可见性。

基类成员public 继承protected 继承private 继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可访问不可访问不可访问

三种继承方式的特点:

3.1 public 继承(最常用)

  • 基类的 public 成员在派生类中仍为 public。

  • 基类的 protected 成员在派生类中仍为 protected。

  • 体现is-a关系(派生类是基类的一种)。

3.2 protected 继承

  • 所有基类的 public 和 protected 成员在派生类中都变为 protected。

  • 通常用于实现层面的继承,而不是接口继承。

3.3 private 继承

  • 所有基类成员在派生类中都变为 private。

  • 体现is-implemented-in-terms-of(根据基类实现)的关系,更接近于组合。

  • 在决定使用 private 继承时,应优先考虑组合(composition)。

4. 派生类的构造与析构

4.1 构造顺序

  1. 先调用基类的构造函数(按继承顺序从左到右)。

  2. 再按照类中成员声明的顺序初始化成员对象(包括成员变量和子对象)。

  3. 最后执行派生类的构造函数体。

4.2 析构顺序

  • 与构造完全相反:先执行派生类的析构函数体,然后析构成员对象,最后调用基类的析构函数。

4.3 显式调用基类构造函数

cpp

class Base { public: Base(int x) : data(x) {} private: int data; }; class Derived : public Base { public: Derived(int x, int y) : Base(x), derivedData(y) {} private: int derivedData; };

注意:如果基类没有默认构造函数,派生类的每个构造函数必须在初始化列表中显式调用基类的某构造函数。

5. 成员覆盖与隐藏

5.1 函数覆盖(override)

派生类可以重新定义基类中已有的非虚成员函数,这叫隐藏(hiding);对于虚函数,重新定义称为覆盖(override),是实现多态的基础。

cpp

class Base { public: virtual void func() { cout << "Base::func()\n"; } void other() { cout << "Base::other()\n"; } }; class Derived : public Base { public: virtual void func() override { cout << "Derived::func()\n"; } // 覆盖 void other() { cout << "Derived::other()\n"; } // 隐藏 };

5.2override关键字(C++11)

  • 显式标记派生类的虚函数要覆盖基类的虚函数。

  • 如果基类没有对应的虚函数,编译会报错,避免意外创建新函数。

5.3final关键字(C++11)

  • final用在类:禁止其他类继承该类。

  • final用在虚函数:禁止派生类再覆盖该虚函数。

cpp

class Base final { }; // 不可以被继承 class Base2 { virtual void func() final; // 派生类不可覆盖 func };

6. 多继承(Multiple Inheritance)

C++ 支持一个派生类同时继承多个基类。

cpp

class A { }; class B { }; class C : public A, public B { };

6.1 二义性问题

如果多个基类含有同名成员,派生类访问时必须使用作用域运算符明确指定来自哪个基类。

cpp

class A { public: void f(); }; class B { public: void f(); }; class C : public A, public B { void call() { A::f(); // 明确调用 A 的 f B::f(); } };

6.2 菱形继承问题

text

Base / \ A B \ / Derived

如果 A 和 B 都继承自 Base,Derived 多重继承 A 和 B,会导致两份 Base 的副本,访问 Base 成员时产生二义性,并且构造和析构次序也变得复杂。

解决方案:虚继承(virtual inheritance)。

7. 虚继承

虚继承确保在菱形继承中,最顶层的基类只被保留一份共享副本。

cpp

class Base { }; class A : virtual public Base { }; class B : virtual public Base { }; class Derived : public A, public B { };

7.1 构造顺序(虚继承)

  • 虚基类的构造函数优先于非虚基类。

  • 虚基类只在最终派生类的构造函数中被初始化一次。

  • 所有虚基类按照它们出现在继承列表中的顺序初始化,再按普通继承顺序。

7.2 虚继承的代价

  • 增加了间接访问和内存开销(通常通过虚基类指针表)。

  • 构造和析构规则复杂,应谨慎使用。

8. 继承与多态

  • 多态:基类指针或引用指向派生类对象,通过虚函数调用实际类型的成员。

  • 虚函数表(vtable):每个含有虚函数的类都有一个虚函数表,通过虚指针(vptr)访问。

  • 纯虚函数与抽象类

    • virtual void func() = 0;表示纯虚函数。

    • 含有纯虚函数的类称为抽象类,不能实例化。

    • 派生类必须实现所有纯虚函数,否则也是抽象类。

cpp

class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { /* 绘制圆形 */ } };

9. 继承与访问控制总结

成员访问性在类内部在派生类内部(public 继承)在外部(通过对象)
private
protected
public
  • protected成员的引入正是为了支持继承:允许派生类访问,但禁止外部访问。

  • 在类的设计中,通常将数据成员设为 private,而将需要被派生类定制的方法设为 protected 或 public virtual。

10. 最佳实践与建议

  1. 优先使用 public 继承,只有当确实需要实现层面的隔离时才考虑 private/protected 继承。

  2. 析构函数应为 virtual:如果通过基类指针删除派生类对象,基类的析构函数必须是虚函数,否则会产生未定义行为(只调用基类析构函数)。

  3. 避免过深的继承层次:多层继承增加理解难度和耦合度,优先考虑组合而不是继承(Composition over Inheritance)。

  4. 使用overridefinal:明确虚函数覆盖意图,增加代码安全性。

  5. 尽量不使用多继承,除非必要,如果必须使用,注意避免菱形继承和名字冲突。

  6. 虚继承只在解决菱形继承时使用,不要滥用。

11. 一个完整示例

cpp

#include <iostream> using namespace std; class Animal { public: Animal(const string& n) : name(n) {} virtual void speak() const { cout << "Animal " << name << " speaks." << endl; } virtual ~Animal() = default; // 虚析构 protected: string name; }; class Dog : public Animal { public: Dog(const string& n) : Animal(n) {} void speak() const override { cout << "Dog " << name << " barks." << endl; } }; class Cat : public Animal { public: Cat(const string& n) : Animal(n) {} void speak() const override { cout << "Cat " << name << " meows." << endl; } }; int main() { Animal* zoo[2]; zoo[0] = new Dog("Buddy"); zoo[1] = new Cat("Kitty"); for (auto a : zoo) a->speak(); for (auto a : zoo) delete a; return 0; }

输出:

text

Dog Buddy barks. Cat Kitty meows.

12. 总结

C++ 的继承机制功能强大而灵活,它支持单继承、多继承、虚继承,并提供了不同的访问控制级别。正确使用继承可以提高代码的复用性和可扩展性,但滥用继承会导致设计复杂、维护困难。理解继承的底层语义(尤其是对象模型、构造/析构顺序、虚函数机制)是成为 C++ 高手的重要一步。

在实际工程中,应牢记:组合优先于继承,继承用于表达稳定的“is-a”关系;当关系不明确时,组合往往带来更低的耦合。

http://www.jsqmd.com/news/761739/

相关文章:

  • SBP预训练技术:合成数据优化与低资源场景实践
  • 手机生成动态漫工具2026推荐,助力高效创作动态漫
  • PHP扩展加固不是选配,是生存刚需:基于200+企业渗透报告的加固优先级矩阵(含SOP执行表)
  • 2026免费GEO监测工具|AI搜索优化必看清单
  • 2026广东酒店管理系统哪家权威:广东酒店管理软件、广东酒店系统、成都RMS酒店管理系统、成都智慧酒店数字化转型方案选择指南 - 优质品牌商家
  • VTAM视频时序预测模型:原理、优化与工业实践
  • 终极3D模型转Minecraft建筑神器:ObjToSchematic完全使用指南
  • 3D高斯表示技术:从视频到3D场景的自动生成
  • 约鲁巴语讽刺检测数据集构建与应用
  • 安全施工日志软件适合哪些工程企业?先看安全是不是要放到一条业务线上
  • 容器云部署与应用实战:从云主机创建到 Docker 私有仓库全流程
  • 深入解析SimpleMem:C++高性能内存池设计与实战优化
  • 告别画面撕裂!用DRM的drmModePageFlip和drmHandleEvent实现流畅翻页(附Linux应用层完整代码)
  • 体验在低功耗设备上通过统一API调用Claude与GPT模型的便捷性
  • Boardcon LGA3576模块:嵌入式AI与多媒体处理实战解析
  • 【R 4.5深度学习黄金窗口期】:官方尚未文档化的reticulate v1.32.1热修复补丁,解决Python 3.12+R交互段错误(限前500名读者获取)
  • 华为EvoScientist
  • 逆向分析踩坑记:用apktool处理Android 13的APK,如何解决那些奇怪的报错?
  • 告别串口助手手打!用Arduino IDE串口监视器玩转ESP8266 AT指令(附完整指令表)
  • 研究 C 语言的 hello world 输出
  • 教育R语言交互式教学开发黄金法则(2024教育部AI融合教学白皮书认证实践框架)
  • 如何高效使用PyTorch Grad-CAM:研究者的终极实战指南
  • STM32CubeMX生成MDK工程,AC6编译器警告太多?手把手教你精准屏蔽(附AC5/IAR对比)
  • FPGA新手避坑指南:用IBERT IP核实测10G GT收发器眼图(附Xilinx 7系列配置)
  • 别再只用gzip了!实测Vite+Vue项目启用Brotli压缩,打包体积再瘦身30%
  • DCMMS:动态上下文记忆管理系统如何解决大模型对话中的上下文污染与Token浪费问题
  • Arm Cortex-A710处理器MTE与PMU异常问题解析
  • 机器人关节驱动方案:DRV8243与MPQ4436选型实测
  • 提升测试效率:用快马快速构建openclaw等软件的自动化卸载测试工具
  • 语言模型训练与优化实战指南