第二十四天:虛函數與純虛函數

  1. 虛函數(Virtual Function)
    • 定義:在基類中使用 virtual 關鍵字聲明的成員函數,允許在派生類中被重新定義(覆蓋,override)。其目的是實現多態性,即通過基類指針或引用調用函數時,根據對象的實際類型來決定調用哪個類的函數版本。
#include <iostream>class Animal {
public:virtual void speak() {std::cout << "Animal speaks" << std::endl;}
};class Dog : public Animal {
public:void speak() override {std::cout << "Dog barks" << std::endl;}
};class Cat : public Animal {
public:void speak() override {std::cout << "Cat meows" << std::endl;}
};int main() {Animal* animal1 = new Dog();Animal* animal2 = new Cat();animal1->speak();animal2->speak();delete animal1;delete animal2;return 0;
}
  • 在上述代碼中,Animal 類中的 speak 函數被聲明為虛函數。DogCat 類從 Animal 類派生,并覆蓋了 speak 函數。在 main 函數中,通過基類指針調用 speak 函數時,實際調用的是對象所對應的派生類中的函數版本,從而實現了多態性。
  1. 純虛函數(Pure Virtual Function)
    • 定義:在基類中聲明的沒有函數體,并且初始化為 0 的虛函數。包含純虛函數的類稱為抽象類,抽象類不能實例化對象,只能作為基類被派生類繼承。派生類必須實現純虛函數,否則派生類也將成為抽象類。
#include <iostream>class Shape {
public:virtual double area() = 0;
};class Circle : public Shape {
public:Circle(double r) : radius(r) {}double area() override {return 3.14159 * radius * radius;}
private:double radius;
};class Rectangle : public Shape {
public:Rectangle(double w, double h) : width(w), height(h) {}double area() override {return width * height;}
private:double width, height;
};int main() {Shape* shape1 = new Circle(5.0);Shape* shape2 = new Rectangle(4.0, 6.0);std::cout << "Circle area: " << shape1->area() << std::endl;std::cout << "Rectangle area: " << shape2->area() << std::endl;delete shape1;delete shape2;return 0;
}
  • Shape 類中的 area 函數是純虛函數。CircleRectangle 類繼承自 Shape 類,并實現了 area 函數。通過這種方式,強制派生類提供自己的 area 計算方法,同時利用基類指針實現多態調用。
  1. 虛函數實現機制

    • 虛函數表(Virtual Table,簡稱 vtable):當一個類中包含虛函數時,編譯器會為該類創建一個虛函數表。虛函數表是一個存儲類成員虛函數指針的數組。每個包含虛函數的類都有自己的虛函數表。
    • 虛指針(Virtual Pointer,簡稱 vptr):每個包含虛函數的對象都包含一個指向其所屬類的虛函數表的指針,即虛指針。當對象被創建時,虛指針被初始化,指向該對象所屬類的虛函數表。
    • 調用過程:當通過基類指針或引用調用虛函數時,首先根據對象的虛指針找到對應的虛函數表,然后在虛函數表中查找與被調用函數對應的指針,最后通過該指針調用實際的函數。例如,在上述 Animal 類及其派生類的例子中,animal1animal2 對象都有自己的虛指針,分別指向 DogCat 類的虛函數表。當調用 animal1->speak() 時,通過 animal1 的虛指針找到 Dog 類的虛函數表,再從虛函數表中找到 speak 函數的指針并調用。
  2. 虛函數表的細節

    • 布局:虛函數表中的函數指針按照虛函數在類中聲明的順序排列。如果派生類覆蓋了基類的虛函數,虛函數表中相應位置的指針會被替換為派生類中該虛函數的實現地址。
    • 多重繼承:在多重繼承的情況下,一個對象可能有多個虛指針,分別指向不同基類的虛函數表。這是因為不同基類可能有不同的虛函數集合。例如,當一個類從多個包含虛函數的基類派生時,每個基類的虛函數表都需要被正確管理,以確保虛函數調用的正確性。
    • 運行時開銷:虛函數機制帶來了運行時的額外開銷,主要包括存儲虛指針的空間開銷以及通過虛指針和虛函數表查找函數指針的時間開銷。然而,這種開銷在大多數情況下是可以接受的,并且為C++ 提供了強大的多態性支持。
  3. 虛函數與純虛函數

    • 析構函數的虛屬性
      • 重要性:當基類指針指向派生類對象,并且通過該指針刪除對象時,如果基類析構函數不是虛函數,那么只會調用基類的析構函數,派生類的析構函數不會被調用,這可能導致內存泄漏。例如:
