多态:
C++实现多态的方法主要包括虚函数、纯虚函数和模板函数
其中虚函数、纯虚函数实现的多态叫动态多态,模板函数、重载等实现的叫静态多态。
区分静态多态和动态多态的一个方法就是看决定所调用的具体方法是在编译期还是运行时,运行时就叫动态多态。
虚函数:
声明:在基类中声明函数为 virtual
。重写:在派生类中重写基类的虚函数。
当我们使用基类指针或引用指向派生类对象时,通过虚函数的机制,可以调用到派生类中重写的函数,从而实现多态。
构造函数为什么不能是虚函数?
构造函数负责初始化类的对象,每个类都应该有自己的构造函数。
语法:在派生类中,基类的构造函数会被自动调用,用于初始化基类的成员。因此,构造函数没有被覆盖的必要,不需要使用虚函数来实现多态。
虚表:虚函数使用了一种称为虚函数表(vtable)的机制。然而,在调用构造函数时,对象还没有完全创建和初始化,所以虚函数表可能尚未设置。这意味着在构造函数中使用虚函数表会导致未定义的行为。
基类析构函数为什么需要是虚函数?
如果基类析构函数没有定义为虚函数,那么在用基类指针或者引用指向派生类对象,析构时只会调用基类析构函数。定义为虚析构,就会先调用派生类析构函数,再调用基类析构函数。
纯虚函数:
声明:使用 =0
声明一个纯虚函数。含义:含有纯虚函数的类称为抽象类,不能实例化。
纯虚函数是一种在基类中声明但没有实现的虚函数。它的作用是定义了一种接口,这个接口需要由派生类来实现。
包含纯虚函数的类称为抽象类(Abstract Class)。抽象类仅仅提供了一些接口,但是没有实现具体的功能。作用就是制定各种接口,通过派生类来实现不同的功能,从而实现代码的复用和可扩展性。
虚函数表:
每个拥有虚函数的类都有一个虚函数表,这个表包含了指向每个虚函数最具体的版本的指针。
虚函数表指针指向一个数组,数组的元素就是函数指针,指向各个虚函数的地址,通过函数的索引,我们就能直接访问对应的虚函数。
虚函数表指针(vptr)通常位于对象的起始位置。这意味着它是对象内存块中的第一个成员。
每个派生类对象都有一个vptr,指向其自己的虚函数表。当通过基类指针调用派生类重写的虚函数时,使用的是派生类对象的虚函数表指针(vptr)。
当派生类重写基类的虚函数时,派生类的虚函数表会包含指向派生类版本的函数指针,而不是基类的版本。这样,即使通过基类的指针或引用调用虚函数,也会调用到派生类中重写的版本。
虚继承:
虚继承(Virtual Inheritance)是C++中的一种多重继承机制,用于解决多重继承中的菱形继承问题(钻石继承问题)。在没有虚继承的情况下,如果两个基类都继承自同一个基类,那么派生类会从两个基类中继承两份基类的成员,这可能导致数据冗余和不一致性。
Base
/ \
/ \
Derived1 Derived2
\ /
\ /
Derived
使用虚继承,Derived就只会继承一份Base类成员。
虚基类指针:
每个使用虚继承的派生类对象都会包含一个虚基类指针(vbptr)。这个指针指向一个指针数组,该数组包含了虚基类的地址和可能的其他信息(如偏移量)。vbptr 允许派生类对象访问其虚基类部分。
内存布局
使用虚继承时,虚基类部分在内存中只有一个实例,并且位于派生类对象的起始位置。基类和派生类的数据成员会按照声明的顺序排列在虚基类部分之后。
vbptr的作用
定位虚基类:vbptr 允许派生类对象定位其虚基类部分的位置。
访问虚基类成员:通过vbptr,可以间接访问虚基类的成员。
解决二义性:确保虚基类在派生类对象中只有一个实例,避免二义性。