1. 多态的实现原理
(1) 虚函数
虚函数是一种允许在继承结构中通过基类指针或引用调用派生类重写函数的成员函数,是运行时多态的基础。
虚函数的调用依赖于实际对象类型,不是指针类型:
Base* p = new Derived();
p->speak(); // 调用 Derived::speak(动态绑定)
如果没有virtual,调用的将是静态类型版本:
class Base { public: void speak() { std::cout << "Base\n"; } };
class Derived : public Base { public: void speak() { std::cout << "Derived\n"; } };
Base* p = new Derived();
p->speak(); // 输出 Base(静态绑定)
虚函数调用比普通函数调用略慢:因为它多了一次表查找和间接调用。
间接调用:函数地址在运行时才决定,调用时是通过某种中介“地址来源”完成跳转,而不是硬编码的地址。当你用虚函数实现多态时,编译器并不能在编译期决定调用哪个函数,而是通过vtable(虚函数表)查找函数地址。
在继承关系中,C++ 默认使用静态绑定(编译期确定调用哪个函数),这不支持多态。
C++ 中虚函数通过“vtable + vptr”实现动态绑定(运行时多态)机制。
(2)虚函数表
虚函数表是由编译器自动生成和维护的,当一个类中包含虚函数(virtual)时,编译器会在背后为该类生成一个虚函数表(vtable),用于存储虚函数地址。
虚函数表本质上是:函数指针数组
每个类只生成一张虚函数表,它是共享的。
存放位置:虚函数表存放在程序的静态数据区,在程序整个运行期间有效。
(3)虚指针
每个含虚函数的对象中会隐藏一个指针 vptr,指向所属类的 vtable。
在对象构造时,vptr 会被初始化指向正确的 vtable。
调用虚函数时,就是通过 vptr 找到 vtable,再取出函数地址进行调用。
虚函数指针当new的时候才产生的
存放位置:对象内部(堆或栈),随对象创建和销毁。
2. 静态绑定和动态绑定
(1)静态绑定
定义:
- 方法的调用在编译时就已经确定。
- 也称为早绑定(Early Binding)或编译时绑定(Compile-time Binding)。
- 适用于重载(Overloading)和非虚方法(Non-Virtual Methods)。
特点:
- 方法调用在编译阶段确定,不会在运行时发生变化。
- 性能更高,因为编译器可以直接优化。
- 适用于非继承关系或不涉及方法重写的情况。
示例(方法重载):
class StaticBindingExample
{
public void Show(int a) => Console.WriteLine("Int version");
public void Show(double a) => Console.WriteLine("Double version");
}
class Program
{
static void Main()
{
StaticBindingExample obj = new StaticBindingExample();
obj.Show(5); // 调用 Show(int a),在编译时就已确定
obj.Show(5.5); // 调用 Show(double a),在编译时就已确定
}
}
在编译阶段,编译器已经确定了调用哪个 Show 方法,所以这是静态绑定。
(2)动态绑定
定义:
- 方法的调用在运行时才确定,而不是在编译时。
- 也称为晚绑定(Late Binding)或运行时绑定(Runtime Binding)。
- 适用于重写(Overriding),即使用
virtual和override关键字的方法。
特点:
- 方法的调用取决于对象的实际类型(而非变量的编译时类型)。
- 运行时决定调用哪个方法,适用于多态。
- 灵活性更高,但性能稍低,因为运行时需要额外的查找开销(通过**虚函数表(VTable)**实现)。
示例(方法重写):
class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal speaks");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Dog barks");
}
}
class Program
{
static void Main()
{
Animal myAnimal = new Dog(); // 父类引用指向子类对象
myAnimal.Speak(); // 运行时决定调用 Dog的Speak()
}
}
在编译时,myAnimal 被视为 Animal 类型,但在运行时,它的实际类型是 Dog,所以调用的是 Dog.Speak()(而不是 Animal.Speak())。
(3)静态绑定 vs 动态绑定
| 对比项 | 静态绑定 | 动态绑定 |
|---|---|---|
| 绑定时机 | 编译时(Compile-time) | 运行时(Runtime) |
| 方法选择 | 取决于方法签名(重载方法) | 取决于对象的实际类型(重写方法) |
| 适用范围 | 方法重载(Overloading)、非虚方法 | 方法重写(Overriding)、虚方法 |
| 关键字 | 无需 virtual 或 override | 需要 virtual 和 override |
| 性能 | 更快(编译器直接优化) | 稍慢(依赖虚函数表,运行时查找) |
| 示例 | 方法重载(Overloading) | 方法重写(Overriding) |

Leave a comment