《c++特性》

目錄

  • 多態
    • 構造函數和析構函數存在多態嗎?
    • 虛函數表
    • 虛析構函數
    • 純虛函數和抽象類
    • 運行時多態和編譯時多態的區別
  • 繼承
    • 設計實例
    • 指針對象和普通對象的區別
    • 正確初始化派生類方式
    • 繼承和賦值的兼容規則
    • protected 和 private 繼承
    • 基類與派生類的指針強制轉換
  • 如何用C實現C++的三大特性
    • C語言實現封裝性
    • C語言實現繼承
    • C語言實現多態

多態

在類的定義中,前面有 virtual 關鍵字的成員函數稱為虛函數;
virtual 關鍵字只用在類定義里的函數聲明中,寫函數體時不用。
「派生類的指針」可以賦給「基類指針」;
通過基類指針調用基類和派生類中的同名「虛函數」時:
若該指針指向一個基類的對象,那么被調用是 基類的虛函數;
若該指針指向一個派生類的對象,那么被調用 的是派生類的虛函數。

調用哪個虛函數,取決于指針對象指向哪種類型的對象。
派生類的對象可以賦給基類「引用」
通過基類引用調用基類和派生類中的同名「虛函數」時:
若該引用引用的是一個基類的對象,那么被調 用是基類的虛函數;
若該引用引用的是一個派生類的對象,那么被 調用的是派生類的虛函數。
調用哪個虛函數,取決于引用的對象是哪種類型的對象。
在面向對象的程序設計中使用「多態」,能夠增強程序的可擴充性,即程序需要修改或增加功能的時候,需要改動和增加的代碼較少。
this 指針的作用就是指向成員函數所作用的對象, 所以非靜態成員函數中可以直接使用 this 來代表指向該函數作用的對象的指針。
pBase 指針對象指向的是派生類對象,派生類里沒有 fun1 成員函數,所以就會調用基類的 fun1 成員函數,在Base::fun1() 成員函數體里執行 this->fun2() 時,實際上指向的是派生類對象的 fun2 成員函數。

構造函數和析構函數存在多態嗎?

在構造函數和析構函數中調用「虛函數」,不是多態。
一般不建議在構造函數或者析構函數中調用虛函數,因為在構造函數和析構函數中調用虛函數不會呈現多態性。

原因是啥呢?你想啊,在構造基類調用基類的構造函數時,派生類的部分還沒有構造,怎么可能能用虛函數實現動態綁定派生生類對象呢,所以構造B基類部分的時候,調用的基類的函數bar;

對于foo函數不是虛函數不會有動態綁定,所以調用的基類部分;
對于第三個bar調用,是虛函數,實現動態綁定,所以調用的是派生類部分。

同樣的道理,當調用繼承層次中某一層次的類的析構函數時,往往意味著其派生類部分已經析構掉,所以也不會呈現出多態。

編譯時即可確定,調用的函數是自己的類或基類中定義的函數,不會等到運行時才決定調用自己的還是派生類的函數。

虛函數表

每一個有「虛函數」的類(或有虛函數的類的派生類)都有一個「虛函數表」,該類的任何對象中都放著虛函數表的指針。「虛函數表」中列出了該類的「虛函數」地址。
可以發現有虛函數的類,多出了 8 個字節,在 64 位機子上指針類型大小正好是 8 個字節,多出來的 8 個字節就是用來放「虛函數表」的地址。
多態的函數調用語句被編譯成一系列根據基類指針所指向的(或基類引用所引用的)對象中存放的虛函數表的地址,在虛函數表中查找虛函數地址,并調用虛函數的指令。
虛函數表的指針」指向的是「虛函數表」,「虛函數表」里存放的是類里的「虛函數」地址,那么在調用過程中,就能實現多態的特性。

虛析構函數

析構函數是在刪除對象或退出程序的時候,自動調用的函數,其目的是做一些資源釋放。

那么在多態的情景下,通過基類的指針刪除派生類對象時,通常情況下只調用基類的析構函數,這就會存在派生類對象的析構函數沒有調用到,存在資源泄露的情況。

解決辦法:把基類的析構函數聲明為virtual
派生類的析構函數可以 virtual 不進行聲明;
通過基類的指針刪除派生類對象時,首先調用派生類的析構函數,然后調用基類的析構函數,還是遵循「先構造,后虛構」的規則。

所以要養成好習慣:

一個類如果定義了虛函數,則應該將析構函數也定義成虛函數;
或者,一個類打算作為基類使用,也應該將析構函數定義成虛函數。
注意:構造函數不能定義成虛構造函數
在構造基類調用基類的構造函數時,派生類的部分還沒有構造,怎么可能能用虛函數實現動態綁定派生生類對象呢,所以構造B基類部分的時候,調用的基類的函數bar;

