zoukankan      html  css  js  c++  java
  • C++_类继承3-动态联编和静态联编

    程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答这个问题。

           将源代码中的函数调用解释为特定的函数代码块被称为函数名联编(binding)

           在C语言中,这非常简单,因为每个函数名对应一个不同的函数。

           但是在C++中由于函数重载的缘故,这项任务非常复杂。编译器必须查看函数参数才能确定使用哪个函数。编译器可以在编译过程中完成联编,这被称作静态联编,又称为早期联编。然而,虚函数使这项工作变得更加困难。使用哪个函数不是在编译时就能确定的,因为编译器不知道用户将选择哪个类型的对象。所以编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编,又称为晚期联编

     

    指针和引用类型的兼容性

           将派生类引用或指针转换为基类引用或指针被称为向上强制转换(upcasting)。这使公有继承不需要进行显式类型转换。该规则是is-a关系的一部分。

           相反的过程——将基类指针或引用转换为派生类指针或引用——称为向下强制转换(downcasting)。如果不使用显式类型转换,向下强制转换是不允许的。

           is-a关系是不可逆的,派生类可以新增数据成员,因此使用这些数据成员的类成员函数不能应用于基类。

    虚成员函数和动态联编

           编译器对非虚方法使用静态联编。对虚方法采用动态联编。

          

    1、 为什么有两种类型的联编以及为什么默认为静态联编?

    这涉及到效率和概念模型。为了使程序能够在运行阶段进行决策,必须采用一些方法跟踪基类指针或引用指向的对象类型,这增加了额外的的处理开销。例如,如果这个类不用做基类,则不需要动态联编。如果派生类不重新定义基类的任何方法,也不需要使用动态联编。这些情况下使用静态联编更合理,效率也更高。因此被设置为C++的默认选择。C++的指导原则之一就是不要为不使用的特性付出代价(内存或处理时间)。仅当程序确实需要虚函数时,才使用它们。

    概念模型:仅将那些预期被重新定义的方法声明为虚的。如果要在派生类中重新定义基类的方法,则将它设置为虚方法;否则,设置为非虚方法。在设计类时,方法属于哪种情况有时候并不那么明显。与现实世界中的很多方面一样,类设计并不是一个线性过程。

     

    2、 虚函数的工作原理

     

    有关虚函数的注意事项

    n  在基类方法的声明中使用关键字virtual可使该方法在基类以及所有派生类(包括从派生类派生出来的类)当中都是虚的。

    n  如果使用指向对象的指针或引用来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编[j周2] 。这种行为非常重要,因为这样基类指针或引用可以指向派生类对象。

    n  如果定义的类将被用作基类,则应该将那些要在派生类中重新定义的类方法声明为虚的。

     

    1、 构造函数

    构造函数不能是虚函数,因为调用构造函数是明确的,创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数。然后,派生类的构造函数将调用基类的构造函数,这种顺序不同于继承机制。派生类不继承基类的构造函数,所以将派生类的构造函数声明为虚的没什么意义。

    2、 析构函数

    基类的析构函数必须是虚函数,除非不用作基类,因为这样编译器才知道调用对象类型对应的析构函数,而不是指针或引用类型对应的析构函数。通常应该给基类提供一个虚析构函数,即使它不需要析构函数。

    3、 友元

    友元不能是虚函数,因为友元不是类成员,只有成员才能是虚函数。

    4、 没有重新定义

    如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本,例外的情况是基类版本是隐藏的。[j周3] 

    5、 重新定义将隐藏方法

     

    class Dwelling

    {

    public:

           virtual void showperks(int a) const;

    };

     

    class Hovel :public Dwelling

    {

    public:

           virtual void showperks() const;

    }

     

    Hovel trump;

    trump.showperks();  //valid

    trump.showperks(5);  //invalid

    新定义将showperks()定义为一个不接受任何参数的函数。重新定义不会生成函数的两个重载版本。

    载版本,而是隐藏了接受一个int参数的基类版本。总之,重新定义继承的方法并不是重载。如果重新定义派生类中的函数,将不只是使用相同的函数参数列表覆盖基类声明,无论参数列表是否相同,该操作将隐藏所有的同名基类方法。

           总结两条经验:

    1、 如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变。因为允许返回类型随类类型的变化而变化。

    2、 如果基类声明被重载了,则应该在派生类中重新定义所有基类版本。如果只重新定义一个版本,则另外两个版本将被隐藏,派生类对象将无法使用它们。

     

    class Dwelling

    {

    public:

    // three overloaded showperks()

           virtual void showperks(int a) const;

           virtual void showperks(double x) const;

           virtual void showperks() const;

    };

     

    class Hovel :public Dwelling

    {

    public:

    // three redefined showperks()

           virtual void showperks(int a) const;

           virtual void showperks(double x) const;

           virtual void showperks() const;

    }

  • 相关阅读:
    2017面向对象程序设计寒假作业2!
    寒假学习计划
    2017面向对象程序设计寒假作业1!
    bzoj3583 杰杰的女性朋友
    poj1185 [NOI2001炮兵阵地]
    bzoj1009 [HNOI2008]GT考试
    EXKMP
    bzoj1355 [Baltic2009]Radio Transmission
    poj1275 Cashier Employment
    bzoj3809 Gty的二逼妹子序列
  • 原文地址:https://www.cnblogs.com/grooovvve/p/10422296.html
Copyright © 2011-2022 走看看