学习C++Prime第十五章第三节--虚函数

3、虚函数

  如前所述,在C++语言中,当我们使用基类的引用或指针调用一个许成源函数时会执行动态绑定。因为我们直到运行时才能直到到底调用了哪个版本的虚函数,所以所有虚函数都必须有定义。通常情况下,如果我们不使用某个函数,则无须为该函数提供定义。但是我们必须为每一个虚函数都提供定义,而不管它是否被用到了,这是因为脸编译器也无法确定到底会使用哪个虚函数。

对虚函数的调用可能在运行时才被解析

  当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数时与绑定到指针或引用上的对象的动态类型相匹配的那一个。
  必须要搞清楚的一点是,动态绑定只有当我们通过指针或引用调用虚函数时才会发生。当我们通过一个具有普通类型(非应用非指针)的表达式调用虚函数时,在编译时就会将调用的版本确定下来。

关键概念:C++的多态性

  OOP的核心思想是多态性。多态性这个词源自希腊语,其含义是“多种形式”。我们把具有继承关系的多个类型称为多态类型,因为我们能使用这些类型的“多种形式”而无须在意它们的差异。引用或这阵的静态类型与动态类型不同这一事实正是C++语言支持多态性的根本所在。
  当我们使用基类的引用或指针调用基类中定义的一个函数时,我们并不知道该函数真正作用的对象是什么类型,因为它可能是一个基类的对象也可能是一个派生类的对象。如果该函数是虚函数,则直到运行时才会决定到底执行哪个版本,判断的依据是引用或指针所绑定的对象的真实类型。
  另一方面,对非虚函数的调用是在编译时进行绑定。类似的,通过对象进行的函数(虚函数或非虚函数)调用也在编译时绑定。对象的类型是确定不变的,我们无论如何都不可能令对象的动态类型与静态类型不一致。因此,通过对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本上。

NOTE:当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型此案有可能与静态类型不同。

派生类中的虚函数

  当我们在派生类中覆盖了某个虚函数时,可以再一次使用virtual关键字指出该函数的性质。然而这么做并非必须,因为一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数。
  一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。
  同样,派生类中虚函数的返回类型也必须与基类函数匹配。该规则存在一个例外,当类的虚函数返回类型是类本身的指针或引用时,上述规则无效。也就是说,如果D由B派生得到,则基类的虚函数可以返回B而派生类的对象函数可以返回D,只不过这样的返回类型要求从D到B的类型转换是可访问的。
NOTE:基类中的虚函数在派生类中隐含地也是一个虚函数。当派生类覆盖了某个虚函数时,该函数在基类中的形参必须与派生类中的形参严格匹配。

final和override说明符

  派生类如果定义了一个函数与基类中虚函数的名字相同但是形参列表不同,这仍然是合法的行为。编译器将认为新定义的这个函数与基类中原有的函数时相互独立的(重载)。这时,派生类的函数并没有覆盖掉基类中的版本。就实际的编程习惯而言,这种声明往往意味着发生了错误,因为我们可能原本希望派生类能覆盖掉基类中的虚函数,但是一不小心把形参列表弄错了。
  要想调试并发现这样的错误显然非常困难。在C++11新标准中我们可以使用override关键字来说明派生类中的虚函数。这么做的好处是在使得程序员的意图更加清晰的同时让编译器可以为我们发现一些错误,后者在编程实践中显得更加重要。如果我们使用override标记了某个函数,但该函数没有覆盖已存在的虚函数,此时编译器将报错。
  只有虚函数才能被覆盖,所以非虚函数的覆盖将会报错。
  如果我们已经把函数定义成final了,则之后任何尝试覆盖该函数的操作都将会引发错误。

final和override说明符出现在形参列表(包括任何const或引用修饰符)以及尾置返回类型之后。

虚函数与默认实参

  和其他函数一样,虚函数也可以拥有默认实参。如果某次函数调用使用了默认实参,则该实参由本次调用的静态类型决定。
  换句话说,如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。此外,传入派生类函数的将是基类函数定义的默认实参。如果派生类函数依赖不同的实参,则程序结果将与我们的预期不符。
NOTE:如果虚函数使用默认实参,则基类和派生类中定义的默认参数最好一致。

回避虚函数的机制

  在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。使用作用于运算符可以实现这一目的,例如下面的代码:

//强行调用基类中定义的函数版本而不管baseP的动态类型到底是什么
double undiscounted = baseP->Quote::net_price(42);

该代码强行调用Quote的net_price函数,而不管baseP实际指向的对象类型到底是什么。该调用将在编译时完成解析。

NOTE:通常情况下,只有成员函数(或友元)中的代码才需要使用作用域运算符来回避虚函数的机制。
  什么时候我们需要回避虚函数的默认机制呢?通常是当一个派生类的虚函数调用它覆盖的基类的虚函数版本时。在此情况下,基类的版本通常完成继承层次中所有类型都要做的共同任务,而派生类中定义的版本需要执行一些与派生类本身密切相关的操作。

WARNING:如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工地码哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值