#include <iostream>class Base {
public:~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* basePtr = new Derived();delete basePtr;return 0;
}
 - **輸出**:只會輸出 “Base destructor”。但如果將 `Base` 類的析構函數聲明為虛函數 `virtual ~Base()`,則會先調用 `Derived` 類的析構函數,再調用 `Base` 類的析構函數,確保資源正確釋放。
  • 純虛析構函數:純虛析構函數是一種特殊情況,一個類可以有純虛析構函數,但必須在類外提供函數體。例如:
class AbstractClass {
public:virtual ~AbstractClass() = 0;
};AbstractClass::~AbstractClass() {std::cout << "AbstractClass destructor" << std::endl;
}class ConcreteClass : public AbstractClass {
public:~ConcreteClass() override {std::cout << "ConcreteClass destructor" << std::endl;}
};
  • 虛函數與模板:模板元編程中,虛函數的使用需要特別注意。模板是在編譯期進行實例化的,而虛函數是運行時多態的基礎。當模板類與虛函數結合時,由于模板的實例化機制,可能會導致一些不易察覺的問題。例如,模板類中的虛函數可能不會像預期那樣在派生類中被正確覆蓋,因為模板的實例化是基于不同的模板參數,每個實例化可能會有不同的虛函數表布局。
  1. 虛函數實現機制與虛函數表
    • 虛函數表與動態綁定:動態綁定是指在運行時根據對象的實際類型來確定調用哪個虛函數的過程。虛函數表是實現動態綁定的關鍵。編譯器在編譯時生成虛函數表,運行時通過虛指針和虛函數表進行函數調用。但在一些優化場景下,如在編譯期能夠確定對象的實際類型(例如通過 constexpr 條件判斷等),編譯器可能會進行靜態綁定,直接調用相應的函數,而不通過虛函數表機制,以提高效率。
    • 虛函數表與內存對齊:由于虛指針的存在,對象的內存布局可能會受到影響。虛指針的大小通常與機器的指針大小相同(例如在 64 位系統中為 8 字節)。為了滿足內存對齊的要求,對象的大小可能會增加。例如,一個類只有一個 int 成員變量(通常 4 字節),但如果它包含虛函數,加上 8 字節的虛指針,并且按照 8 字節對齊,對象的大小就會變為 8 字節,而不是 4 + 8 = 12 字節(因為內存對齊會補齊)。
    • 虛函數表的維護與繼承層次:在復雜的繼承層次結構中,虛函數表的維護變得更加復雜。當有多層繼承和虛函數的覆蓋時,編譯器需要確保虛函數表的一致性。例如,在菱形繼承(一個派生類從兩個基類繼承,而這兩個基類又從同一個基類繼承)的情況下,虛函數表的布局需要精心設計,以避免二義性和重復調用等問題。C++ 的虛繼承機制就是為了解決這類問題,它通過引入虛基類指針等方式,確保虛函數表在復雜繼承結構中的正確維護。

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

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

相關文章

uniapp微信小程序-登錄頁面驗證碼的實現(springboot+vue前后端分離)EasyCaptcha驗證碼 超詳細

一、項目技術棧登錄頁面暫時涉及到的技術棧如下:前端 Vue2 Element UI Axios&#xff0c;后端 Spring Boot 2 MyBatis MySQL Redis EasyCaptcha JWT Maven后端使用IntelliJ IDEA 2024.3.5 前端使用 HBuilder X 和 微信開發者工具二、實現功能及效果圖過期管理驗證碼有…

