C++相關概念和易錯語法(22)(final、純虛函數、繼承多態難點)

1.final

final在繼承和多態中都可以使用,在繼承中是指不想將自己被繼承,在多態中是指不想該函數被重寫,比較簡單,下面是一些使用例子。

2.純虛函數

當我們需要抽象一個類的時候,我們就需要用到純虛函數。所謂抽象的類是指高度概括的,需要針對不同事物有不同處理的。如植物是一種抽象的類,而像蘋果、香蕉就是具象的,單獨討論植物太過龐大,沒有太大意義,因此我們的重心放在由植物具象出來的蘋果,我們可以具體討論它的成分、營養價值等。理解了這個例子,就能理解為什么有抽象類,純虛函數的存在了

這就是一個純虛函數,就是在虛函數后面加上 = 0,它對應的類就叫抽象類。注意,只要有一個純虛函數,這個類就叫抽象類,抽象類不能被實例化,就算你不打算用這個純虛函數。唯一能做的就是調用這個類里面的static成員,因為它們不需要實例化就能調用

這么做的意義就在于純虛函數對應的類本身就高度抽象,實例化它沒有意義。但我們可以討論將它具象化的事物,這就要用到虛函數的重寫功能。我們可以理解,純虛函數存在的意義是依賴于虛函數的性質存在的,這里需要我們深刻思考。

3.繼承、多態難點

繼承、多態的用法、意義幾乎講的差不多了,絕大多數情況下已經夠用了,只不過在極少數情況下仍有一些坑。

(1)多態調用重寫的函數

先看下面的代碼,想想結果是什么


#include <iostream>
using namespace std;class A
{
public:virtual void test(int a = 0){}
};class B final : public A
{
public:void test(int a){cout << a << endl;}
};int main()
{A* a = new B;a->test();return 0;
}

不少人會想,這難道不報錯嗎?但結果是

我們需要知道,當構成多態和重寫時,調用函數是以父類聲明+子類定義進行的,對于三個類及以上都是如此,這個父類指的是構成多態的父類

我們也可以進一步理解為什么只需要父類寫virtual,子類可以不寫,因為子類的函數聲明根本沒有意義(在多態中),寫不寫都是以父類的聲明為標準。但是在多態語法以外就不會出現這種反直覺處理情況了。


(2)繼承調用父類函數時this的類型變化

先看看下面的代碼,想想test2的隱含的this指針是B*還是A*


#include <iostream>
using namespace std;class A
{
public:virtual void test(int a = 0){}	void test2(){test();}
};class B final : public A
{
public:void test(int a = 1){cout << a << endl;}
};int main()
{B* a = new B;a->test2();return 0;
}

既然是B*調用函數,那理所應當應該是B*為形參來接受啊,但實際不是這么理解的。

當子類去調用父類的成員函數時,隱含的指針類型始終是父類的。要理解這里,我們假設這個指針的類型是子類的,那如果子類又寫了一個一模一樣的函數構成隱藏,那么就會因為參數和假設的函數完全相同而報錯,所以是行不通的。

當子類調用父類時,this指針會發生一次賦值兼容轉換,這里是從B*賦值兼容轉換為A*,賦值兼容轉換為指針只會影響訪問的方式,指針的值,指向的內容都不會改變。但學了多態之后,我們是否可以將這種特性和多態的形成條件結合起來呢?上面這段代碼就是如此。

結合上一個易錯點,這段代碼的最終結果是

(3)多態訪問限制的特殊處理

先看看下面的代碼,看看是否能夠正常訪問


#include <iostream>
using namespace std;class A
{
public:virtual void Test(){cout << "A" << endl;}
};class B : public A
{
private:void Test(){cout << "B" << endl;}
};int main()
{A* p = new B;p->Test();return 0;
}

很多人以為p的類型是A*,A訪問不了B,但其實程序運行沒有問題

我們要理解訪問限定符限制的是什么,是防止其它類調用private的函數,這里p是一個指針,本身就指向B對象的空間,只不過訪問方式按A進行。由于符合多態的條件,就按虛函數表進行訪問。那么問題在于:B會不會阻止呢?

