前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C++的函数隐藏、覆盖和重载

C++的函数隐藏、覆盖和重载

作者头像
bear_fish
发布于 2018-09-20 03:26:40
发布于 2018-09-20 03:26:40
1.3K0
举报

http://blog.csdn.net/lin49940/article/details/5553664

     看了 林锐 的  《高质量编程指南》8.2.2 令人迷惑的隐藏规则.  (这里的隐藏是指派生类的函数屏蔽了与其同名的基类函数)

     这一节写得很好: 1. 把出现隐藏的情况列举出来了.

                     2. 举的例子很贴切, 让人能更好的理解. 

                     3. 对出现隐藏函数情况的理解.

                     4. 提出对应的解决方案. 

  •  如果派生类的函数与基类的函数同名, 但是参数不同. 此时, 不论有无 virtual 关键字, 基类的函数将被隐藏(注意别与重载混淆).
  •  如果派生类的函数与基类的函数同名, 并且参数也相同, 但是基类函数没有 virtual 关键字. 此时, 基类的函数被隐藏(注意别与覆盖混淆). 

      就是以上两种情况导致了函数隐藏的情况出现. 看看书里的例子:

#include <iostream> using namespace std; 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;}     };

int main() {     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     //出现隐藏的情况 1     pb->g(3.14f);             //Base::g(float) 3.14     pd->g(3.14f);             //Derived::g(int)  3       (surprise!)     //出现隐藏的情况 2     pb->h(3.14f);             //Base::h(float) 3.14         (surprise!)     pd->h(3.14f);             //Derived::h(float)  3.14

    system("pause");     return 0; }


  •  如果派生类的函数与基类的函数同名, 并且参数也相同, 但是基类函数没有 virtual 关键字. 此时, 基类的函数被隐藏(注意别与覆盖混淆). 

          //出现隐藏的情况  pb->h(3.14f);             //Base::h(float) 3.14         (surprise!)           pd->h(3.14f);             //Derived::h(float)  3.14

个人看法:

     如果你学过 java 的多态, 对这个结果应该很难接受.

     Derived 对象d 被隐式转换为 Base 对象, 那么该 Base 对象跟Derived 对象d 同名的函数被 Derived 对象d 覆盖. 所以两者的执行结果应该是一样的.

     但是这里是 C++, 不是 java. 对于C++ 来说, 如果 Base 类的某个函数没有 virtual 关键字, 那该函数跟 Derived 类的同名函数(参数也相同)是没有什么关系的. 

     这个请看下 《C++ Primer》501页下面的"关键概念: 名字查找和继承".

     pb 是 Base 类指针,  pb指针 绑定到 Derived 对象 d,  但是由于 Base 类的 h(float) 函数不是虚函数,  无论实际对象是什么类型, 都执行 Base::h(float). 

      程序会直接在 Base 类中寻找 h 函数; 如果没有 h 函数, 那就会去其父类中寻找 h 函数 ; 如果还是找不到 h 函数 , 那就会去其父类的上一层类中继续寻找 h 函数 ; 一次类推, 一直到找到方法A 为止; 如果最终都找不到, 你的程序应该是不能通过编译的!(这种查找方式倒是跟 java 一样)

      java 的函数是没有 virtual 关键字的, 但是派生类和基类只要函数名和参数相同, 那么该函数就被覆盖了. 如果反过来想, 相对于 C++, 那不是 java 的每个函数都是虚函数吗?  可能C++ 在于效率上考虑, 不想所有的函数都使用动态联编.


  •  如果派生类的函数与基类的函数同名, 但是参数不同. 此时, 不论有无 virtual 关键字, 基类的函数将被隐藏(注意别与重载混淆).

          //出现隐藏的情况 1          pb->g(3.14f);             //Base::g(float) 3.14          pd->g(3.14f);             //Derived::g(int)  3       (surprise!)

个人看法:

