一個例子徹底搞懂C++的虛函數和純虛函數

學習C++的多態性,你必然聽過虛函數的概念,你必然知道有關她的種種語法,但你未必了解她為什么要那樣做,未必了解她種種行為背后的所思所想。深知你不想在流于表面語法上的蜻蜓點水似是而非,今天我們就一起來揭開擋在你和虛函數(女神)之間的這一層窗戶紙。

首先,我們要搞清楚女神的所作所為,即語法規范。然后再去探究她背后的邏輯道理。她的語法說來也不復雜,概括起來就這么幾條:

  1.在類成員方法的聲明(不是定義)語句前面加個單詞:virtual,她就會搖身一變成為虛函數;
  2.在虛函數的聲明語句末尾中加個 =0 ,她就會搖身一變成為純虛函數;
  3.子類可以重新定義基類的虛函數,我們把這個行為稱之為復寫(override);
  4.不管是虛函數還是純虛函數,基類都可以為他們提供實現(implementation),如果有的話子類可以調用基類的這些實現;
  5.子類可自主選擇是否要提供一份屬于自己的個性化虛函數實現;
  6.子類必須提供一份屬于自己的個性化純虛函數實現。

語法都列出來了,背后的邏輯含義是什么呢?我們用一個生動的例子來說明,虛函數是如何實現多態性的。

假設我們要設計關于飛行器的類,并且提供類似加油、飛行的實現代碼,考慮具體情況,飛行器多種多樣,有民航客機、殲擊機、轟炸機、直升機、熱氣球、火箭甚至竄天猴、孔明燈、紙飛機!

假設我們有一位牛得一比的飛行員,他能給各式各樣的飛行器加充不同的燃料,也能駕駛各式各樣的飛行器。下面我們來看看這些類可以怎么設計。

首先,飛行器。由于我們假設所有的飛行器都有兩種行為:加油和飛行。因此我們可以將這兩種行為抽象到一個基類中,并由它來派生具體的某款飛行器。

1 // 這是一個描述飛行器的基類,提供了兩個基本的功能:加油和飛行。
2 class aircraft {
3     virtual void refuel();  // 加燃油,普通虛函數
4     virtual void fly()=0;   // 飛行,純虛函數
5 };
1 // 這是一個普通虛函數,意味著基類希望子類提供自己的個性化實現代碼,但基類
2 // 同時也提供一個缺省的虛函數實現版本,在子類不復寫該虛函數的情況下作為備選方案。
3 void aircraft::refuel() {
4     // 加通用型燃油
5     std::cout << "加通用燃油" << std::endl;
6 }
1 // 這是一個純虛函數,意味著基類強制子類必須提供自己的個性化版本,否則編譯將失敗。
2 void aircraft::fly() {
3     // 一種不應該被使用的缺省飛行方案
4     std::cout << "一種不應該被使用的缺省飛行方案" << std::endl;
5 }

但讓人驚奇的是,C++仍然保留了基類提供該純虛函數代碼實現的權利,這也許是給千變萬化的實際情況留下后路。

有了基類aircraft,我們就可以瀟灑地派生出各式各樣的飛行器了,比如轟炸機和直升機:

 1 // 轟炸機類定義,復寫了加油和飛行
 2 class bomber : public aircraft {
 3     void refuel() {}  // 加轟炸機的特殊燃油
 4     void fly() {}     // 轟炸機實彈飛行
 5 };
 6 
 7 // 直升機類定義,復寫了飛行代碼,但沒有復寫加油
 8 class copter : public aircraft {
 9     void fly() {}  // 直升機盤旋
10 };

以上代碼可以看到,直升機類(copter)沒有自己的加油方式,直接使用了基類提供的缺省加油的方式。此時我們來定義一個能駕馭多機型的王牌飛行員類:

1 // 一個能駕駛各種飛行器的王牌飛行員
2 class pilot {
3     void refuelPlane(aircraft *p);
4     void dirvePlane(aircraft *p);
5 };
1 // 給我什么飛機我就加什么油
2 void pilot::refuelPlane(aircraft *p) {
3     p->refuel();
4 }
5 
6 // 給我什么飛機我就怎么飛
7 void pilot::dirvePlane(aircraft *p) {
8     p->fly();
9 }

很明顯,我們接下來要給這位很浪的飛行員表演一下操縱各種飛行器的機會,我們來定義各種飛機然后丟給他去處理。