純虛函數和抽象類

純虛函數:沒有函數體的虛函數。
包含純虛函數的類叫抽象類

抽象類只能作為基類來派生新類使用,不能創建抽象類的對象
抽象類的指針和引用可以指向由抽象類派生出來的類的對象

運行時多態和編譯時多態的區別

編譯時的多態,是指參數列表的不同, 來區分不同的函數, 在編譯后, 就自動變成兩個不同的函數。

運行時多態:用到的是后期綁定的技術, 在程序運行前不知道,會調用那個方法, 而到運行時, 通過運算程序,動態的算出被調用的地址. 運行時多態,也就是動態綁定,是指在執行期間(而非編譯期間)判斷所引用對象的實際類型,根據實際類型判斷并調用相應的屬性和方法

繼承

派生類是通過對基類進行修改和擴充得到的,在派生類中,可以擴充新的成員變量和成員函數。
派生類擁有基類的全部成員函數和成員變量,不論是private、protected、public。需要注意的是:在派生類的各個成員函數中,不能訪問基類的 private 成員。
在派生類對象中,包含著基類對象,而且基類對象的存儲位置位于派生類對象新增的成員變量之前,相當于基類對象是頭部。派生類對象的大小 = 基類對象成員變量的大小 + 派生類對象自己的成員變量的大小

設計實例

假設要寫一個小區養狗管理系統:

需要寫一個「主人」類。
需要些一個「狗」類。

假定狗只有一個主人,但是一個主人可以最多有 10 條狗,應該如何設計和使用「主人」類 和「狗」類呢?我們先看看下面幾個例子。

為狗類設一個主人類的對象指針;
為主人類設一個狗類的對象指針數組。

class CDog;
class CMaster // 主人類
{CDog  * pDogs[10]; // 狗類的對象指針數組
};class CDog  // 狗類
{CMaster * pm;   // 主人類的對象指針
};

在這里插入圖片描述
因為相當于狗和主人是獨立的,然后通過指針的作用,使得狗是可以指向一個主人,主人也可以同時指向屬于自己的 10 個狗,這樣會更靈活。

指針對象和普通對象的區別

如果不用指針對象,生成 A 對象的同時也會構造 B 對象。用指針就不會這樣,效率和內存都是有好處的。

class Car
{Engine engine; // 成員對象Wing * wing;   // 成員指針對象
};

定義一輛汽車,所有的汽車都有 engine,但不一定都有 wing 這樣對于沒有 wing 的汽車,wing 只占一個指針,判斷起來也很方便。
空間上講,用指針可以節省空間,免于構造 B 對象,而是只在對象中開辟了一個指針,而不是開辟了一個對象 B 的大小。
效率上講,使用指針適合復用。對象 B 不但 A 對象能訪問,其他需要用它的對象也可以使用。
指針對象可以使用多態的特性,基類的指針可以指向派生鏈的任意一個派生類。
指針對象,需要用它的時候,才需要去實例化它,但是在不使用的時候,需要手動回收指針對象的資源。

正確初始化派生類方式

class Bug {
private :int nLegs; int nColor;
public:int nType;Bug (int legs, int color);void PrintBug (){ };
};Bug::Bug( int legs, int color)
{nLegs = legs;nColor = color;
}
class FlyBug : public Bug  // FlyBug 是Bug 的派生類
{int nWings;
public:FlyBug( int legs,int color, int wings);
};

正確的FlyBug 構造函數:
通過調用基類構造函數來初始化基類,在執行一個派生類的構造函數 之前,總是先執行基類的構造函數。所以派生類析構時,會先執行派生類析構函數,再執行基類析構函數。

// 正確的FlyBug 構造函數:
FlyBug::FlyBug ( int legs, int color, int wings):Bug( legs, color)
{nWings = wings;
}

繼承和賦值的兼容規則

派生類的對象可以賦值給基類對象
派生類對象可以初始化基類引用
派生類對象的地址可以賦值給基類指針
如果派生方式是 private 或 protected,則上述三條不可行。

protected 和 private 繼承

protected 繼承時,基類的 public 成員和 protected 成員成為派生類的 protected 成員;
private 繼承時,基類的 public 成員成為派生類的 private 成員,基類的 protected 成員成 為派生類的不可訪問成員;
派生方式是 private 或 protected,則是無法像 public 派生承方式一樣把派生類對象賦值、引用、指針給基類對象。

基類與派生類的指針強制轉換

public 派生方式的情況下,派生類對象的指針可以直接賦值給基類指針:

Base *ptrBase = & objDerived;

ptrBase 指向的是一個 Derived 派生類(子類)的對象。
*ptrBase 可以看作一個 Base 基類的對象,訪問它的 public 成員直接通過 ptrBase 即可,但不能通過 ptrBase 訪問 。objDerived 對象中屬于 Derived 派生類而不屬于基類的成員。
通過強制指針類型轉換,可以把 ptrBase 轉換成 Derived 類的指針。

