多态中虚函数的简单理解
来源:互联网 发布:仙知机器人 冯源 编辑:程序博客网 时间:2024/06/10 04:07
虚函数
在之前,我曾经在多态中简单的提到过虚函数,虚函数就是用virtual关键字去修饰类的成员函数,然后将这个函数在派生类中去重新实现。
以代码为例:
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;class B{public:virtual void Test1(int i){cout << "B:Test1()" << endl;}virtual void Test2(int i){cout << "B:Test2()" << endl;}void Test3(int i){cout << "B:Test3()" << endl;}};class C: public B{public:virtual void Test1(int i){cout << "C:Test1()" << endl;}void Test2(int i){cout << "C:Test2()" << endl;}virtual void Test3(int i){cout << "C:Test3()" << endl;}};int main(){B* b = new C;b->Test1(0);b->Test2(0);b->Test3(0);return 0;}
其中Test1就是进行了重写的虚函数,而Test2没有进行重写,Test3则是不是虚函数,所以这里的Test2和Test3都没有实现动态多态,只有Test1实现了动态多态。
之前强调过的我在这里再强调一遍,动态多态有两个条件:
①必须是虚函数;②必须通过基类类型的指针或引用进行调用。
纯虚函数
介绍了虚函数,那么就得提到纯虚函数。纯虚函数就是在成员函数的形参后面写上=0,那么成员函数就是纯虚函数。而包含纯虚函数的类就叫做抽象类(又叫接口类)。但是需要注意的是,抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。上面说了虚函数,大体的解释了一下虚函数的使用方式,但是,在类中使用虚函数的时候,有一些注意事项你还是需要注意的:
①派生类重写基类中的虚函数实现多态,函数名,参数列表,返回值全都要一样。(协变除外,协变是返回值不一样);
②派生类中重写的虚函数要始终保持与基类中虚函数的特性一致;
③只有类中的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数;
④构造函数不能定义为虚函数,但是最好将基类的析构函数声明为虚函数,另外operate=定义为虚函数也是可以的,但是最好不要这样做,容易导致混淆;
⑤不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为;
⑥如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加;
⑦虚表是所有类对象实例共用的。
虚表
既然在上面的注意事项中写到了虚表,那么在这里就得说一下虚表并进行虚表剖析。
对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针。
以代码为例:
class Test{public:Test(){i = 10;cout << "this = " << this << endl;}virtual ~Test(){}private:int i;};int main(){Test t;cout << sizeof(t) << endl;return 0;}
通过这个图片我们可以发现,在创建带有虚函数的类时,编译器会帮助维护一张虚表,其中对象的前四个字节就是指向虚表的指针。
下面我们将分析没有覆盖和有覆盖两种情况。
没有覆盖:
代码:
class Base{public:Base(){ i = 10; }virtual void FunTest0(){ cout << "Base::FunTest0()"; }virtual void FunTest1(){ cout << "Base::FunTest1()"; }virtual void FunTest2(){ cout << "Base::FunTest2()"; }private:int i;};class Derived :public Base{public:virtual void FunTest4(){ cout << "Derived::FunTest4()"; }virtual void FunTest5(){ cout << "Derived::FunTest5()"; }virtual void FunTest6(){ cout << "Derived::FunTest6()"; }};typedef void(*FUN_TEST)();void FunTest(){Base b;cout << "Base vfptr:" << endl;for (int i = 0; i < 3; ++i){FUN_TEST funTest = (FUN_TEST)(*((int*)*(int *)&b + i));funTest();cout << ": " << (int *)funTest << endl;}cout << endl;Derived d;cout << "Derived vfptr:" << endl;for (int i = 0; i < 6; ++i){FUN_TEST funTest = (FUN_TEST)(*((int*)*(int *)&d + i));funTest();cout << ": " << (int *)funTest << endl;}}int main(){FunTest();return 0;}
由代码进行分析:
有覆盖:
代码:
class Base{public:virtual void FunTest0(){ cout << "Base::FunTest0()" << endl; }virtual void FunTest1(){ cout << "Base::FunTest1()" << endl; }virtual void FunTest2(){ cout << "Base::FunTest2()" << endl; }virtual void FunTest3(){ cout << "Base::FunTest3()" << endl; }};class Derived :public Base{public:virtual void FunTest0(){ cout << "Derived::FunTest0()" << endl; }virtual void FunTest1(){ cout << "Derived::FunTest1()" << endl; }virtual void FunTest4(){ cout << "Derived::FunTest4()" << endl; }virtual void FunTest5(){ cout << "Derived::FunTest5()" << endl; }};typedef void(*_pFunTest)();void FunTest(){Base base;for (int i = 0; i < 4; ++i){_pFunTest pFunTest = (_pFunTest)(*((int*)*(int *)&base + i));pFunTest();}cout << endl;Derived derived;for (int i = 0; i < 6; ++i){_pFunTest pFunTest = (_pFunTest)(*((int*)*(int *)&derived + i));pFunTest();}}void TestVirtual(){Base base0;Derived derived;Base& base1 = derived;}int main(){FunTest();TestVirtual();return 0;}由代码进行分析:
下面我再简单剖析一下多重继承中有无虚函数覆盖的情况:
多重继承:没有虚函数覆盖
代码:
class Base0{public:Base0(){ i = 0xB0; }virtual void PrintB0(){ cout << "i = " << hex << i << " Base0::PrintB0()" << endl;}int i;};class Base1{public:Base1(){ i = 0xB1;}virtual void PrintB1(){ cout << "i = " << hex << i << " Base1::PrintB1()" << endl; }int i;};class Base2{public: Base2(){ i = 0xB2; }virtual void PrintB2(){ cout << "i = " << hex << i << " Base2::PrintB2()" << endl;}int i;};class Derived :public Base0, public Base1, public Base2{public:Derived(){ i = 0xD0;}virtual void PrintD(){ cout << "i = " << hex << i << " Derived::PrintD()" << endl;}int i;};typedef void(*VFTABLE_FUN)();void PrintVfPTab(char * _pStr, int *_pVfAddr){cout << _pStr << endl;for (int i = 0;; i++){VFTABLE_FUN pPrintVTab = (VFTABLE_FUN)(*(_pVfAddr + i));if (NULL == pPrintVTab){break;}pPrintVTab();cout << (int*)pPrintVTab << endl;}cout << endl;}void FunTest(){Derived derived;cout << sizeof(derived) << endl;int *pVfTAddr = NULL;Base0& base0 = derived;pVfTAddr = (int*)(*(int *)&base0);PrintVfPTab("Base0 virtual Tab:", pVfTAddr);Base1& base1 = derived;pVfTAddr = (int*)(*(int *)&base1);PrintVfPTab("Base1 virtual Tab:", pVfTAddr);Base2& base2 = derived;pVfTAddr = (int*)(*(int *)&base2);PrintVfPTab("Base2 virtual Tab:", pVfTAddr);pVfTAddr = (int*)(*(int *)&derived);PrintVfPTab("Derived virtual Tab:", pVfTAddr);derived.PrintB0();derived.PrintB1();derived.PrintB2();derived.PrintD();}int main(){FunTest();return 0;}由代码进行分析:
多重继承:有虚函数覆盖
代码:
class Base0{public:Base0(){ i = 0xA0; }virtual void Print(){ cout << "i = " << hex << i << " Base2::Print()" << endl; }int i;};class Base1{public:Base1(){ i = 0xB0; }virtual void Print(){ cout << "i = " << hex << i << " Base2::Print()" << endl; }int i;};class Base2{public:Base2(){ i = 0xC0; }virtual void Print(){ cout << "i = " << hex << i << " Base2::Print()" << endl; }int i;};class Derived :public Base0, public Base1, public Base2{public:Derived(){ i = 0xD0; }virtual void Print(){ cout << "i = " << hex << i << " Derived::Print()" << endl; }int i;};void FunTest(){Derived derived;cout << sizeof(derived) << endl;Base0& base0 = derived;base0.Print();Base1& base1 = derived;base1.Print();Base2& base2 = derived;base2.Print();derived.Print();}由代码进行分析:
注:以上代码全是放在VS2013下进行调试的。
- 多态中虚函数的简单理解
- 简单说说select函数的理解
- Oracle中coalesce函数的简单理解
- R语言函数的简单理解
- 对connect函数的简单理解
- 对super函数的简单理解
- 虚函数简单理解
- 激活函数简单理解
- 带默认参数值的函数的简单理解
- C语言中fpritnf函数的简单理解
- jquery中事件对象、事件处理函数的简单理解
- 【Linux】关于理解fork()函数的简单例子
- 简单粗暴理解map函数
- C++中虚函数的理解,以及简单继承情况下的虚函数的表!
- static的简单理解
- JNDI的简单理解
- JNDI的简单理解
- JNDI的简单理解
- Android嵌入Web页面及缓存的处理
- 求斐波那契数列的第n个数;1,1,2,3,5,8,13,21.....
- hdu 1241(dfs基础题)
- C语言中的一些关键字(十二)
- Android中Fragment的使用技巧
- 多态中虚函数的简单理解
- 1112
- java 用 for do...while 和 while循环求1到100之间的偶数和
- 排序算法总结
- acdream 1025 Transform 简单dp
- 简单时间轴
- jQuery实现放大镜效果
- ARM常用指令
- Linux下将文件打包、压缩并分割成指定大小