虚函数是一个使用virtual关键字声明的成员函数,在基类中声明虚函数,在子类中可以使用override重写该函数。虚函数根据指针或引用指向的实际对象调用,实现运行时的多态。
虚函数表(虚表)是一个用于存储虚函数地址的数组,每个包含 虚函数的类都存在一个虚函数表,编译器会为每一个包含虚函数的类分配一根虚函数指针(vptr),虚函数指针指向虚函数表,在运行时会根据虚函数指针找到虚函数表,然后根据虚函数的索引找到该虚函数的地址,然后调用函数。虚函数表之中的虚函数是按照类之中的虚函数的顺序排列的。当对象初始化时,虚指针会被初始化指向该类的虚函数表。
在C++之中虚函数使用virtual关键字声明,重写该方法是可以使用override显式地声明重写该虚方法,但是在C#之中是强制要求书写override重写方法的。
在C++之中由于虚函数的调用主要操控虚指针找到虚函数表对虚函数进行操作的,这一操作相对比较直接,所以性能较高,特别是对于一些性能要求比较高的场景之中。而在C#之中,虚函数的调用由托管空间管理,性能比较低,但是这种性能差异在场景之中并不明显。
在C++之中存在多继承,虚函数的实现就较为复杂,可能涉及多个虚函数表和虚函数指针,但是在C#之中不存在多继承,但是可以多继承接口,由于接口只是声明了方法而没有实现方法体,类实现接口时也会基于运行时多态决定实现类的哪个接口方法,所以C#之中的虚函数与接口的机制是并非完全独立的。在C++之中不存在默认的虚函数,在C#之中也不存在默认的虚函数,但是如果C#中的类被声明成abstract,那么该抽象类之中的抽象方法默认都被认为是虚的,需要在子类中进行重写,不过抽象类中也可以存在非抽象的,非虚的,以及非抽象虚的方法。
举例:
#include<iostream>
using namespace std;
class TestBase{
public:
virtual void Speak(){
cout<<"TestBase"<<endl;
}
};
class Test:public TestBase{
public:
void Speak()override{
cout<<"Test"<<endl;
}
};
int main(){
Test test;
TestBase* testBase=&test;
testBase->Speak();
}