C++ 中重寫重載和隱藏的區別

重寫(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();
}

以上代碼實現了子類重寫父類中的函數。所以父類子類調用同一個函數會有不同的實現。仿真如下:

image-20240515164906765

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;
}

輸出結果為:

image-20240515173641206

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;
}

image-20240515174346499

其中 foo(int x, int y) 函數發生了隱藏,void foo(int x)函數發生了重寫, Base *pb = new Derived;發生了父類指針指向子類對象,隱藏由于是父類指針,所以調用了父類的實現,重寫由于是子類對象,所以調用了子類實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/13741.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13741.shtml
英文地址,請注明出處:http://en.pswp.cn/web/13741.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何寫好科研論文(討論)

討論1. 如何去選取第一批要閱讀的論文&#xff1f; 當我選擇第一批要閱讀的論文時&#xff0c;我會遵循以下幾個步驟&#xff0c;以確保所選的論文對我的研究最有幫助&#xff1a; 研究問題的相關性&#xff1a; 明確我的研究問題或主題&#xff1a;首先&#xff0c;我會確保自…

實例展示vue單元測試及難題解惑

通過生動詳實的例子帶你排遍vue單元測試過程中的所有疑惑與難題。 技術棧&#xff1a;jest、vue-test-utils。 共四個部分&#xff1a;運行時、Mock、Stub、Configuring和CLI。 運行時 在跑測試用例時&#xff0c;大家的第一個絆腳石肯定是各種undifned報錯。 解決這些報錯…

【HarmonyOS4學習筆記】《HarmonyOS4+NEXT星河版入門到企業級實戰教程》課程學習筆記(九)

課程地址&#xff1a; 黑馬程序員HarmonyOS4NEXT星河版入門到企業級實戰教程&#xff0c;一套精通鴻蒙應用開發 &#xff08;本篇筆記對應課程第 16 節&#xff09; P16《15.ArkUI-狀態管理-任務統計案例》 1、實現任務進度卡片 怎么讓進度條和進度展示文本堆疊展示&#xff1…

./scripts/Makefile.clean 文件分析

文章目錄 目標 $(subdir-ymn)目標__clean $(clean-dirs): ??? make -f ./scripts/Makefile.clean obj$(patsubst _clean_%,%,$) $(clean-dirs)$(patsubst _clean_%,%,$)_clean_api _clean_cmd _clean_common _clean_disk _clean_drivers _clean_drivers/ddr/altera _clean_d…

react中的useEffect()的使用

useEffect()是react中的hook函數&#xff0c;作用是用于創建由渲染本身引起的操作&#xff0c;而不是事件的觸發&#xff0c;比如Ajax請求&#xff0c;DOM的更改 首先useEffect()是個函數&#xff0c;接受兩個參數&#xff0c;第一個參數是一個方法&#xff0c;第二個參數是數…

數據結構--樹與二叉樹--編程求以孩子兄弟表示法存儲的森林的葉結點個數

數據結構–樹與二叉樹–編程求以孩子兄弟表示法存儲的森林的葉結點個數 題目 編程求以孩子兄弟表示法存儲的森林的葉結點個數 ps&#xff1a;題目來源2025王道數據結構 思路 樹上的操作大多數是通過遞歸進行的 我們可以從根節點開始遞歸 如果結點 N 沒有孩子指針&#xff…

【Entity Framework】如何理解EF中的級聯刪除

【Entity Framework】如何理解EF中的級聯刪除 文章目錄 【Entity Framework】如何理解EF中的級聯刪除一、概述二、發生級聯行為時2.1/刪除主體/父實體2.2/斷開關系 三、發生級聯行為的位置3.1/級聯刪除被跟蹤實體3.2/數據庫中的級聯刪除 四、級聯NULL 一、概述 Entity Framewo…

vue3 路由跳轉 攜帶參數

實現功能&#xff1a;頁面A 跳轉到 頁面B&#xff0c;攜帶參數 路由router.ts import { createRouter, createWebHistory } from "vue-router";const routes: RouteRecordRaw[] [{path: "/demo/a",name: "aa",component: () > import(&quo…

x264 碼率控制原理:x264_ratecontrol_start 函數

x264_ratecontrol_start 函數 函數原理 函數功能:編碼一幀之前,為當前幀選擇一個量化 QP,屬于幀級別碼率控制;這對于控制視頻質量和文件大小至關重要。通過調整QP,編碼器可以在保持視頻質量的同時,盡可能減小輸出文件的大小。函數參數:x264_t *h: 編碼器上下文結構體指…

十七、個人信息出境標準合同的具體內容有哪些?

