• 【转】c++重载、覆盖、隐藏——理不清的区别


    原文网址:http://blog.sina.com.cn/s/blog_492d601f0100jqqm.html

    再次把林锐博士的《高质量c++编程指南》翻出来看的时候,再一次的觉得这是一本难得的好书。实践派写的东西跟理论派和翻译派写的书有着本质的 区别,每次读这本书都觉得为什么自己读了这么多遍,还是会犯一些上面讲的小错误,编代码有时候莫名其妙又会把自己转糊涂了。这本书浅显易懂,而且提到了编 程过程中应该注意的很多细节,里面展开来讲的细节又偏偏是我觉得最为薄弱的环节,如果大家想学或者正在学习c++。建议大家用心的把这本并不长也不高深的 书好好的读几遍。

       关于c++语言中重载、覆盖、隐藏这三个容易混淆的概念,我依然以林博士书中的例子发散开来回忆一下。先列举这三个概念必须满足的条件:
     函数重载的特征:
      1)处在相同的空间中(即相同的作用范围内,比如一个类中)。
      2)函数名相同。
      3)参数不同(相同位置参数的类型不同,或者参数的个数不同)。
      4)virtual关键字可有可无。
     
     覆盖的特征:
      1)不同的范围(例如分别位于基类与派生类)。
      2)函数名相同。
      3)参数相同(参数个数与类型均相同)。
      4)基类函数必须有virtual关键字(派生类可有可无,因为基类函数被声明为虚函数,派生类同名函数一定也是虚函数)。
     
     隐藏的特征:
      1)不同的范围(例如分别位于基类与派生类)。
      2)函数名相同。
      3)参数可相同也可不同(注意此处还有两种情况)。
      4)virtual关键字可有可无。
     注意:隐藏与覆盖的区别就在于如下两条:
      1)如果派生类的函数与基类的函数同名,但是参数不同(不可能构成覆盖)。此时无论有无virual关键字,基类的函数将被隐藏。(不可能构成重载,因为重载必须在同一个类中)
      2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virual关键字(如果有virual关键字,则满足覆盖的条件)。此时基类的函数被隐藏。
     
    隐藏:
    下面是林博士原书中的例子:
     #include <iostream.h>
     class Base
     {
         public:
                  void g(float x){cout << "Base::g(float)" << x <<endl;}       //注意c++中将在类声明中定义了实现的函数自动默认为内联函数
                  void h(float x){cout << "Base::h(float)" << x <<endl;}
     }
     class Derived : public Base
     {
         public:
                  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;
              //behavior depends on type of pointer
              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;
     }
      参照隐藏规则,派生类的成员函数隐藏了基类的同名函数。所谓隐藏就是指派生类类型的对象、引用、指针访问基类和派生类都有的同名函数的时候,访问的是派生 类的函数,隐藏了基类同名函数。派生类既然自动继承了基类的成员,那么基类成员就可以被派生类直接访问,那么为什么访问的是派生类的成员函数呢?所以隐藏 规则实际上就是默认的c++名字解析过程。
      在继承机制下,派生类的类域被嵌套在基类的类域中,派生类的名字解析过程如下:
      1)首先在派生类中查找改名字。
      2)如果第一步未查找到,及派生类的类域对改名字无法进行解析,则编译器在外围基类类域查找改名字的定义。
      所以准确来说,当派生类和基类有同一名字的成员时,派生类成员是隐藏了对基类成员的直接访问。那么如果访问到基类同名成员呢?加上类作用域限定例如:Base::g(float)就可以访问了。
     
    覆盖:
       覆盖规则造成的调用现象,其实就是类的虚函数实现原理生成的。为了达到动态绑定(后期绑定)的目的,C++编译器通过某个表格(一般称为vtable), 在执行期"间接"调用实际上欲绑定的函数。每一个内含虚函数的类,C++编译器都会为它做出一个虚函数表,表中的每一个元素都指向一个虚函数的地址。 
     
       举个例子:
        class base{
        public:
            func();
            virtual vfunc1();
            virtual vfunc2();
            virtual vfunc3();
        private:
            int _data1;
            int _data2;
        };
        base对象实例在内存中占据的空间是这样的:
         base对象实例          vtable
    --------------------------------------------------------------------------
             vptr ---------> (*vfunc1)() -----------> base::vfunc1();
            _data1           (*vfunc2)() -----------> base::vfunc2();
            _data2           (*vfunc3)() -----------> base::vfunc3();
    --------------------------------------------------------------------------
     
        当派生类改写了虚函数时,虚函数表相应的被修改了:
        class derived: public base{
        public:
            vfunc2();
        };
        derived对象实例              vtable
    --------------------------------------------------------------------------
             vptr  ---------> (*vfunc1)() -----------> base::vfunc1()      
            _data1;           (*vfunc2)() -----------> derived::vfunc2()     ****注意,这里变了!!!***
            _data2;           (*vfunc3)() -----------> base::vfunc3()
    --------------------------------------------------------------------------
     
        所以当你写下如下程序的时候:
        void main(void)
        {
            Derived d;
            Base *pb = &d;
            pb->vfunc2(); // Derived::vfunc2(void)
        } 
        就不难理解为何pb->vfunc2()调用的是derived::vfunc2()了,因为pb实际上指向派生类derived的实例,而派生类中的虚函数表已经被修改了。
     
        总结:简单来说,隐藏规则就是C++的名字解析过程,自里向外解析,这个好理解;而覆盖规则其实就是C++虚函数表的实现原理。
  • 相关阅读:
    JDK源代码学习-基础类
    六、Java多人博客系统-2.0版本-代码实现
    JDK源代码学习-ArrayList、LinkedList、HashMap
    Java拓展接口-default关键词
    七、Java多人博客系统-2.0版本-docker部署
    五、Java多人博客系统-2.0版本-数据库设计
    四、Java多人博客系统-2.0版本
    max file descriptors [10240] for elasticsearch process is too low, increase to at least [65535]
    system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
    which: no java in (/sbin:/usr/sbin:/bin:/usr/bin) Could not find any executable java binary. Please install java in your PATH or set JAVA_HOME
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4325336.html
Copyright © 2020-2023  润新知