高质量程序设计指南--重载 覆盖 隐藏

来源:互联网 发布:知乎账号 编辑:程序博客网 时间:2024/04/20 15:47
 

成员函数的重载、覆盖与隐藏(书摘)

——《高质量程序设计指南》

成员函数的重载,覆盖(override,也叫改写)与隐藏(也叫遮蔽)很容易混淆,程序员必须搞清楚概念,否则调用结果可能与期望不符。

1.   重载与覆盖

成员函数被重载的特征是:

具有相同的作用域(即同一个类定义中);

函数名字相同;

参数类型,顺序或者数目不同(包括const参数和非const参数)

Virtual关键字可有可无;

覆盖是指派生类重新实现(或者改写了)基类的成员函数,其特征是:

不同的作用域(分别位于派生类和基类中);

函数名称相同;

参数列表完全相同;

基类函数必须是虚函数;

 

如下示例,函数Base::f(int)与Base::f(float)构成重载,

而Base::g(void)则被Derived::g(void)覆盖

 

#include <iostream.h>

Class Base

{

Public:

  Void f(int x){cout<<”Base::f(int)”<<x<<endl;}

  Void f(float x){cout<<”Base::f(float)”<<x<<endl;}

  Virtual void g(void){cout<<”Base:g(void)”<<endl;}

};

Class Derived::public Base

{

Public:

   Virtual void g(void){cout<<”Derived::g(void)”<<endl;}

};

 

Void main(void)

{

Derived d;

Base *pb = &d;

Pb->f(42);                   //Base::f(int) 42

Pb->f(3.14f)                 //Base::f(float) 3.14

Pb->g();                    //Derived::g(void)

   

}

 

提示:

1.       Virtual 关键字告诉编译器,派生类中相同的成员函数应该放到vtable中,并且替换基类中相应成员函数的槽位;

2.       虚函数的覆盖有两种方式:完全重写和扩展。扩展是指派生类虚函数首先调用基类的虚函数,然后再增加新的功能;

2.令人迷惑的隐藏规则

本来仅仅区别重载与覆盖并不算困难,单丝C++的隐藏规则使问题的复杂性陡然增加。这里“隐藏”是指派生类的成员函数遮蔽了与其同名的基类成员,具体规则如下:

     派生类的函数与基类的函数同名,但是参数列表有所差异。此时,不论有无virtual关键字,基类的成员函数在派生类中将被隐藏(注意别与重载混淆);

     派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字,此时,基类的函数在派生类中将被隐藏(注意别于覆盖混淆)。

 

如下示例:

函数Derived::f(float)覆盖了Base::f(float)

函数Derived::g(int)隐藏了Base::g(float),而不是重载

函数Derived::h(folat)隐藏了Base::h(float),而不是覆盖

 

#include <iostream.h>

Class Base

{

  Public:

Virtual void f(float x){cout<<”Base::f(float)”<<x<<endl;}

Void g(float x){cout << “base::g(float)”<<x<,<endl;}

Void h(float x){cout << “base::h(float)”<<x<<endl}

};

 

Class derived : public base

{

Public:

    Virtual void f(float x){cout << “derived::f(float)” <<x <<endl}

Void g(int x){cout <<“derived::g(int)”<< x <<endl}

Void h(float x){cout << “derived::h(float)”<<x<<endl}

}

 

Void main(void)

{

       Derived d;

       Base *pb = &d;

       Derived *pd = &d;

      

       Pb->f(3.14f);                //动态绑定:derived::f(float) 3.14

Pd->f(3.14f);                //动态绑定:derived::f(float) 3.14

 

//程序的行为依赖于指针的静态类型

Pb->g(3.14f);         //base::g(float)3.14

Pd->g(3.14f);         //derived::g(int)3

 

Pb->h(3.14f);         //base::h(float) 3.14

Pd->h(3.14f);         //derived::h(float)3.14

}

 

提示:

如果确实想使用基类的成员函数,可以在派生类定义中的任何地方显式的使用using关键字,如下:

Class derived: public base

{

Public:

  Using Base::g;

  Void g(int x) {……}

};

3.   摆脱隐藏

隐藏规则引起了不少麻烦,如下,程序员可能期望语句pd->f(10)调用函数base::f(int),但是base::f(int)不幸被derived::f(char *)隐藏了。由于数字10不能被隐式的转换为字符串类型所以在编译时出错。

Class base

{

Public:

      Void f(int x);

};

 

Class derived : public base

{

      Void f(char *str);

};

 

Void test(void)

{

      Derived *pd = new derived;

      Pd->f(10);    //error ,此处在编译的时候出错。

}

 

有了隐藏规则,编译器将明确的指出错误;

假如派生类有多个基类(多重继承),有时搞不清楚哪些基类定义了函数f, 如果没有隐藏规则,那么pd->f(10)可能会调用一个出乎意料的基类函数f().

提示:

如果语句pd->f(10)确实想调用函数base::f(int),那么有两个方法:

1.       就是使用using声明;

Class derived : public base

{

   Public:

     Using Base::g;

     Void g(int x){……}

}    

 

2.       把类修改成如下的示例:

Class derived: public base

{

       Public:

      Void f(char *str);

      Void f(int x) {base::f(x);}

};

原创粉丝点击