C++ 多态详解:从概念到实现原理
本文系统梳理了 C++ 多态的概念、实现条件及核心机制。内容包括虚函数定义与重写规则、override 和 final 关键字用法、抽象类与接口继承、多态底层原理(虚函数表与虚指针)、静态与动态多态的区别,以及多继承场景下的虚表结构。此外,还总结了常见的面试题,如析构函数是否可为虚函数、静态成员能否为虚函数、sizeof 计算等,帮助开发者深入理解多态本质并应用于实际开发。

本文系统梳理了 C++ 多态的概念、实现条件及核心机制。内容包括虚函数定义与重写规则、override 和 final 关键字用法、抽象类与接口继承、多态底层原理(虚函数表与虚指针)、静态与动态多态的区别,以及多继承场景下的虚表结构。此外,还总结了常见的面试题,如析构函数是否可为虚函数、静态成员能否为虚函数、sizeof 计算等,帮助开发者深入理解多态本质并应用于实际开发。

多态是面向对象编程的三大核心特性(封装、继承、多态)之一,它使得同一接口可以呈现出不同的行为,极大地提升了代码的灵活性和可扩展性。在 C++ 中,多态的实现与虚函数、虚表等机制紧密相关,其底层逻辑涉及编译期与运行期的不同处理方式。
本文将系统梳理 C++ 多态的概念、实现条件、核心机制(虚函数与虚表),并深入解析多态在继承场景下的表现,同时结合典型问题与示例代码,帮助读者全面理解多态的本质与应用。
通俗来说,多态即多种形态,具体指完成某个行为时,不同的对象会产生出不同的状态,从而实现更灵活和方便的调用。
被 virtual 修饰的类成员函数称为虚函数。
派生类中有一个跟基类完全相同的虚函数(即返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写或者覆盖了基类的虚函数。
例外情况:
virtual(建议加上)。析构函数是否为虚函数? 析构函数可以是虚函数。如果基类析构函数声明为虚函数,则构成虚函数重写。这是为了防止通过基类指针删除派生类对象时,只调用基类析构函数而导致资源泄漏。
注意:多态调用看的是指向的对象,普通的调用看的是当前的类型。
为什么必须使用父类的指针或引用?
子类虚表的构建: 子类继承父类时,会先复制一份父类的虚表。如果子类没有重写父类的虚函数,虚表中对应函数指针指向父类实现;若子类重写了某个虚函数,则用子类自己的虚函数地址覆盖虚表中对应的函数指针。
virtual void text() final {} // 前有无 virtual 不重要
class Person {
public:
virtual void text() {}
};
class Student : public Person {
public:
virtual void text() override {}
};
| 特性 | 重载 (Overload) | 覆盖/重写 (Override) | 隐藏 (Hide) |
|---|---|---|---|
| 发生范围 | 同一类中 | 基类与派生类之间 | 基类与派生类之间 |
| 函数名 | 相同 | 相同 | 相同 |
| 参数列表 | 不同 | 相同 | 任意 |
| virtual | 无关 | 必须有 | 可有可无 |
| 返回类型 | 无关 | 需符合协变规则 | 无关 |
在虚函数后面写上 =0,则该函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
作用: 强制要求派生类重写虚函数,体现接口继承关系。
包含虚函数的类会有虚函数表指针,虚函数表指针指向的是虚函数表的地址。虚函数表里面存了虚函数的指针。同一个类的所有实例对象共享同一个虚函数表。
注意:VS 编译器的虚表指向的地址后面通常会有 0 作为结束标记。但在增量编译后可能消失,需清理解决方案或重新生成。
虚表本质上是函数指针数组。
typedef void(*FUNC_PTR)();
void PrintVFT(FUNC_PTR* table) {
for (size_t i = 0; table[i] != nullptr; i++) {
printf("[%d]:%p->", i, table[i]);
FUNC_PTR f = table[i];
f();
}
printf("\n");
}
int main() {
Student st;
int vft2 = *((int*)&st);
PrintVFT((FUNC_PTR*)vft2);
return 0;
}
注意:成员变量的变化可能会导致虚表的打印出错,因为可能会影响内存布局。虚表和虚基表都是在编译阶段生成的,对象实例化之后才会与虚表有联系(通过虚表指针)。
核心的实现机制就是虚函数表和虚指针。满足多态的话,子类的虚指针指向的虚表中的虚函数就会覆盖父类的虚函数的地址,然后调用的就是子类的虚函数了。
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
int main() {
Derive d;
cout << sizeof(d) << endl; // X86 环境下,这个占 20 个字节,组成:两个基类 (都是一个虚表指针加一个成员变量) 加一个成员变量
Base1* ptr1 = &d;
ptr1->func1();
Base2* ptr2 = &d;
ptr2->func1(); // 通过修正 this 指针,来让 this 指针指向派生类的头
return 0;
}
注意:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中(其实是末尾)。Base2 中 func1 的地址不一样是为了 jmp 去修正 this 指针的位置。
class A {
public:
static A CreateObj() {
return A();
}
private:
A() {}
};
final (C++11)。
class A final {}
常考笔试题:sizeof(Base) 是多少?(X86 环境下)
class Base {
public:
virtual void Func1() { cout << "Func1()" << endl; }
private:
char _b = 1;
};
int main() {
cout << sizeof(Base) << endl; // 答案:8 个字节
return 0;
}
注意:类里面还有一个虚函数表指针 (
_vfptr),所以即使只有一个char成员,大小也会受对齐和虚表指针影响。
class A {
public:
virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
virtual void test() { func(); }
};
class B : public A {
public:
void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main() {
B* p = new B;
p->test();
return 0;
}
结果输出 B->1。这是因为三同里面的形参相同只用形参的类型相同就行,缺省参数和名字可以不同。在 test() 中调用 func() 时,由于 this 不是父类指针,不构成多态,使用的是静态绑定的缺省值。如果把 test() 放在了 B 里面,就应该输出 B->0 了。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online