【Java】HashMap的詳細介紹

目錄 一.HashMap 1.基本概念 2.底層數據結構&#xff1a; 3.HashCode和equals方法 為什么重寫HashCode方法&#xff1f; 為什么重新equals方法&#xff1f; 4.put操作 1.初始化和數組檢查 2.計算索引并檢查桶是否為空 3.桶不為null&#xff0c;處理哈希沖突 4.判斷鏈…

nifi 增量處理組件

在Apache NiFi中&#xff0c;QueryDatabaseTable 是一個常用的處理器&#xff0c;主要用于從關系型數據庫表中增量查詢數據&#xff0c;特別適合需要定期抽取新增或更新數據的場景&#xff08;如數據同步、ETL流程&#xff09;。它的核心功能是通過跟蹤指定列的最大值&#xff…

【數據可視化-90】2023 年城鎮居民人均收入可視化分析:Python + pyecharts打造炫酷暗黑主題大屏

&#x1f9d1; 博主簡介&#xff1a;曾任某智慧城市類企業算法總監&#xff0c;目前在美國市場的物流公司從事高級算法工程師一職&#xff0c;深耕人工智能領域&#xff0c;精通python數據挖掘、可視化、機器學習等&#xff0c;發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN…

Multiverse模型:突破多任務處理和硬件效率瓶頸的AI創新(上)

隨著人工智能技術的快速發展&#xff0c;多模態模型成為了當前研究的熱點。多模態模型的核心思想是能夠同時處理和理解來自不同模態&#xff08;如文本、圖像、音頻等&#xff09;的數據&#xff0c;從而為模型提供更加全面的語境理解和更強的泛化能力。 楊新宇&#xff0c;卡…

OpenCV 高斯模糊降噪

# 高斯模糊處理(降噪) # 參數1: 原始圖像 # 參數2: 高斯核尺寸(寬,高&#xff0c;必須為正奇數) # 其他模糊方法: # - cv.blur(): 均值模糊 # - cv.medianBlur(): 中值模糊 # - cv.bilateralFilter(): 雙邊濾波 blur cv.GaussianBlur(img, (7,7), cv…

常見通信協議詳解:TCP、UDP、HTTP/HTTPS、WebSocket 與 RPC

在現代網絡通信中&#xff0c;各種協議扮演著至關重要的角色&#xff0c;它們決定了數據如何在網絡中傳輸、控制其可靠性、實時性與適用場景。對于開發者而言&#xff0c;理解這些常見的通信協議&#xff0c;不僅有助于更好地設計系統架構&#xff0c;還能在面對不同業務需求時…

深入解析MPLS網絡中的路由器角色

一、 MPLS概述&#xff1a;標簽交換的藝術 在深入角色之前&#xff0c;我們首先要理解MPLS的核心思想。傳統IP路由是逐跳進行的&#xff0c;每一臺路由器都需要對數據包的目的IP地址進行復雜的路由表查找&#xff08;最長匹配原則&#xff09;&#xff0c;這在網絡核心層會造成…

AI的拜師學藝,模型蒸餾技術

AI的拜師學藝&#xff0c;模型蒸餾技術什么是模型蒸餾&#xff0c;模型蒸餾是一種高效的模型壓縮與知識轉移方法&#xff0c;通過將大型教師模型的知識精煉至小型學生模型&#xff0c;讓學生模型模仿教師模型的行為和內化其知識&#xff0c;在保持模型性能的同時降低資源消耗。…

Python爬蟲從入門到精通(理論與實踐)

目錄 1. 爬蟲的魅力:從好奇心到數據寶藏 1.1 爬蟲的基本流程 1.2 準備你的工具箱 2. 第一個爬蟲:抓取網頁標題和鏈接 2.1 代碼實戰:用requests和BeautifulSoup 2.2 代碼解析 2.3 遇到問題怎么辦? 3. 進階爬取:結構化數據抓取 3.1 分析網頁結構 3.2 代碼實戰:抓取…

