C++的静态绑定和动态绑定、虚函数表的理解
概念
在C++中,静态绑定(Static Binding)和动态绑定(Dynamic Binding)是两种不同的函数调用机制,主要涉及到如何根据对象的类型来选择执行哪个函数的决定过程。这两种机制在多态性的实现中扮演着关键角色。动态绑定是面向对象编程中实现多态性的关键机制,允许代码在更高的抽象层次上运行,提高了代码的复用性和扩展性。而静态绑定则因其高效性,在不需要多态性的情况下,依然是一个很好的选择。
下面我将通过代码来详细说明这个内容。
静态绑定
静态绑定,也称为早期绑定,指的是函数调用在编译时期就已经确定了。在这种情况下,调用哪个函数是根据指针或引用的 静态类型(即编码时指定的类型,而不是运行时的实际类型) 来决定的。静态绑定适用于非虚函数(non-virtual functions)。
- 优点:效率高,因为函数调用的决策在编译时就已经完成了,不需要在运行时进行查找。
- 缺点:缺乏灵活性,不能根据对象的实际类型来调用对应的函数,不利于实现多态性。
动态绑定
动态绑定,也称为晚期绑定,是指函数调用在运行时确定。这通常是通过虚函数(virtual functions)机制来实现的。当一个函数在基类中被声明为虚函数后,派生类可以重写(Override)这个函数,创建自己的实现。如果通过基类的指针或引用调用这个函数,那么实际调用的版本 将根据对象的实际类型(运行时的类型) 来确定。
- 优点:提供了灵活性,允许多态性的实现。可以根据对象的实际类型调用相应的函数,即使是在基类的指针或引用上调用。
- 缺点:效率相对较低,因为需要在运行时进行类型检查和函数查找。
注意点
什么是绑定
"绑定"指的是函数调用与函数实现之间的关联,在C++中,这种绑定可以是静态的(编译时决定)或动态的(运行时决定)。
- 静态绑定发生在一个函数调用与一个函数实现之间。这里,函数的调用是基于调用对象的静态类型(即声明时的类型)来决定的。也就是说,编译器在编译时就决定了会调用哪个函数;
- 动态绑定发生在一个虚函数调用与多个可能的函数实现之间。在这种情况下,函数的调用是基于调用对象的实际类型(即运行时对象的类型)来决定的。也就是说,运行时系统(而不是编译器)在运行时决定了会调用哪个函数。
静态绑定和动态绑定如何判断
不加virtual:静态绑定
当一个成员函数没有被声明为virtual时,其绑定方式是静态的。这意味着函数调用会在编译时解析,基于对象的静态类型(即代码中声明的类型)。这种方式适用于大多数普通函数调用,它能提供更高的执行效率,因为调用的函数在编译时就已经确定,无需在运行时进行额外的查找。
如果你调用一个非虚函数,C++编译器会根据调用该函数的对象的类型(更准确地说,是指针或引用的类型)来确定应该调用哪个函数。
静态绑定适用于当你不需要多态性,即在编译时就能确定调用哪个函数的场景。
加virtual:动态绑定
当在一个类的成员函数前加上virtual关键字时,你是在告诉编译器:“请在运行时决定调用哪个函数”。这允许所谓的“后期绑定”或“动态绑定”,意味着如果有任何派生类重写了该虚函数,那么通过基类指针或引用调用该函数时,将调用对象实际类型的函数实现,而不是其静态类型的函数实现。(下面会介绍其中的原理——虚函数表)
动态绑定是实现多态性的关键,特别是当你想通过基类的接口来调用派生类的实现时。
关键点
一旦在基类中声明了一个函数为virtual,所有派生类中的同名函数都自动成为虚函数,即使在派生类中没有显式地使用virtual关键字。
构造函数不能是虚函数。静态成员函数也不能是虚函数,因为静态成员函数不依赖于类的任何特定实例。
代码举例
#include <iostream>
class A
{