C++之多态

来源:互联网 发布:西安交通大学软件学院 编辑:程序博客网 时间:2024/04/19 06:22

多态

多态就是指一种事物在不同时期的不同体现。

多态有两张情况,动态多态静态多态

静态多态

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

静态多态的实现方法有两种:

1.重载
#include <iostream>using namespace std;int add(int left, int right){    return left + right;}float add(float left, float right){    return left + right;}int main(){    cout << add(10, 20) << endl;    cout << add(3.13f, 2.14f) << endl;    system("pause");    return 0;}
2.泛型编程

关于泛型编程我还没有了解太多,会在以后的学习中了解并总结。

动态多态

动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
#include <iostream>#include <Windows.h>using namespace std;class WashRoom{public:    void GoToManWashRoom()    {        cout << "Man-->Please Left" << endl;    }    void GoToWomanWashRoom()    {        cout << "Woman-->Please Right" << endl;    }};class Person{public:    virtual void GoToWashRoom(WashRoom & washRoom) = 0;};class Man :public Person{public:    virtual void GoToWashRoom(WashRoom & washRoom)    {        washRoom.GoToManWashRoom();    }};class Woman :public Person{public:    virtual void GoToWashRoom(WashRoom & washRoom)    {        washRoom.GoToWomanWashRoom();    }};void FunTest(){    WashRoom washRoom;    for (int iIdx = 1; iIdx <= 10; ++iIdx)    {        Person* pPerson;        int iPerson = rand() % iIdx;        if (iPerson & 0x01)            pPerson = new Man;        else            pPerson = new Woman;        pPerson->GoToWashRoom(washRoom);        delete pPerson;        pPerson = NULL;        Sleep(1000);    }}int main(){    FunTest();    system("pause");    return 0;}

但是想要实现多态绑定必须满足几点条件:

1.通过基类类型的引用或者指针调用虚函数

对象的类型

静态类型:对象声明时的类型,在编译期间确定。动态类型:目前所指对象的类型,在运行期间确定。
class CBase{};class CDe1:public CBase{};class CDe2:public CBase{};int main(){    //pd1的静态类型是CDe1*,动态类型是CDe1    CDe1* pd1 = new CDe1;    //pb的静态类型是CBase*,动态类型是CDe1    CBase* pb = pd1;    CDe2* pd2 = new CDe2;    //pb的动态类型现在是CDe2    pb = pd2;    system("pause");    return 0;}

2.必须是虚函数并且派生类一定要对虚函数进行重写

class Base{public :   virtual void FunTest1( int _iTest)   {      cout <<"Base::FunTest1()" << endl;   }   void FunTest2(int _iTest)   {      cout <<"Base::FunTest2()" << endl;   }   virtual void FunTest3(int _iTest1)   {      cout <<"Base::FunTest3()" << endl;   }   virtual void FunTest4( int _iTest)   {      cout <<"Base::FunTest4()" << endl;    }};class Derived :public Base{public :   //正确   virtual void FunTest1(int _iTest)   {      cout <<"Derived::FunTest1()" << endl;   }   //错误,基类中不是虚函数,   virtual void FunTest2(int _iTest)   {      cout <<"Derived::FunTest2()" << endl;   }   //错误,不是虚函数   void FunTest3(int _iTest1)   {   cout <<"Derived::FunTest3()" << endl;   }   //错误,参数列表不一样   virtual void FunTest4(int _iTest1,int _iTest2)   {      cout<<"Derived::FunTest4()"<<endl;   }};

在静态多态的实现中有一个重载,在动态多态中有一个重写,我们也就对继承体系中同名成员函数的关系来个小的总结

  1. 重载

     1.在同一作用域中

    2.函数名字要相同,但是参数列表不同
    3.返回值可以不同

  2. 重写

      1.在同一作用域中

    2.函数名字,参数列表和返回值都相同,但是有个特例(协变)
    3.必须有virtual关键字
    4.访问限定符可以不同

  3. 重定义

    1.在不同作用域中
    2.函数名字相同
    3.在基类和派生类中,只要不构成重写的就是重定义。

纯虚函数

