多态与继承中多态

来源:互联网 发布:tbc魔兽世界数据库 编辑:程序博客网 时间:2024/06/02 00:05

多态

多态:一词最初来源于希腊语,意思是具有多种形式或形态的情形,在C++语言中多态有着更广泛的含义。

 

【静态多态】

静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推
断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。

静态多态的事例:

int my_add(int a, int b){return a + b;}float my_add(float a, float b){return a + b;}int main(){     cout << my_add(1, 2)<<endl;cout << my_add(1.3f, 1.2f)<<endl;return 0;} 

【动态多态】

动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
使用 virtual 关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。

【动态绑定条件】
1、必须是虚函数
2、通过基类类型的引用或者指针调用虚函数

动态多态的事例:

class Base{public:Base(){}~Base(){}virtual void Printf(){cout<<"Base()"<<endl;}};class Derived:public Base{public:Derived(){}void Printf(){cout<<"Derived()"<<endl;}~Derived(){}};void Funtest(){Base b1;Derived* d1 = (Derived*)(&b1);d1->Printf();}

从这个代码可以看出来,虽然它将基类的对象经过强转赋给了派生类的指针但是 d1->Printf()  还是指向基类的 Printf 函数。是因为
在调用 Printf 时取的还是Base类对象的地址。


1、在派生类中进行重写(覆盖)的虚函数必须满足函数原型相同(参数列表,函数名,返回值类型(协变除外))。

下面的程序能通过编译吗?会打印什么?

class CBase{public :virtual void FunTest1( int _iTest){cout <<"CBase::FunTest1()" << endl;}void FunTest2(int _iTest){cout <<"CBase::FunTest2()" << endl;}virtual void FunTest3(int _iTest1){cout <<"CBase::FunTest3()" << endl;}virtual void FunTest4( int _iTest){cout <<"CBase::FunTest4()" << endl;}};class CDerived :public CBase{public :virtual void FunTest1(int _iTest){cout <<"CDerived::FunTest1()" << endl;}virtual void FunTest2(int _iTest){cout <<"CDerived::FunTest2()" << endl;}void FunTest3(int _iTest1){cout <<"CDerived::FunTest3()" << endl;}virtual void FunTest4(int _iTest1,int _iTest2){cout<<"CDerived::FunTest4()"<<endl;}};int main(){CBase* pBase = new CDerived;pBase-> FunTest1(0 );pBase-> FunTest2(0 );pBase-> FunTest3(0 );pBase-> FunTest4(0 );pBase-> FunTest4(0 , 0);return 0;}



在以上代码中基类和派生类的 Funtest4 函数它们的参数列表中的参数的个数不同,所以 Funtest 函数并没有构成重写所以此时 pBase 所指的函数还是Funtest4(int  _iTest) 但是在传参的过程中给Funtest 函数传递了两个参数所以并不匹配故编译器会报错。

这时将代码改成这样

class CBase{public :virtual void FunTest1( int _iTest){cout <<"CBase::FunTest1()" << endl;}void FunTest2(int _iTest){cout <<"CBase::FunTest2()" << endl;}virtual void FunTest3(int _iTest1){cout <<"CBase::FunTest3()" << endl;}virtual void FunTest4( int _iTest){cout <<"CBase::FunTest4()" << endl;}};class CDerived :public CBase{public :virtual void FunTest1(int _iTest){cout <<"CDerived::FunTest1()" << endl;}virtual void FunTest2(int _iTest){cout <<"CDerived::FunTest2()" << endl;}void FunTest3(int _iTest1){cout <<"CDerived::FunTest3()" << endl;}virtual void FunTest4(int _iTest1,int _iTest2){cout<<"CDerived::FunTest4()"<<endl;}};int main(){CBase* pBase = new CDerived;pBase-> FunTest1(0 );pBase-> FunTest2(0 );pBase-> FunTest3(0 );pBase-> FunTest4(0 );//pBase-> FunTest4(0 , 0);return 0;}

此时可以看出Funtest1 和 Funtest3 函数构成了重写因为 Funtest1 和 Funtest3 函数首先它们都被 virtual 关键字修饰,其次,他们返回值,函数名,还有参数列表都和派生类的函数一样,虽然在 Funtest3 中没有带 virtual 关键字但是只要在基类的函数带上 virtual 关键字派生类可以不用带上 virtual 关键字就可以构成重写( 我认为最好还是写上,这样会比较只管容易辨别),前提是它们的返回值,参数列表,函数名都相同。 这样就显而易见了,因为 Funtest2 函数前面没有加关键字 virtual 尽管基类和派生类的Funtest2 函数都相同但是没有关键字 virtual 就不能构成重写。


2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。

3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。(因为静态成员函数是该类的所

有对象所共享的,可以通过类名和作用域限定符调用,也就是可以不构建对象,但是虚表地址(虚表地址一会在后

面讲)必须要通过对象的地址才能获取。)

4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆

第一、为什么不能把构造函数定义为虚函数,因为,调用虚函数首先必须要有对象,对象通过虚表指针来调用虚函数,而构造函数是创建对象,而对象还没有创建好所以就掉不了虚函数,所以不行。

第二、为什么最好不要讲operator=定义为虚函数呢?首先假如定义两个个对象Base b1,Derived d1;如果重载了辅助运算符,而且虚函数,则d1 = b1;就成立了,但是基类不给派生类赋值;二期各自都有自己的赋值运算符,而成为虚函数还要面临多态的选择。所以最好不要将赋值运算符写成虚函数。


6、不要在构造函数和析构函数中调用虚函数

因为构造函数中调用虚函数,此时对象创建还不完整如果调用构造函数会导致对象可能创建的不完整,而在析构函数

中调用也一样,因为析构函数的功能就是销毁对象,如果调用析构函数这样可能导致对象销毁不完全,从而导致内

存泄漏。

7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一

样,但是构成覆盖,这里编译器做了特殊处理)