我們先看看什么情況是會阻止的

我們發現無論在A還是在main函數中,都沒有辦法調用B中的private成員,這也符合我們之前的預期。但是為什么A* p = new B;??p->Test();這種操作就可以呢?

事實上,這是多態中的特殊處理,當我們用父類的指針或引用來訪問子類的虛函數時,是會以父類的訪問限定符為標準的。子類的限制不會起到作用。同理,就算子時public,父是private,那么就無法訪問

一般建議都設為public

4.動靜態綁定

動靜態綁定都是為了定位一個函數,從反匯編的角度上講就是確定call的對應的地址是什么,只不過兩者的方式有一定的區別。

(1)動態綁定:多態調用函數的核心時動態綁定,也叫運行時綁定。也就是借助虛函數表,在這個函數指針數組中確定函數的地址。
(2)靜態綁定:我們平時寫的函數都可以認為是靜態綁定(包括函數重載、普通函數、模板函數),函數如果聲明定義在一起就在編譯后進符號表,如果聲明定義分離在兩個文件則在鏈接時進符號表,運行時是根據符號表來查找函數。

在多態中,在滿足動態綁定的情況下我們指定類域調用函數那就自動轉為靜態綁定,就失去了多態的特性。


#include <iostream>
using namespace std;class A
{
public:virtual void Test(){cout << "A" << endl;}
};class B final : public A
{
public:void Test(){cout << "B" << endl;}
};int main()
{A* p = new B;p->Test();p->A::Test();return 0;
}

運行結果

5.繼承、多態的一些知識點和處理技巧

(1)多用const修飾函數,保證匿名對象傳參可以調用函數

(2)函數第一句的指令理解為函數的地址,成員函數要打印它們的地址函數名前要加&,其余函數函數名就是它的地址(&可加可不加,但成員函數一定要加)

(3)cout打印地址很麻煩,char*不會打印地址,會按字符串去打印,這跟流插入的重載有關。有幾個關于函數指針的重載會導致出現bug,打印地址很受阻,最好使用printf

(4)關聯性強的類型之間支持隱式類型轉換,如整型家族+double(內置類型)、指針之間,有的支持強轉,如int和int*。

關聯性弱的自定義類型,想取頭地址,可以使用*((int*)&Base),虛函數表的地址就在類的開頭

(5)只有virtual修飾的成員函數才能叫作虛函數,而像static修飾的成員函數、全局函數都不能定義為虛函數,全局定義的虛函數沒有意義,static修飾的成員函數不屬于對象,就算加了virtual,也進不了虛函數表,沒有意義。

(6)virtual修飾的成員函數聲明定義分離時定義處不寫virtual

(7)友元不是成員函數,所以不能用virtual修飾

(8)多繼承可能有多張虛函數表,按繼承順序排序,但單繼承對應的就只有一張虛函數表,如果多繼承后自己又寫了虛函數,則默認放在第一張虛函數表后面

(9)如果不重寫虛函數,那共用同一個函數,如果所有的函數都不重寫,兩個類存的函數的地址都相同,但是這對應兩張虛函數表,開辟的是不同的空間

(10)虛函數表是在編譯期間就形成了。而多態是動態綁定(運行時綁定/晚期綁定),是因為編譯時編譯器只負責檢查語法錯誤,而不負責讀取內容,只有運行起來才知道函數調用的地址。

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

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

相關文章

C# 4.0 等待線程結束

在C#中&#xff0c;如果你正在使用多線程編程&#xff0c;并且想要等待一個或多個線程完成它們的工作再繼續執行&#xff0c;有幾種方式可以實現。從C# 4.0開始&#xff0c;雖然直接用于等待線程結束的特性&#xff08;如Thread.Join()&#xff09;在之前的版本中也已經存在&am…

升級版凱撒密碼加密解密器

