zoukankan      html  css  js  c++  java
  • c++多态性详解(转)

    什么是多态?

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

    这要先从对象的类型说起!对象的类型有两种:

    实例:Derived1类和Derived2类继承Base类

    class Base  
    {};  
      
    class Derived1 : public Base  
    {};  
      
    class Derived2 : public Base  
    {};  
      
    int main()  
    {  
        Derived1 pd1 = new Derived1; //pd1的静态类型为Derived1,动态类型为Derived1  
        Base *pb = pd1; //pb的静态类型为Base,动态类型现在为Derived1  
        Derived2 pd2 = new Derived2; //pd2的静态类型为Derived2,动态类型现在为Derived2  
        pb = pd2; //pb的静态类型为Base,动态类型现在为Derived2  
      
        return 0;  
    }
    

      对象有静态类型,也有动态类型,这就是一种类型的多态。

    多态分类

    多态有静态多态,也有动态多态。 静态多态,比如函数重载,能够在编译器确定应该调用哪个函数;动态多态,比如继承加虚函数的方式(与对象的动态类型紧密联系,后面详解),通过对象调用的虚函数是哪个是在运行时才能确定的。

    【静态多态】

    实例:函数重载 ( 指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数 )

    long long Add(int left, int right)  
    {  
        return left + right;  
    }  
      
    double Add(float left, float right)  
    {  
        return left + right;  
    }  
      
    int main()  
    {  
        cout<<Add(10, 20)<<endl; //语句一  
        cout<<Add(12.34f, 43.12f)<<endl; //语句二  
      
        return 0;  
    }
    

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

    【动态多态】

    进入动态多态前,先看一个普通继承的例子:

    class Person  
    {  
    public:  
        void GoToWashRoom()  
        {  
            cout<<"Person-->?"<<endl;  
        }  
    };  
      
    class Man : public Person  
    {  
    public:  
        void GoToWashRoom()  
        {  
            cout<<"Man-->Please Left"<<endl;  
        }  
    };  
      
    class Woman : public Person  
    {  
    public:  
        void GoToWashRoom()  
        {  
            cout<<"Woman-->Please Right"<<endl;  
        }  
    };  
      
    int main()  
    {  
        Person per, *pp;  
        Man man, *pm;  
        Woman woman, *pw;  
      
        pp = &per;  
        pm = &man;  
        pw = &woman;  
          
        //第一组       //这些都是毫无疑问的  
        per.GoToWashRoom(); //调用基类Person类的函数  
        pp->GoToWashRoom();  //调用基类Person类的函数  
        man.GoToWashRoom(); //调用派生类Man类的函数   
        pm->GoToWashRoom();  //调用派生类Man类的函数   
        woman.GoToWashRoom();   //调用派生类Woman类的函数   
        pw->GoToWashRoom();  //调用派生类Woman类的函数   
          
        //第二组  
        pp = &man;  
        pp->GoToWashRoom();  //调用基类Person类的函数  
        pp = &woman;  
        pp->GoToWashRoom();  //调用基类Person类的函数  
          
        return 0;  
    }
    

      

    运行结果:

    第一组毫无疑问,通过本类对象和本类对象的指针就是调用本类的函数;

    第二组中先让基类指针指向子类对象,然后调用该函数,调用的是基类的,后让基类指针指向另一个子类对象,调用的是还是基类的函数。

    这是因为p的类型是一个基类的指针类型,那么在p看来,它指向的就是一个基类对象,所以调用了基类函数。就像一个int型的指针,不论它指向哪,读出来的都是一个整型(在没有崩溃的前提下),即使将它指向一个float。

    再来对比着看下一个例子。

    实例:继承+虚函数

    class Person  
    {  
    public:  
        virtual void GoToWashRoom() = 0;  
    };  
      
    class Man : public Person  
    {  
    public:  
        virtual void GoToWashRoom()  
        {  
            cout<<"Man-->Please Left"<<endl;  
        }  
    };  
      
    class Woman : public Person  
    {  
    public:  
        virtual void GoToWashRoom()  
        {  
            cout<<"Woman-->Please Right"<<endl;  
        }  
    };  
      
    int main()  
    {  
        for (int i = 0; i < 10; i++)  
        {  
            Person *p;  
            if (i&0x01)  
                p = new Man;  
            else  
                p = new Woman;  
      
            p->GoToWashRoom();  
            delete p;  
            sleep(1);  
        }  
      
        return 0;  
    }
    

      

    运行结果:

    就像上边这个例子所演示的那样,通过重写虚函数(不再是普通的成员函数,是虚函数!),实现了动态绑定,即在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。

    使用virtual关键字修饰函数时,指明该函数为虚函数(在例子中为纯虚函数),派生类需要重新实现,编译器将实现动态绑定。在上边例子中,当指针p指向Man类的对象时,调用了Man类自己的函数,p指向Woman类对象时,调用了Woman类自己的函数。

    【动态绑定条件】

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

    总结

    • 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
    • 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
    • 只有类的成员函数才能定义为虚函数,静态成员函数不能定义为虚函数
    • 如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
    • 构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容 易混淆
    • 不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会 出现未定义的行为
    • 最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构 函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
    • 虚表是所有类对象实例共用的

    tips:协变:在C++中,只要原来的返回类型是基类类型的指针或引用,新的返回值类型是派生类的指针或引用,覆盖的方法就可以改变返回类型,这样的返回类型称为协变返回类型。

    //协变,也可以构成重写(覆盖),但返回值是该类类型的指针或引用  
    class Base     
    {         
        virtual Base * FunTest()    
        {    
            //do something    
        }    
    };    
    class Derived : public Base    
    {    
        Derived * FunTest()    
        {    
            //do something    
        }    
    };
    

      

    容易混淆的点:

  • 相关阅读:
    如何复制保存阿里巴巴的图片。
    如何在windows2003(IIS6)下配置IIS,使其支持cshtml
    数据库字符串加法,目前没成功
    使用ASP.NET AJAX与Bootstrap 弹窗解决方案
    在MyBatis中采用模糊查询变量的引用标志应当是$而不是#
    如何让SpringBoot工程在log/控制台中实时打印MyBatis执行的SQL语句
    雇员信息完全分页方案
    将雇员信息分页显示
    把Employees显示在页面上
    给EmpMapper开放Restful接口
  • 原文地址:https://www.cnblogs.com/zhanghu52030/p/8004523.html
Copyright © 2011-2022 走看看