c++的多态性可以分为两种:
1.编译时多态:运算符重载和函数重载。这个比较简单,就简单介绍一下,重点是运行时多态。
运算符重载主要运用c++的operator关键字对运算符重新定义:
class test { int a,b; public: test(); test(int a,int b); void operator>>(test &); }; test::test() { a=b=1; } test::test(int a,int b) { this->a=a; this->b=b; } void test::operator >>(test &p) { cout<<p.a<<" "<<p.b<<endl; } int main() { test cout(1,2),b; cout>>b; return 0; }
函数重载:有一点要记住:只有返回值不同的重定义函数是错误的,其它的不多说了。
2.运行时多态:其一虚基类表可以说主要来自处理继承的倒平行四边形问题(套用别人的话。。。),即一个基类有多个子类继承,而多个子类又只由一个孙子类继承。如下面一个例子:
class base { public: int a; }; class d1 :public base { public: int b; }; class d2 :public base { public: int c; }; class d3 :public d1, public d2 { public: int sum; }; int main() { d3 _d3; _d3.a = 10; _d3.b = 20; _d3.c = 30; _d3.sum = _d3.a + _d3.b + _d3.c;//error cout << _d3.sum << endl; }
注释error的地方就是因为_d3.a不明确,因为即可能是d1,d2的也可能是d3的,那么要解决这个问题,有两个方法:
第一个是标明作用域即_d3.d1::a,这个方法比较简单并且基本也不用,不再赘述
第二个就是使用virtual关键字使多类继承只保留一个基类的副本(重点来了!!)
class base { public: int a; }; class d1 :virtual public base { public: int b; }; class d2 :virtual public base { public: int c; }; class d3 :public base { public: int d; }; class d4 :public d1, public d2 { public: int sum; }; int main() { d4 _d4; _d4.a = 10; _d4.b = 20; _d4.c = 30; _d4.sum = _d4.a + _d4.b + _d4.c; cout << _d4.sum << endl;//correct cout << sizeof(d1)<< " " << sizeof(d2) << " " << sizeof(d3) << endl;// 输出结果12 12 8 return 0; }
通过在继承的过程中加上virtual关键字,说明是一个虚基类,虚基类在发生多重继承时,只会保留一个相同的类成员,从而从根本上解决了倒平行四边形效应!
另外对最后一个输出,可以看出来 没有加virtual关键字的类比加了的少4个字节。这是什么原因呢,通过反汇编可以看一下:
可以看出每个类都有虚基类表作为对象的数据成员保存,所以上面的程序执行后都会多出4字节来。
其二虚函数(可以说是运行多态的主要内容了):虚函数 跟函数重载形式上很像,但完全不是一个东西,虚函数必须保证函数名、参数都完全相同,只是函数体不一样而已。
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的,即虚表。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
通过一个例子来分析一下:
class test { public: virtual void vfunc() { cout << "base‘s vfunc" << endl; } }; class d1 :public test { public: void vfunc() { cout << "d1‘s vfunc!" << endl; } }; class d2 :public test { public: void vfunc() { cout << "d2‘s vfunc!" << endl; } }; int main() { _asm { int 3 }//用于调试 test a, *p; d1 b; d2 c; p = &a; p->vfunc(); p = &b; p->vfunc(); p = &c; p->vfunc(); return 0; }
CPU Disasm 地址 十六进制数据 指令 注释 0101273E CC int3 0101273F 8D4D F8 lea ecx, [ebp-8] ; this 指针 a 01012742 E8 40E9FFFF call Demo.01011087 ; test::test 01012747 8D4D E0 lea ecx, [ebp-20] ; this 指针 b 0101274A E8 9FEBFFFF call Demo.010112EE ; d1::d1 0101274F 8D4D D4 lea ecx, [ebp-2C] ; this 指针 c 01012752 E8 96ECFFFF call Demo.010113ED ; d2::d2 01012757 8D45 F8 lea eax, [ebp-8] ; 对象a的地址保存到eax中 0101275A 8945 EC mov dword ptr [ebp-14], eax ; 借助对象指针p保存对象a 0101275D 8B45 EC mov eax, dword ptr [ebp-14] ; 设置当前this指针为对象a的 01012760 8B10 mov edx, dword ptr [eax] ; 虚表’vftable 01012762 8BF4 mov esi, esp 01012764 8B4D EC mov ecx, dword ptr [ebp-14] 01012767 8B02 mov eax, dword ptr [edx] 01012769 FFD0 call eax
通过反汇编可以看出来,调用虚函数主要是通过this指针区分不同的函数,并且虚表在对象数据的首部。
下面来看一下vftable虚表数据结构:
简单来说,继承父类的子类中的虚表算是一个二维数组,盗用一下别人的图,讨论一下多重继承:
三个父类派生出一个子类,其中子类的f()函数覆盖了父类中的f()函数,来看一下子类中的虚表结构:
通过这张图我们可以很清晰的看出来每一个父类都在占有一个数组。对于这个程序再进行一次反汇编:
class Base1 { public: virtual void vfunc() { cout << "base‘s vfunc" << endl; } }; class Base2 { public: virtual void vfunc() { cout << "d1‘s vfunc!" << endl; } }; class Derive :public Base1,public Base2 { public: void vfunc() { cout << "d2‘s vfunc!" << endl; } }; int main() { _asm { int 3 } Derive d; d.vfunc(); return 0; }
通过eax+4可以看出来这个线性结构的递增,即虚表的增加。(这OD突然崩了。崩的莫名其妙。。。本来打算复制过来的,结果只能截屏了,也没办法注释分析了。。。见谅。。)