这个其实也不能说是隐藏, 因为 g(float) 和 g(int) 是不同的函数, C++编译后在符号库中的名字分别是 _g_float 和 _g_int.即使他们都有 virtual 关键字, 但是因为是分别存在与派生类和基类中的不同函数, 所以在不存在覆盖的关系(重载更不可能).

     pb 是 Base 类指针,  pb指针 绑定到 Derived 对象 d, Base 类根本就没有 g(int) 函数, 所以 pd 指针是总不可能去调用 Derived::g(int) 函数的.

      pb->g(3.14f);  程序在 Base 类中找到匹配的函数 Base::g(float) , 然后调用这个函数.

      pd->g(3.14f);             //Derived::g(int)  3       (surprise!)

      编译先在 Derived 类中查找匹配 g(3.14f) 的函数,  他找到了 g(int) , 并且在 Derived 类中只有一个函数匹配. 即使 g(int) 是 virtual 的, 但pd 指针指向的 Derived 对象 d 的 g(int) 函数跟 Derived 类的 g(int) 函数是一样的, 调用的都是 Derived::g(int) 函数, 所以不存在多态, 也就无需动态联编了. (需要动态联编的条件请看《C++ Primer》15.2.4 "virtual 与其他成员函数" 开头部分, 这里之所以无需动态联编, 是因为不满足动态联编的第二个条件).

      即使 Base 类有匹配的函数virtual g(float x), 但是virtual g(float x) 是存放在 Derived 对象 d 的虚函数表(virtual function table, vtbl, plus 13.4.4) 中的, 如果不进行动态联编, 程序不会去 vtbl 中查找对应的函数地址, vtbl 中的函数地址是不会被引用到的, 也就不会被调用了.

      所以把 Base 类的 g(float x) 加上 virtual 关键字, 结果不会改变; 再把 Derived 类的 g(int)  加上 virtual 关键字, 结果也是不变的.

       如果 Derived 类添加一个函数 virtual void g(float x){cout << "Derived::g(float) "  << x << endl;}, 把 Base 类的 g(float x) 加上 virtual 关键字.

       那结果就是

                      pb->g(3.14f);             //Derived::g(float) 3.14                       pd->g(3.14f);             //Derived::g(float) 3.14  

        pb->g(3.14f)

        pb 是 Base 类指针,  pb指针 绑定到 Derived 对象 d. 由于 Base 类的 g(float) 函数的 virtual 的, 并且是 Base 类指针调用  g(float) 函数, pb指针绑定的对象 d 的静态类型是 Derived 类, Derived 类的 g(float) 函数也是 virtual 的, 通常只有在运行程序时才能确定对象的动态类型.  所以编译器对 虚函数 g(float) 使用动态联编.

         因为 Derived 类提供了虚函数 g(float) 的新定义, 所以在 Derived 对象 d 的虚函数表(vtbl) 中g(float) 函数的地址保存为 Derived::g(float) 函数的地址. pb 指针调用虚函数 g(float) 时候, 程序到 Derived 对象 d 的虚函数表(vtbl) 中查找 g(float) 函数的地址, 然后就执行该地址的函数. 所以 pb->g(3.14f)  执行了 Derived::g(float) 函数. 


      说起来, 子类要重载父类的方法, 还真是麻烦呢, 难道要全部方法copy 过来? 其实也不必要呢, 如果是子类对象能隐式转换父类对象, 但是子类自有的方法, 对于基类对象来说是不存在的, 基类对象当然也不能调用这些方法了. 所以呢, 子类不必要重载父类的方法, 建一个属于自己的方法还更好! 

  virtual 关键字, 好像就是在告诉你, 我这个函数可以给派生类同名字同参数的函数覆盖; 纯虚函数更是直接告诉派生类, 你一定要写一个同名字同参数的函数覆盖我,  哈哈!

重要查考: 《C++ Primer》第480页 "关键概念:C++ 中的多态性".

