面向对象C++——多态

来源:互联网 发布:日程计划表软件 编辑:程序博客网 时间:2024/05/18 00:23

多态

   

    静态多态(早绑定)

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

#include <iostream>using namespace std;int Add(int left, int right){return left+right;}char Add(char left, char right){return left+right;}float Add(float left, float right){return left+right;}int main(){cout<<Add(10, 20)<<endl;//调用int Add(int, int)cout<<Add('1', '2')<<endl;//调用char Add(char, char)cout<<Add(1.2f, 3.4f)<<endl;//调用float Add(float, float)system("pause");return 0;}
    运行结果:

     

   动态多态(晚绑定)

          在程序执行期间完成,编译器根据所引用对象的实际类型调用相应的方法。  


C++中实现多态的方式

#include <iostream>using namespace std;class A{public:virtual void a1();virtual void a2();private:int _a;};class B:public A{public:void a1();private:int _b;};int main(){    B b;system("pause");return 0;}


   A类中的a1和a2函数声明为虚函数,编译器为A类准备了一张虚表:

     
   B类继承自A类,所以B类也有一张虚表:

     

    主函数执行到B b;编译器在分配空间时,除了成员int _a和int _b之外,还分配了一个空间来存放虚指针,指向             B类的虚表:

      

纯虚函数

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

#include <iostream>using namespace std;class Person{public:virtual void Display() = 0;//纯虚函数protected:string _name;//姓名};class Student:public Person{};


动态绑定的条件

    必须为虚函数

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

#include <iostream>using namespace std;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 FunTest(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);system("pause");return 0;}
     运行结果:

        

    *分析:FunTest1在基类中是虚函数,在派生类中也对其进行了重写;

                  FunTest2在基类中没有定义成虚函数,所以第二行打印了基类函数体的内容;

                  FunTest3打印了派生类函数体的内容,说明派生类重写基类的虚函数时不需要加virtual关键字;

                  FunTest4形参个数不同,派生类没有对基类进行重写;

    总结:1.派生类重写基类的虚函数实现多态,要求函数名,参数列表,返回值完全相同(协变除外);

                  2.基类中定义了虚函数,在派生类中该函数始终保持虚函数的特征;

                  3.只有类的非静态成员函数才能定义成虚函数,静态成员函数不可以;

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

                  5.构造函数不能定义为虚函数,赋值运算符的重载最好也不要定义成虚函数;

                  6.不要在构造函数和析构函数中调用虚函数,因为在构造函数和析构函数中对象是不完整的;

                  7.最好将基类的析构函数声明为虚函数(因为派生类的析构函数和基类的析构函数名称不同,但是会构                         成覆盖);

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


虚表剖析

#include <iostream>using namespace std;class Base{public:Base(){_data = 10;cout<<"this:"<<this<<endl;}virtual ~Base(){}private:int _data;};int main(){Base b;cout<<sizeof(b)<<endl;system("pause");return 0;}
   运行结果:

    

   分析:根据之前学习的知识,这里的Base类应该占4个字节空间,而实际的输出结果是8。原因是析构函数被定                     义为虚函数,有虚函数的类会多四个字节,存放一个地址,这个地址指向虚表。

   

    将虚表具体分类:

        ①单继承:没有虚函数覆盖

#include <iostream>using namespace std;class Base{public:Base(){_a = 10;}virtual void FunTest0(){cout<<"Base::FunTest0()"<<endl;}virtual void FunTest1(){cout<<"Base::FunTest1()"<<endl;}virtual void FunTest2(){cout<<"Base::FunTest2()"<<endl;}private:int _a;};class Derived:public Base//没有对基类进行重写(没有覆盖){public:virtual void FunTest4(){cout<<"Derived::FunTest4()"<<endl;}virtual void FunTest5(){cout<<"Derived::FunTest5()"<<endl;}virtual void FunTest6(){cout<<"Derived::FunTest6()"<<endl;}};typedef void(*FUN_TEST)();void FunTest(){Base b;cout<<"Base vfptr:"<<endl;FUN_TEST* funtest1 = (FUN_TEST*)(*(int*)&b);  //取虚表内的指针赋给一个函数指针变量//(int*)&b得到虚表地址//解引用得到指针地址while (*funtest1){(*funtest1)();cout <<(int*)funtest1<< endl;      //打印地址++funtest1;                        //函数指针向后偏移}cout<<endl;Derived d;cout<<"Derived vfptr:"<<endl;FUN_TEST* funtest2 = (FUN_TEST*)(*(int*)&b); while (*funtest2){(*funtest2)();cout << (int*)funtest2 << endl;      ++funtest2;                           }}int main(){FunTest();system("pause");return 0;}
             

        ②单继承:有虚函数覆盖