1 // 定義兩架飛機,一架轟6K,一架武直10
2 aircraft *H6K = new bomber;
3 aircraft *WZ10 = new copter;
1 // 來一個王牌飛行員,給H6K加油(加的是轟炸機特殊燃油),并且按照H6K的特點飛行
2 pilot Jack;
3 Jack.refuelPlane(H6K);  // 加轟炸機燃油
4 Jack.flyPlane(H6K);     // 轟炸機實彈飛行
5 
6 // 給WZ10加油(加的是基類提供的通用燃油),按照WZ10的特點飛行
7 Jack.refuelPlane(WZ10);  // 加通用型燃油
8 Jack.flyPlane(WZ10);     // 直升機盤旋

上述代碼體現了最經典的所謂多態的場景,給Jack不同的飛機,就能表現不同的結果。虛函數和純虛函數都能做到這一點,區別是,子類如果不提供虛函數的實現,那就會自動調用基類的缺省方案。而子類如果不提供純虛函數的實現,則編譯將會失敗。基類提供的純虛函數實現版本,無法通過指向子類對象的基類類型指針或引用來調用,因此不能作為子類相應虛函數的備選方案。

下面給出總結:

  1.當基類的某個成員方法,在大多數情形下都應該由子類提供個性化實現,但基類也可以提供一個備選方案的時候,請將其設計為虛函數。例如飛行器的加油動作,每種不同的飛行器原則上都應該有自己的個性化的加充燃油的方式,但也不免可以有一種通用的燃油加充方式;
  2.當基類的某個成員方法,必須由子類提供個性化實現的時候,請將其設計為純虛函數。例如飛行器的飛行動作,邏輯上每種飛行器都必須提供為其特殊設計的個性化飛行行為,而不應該有任何一種"通用的飛行方式";
  3.使用一個基類類型的指針或者引用,來指向子類對象,進而調用經由子類復寫了的個性化的虛函數,這是C++實現多態性的一個最經典的場景;
  4.基類提供的純虛函數的實現版本,并非為了多態性考慮,因為指向子類對象的基類指針和引用無法調用該版本。純虛函數在基類中的實現跟多態性無關,它只是提供了一種語法上的便利,在變化多端的應用場景中留有后路;
  5.虛函數和普通的函數實際上是存儲在不同的區域的,虛函數所在的區域是可被覆蓋(也稱復寫override)的,每當子類定義相同名稱的虛函數時就將原來基類的版本給覆蓋了,另一側面也說明了為什么基類中聲明的虛函數在后代類中不需要另加聲明一律自動為虛函數,因為它所存儲的位置不會發生改變。而普通函數的存儲區域不會覆蓋,每個類都有自己獨立的區域互不相干。

轉自https://blog.csdn.net/vincent040/article/details/78848322,并對代碼做了小幅修正,在此感謝原作者。

轉載于:https://www.cnblogs.com/chwei2ch/p/10628608.html

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

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

相關文章

利用Caffe實現mnist的數據訓練

阿里云的參考文檔&#xff1a;https://help.aliyun.com/document_detail/49571.html在文檔里提供了caffe的一個案例&#xff0c;利用Caffe實現mnist的數據訓練。準備的數據源可以在“深度學習案例代碼及數據下載”頁找到Caffe數據下載并解壓。要訓練自己的圖片&#xff0c;還是…

06 函數式編程

1 函數式編程簡介 函數&#xff1a;function 函數式&#xff1a;functional 一種編程范式 特點&#xff1a; 把計算視為函數而非指令 純函數式編程&#xff1a;不需要變量&#xff0c;沒有副作用&#xff0c;測試簡單 支持高階函數&#xff0c;代碼簡潔 Python支持的函數式…

Android SDK開發

目前我們的應用內使用了 ArcFace 的人臉檢測功能&#xff0c;其他的我們并不了解&#xff0c;所以這里就和大家分享一下我們的集成過程和一些使用心得 集成 ArcFace FD 的集成過程非常簡單 在 ArcFace FD 的文檔上有說明支持的系統為 5.0 及以上系統&#xff0c;但其實在 4.4 系…

jQuery WeUI 上傳

jQuery WeUI 是專為微信公眾賬號開發而設計的一個框架&#xff0c;jQuery WeUI的官網&#xff1a;http://jqweui.com/ 需求&#xff1a;需要在微信公眾號網頁添加上傳圖片功能 技術選型&#xff1a;實現上傳圖片功能可選百度的WebUploader、餓了么的Element和微信的jQuery WeUI…

07 模塊

模塊和包的概念 等同于java中的Package 模塊名文件名&#xff08;無后綴&#xff09; 在文件系統中&#xff0c;包就是文件夾&#xff0c;模塊就是xxx.py文件 每層包下面都有__init__.py文件 導入模塊 >>> import math >>> math.pow(2, 0.5) >>…

1.rabbitmq 集群版安裝及使用nginx進行四層負載均衡設置

