C++多态实现原理探究
多态:是子类对象的地址赋值给父类指针,接收同一消息运行时产生不同方式的响应;
//有虚函数的类A
class A
{
virtual void fun()
{
};
};
//有虚函数的类C
class C
{
virtual void test()
{
};
};
//继承A和C的类B 多继承
class B: public A, public C
{
};
//没有虚函数的类D
class D
{
};
//有函数,但不是虚函数
class E
{
void fun()
{
};
};
//继承有虚函数的类F
class F: public C
{
};
//有成员变量的G
class G
{
int a;
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B b;
C c;
D d;
F f;
cout<<"sizeof(A)"<<sizeof(A)<<endl;//4 有虚函数会产生一个虚函数表,虚函数表中会有一个虚函数指针 占4个字节
cout<<"sizeof(C)"<<sizeof(C)<<endl;//4 同A
cout<<"sizeof(B)"<<sizeof(B)<<endl; //8多继承会有多个虚函数表 包含A和C的虚函数表 总共8个字节
cout<<"sizeof(D)"<<sizeof(D)<<endl;//1 没有虚函数,操作系统分配一个字节的内存
cout<<"sizeof(E)"<<sizeof(E)<<endl;//1 有一个函数,函数不需要内存
cout<<"sizeof(F)"<<sizeof(F)<<endl;//4 继承一个类C 单继承 只有一个虚函数表
cout<<"sizeof(G)"<<sizeof(G)<<endl;//4 有个整型成员变量 占4个字节
system("pause");
return 0;
}
输出结果:
输出结果可以看出,普通的类,不存在虚函数内存为1,当含有虚函数后,就会存在虚函数表,虚函数表中就有虚函数指针占用4个字节的长度,单继承只有一个虚函数表,多继承会有多个虚函数表。
调试可以看出内存结构模型:
从上面内存可以看出没有使用虚函数的类D没有虚函数表和虚函数指针;而继承了类C的F 具有一个虚函数指针,同时继承A和C的类B同时具有A和C的虚函数表和指针;那么派生类自己也存在虚函数时是怎么样的呢?
class H:public A
{
//重写父类虚函数
virtual void fun()
{
};
//自己的虚函数
virtual void testfun()
{
};
};
难道说派生类中的虚函数相当于普通函数吗?派生类中定义的非基类的虚函数,并没有产生新的虚函数表,也没有在基类的虚函数表中存在。
class A
{
public:
virtual void fun()
{
printf("%s\n", __FUNCTION__);
};
virtual void myfun()
{
printf("%s\n", __FUNCTION__);
};
};
class H :public A
{
public:
virtual void fun()
{
printf("%s\n", __FUNCTION__);
};
virtual void testfun()
{
printf("%s\n", __FUNCTION__);
};
void testfun1()
{
printf("%s\n", __FUNCTION__);
};
};
int _tmain(int argc, char* argv[])
{
H h;
// 我是x32编译的,如果是x64程序就把 unsigned int 换成unsigned long long
unsigned int vptr = *((unsigned int *)(&h));
printf("%p\n", vptr);
unsigned int vfun1 = *((unsigned int *)(vptr));
printf("%p\n", vfun1);
unsigned int vfun2 = *((unsigned int *)(vptr) + 1);
printf("%p\n", vfun2);
unsigned int vfun3 = *((unsigned int *)(vptr) + 2);
printf("%p\n", vfun3);
unsigned int vfun4 = *((unsigned int *)(vptr) + 3);
printf("%p\n", vfun4);
typedef void(*Fun)(void);Fun pfun = (Fun)vfun1; //输出 H::fun(); pfun();pfun = (Fun)vfun2; //输出 A::myfun(); pfun();pfun = (Fun)vfun3; //输出 H::testfun(); pfun();system(“pause”);return 0;}
003D7B00 属于虚函数指针起始地址
003D12B7 H::fun地址
003D12C6 A::fun地址
003D12B2 H::testfun地址 说明派生类的虚函数也是保存在那一个虚函数表,只是编译器上没有显示出来而已
(个人观点,如果不对,请指正)
00000000 NULL 本以为这个位置是普通函数的地址,但是普通函数是不会保存在虚函数表中的
派生类的虚函数和普通函数还是有区别的。编译器并不是按照同样的方式处理的。
调试看多态:
class Base
{
public:
virtual void Name()
{
cout<<"Base:: Name()"<<endl;
};
virtual void Age()
{
cout<<"Base :: Age()"<<endl;
};
virtual void Sex()
{
cout<<"Base :: Sex()"<<endl;
};
};
class Derived1 : public Base
{
virtual void Name()
{
cout<<"Derived1:: Name()"<<endl;
};
virtual void Age()
{
cout<<"Derived1 :: Age()"<<endl;
};
virtual void Sex()
{
cout<<"Derived1 :: Sex()"<<endl;
};
};
class Derived2 : public Base
{
virtual void Name()
{
cout<<"Derived2:: Name()"<<endl;
};
virtual void Age()
{
cout<<"Derived2 :: Age()"<<endl;
};
virtual void Sex()
{
cout<<"Derived2 :: Sex()"<<endl;
};
};
class Derived3 : public Base
{
};
int _tmain(int argc, _TCHAR* argv[])
{
Derived1 d1;
Derived2 d2;
Derived3 d3;
Base * base;
Base base1;
base = &d1;
base->Name();
base = &d2;
base->Name();
system("pause");
return 0;
}
从上面看出,基类vfptr指针地址00f07958 派生类 d1 :vfptr指针 00f078b8 ;d2: vfptr指针00f0785c;
从内存上看,如果单纯的基类,虚函数表中的地址是基类函数地址;00f07958 vfptr指针指向到了base1基类的地址
但当基类指针赋值之类对象地址后地址 00f00785c vfptr 指向派生类Derived1类的vfptr指针,虚函数表被替换为Derived1的虚函数表。在编译的时候地址是不确定的,只有在运行时,给父类指针赋值后,指向才会改变,才知道运行时调用那一个函数;
当你将其赋值为另外的子类对象时,相应的地址也会随之改变,可以看到赋值改变后,vfptr改变未00f078b8,为第二个派生类的虚函数指针地址;
输出结果:
我认为多态的原理,就是如果一个类中含有虚函数,那么这个类在实例化的时候就会产生一个虚函数表vftable,虚函数表中有一个虚函数指针vfptr,虚函数在虚函数表中的位置是按照声明时候的顺序确定的;在创建一个指向基类对象指针,这个时候这个指针是什么也没有的,指向NULL, 当你给他赋值某一个子类对象的时候,这个基类指针的vftable就会被替换为赋值后的那个子类的vftable虚函数表,这时候,vfptr指针也指向那个子类的虚函数地址(我认为这个过程叫动态绑定);只时候通过基类指针就能找到派生类函数的地址,运行的时候就不会调用到基类的函数,因为虚函数表已经被替换掉了,通过vfptr指针只能找到派生类的函数地址。