#include <iostream>using namespace std;class Base{public:Base(){_a = 10;}virtual void FunTest0(){cout<<"Base::FunTest0()"<<endl;}virtual void FunTest1(){cout<<"Base::FunTest1()"<<endl;}virtual void FunTest2(){cout<<"Base::FunTest2()"<<endl;}private:int _a;};class Derived:public Base//对FunTest0()和FunTest1()进行了重写(有覆盖){public:virtual void FunTest0(){cout<<"Derived::FunTest0()"<<endl;}virtual void FunTest1(){cout<<"Derived::FunTest1()"<<endl;}virtual void FunTest3(){cout<<"Derived::FunTest3()"<<endl;}};typedef void(*FUN_TEST)();void FunTest(){Base b;cout<<"Base vfptr:"<<endl;FUN_TEST* funtest1 = (FUN_TEST*)(*(int*)&b);  //取虚表内的指针赋给一个函数指针变量//(int*)&b得到虚表地址//解引用得到指针地址while (*funtest1){(*funtest1)();cout <<(int*)funtest1<< endl;      //打印地址++funtest1;                        //函数指针向后偏移}cout<<endl;Derived d;cout<<"Derived vfptr:"<<endl;FUN_TEST* funtest2 = (FUN_TEST*)(*(int*)&b); while (*funtest2){(*funtest2)();cout << (int*)funtest2 << endl;      ++funtest2;                           }}int main(){FunTest();system("pause");return 0;}

             

                总结——派生类虚表的生成方式:

               (1)首先是拷贝基类的虚函数表;

               (2)如果派生类重写了虚表的某个函数,就覆盖同位置的基类虚函数;

               (3)最后是派生类自己新定义的函数。

        ③多继承:没有虚函数覆盖

#include <iostream>using namespace std;class Base0{public:Base0(){_data = 0xB0;}virtual void PrintB0(){cout<<"_data = "<<_data<<"Base0()::PrintB0"<<endl;}private:int _data;};class Base1{public:Base1(){_data = 0xB1;}virtual void PrintB1(){cout<<"_data = "<<_data<<"Base1()::PrintB1"<<endl;}private:int _data;};class Base2{public:Base2(){_data = 0xB2;}virtual void PrintB2(){cout<<"_data = "<<_data<<"Base2()::PrintB2"<<endl;}private:int _data;};class Derived:public Base0, public Base1, public Base2{public:Derived(){_data = 0xD0;}virtual void PrintD(){cout<<"_data = "<< _data<<"Derived::PrintD()"<<endl;}private:int _data;};typedef void(*VFTABLE_FUN)();void FunTest(){Derived d;Base0& b0 = d;VFTABLE_FUN* vfp0 = (VFTABLE_FUN*)(*(int*)&b0);cout <<"Base0 vfptr:"<< endl;while (*vfp0){(*vfp0)();      ++vfp0;                          }cout << endl;Base1& b1 = d;VFTABLE_FUN* vfp1 = (VFTABLE_FUN*)(*(int*)&b1);cout <<"Base1 vfptr:"<< endl;while (*vfp1){(*vfp1)();++vfp1;}cout << endl;Base2& b2 = d;VFTABLE_FUN* vfp2 = (VFTABLE_FUN*)(*(int*)&b2);cout <<"Base1 vfptr:"<< endl;while (*vfp2){(*vfp2)();++vfp2;}cout << endl;VFTABLE_FUN* vfp3 = (VFTABLE_FUN*)(*(int*)&d);cout <<"Derived vfptr:"<< endl;while (*vfp3){(*vfp3)();++vfp3;}}int main(){FunTest();system("pause");return 0;}
         运行结果:

           

           
            分析:根据监控和内存可以看出,派生类的虚表中首先存放了基类Base0的部分,接着是Base1,                              Base2, 最后是自己的内容;

        ④多继承:有虚函数覆盖

#include <iostream>using namespace std;class Base0{public:Base0(){_data = 0xB0;}virtual void Print(){cout<<"_data = "<<_data<<" Base0()::Print"<<endl;}private:int _data;};class Base1{public:Base1(){_data = 0xB1;}virtual void Print(){cout<<"_data = "<<_data<<" Base1()::Print"<<endl;}private:int _data;};class Base2{public:Base2(){_data = 0xB2;}virtual void Print(){cout<<"_data = "<<_data<<" Base2()::Print"<<endl;}private:int _data;};class Derived:public Base0, public Base1, public Base2{public:Derived(){_data = 0xD0;}virtual void Print(){cout<<"_data = "<< _data<<" Derived::Print()"<<endl;}private:int _data;};void FunTest(){Derived d;Base0& b0 = d;b0.Print();Base1& b1 = d;b1.Print();Base2& b2 = d;b2.Print();d.Print();}int main(){FunTest();system("pause");return 0;}
    运行结果:

     

    分析:同单继承有覆盖的原理相同,在派生类中进行重写的基类虚函数被覆盖。

1 0
原创粉丝点击