C++類對象多態基礎語法【超詳細】

文章目錄

  • 前言
  • 1. 虛函數
    • 1.1 現象
    • 1.2 多態
    • 1.3 析構函數
    • 1.4 override和final
    • 1.5 重載、隱藏、重寫對比
  • 2. 抽象類
    • 2.1 抽象類特性
    • 2.2 抽象類的應用場景
  • 3. 多態實現的底層原理
  • 4. 靜態綁定和動態綁定
  • 5. 總結

前言

多態是面向對象三大特性之一,也是細節最多的語法之一。學習類對象的多態,我們不僅僅要看到基礎語法,也要體會到底層原理。從而我們結合類與對象的其它性質,才能更好地體會到多態的魅力。

下面小編會和大家一起探討多態的基礎語法細節!

關于虛函數,還有涉及很多很多的問題(例如:構造中的多態調用、初始化順序……),小編會在一篇講解習題的文章中談到!

注:本文章的測試用例均在VS2022 x86環境下進行

1. 虛函數

小編會由現象引入多態

1.1 現象

來看下面一個例子,例1:

#include<iostream>
#include<string>
using namespace std;class animal
{
public:animal(const string& name):_name(name){}virtual void call(){cout << "name is : " << _name << " call : sound" << endl;}
protected:string _name;
};class cat : public animal
{
public:cat(const string& name = ""):animal(name){}virtual void call(){cout << "name is : " << _name << " call : meow" << endl;}
};class dog : public animal
{
public:dog(const string& name = ""):animal(name){}virtual void call(){cout << "name is : "<< _name << " call : growl" << endl;}
};int main()
{cat c("Lucy");dog d("Juck")animal* ptr1 = &c;animal *ptr2 = &d;ptr1->call();ptr2->call();return 0;
}

說明:
上面代碼定義了一個animal的類,其中成員是animal的名字和叫聲。
dogcat來繼承這個animal的類,并且重寫了叫聲方法。
創建了一個catdog對象并且由animal的指針來接受這兩個指針。然后調用call方法,觀察現象!

在這里插入圖片描述
結果是我們可以正常得到catdogcall函數調用結果!!

這就是多態

1.2 多態

多態的字面意思:對于同一種行為,不同對象去完成產生了不同的結果(形態)!

  • 多態:是在不同繼承的類對象,去調用同一個函數產生了不同的行為。

根據剛剛的例1,我們可以得到的事實是:

  1. catdog繼承于同一個基類animal
  2. animal中用virtual聲明了函數call
  3. catdog中都對函數call再進行了重寫

看到的現象

  1. 同一個類型的animal*指針指向了不同的對象,調用同一個call函數,產生了不同的結果。

接下來我們正式步入多態語法的講解

  • 多態的兩個條件

    1. 基類指針或者引用,指向派生類。(原因后面會談到)
    2. 被調用的函數一定是虛函數,派生類必須對虛函數進行重寫
  • 虛函數在類里virtual關鍵字修飾的函數被稱為虛函數。

    • 注意:這個virtual關鍵字和繼承的虛擬繼承沒有關系
  • 重寫(也叫覆蓋)

    1. 函數必須是虛函數
    2. 函數名完全和基類相同、函數參數列表完全和基類相同、函數返回值和基類相同。(三同)
    • 需要注意的是:

      • virtual關鍵字可以在派生類的時候不用聲明,但是基類必須顯示聲明
      • 協變:返回值可以不同。但是必須滿足父子關系的同類型的返回值。例如:基類返回基類的指針,派生類虛函數就可以返回派生類的指針(不可以是引用)。(只要滿足父子關系即可,并不用管是什么類型)
      • 派生類方法的訪問權限與基類方法的訪問權限只會影響當前類型的訪問
        建議:基類的訪問可以公有。
      • 了解:派生類方法的拋出的異常不能大于基類方法的異常范圍。

下面我們來進行解析

  1. 必須是指針或者引用。那如果是基類對象呢?

例2:

#include<iostream>
#include<string>
using namespace std;
class animal
{
public:animal(const string& name):_name(name){}virtual void call(){cout << "name is : " << _name << " call : sound" << endl;}
protected:string _name;
};class cat : public animal
{
public:cat(const string& name = ""):animal(name){}virtual void call(){cout << "name is : " << _name << " call : meow" << endl;}
};int main()
{cat c("Lucy");animal a = c; //用基類的對象得到對象ca.call(); //嘗試調用call()函數return 0;
}

在這里插入圖片描述
沒有產生預期的結果!(底層解析的時候會告訴讀者為什么)