《C++ Primer plus》13.4.4 虚拟成员函数和动态联编.

               《C++ Primer plus》第 449 页 "虚函数的工作原理".

               《C++ Primer》15.2.4 virtual 与其他成员函数.

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015年09月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
重载(overload)、覆盖(override)、隐藏(hide)的区别
这三个概念都是与OO中的多态有关系的。如果单是区别重载与覆盖这两个概念是比较容易的,但是隐藏这一概念却使问题变得有点复杂了,下面说说它们的区别吧。 重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。 覆盖(也叫重写)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。 隐藏是指派生类中的函数把基类中相同名字的函数屏蔽掉了。隐藏与另外两个概念表面上看来很像,很难区分,其实他们的关键区别就是
Linux云计算网络
2018/01/10
2.6K0
浅谈C++多态性
C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。我们今天就会为大家具体介绍当中C++多态性的一些基本知识,以方便大家在学习过程中对此可以有一个充分的掌握。   多态性能够简单地概括为“一个接口,多种方法”,程序在执行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphisn),字面意思多种形状。   C++多态性是通过虚函数来实现的,虚函数同意子类又一次定义成员函数,而子类又一次定义父类的做法称为覆盖(override),或者称为重写。(这里我认为要补充,重写的话能够有两种,直接重写成员函数和重写虚函数,仅仅有重写了虚函数的才干算作是体现了C++多态性)而重载则是同意有多个同名的函数,而这些函数的參数列表不同,同意參数个数不同,參数类型不同,或者两者都不同。编译器会依据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。 多态与非多态的实质差别就是函数地址是早绑定还是晚绑定。假设函数的调用,在编译器编译期间就能够确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而假设函数调用的地址不能在编译器期间确定,须要在执行时才确定,这就属于晚绑定。   那么多态的作用是什么呢,封装能够使得代码模块化,继承能够扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的到底是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
全栈程序员站长
2022/07/13
4120
60秒问答:多态和函数重载的关系?
运行时的多态:通过类继承和虚函数实现的(根据虚表指针 指向 派生类的函数,还是基类的函数)
早起的鸟儿有虫吃
2021/07/22
1.5K0
C++隐藏规则
在面向对象的开发过程中,经常出现类的继承,这里面出现的成员函数的重载(overload)、覆盖(override)与隐藏(hidden)很容易混淆。
全栈程序员站长
2022/07/15
2330
C++中函数重载、隐藏、覆盖和重写的区别
C++规定在同一作用域中,同名函数的形式参数(指参数的个数、类型或者顺序)不同时,构成函数重载。
恋喵大鲤鱼
2018/08/03
8.5K1
万字长文【C++】高质量编程指南
1,应用 ifndef/define/endif结构产生预处理块的目的是:防止头文件被重复引用。
用户9831583
2022/06/16
1.6K0
深入了解C++虚函数
虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。 作用: C++ “虚函数”的存在是为了实现面向对象中的“多态”,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数。通过动态赋值,实现调用不同的子类的成员函数(动态绑定)。正是因为这种机制,把析构函数声明为“虚函数”可以防止内存泄露。
Sky_Mao
2020/07/24
6570
深入了解C++虚函数
虚函数
  虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public(可以定义为private or proteceted, 但是对于多态来说,没有意义。),在基类的类定义中定义虚函数的一般形式:
