Guru of the Week #5:虚函数的重新定义

作者:Hub Sutter
译者:plpliuly

/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第5篇,原文的版权是属于Hub Sutter(著名的C++专家,"Exceptional C++"的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者
*/

#5 虚函数的重新定义 (1997年5月14提出)
难度: 6/10

    虚函数是C++的一个基本特性,对不对?如果你能够回答下面的问题,那说明你确实已经掌握.

问题:
    假设你正在看一些很老的源码,其中有一段如下的代码,作者已经不知其人了.这个作者写这段代码好像仅仅是为了验证C++特性的工作机理而做的小实验.作者期望这个程序打印出什么样的结果呢,可实际上的输出结果又是怎样呢?
    #include <iostream>
    #include <complex>
    using namespace std;

    class Base {
    public:
        virtual void f( int ) {
            cout << "Base::f(int)" << endl;
        }

        virtual void f( double ) {
            cout << "Base::f(double)" << endl;
        }

        virtual void g( int i = 10 ) {
            cout << i << endl;
        }
    };

    class Derived: public Base {
    public:
        void f( complex<double> ) {
            cout << "Derived::f(complex)" << endl;
        }

        void g( int i = 20 ) {
            cout << "Derived::g() " << i << endl;
        }
    };

    void main() {
        Base    b;
        Derived d;
        Base*   pb = new Derived;

        b.f(1.0);
        d.f(1.0);
        pb->f(1.0);

        b.g();
        d.g();
        pb->g();

        delete pb;
    }

答案:

    首先,一些代码风格问题:
   
    1.void main()
    
    这并不是一个main函数的合法声明,尽管很多编译器都允许这样做.应该使用"int main()"或者"int main(int argc,char* argv[])".
    但是,请注意此处并不需要返回结果(尽管给外面的调用者返回一个报告错误的返回值是书写函数的好风格)...如果main没有返回语句,效果就和"return 0;"一样.

   2.delete pb;

   这看起来好像没有什么不妥,但是只有当Base类提供了虚拟析构函数才会没事.否则,通过一个没有虚拟析构函数的基类的指针来释放继承类的对象是危险的,因为,你能得到的结果就是出错.(译者注:至少是内存泄漏)
   [原则]将基类的析构函数定义为虚拟函数

   3.Derived::f( complex<double> )

   Derived类并没有重载Base::f...它仅仅是覆盖了它们.此处的区别是非常重要的,因为这意味着Base::f(int)和Base::f(double)对Derived类是不可见的!(注意:某些流行的编译器甚至对此连警告错误都不给.)
   [原则]当你在继承类中定义一个与基类中函数同名的函数,如果你不想将基类中的函数隐藏掉,你必须用"using"指示语句来将基类中的函数纳入继承类的scope中来,以便对继承类可见.

   4.Derived::g( int i = 10 )

   除非你真的想让别人迷糊,否则不要在继承类中改变函数的参数缺省值.(一般来讲,尽量采用函数重载来代替参数缺省值的改变是一个不错的方法.)当然,这是合法的C++语句,结果也是可预知的,但是,不要这样做.你可以看看下面的讨论中这样做会怎样的让别人迷惑.
  [原则]决不要改变基类中函数的参数缺省值.

   讨论完几个主要的代码风格问题后,让我们切入正题来看看程序是不是会如代码作者所希望的那样运行:

   void main() {
        Base    b;
        Derived d;
        Base*   pb = new Derived
;

        b.f(1.0);
   没问题.调用Base::f( double ).
   
        d.f(1.0);
  
此处调用Derived::f( complex<double> ).为什么?记住Derived类没有声明"using Base::f;",因此Base::f( int )和Base::f( double )不会被调用.
   代码作者也许希望调用后者,但是此处连一个编译错误都不会给出.comlex<double>有一个对于double类型的隐式转换(*),因此编译器将上面的语句看作是Derived::f( complex<double>(1.0) ).
 
   (*)根据目前的C++标准草案,这个转换构造函数不是explicit的

     pb->f( 1.0 );
  
有趣的是,尽管Base* pb指向一个Derived的对象,此处调用的是Base::f( double ),因为编译器的重载解释(overload resolution)是根据静态数据类型(此处就是Base),而不是动态类型(此处为Derived).
 
     b.g();
  
这条语句将打印"10",因为它调用了Base::g( int ),其参数的缺省值是10.没有疑问.

     d.g();
   
这条语句将打印"Derived::g()20",因为它调用了Derived::g( int ),其参数的缺省值是20.同样没疑问.

     pb->g();
  
这条语句将打印"Derived::g()10"...这可能会让你吓一大跳,百思不解.但编译器做得是很对的.需要记住的是,和函数重载一样,参数缺省值是从对象的静态类型(此处是Base)解释出来的,因此此处的参数缺省值取10.然而,此处的函数碰巧是虚函数,因此实际被调用的函数是由对象的动态类型(此处是Derived)决定的.
  
   如果你完全理解了上面的最后几段解释,你也就理解了我们今天讨论的问题.祝贺你.
 
      delete pb;
      }
  
此处的delete显然会让对象只是部分的释放,导致内存错误...看看上面讨论的虚拟构造函数就明白了.
----
(结束)

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
<p> <span style="font-size:14px;color:#337FE5;">【为什么学爬虫?】</span> </p> <p> <span style="font-size:14px;">       1、爬虫入手容易,但是深入较难,如何写出高效率爬虫,如何写出灵活性高可扩展爬虫都是一项技术活。另外在爬虫过程中,经常容易遇到被反爬虫,比如字体反爬、IP识别、验证码等,如何层层攻克难点拿到想要数据,这门课程,你都能学到!</span> </p> <p> <span style="font-size:14px;">       2、如果是作为一个其他行业开发者,比如app开发,web开发,学习爬虫能让你加强对技术认知,能够开发出更加安全软件和网站</span> </p> <p> <br /> </p> <span style="font-size:14px;color:#337FE5;">【课程设计】</span> <p class="ql-long-10663260"> <span> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 一个完整爬虫程序,无论大小,总体来说可以分成三个步骤,分别是: </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 网络请求:模拟浏览器行为从网上抓取数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据解析:将请求下来数据进行过滤,提取我们想要数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据存储:将提取到数据存储到硬盘或者内存中。比如用mysql数据库或者redis等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 那么本课程也是按照这几个步骤循序渐进进行讲解,带领学生完整掌握每个步骤技术。另外,因为爬虫多样性,在爬取过程中可能会发生被反爬、效率低下等。因此我们又增加了两个章节用来提高爬虫程序灵活性,分别是: </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 爬虫进阶:包括IP代理,多线程爬虫,图形验证码识别、JS加密解密、动态网页爬虫、字体反爬识别等。 </li> <li class="" style="font-size:11pt;color:#494949;"> Scrapy和分布式爬虫:Scrapy框架、Scrapy-redis组件、分布式爬虫等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 通过爬虫进阶知识点我们能应付大量反爬网站,而Scrapy框架作为一个专业爬虫框架,使用他可以快速提高我们编写爬虫程序效率和速度。另外如果一台机器不能满足你需求,我们可以用分布式爬虫让多台机器帮助你快速爬取数据。 </p> <p style="font-size:11pt;color:#494949;">   </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 从基础爬虫到商业化应用爬虫,本套课程满足您所有需求! </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;background-color:#FFFFFF;color:#337FE5;">【课程服务】</span> </p> <p> <span style="font-size:14px;">专属付费社群+定期答疑</span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"><br /> </span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"></span> </p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值