基类希望派生类定义适合自己的版本,就把这些函数声明成虚函数
虚函数表(vtable):每个包含虚函数的类都有一个与之关联的虚函数表。虚函数表是一个包含指向类的虚函数实现的指针的数组。当派生类覆盖基类的虚函数时,派生类的虚函数表将包含指向派生类的实现的指针,而基类的虚函数表将包含指向基类实现的指针。
每个包含虚函数的类的对象都有一个指向与其关联的虚函数表的指针。这个指针通常存储在对象内存布局的开始处,以便快速访问。这意味着每个对象都有一个指向它所属类的虚函数表的指针。
当通过基类指针调用虚函数时,编译器生成的代码会首先使用this指针找到对象的虚函数表指针,然后在虚函数表中查找相应的函数指针,最后调用该函数。这个过程在运行时发生,因此允许多态行为。
虚函数的调用在程序运行时(runtime)进行解析。这也被称为动态绑定或运行时多态性。在编译时,编译器不知道应该调用哪个类的虚函数实现,因为具体的实现取决于程序运行时指向的对象类型。
由于虚函数的工作原理,导致下面两个现象
构造函数不能是虚函数:
虚函数的主要目的是实现多态性,允许派生类覆盖基类的实现。然而,在构造对象时,必须明确调用哪个类的构造函数。构造函数是用来初始化对象状态的,包括基类和派生类的成员变量。如果构造函数是虚函数,那么在构造派生类对象时,将不清楚应该调用哪个构造函数来初始化基类部分。这将导致对象状态未定义。
在构造函数执行期间,虚函数表(vtable)尚未完全建立。虚函数表是C++用来实现动态多态性的机制,它存储了指向虚函数实现的指针。在构造函数中,虚函数表会根据当前正在构造的类来设置。因此,在构造基类时,虚函数表将指向基类的实现,而在构造派生类时,虚函数表将更新为指向派生类的实现。如果构造函数是虚函数,将无法保证正确的虚函数表设置,从而导致未定义行为。
析构函数可以(且通常应该)是虚函数,以确保在通过基类指针删除派生类对象时正确调用派生类的析构函数。这样可以确保正确清理派生类对象占用的资源。
由于inline函数在编译时就会被嵌入,它们不能是虚函数,因为虚函数的调用是在运行时解析的
static成员函数没有this指针,它们不能是虚函数,因为虚函数依赖于this指针来访问虚函数表
constructor构造函数是用于初始化对象的状态的,它们不能是虚函数。虚函数是用于实现多态性的,而在构造对象时,需要明确调用哪个类的构造函数来初始化对象
是一种在C++中表示抽象类的方法。抽象类是一种不能实例化的类,它主要用于作为其他类的基类。纯虚函数是一个在基类中声明的虚函数,它没有定义具体的实现,而是强制派生类提供实现。换句话说,包含纯虚函数的类无法创建实例,必须通过其派生类创建实例。
class Animal {
public:virtual void makeSound() const = 0; // 纯虚函数
};class Dog : public Animal {
public:void makeSound() const override {std::cout << "Woof!" << std::endl;}
};class Cat : public Animal {
public:void makeSound() const override {std::cout << "Meow!" << std::endl;}
};int main() {// Animal animal; // 错误:不能创建抽象类的实例Dog dog;Cat cat;dog.makeSound(); // 输出:Woof!cat.makeSound(); // 输出:Meow!return 0;
}
菱形继承(也称钻石继承)会导致派生类具有多个顶级基类的成员变量和成员函数实例,导致编译错误
为了解决菱形继承问题,C++引入了虚拟继承(virtual inheritance)。虚拟继承确保顶级基类在派生类中只有一个实例。要使用虚拟继承,只需在基类前加上virtual关键字