先给出文字说明,然后再给出代码解释:
如果我们决定改写基类所提供的虚拟函数,那么派生类所提供的新定义,其函数型别必须完全符合基类所声明的函数原型,包括:参数列、返回型别、常量性(const-ness)。
下面给出程序说明:基类num_sequence中声明虚拟函数what_am_i(),派生类中改写该函数。
1、正确的写法
1.1 基类的声明
1 #pragma once
2
3 class num_sequence
4 {
5 public:
6 num_sequence(void);
7 virtual const char* what_am_i() const { return "num_sequence \n"; } //注意这里的两个const
8 virtual ~num_sequence(void);
9 };
1.2 派生类中正确的改写
#pragma once
#include "num_sequence.h"
class Fibonacci :
public num_sequence
{
public:
Fibonacci(void);
virtual const char* what_am_i() const { return "Fibonacci \n"; } //同样注意这里的两个const,少哪个都不行,
//后面详解少其中任何一个const的运行情况
~Fibonacci(void);
};
上述是正确的改写,下面给出两种缺少const的错误改写:
2.1 少 函数后面的const(即第二个const)
Wrong1
1 #pragma once
2 #include "num_sequence.h"
3
4 class Fibonacci :
5 public num_sequence
6 {
7 public:
8 Fibonacci(void);
9 virtual const char* what_am_i() { return "Fibonacci \n"; } //注意这里少了const
10 ~Fibonacci(void);
11 };
main函数中测试:
1 // EssentialCppP162.cpp : 定义控制台应用程序的入口点。
2 //
3
4 #include "stdafx.h"
5
6 #include "Fibonacci.h"
7 #include <iostream>
8
9 using namespace std;
10 int _tmain(int argc, _TCHAR* argv[])
11 {
12 Fibonacci b;
13 num_sequence p;
14 num_sequence *pp = &b;
15 cout << pp->what_am_i();
16 cout << b.what_am_i();
17 return 0;
18 }
输出的结果为:
num_sequence
Fibonacci
请按任意键继续. . .
解释:这里子类Fibonacci中并没有改写基类的what_am_i(),而是重新定义了一个what_am_i()函数(PS:这里说成是重载what_am_i()更合适)。所以尽管pp是指向子类的指针,但子类没有重定义该虚函数,最后就调用的是基类的what_am_i()函数,输出num_sequence。而b.what_am_i()则因为b为非const,会调用Fibonacci中的what_am_i())。(PPS:这里如果是const num_sequence *pp = &b; cout << pp->what_am_i(); 输出也是num_sequence,原因不说了。)
PS:这里Essential C++ P161上说的是在 Intel C++编译器上编译时,会输出警告: warning #653: "const char *Fibonacci::what_am_i()" does not match "num_sequence::what_am_i" -- virtual function override intended?
但我在VS200中文版中测试时候完全没有警告,所以写改写基类虚拟函数时候一定要小心,尽量用ctrl+c从基类中复制过来,防止手动敲入函数名字时出错。
2.2 少函数返回类型中的的const(即前面的那个const)
Wrong2
1 #pragma once
2 #include "num_sequence.h"
3
4 class Fibonacci :
5 public num_sequence
6 {
7 public:
8 Fibonacci(void);
9 virtual char* what_am_i() const { return "Fibonacci \n"; }
10 ~Fibonacci(void);
11 };
此种情况编译器不会通过编译,因为函数重载不是根据返回类型来定的,所以编译器会认为这里的what_am_i()是继承的基类的函数。然后根据本篇文章开头说的函数型别必须完全符合基类的声明,这里就会报错。VS2008下报错为:
1>e:\vsprog\临时测试文件夹\essentialcppp162\essentialcppp162\fibonacci.h(11) : error C2555: “Fibonacci::what_am_i”: 重写虚函数返回类型有差异,且不是来自“num_sequence::what_am_i”的协变
1>e:\vsprog\临时测试文件夹\essentialcppp162\essentialcppp162\num_sequence.h(7) : 参见“num_sequence::what_am_i”的声明
2.3 两个const都少了
Wrong3
1 #pragma once
2 #include "num_sequence.h"
3
4 class Fibonacci :
5 public num_sequence
6 {
7 public:
8 Fibonacci(void);
9 virtual char* what_am_i() { return "Fibonacci \n"; }
10 ~Fibonacci(void);
11 };
这和第一种错误一样,都是一个重载的函数,而不是改写基类的虚拟函数。。
关于继承基类的虚拟函数的说明就到此结束。最后说一下改写基类的虚拟函数时,子类中声明不一定非得加上关键词virtual。编译器会依据两个函数的原型声明,决定某个函数是否会改写其基类中的同名函数( 比如这里1.2中可以写成这样const char* what_am_i() const { return "Fibonacci \n"; } )。
=============================
补充:
“返回型别必须完全吻合” 这一规则有个例外:当基类的虚拟函数返回某个基类形式(通常是pointer或reference)时:派生类中的同名函数便可以返回该基类所派生出来的型别:举例如下(尚不知道这里实际工程项目中的用处):
基类num_sequnece: virtual num_sequence *clone() = 0;
子类Fibonacci: [virtual] Fibonacci *clone() { return new Fibonacci ( *this ); }