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

将源代码中的函数调用解释为特定的函数代码块被称为函数名联编(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;

}

C++_类继承3-动态联编和静态联编的更多相关文章

  1. C++_类继承6-继承和动态内存分配

    如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将怎样影响派生类的实现?这个问题的答案取决于派生类的属性.如果派生类也使用动态内存分配,那就需要注意学习新的小技巧. 派生类不适用new // ...

  2. C++_类继承5-抽象基类

    abstract base class,ABC 抽象基类 有时候is-a规则并不像看上去那么简单,例如圆和椭圆的关系.圆是椭圆的特殊情况.椭圆可以派生出圆.但是椭圆的数据成员及方法对于圆来说是信息冗余 ...

  3. C++_类继承1-从一个简单的类开始

    面向对象编程的主要目的之一是:提供可重用的代码.尤其是项目很庞大的时候,重用测试过的代码比重新编码代码要好得多. C++提供了更高层次的重用性.其中之一就是继承这个概念. 一些厂商提供了类库.类库由类 ...

  4. C++_类继承7-类设计回顾

    编译器生成的成员函数 编译器会自动生成一些公有的成员函数——特殊成员函数. 1. 默认构造函数 提供构造函数的动机之一是确保对象总能被正确地初始化.如果类包含指针成员,则必须初始化这些成员.最好提供一 ...

  5. C++_类继承4-访问控制protected

    public和private来控制对类成员的访问. 还存在另外一个访问类别,这种类别用关键字protected表示.protected和private相似,在类外只能用公有类成员来访问protecte ...

  6. C++_类继承2-多态公有继承

    有时候希望同一个方法在派生类和基类中的行为是不同的.换句话说,方法的行为取决于调用该方法的对象.这种较复杂的行为称为多态——具有多种形态.即同一种方法其行为随上下文而异.有两种重要的机制可用于实现多态 ...

  7. C++的静态联编和动态联编详解

    一.概述: 通常来说联编就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址,它是计算机程序彼此关联的过程.按照联编所进行的 ...

  8. C++的静态联编和动态联编

    联编的概念 联编是指一个计算机程序自身彼此关联的过程,在这个联编过程中,需要确定程序中的操作调用(函数调用)与执行该操作(函数)的代码段之间的映射关系. 意思就是这个函数的实现有多种,联编就是把调用和 ...

  9. C++ Primer Plus 学习之 类继承

    主要介绍了类的继承.虚函数.类继承的动态内存分配问题.继承与友元函数. 公有派生 基类的公有成员和私有成员都会成为派生类的一部分. 基类的私有成员只能通过基类的公有或者保护方法访问.但是,基类指针或引 ...

随机推荐

  1. Leetcode:Two Sum分析和实现

    问题表示提供一个整数数组nums,以及一个目标target,要找到两个下标i与j,使得nums[i] + nums[j] = target. 最简单的思路是两次循环: for a in nums fo ...

  2. 自制模仿apache访问日志文件格式的php日志类

    <?php // 访问日志写入类 @author 王伟 2011.12.14class Log{        //项目跟路径    private $root_path;        //日 ...

  3. 605. Can Place Flowers零一间隔种花

    [抄题]: Suppose you have a long flowerbed in which some of the plots are planted and some are not. How ...

  4. c语言实践 1/1-1/2+1/3-1/4+...

    其实这个题目和上面那个是一样的 /* 1/1-1/2+1/3-1/4+...1/n; */ int n = 1; double sum = 0; double frac = 0; int i = 1; ...

  5. 初次接触URDF

    使用URDF创建机器人3D仿真模型 在真实的机器人上编程可以更好地让我们理解机器人的控制方式,因为真实的机器人会有反馈.如果没有真实的机器人,那么ROS仿真是一个很好的选择. ROS通过URDF(Un ...

  6. Part3_lesson4---协处理器访问指令

    1.什么是协处理器? CP15是协处理器, CP15的作用:系统控制协处理器CP15,它提供了额外的寄存器,这些寄存器用于配置和控制cache,MMU,保护系统,时钟模式,和其他的系统项,比如大小端操 ...

  7. c#并发编程经典实例文摘

    第1章 并发编程概述 1.1 并发编程简介 并发: 多线程(包括并行处理) 异步编程(异步操作)程序启动一个操作,而该操作将会在一段时间后完成 响应时编程(异步事件)可以没有一个实际的开始,可以在任何 ...

  8. Monkey基础命令

    最近一直在看关于自动化测试的文章和工具,这是之前学习monkey的一些知识,想总结一下,方便以后查看,当然也可以提供一些参考.monkey 适合做压力测试,我们可以发送命令让它自己运行,并且指定运行动 ...

  9. PC/APP/H5三端测试的相同与不同

    随着手机应用的不断状态,同一款产品的移动端应用市场占相较PC端也越来越大,那么app与PC端针对这些产品的测试有什么相同与不同之处呢?总结如下: 首先谈一谈相同之处: 一,针对同一个系统功能的测试,三 ...

  10. 当Linux用尽内存

    Mulyadi Santosa 也许你很少面临这一情况,但是一旦如此,你一定知道出什么错了:可用内存不足或者说内存用尽(OOM).结果非常典型:你不能再分配内存,内核会杀掉一个任务(一般是正在运行那个 ...