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

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

作者头像
bear_fish
发布2018-09-20 11:26:40
1.1K0
发布2018-09-20 11:26:40
举报

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 删除。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档