面向对象三大特性:封装、继承、多态。
如果说封装让代码更安全,那么继承让代码复用,多态让代码可扩展。
继承和多态是 C++ 面向对象的重中之重、面试必考、工程必用。很多同学学完只会背概念,分不清重写、重载、隐藏,不懂虚函数原理,不知道动态绑定到底是什么。
本文带你从零吃透:继承体系 + 访问权限 + 构造析构规则 + 多态原理 + 虚函数 + 纯虚函数 + 抽象类,全程配图、流程图、可运行代码,一次性搞定所有难点。
一、什么是继承(核心概念)
1.1 生活理解
继承:子类拥有父类的所有属性和行为,并且可以扩展自己的新功能。
父类(基类):通用、抽象、公共代码
子类(派生类):特有、扩展、个性化代码
1.2 继承的作用
代码复用:公共代码只写一次,所有子类直接复用
代码分层:结构清晰,层级明确
为多态做铺垫:没有继承,就没有多态
1.3 继承语法
class 子类名 : 继承方式 父类名 { // 子类新成员 };1.4 三种继承方式(面试重点)
C++ 支持三种继承:public / protected / private
继承方式 | 父类 public | 父类 protected | 父类 private | 使用场景 |
|---|---|---|---|---|
public 公有继承 | public | protected | 不可见 | 日常开发 99% 使用 |
protected 保护继承 | protected | protected | 不可见 | 极少用 |
private 私有继承 | private | private | 不可见 | 几乎不用 |
核心结论:
private 成员永远无法被子类继承访问
想让父类成员子类能用、外部不能用→ 用protected
工程开发只记:一律使用 public 继承
二、继承中的构造与析构顺序
2.1 核心规则
构造函数:先父后子
析构函数:先子后父
2.2 执行流程可视化
2.3 验证代码
#include <iostream> using namespace std; class Parent { public: Parent() { cout << "父类构造" << endl; } ~Parent() { cout << "父类析构" << endl; } }; class Child : public Parent { public: Child() { cout << "子类构造" << endl; } ~Child() { cout << "子类析构" << endl; } }; int main() { Child c; return 0; }输出顺序:
父类构造 → 子类构造 → 子类析构 → 父类析构
三、继承中的同名隐藏(踩坑重点)
3.1 什么是隐藏?
当子类和父类有同名成员函数/变量:
子类函数会隐藏父类同名函数,父类函数在子类中不可见。
3.2 隐藏 vs 重写(提前区分)
隐藏:普通函数,父子同名,不构成多态
重写(覆盖):虚函数,父子同名同参,构成多态
四、多态详解(OOP 灵魂)
4.1 什么是多态?
多态:同一行为,不同表现。
通俗理解:父类指针指向子类对象,调用函数时执行子类的方法。
4.2 多态的成立条件(面试必考四条)
必须同时满足:
存在继承关系
父类有virtual 虚函数
子类重写(override)虚函数
父类指针/引用 指向子类对象
4.3 多态执行原理流程图
4.4 多态完整实战代码
#include <iostream> using namespace std; // 父类 class Animal { public: // 虚函数:支持多态 virtual void speak() { cout << "动物在叫" << endl; } }; // 子类1 class Dog : public Animal { public: // 重写虚函数 void speak() override { cout << "汪汪汪" << endl; } }; // 子类2 class Cat : public Animal { public: void speak() override { cout << "喵喵喵" << endl; } }; // 统一接口,实现多态 void doSpeak(Animal &a) { a.speak(); } int main() { Dog d; Cat c; doSpeak(d); doSpeak(c); return 0; }输出结果:
汪汪汪 、 喵喵喵
关键点:同样的speak()接口,不同对象不同行为,这就是多态。
五、重载、重写、隐藏 终极区别(面试高频)
类型 | 作用域 | 函数签名 | virtual | 是否多态 |
|---|---|---|---|---|
重载 Overload | 同类 | 函数名同,参数不同 | 不需要 | 否 |
隐藏 Hide | 父子类 | 同名即可 | 非虚函数 | 否 |
重写 Override | 父子类 | 完全一致(名/参/返回) | 必须 virtual | 是 |
六、虚函数、纯虚函数、抽象类
6.1 普通虚函数
有默认实现,子类可重写也可不重写。
virtual void func() {}6.2 纯虚函数(重点)
没有实现,强制子类必须重写。
virtual void func() = 0;6.3 抽象类
包含纯虚函数的类就是抽象类。
特性:
无法实例化对象
只能被继承
子类必须实现所有纯虚函数,否则子类也是抽象类
6.4 抽象类实战(接口思想)
#include <iostream> using namespace std; // 抽象父类 class Shape { public: // 纯虚函数 virtual void getArea() = 0; }; class Circle : public Shape { public: void getArea() override { cout << "圆形面积" << endl; } }; class Rect : public Shape { public: void getArea() override { cout << "矩形面积" << endl; } }; int main() { Shape *s1 = new Circle(); Shape *s2 = new Rect(); s1->getArea(); s2->getArea(); delete s1; delete s2; return 0; }七、多态底层原理:虚函数表 vtable
7.1 什么是虚表?
一旦类中出现virtual函数,编译器会自动生成一张虚函数表。
每个对象内存中会多出一个虚表指针 vptr,指向本类的虚表。
7.2 多态调用原理
父类指针指向子类对象
通过对象的 vptr 找到虚表
调用虚表中子类重写后的函数地址
这就是动态绑定(运行时确定调用哪个函数)
7.3 静态绑定 vs 动态绑定
普通函数:静态绑定,编译阶段确定调用地址
虚函数:动态绑定,运行时根据对象真实类型确定
八、继承多态经典坑点:虚析构函数
8.1 问题场景
父类指针 delete 子类对象时:如果析构非虚,只会调用父类析构,子类内存泄漏!
8.2 正确写法
class Parent { public: virtual ~Parent() {} // 虚析构 };工程规范:只要类可能被继承,析构函数一律写 virtual
九、全文总结
1. 继承核心
目的:代码复用、层级抽象
构造:先父后子
析构:先子后父
开发只用public 继承
2. 多态核心
条件:继承 + 虚函数 + 重写 + 父指子对象
本质:动态绑定、虚函数表
价值:接口统一、扩展性极强(开闭原则)
3. 三大函数区别
重载:同域、参数不同
隐藏:父子同名、非虚函数
重写:父子虚函数、完全覆盖 ——唯一多态
4. 工程准则
需要复用 → 继承
需要统一接口、多场景扩展 → 多态 + 虚函数
基类析构必写 virtual
统一规范用 override 显式重写