目錄 開頭程序程序的流程圖程序加密與解密的效果例1加密的過程加密之后的文本 例2解密之后的文本解密之后的文本 例3加密之后的文本加密之后的文本 結尾 開頭 大家好&#xff0c;我叫這是我58。今天&#xff0c;我們來看一下我用C語言編譯的升級版凱撒密碼加密解密器和與之相關…

小程序 - - - - - 實現漸隱漸顯(監聽滾動距離版)

代碼如下&#xff1a; <!-- fixed-left --> <view class"fixed-box" animation"{{animationData}}">這里是漸隱漸顯的標簽 </view>.fixed-box {position: fixed;left: 0;top: 0;z-index: 999;background-color: #ccc;/* background-colo…

如何設計統計量及相關假設檢驗

一、如何設置H0和H1假設 誰做H0&#xff0c;誰做H1&#xff0c;在統計學的假設檢驗里是有約定俗成的規定的。即&#xff1a;status quo&#xff08;默認/現狀&#xff09;是H0&#xff0c;而新觀點或試圖challenge現狀的是H1。H1也叫research hypothesis&#xff0c;所以我們做…

【多個Python版本存在,使用pip+不同版本安裝庫時,windows彈出打開方式窗口的解決方法】

問題描述 電腦上存在python3.9&#xff0c;3.10&#xff0c;3.11&#xff0c;安裝順序也是先安裝3.9&#xff0c;然后3.10&#xff0c;最后3.11&#xff0c;那么直接使用pip安裝&#xff0c;會裝在3.11的位置&#xff0c;經過搜索可以通過pip版本&#xff0c;比如pip3.9 insta…

1.3- Zygote

第三節 Zygote 在Android系統中&#xff0c;Zygote是一個非常核心的組件&#xff0c;它扮演著孵化新應用程序進程的角色。Zygote是Android啟動過程中創建的第一個Java虛擬機&#xff08;JVM&#xff09;實例&#xff08;在Android中稱為Dalvik或ART虛擬機&#xff0c;取決于An…

如何在勒索軟件攻擊中幸存下來:最佳備份實踐、勒索攔截方案

無論身處什么業務或行業&#xff0c;數據都是您業務的關鍵資產。沒有針對數據進行安全可靠的備份保護&#xff0c;您將會受到許多“可能性”的威脅&#xff0c;無論數據丟失是由于在鍵盤上灑了飲料還是遭受到了勒索軟件的攻擊。 為了確保業務不被中斷&#xff0c;企業數據不會…

Python: 初識Python

文章目錄 1. Python的背景知識1.1 Python是咋來的?1.2 Python的特點1.3 Python能干啥?1.4 Python的缺點 2. 搭建Python環境2.1 安裝Python2.2 安裝PyCharm2.3 用pycharm編寫python程序 1. Python的背景知識 1.1 Python是咋來的? 由Guido van Rossum于1989年圣誕節為打發無…

一個用于管理多個 Node.js 版本的安裝和切換開源工具

大家好&#xff0c;今天給大家分享一個用于管理多個Node.js版本的工具 NVM&#xff08;Node Version Manager&#xff09;&#xff0c;它允許開發者在同一臺機器上安裝和使用不同版本的Node.js&#xff0c;解決了版本兼容性問題&#xff0c;為開發者提供了極大的便利。 在開發環…

路網雙線合并單線——ArcGISpro 解決方法

路網雙線合并成單線是一個在地圖制作、交通規劃以及GIS分析中常見的需求。雙線路網定義&#xff1a;具有不同流向、不同平面結構的道路。此外&#xff0c;車道數較多的道路&#xff08;例如&#xff0c;雙黃實線車道數大于4的道路&#xff09;也可以視為雙線路網&#xff0c;本…

iPhone 如何修改鎖屏密碼?修改密碼的具體步驟總結

修改 iPhone 鎖屏密碼 當你還記得當前設置的鎖屏密碼時&#xff0c;想要修改密碼就非常的簡單了&#xff0c;只需要簡單的點幾下就可以重新設置新密碼&#xff0c;下面是具體的操作步驟&#xff1a; 首先我們進入設置應用程序&#xff0c;然后找到“面容 ID 與密碼”。 然后需…