可以看下面一个列子

class Base{public:Base(){}~Base(){}};class Derived:public Base{public:Derived(){}~Derived(){}};int main(){Base *p = new Derived;delete p;return 0;}


是在main函数中用基类的指针去操作派是申请了一个派生类大小的空间并将它的地址赋给基类的指针,释放指针P的

过程是:只是调用了基类的析构函数释放了基类的资源,而没有调用派生类的析构函数.一般情况下,这样的删除只能够

删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏.将析构函数声明为虚函数

class Base{public:Base(){cout<<"Base()"<<endl;}~Base(){cout<<"~Base()"<<endl;}};class Derived:public Base{public:Derived(){cout<<"Derived()"<<endl;}~Derived(){cout<<"~Derived()"<<endl;}};int main(){Base *p = new Derived;delete p;return 0;}


所以将析构函数声明为虚函数,此时虽然是基类的指针 p 但是经过重写后p还是指向了派生类的析构函数直接
将对象释放完,这样就不会造成内存泄漏。

8、虚表是所有类对象实例共用的

对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针

class CTest{public:CTest(){iTest = 10;cout<<"this = "<<this<<endl;}virtual ~CTest(){};private:int iTest;};int main(){CTest c1;cout<<sizeof(CTest)<<endl;return 0;}

在上面这张图里,首先我们先拿到 c1的this指针也就是它的地址。然后通过地址我们可以看出在c1成员变量之前

有一个地址,这个地址是存储虚表的地址也就是虚函数的虚表指针,而这个表中就是存放它的虚函数析构函数后

面那4个字节则存储的是成员变量的值10。

多态实现的条件: 
①必须继承; 
②派生类中重写(覆盖)基类中的虚函数。

从前面那些例子中我们可以看出动态绑定时和类型无关,只和绑定的对象有关,根据绑定的对象去调用绑定对象的

类中的虚函数。

多态的调用:
1)如果不是虚函数——>直接调用类中函数; 
2)如果是虚函数: 
①取虚表指针; 
②取该虚函数在虚表中的偏移量; 
③定位虚函数。

1、单继承的初识

class Base{public:virtual void Funtest1(){cout<<"Funtest1()"<<endl;}virtual void Futest2(){cout<<"Funtest2()"<<endl;}};class Derived:public Base{public:virtual void Funtest1(){cout<<"Funtest1()"<<endl;}virtual void Futest2(){cout<<"Funtest2()"<<endl;}};int main(){Derived d1;return 0;}

从上图看出&d1后而在它的地址中只有一个地址是因为基类和派生类都没有成员变量只有虚函数,所以就只存储了

它们的虚表地址。而在这个虚表指针所指的地址中存储这两个函数的地址。

2、单继承的覆盖(重写)

typedef void(*pFun)();//重命名一个函数指针类型void PrintVfptr(pFun* _pPfun){while (*_pPfun){(*_pPfun)();//调用虚函数_pPfun = (pFun*)((int*)_pPfun + 1);}}class Base{public:virtual void Funtest1(){cout<<"Base::Funtest1()"<<endl;}virtual void Futest4(){cout<<"Base::Funtest4()"<<endl;}int _b;};class Derived:public Base{public:virtual void Funtest1(){cout<<"Derived::Funtest1()"<<endl;}virtual void Futest2(){cout<<"Derived::Funtest2()"<<endl;}virtual void Futest3(){cout<<"Derived::Funtest3()"<<endl;}int _d;};int main(){Derived d1;d1._b = 1;d1._d = 2;pFun* pPfun = (pFun*)*(int*)&d1;PrintVfptr(pPfun);return 0;}



派生类的虚函数表的生成过程: 
①先拷贝基类的虚函数表; 
②在派生类中查看,如果派生类中重写了基类的某些虚函数,那就在同位置进行覆盖(使用派生类中重写的虚函数

的地址); 
③跟上派生类自己新定义的虚函数(如果有多个,顺序按照在类中的声明顺序)。

3、多继承

