重载(overload), 重写(override), 重定义(redefine) 及多态性,虚函数总结

一、重载(overload)
指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断
(1)相同的范围(在同一个作用域中即同一类中) ;
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
(5)返回值可以不同;

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

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

作用效果和原理编译器根据函数不同的参数表,将函数体与函数调用进行早绑定,是静态的

重载与多态无关,只是一种语言特性,与面向对象无关。重载不关心函数的返回值类型。

     

二、重写(也称为覆盖 override)
是指派生类重新定义基类的虚函数,即会覆盖基类的虚函数(多态性)特征是:
(1)不在同一个作用域(分别位于派生类与基类) ;
(2)函数名字相同;
(3)参数列表相同;
(4)基类函数必须有 virtual 关键字,不能有 static 。
(5)返回值相同(或是协变即基类中返回类型是基类引用或指针则派生类中返回可以修改为派生类引用或指针),否则报错;

(6)重写函数的访问修饰符可以不同。尽管 virtual 是 private 的,派生类中重写改写为 public,protected 也是可以的

特殊情况:若派生类重写的虚函数属于一个重载版本,则该重写的函数会隐藏基类中与虚函数同名的其他函数,另外的版本将被隐藏,派生类无法使用

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

注意:

1)如果是通过引用或者指针而不是对象调用(如基类类型的指针指向派生类),调用函数的处理方法(是否多态性取决于是否重写了虚函数。如果是,则根据多态性,调用指向或引用的对象的类(如派生类的函数)。如果不是,则是正常的静态函数调用,调用引用类型或者指针类型的类(如基类函数)。

2)派生类中的所有不属于基类的信息都无法通过基类指针(或基类引用)或派生类对象初始化基类对象(会产生对象切片)来获取,因为编译器在解析该指针或引用指向的内存区时是按照基类的信息来解释的。除了基类指针或基类引用间接指向派生类类型且基类中使用了virtual时,编译器则会通过基类信息动态联编派生类中的重写的函数,产生多态。

      DerivedMyBase aDMB;
      MyBase* pMB &aDMB;
      pMB->Print();//该函数在基类MyBase中无定义,只在派生类DerivedMyBase中定义了

例子并没有发生对象切片,但仍然报错。因为pMB 仅仅是一个指针,通过该指针引用的是 aDMB ,但编译器对于该指针应用对象的了解仅于 MyBase
对于DerivedMyBase类的信息一无所知,故



三、重定义(也称为隐藏redefine)

派生类对基类的成员函数重新定义,即派生类定义了某个函数,该函数的名字与基类中的函数名字一样。 派生类对象中的(非派生类指针或引用)基类的函数将被隐藏

(1)不在同一个作用域(分别位于派生类与基类) ;
(2)函数名字相同;
(3)返回值可以不同;
(4)参数不同。此时,不论有无 virtual 关键字。
(5)参数相同,但是基类函数没有 virtual关键字。

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

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

注意:是否是否隐藏决定于指针的类型,与指针指向什么无关。派生类重定义的基类函数只有基类类型指针才能访问它,派生类类型指针只能访问重定义的那个函数,虽然基类函数仍然在派生类代码中。




实例1:

