一 。虚函数指针在类的成员变量之前
class A{
public:
int a;
virtual void func(){
cout << "virtual func" <<endl;
}
}
int main(){
A aobj;
char* p1 = reinterpret_cast<char*>(&aobj);
char* p2 = reinterpret_cast<char*>(&(aobj.a));
if (p1 == p2) {
cout << "虚指针在下" << endl;
}
else
{
cout << "虚指针在上" << endl;
}
}
二。 一个类只有包含虚函数才会存在虚函数表,同属于一个类对象共享虚函数表,但是有各自的vptr
指向的虚函数表首地址相同。
父类中有虚函数子类中就有虚函数,父类中有虚函数表子类中也有虚函数表,继承父类的
不管父类还是子类,都只有一个虚函数表,不能认为子类中有一个虚函数表 + 父类中有一个虚函数表
即便子类中的virtual关键字去掉 依然也是虚函数
如果子类中完全没有新的虚函数,则我们可以认为子类的虚函数表和父类的虚函数表相同
但是只是内容相同,这个两个表在内存中处于不同的位置, 这是内容相同的两个表
三。 一个对象 如果它的类有多个基类则有多个虚指针(是两个虚指针,而不是两个虚函数表
在多继承中,对应各个基类的vptr按继承顺序一次防止在类的内存空间中,且子类与第一个基类公用一个vptr
第二个基类有自己的vptr
四。 虚函数表指针什么时候创建出来的
// vptr 跟着对象走 所以对象什么时候创建出来 vptr就什么时候创建出来
// 实际对于这种有虚函数的类,在编译的时候,编译器就会网构造函数中添加vptr赋值的代码
// 当程序运行,遇到创建对象的代码,执行对象的构造函数,那么这个构造函数里有给对象的vprt赋值的语句
五。 虚函数表什么时候创建。
实际虚函数表是编译器在编译期间(不是运行期间 就为每个类确定了对应的虚函数表vtbl的内容
也是编译器在编译期间在对应的类构造函数中添加vprt赋值的代码
当程序运行,遇到创建对象的代码,执行对象的构造函数,那么这个构造函数里有给对象的vprt赋值的语句
六 单纯的类不纯时引发的虚函数调用问题
单纯的类:比较简单的类 尤其不包含虚函数和虚基类
某些情况下编译器回望类内部增加一些我们看不到,但是真实存在的成员变量(隐藏成员变量) 有了这种变量的类
就不单纯了
同时这种隐藏的成员便改良的增加(使用)或者赋值的时机,往往都是在执行构造函数和拷贝构造函数之前进行
这时候代码中使用memset memcpy 这种函数就会把虚指针清零操作,new操作会触发异常
为什么直接定义对象就可以使用?
静态联编 : 我们编译器的时候就能确定调用哪个函数,把调用语句和被调用函数绑定到一起
动态联编 : 在程序运行时,根据具体情况,动态的把调用语句和被调用函数绑定到一起,
动态联编一般只有在多态和虚函数情况下才存在
在直接定义一个对象的时候编译器在进行静态联编 已经确定了具体调用哪个函数 即使没有虚函数表也能正常使用
单纯的调用虚函数 不是多态 只有指针和引用才有多态 所以new会出问题
所以虚函数 多态其实是给函数和指针准备