#include "iostream"using namespace std;typedef void(*pFun)();//重命名一个函数指针类型void PrintVfptr(pFun* _pPfun){while (*_pPfun){(*_pPfun)();//调用虚函数_pPfun = (pFun*)((int*)_pPfun + 1);}}class Base1{public:virtual void Funtest1(){cout<<"Base1::Funtest1()"<<endl;}virtual void Futest2(){cout<<"Base1::Funtest2()"<<endl;} virtual void Futest3(){cout<<"Base1::Funtest3()"<<endl;}int _b1;};class Base2{public:virtual void Funtest4(){cout<<"Base2::Funtest4()"<<endl;}virtual void Futest5(){cout<<"Base2::Funtest5()"<<endl;}virtual void Futest6(){cout<<"Base2::Funtest6()"<<endl;}int _b2;};class Derived:public Base1,public Base2{public:virtual void Funtest1(){cout<<"Derived::Funtest1()"<<endl;}virtual void Futest2(){cout<<"Derived::Funtest2()"<<endl;}virtual void Futest5(){cout<<"Derived::Funtest5()"<<endl;}virtual void Futest7(){cout<<"Derived::Funtest7()"<<endl;}int _d;};int main(){Derived d1;d1._b1 = 1;d1._b2 = 2;d1._d = 3;Base1& b1 = d1;pFun* pPfun = (pFun*)*(int*)&b1;PrintVfptr(pPfun);Base2& b2 = d1;pPfun= (pFun*)(*(int*)&b2);PrintVfptr(pPfun);return 0;}


由上个图知道在多继承中派生类中不是虚函数的那个函数会添加在第一个继承的类的虚表的最后。

4、菱形继承

#include "iostream"using namespace std;typedef void(*pFun)();//重命名一个函数指针类型void PrintVfptr(pFun* _pPfun){while (*_pPfun){(*_pPfun)();//调用虚函数_pPfun = (pFun*)((int*)_pPfun + 1);}}class Base{public:virtual void Funtest1(){cout<<"Base1::Funtest1()"<<endl;}int _d;};class Base1{public:virtual void Funtest1(){cout<<"Base1::Funtest1()"<<endl;}virtual void Futest2(){cout<<"Base1::Funtest2()"<<endl;}int _b1;};class Base2{public:virtual void Funtest1(){cout<<"Base2::Funtest1()"<<endl;}virtual void Futest3(){cout<<"Base2::Funtest3()"<<endl;}int _b2;};class Derived:public Base1,public Base2{public:virtual void Funtest1(){cout<<"Derived::Funtest1()"<<endl;}virtual void Futest2(){cout<<"Derived::Funtest2()"<<endl;}virtual void Futest4(){cout<<"Derived::Funtest4()"<<endl;}int _d;};int main(){Derived d1;d1._b1 = 1;d1._b2 = 2;d1._d = 3;Base1& b1 = d1;pFun* pPfun = (pFun*)*(int*)&b1;PrintVfptr(pPfun);Base2& b2 = d1;pPfun= (pFun*)(*(int*)&b2);PrintVfptr(pPfun);return 0;}



由上图知菱形继承中派生类Derived现将Base1类中的虚函数一重写然后把自己的函数加进去,值得一提的是

在Base1虚表后显示将Base1的父类_b的值储存起来然后在储存自己类中的值,在Base2类的虚表后面也一样先是

将Base2父类的值储存然后在储存自己的值最后才储存派生类的值。

5、菱形虚继承

#include "iostream"using namespace std;typedef void(*pFun)();//重命名一个函数指针类型void PrintVfptr(pFun* _pPfun){while (*_pPfun){(*_pPfun)();//调用虚函数_pPfun = (pFun*)((int*)_pPfun + 1);}}class Base{public:virtual void Funtest1(){cout<<"Base1::Funtest1()"<<endl;}int _b;};class Base1:virtual public Base{public:virtual void Funtest1(){cout<<"Base1::Funtest1()"<<endl;}virtual void Futest2(){cout<<"Base1::Funtest2()"<<endl;}int _b1;};class Base2:virtual public Base{public:virtual void Funtest1(){cout<<"Base2::Funtest1()"<<endl;}virtual void Futest3(){cout<<"Base2::Funtest3()"<<endl;}int _b2;};class Derived:public Base1,public Base2{public:virtual void Funtest1(){cout<<"Derived::Funtest1()"<<endl;}virtual void Futest2(){cout<<"Derived::Funtest2()"<<endl;}virtual void Futest3(){cout<<"Derived::Funtest3()"<<endl;}int _d;};int main(){Derived d1;d1._b = 1;d1._b1 = 2;d1._b2 = 3;d1._d = 4;Base1& b1 = d1;pFun* pPfun = (pFun*)*(int*)&b1;PrintVfptr(pPfun);Base2& b2 = d1;pPfun= (pFun*)(*(int*)&b2);PrintVfptr(pPfun);return 0;}


由上图分析可知:将Base类的虚表只保存了一份,也就是说类Base的虚函数只保存了一份,所有类都共享,Derived只将Base1和Base2的一些函数重写了一下。

0 0