全栈程序员站长
2022/09/06
9340
虚函数
C++:41---覆盖和隐藏
C++类中覆盖与隐藏一直是一个容易理解出错的地方,接下来我就详细讲解一下区别在何处
用户3479834
2021/02/03
4630
C++:46---绝不重新定义继承而来的non-virtual函数
一、看一个隐藏non-virtual函数的例子 假设class D以public的方式继承于class B,代码如下: class B { public: void mf(); }; class D :public B {}; int main() { D x; B *pB = &x; pB->mf(); //调用B::mf() D *pD = &x; pD->mf(); //调用D::mf() return 0; } 二、静态绑定与动态绑定 关于静态绑定、动态绑定的概念之前,大家先了解下静态类型的
用户3479834
2021/02/03
4980
C++关键知识点梳理
类似于函数,但是其()中的参数不是真的函数参数,在编译器进行宏展开时对()里的参数进行"一对一"的替换。
liddytang
2023/03/08
1K0
多态性 - C++中实现运行时多态的方式
C++中的多态性是指同一个函数可以有多种不同的实现方式,并且在运行时根据实际情况进行选择执行。在C++中实现多态有两种方式:静态多态和动态多态。静态多态是指在编译时确定函数的实现,包括函数重载和模板函数;动态多态是指在运行时根据对象的实际类型来确定函数的实现,包括虚函数和抽象类。
很酷的站长
2023/09/01
4810
多态性 - C++中实现运行时多态的方式
【C++】模板/继承/多态
基类对象 < -派生类对象 类型从下到上的转换(可以) 派生类对象 <- 基类对象 类型从上到下的转换(不可以) 基类指针(引用)<- 派生类对象 类型从下到上的转换(可以) 派生类指针(引用)<-基类对象 类型从上到下的转换(不可以)
洁洁
2024/09/05
1550
C++多态
在 C++ 程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中,一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法);也就是说,每个对象可以用自己的方式去响应共同的消息所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。换言之,可以用同样的接口访问功能不同的函数,从而实现“一个接口,多种方法”。在C++中主要分为静态多态和动态多态两种,在程序运行前就完成联编的称为静态多态,主要通过函数重载和模板实现,动态多态在程序运行时才完成联编,主要通过虚函数实现。
范中豪
2021/05/19
1.9K0
C++多态
【C++ 多态】—— 礼器九鼎,釉下乾坤,多态中的 “风水寻龙诀“
同样有关运算符重载的知识这里也不再过多讲解,在类和对象中已经讲的很详细了,忘了就快去复习一下吧!! 👉 C++ 类和对象 进阶篇
换一颗红豆
2025/04/01
600
【C++ 多态】—— 礼器九鼎,釉下乾坤,多态中的 “风水寻龙诀“
【专业技术】C++虚函数的缺省参数
编者按:缺省参数,缺省函数,缺省构造函数,如果你有些迷糊,本文可以让你把它看个清楚。呵呵。 前些日子,有个同学问我一个关于虚函数的缺省参数问题。他是从某个论坛上看到的,但是自己没想通,便来找我。现在分享一下这个问题。先看一小段代码: #include <iostream> using namespace std; class A { public: virtual void Fun(int number = 10) { cout <<
程序员互动联盟
2018/03/14
1.3K0
关于虚函数的学习思考
为什么需要虚函数?为的是实现类的多态特性,能够使同一个方法在派生类与基类的产生不同的行为。 这对于涉及类引用/指针操作的处理是很有帮助的, 如果不使用关键字virtual,那么程序将根据引用/指针的类型去选择方法;反之如果使用了virtual,那么程序将根据引用/指针的对象去选择方法。这背后考虑了类继承is-a中的机制。 即基类的指针/引用既可以指向基类对象,也可以指向派生类对象,反之不成立,请注意这样的关系是单向的。但它是合理的,因为派生类有新的数据成员与成员函数,而对于新成员,基类往往没有能提供处理的相应函数。 虚函数的工作原理是为对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,这种数组成为虚函数表(virtual function table, vtbl)。在调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表。这也暗示了使用虚函数,会带来一定程序的内存和时间开销。 虽然非虚函数的效率比虚函数高,但是非虚函数不具备动态联编功能。 什么是动态联编?首先了解一下函数名联编。函数名联编是指将函数调用解释为执行特定的函数代码块。在编译过程中进行联编成为静态联编。然而很可能在执行阶段,我们才确定需要使用哪一个函数,因此我们需要动态联编,即指编译器生成在程序运行阶段执行正确的虚函数的代码的行为。
_xiaoSu
2022/11/08
2120
【C++】虚函数指针和虚函数列表
本篇文章主要来讲述,C++多态的实现原理,也就是虚函数和虚函数列表是怎么回事?它们是如何实现多态的?
灰子学技术
2020/04/02
1.6K0
C++虚函数
虚函数是动态多态性的基础,其调用的方式是动态联编(又称晚期联编,简单解释为只有在程序运行时才决定调用基类的还是子类的,系统会根据基类指针所指向的对象来决定要调用的函数);
卡尔曼和玻尔兹曼谁曼
2019/01/25
1.2K0
C++虚函数
动态联编实现原理分析
所谓动态联编,是指被调函数入口地址是在运行时、而不是在编译时决定的。C++语言利用动态联编来完成虚函数调用。C++标准并没有规定如何实现动态联编,但大多数的C++编译器都是通过虚指针(vptr)和虚函数表(vtable)来实现动态联编。 基本的思路是: (1)为每一个包含虚函数的类建立一个虚函数表,虚函数表的每一个表项存放的是个虚函数在内存中的入口地址;
恋喵大鲤鱼
2018/08/03
4610
动态联编实现原理分析
相关推荐
重载(overload)、覆盖(override)、隐藏(hide)的区别
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档