python3多進程用途和場景

Python3 的多進程模塊 multiprocessing 提供了多種用于并行處理的功能&#xff0c;適用于各種場景。以下是一些常見的用途和場景&#xff1a; 用途 CPU 密集型任務&#xff1a; 多進程適用于需要大量 CPU 計算的任務&#xff0c;例如數值計算、數據處理、圖像處理等。這些任務…

Redis的中BitMap的應用

一、應用場景 通常用于構建布隆過濾器 業務場景需要頻繁的查詢數據庫里的數據&#xff0c;但是這些數據又不一定都存在&#xff0c;一些大量無效的數據庫請求&#xff0c;占用了數據庫的鏈接。 本質上保護數據庫&#xff0c;減少無用的請求。 解決&#xff1a; 1、把查詢的…

(01)Unity使用在線AI大模型(使用百度千帆服務)

目錄 一、概要 二、環境說明 三、申請百度千帆Key 四、使用千帆大模型 四、給大模型套殼 一、概要 在Unity中使用在線大模型分為兩篇發布&#xff0c;此篇文檔為在Python中使用千帆大模型&#xff0c;整體實現邏輯是&#xff1a;在Python中接入大模型—>發布為可傳參的…

護眼臺燈的功能作用有哪些?深挖臺燈護眼是真的嗎

隨著現代生活方式的改變&#xff0c;孩子們面臨著越來越多的視力挑戰。在近視學生中&#xff0c;近10%為高度近視&#xff0c;且占比隨年級升高而增長。幼兒園6歲兒童中有1.5%為高度近視&#xff0c;而高中階段則達到了17.6%。為了守護孩子們的視力健康&#xff0c;在科技飛速發…

關鍵字 internal

在C#中&#xff0c;internal 關鍵字是一個訪問修飾符&#xff0c;它用于限制類型或類型成員的訪問性。當一個類型&#xff08;類、結構體、接口、枚舉等&#xff09;或類型成員&#xff08;字段、屬性、方法、事件等&#xff09;被聲明為 internal 時&#xff0c;它只能在同一程…

無符號數和有符號數的轉換

1、有符號數轉換成無符號數 1.1 例一 首先&#xff0c;我們需要清楚 C語言中負數是以補碼的形式進行存儲的。 示例&#xff1a;負數-1&#xff0c; &#xff08;此處&#xff0c;假設是8位二進制表示&#xff09; 對應正數的原碼&#xff1a;0000 0001&#xff1b;取反&…

通俗易懂多圖透徹講解二叉樹的遍歷--前序, 中序和后序

二叉樹的遍歷是一個數據結構中經常會遇到的知識點, 具體又分為前序, 中序和后序三種. 什么是樹? 先來理解一下什么是樹, 從一個我們相對熟悉的家譜樹(Family Tree)說起吧. 家族的根是爺爺, 然后生了兩個娃, 大伯和你爸爸. 繼續往下, 有堂哥堂姐, 還有你以及你妹, 等等. 一個…

簡化流程,強化協作——揭秘可道云TeamOS文檔審批的實用魅力

在團隊協作的過程中&#xff0c;文檔審批是確保信息安全和流程規范的重要環節。然而&#xff0c;傳統的文檔審批流程往往繁瑣且僵化&#xff0c;難以滿足團隊快速響應和靈活協作的需求。 可道云teamOS的文檔審批功能&#xff0c;以其獨特的靈活性和便捷性&#xff0c;為團隊帶…

吸血鬼之戀

吸血鬼之戀 AI制作&#xff0c;吸血鬼之戀&#xff0c;BGM選自《暮光之城》&#xff0c;希望大家喜歡。 歡迎你分享你的作品到我們的平臺上&#xff1a;http://www.shxcj.com 或者 www.2img.ai 讓更多的人看到你的才華。 創作不易&#xff0c;覺得不錯的話&#xff0c;點個贊吧…