《成员函数的重载、覆盖与隐藏》
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

成员函数的重载、覆盖(override)与隐藏很容易混淆,C++程序员必须要搞清楚
概念,否则错误将防不胜防。
8.2.1 重载与覆盖
1.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
2.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

/**************************************************************************/
示例8-2-1 中,函数Base::f(int)与Base::f(float)相互重载,而Base::g(void)
被Derived::g(void)覆盖。
#include
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)
}
示例8-2-1 成员函数的重载和覆盖
/**************************************************************************/
8.2.2 令人迷惑的隐藏规则
本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
/**************************************************************************/
示例程序8-2-2(a)中:
(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。
#include
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; }
};
示例8-2-2(a)成员函数的重载、覆盖和隐藏
/**************************************************************************/
据作者考察,很多C++程序员没有意识到有“隐藏”这回事。由于认识不够深刻,
“隐藏”的发生可谓神出鬼没,常常产生令人迷惑的结果。
/**************************************************************************/
示例8-2-2(b)中,bp 和dp 指向同一地址,按理说运行结果应该是相同的,可事
实并非这样。
void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}
示例8-2-2(b) 重载、覆盖和隐藏的比较
/**************************************************************************/
8.2.3 摆脱隐藏
隐藏规则引起了不少麻烦。
/**************************************************************************/
示例8-2-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
{
public:
void f(char *str);
};
void Test(void)
{
Derived *pd = new Derived;
pd->f(10); // error
}
示例8-2-3 由于隐藏而导致错误
/**************************************************************************/
从示例8-2-3 看来,隐藏规则似乎很愚蠢。但是隐藏规则至少有两个存在的理由:
? 写语句pd->f(10)的人可能真的想调用Derived::f(char *)函数,只是他误将参数
写错了。有了隐藏规则,编译器就可以明确指出错误,这未必不是好事。否则,编
译器会静悄悄地将错就错,程序员将很难发现这个错误,流下祸根。
? 假如类Derived 有多个基类(多重继承),有时搞不清楚哪些基类定义了函数f。如
果没有隐藏规则,那么pd->f(10)可能会调用一个出乎意料的基类函数f。尽管隐
藏规则看起来不怎么有道理,但它的确能消灭这些意外。
示例8-2-3 中,如果语句pd->f(10)一定要调用函数Base::f(int),那么将类
Derived 修改为如下即可。
class Derived : public Base
{
public:
void f(char *str);
void f(int x) { Base::f(x); }
};
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
首先,我们来看一个非常简单的例子,理解一下什么叫函数隐藏hide。
#include
using namespace std;
class Base{
public:
    void fun() { cout << "Base::fun()" << endl; }
};
class Derive : public Base{
public:
    void fun(int i) { cout << "Derive::fun()" << endl; }
};
int main()
{
    Derive d;
    //下面一句错误,故屏蔽掉
    //d.fun();error C2660: 'fun' : function does not take 0 parameters
    d.fun(1);
    Derive *pd =new Derive();
    //下面一句错误,故屏蔽掉
    //pd->fun();error C2660: 'fun' : function does not take 0 parameters
    pd->fun(1);
    delete pd;
    return 0;
}
/*在不同的非命名空间作用域里的函数不构成重载,子类和父类是不同的两个作用域。
在本例中,两个函数在不同作用域中,故不够成重载,除非这个作用域是命名空间作用域。*/
在这个例子中,函数不是重载overload,也不是覆盖override,而是隐藏hide。
接下来的5个例子具体说明一下什么叫隐藏
例1
#include
using namespace std;
class Basic{
public:
         void fun(){cout << "Base::fun()" << endl;}//overload
         void fun(int i){cout << "Base::fun(int i)" << endl;}//overload
};
class Derive :public Basic{
public:
         void fun2(){cout << "Derive::fun2()" << endl;}
};
int main()
{
         Derive d;
         d.fun();//正确,派生类没有与基类同名函数声明,则基类中的所有同名重载函数都会作为候选函数。
         d.fun(1);//正确,派生类没有与基类同名函数声明,则基类中的所有同名重载函数都会作为候选函数。
         return 0;
}
例2
#include
using namespace std;
class Basic{
public:
         void fun(){cout << "Base::fun()" << endl;}//overload
         void fun(int i){cout << "Base::fun(int i)" << endl;}//overload
};
class Derive :public Basic{
public:
         //新的函数版本,基类所有的重载版本都被屏蔽,在这里,我们称之为函数隐藏hide
         //派生类中有基类的同名函数的声明,则基类中的同名函数不会作为候选函数,即使基类有不同的参数表的多个版本的重载函数。
         void fun(int i,int j){cout << "Derive::fun(int i,int j)" << endl;}
         void fun2(){cout << "Derive::fun2()" << endl;}
};
int main()
{
         Derive d;
         d.fun(1,2);
         //下面一句错误,故屏蔽掉
         //d.fun();error C2660: 'fun' : function does not take 0 parameters
         return 0;
}
例3
#include
using namespace std;
class Basic{
public:
         void fun(){cout << "Base::fun()" << endl;}//overload
         void fun(int i){cout << "Base::fun(int i)" << endl;}//overload
};
class Derive :public Basic{
public:
         //覆盖override基类的其中一个函数版本,同样基类所有的重载版本都被隐藏hide
         //派生类中有基类的同名函数的声明,则基类中的同名函数不会作为候选函数,即使基类有不同的参数表的多个版本的重载函数。
         void fun(){cout << "Derive::fun()" << endl;}
         void fun2(){cout << "Derive::fun2()" << endl;}
};
int main()
{
         Derive d;
         d.fun();
         //下面一句错误,故屏蔽掉
         //d.fun(1);error C2660: 'fun' : function does not take 1 parameters
         return 0;
}
例4
#include
using namespace std;
class Basic{
public:
         void fun(){cout << "Base::fun()" << endl;}//overload
         void fun(int i){cout << "Base::fun(int i)" << endl;}//overload
};
class Derive :public Basic{
public:
         using Basic::fun;
         void fun(){cout << "Derive::fun()" << endl;}
         void fun2(){cout << "Derive::fun2()" << endl;}
};
int main()
{
         Derive d;
         d.fun();//正确
         d.fun(1);//正确
         return 0;
}
/*
输出结果
Derive::fun()
Base::fun(int i)
Press any key to continue
*/
例5
#include
using namespace std;
class Basic{
public:
         void fun(){cout << "Base::fun()" << endl;}//overload
         void fun(int i){cout << "Base::fun(int i)" << endl;}//overload
};
class Derive :public Basic{
public:
         using Basic::fun;
         void fun(int i,int j){cout << "Derive::fun(int i,int j)" << endl;}
         void fun2(){cout << "Derive::fun2()" << endl;}
};
int main()
{
         Derive d;
         d.fun();//正确
         d.fun(1);//正确
         d.fun(1,2);//正确
         return 0;
}
/*
输出结果
Base::fun()
Base::fun(int i)
Derive::fun(int i,int j)
Press any key to continue
*/
 
