前言:多态是C++面向对象设计的核心支柱之一,它让代码获得了优雅的灵活性和扩展性。然而,当你在代码中写下virtual关键字时,编译器背后究竟做了什么?虚函数表如何悄无声息地支撑起动态调用的魔法?运行时类型识别(RTTI)又如何在安全性与性能之间权衡?
本文将带你从编译器的视角出发,层层剖析多态的实现机制。我们将从虚函数表的内存布局开始,探讨其如何实现动态绑定;接着深入RTTI的底层原理,分析typeid和dynamic_cast的开销与适用场景;最后结合现代C++特性(如final和override),讨论如何高效且安全地设计多态体系。
目录

1. 多态的概念
多态:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会生出不同的状态。
举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。
所以:
多态的本质:“一种接口,多种实现”

2.多态的定义
C++ 中的多态分类:编译时多态(静态多态) 和 运行时多态(动态多态)
核心区别在于:确定调用哪个实现的时机(编译阶段 vs 运行阶段)
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。
那么在继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
2.1虚函数
想理解这两个条件,我们首先得来了解一下虚函数和重写
虚函数:即被virtual修饰的类成员函数称为虚函数。(virtual 不能和 static 一起使用)
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
2.2虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),必须是三同!,称子类的虚函数重写了基类的虚函数。
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
这样使用*/
/*void BuyTicket() { cout << "买票-半价" << endl; }*/
};
void Func(Person& p)
{ p.BuyTicket(); }
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
2.3调用虚函数
调用虚函数有两个点:
(1)参数类型必须是基类对象
(2)参数必须是引用或者指针
#include <iostream>
using namespace std;
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual ~Person() {} // 建议添加虚析构函数
};
class Student : public Person {
public:
virtual void BuyTicket() override { cout << "买票-半价" << endl; }
};
int main() {
// 场景1:基类指针指向派生类对象
Person* p1 = new Student();
p1->BuyTicket(); // 输出:"买票-半价"(多态调用)
// 场景2:基类对象直接调用
Person p2;
p2.BuyTicket(); // 输出:"买票-全价"
// 场景3:派生类对象调用
Student s;
s.BuyTicket(); // 输出:"买票-半价"
delete p1; // 释放内存
return 0;
}


最低0.47元/天 解锁文章
162





