zoukankan      html  css  js  c++  java
  • C++基础:C++函数名字的隐藏:重写、重载、重定义

    本文转载自:http://www.cnblogs.com/wly603/archive/2012/05/26/2519171.html

    概要:

         C++中经常出现函数名字一样,但参数列表或返回值不同的函数,要搞清楚函数的正确调用关系,需理清三个概念:重写(override)、重载(overload)、重定义(redefine)。

    一、三个基本概念

      1、重定义(redefine):派生类对基类的成员函数重新定义,即派生类定义了某个函数,该函数的名字与基类中的函数名字一样。

                    特点:(1)不在同一个作用域(分别位于基类、派生类)  (2)函数的名字必须相同  (3)对函数的返回值、形参列表无要求

                    特殊情况:若派生类定义的该函数与基类的成员函数完全一样(返回值、形参列表均相同),且基类的该函数为virtual,则属于派生类重写基类的虚函数。

                    作用效果:若重新定义了基类中的一个重载函数,则在派生类中,基类中该名字的函数(即其他所有重载版本)都被自动隐藏,包括同名的虚函数。

      2、重载(overload):函数名字相同,但它的形参个数或者顺序,或者类型不同,但是不能靠返回类型来判断

                  特点:(1)位于同一个类中  (2)函数的名字必须相同    (3)形参列表不同(可能是参数个数  or  类型  or  顺序 不同),返回值无要求  

                  特殊情况:若某一个重载版本的函数前面有virtual修饰,则表示它是虚函数。但它也是属于重载的一个版本

                                不同的构造函数(无参构造、有参构造、拷贝构造)是重载的应用

                  作用效果和原理:编译器根据函数不同的参数表,将函数体与函数调用进行早绑定。重载与多态无关,只是一种语言特性,与面向对象无关。

      3、重写(override):派生类重定义基类的虚函数,即会覆盖基类的虚函数   (多态性)

                特点:(1)不在同一个作用域(分别位于基类、派生类)  (2)函数名、形参列表、返回值相同  (3)基类的函数是virtual

                特殊情况:若派生类重写的虚函数属于一个重载版本,则该重写的函数会隐藏基类中与虚函数同名的其他函数。

                作用效果:父类的指针或引用根据传递给它的子类地址或引用,动态地调用属于子类的该函数。这个晚绑定过程只对virtual函数起作用

                             具体原理是由虚函数表(VTABLE)决定的,在第三节介绍。

     

    二、程序实例

    1、两个类:基类( 取名Test)和派生类( 取名XX)   名字不规范,哈哈随便取得!

    基类和派生类的结构
    
    //Base class
    class Test
    {
    public:
        int a;
    
        Test()
        {
            cout<<"Test() 无参构造函数!"<<endl;
        }
    
        Test(int data)
        {
            a = data;
            cout<<"Test(int data) 有参构造函数!"<<endl;
        }
    
        Test(const Test &tmp)
        {
            a = tmp.a;
            cout<<"Test 拷贝构造函数!!"<<endl;        
        }
    
        //基类中对函数名f,进行了重载。其中最后一个重载函数为虚函数
        void f()const
        {
            cout<<"调用 void Test::f()"<<endl;
        }
    
        //overload
        int f(int data) const
        {
            cout<<"调用 Test f(int data)"<<endl;
            return 1;
        }
    
           //overload    虚函数
        virtual double f(int dataA,int dataB)
        {
            cout<<"调用 Test f(int a,int b)"<<endl;
            return dataA*dataB/2.0;
        }
    
    };
    
    class  XX: public Test
    {
    public:
        Test atest;//先调用基类的构造函数,然后对象成员的构造函数,最后才是派生类的构造函数
    
        XX()
        {
            cout<<"XX() 无参构造函数被调用!"<<endl;
        }
    
        //对基类的函数名f,进行了重定义。则会隐藏基类中的其他f函数
        //redefine
        int  f() const
        {
            cout<<" 调用 XX f()函数"<<endl;
             return 1;
        }
    
        //重写基类的虚函数
        //redefine   override
        double f(int dataA,int dataB)
        {
            cout<<"调用 XX f(int dataA,int dataB)函数"<<endl;
            return (dataA+dataB)/2.0;
        }
    };

    分析:基类class Test中定义了名为f的3个重载函数,其中最后一个是虚函数

             派生类class  XX中对f进行了重定义,所以会隐藏基类中名为f的版本。其中派生类的double f(int dataA,int dataB)属于对虚函数的重写

    测试---主程序
    
    int main()
    {
    //-----test 1------------------------
        cout<<"-------test 1------------"<<endl;
        //Base class
        Test aaTest;
           aaTest.f();
        aaTest.f(12);
        aaTest.f(10,20);
    
        //derived class
         XX d;
        d.f();
    //    d.f(2); //error C2661: 'f' : no overloaded function takes 1 parameters
        d.f(10,20);
    
    //--------test 2----------------------------------
        cout<<"-------test 2------------"<<endl;
        Test b = d;
        b.f(); 
        b.f(10,20);//调用的是基类的函数,不发生多态
    
    //--------test 3----------------------------------------
        cout<<"-------test 3------------"<<endl;
        Test &bR = d;//引用
        b.f();//f()不是虚函数,调用基类的函数
        bR.f(10,20);//调用的是派生类的函数,发生多态
    
    //--------test 4--------------------------------------
        cout<<"-------test 4------------"<<endl;
        Test* pB = &d;
        b.f();
        pB->f(10,20);//调用的是派生类的函数,发生多态
    
        return 1;
    }


    分析:(1)test 1中进行了重载测试,根据传递参数的不一样,调用不同的函数  (早绑定,与多态无关)

             (2)test 2中Test b = d;定义了一个基类对象,用派生类对象来进行初始化。这会调用基类的拷贝构造函数,生成基类的对象b,基类的拷贝构造函数初始化b的VPTR,指向b的VTABLE。因此所有的函数调用都只发生在基类,不会产生多态。

                这是一个对象切片过程(参见《C++编程思想.第二版》P370),对象切片是当它拷贝到一个新的对象时,会去掉原来对象的一部分,而不是像使用指针或引用那样简单地改变地址的内容。

            (3)test 3和test 4中,定义的基类指针和引用,故会发生多态。

     

    三、晚绑定原理:虚函数表

         编译器会对每一个包含虚函数的类(或者从包含虚函数的基类派生的类)创建一个表(VTABLE),里面存放特定类的虚函数的地址。然后编译器秘密地放置一指针vpointer(VPTR),指向这个对象的vtable。当通过基类指针做虚函数调用时(即多态调用时),编译器静态地插入能取得这个VPTR并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数并引起晚绑定的发生。

        这个具体过程, 可以参见本人的另一篇博客《C模拟实现C++的多态http://www.cnblogs.com/wly603/archive/2012/04/11/2441912.html

     

       (完)



  • 相关阅读:
    bzoj1015星球大战(并查集+离线)
    bzoj1085骑士精神(搜索)
    bzoj1051受欢迎的牛(Tarjan)
    左偏树学习
    hdu1512 Monkey King(并查集,左偏堆)
    左偏树(模板)
    PAT (Basic Level) Practice (中文) 1079 延迟的回文数 (20分) (大数加法)
    PAT (Basic Level) Practice (中文) 1078 字符串压缩与解压 (20分) (字符转数字——栈存放)
    PAT (Basic Level) Practice (中文) 1077 互评成绩计算 (20分) (四舍五入保留整数)
    PAT (Basic Level) Practice (中文) 1076 Wifi密码 (15分)
  • 原文地址:https://www.cnblogs.com/f8master/p/3826100.html
Copyright © 2011-2022 走看看