本文整理自学习笔记,涵盖继承和多态的核心知识点、代码示例及面试高频问题
一、继承
1.1 基本概念
-
定义:派生类(子类)继承基类(父类)的成员(数据 + 函数),实现代码复用和 is‑a 关系
-
访问控制:
-
public继承:基类的public→ 派生类的public,protected→protected,private不可见 -
protected继承:基类的public/protected→ 派生类的protected -
private继承:基类的public/protected→ 派生类的private(常用于实现"用组合代替继承"的变体) -
转化为类外类内可不可用的问题
-
-
默认方式 :
class默认private继承,struct默认public继承
1.2 构造函数与析构函数
-
派生类构造函数必须调用基类构造函数(显式或隐式)
-
构造顺序:先基类 → 后派生类
-
析构顺序:先派生类 → 后基类
-
基类析构函数通常声明为
virtual(见多态部分)
cpp
class Base {
public:
Base(int x) : _x(x) {}
virtual ~Base() {} // 虚析构
private:
int _x;
};
class Derived : public Base {
public:
Derived(int x, int y) : Base(x), _y(y) {}
private:
int _y;
};
1.3 同名隐藏
-
派生类中定义与基类同名的函数(不管参数列表是否相同),会隐藏基类的所有同名函数(不构成重载)
-
若需要基类版本,使用
using Base::func;引入 -
也可再调用时 进行显示调用
cpp
class Base {
public:
void f(int) {}
void f(double) {}
};
class Derived : public Base {
public:
void f(int) {} // 隐藏 Base::f(double)
// using Base::f; // 解除隐藏
};
1.4 继承方式对成员的访问权限
| 基类成员权限 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
private继承一般少见 实践中很少使用
1.5 继承与组合的选择--复用
-
is‑a → 继承(例如 Dog is a Animal)
-
has‑a → 组合(例如 queue的底层实现)
1.6 面试常见考点(继承)
-
派生类构造函数如何初始化基类成员?
在初始化列表中显式调用基类构造函数
-
什么是同名隐藏?如何访问被隐藏的基类成员?
派生类同名函数隐藏基类所有同名函数。用
Base::func()调用,或者再类外显示调用 -
private继承的意义是什么?实现"用组合代替继承"的语法糖,通常用于适配器模式(
std::stack默认继承deque但使用私有继承) -
析构函数是否必须为虚?
见多态部分
二、多态
2.1 静态多态 vs 动态多态
-
静态多态:函数重载、模板(编译期确定)
-
动态多态:通过虚函数和基类指针/引用,运行时确定调用哪个函数
2.2 虚函数-Virtual Function
-
在基类中声明为
virtual,派生类可重写(override--检查重写是否正确) -
通过基类指针或引用调用虚函数时,会调用实际对象类型的版本
-
虚函数表(vtable)实现细节
cpp
class Animal {
public:
virtual void speak() const { cout << "???" << endl; }
virtual ~Animal() {}
};
class Dog : public Animal {
public:
void speak() const override { cout << "Woof!" << endl; }
};
void makeSound(const Animal& a) { a.speak(); }
Dog d;
makeSound(d); // 输出 "Woof!"
2.3 override 与 final 说明符(C++11)
-
override:显式声明重写基类虚函数,如果签名不匹配则编译错误 -
final:禁止派生类重写该虚函数,或禁止类被继承
cpp
class Base {
virtual void f() final;
virtual void g();
};
class Derived : public Base {
void g() override; // OK
// void f() override; // 错误,f 是 final
};
2.4 虚析构函数
- 基类析构函数应为
virtual,否则delete 基类指针时不会调用派生类析构函数,导致内存泄漏
cpp
Base* p = new Derived();
delete p; // 若 ~Base() 非虚,则不会调用 ~Derived()
2.5 纯虚函数与抽象类
-
纯虚函数:
virtual void func() = 0; -
包含纯虚函数的类称为抽象类,不能实例化
-
派生类必须实现所有纯虚函数才能成为非抽象类
-
设置抽象类的一个目的是强制派生类 否则无法实例化对象
2.6 虚函数表(vtable)与内存布局(理解即可)
-
每个多态类有一个虚函数表,对象中有一个虚指针(
_vptr)指向该表 -
派生类虚表先复制基类虚表,再替换被重写的函数指针
2.7 面试常见考点(多态)
-
虚函数是如何实现动态绑定的?
每个对象有一个虚指针(vptr),指向虚函数表(vtable)调用虚函数时通过 vptr 找到对应的函数地址
-
为什么基类析构函数需要是虚的?
避免派生类资源未释放。如果基类析构非虚,
delete base_ptr只会调用基类析构,派生类部分未析构 -
构造函数可以是虚的吗?
不能。虚函数表在构造函数执行完后才建立
-
静态函数可以是虚的吗?
不能。虚函数依赖对象,静态函数属于类
-
override和final的作用?override让编译器检查重写是否正确;final禁止进一步重写或继承--具体情况依照位置 -
什么是纯虚函数?抽象类的作用?
纯虚函数强制派生类提供实现,抽象类用于定义接口,不能创建对象
-
能否在构造函数或析构函数中调用虚函数?
可以,但不会发生多态(调用的是当前类版本的函数,因为派生类部分尚未构造或已经析构)
-
虚函数表在内存中位于哪个区?
通常位于只读数据段(.rodata)或代码段(.rdata),虚指针位于对象内存布局中
三、总结要点
-
继承用于 is‑a 关系,注意访问控制和构造函数调用顺序
-
多态通过虚函数实现,基类析构函数需为虚
-
区分重载、隐藏、重写(覆盖)
-
抽象类用于定义接口,纯虚函数强制派生类实现
-
面试常问虚函数实现原理、虚表位置、构造函数中能否调用虚函数等
我的gitee仓库