C++中重載、重寫與隱藏從語法到底層實(shí)現(xiàn)
在 C++ 面向?qū)ο缶幊讨校?ldquo;重載(Overload)”、“重寫(Override)” 和 “隱藏(Hide)” 是三個(gè)極易混淆的概念。它們雖都涉及函數(shù)名的復(fù)用,但其語義、作用機(jī)制和使用場景卻截然不同。本文將從語法規(guī)則到底層實(shí)現(xiàn),全面解析三者的差異,并深入探討虛函數(shù)表(vtable)的工作原理。
一、函數(shù)重載(Overload)
? 定義
在同一個(gè)作用域中,允許存在多個(gè)同名函數(shù),只要它們的參數(shù)簽名(參數(shù)個(gè)數(shù)、類型或順序)不同。編譯器會(huì)根據(jù)調(diào)用時(shí)的參數(shù)類型自動(dòng)匹配最合適的函數(shù)版本。
?? 核心特性
- 作用域:同一類或同一命名空間內(nèi)
- 區(qū)分依據(jù):參數(shù)列表(個(gè)數(shù)、類型、順序)必須不同(返回值類型不同不足以構(gòu)成重載)
- 綁定方式:編譯期靜態(tài)綁定(編譯器在編譯時(shí)確定調(diào)用哪個(gè)函數(shù))
- 與繼承無關(guān):僅針對(duì)同一作用域內(nèi)的函數(shù)
示例代碼
#include <iostream>
using namespace std;
class Calculator {
public:
// 重載:參數(shù)類型不同
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
// 重載:參數(shù)個(gè)數(shù)不同
int add(int a, int b, int c) { return a + b + c; }
};
int main() {
Calculator calc;
cout << calc.add(2, 3) << endl; // 調(diào)用 add(int, int)
cout << calc.add(2.5, 3.5) << endl; // 調(diào)用 add(double, double)
cout << calc.add(1, 2, 3) << endl; // 調(diào)用 add(int, int, int)
return 0;
}總結(jié)
重載的本質(zhì)是 “同名函數(shù)的多版本實(shí)現(xiàn)”,目的是讓函數(shù)名更直觀(如用 add 統(tǒng)一表示加法操作,無論參數(shù)是整數(shù)還是小數(shù)),其匹配邏輯由編譯器在編譯期完成。
二、函數(shù)重寫(Override)
? 定義
在派生類中重新實(shí)現(xiàn)基類中聲明為 virtual 的函數(shù),要求函數(shù)簽名(函數(shù)名、參數(shù)列表、返回值類型)完全一致。重寫是實(shí)現(xiàn)多態(tài)的核心機(jī)制。
?? 核心特性
- 作用域:派生類重寫基類的虛函數(shù)
- 前提條件:基類函數(shù)必須用
virtual聲明,派生類函數(shù)可加override關(guān)鍵字(C++11 起,用于顯式表明重寫意圖,編譯器會(huì)檢查匹配性) - 綁定方式:運(yùn)行期動(dòng)態(tài)綁定(通過虛函數(shù)表實(shí)現(xiàn))
- 目的:實(shí)現(xiàn)多態(tài)(同一接口,不同實(shí)現(xiàn))
示例代碼
#include <iostream>
using namespace std;
class Shape {
public:
// 基類中聲明為虛函數(shù)
virtual void draw() { cout << "繪制圖形" << endl; }
};
class Circle : public Shape {
public:
// 重寫基類的 draw 函數(shù),用 override 顯式標(biāo)注
void draw() override { cout << "繪制圓形" << endl; }
};
class Rectangle : public Shape {
public:
void draw() override { cout << "繪制矩形" << endl; }
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Rectangle();
// 運(yùn)行時(shí)根據(jù)實(shí)際對(duì)象類型調(diào)用對(duì)應(yīng)函數(shù)(多態(tài))
shape1->draw(); // 輸出:繪制圓形
shape2->draw(); // 輸出:繪制矩形
delete shape1;
delete shape2;
return 0;
}注意事項(xiàng)
- 若派生類函數(shù)簽名與基類虛函數(shù)不一致,
override會(huì)導(dǎo)致編譯錯(cuò)誤(幫助開發(fā)者發(fā)現(xiàn)拼寫或參數(shù)錯(cuò)誤) - 基類虛函數(shù)被重寫后,派生類的函數(shù)默認(rèn)也是虛函數(shù)(即使不加
virtual) - 重寫的函數(shù)返回值類型必須與基類一致(協(xié)變返回類型除外,如基類返回
Shape*,派生類返回Circle*)
三、函數(shù)隱藏(Hide)
? 定義
當(dāng)派生類中定義了一個(gè)與基類同名函數(shù)時(shí),若該函數(shù)不是重寫(即基類函數(shù)非虛函數(shù),或參數(shù)簽名不同),則派生類的函數(shù)會(huì) “隱藏” 基類中所有同名函數(shù) —— 即基類的同名函數(shù)在派生類中不可見,除非顯式調(diào)用。
?? 核心特性
- 作用域:派生類與基類之間
- 觸發(fā)條件:
- 基類函數(shù)未聲明為
virtual,派生類定義同名函數(shù)(無論參數(shù)是否相同) - 基類函數(shù)是虛函數(shù),但派生類函數(shù)參數(shù)簽名與基類不同
- 基類函數(shù)未聲明為
- 綁定方式:編譯期靜態(tài)綁定
- 效果:派生類中無法直接調(diào)用被隱藏的基類函數(shù),需用
基類名::函數(shù)名顯式調(diào)用
示例代碼
#include <iostream>
using namespace std;
class Base {
public:
void func(int x) { cout << "Base::func(int): " << x << endl; }
void func(string s) { cout << "Base::func(string): " << s << endl; }
};
class Derived : public Base {
public:
// 派生類定義同名函數(shù),參數(shù)類型不同(隱藏基類所有 func 函數(shù))
void func(double x) { cout << "Derived::func(double): " << x << endl; }
};
int main() {
Derived d;
d.func(3.14); // 調(diào)用 Derived::func(double)
// d.func(10); // 編譯錯(cuò)誤:基類的 func(int) 被隱藏
// d.func("hi"); // 編譯錯(cuò)誤:基類的 func(string) 被隱藏
// 顯式調(diào)用基類被隱藏的函數(shù)
d.Base::func(10); // 輸出:Base::func(int): 10
d.Base::func("hi"); // 輸出:Base::func(string): hi
return 0;
}如何避免隱藏?
若希望在派生類中同時(shí)保留基類的同名函數(shù),可使用 using 聲明引入基類函數(shù):
class Derived : public Base {
public:
using Base::func; // 引入基類所有 func 重載函數(shù)
void func(double x) { cout << "Derived::func(double): " << x << endl; }
};
// 此時(shí)可正常調(diào)用基類的 func 函數(shù)
int main() {
Derived d;
d.func(10); // 調(diào)用 Base::func(int)
d.func("hi"); // 調(diào)用 Base::func(string)
d.func(3.14); // 調(diào)用 Derived::func(double)
return 0;
}四、虛函數(shù)表(vtable):重寫的底層實(shí)現(xiàn)
重寫的核心是多態(tài),而多態(tài)的底層依賴于虛函數(shù)表(vtable) 和虛指針(vptr) 機(jī)制。以下是其工作原理:
1. 虛函數(shù)表的本質(zhì)
- vtable:每個(gè)包含虛函數(shù)的類(或其派生類)會(huì)在編譯期生成一個(gè)虛函數(shù)表,這是一個(gè)存儲(chǔ)函數(shù)指針的數(shù)組,其中存放著該類所有虛函數(shù)的地址。
- vptr:每個(gè)類的實(shí)例對(duì)象會(huì)包含一個(gè)虛指針(通常是對(duì)象的第一個(gè)成員),指向該類的 vtable。
2. 實(shí)現(xiàn)過程(以之前的Shape類為例)
基類 Shape:
- 編譯期生成 vtable,包含 Shape::draw() 的地址。
- 每個(gè) Shape 對(duì)象有一個(gè) vptr,指向 Shape 的 vtable。
派生類 Circle:
- 繼承 Shape 的 vtable,但會(huì)用 Circle::draw() 的地址覆蓋 vtable 中 Shape::draw() 的位置。
- 每個(gè) Circle 對(duì)象的 vptr 指向 Circle 的 vtable(而非 Shape 的)。
調(diào)用時(shí)的動(dòng)態(tài)綁定:
- 當(dāng)執(zhí)行 shape1->draw() 時(shí),編譯器無法確定 shape1 指向的是 Shape 還是 Circle 對(duì)象。
- 實(shí)際執(zhí)行時(shí),程序通過 shape1 的 vptr 找到對(duì)應(yīng)的 vtable,再調(diào)用表中 draw() 函數(shù)的地址 —— 若 shape1 指向 Circle 對(duì)象,則調(diào)用 Circle::draw()。
3. 虛函數(shù)表的特性
- 每個(gè)類只有一個(gè) vtable:所有實(shí)例共享同一個(gè) vtable(節(jié)省內(nèi)存)。
- 派生類 vtable 包含基類虛函數(shù):若派生類未重寫基類虛函數(shù),則 vtable 中仍保留基類函數(shù)的地址。
- 動(dòng)態(tài)綁定的開銷:相比普通函數(shù)調(diào)用,虛函數(shù)調(diào)用需多一次 “通過 vptr 查找 vtable” 的操作,效率略低(但通??珊雎裕?。
五、三者對(duì)比一覽表
| 特性 | 重載(Overload) | 重寫(Override) | 隱藏(Hide) |
|---|---|---|---|
| 作用域 | 同一類 / 命名空間 | 派生類與基類之間 | 派生類與基類之間 |
| 函數(shù)關(guān)系 | 同名不同參 | 同名同參(基類為虛函數(shù)) | 同名(參數(shù)不同或基類非虛函數(shù)) |
| 與繼承的關(guān)系 | 無 | 必須繼承 | 必須繼承 |
| 虛函數(shù)要求 | 無需 | 基類必須聲明 virtual | 基類通常非虛函數(shù) |
| 綁定方式 | 編譯期靜態(tài)綁定 | 運(yùn)行期動(dòng)態(tài)綁定(vtable) | 編譯期靜態(tài)綁定 |
| 核心目的 | 提供多版本函數(shù)接口 | 實(shí)現(xiàn)多態(tài)(同一接口不同實(shí)現(xiàn)) | 屏蔽基類同名函數(shù) |
| 調(diào)用方式 | 直接調(diào)用,編譯器自動(dòng)匹配 | 通過基類指針 / 引用調(diào)用派生類對(duì)象 | 直接調(diào)用派生類函數(shù),基類需顯式調(diào)用 |
六、記憶口訣與實(shí)戰(zhàn)建議
記憶口訣
- 重載:同域同名,參數(shù)不同,編譯匹配。
- 重寫:繼承虛函,簽名相同,運(yùn)行多態(tài)。
- 隱藏:繼承同名,非重寫則藏,顯式調(diào)用。
實(shí)戰(zhàn)建議
- 用 override 標(biāo)注重寫:明確意圖,讓編譯器檢查簽名是否正確,避免意外隱藏。
- 避免同名函數(shù)在派生類中隨意定義:若無需重寫,盡量不要定義與基類同名的函數(shù)。
- 必要時(shí)用 using 引入基類函數(shù):當(dāng)需要在派生類中保留基類同名函數(shù)時(shí),用 using 顯式引入。
- 理解虛函數(shù)表機(jī)制:不僅能幫你掌握多態(tài)的本質(zhì),還能在調(diào)試復(fù)雜繼承關(guān)系時(shí)快速定位問題。
結(jié)語
掌握重載、重寫和隱藏的差異,是學(xué)好 C++ 面向?qū)ο缶幊痰幕A(chǔ)。其中,重寫與虛函數(shù)表機(jī)制更是 C++ 多態(tài)的核心,理解其底層原理能讓你在設(shè)計(jì)類層次結(jié)構(gòu)時(shí)更加得心應(yīng)手。記?。赫Z法是表象,機(jī)制是本質(zhì),只有透過現(xiàn)象看本質(zhì),才能真正掌握這些概念。
到此這篇關(guān)于C++中重載、重寫與隱藏從語法到底層實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++ 重載、重寫與隱藏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在std::thread中創(chuàng)建并管理QEventLoop的全面解析
QEventLoop的工作原理可以簡單地理解為一個(gè)無限循環(huán),它會(huì)不斷地檢查是否有新的事件需要處理,如果有,就將事件從事件隊(duì)列中取出,然后找到相應(yīng)的事件處理器進(jìn)行處理,這篇文章主要介紹了在std::thread中創(chuàng)建并管理QEventLoop的全面指南,需要的朋友可以參考下2023-06-06
Qt中Tab與Tree組件實(shí)現(xiàn)分頁菜單
本文主要介紹tabWidget選擇夾組件與TreeWidget樹形選擇組件的常用方法及靈活運(yùn)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12