Base * ptrBase = &objDerived;
Derived *ptrDerived = ( Derived * ) ptrBase;

如何用C實現C++的三大特性

C語言實現封裝性

構體AchievePackage中有成員變量_a和兩個函數指針,在InitStruct函數中被賦予兩個函數的地址(函數名即為其地址,也可為&fun1,得到的值一樣),故在此處InitStruct函數相當于該結構體的構造函數,既可以初始化其成員變量_a的值,也在對象定義的同時為其函數指針賦值(需顯示調用)
在這里插入圖片描述

C語言實現繼承

兩個類如果存在繼承關系,其子類必定具有父類的相關屬性(即變量)和方法(即函數)。
用“組合”去實現一下C語言中的繼承:
結構體嵌套:
在這里插入圖片描述

C語言實現多態

多態是通過父類的指針或引用,調用了一個在父類中是virtual類型的函數,實現動態綁定機制。
若想使用父類的指針/引用調用子類的函數,需要在父類中將其聲明為虛函數(virtual),且必須與子類中的函數參數列表相同,返回值也相同。
C++的多態是通過覆蓋實現的,即父類的函數被子類覆蓋了!
父類的該函數為虛函數,告訴父類的指針/引用,你調用這個函數的時候必須看一看你綁定的對象到底是哪個類的對象,然后去那個類里調用該函數!

//C語言模擬C++的繼承與多態typedef void (*FUN)();      //定義一個函數指針來實現對成員函數的繼承struct _A       //父類
{FUN _fun;   //由于C語言中結構體不能包含函數,故只能用函數指針在外面實現int _a;
};struct _B         //子類
{_A _a_;     //在子類中定義一個基類的對象即可實現對父類的繼承int _b;
};void _fA()       //父類的同名函數
{printf("_A:_fun()\n");
}
void _fB()       //子類的同名函數
{printf("_B:_fun()\n");
}

下面是測試代碼:

//C語言模擬繼承與多態的測試
_A _a;    //定義一個父類對象_a
_B _b;    //定義一個子類對象_b
_a._fun = _fA;        //父類的對象調用父類的同名函數
_b._a_._fun = _fB;    //子類的對象調用子類的同名函數_A* p2 = &_a;   //定義一個父類指針指向父類的對象
p2->_fun();     //調用父類的同名函數
p2 = (_A*)&_b;  //讓父類指針指向子類的對象,由于類型不匹配所以要進行強轉
p2->_fun();     //調用子類的同名函數

效果:在這里插入圖片描述

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

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

相關文章

Scala中的while循環

在Scala中的while循環 (while loop in Scala) while loop in Scala is used to run a block of code multiple numbers of time. The number of executions is defined by an entry condition. If this condition is TRUE the code will run otherwise it will not run. Scala中…

Linux操作系統啟動過程

在做開發的過程中,突然發現,要對系統做一些有意義的改變,必須要對操作系統的啟動過程有一定的了解,不然就是修改你都不知道從哪里下手啊,然后就是找來資料看,去網上看別人的博客,有了前一周一些…

方法命名的區別

GetDecimalFromString ExtractDecimal 這2個方法名那個比較好呢。上邊的明顯的是中式英語,單詞拼湊而成的。下邊的更加流暢一些。方法名稱取名還是很有要求的。要通俗易懂還要符合文法。從上邊的可以擴展出什么想法呢。 ExtractDecimalExtractDoubleExtractInt16Ext…

工作排序問題

Problem statement: 問題陳述: Given an array of jobs where every job has a deadline and a profit. Profit can be earned only if the job is finished before the deadline. It is also given that every job takes a single unit of time, so the minimum p…

牛客網與leetcode刷題(高頻題中簡單or中等的)

目錄1、反轉鏈表2、排序3、先序中序后序遍歷4、最小的k個數5、子數組的最大累加和6、 用兩個棧實現隊列7、142. 環形鏈表 II8、20. 有效的括號9、最長公共子串(動態規劃),磕磕絆絆10、二叉樹之字形層序遍歷11、重建二叉樹12、LRU緩存13、合并兩個有序鏈表15、大數加法16、一個二…

AMUL的完整形式是什么?

AMUL:阿南德牛奶聯盟有限公司 (AMUL: Anand Milk Union Limited) AMUL is an abbreviation of Anand Milk Union Limited. It is an Indian milk product cooperative dairy organization that is based in the small town of Anand in the state of Gujarat. AMUL …

mochiweb 源碼閱讀(十一)