 在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类), 抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

在一开始我们定义的washroom类中就有纯虚函数

class Person{public:    //纯虚函数    virtual void GoToWashRoom(WashRoom & washRoom) = 0;};

剖析虚函数列表

#include <iostream>using namespace std;class C{public:    C()    {        i = 10;        cout << "this = " << this << endl;    }    virtual ~C(){};private:    int i;};int main(){    C c;    cout << sizeof(c) << endl;    system("pause");    return 0;}

如果在类中我们定义的不是虚拟析构函数,是普通析构函数的话,类的大小应该为4,就是类C的成员变量i的大小,

这里写图片描述

从图中我们可以看到,此类的大小为8,
在类成员变量的上面多出了4个字节的内容,这和我们之前解决菱形问题二义性引入虚拟继承的类似

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

CTest类的内存布局:

这里写图片描述

这里写图片描述

内存2中的00 00 00 00为虚表的结束标志

单继承下的虚表列表

class Base{public:virtual void FunTest0(){cout<<"Base::FunTest0()";}virtual void FunTest1(){cout<<"Base::FunTest1()";}virtual void FunTest2(){cout<<"Base::FunTest2()";}};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 (*VirtualFunPtr)();void PrintVirtualTable(Base& b, const string& strInfo){cout<< strInfo<<endl;VirtualFunPtr* pVirtualFun = (VirtualFunPtr*)((*( int *)&b));while(*pVirtualFun){(*pVirtualFun)();cout<< ": "<<pVirtualFun<<endl;pVirtualFun++;}cout<<endl;}int main(){Base b;Derived d;PrintVirtualTable(b, "Base Vpf:");PrintVirtualTable(d, "Derived Vpf:");return 0;}

这里写图片描述
基类内存布局
这里写图片描述
基类的虚表
这里写图片描述

派生类的内存布局
这里写图片描述

派生类的虚表
这里写图片描述

由图可知,虚函数是按照其声明顺序存在于虚表中的
在派生类中,前面是基类的虚函数,后面是派生类的虚函数。

这是在没有发生重写的情况下。

下面就来看看重写的情况

lass Base{public:    virtual void FunTest0(){ cout << "Base::FunTest0()"; }    virtual void FunTest1(){ cout << "Base::FunTest1()"; }    virtual void FunTest2(){ cout << "Base::FunTest2()"; }};class Derived :public Base{public:    virtual void FunTest4(){ cout << "Derived::FunTest4()"; }    virtual void FunTest1(){ cout << "Derived::FunTest1()"; }    virtual void FunTest6(){ cout << "Derived::FunTest6()"; }};typedef void(*VirtualFunPtr)();void PrintVirtualTable(Base& b, const string& strInfo){    VirtualFunPtr* pVirtualFun = (VirtualFunPtr*)((*(int *)&b));    while (*pVirtualFun)    {        (*pVirtualFun)();        cout << ": " << pVirtualFun << endl;        pVirtualFun++;    }    cout << endl;}int main(){    Base b;    Derived d;    PrintVirtualTable(b, "Base Vpf:");    PrintVirtualTable(d, "Derived Vpf:");    system("pause");    return 0;}

这里写图片描述

多继承的情况

using namespace std;class Base1{public:    Base1(){ _b1 = 1; }    virtual void PrintBase1(){ cout << "Base1::PrintBase1()"; }    int _b1;};class Base2{public:    Base2(){ _b2 = 2; }    virtual void PrintBase2(){ cout << "Base2::PrintBase2()"; }    int _b2;};class Derived :public Base1, public Base2{public:    Derived(){ _d = 3; }    virtual void PrintDdrived(){ cout << "Derived::PrintDerived()"; }    int _d;};typedef void(*VirtualFunPtr)();void PrintVt(Base1& b, const string& strInfo){    VirtualFunPtr* pVirtualFun = (VirtualFunPtr*)((*(int *)&b));    while (*pVirtualFun)    {        (*pVirtualFun)();        cout << ": " << pVirtualFun << endl;        pVirtualFun++;    }    cout << endl;}int main(){    Base1 b1;    Base2 b2;    Derived d;    PrintVt(b1, "Base Vpf:");    PrintVt(d, "Derived Vpf:");    return 0;}

其内存布局

这里写图片描述

这里写图片描述

从图中我i们可以看到,派生类的虚表是接在第一个继承的基类的后面的。