1.安裝erlang 需要注意erlang的版本是否滿足rabbitmq的需求 這里用到的版本是&#xff1a;Erlang 19.0.4 RabbitMQ 3.6.15 wget http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.centos.x86_64.rpmrpm -ivh erlang-19.0.4-1.el7.centos.x86_64.rpm yum -y inst…

使用WEUI uploader上傳圖片

使用WEUI uploader上傳圖片&#xff0c;博主費了很大的勁總算找到完整的了&#xff0c;并且帶后臺接收代碼&#xff0c;有需要的朋友拿去吧&#xff0c;親測可用&#xff01; 一、html代碼<link rel"stylesheet" href"https://res.wx.qq.com/open/libs/weui/…

08 面向對象編程

1 介紹 面向對象編程是一種程序設計范式 把程序看做不同對象的相互調用&#xff0c;對現實世界建立對象模型。 面向對象編程的基本思想&#xff1a; 類和實例&#xff1a; 類用于定義抽象類型 實例根據類的定義被創建出來 2 定義類并創建實例 類通過class關鍵字定義&…

H5+jqweui實現手機端圖片壓縮上傳 Base64

H5jqweui實現手機端圖片壓縮上傳主要功能&#xff0c;使用H5的formData上傳base64格式的圖片&#xff0c;canvas壓縮圖片&#xff0c;前端樣式使用weui&#xff0c;為方便起見&#xff0c;使用了jquery封裝過的weui&#xff0c;jqweui。話不多少&#xff0c;開始上代碼。前端代…

09 類的繼承

繼承一個類 class Person(object): def __init__(self, name, gender): self.name name self.gender gender class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score score 判斷類型 isinstance()可以…

vue 中v-if 與v-show 的區別

相同點或者說功能&#xff0c;都可以動態操作dom元素的顯示隱藏 不同點&#xff1a; 1.手段&#xff1a;v-if是動態的向DOM樹內添加或者刪除DOM元素&#xff1b;v-show是通過設置DOM元素的display樣式屬性控制顯隱&#xff1b;2.編譯過程&#xff1a;v-if切換有一個局部編譯/卸…

vue打包后放在 nginx部署時候的配置文件

部署了三套程序,默認的&#xff0c;admin和design#user nobody; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections 1024; }http {include …

淘淘商城之技術選型、開發工具和環境、人員配置

一、技術選型 1&#xff09;Spring、SpringMVC、Mybatis 2&#xff09;JSP、JSTL、jQuery、jQuery plugin、EasyUI、KindEditor&#xff08;富文本編輯器&#xff09;、CSSDIV 3&#xff09;Redis&#xff08;緩存服務器&#xff09; 4&#xff09;Solr&#xff08;搜索&#x…

啟動代碼格式:nginx安裝目錄地址 -c nginx配置文件地址

啟動啟動代碼格式&#xff1a;nginx安裝目錄地址 -c nginx配置文件地址 例如&#xff1a;[rootLinuxServer sbin]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf停止nginx的停止有三種方式&#xff1a; 從容停止1、查看進程號[rootLinuxServer ~]# ps -ef…

Lecture 3 Divide and Conquer

1.Divide the problem(instance) into one or more sub-problem; 2.Conquer each sub-problem recursively; 3.Combine solutions.

Maven報錯找不到jre

富人之所以越來越富&#xff0c;窮人之所以越來越窮&#xff0c;中產階級之所以總是在債務泥潭中掙扎&#xff0c;其主要原因之一在于他們對金錢的觀念不是來自學校&#xff0c;而是來自家庭。 ---《窮爸爸富爸爸》 一、報錯提示 常規配置maven環境變量&#xff0c;報錯&#x…

vue按照url地址訪問出錯404

問題描述&#xff1a; 最近在開發cms的時候使用Vue.js框架&#xff0c;利用vue-route結合webpack編寫了一個單頁路由項目&#xff0c;自己在服務器端配置nginx。部署完成后&#xff0c;訪問沒問題&#xff0c;從頁面中點擊跳轉也沒問題&#xff0c;但是只要點擊刷新或通過瀏覽器…

Lecture 4 Quick Sort and Randomized Quick Sort

Quick Sort --Divide and Conquer --Sorts “in place” --Very practical with tuning Divide and Conquer: 1.Divide: Partition array into 2 sub-arrays around pivot x such that elements in lower sub-array < x < elements in upper sub-array; 2.Conquer: …

HDU 3966 樹鏈剖分后線段樹維護

題意: 一棵樹, 操作1.$path(a,b)$之間的點權$k$ 操作2.單點查詢 題解: 樹鏈剖分即可,注意代碼細節,雙向映射 主要是記錄一下板子 #include <string.h> #include <stdio.h> #include <algorithm> #define endl \n #define ll long long #define ull unsigned …