直到今日,才發現自己對重載的認識長時間以來都是錯誤的。幸虧現在得以糾正,真的是恐怖萬分,雷人至極。一直以來,我認為重載可以發生在基類和派生類之間,例如:
1 class A { 2 public: 3 void test(int); 4 }; 5 class B : public A { 6 public: 7 void test(int, int); 8 }; 9 10 void main() 11 { 12 B b; 13 14 b.test(5); //錯誤,應該b.A::test(5); 15 }
我一直認為當類B把類A中的test函數繼承之後,在類B中,類A的test函數和類B自己定義的test函數是重載關系(因為我覺得這兩個函數靠形參個數區分開來了),進而,我就認為第14行會調用類A的test函數。非常雷人。現在把重載和隱藏的注意事項總結出來,供理解有誤的人們參考:
重載:
在一個類內,如果存在若干個同名函數,而且這些函數之間可以用形參個數或者形參類型區分開來的時候(注意不能靠函數返回類型區分),這幾個函數就互為重載函數。這時,當你通過類對象調用這幾個函數時,編譯器就可以通過你傳遞的實參個數或者類型,去匹配相應的函數,而不會發生歧義。這也就是重載函數的作用所在(讓你可以使用若干個同名函數)。需要注意的是:
1.重載絕對不會發生在基類和派生類之間,如上例所示。當基類和派生類中存在同名函數時,無論同名函數的形參個數或者類型是否相同,派生類中的同名函數都會將基類中的同名函數隱藏掉,因此它們是隱藏關系,而不是重載關系。關於隱藏,後邊會提到。如此以來,上例的14行在編譯時就會報錯,提示類B中沒有test(int)類型的函數。
2.在同一個類中,重載函數之間必須依靠形參個數或者形參類型來進行區分,不能依靠返回類型。也就是說,如果同一個類中的兩個同名函數形參個數和類型完全相同,但是返回值類型不同,這時候編譯就會報錯,因為當你通過類對象調用該同名函數時,編譯器會出現二義性,不知道該選擇哪個函數。記著,重載必須靠形參來區分。
3.在同一個類中,虛函數和虛函數,虛函數和普通函數之間也可以重載,規則完全同上。虛函數下邊會提到。
隱藏:
隱藏只能出現在基類和派生類之間,而不能發生在同一個類內(比如上述2中,只會引起編譯器出現二義性)。當基類和派生類中存在同名函數時,無論同名函數的形參個數或者類型是否相同,派生類中的同名函數都會將基類中的同名函數隱藏掉,而不會是重載關系。這時,當你通過派生類對象調用該同名函數時,只能訪問派生類的該函數,如果硬要訪問基類的該函數,則需要在函數名前加上類作用域,如上邊代碼所示。
虛函數:
在一個類中,用virtual關鍵字聲明的函數都是虛函數。虛函數存在的唯一目的,就是為了實現多態(動態綁定/運行時綁定)。關於多態,後面會提到。虛函數只有在基類和派生類之間才能發揮虛特性(也就是說才能發揮虛函數的真正的目的)。在同一個類中,所有虛函數就和普通函數是一樣,使用同樣的重載規則(重載的第3點中提到過)。因此在同一個類中可以把虛函數看作普通函數來使用(因為其虛特性發揮不出來),使用方法和注意事項與普通函數一模一樣。
多態:
多態是面向對象思想的精髓所在。說白了,就是通過基類指針或引用調用一個成員函數時,直到運行階段在才能決定該成員函數是哪個派生類中定義的成員函數。有點抽象吧?沒事,先看一段代碼吧。
1 class A { 2 public: 3 virtual void test(int); 4 }; 5 6 class B : public A { 7 public: 8 void test(int); 9 }; 10 11 class C : public A { 12 public: 13 void test(int); 14 }; 15 16 void main() 17 { 18 A *a0; 19 A &a1 = b; 20 A &a2 = c; 21 B b; 22 C c; 23 24 a0 = &b; 25 a0.test(2); //調用類B的test函數 26 27 a0 = &c; 28 a0.test(3); //調用類C的test函數 29 30 a1.test(4); //調用類B的test函數 31 a2.test(5); //調用類C的test函數 32 }
我們先說什麼是多態吧,隨後再講產生多態的條件。第18行,在main函數中定義了一個指向類A類型的指針變量a0,第21和22行分別定義了派生類B和C的對象b,c。第24行,將對象b的指針賦給a0,第25行a0.test將調用類B的test函數;第27行,將對象c的指針賦給a0,第28行a0.test將調用類C的test函數。這就是多態,有感覺了嗎?說白了,就是當基類指針變量指向了哪個派生類對象,就可以調用哪個派生類對象的方法。類似的,引用也可以實現多態,第19-20,30-31行所展示的。
下面總結下實現多態的條件:
哪些成員函數想要以多態的形式來執行,那麼這些函數必須:
1.在基類中將這些成員函數聲明為虛函數,並實現(必須要實現)。
2.在派生類中也聲明這些成員函數並實現(必須實現),基類和派生類的這些函數必須同名,而且其形參個數和類型,返回值類型必須與基類中的這些函數完全相同。此時,派生類中這些函數無論是否用virtual來聲明,都會被自動虛化。
3.將派生類對象賦給基類的指針變量或者引用。至此,多態實現,可用基類指針或引用調用派生類的方法(符合多態條件的方法,而不是普通方法)。
實現多態的這三個條件必須完全滿足,虛函數的虛特性才能發揮出來,也才能實現多態。缺少任何一個條件,虛函數的虛特性都會被打破,無法實現多態。虛特性被打破的虛函數和普通函數是一樣的,因此說虛函數的唯一用途就是實現多態。
下面舉一些不是多態的例子:
a.基類中聲明為虛函數,派生類中也聲明為虛函數,並且也同名。但是派生類中該函數的形參類型或者形參個數和基類中的不相同。此時,多態不滿足,派生類和基類的這兩個虛函數僅僅是隱藏關系,沒有虛特性。
b.基類中聲明為虛函數,派生類中也聲明了一個同名函數,但沒有使用virtual,並且形參類型或者形參個數和基類不相同。這時候基類中的虛函數也丟失虛特性,派生類的該函數不會被虛化,當然也就夠不成多態,這兩個函數也僅僅是隱藏關系。
c.基類中的函數不是虛函數,派生類中聲明為虛函數,它們同名,這時也夠不成多態,派生類的虛函數沒有虛特性,它們也是隱藏關系。
d.基類和派生類的兩個函數同名,都是虛函數,形參的個數和類型也都相同,但是返回值類型不同,這時編譯會報錯,因為兩個虛函數在隱藏時,返回值類型發生了沖突,因此隱藏發生錯誤。注意,如果這兩個函數不是虛函數,這不會報錯,隱藏會成功;同時,如果派生類中是虛函數,基類中不是虛函數,也不過報錯,隱藏也是成功的。這也說明,虛化並隱藏時,返回值類型一定要保持相同。
這是一道二級的題吧答案是B
是的,函數的重載是在編譯的時候發生,而虛函數是在運行的時候確定。其實有時候稱函數的重載為靜態多態性,而虛函數為動態多態性