C 基礎教程物件導向(學習筆記(55))

2021-08-30 08:39:54 字數 3408 閱讀 4542

為了解決繼承的一些常見挑戰,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...