复制代码
 1 #include <iostream>
 2 #include <complex>
 3 using namespace std;
 4 
 5 class Base
 6 {
 7 public:
 8     virtual void a(int x)    {    cout << "Base::a(int)" << endl;      }
 9     // overload the Base::a(int) function
10     virtual void a(double x) {    cout << "Base::a(double)" << endl;   }
11     virtual void b(int x)    {    cout << "Base::b(int)" << endl;      }
12     void c(int x)            {    cout << "Base::c(int)" << endl;      }
13 };
14 
15 class Derived : public Base
16 {
17 public:
18     // redefine the Base::a() function
19     void a(complex<double> x)   {    cout << "Derived::a(complex)" << endl;      }
20     // override the Base::b(int) function
21     void b(int x)               {    cout << "Derived::b(int)" << endl;          }
22     // redefine the Base::c() function
23     void c(int x)               {    cout << "Derived::c(int)" << endl;          }
24 };
25 
26 int main()
27 {
28     Base b;
29     Derived d;
30     Base* pb = new Derived;
31     // ----------------------------------- //
32     b.a(1.0);                              // Base::a(double)
33     d.a(1.0);                              // Derived::a(complex)
34     pb->a(1.0);                            // Base::a(double), This is redefine the Base::a() function
35     // pb->a(complex<double>(1.0, 2.0));   // clear the annotation and have a try
36     // ----------------------------------- //
37     b.b(10);                               // Base::b(int)
38     d.b(10);                               // Derived::b(int)
39     pb->b(10);                             // Derived::b(int), This is the virtual function
40     // ----------------------------------- //
41     delete pb;
42 
43     return 0;
44 } 
复制代码
通过这里可以看出:

1.Base类中的第二个函数a是对第一个的重载

2.Derived类中的函数b是对Base类中函数b的重写,即使用了虚函数特性。

3.Derived类中的函数a是对Base泪中函数a的隐藏,即重定义了。

4. pb指针是一个指向Base类型的指针,但是它实际指向了一个Derived的空间,这里对pd调用函数的处理(多态性)取决于是否重写(虚函数特性)了函数,若没有,则依然调用基类

5.只有在通过基类指针或基类引用 间接指向派生类类型时多态性才会起作用。

6.因为Base类的函数c没有定义为virtual虚函数,所以Derived类的函数c是对Base::c()的重定义




实例2:

基类和派生类的结构

复制代码
//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;
    }
};
复制代码
测试---主程序
复制代码
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;定义了一个基类对象,用派生类对象来进行初始化。这会调用基类的拷贝构造函数,将d拷贝生成基类对象b。b对象中只有基类Test的信息,XX类的其他信息都被切片了。基类的拷贝构造函数初始化bVPTR指针,指向b的虚函数表VTABLE。因此所有的函数调用都只发生在基类,不会产生多态。

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

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

------------------------------



多态的几个问题:

多态性是一种对消息的处理方式可以随接手消息的处理对象而变的一种机制

1.向上强制转换

派生类引用或指针转换为基类引用或指针,如 Test* pB = &d,不需要进行显示类型转换,且总是安全的。反之,称为向下强制转换,必须要显式类型转换,如XX* d = (XX*)&pB,且可能不安全。

2.动态联编(晚捆绑或晚期联编)

联编是指把函数体与函数调用相联系。

隐式向上强制转换使程序在编译时不能确定用户使用哪个函数,编译器必须生成能够在程序运行时选择正确函数体的代码,这被称为动态联编(或晚期联编)。关键字virtual告诉编译器它应该实行动态联编。

3.纯虚函数和抽象类

纯虚函数是一种特殊的虚函数,它的一般格式如下:
    class <类名>
    {
        virtual <类型><函数名>(<参数表>)=0;
        
    }; 
    在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做 
纯虚函数和虚函数的区别:
1)虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。
2)虚函数可以被直接使用,也可以被子类(sub class)重写以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。 


抽象类不能创建类对象。任何对抽象类的实例化操作都将导致错误的产生,因为抽象类是不能直接被调用的,必须被子类继承重载后,根据要求调用其子类的方法 。


4.对象切片

1)无虚拟机制继承的对象切片

如test  2中Test  b  = d

2)对象切片原理

对象切片产生的原因是bitwise(逐位)copy构造函数的调用。在“MyBase aMB aDMB;”中由编译器生成的拷贝构造函数不需要对虚拟机制进行额外的处理,此时依照bitwise copy,所有属于DerivedMyBase的信息都丢掉了。而在“ MyBase pMB &aDMB;”中,根本就不需要调用copy ctor,所以切片不会发生。





虚函数

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

虚函数提供了一种分辨对象执行不同功能的能力, 但是从效率角度出发时,虚函数不是高效的,它需要VPTR指针的压栈出栈操作等。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值