先給出文字說明,然後再給出代碼解釋:
如果我們決定改寫基類所提供的虛擬函數,那麼派生類所提供的新定義,其函數型別必須完全符合基類所聲明的函數原型,包括:參數列、返回型別、常量性(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);
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的運行情況www.2cto.com
~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 ); }
摘自 ziyoudefeng