如果基类有某个函数的多个重载(overload)版本,而你在派生类中重写(override)了基类中的一个或多个函数版本,或是在派生类中重新添加了新的函数版本(函数名相同,参数不同),则所有基类的重载版本都被屏蔽,在这里我们称之为隐藏hide。所以,在一般情况下,你想在派生类中使用新的函数版本又想使用基类的函数版本时,你应该在派生类中重写基类中的所有重载版本。你若是不想重写基类的重载的函数版本,则你应该使用例4或例5方式,显式声明基类名字空间作用域。
 
事实上,C++编译器认为,相同函数名不同参数的函数之间根本没有什么关系,它们根本就是两个毫不相关的函数。只是C++语言为了模拟现实世界,为了让程序员更直观的思维处理现实世界中的问题,才引入了重载和覆盖的概念。重载是在相同名字空间作用域下,而覆盖则是在不同的名字空间作用域下,比如基类和派生类即为两个不同的名字空间作用域。在继承过程中,若发生派生类与基类函数同名问题时,便会发生基类函数的隐藏。当然,这里讨论的情况是基类函数前面没有virtual 关键字。在有virtual 关键字关键字时的情形我们另做讨论。
 
继承类重写了基类的某一函数版本,以产生自己功能的接口。此时C++编绎器认为,你现在既然要使用派生类的自己重新改写的接口,那我基类的接口就不提供给你了(当然你可以用显式声明名字空间作用域的方法,见[C++基础]重载、覆盖、多态与函数隐藏(1))。而不会理会你基类的接口是有重载特性的。若是你要在派生类里继续保持重载的特性,那你就自己再给出接口重载的特性吧。所以在派生类里,只要函数名一样,基类的函数版本就会被无情地屏蔽。在编绎器中,屏蔽是通过名字空间作用域实现的。
 
所以,在派生类中要保持基类的函数重载版本,就应该重写所有基类的重载版本。重载只在当前类中有效,继承会失去函数重载的特性。也就是说,要把基类的重载函数放在继承的派生类里,就必须重写。
 
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,具体规则我们也来做一小结:
1          如果派生类的函数与基类的函数同名,但是参数不同。此时,若基类无virtual关键字,基类的函数将被隐藏。(注意别与重载混淆,虽然函数名相同参数不同应称之为重载,但这里不能理解为重载,因为派生类和基类不在同一名字空间作用域内。这里理解为隐藏)
2          如果派生类的函数与基类的函数同名,但是参数不同。此时,若基类有virtual关键字,基类的函数将被隐式继承到派生类的vtable中。此时派生类vtable中的函数指向基类版本的函数地址。同时这个新的函数版本添加到派生类中,作为派生类的重载版本。但在基类指针实现多态调用函数方法时,这个新的派生类函数版本将会被隐藏。
3          如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏。(注意别与覆盖混淆,这里理解为隐藏)。
4          如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数有virtual关键字。此时,基类的函数不会被“隐藏”。(在这里,你要理解为覆盖哦^_^)。
好了,我们先来一个小小的总结重载与覆盖两者之间的特征
重载overload的特征:
1          相同的范围(在同一个类中);
2          函数名相同参数不同;
3          virtual 关键字可有可无。
 