大家好,今天周六,繼續接著上一篇,跟大家分享mochiweb源碼。上一篇,最后我們看到了mochiweb_socket_server:listen/3函數: listen(Port, Opts, State#mochiweb_socket_server{sslSsl, ssl_optsSslOpts}) ->case moch…

Android下拉刷新完全解析,教你如何一分鐘實現下拉刷新功能 (轉)

轉載請注明出處:http://blog.csdn.net/guolin_blog/article/details/9255575 最 近項目中需要用到ListView下拉刷新的功能,一開始想圖省事,在網上直接找一個現成的,可是嘗試了網上多個版本的下拉刷新之后發現效果都不怎么理 想。有…

Python中的append()和extend()

列出append()方法 (List append() method) append() method is used to insert an element or a list object to the list and length of the List increased by the 1. append()方法用于將元素或列表對象插入列表,并且列表長度增加1。 Syntax: 句法: …

紅黑樹的實現

目錄1、紅黑樹原理1、紅黑樹性質2、變換規則(從插入結點的角度來講)1.變色2.左旋3.右旋3、刪除結點需要注意的地方2、代碼1、定義結點以及構造函數2、定義紅黑樹類以及聲明它的方法3、左旋4、右旋5、插入操作6、修正操作7、刪除操作3、參考鏈接1、紅黑樹…

118 - ZOJ Monthly, July 2012

http://acm.zju.edu.cn/onlinejudge/showContestProblems.do?contestId339 都是賽后做的。。。弱爆了 A題是找由2和5組成的數字的個數 直接打個表就行了 只是比賽的時候不知道怎么打表啊。。 View Code #include<cstdio> #include<cstring> #include<algorith…

edp1.2和edp1.4_EDP??的完整形式是什么?

edp1.2和edp1.4EDP??&#xff1a;電子數據處理 (EDP: Electronic Data Processing) EDP is an abbreviation of Electronic Data Processing. It alludes to the functioning of operations of commercial data, documents processing of storing, with the use of a compute…

高效讀書心得

1.盡量閱讀中文版 雖然有人英文很強&#xff0c;有的翻譯很差&#xff0c;但AnyWay 中文閱讀與理解的時間&#xff0c;略讀與快速定位感興趣內容的速度還是要快一些。 2.即時批注、總結筆記與交流 雖然愛書&#xff0c;但發現最有效的讀書方式還是不斷的制造脂批本&…

《MySQL——增刪改查以及常用語法》

目錄登錄和退出MySQL服務器基本語法&#xff08;增刪改查&#xff09;登錄和退出MySQL服務器 # 登錄MySQL 密碼 $ mysql -u root -p12345612 # 退出MySQL數據庫服務器 exit;基本語法&#xff08;增刪改查&#xff09; -- 顯示所有數據庫 show databases;-- 創建數據庫 CREA…

WCF簡介

一、簡介 WCF是Windows Communication Foundation縮寫&#xff0c;是Microsoft為構建面向服務的應用提供的分布式通信編程框架&#xff0c;是.NET Framework 3.5的重要組成部分。使用該框架&#xff0c;開發人員可以構建跨平臺、安全、可靠和支持事務處理的企業級互聯應用解決方…

css鏈接樣式_CSS中的樣式鏈接

css鏈接樣式CSS樣式鏈接 (CSS Styling Links) The links in CSS can be styled in various ways to make our website more presentable and attractive. The links can also be styled depending on their states e.g. visited, active, hover, etc. CSS中的鏈接可以通過各種方…

《MySQL——約束》

目錄主鍵約束唯一主鍵非空約束默認約束外鍵約束主鍵約束 -- 主鍵約束 -- 使某個字段不重復且不得為空&#xff0c;確保表內所有數據的唯一性。 CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(20) );-- 聯合主鍵 -- 聯合主鍵中的每個字段都不能為空&#xff0c;并且加起…

UIControl事件

CHENYILONG BlogUIControl事件 FullscreenUIControl事件1.UIControlEventTouchDown單點觸摸按下事件&#xff1a;用戶點觸屏幕&#xff0c;或者又有新手指落下的時候。2.UIControlEventTouchDownRepeat多點觸摸按下事件&#xff0c;點觸計數大于1&#xff1a;用戶按下第二、三、…

C++ 為什么要使用#ifdef __cplusplus extern C { #endif

經常看到別人的頭文件 有這樣的代碼 #ifdef __cplusplus extern "C" { #endif// C 樣式 的函數#ifdef __cplusplus } #endif 為什么要這樣呢&#xff1f; 因為 C 語言不支持重載函數 也就是同名函數&#xff0c;參數卻不一樣,C支持&#xff0c;其編譯器對函數名的處理…

css中的媒體查詢_CSS中的媒體查詢

css中的媒體查詢CSS | 媒體查詢 (CSS | Media Queries) Creating a web page is not an easy task as it requires loads of content and data so that it becomes strongly responsive to the users. To do that various contents are even added e.g.: resources, informativ…