根據《標準合同辦法》第六條&#xff0c;標準合同應當嚴格按照網信辦制定版本訂立&#xff0c;個人信息處理者可以與境外接收方約定其他條款&#xff0c;但不得與標準合同相沖突。 根據《標準合同辦法》附件&#xff0c;目前版本的標準合同內容主要包括&#xff1a; 1. 個人信…

Flutter 中的 TextButton 小部件:全面指南

Flutter 中的 TextButton 小部件&#xff1a;全面指南 在Flutter的世界里&#xff0c;TextButton是一個基礎的小部件&#xff0c;用于創建只包含文本的按鈕。它通常用于對話框、表單以及需要強調主要操作的界面。本文將為您提供一個全面的指南&#xff0c;幫助您了解如何使用T…

地信遙感測繪電子書

《地理信息系統概論》&#xff0c;黃杏元&#xff0c;馬勁松編著&#xff0c;第三版&#xff0c;高等教育出版社&#xff0c;2008年 《地理信息系統》&#xff08;第二版&#xff09;湯國安&#xff0c;趙牡丹&#xff0c;楊昕等編&#xff0c;高等教育出版社&#xff0c;2010…

【stm32/CubeMX、HAL庫】嵌入式實驗五:定時器(2)|PWM輸出

參考&#xff1a; 【【正點原子】手把手教你學STM32CubeIDE開發】 https://www.bilibili.com/video/BV1Wp42127Cx/?p13&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c3989f6189fd3 《嵌入式系統基礎與實踐》劉黎明等編著&#xff0c;第九章定時器&#xff0c…

8操作系統定義、分類及功能+設備管理+作業管理 軟設刷題 軟考+

操作系統定義、分類及功能設備管理作業管理 知識點1-55-1010-1515-2020-2525-3030-35 刷題操作系統定義、分類及功能1-55-1010-15作業管理1-5設備管理1-55-10 知識點 1-5 1 嵌入式操作系統的特點&#xff1a; 1.微型化&#xff0c;從性能和成本角度考慮&#xff0c;希望占用的…

145.棧和隊列:刪除字符串中的所有相鄰重復項(力扣)

題目描述 代碼解決 class Solution { public:string removeDuplicates(string s) {// 定義一個棧來存儲字符stack<char> st;// 遍歷字符串中的每一個字符for(int i 0; i < s.size(); i){// 如果棧為空或棧頂字符與當前字符不相同&#xff0c;則將當前字符入棧if(st.e…

Jenkins的Pipeline流水線

目錄 前言 流水線概念 什么是流水線 Jenkins流水線 pipeline node stage step 創建一個簡單的流水線 創建Pipeline項目 選擇模板 測試 前言 提到 CI 工具&#xff0c;首先想到的就是“CI 界”的大佬——Jenkjns,雖然在云原生爆發的年代,蹦出來了很多云原生的 CI 工具…

Hive的窗口函數

定義&#xff1a; 聚合函數是針對定義的行集(組)執行聚集,每組只返回一個值.如sum()、avg()、max() 窗口函數也是針對定義的行集(組)執行聚集,可為每組返回多個值.如既要顯示聚集前的數據,又要顯示聚集后的數據.步驟&#xff1a; 1.將記錄分割成多個分區. 2.在各個分區上調用窗…

word-表格疑難雜癥診治

一、用表格進行排版圖片、制作公文頭 可以在插入圖片時固定列寬 二、表格中的疑難雜癥 問題一&#xff1a;表格超過頁面&#xff0c;右側文字看不見 解決&#xff1a;表格窗口-布局-自動調整-根據窗口自動調整表格 問題二&#xff1a;表格底部文字被遮擋 解決&#xff1a;布…

ArcGIS Maps SDK for JS:使用queryFeatures方法查詢 FeatureLayer 中符合條件的要素

文章目錄 方式一&#xff1a;使用featureLayer.createQuery()方法方式二&#xff1a;使用 Query 構造函數方式三&#xff1a;簡化寫法 要想查詢FeatureLayer 圖層中滿足某些條件的要素&#xff0c;可以使用ArcGIS API for JavaScript 提供的queryFeatures() 方法和 Query 對象進…

【linux】yumvim工具理解使用

目錄 Linux 軟件包管理器 yum 關于 rzsz 注意事項 查看軟件包 Linux開發工具 Linux編輯器-vim使用 vim的基本概念 vim的基本操作 vim正常模式命令集 vim末行模式命令集 簡單vim配置 配置文件的位置 sudo提權 Linux 軟件包管理器 yum 1.yum是什么&#xff1…