這就給我們提示:普通對象的調用看的是類型。(千萬不要和隱藏弄混了)

  1. 一定是需要調用虛函數。如果不是調用虛函數,那么就是滿足繼承體系中的隱藏(重定義)關系了!

例3:

#include<iostream>
#include<string>
using namespace std;
class animal
{
public:animal(const string& name):_name(name){}void func(){//無關鍵字virtual聲明—>普通的成員函數cout << "1" << endl;}virtual void call(){cout << "name is : " << _name << " call : sound" << endl;}
protected:string _name;
};class cat : public animal
{
public:cat(const string& name = ""):animal(name){}void func(){cout << "2" << endl;}void call(){cout << "name is : " << _name << " call : meow" << endl;}
};int main()
{cat c("Lucy");animal* ptr = &c;ptr->func(); //普通調用ptr->call(); //多態調用return 0;
}

上面代碼func函數的調用構成普通的隱藏關系。

  1. 協變演示:

例4:

#include<iostream>
#include<string>
using namespace std;
class A
{};class B : public A
{};class animal
{
public:animal(const string& name):_name(name){}virtual A* call(){cout << "name is : " << _name << " call : sound" << endl;A* ptr = new A; //僅僅由于演示return ptr;}
protected:string _name;
};class cat : public animal
{
public:cat(const string& name = ""):animal(name){}virtual B* call(){cout << "name is : " << _name << " call : meow" << endl;B* ptr = new B;return ptr;}
};int main()
{cat c("Lucy");animal* ptr1 = &c;ptr->call();return 0;
}

小編是為了演示而newAB對象,大家不要寫出這樣的代碼。

在這里插入圖片描述

  1. 訪問權限問題

例5:

#include<iostream>
#include<string>
using namespace std;
class animal
{
public:animal(const string& name):_name(name){}virtual void call(){cout << "name is : " << _name << " call : sound" << endl;	}
protected:string _name;
};class cat : public animal
{
public:cat(const string& name = ""):animal(name){}private:virtual void call(){cout << "name is : " << _name << " call : meow" << endl;}
};int main()
{cat c("Lucy");animal* ptr1 = &c;ptr->call(); //沒有影響return 0;
}

上面代碼的animalcall方法訪問權限為public,而catcall方法訪問權限為private,但是不影響animal*ptr調用。

1.3 析構函數

說到多態,不得不提及一個默認成員函數了:析構函數

來看下面一個場景:

例6(錯誤示例):

#include<iostream>
using namespace std;
class Base
{
public:~Base(){cout << "~Base()" << endl;}
};class Derived : public Base
{
public:~Derived(){cout << "~Derived()" << endl;}
};int main()
{Base *ptr = new Derived;delete ptr;return 0;
}

說明:
上面代碼Derived繼承Base類,并且由Base指針指向newDerived對象,然后delete該指針指向的對象。
來看運行結果:
在這里插入圖片描述
我們明明是想調用Derived的析構函數,為什么會調用Base的呢?

  • 下面我們來分析一下:

    1. delete調用析構 + 銷毀空間。
    2. 上面我們談到:普通函數的調用只能看類型。(1.2的例3)
    3. BaseDerived的析構函數很顯然沒有被聲明為一個虛函數,那么調用就被當作普通函數來對待。
    4. 那么對于一個Base的指針來說,如果以該指針類型來調用析構函數的話,就只會調用Base類型的析構函數。

如果想要解決這個問題:

  • 析構函數要被處理為統一的名字 — 滿足三同。這個任務已經幫助我們完成了,統一被處理為:destructor
  • 析構函數要被聲明為虛函數

例7:

#include<iostream>
using namespace std;class Base
{
public:virtual ~Base(){cout << "~Base()" << endl;}
};class Derived : public Base
{
public:virtual ~Derived(){cout << "~Derived()" << endl;}
};int main()
{Base *ptr = new Derived;delete ptr;return 0;
}

來看運行結果:

在這里插入圖片描述

  • 注意

    編譯默認生成的析構函數不會為你聲明為一個虛函數。如果涉及以上想要使用delete的場景,還請顯示聲明析構函數為虛函數

1.4 override和final

C++11提出這兩個關鍵字,更好地規范了虛函數的重寫

  1. override

    • 用途:聲明在派生類函數的參數列表后。檢查派生類虛函數是否重寫了基類的某個虛函數。如果沒有重寫,編譯器會報錯。

例8:

#include<iostream>
using namespace std;class Base
{
public:virtual ~Base(){cout << "~Base()" << endl;}virtual void func(){cout << "Base" << endl;}
};class Derived : public Base
{
public:virtual ~Derived(){cout << "~Derived()" << endl;}virtual void func() override //聲明override,并且完成了重寫:三同{cout << "Derived" << endl;}
};int main()
{Base *ptr = new Derived;ptr->func();delete ptr;return 0;
}
  1. final

    • 用途:修飾虛函數,表示該虛函數不能再被重寫。

例9(錯誤樣例,代碼final檢查錯誤):

#include<iostream>
using namespace std;
class Base
{
public:virtual ~Base(){cout << "~Base()" << endl;}virtual void func() final //聲明{cout << "Base" << endl;}
};class Derived : public Base
{
public:virtual ~Derived(){cout << "~Derived()" << endl;}virtual void func(){cout << "Derived" << endl;}
};

在這里插入圖片描述

1.5 重載、隱藏、重寫對比

這三個是比較容易混淆的概率,小編在這里為大家對比一下

名稱特性
重載1、兩個函數在同一作用域下 2、函數名和參數列表相同
隱藏(重定義)1、兩個函數分別在父子類域中 2、函數名相同
重寫(覆蓋)1、兩個函數分別在父子類域中 2、三同(協變例外)3、兩個函數都是虛函數
  • 我們可以得到一個小結論

    • 父子類域中的兩個同名函數,不是構成重寫就是隱藏

2. 抽象類

有時候我們描述一個事物,但是這個事物在現實生活中是“抽象”的。那么對于它的方法而言就是一個抽象的方法,但是這個事物可以得到延展。例如:形狀。

  • 抽象類:含有純虛函數的類被稱為“抽象類”。

    • 純虛函數

      是在基類中聲明的函數,它在基類中沒有定義,但要求任何該類的派生類都要重寫自己的實現方法

      在基類中實現純虛函數的方法是:在虛函數的方法函數原型后面添加= 0

例10:

#include<iostream>
using namespace std;
class shape //該類為抽象類
{
public:virtual double area() const = 0 // 聲明為純虛函數并且const修飾{}
};class circle : public shape 
{
public:virtual double area() const //const修飾指針也是參數列表中的一環{//業務處理}
};

上面代碼中,就是一個抽象類shape,由circle繼承這個抽象類,并且重寫方法area

2.1 抽象類特性

  1. 抽象類是不能實例化對象出來的

    我們可以理解為:類似這樣的抽象類在現實生活中也找不到實體!

    • 也就是意味著:抽象類只能作為其它類的基類

    在這里插入圖片描述

  2. 抽象類的本身類型不能作為函數參數或者函數返回值,也不能作為顯示類型轉換的類型

    原因同上:抽象類不能示例化出對象!

  3. 如果派生類沒有重寫該純虛函數,那么該派生類也不能實例化出對象。

    在這里插入圖片描述4. 可以定義抽象類類型指針或者引用來指向其派生類。這里小編就不再列舉例子了。

2.2 抽象類的應用場景

  • 純虛函數常用于定義接口規范,強制派生類實現特定功能。抽象基類僅提供接口聲明,不包含具體實現,確保所有派生類遵循統一的接口標準。抽象類體現了一種:接口繼承的理念。

我們可以將抽象類用于描述一些抽象的事物

  1. 圖像
  2. 游戲角色
  3. ……

這些還是需要大家實戰體會。

3. 多態實現的底層原理

關于這個問題,小編打算另起一文,如下:

C++類對象多態底層原理及擴展問題

在這篇文章中小編會和大家探討:

  1. 多態調用的原理:解析虛函數指針,虛函數表,多態成立的兩個條件
  2. 拓展虛函數在虛函數表中的存放位置:單繼承和多繼承
  3. ……

4. 靜態綁定和動態綁定

上面鏈接那篇文章談到了一個話題:多態調用的消耗。實際上這是一個動態綁定的。匯編層面上的差異我們已經可以得知了。

  • 靜態綁定(前期綁定):

    • 在程序編譯期間就已經確定了程序的行為,這就是靜態綁定
    • 例如:函數重載也是一種靜態多態
  • 動態綁定(后期綁定):

    • 在程序運行期間根據具體的類型確定程序的行為,這就是動態綁定
    • 例如:虛函數重寫多態調用動態多態

5. 總結

我們總結一下這個部分的知識點

  1. virtual關鍵字聲明虛函數,建議析構函數聲明為虛函數。
  2. 構成多態的兩個條件:a、重寫 b、指針和引用調用。
  3. 重載、隱藏、重寫對比。
  4. 什么是抽象類?具體的性質?
  5. 多態調用的底層原理?虛函數指針、虛函數表?
  6. 了解靜態多態和動態多態 。

完……

  • 希望這篇文章能夠幫助你!

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

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

相關文章

Flask 入門到實戰(3):用 SQLAlchemy 優雅操作數據庫

深入理解 Flask ORM&#xff1a;用 SQLAlchemy 優雅操作數據庫一、前言&#xff1a;什么是 ORM&#xff1f;為什么要用它&#xff1f; 傳統數據庫操作要寫 SQL&#xff0c;比如&#xff1a; SELECT * FROM users WHERE id 1;而使用 ORM 后&#xff0c;你可以這樣寫&#xff1a…

源表=電源+數字表?一文看懂SMU源表 2025-04-14

源表(Source Meter Unit, SMU)廣泛用于半導體器件、材料、醫療、發光器件與光通信等行業,測量器件的伏安(I-V)特性曲線、絕緣材料的電阻值(電阻率)、電容的絕緣電阻(漏電流)、光電器件的暗電流或者L-I-V等。 源表的名稱已經清晰的告訴我們,它包含了高精度電源輸出和…

單片機STM32F103:DMA的原理以及應用

STM32F103系列微控制器&#xff08;基于ARM Cortex-M3內核&#xff09;集成了**DMA&#xff08;Direct Memory Access&#xff0c;直接內存訪問&#xff09;**控制器&#xff0c;用于在存儲器與外設、存儲器與存儲器之間高效傳輸數據&#xff0c;減少CPU的干預&#xff0c;從而…

Webview 中可用的 VS Code 方法

在 VS Code Webview 的 HTML 中&#xff0c;不能直接調用 VS Code 的 API&#xff08;如 vscode.window.showInformationMessage&#xff09;&#xff0c;但可以通過 acquireVsCodeApi() 獲取一個受限的 vscode 對象&#xff0c;用于與插件主程序通信。以下是詳細說明和示例&am…

Qt:布局管理器Layout

目錄 布局管理器 QVBoxLayout QHBoxLayout QGirdLayout QFormLayout Spacer 布局管理器 在以往的界面操作上&#xff0c;都是程序員手動拖動控件來布局&#xff0c;這種方式有一些不足之處&#xff0c;比如不能很好的把握控件之間的距離&#xff0c;以及控件的大小&…

【Java編程動手學】深入剖析Java網絡編程:原理、協議與應用

文章目錄一、引言二、計算機網絡基礎1、計算機網絡的概念2、網絡地址的重要性三、套接字編程&#xff1a;網絡通信的基石1、套接字的概念2、TCP通信編程示例四、TCP通信編程&#xff1a;可靠的數據傳輸1、TCP協議的特點2、實際應用中的TCP通信五、UDP通信編程&#xff1a;高效的…

vue3.2 前端動態分頁算法

文章目錄背景思路頁面情況核心代碼小結效果背景 1. 后臺接口只是動態返回一個數組的數據&#xff0c;前端需要根據數據量的大小判斷是否需要分頁&#xff0c;頁面高度固定2. 頁面根據頁數大小有不同的展示a. 只有一頁 頭部 內容 統計 尾部b. 多頁i. 第一頁 頭部 內容 尾…

UC瀏覽器PC版自2016年后未再更新不支持vue3

win uc瀏覽器&#xff0c;點擊頁面觸發異常。UC瀏覽器PC版自2016年后未再更新&#xff08;最新版本停留在Chromium 50內核&#xff09;。其內置內核版本較低&#xff08;如Trident/Blink舊版&#xff09;&#xff0c;無法支持Vue 3等現代前端框架的語法特性&#xff08;如ES6、…

亞古數據:澳大利亞公司的ABN和ACN號碼是什么?

在跨國商業的迷宮中&#xff0c;了解目標市場的公司注冊細節是一項不可或缺的技能。對于與中國企業有業務往來的朋友們來說&#xff0c;澳大利亞這片充滿機遇的土地上&#xff0c;兩個縮寫——ABN與ACN&#xff0c;如同解鎖合作之門的密鑰&#xff0c;顯得尤為重要。今天&#…

LangChain框架 Prompts、Agents 應用

目錄 (Prompts)提示作用 Prompts 常見操作 基礎 PromptTemplate 使用 Few-shot 提示模板 ChatPromptTemplate (對話提示模板) (Agents)代理作用 Agents 常見操作 基礎 Agent 使用 自定義工具 Agent 高級應用示例 帶記憶的對話代理 使用本地模型的代理 結構化輸出代…

模擬實現unordered_map

1.定義unordered_map 是 C 標準庫中的哈希表容器&#xff0c;特點是無序存儲、平均 O (1) 時間復雜度的插入 / 查找 / 刪除操作。其核心原理是通過哈希函數將關鍵字映射到哈希桶&#xff08;bucket&#xff09;&#xff0c;再通過鏈表或紅黑樹處理哈希沖突。2.實現原理1. 哈希表…

史上最詳細Java并發多線程(面試必備,一篇足矣)

第一章&#xff1a;線程基礎 1.1 線程與進程 進程&#xff1a;系統資源分配的基本單位&#xff0c;擁有獨立的內存空間 線程&#xff1a;CPU調度的基本單位&#xff0c;共享進程內存空間 關系&#xff1a;一個進程可包含多個線程&#xff0c;線程切換成本遠低于進程 1.2 線程的…

【DataFlow】數據合成流水線工具

1.整體解讀 核心思想&#xff1a;以數據為中心的AI&#xff08;Data-Centric AI&#xff09; DataFlow 的核心目標是通過一系列自動化“流水線”&#xff08;Pipelines&#xff09;來處理和生成高質量的數據&#xff0c;從而提升大語言模型&#xff08;LLM&#xff09;在特定領…

Hangfire 調用報錯解決方案總結

System.ArgumentNullException: 值不能為 null 錯誤在使用 Hangfire 時確實是一個常見問題&#xff0c;特別是在配置 Hangfire 服務器時。問題分析這個錯誤通常發生在以下情況&#xff1a;沒有正確配置 Hangfire 服務器隊列配置缺失或不正確連接字符串配置問題解決方案要點正確…

MySQL的使用

MySQL的使用一、mysql中的周邊命令1. 檢查版本2. 查看字符集3. 查看客戶端連接4. 查看最后一條警告消息二、數據庫、數據表的管理1. 語法規則2. 數據庫2.1 查看數據庫2.2 創建數據庫2.3 選擇數據庫2.4 查看創建數據庫命令2.5 創建庫時添加字符集2.6 修改數據庫字符集2.7 刪除數…

2025Nginx最新版講解/面試

維護系統多服務器部署&#xff0c;將我們請求代理到各個服務器。代理正向代理&#xff0c;代理對象是我們的客戶端&#xff0c;目標對象不知道我們用戶。VPN就是典型的正向代理。反向代理&#xff0c;代理對象是服務端&#xff0c;用戶不知道服務端具體信息。而這正是Nginx所做…

JAVASCRIPT 前端數據庫-V8--仙盟數據庫架構-—-—仙盟創夢IDE

老版本 在v1 版本中我們講述了 基礎版的應用 JAVASCRIPT 前端數據庫-V1--仙盟數據庫架構-—-—仙盟創夢IDE-CSDN博客 接下載我們做一個更復雜的的其他場景 由于&#xff0c;V1查詢字段必須 id 接下來我們修改了了代碼 JAVASCRIPT 前端數據庫-V2--仙盟數據庫架構-—-—仙盟創…

UNIX 域套接字實現本地進程間通信

&#x1f680; 使用 UNIX 域套接字 (AF_UNIX) 實現高效進程通信 在 Linux 和其他類 UNIX 系統中&#xff0c;進程間通信 (IPC) 的方法有很多種&#xff0c;例如管道、消息隊列、共享內存等。然而&#xff0c;當你的應用程序需要在 同一臺機器上的不同進程間進行高效、低延遲的數…

【Axure教程】中繼器間圖片的傳遞

中繼器在Axure中可以作為圖片保存的數據庫&#xff0c;在實際系統中&#xff0c;我們經常需要將選擇數據庫的圖片添加到其他圖片列表中&#xff0c;所以今天就教大家&#xff0c;怎么在Axure中實現中繼器之間的圖片傳遞&#xff0c;包含將一個中繼器中的圖片列表傳遞到另一個中…

專題:2025云計算與AI技術研究趨勢報告|附200+份報告PDF、原數據表匯總下載

原文鏈接&#xff1a;https://tecdat.cn/?p42935 關鍵詞&#xff1a;2025, 云計算&#xff0c;AI 技術&#xff0c;市場趨勢&#xff0c;深度學習&#xff0c;公有云&#xff0c;研究報告 云計算和 AI 技術正以肉眼可見的速度重塑商業世界。過去十年&#xff0c;全球云服務收…