覆盖override是指派生类函数覆盖基类函数,覆盖的特征是:
1          不同的范围(分别位于派生类与基类);
2          函数名和参数都相同;
3          基类函数必须有virtual 关键字。(若没有virtual 关键字则称之为隐藏hide

转载于:https://blog.51cto.com/lixiaomeng/721239

《C++成员函数重载、覆盖与隐藏》相关推荐

  1. ComeFuture英伽学院——2020年 全国大学生英语竞赛【C类初赛真题解析】(持续更新)

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  2. ComeFuture英伽学院——2019年 全国大学生英语竞赛【C类初赛真题解析】大小作文——详细解析

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  3. 信息学奥赛真题解析(玩具谜题)

    玩具谜题(2016年信息学奥赛提高组真题) 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业.有一天, 这些玩具小人把小南的眼镜藏了起来.小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的 ...

  4. 信息学奥赛之初赛 第1轮 讲解(01-08课)

    信息学奥赛之初赛讲解 01 计算机概述 系统基本结构 信息学奥赛之初赛讲解 01 计算机概述 系统基本结构_哔哩哔哩_bilibili 信息学奥赛之初赛讲解 02 软件系统 计算机语言 进制转换 信息 ...

  5. 信息学奥赛一本通习题答案(五)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  6. 信息学奥赛一本通习题答案(三)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  7. 信息学奥赛一本通 提高篇 第六部分 数学基础 相关的真题

    第1章   快速幂 1875:[13NOIP提高组]转圈游戏 信息学奥赛一本通(C++版)在线评测系统 第2 章  素数 第 3 章  约数 第 4 章  同余问题 第 5 章  矩阵乘法 第 6 章 ...

  8. 信息学奥赛一本通题目代码(非题库)

    为了完善自己学c++,很多人都去读相关文献,就比如<信息学奥赛一本通>,可又对题目无从下手,从今天开始,我将把书上的题目一 一的解析下来,可以做参考,如果有错,可以告诉我,将在下次解析里重 ...

  9. 信息学奥赛一本通(C++版) 刷题 记录

    总目录详见:https://blog.csdn.net/mrcrack/article/details/86501716 信息学奥赛一本通(C++版) 刷题 记录 http://ybt.ssoier. ...

  10. 最近公共祖先三种算法详解 + 模板题 建议新手收藏 例题: 信息学奥赛一本通 祖孙询问 距离

    首先什么是最近公共祖先?? 如图:红色节点的祖先为红色的1, 2, 3. 绿色节点的祖先为绿色的1, 2, 3, 4. 他们的最近公共祖先即他们最先相交的地方,如在上图中黄色的点就是他们的最近公共祖先 ...

最新文章

  1. 使用IDEA开发Servlet程序
  2. c#实例 让你明白什么是继承
  3. 即日起更新机器学习相关博客
  4. 全面解读SDH、MSTP、OTN和PTN的区别和联系
  5. 全球及中国再生橡胶产业发展动态及十四五运营状况分析报告2021版
  6. 移动IM开发指南2:心跳指令详解
  7. VTK:Utilities之ShepardMethod
  8. 动态栈Stack的C语言实现
  9. MQTT-SN协议乱翻之实现要点
  10. mysql 查询语句性能优化
  11. 基础算法 —— 高精度计算 —— 高精度除法
  12. LeetCode 123. 买卖股票的最佳时机 III(Best Time to Buy and Sell Stock III)
  13. flask 重定向(redirect)和会话(session)
  14. 浅谈语音识别技术的发展趋势与应用前景 - 全文
  15. 计算机图形学完整笔记(三):裁剪
  16. 安卓平板python编程软件下载_Notepad++中文版
  17. Python】Scrapy抓取多玩Gif图片
  18. linux下添加4g模块驱动程序,【经验】4G模块SLM750在Linux系统下增加USB串口的详细操作指南...
  19. 百度地图API之绘制折线及点击事件
  20. 从企业实务角度解读 ITIL4 之14个通用管理实践

热门文章

  1. HTML数字比较大小游戏,Javascript 比较两个数大小并输出最大数
  2. 分享6 个值得收藏的 Python 代码
  3. python如何最适合web开发中的人工智能?
  4. evoc服务器长鸣报警显示正常,UPS电源故障灯亮,蜂鸣器长鸣报警怎么办
  5. 文件服务器搭建 xp,xp文件服务器搭建
  6. 解析postgresql 删除重复数据案例
  7. 了解下WSDL 绑定
  8. Linux shell脚本判断服务器网络是否可以上网
  9. 从业5年,教你学习Linux开发
  10. 单片机干嘛的?嵌入式是单片机吗?