為了解決繼承的一些常見挑戰,c ++ 11為c ++新增了兩個特殊識別符號:override和final。請注意,這些識別符號不被視為關鍵字 - 它們是在某些上下文中具有特殊含義的普通識別符號。
雖然final不是很常用,但是override是乙個非常棒的補充,你應該使用。在本課中,我們將看一下虛函式override返回型別必須匹配的規則的乙個例外。
override說明符
正如我們在上一課中提到的,如果派生類虛函式的簽名和返回型別完全匹配,則它只被視為重寫。這可能導致無意的問題,其中乙個旨在實現覆蓋的功能實際上不是。
請考慮以下示例:
class a
virtual const char* getname2(int x) };
class b : public a
// 注意:引數是乙個短整數
virtual const char* getname2(int x) const // 注意:函式是const};
int main()
因為rbase是對b物件的a引用,所以這裡的意圖是使用虛函式來訪問b :: getname1()和b :: getname2()。但是,因為b :: getname1()採用不同的引數(short int而不是int),所以它不被視為a :: getname1()的重寫。更隱蔽的是,因為b :: getname2()是const而a :: getname2()不是,所以b :: getname2()不被認為是a :: getname2()的重寫。
因此,該程式列印:aa
在這種特殊情況下,因為a和b只是列印它們的名字,所以很容易看出我們搞砸了我們的override,並且呼叫了錯誤的虛函式。但是,在乙個更複雜的程式中,函式具有行為或返回未列印的值,這些問題可能很難除錯。
為了幫助解決那些意圖override但不是override的函式的問題,c ++ 11引入了覆蓋說明符。通過將說明符放在const所在的相同位置,可以將override應用於任何override函式。如果函式未override基類函式,則編譯器會將該函式標記為錯誤。
class a
virtual const char* getname2(int x)
virtual const char* getname3(int x) };
class b : public a
// 編譯錯誤,函式不是覆蓋
virtual const char* getname2(int x) const override // 編譯錯誤,函式不是覆蓋
virtual const char* getname3(int x) override // 可以,函式是a :: getname3(int)的重寫 };
int main()
上面的程式產生兩個編譯錯誤:乙個用於b :: getname1(),另乙個用於b :: getname2(),因為它們都不會override先前的函式。b :: getname3()會override a :: getname3(),因此不會為該行生成錯誤。
使用override說明符沒有效能損失,它有助於避免意外錯誤。因此,我們強烈建議您將其用於您編寫的每個虛函式override,以確保您實際上override了您認為的函式。
規則:將override說明符應用於您編寫的每個預期override函式。
final說明符
在某些情況下,您可能不希望某人能夠override虛函式或從類繼承。最終說明符可用於告訴編譯器強制執行此操作。如果使用者嘗試覆蓋已指定為final的函式或類,則編譯器將給出編譯錯誤。
在我們想要限制使用者覆蓋函式的情況下,最後的說明符在override指定符的相同位置使用,如下所示:
class a};
class b : public a
// 可以,覆蓋a :: getname()};
class c : public b
// 編譯錯誤:覆蓋b :: getname(),這是最終的
};
在上面的**中,b :: getname()重寫了a :: getname(),這很好。但是b :: getname()具有最終說明符,這意味著該函式的任何進一步覆蓋都應被視為錯誤。事實上,c :: getname()嘗試覆蓋b :: getname()(此處與final說明符不相關,它只是用於良好實踐),因此編譯器將給出編譯錯誤。
在我們想要阻止從類繼承的情況下,在類名後面應用final說明符:
class a};
class b final : public a // 注意:在這裡使用最終說明符};
class c : public b // 編譯錯誤:無法從最終類繼承
};
在上面的例子中,b類被宣告為final。因此,當c嘗試從b繼承時,編譯器將給出編譯錯誤。
協變返回型別
有一種特殊情況,派生類虛函式override可以具有與基類不同的返回型別,仍然被視為匹配覆蓋。如果虛函式的返回型別是指標或對類的引用,則override函式可以返回指標或對派生類的引用。這些被稱為協變返回型別。這是乙個例子:
#include class base
void printtype() };
class derived : public base
void printtype() };
int main()
這列印:
called derived::getthis()
returned a derived
called derived::getthis()
returned a base
請注意,某些較舊的編譯器(例如visual studio 6)不支援協變返回型別。
關於協變返回型別的乙個有趣的注意事項:c ++無法動態選擇型別,因此您將始終獲得與被呼叫函式的基本版本匹配的型別。
在上面的例子中,我們首先呼叫d.getthis()。由於d是derived,因此呼叫derived :: getthis(),它返回derived *。然後,此derived *用於呼叫非虛函式derived :: printtype()。
現在有趣的案例。然後我們呼叫b-> getthis()。變數b是指向派生物件的base指標。base :: getthis()是虛函式,因此呼叫derived :: getthis()。雖然derived :: getthis()返回derived *,但由於函式的基本版本返回base *,返回的derived *將向下轉換為base *。因此,呼叫base :: printtype()。
換句話說,在上面的示例中,如果您使用首先鍵入為derived物件的物件呼叫getthis(),則只能獲得derived *。
C 基礎教程物件導向(學習筆記5(2))
在編寫具有多個建構函式的類 大多數建構函式 時,必須為每個建構函式中的所有成員指定預設值會導致冗餘 如果更新成員的預設值,則需要觸控每個建構函式。從c 11開始,可以直接為普通類成員變數 不使用static關鍵字的變數 提供預設初始化值 class rectangle void print int ...
C 基礎教程物件導向(學習筆記(23))
過載一元運算子 與您目前看到的運算子不同,正 負 和邏輯非 運算子都是一元運算子,這意味著它們只能在乙個運算元上執行。因為它們僅對它們所應用的物件進行操作,所以通常將一元運算子過載實現為成員函式。所有三個運算元都以相同的方式實現。讓我們看一下我們如何在前面的例子中使用的cents類上實現operat...
C 基礎教程物件導向(學習筆記(24))
過載比較運算子相對簡單,因為它們遵循我們在過載其他運算子時看到的相同模式。因為比較運算子都是不修改左運算元的二元運算子,所以我們將使過載的比較運算子宣告為友元函式。這是乙個帶有過載運算子 和operator!的car類的示例。include include class car friend bool...