一,虚函数一般实现模型:
每一个类只要含有虚函数,就会创建一个虚函数表,里面包含的虚函数的地址,每个类对象里面包含一个指针(vptr)指向这个虚函数表。(ps基于主流编译器的,标准并未规定)
下面我们来测试一下:
#include<iostream> using namespace std; class A { private: int va; //4 int vb; //4 }; class B { public: virtual void foo(){}; private: int va; //4 int vb; //4 }; class C { public: virtual void foo(){}; virtual void g(){}; private: int va; //4 int vb; //4 }; int main() { A a; B b; C c; cout<<"a:"<<sizeof(a)<<endl; cout<<"b:"<<sizeof(b)<<endl; cout<<"c:"<<sizeof(b)<<endl; return 0; }
上述例子可以看出,首先没有虚函数的类的对象里面没有额外花销;其次添加了了虚函数之后类对象内部就添加了一个我们上述说道的指针;最后继续添加虚函数并不会继续改变类对象的大小。
二.多态(简单提一下重点不在它):
多态表示一下基类的指针或者引用指向一个派生类的对象,然后通过基类指针或引用调用基类中定义的函数时,我们并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的。
识别一个类是否支持多态,唯一适当的方法就是看看他是否有任何虚函数:只要类拥有一个虚函数,他就需要这份额外的执行期信息。
如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。
三.单一继承下的虚函数:
一个类只会有一个虚函数表,每个表内含其对应的类对象中所有虚函数的地址。
首先我们先学一下取虚函数表地址的过程:
1,一般指向虚函数表的指针(vptr)都是放在对象的首部,所以我们对类对象取地址则是&vptr
2,我们对取得地址转换为指针类型,然后解引用,得到vptr也就是虚函数表的地址。
取得了虚函数表的地址我们就可以对虚函数进行操作了,因为虚函数表里面存的都是虚函数的地址。
#include<iostream> using namespace std; class A { public: virtual void foo(){ cout << "virtual foo function" << endl; }; virtual void goo(){ cout << "virtual goo function" << endl; }; }; typedef void(*F)(void); //结合右左法则很好理解 ,F是一个函数指针。 int main() { A a; int **p = (int**)&a; cout << p << endl; //取vptr的地址及虚函数指针的地址 cout << p[0] << endl; //虚函数指针,由于虚函数指针指向虚函数表,所以它是虚函数表的地址 cout << p[0][0] << endl; //虚函数表里面存的虚函数的地址 F f1 = (F)(p[0][0]); //f1指向虚函数A::foo f1(); F f2 = (F)(p[0][1]);//f2指向虚函数A::goo f2(); return 0; }
下面我们对继承里面虚函数地址的变化进行研究:
#include<iostream> using namespace std; class A { public: virtual void foo(){ cout<<"virtual foo function"<<endl; }; virtual void goo(){ cout<<"virtual goo function"<<endl; }; }; class B:public A { public: void goo(){ cout<<"1"<<endl;} //对于基类的虚函数进行修改 virtual void hoo(){ cout<<"virtual hoo function"<<endl;} }; int main() { A a; B b; int **p = (int**)&a; int **q = (int**)&b; cout<<"A的虚函数表地址"<<p[0]<<endl; cout<<"A::foo地址"<<p[0][0]<<endl; cout<<"A::goo地址"<<p[0][1]<<endl; cout<<"B的虚函数表地址"<<q[0]<<endl; cout<<"B::foo地址"<<q[0][0]<<endl; cout<<"B::goo地址"<<q[0][1]<<endl; cout<<"B::hoo地址"<<q[0][2]<<endl; return 0; }
对于一个派生类来说它的虚函数包含的地址有三种可能性:
1,派生类直接继承基类的虚函数而不修改,则该函数的地址会从基类的虚表直接拷贝到派生类的虚表里面;
2,派生类从基类继承的虚函数并且进行了修改,这部分虚函数在派生类虚表里面有了新的地址,和基类的不同;
3,派生类自己声明的虚函数,这些函数都是新的函数地址。
四,多重继承的虚函数:
class A { public: virtual void foo(){ cout<<"virtual foo function"<<endl; }; private: int va; }; class B { public: virtual void hoo(){ cout<<"virtual hoo function"<<endl;} private: int vb; }; class C:public A,public B { public: virtual void goo(){ cout<<"virtual goo function"<<endl; } private: int vc; };
多重继承有多个虚函数表,一般多了n-1个虚函数表针对每一个虚函数表都有一个指针指向它,当派生类对基类的虚函数进行重写时,派生类的函数覆盖基类的函数在对应的虚函数位置,当派生类有新的虚函数时,这些虚函数被加在第一个虚函数表的后面。