在一次修改代碼過程中踩的坑,下來研究了一下,發現C++中虛函數重載后會產生很多有意思的情況,在這里總結了一下。
C++中有重載(overload)和重寫(override)以及重定義這幾個概念,1 overload:指的是相同作用域中的兩個函數的函數名相同,但參數列表的個數、順序、類型不同。而override指的是子類中重新定義的父類的虛函數。2 override:overload要求兩個函數的參數列表必須不同,但是不要求這兩個函數必須是虛函數。而override要求必須是虛函數且父類的虛函數必須有virtual關鍵字,函數的參數列表和返回值也必須相同。子類中override的虛函數的訪問修飾符可以不同。3 重定義也是描述分別位于父類與子類中的同名函數的,但返回值可以不同。如果參數列表不同,這時子類中重定義的函數不論是否有virtual關鍵字,都會隱藏父類的同名函數。如果參數列表相同,但父類中的同名函數沒有virtual關鍵字修飾,此時父類中的函數仍然被隱藏。
虛函數的典型用法是:
#include <iostream>using namespace std; class Base{public: virtual void f();};void Base::f(){ cout << "Base::f()" << endl;}class Derived:public Base{public: virtual void f();};void Derived::f(){ cout << "Derived::f()" << endl;}int main() { Base *p1 = new Base; Base *p2 = new Derived; p1->f(); p2->f(); delete p1; delete p2; return 0;}程序的輸出是:
Base::f()Derived::f()那么如果虛函數之間又發生了overload,會出現什么情況?我們先看最簡單的情況:
#include <iostream>using namespace std; class Base{public: virtual void f(int);};void Base::f(int a){ cout << "Base::f(int) " << a << endl;}class Derived:public Base{public: virtual void f();};void Derived::f(){ cout << "Derived::f()" << endl;}int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(); delete p1; delete p2; return 0;}編譯上面的代碼,會發生如下錯誤:
test.cpp: In function ‘int main()’:test.cpp:28:11: error: no matching function for call to ‘Base::f()’ p2->f(); ^test.cpp:10:6: note: candidate: virtual void Base::f(int) void Base::f(int a){ ^test.cpp:10:6: note: candidate expects 1 argument, 0 PRovided這就是因為父類中虛函數的參數列表已經發生變化,這時不論子類中重定義的函數不論是否有virtual關鍵字,都會隱藏父類的同名函數。這時子類中只是重定義了一個自己的函數virtual void f(),而并沒有override父類中對應的虛函數。p2是一個指向Base類型的指針,根據虛函數的特性,對p2→f();的處理取決于是否override了父類的虛函數,如果沒有,仍然會調用調用父類中被override的虛函數,但是現在父類中的函數已經成為了virtual void f(int),因此在執行p2→f()時會由于缺少輸入參數而出現上述錯誤。為了證明上述論斷,可以在執行p2→f()時傳入參數來判斷:
#include <iostream>using namespace std; class Base{public: virtual void f(int);};void Base::f(int a){ cout << "Base::f(int) " << a << endl;}class Derived:public Base{public: virtual void f();};void Derived::f(){ cout << "Derived::f()" << endl;}int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(2); delete p1; delete p2; return 0;}這時可以通過編譯,執行結果為
Base::f(int) 1Base::f(int) 2可以看到,子類自己定義的virtual void f()其實是父類的virtual void f(int)的一個重定義的函數,這時盡管p2實際指向了一個Derived對象,但由于沒有override父類對應的虛函數,在執行 p2→f(2)時將執行父類的virtual void f(int)。也可以這樣修改:
#include <iostream>using namespace std; class Base{public: virtual void f(int); virtual void f();};void Base::f(int a){ cout << "Base::f(int) " << a << endl;}void Base::f(){ cout << "Base::f() " << endl;}class Derived:public Base{public: virtual void f();};void Derived::f(){ cout << "Derived::f()" << endl;}int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(); delete p1; delete p2; return 0;}因為父類中定義了可被子類override的函數,所以這時執行p2→f()又會重新執行子類的virtual void f():
Base::f(int) 1Derived::f()我們甚至還可以這樣驗證:
#include <iostream>using namespace std; class Base {public: virtual void f(int); virtual void f();};void Base::f(int a) { cout << "Base::f(int) " << a << endl;}void Base::f() { cout << "Base::f() " << endl;}class Derived:public Base {public: virtual void f(float);};void Derived::f(float a) { cout << "Derived::f(float)" << showpoint << a << endl;}int main() { Base *p1 = new Base; p1->f(1); Base *p2 = new Derived; p2->f(**2.0**); delete p1; delete p2; return 0;}這時輸出仍然為
Base::f(int) 1Base::f(int) 2這說明如果通過指向父類的指針,調用虛函數時,如果子類重定義了該虛函數(參數列表發生變化),則實際調用的仍是父類中的虛函數。上面都是通過指向父類的指針來調用虛函數的,那么如果通過指向子類的指針調用虛函數會發生什么:
#include <iostream>using namespace std; class Base {public: virtual void f(int); virtual void f();};void Base::f(int a) { cout << "Base::f(int) " << a << endl;}void Base::f() { cout << "Base::f() " << endl;}class Derived:public Base {public: virtual void f(float);};void Derived::f(float a) { cout << "Derived::f(float)" << showpoint << a << endl;}int main() { Base *p1 = new Base; p1->f(1); Derived *p2 = new Derived; p2->f(2.0); p2->f(3); delete p1; delete p2; return 0;}這時輸出就變為了:
Base::f(int) 1Derived::f(float)2.00000Derived::f(float)3.00000這說明,如果通過指向子類的指針調用虛函數,并且子類重定義了父類的虛函數,這時實際調用的就將是子類中的虛函數。
新聞熱點
疑難解答
圖片精選