重寫(override)、重載(overload)和隱藏(overwrite)在C++中是3個完全不同的概念。我們這里對其進行詳細的說明
1、重寫(override)是指派生類覆蓋了基類的虛函數,這里的覆蓋必須滿足有相同的函數簽名和返回類型,也就是說有相同的函數名、形參列表以及返回類型。
2、重載(overload)是指C++允許在同一作用域中聲明幾個功能類似的同名函數,這些函數的函數名相同,但是函數簽名不同,也就是說有不同的形參。
3、隱藏(overwrite)是指基類成員函數,無論它是否為虛函數,當派生類出現同名函數時,如果派生類函數簽名不同于基類函數,則基類函數會被隱藏。如果派生類函數簽名與基類函數相同,則需要確定基類函數是否為虛函數,如果是虛函數,則這里的概念就是重寫;否則基類函數也會被隱藏。另外,如果還想使用基類函數,可以使用
using
關鍵字將其引入派生類。
一、重寫
函數重寫的基本原則:在基類中,通過使用關鍵字 virtual 來聲明一個虛函數,派生類可以通過重新定義基類中的虛函數來實現函數重寫。
例如:
class Father {
public:virtual void func() {cout << "Father" << endl;}
};class Child: public Father {
public:void func() {cout << "Child" << endl;}
};int main() {Father f;Child c;f.func();c.func();
}
以上代碼實現了子類重寫父類中的函數。所以父類子類調用同一個函數會有不同的實現。仿真如下:
1.1、重寫引發的問題
重寫虛函數很容易出現錯誤,原因是C++語法對重寫的要求很高,稍不注意就會無法重寫基類虛函數。且這些錯誤不易被發現,編譯器可能也不會提示:
class Base {
public:virtual void some_func() {}virtual void foo(int x) {}virtual void bar() const {}void baz() {}
};class Derived : public Base {
public:virtual void sone_func() {}virtual void foo(int &x) {}virtual void bar() {}virtual void baz() {}
};
Derived
的4個函數都沒有觸發重寫操作。第一個派生類虛函數sone_func
的函數名與基類虛函數some_func
不同,所以它不是重寫。第二個派生類虛函數foo(int &x)
的形參列表與基類虛函數foo(int x)
不同,所以同樣不是重寫。第三個派生類虛函數bar()
相對于基類虛函數少了常量屬性,所以不是重寫。最后的基類成員函數baz
根本不是虛函數,所以派生類的baz
函數也不是重寫。
1.2、使用override說明符
重寫容易出錯,尤其繼承關系非常復雜的時候。所以C++11標準提供了一個非常實用的override說明符,明確告訴編譯器這個虛函數需要覆蓋基類的虛函數,一旦編譯器發現該虛函數不符合重寫規則,就會給出錯誤提示。
class Derived : public Base {
public:virtual void sone_func() override {}virtual void foo(int &x) override {}virtual void bar() override {}virtual void baz() override {}
};
如果沒有override說明符,則修改基類虛函數將面臨很大的風險,因為編譯器不會給出錯誤提示,我們只能靠測試來檢查問題所在。
1.3、使用final說明符
可以為基類聲明純虛函數來迫使派生類繼承并且重寫這個純虛函數。但是一直以來,C++標準并沒有提供一種方法來阻止派生類去繼承基類的虛函數。C++11標準引入final說明符解決了上述問題,它告訴編譯器該虛函數不能被派生類重寫。final說明符也需要聲明在虛函數的尾部。
class Father {
public:virtual void func() final {cout << "Father" << endl;}
};class Child: public Father {
public:// 報錯,不能重寫final修飾的函數void func() {cout << "Child" << endl;}
};
最后要說明的是,final說明符不僅能聲明虛函數,還可以聲明類。如果在類定義的時候聲明了final,那么這個類將不能作為基類被其他類繼承
class Base final {
public:virtual void foo(int x) {}
};// 報錯,不能繼承final修飾的類
class Derived : public Base {
public:void foo(int x) {};
};
1.4、override和final的特別之處
在C++11標準中,override和final并沒有被作為保留的關鍵字,其中override只有在虛函數尾部才有意義,而final只有在虛函數尾部以及類聲明的時候才有意義,因此以下代碼仍然可以編譯通過:
void override() {}
void final() {}
二、重載
函數重載的條件:
1、參數個數不同
2、參數類型不同
3、參數順序不同
void fun(int i) {cout << "打印整數: " << i << endl;
}
void fun(int i, int j) {cout << "打印兩個整數: " << i << " 和 " << j << endl;
}
void fun(float f) {cout << "打印浮點數: " << f <<endl;
}fun(4); // 調用第一個 fun 函數
fun(2, 3); // 調用第二個 fun 函數
fun(1.5f); // 調用第三個 fun 函數
**注意:**返回值不同不是函數重載的判斷標準
void fun(int i) {cout << "打印整數: " << i << endl;
}int fun(int i) {cout << "打印整數: " << i << endl;return 0;
}fun(4); // 報錯,并不知道調用哪個函數
2.1、函數重載的底層原理
為什么C++支持函數重載而C不支持?C語言中同名函數編譯完還是同名的,兩個重名函數的地址都是有效值,所以在重定位的時候就會產生沖突和歧義。而C++會對函數名進行修飾,例如:
void f(int a, double b) { printf("%d %lld", a, b) }
函數名 f
會被修正為 _Z1fid
,Linux下的命名規則為
函數名被修飾為:_Z + 函數名長度 + 函數名 + 各個形參類型首字母的小寫
這也就解釋了為什么函數重載和返回值無關,為什么和參數個數,類型,順序不同就可以重載,因為他們修飾完后的函數名就是不同的。
三、隱藏
隱藏指在某些情況下,派生類中的函數屏蔽了基類中的同名函數:
1、兩個函數參數相同,但是基類不是虛函數。和重寫的區別在于基類函數是否是虛函數
2、兩個函數參數列表不同,無論基類函數是否虛函數,基類函數都將被覆蓋。和重載的區別在于兩個函數不在同一個類中
下面舉例說明:
class Base {
public:void funA(){cout<<"funA()"<<endl;}virtual void funB(){cout<<"funB()"<<endl;}
};class Heri:public Base {
public:// 隱藏,基類中同名函數不是虛函數void funA(){cout<<"funA():Heri"<<endl;}// 隱藏,參數列表不同,無論基類是否是虛函數,基類函數都將被覆蓋void funA(int a){cout<<"funA(int a):heri"<<a<<endl;}// 重寫,基類是虛函數void funB(){cout<<"funB():heri"<<endl;}
};
隱藏使用的時候記住一句,派生類的指針或引用,對象調用子類和父類同名的函數,父類的同名函數被子類隱藏,調用的是子類的函數。
看下面代碼:
class Base {
public:void fun1() { cout<<"base:fun1()"<<endl; fun(); }virtual void fun() { cout<<"base:fun()"<<endl; }
};class Deriverd:public Base {
public:virtual void fun1() { cout<<"deriverd:fun1()"<<endl; }void fun() { cout<<"deriverd:fun()"<<endl; }
};int main() {Base *pb = new Deriverd;pb->fun1();return 0;
}
輸出結果為:
main
函數中創建了父類的指針指向了子類的對象,然后通過父類的指針調用具有隱藏關系的fun1()
函數,我們會以為pb->fun1()
調用的是子類的函數fun1()
,實際并不是,隱藏關系的函數,誰調用就用誰的函數,按照正常的函數調用使用便可得正確的結果,這里是父類指針調用,就用父類的函數fun1()
。這就是隱藏和重寫的區別。
四、重寫與隱藏的區別
看下面的代碼:
class Base {
public:virtual void foo(int x) { cout << "Base: " << x << endl;}void foo(int x, int y) { cout << "Base: " << x << ' ' << y << endl; }
};// 報錯,不能繼承final修飾的類
class Derived : public Base {
public:void foo(int x) { cout << x << endl; };void foo(int x, int y) {cout << x << ' ' << y << endl; }
};
int main() {Base *pb = new Derived;pb->foo(1);pb->foo(1, 2);return 0;
}
其中 foo(int x, int y)
函數發生了隱藏,void foo(int x)
函數發生了重寫, Base *pb = new Derived;
發生了父類指針指向子類對象,隱藏由于是父類指針,所以調用了父類的實現,重寫由于是子類對象,所以調用了子類實現。