【DDIA】第三部分:衍生數據

1. 章節介紹 本章節是《設計數據密集型應用》的第三部分&#xff0c;聚焦于多數據系統集成問題。前兩部分探討了分布式數據庫的基礎內容&#xff0c;但假設應用僅用一種數據庫&#xff0c;而現實中大型應用常需組合多種數據組件。本部分旨在研究不同數據系統集成時的問題&#…

Spring配置線程池開啟異步任務

一、單純使用Async注解。1、Async注解在使用時&#xff0c;如果不指定線程池的名稱&#xff0c;則使用Spring默認的線程池&#xff0c;Spring默認的線程池為SimpleAsyncTaskExecutor。2、方法上一旦標記了這個Async注解&#xff0c;當其它線程調用這個方法時&#xff0c;就會開…

AI數據倉庫優化數據管理

內容概要AI數據倉庫代表了現代企業數據管理的重大演進&#xff0c;它超越了傳統數據倉庫的范疇。其核心在于利用人工智能技術&#xff0c;特別是機器學習和深度學習算法&#xff0c;來智能化地處理從多源數據整合到最終價值提取的全過程。這種新型倉庫不僅能高效地統一存儲來自…

SpringMVC(詳細版從入門到精通)未完

SpringMVC介紹 MVC模型 MVC全稱Model View Controller,是一種設計創建Web應用程序的模式。這三個單詞分別代表Web應用程序的三個部分: Model(模型):指數據模型。用于存儲數據以及處理用戶請求的業務邏輯。在Web應用中,JavaBean對象,業務模型等都屬于Model。 View(視圖…

vue3運行機制同tkinter做類比

把剛才“Vue3 蓋別墅”的故事&#xff0c;和 Python 的 tkinter 做一個“一一對應”的翻譯&#xff0c;你就能瞬間明白兩件事的異同。 為了直觀&#xff0c;用同一棟房子比喻&#xff1a; Vue3 的“網頁” ? tkinter 的“桌面窗口”瀏覽器 ? Python 解釋器 Tcl/Tk 引擎 下面…

Fastadmin后臺列表導出到表格

html中添加按鈕<a href"javascript:;" class"btn btn-success btn-export" title"{:__(導出數據)}" ><i class"fa fa-cloud-download"></i> {:__(導出數據)}</a>對應的js添加代碼處理點擊事件&#xff0c;添加…

Nginx反向代理與緩存實現

1. Nginx反向代理核心配置解析 1.1 反向代理基礎配置結構 Nginx反向代理的基礎配置結構主要包括server塊和location塊的配置。一個典型的反向代理配置示例如下&#xff1a; server {listen 80;server_name example.com;location / {proxy_pass http://backend_servers;proxy_se…

第2節 如何計算神經網絡的參數:AI入門核心邏輯詳解

?? 核心目標:找到最佳w和b! 上期咱們聊了神經網絡就是復雜的"線性變換+激活函數套娃",今天的重頭戲就是:怎么算出讓模型完美擬合數據的w(權重)和b(偏置)!先從最簡單的線性函數說起,一步步揭開神秘面紗 那么如何計算w和b呢?首先明確我們需要的w和b能夠讓…

AutoSar AP平臺功能組并行運行原理

在 AUTOSAR Adaptive Platform&#xff08;AP&#xff09;中&#xff0c;同一個機器上可以同時運行多個功能組&#xff08;Function Groups&#xff09;&#xff0c;即使是在單核CPU環境下。其調度機制與進程調度既相似又存在關鍵差異&#xff0c;具體實現如下&#xff1a;功能…

linux服務器查看某個服務啟動,運行的時間

一 查看服務啟動運行時間1.1 查看啟動時間查看啟動時間&#xff08;精確到秒&#xff09;&#xff1a;ps -p <PID> -o lstart例子如下&#xff1a;ps -p 1234 -o lstart1.2 查詢運行時長ps -p <PID> -o etimeps -p 1234 -o etime1.3 總結