1、方法重写:子类可以重写父类的函数
2、向上类型转换:用一个父类指针指向子类对象的时候,假如调用的是虚函数,会自动暂时的将该指针转换为子类类型
虚函数的作用就是指针类型转换,但即使没有虚函数也可以重写方法
虚表是什么:只是一个数组,里面的每个元素存放的是虚函数的地址。
虚指针是什么:只是一个指针,指向虚表。
需要特别注意的是:虚表是对于类而言的,虚指针则是针对对象而言的。
也就是说,可以认为虚表内存存在于类内存中(代码区),每一个类只需要一份就可以了。虚指针则存在于对象的内存(堆栈区)里,每一个对象就有一个虚指针。假如某个类实例化了10000个对象,那么虚指针就要占用10000*8字节(假设每个指针占用8字节),而虚表的占用内存则完全不变。
注意:每一个实例化的对象里面都有一个虚指针,要是编译器发现这个对象要调用虚函数,虚指针就会指向该对象类的虚函数表,去寻找虚函
我们只要当p指向父类对象的时候,指向的是父类的vptr;当p指向父类对象的时候,指向的是子类的vptr
虚函数表的具体实现
当一个类里存在虚函数时,编译器会为类创建一个虚函数表vtable,虚函数表是一个数组,数组的元素存放的是类中虚函数的地址。
编译器还会在**对象的存储空间中安插一个指针vfptr,**指向虚函数表数组的起始位置
每一个有虚函数的类都有一个虚函数表,虚函数是整个类所共有的,虚函数表存储在对象内存最开始的位置。如果子类继承了多个父类,并且父类有虚函数,则子类要存储多个虚函数指针。
1、基类base里面最初的虚函数表
2、当子类Node继承基类base但是没有将虚函数重写,Node的虚函数表
3、当子类Node覆盖了基类的虚函数,Node的虚函树表
父类被覆盖的函数f(),都被替换为子类的虚函数f()
//基类
class Base{
public:
virtual void func();
protected:
int m_a;
int m_b;
};
void Base::func(){ cout<<"调用基类"<<endl; }
//派生类
class Derived: public Base{
public:
void func();
private:
int m_c;
};
void Derived::func(){ cout<<"调用子类"<<endl; }
int main(){
Base *p;
int n;
cin>>n;//用户操作不同,p指向的对象类型不同
if(n <= 100){
p = new Base();
}else{
p = new Derived();
}
cout<<typeid(*p).name()<<endl;
return 0;
}
输入 45,运行结果为:
45↙
基类
输入 130,运行结果为:
130↙
子类
同一个虚函数,会根据我们指针指向不同对象去对找到对应的虚函数表,然后绑定我们需要的虚函数