構(gòu)造管“生”對(duì)象?析構(gòu)管“埋”對(duì)象?C++中構(gòu)造析構(gòu)還沒整明白?
在談?lì)惖臉?gòu)造前,先聊聊面向?qū)ο缶幊膛c面向過程的個(gè)人體會(huì)。
面向過程策略
要談這個(gè)問題,先來(lái)對(duì)比一下傳統(tǒng)面向過程的編程策略:
比較典型的–C, Pascal, Basic, Fortran語(yǔ)言,傳統(tǒng)的做法是整了很多函數(shù),整合時(shí)主要是整合各函數(shù)的調(diào)用,main函數(shù)協(xié)調(diào)對(duì)過程的調(diào)用,并將適當(dāng)?shù)臄?shù)據(jù)作為參數(shù)在調(diào)用間進(jìn)行傳遞流轉(zhuǎn)而實(shí)現(xiàn)業(yè)務(wù)需求。信息流轉(zhuǎn)主要靠:
-
函數(shù)傳遞參數(shù) -
函數(shù)返回值
如上圖,傳統(tǒng)面向過程編程語(yǔ)言,比如C語(yǔ)言其編程的主要元為函數(shù),這種語(yǔ)言有這些弊端:
-
程序主要由函數(shù)組成,重用性較差。比如直接將從一個(gè)程序源文件中復(fù)制一個(gè)函數(shù)并在另一個(gè)程序中重用是非常困難的,因?yàn)樵摵瘮?shù)可能需要包含頭文件,引用其他全局變量或者其他函數(shù)。換句話說(shuō),函數(shù)沒有很好地封裝為獨(dú)立的可重用單元。相互牽扯、相互耦合比較復(fù)雜。 -
過程編程語(yǔ)言不適合用于解決現(xiàn)實(shí)生活中問題的高級(jí)抽象。例如,C程序使用諸如if-else,for循環(huán),array,function,pointer之類的結(jié)構(gòu),這些結(jié)構(gòu)是低級(jí)的并且很難抽象出諸如客戶關(guān)系管理(CRM)系統(tǒng)或足球?qū)崨r游戲之類的實(shí)際問題。(想象一下,使用匯編代碼來(lái)實(shí)現(xiàn)足球?qū)崨r游戲,酸爽否?顯然C語(yǔ)言比匯編好,但還不是足夠好)
當(dāng)然如C語(yǔ)言開發(fā),現(xiàn)在的編程策略已經(jīng)大量引入了面向?qū)ο缶幊滩呗粤?,但是要?shí)現(xiàn)這些對(duì)象編程策略,則開發(fā)人員本身需要去實(shí)現(xiàn)這些面向?qū)ο蟊旧淼牟呗?,需要自己手撕?shí)現(xiàn)這些基礎(chǔ)的對(duì)象思想。所以這里僅僅就語(yǔ)言本身特征做為說(shuō)明,不必糾結(jié)。而這些策略如果由編程語(yǔ)言本身去實(shí)現(xiàn),則顯然是一個(gè)更優(yōu)異的解決方案。但是比如嵌入式領(lǐng)域?yàn)槁锊恢苯佑肅++呢,而往往更多采用C的方式采用對(duì)象策略來(lái)擼代碼呢? 嵌入式領(lǐng)域更多需要與底層硬件打交道,而C語(yǔ)言本身抽象層級(jí)相對(duì)更適合這種場(chǎng)景,結(jié)合對(duì)象策略編程則可以兼顧重用封裝等考量。
回到技術(shù)發(fā)展歷史來(lái)看,1970年代初期,美國(guó)國(guó)防部(DoD)成立了一個(gè)工作隊(duì),調(diào)查其IT預(yù)算為何總是失控的原因,其調(diào)查結(jié)果是:
-
預(yù)算的80%用于軟件(其余20%用于硬件)。 -
超過80%的軟件預(yù)算用于維護(hù)(僅剩余的20%用于新軟件開發(fā))。 -
硬件組件可以應(yīng)用于各種產(chǎn)品,其完整性通常不會(huì)影響其他產(chǎn)品。(硬件可以共享和重用!硬件故障是隔離的?。? -
軟件過程通常是不可共享且不可重用的。軟件故障可能會(huì)影響計(jì)算機(jī)中運(yùn)行的其他程序。
而面向?qū)ο缶幊陶Z(yǔ)言則很好的解決了這些弊端:
-
OOP的基本單元是一個(gè)類,該類將靜態(tài)屬性和動(dòng)態(tài)行為封裝在一個(gè)“黑盒”里,并開放使用這些黑盒的公共接口。由于類相對(duì)函數(shù)具有更好的封裝,因此重用這些類更加容易。換句話說(shuō),OOP將同一黑盒中的軟件實(shí)體的數(shù)據(jù)結(jié)構(gòu)和算法組合在一起。 良好的類封裝不需要去讀實(shí)現(xiàn)代碼,只要知道接口怎么用,實(shí)現(xiàn)真正意義上的造磚,哪里需要哪里搬! -
OOP語(yǔ)言允許更高級(jí)別的抽象來(lái)解決現(xiàn)實(shí)生活中的問題。傳統(tǒng)的過程語(yǔ)言例如C需要程序猿根據(jù)計(jì)算機(jī)的結(jié)構(gòu)(例如內(nèi)存位和字節(jié),數(shù)組,決策,循環(huán))進(jìn)行思考,而不是根據(jù)您要解決的問題進(jìn)行思考。OOP語(yǔ)言(例如Java,C ++,C#)使開發(fā)人員可以在問題空間中進(jìn)行思考,并使用軟件對(duì)象來(lái)表示和抽象問題空間的實(shí)體進(jìn)行建模從而解決問題。 因此OOP會(huì)更聚焦于問題域!
面向?qū)ο蟛呗?/span>
而現(xiàn)代面向?qū)ο缶幊陶Z(yǔ)言(OOP: Object-Oriented Programming) ,從語(yǔ)言本身角度,其編程的場(chǎng)景則變成下面一種截然不一樣的畫風(fēng):
程序的運(yùn)行態(tài):是不同的實(shí)例化對(duì)象一起協(xié)作干活的場(chǎng)景
應(yīng)用程序通過對(duì)象間消息傳遞,對(duì)象執(zhí)行自身的行為進(jìn)而實(shí)現(xiàn)業(yè)務(wù)需求。編寫代碼,設(shè)計(jì)類,撰寫類的代碼,然而應(yīng)用程序運(yùn)行時(shí),卻是以相應(yīng)的類實(shí)例化的對(duì)象協(xié)作完成邏輯,這就是所謂面向?qū)ο缶幊痰暮x。那么對(duì)于對(duì)象而言,具有哪些屬性呢?
-
對(duì)象是一種抽象,所謂抽象就是類。比如MFC中的Window -
代表現(xiàn)實(shí)世界的實(shí)體 -
類是定義共享公共屬性或?qū)傩缘臄?shù)據(jù)類型 -
對(duì)象是類的實(shí)例存在,類本身在程序的運(yùn)行態(tài)并不存在,以對(duì)象存在。 -
對(duì)象具有狀態(tài),或者稱為屬性,是運(yùn)行時(shí)值。比如MFC窗體大小,title等 -
對(duì)象具有行為,亦稱為方法或者操作。比如常見MFC中窗體具有show方法 -
對(duì)象具有消息,通過發(fā)送消息來(lái)請(qǐng)求對(duì)象執(zhí)行其操作之一,消息是對(duì)象之間交換數(shù)據(jù)的方式。
從上面的描述,應(yīng)用程序本質(zhì)是很多對(duì)象相互協(xié)作一起在干活,就好比一個(gè)車間,有機(jī)器、工人、工具等一起相互在一起產(chǎn)生相互作用,從而完成某項(xiàng)產(chǎn)品的制造。那么,這些對(duì)象從哪里來(lái)呢?
對(duì)象來(lái)自于類的實(shí)例化,誰(shuí)負(fù)責(zé)實(shí)例化對(duì)象呢?這就是類中構(gòu)造函數(shù)干的活,那么析構(gòu)函數(shù)就是銷毀對(duì)象的。所以構(gòu)造函數(shù)管生,析構(gòu)函數(shù)管埋。
構(gòu)造管?“生”
構(gòu)造函數(shù)按照類的樣式生成對(duì)象,也稱為實(shí)例化對(duì)象,那么C++中有哪幾種構(gòu)造函數(shù)呢?
構(gòu)造函數(shù)的相同點(diǎn):
-
函數(shù)名都與類的名字一樣; -
構(gòu)造函數(shù)都沒有返回類型; -
創(chuàng)建對(duì)象時(shí)會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù);
那為嘛又要整這么幾個(gè)不同的構(gòu)造函數(shù)呢?舉個(gè)生活中你或許遇到過的栗子:
-
Case 1: 打比方你去商店對(duì)售貨員說(shuō)買個(gè)燈泡,沒有什么附加信息(比如品牌、功率、發(fā)光類型、尺寸等),那么售貨員把常見的燈泡給你一個(gè),這就相當(dāng)于C++語(yǔ)言中創(chuàng)建對(duì)象時(shí),按照默認(rèn)方式創(chuàng)建,故調(diào)用默認(rèn)構(gòu)造函數(shù)。 -
Case 2: 拿到燈之后回家一裝,擦,太大了裝不上!這回你聰明了,量了下安裝尺寸,跑去給售貨員說(shuō)你要XX大小的燈,此時(shí)就相當(dāng)于C++利用參數(shù)化構(gòu)造函數(shù)實(shí)例化對(duì)象。 -
Case 3: 擦,用了很久燈又掛了,這回你更聰明了,把壞燈卸下來(lái)帶到商店照著買一個(gè),這場(chǎng)景就有點(diǎn)像C++中的拷貝構(gòu)造函數(shù)了~。
那么,到底不同的構(gòu)造函數(shù)有些什么不同呢?為嘛C++語(yǔ)言設(shè)計(jì)這么多種不同的構(gòu)造函數(shù)呢?
-
默認(rèn)構(gòu)造函數(shù):默認(rèn)構(gòu)造函數(shù)不帶任何參數(shù)。 如果不指定構(gòu)造函數(shù),則C ++編譯器會(huì)為我們生成一個(gè)默認(rèn)構(gòu)造函數(shù)(不帶參數(shù),并且具有空主體)。 -
參數(shù)化構(gòu)造函數(shù):參數(shù)傳遞給構(gòu)造函數(shù),這些參數(shù)用于創(chuàng)建對(duì)象時(shí)初始化對(duì)象。要實(shí)現(xiàn)參數(shù)化構(gòu)造函數(shù),只需像向其他函數(shù)一樣向其添加參數(shù)即可。定義構(gòu)造函數(shù)的主體時(shí),使用參數(shù)初始化對(duì)象的數(shù)據(jù)成員。 -
拷貝構(gòu)造函數(shù):拷貝構(gòu)造函數(shù),顧名思義,就是按照現(xiàn)有對(duì)象一模一樣克隆出一個(gè)新的對(duì)象。其參數(shù)一般為現(xiàn)有對(duì)象的引用,一般具有如下函數(shù)原型:
//函數(shù)名為類名,參數(shù)為原對(duì)象const引用
ClassName(const?ClassName?&old_object);?
析構(gòu)管“埋”
析構(gòu)函數(shù)通常用于釋放內(nèi)存,并在銷毀對(duì)象時(shí)對(duì)類對(duì)象及其類成員進(jìn)行其他清理操作。當(dāng)類對(duì)象超出生命周期范圍或被顯式刪除時(shí),將為該類對(duì)象調(diào)用析構(gòu)函數(shù)。
那么析構(gòu)函數(shù)具有哪些特點(diǎn)呢?
銷毀對(duì)象時(shí),將自動(dòng)調(diào)用析構(gòu)函數(shù)。 不能將其聲明為static或const。 析構(gòu)函數(shù)沒有參數(shù),也沒有返回類型。 具有析構(gòu)函數(shù)的類的對(duì)象不能成為聯(lián)合的成員。 析構(gòu)函數(shù)應(yīng)在該類的public部中聲明。 程序員無(wú)法訪問析構(gòu)函數(shù)的地址。 一個(gè)類有且僅有一個(gè)析構(gòu)函數(shù)。 如果沒有顯式定義析構(gòu)函數(shù),編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)的析構(gòu)函數(shù)。
既然析構(gòu)函數(shù)是構(gòu)造函數(shù)的反向操作,對(duì)于對(duì)象管"埋",那么什么時(shí)候“埋”呢?
-
函數(shù)返回退出 -
程序被關(guān)掉,比如一個(gè)應(yīng)用程序被kill -
包含局部對(duì)象的塊結(jié)尾 -
主動(dòng)調(diào)用刪除符delete
前面說(shuō)如果程序猿沒有顯式定義析構(gòu)函數(shù),編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)的析構(gòu)函數(shù)。言下之意是有的時(shí)候需要顯式定義析構(gòu)函數(shù),那么什么時(shí)候需要呢?
當(dāng)類中動(dòng)態(tài)分配了內(nèi)存時(shí),或當(dāng)一個(gè)類包含指向在該類中分配的內(nèi)存的指針時(shí),應(yīng)該編寫一個(gè)析構(gòu)函數(shù)以釋放該類實(shí)例之前的內(nèi)存。否則會(huì)造成內(nèi)存泄漏。
“生”與“埋”舉例
前面說(shuō)構(gòu)造管“生”,析構(gòu)管“埋”,那么到底怎么“生”的呢?怎么“埋”呢?,看看栗子:
#include?
using?namespace?std;
class?Rectangle
{
public:?
?Rectangle();?
?Rectangle(int?w,?int?l);
? Rectangle(const?Rectangle?&rct)?{width?=?rct.width;?length?=?rct.length;?}
?~Rectangle();
public:
? int?width,?length;
};
Rectangle::Rectangle()
{
? cout?<"默認(rèn)矩形誕生了!"?<endl;
}
Rectangle::Rectangle(int?w,?int?l)
{
?width ?=?w;
? length?=?l;
?cout?<"指定矩形誕生了!"?<endl;
}
Rectangle::~Rectangle()
{
? cout?<"矩形埋掉了!"?<endl;
}
int?main()
{?
? Rectangle?rct1;
? Rectangle?*pRct?=?new?Rectangle(2,3);
?? Rectangle?rct2??=?rct1;
??
?return?0;
}
這個(gè)簡(jiǎn)單的代碼,實(shí)際運(yùn)行的輸出結(jié)果:
默認(rèn)矩形誕生了!
指定矩形誕生了!
矩形埋掉了!
矩形埋掉了!
技術(shù)人總是喜歡眼見為實(shí):因?yàn)榭匆?,所以相信?/strong>,看看其對(duì)應(yīng)的匯編代碼(VC++ 2010匯編結(jié)果,這里僅貼出main函數(shù),僅為理解原理,對(duì)于匯編指令不做描述,其中#為對(duì)匯編注釋):
31: int main()
32: {
012C1660 55 push ebp
012C1661 8B EC mov ebp,esp
012C1663 6A FF push 0FFFFFFFFh
012C1665 68 76 53 2C 01 push offset __ehhandler$_main (12C5376h)
012C166A 64 A1 00 00 00 00 mov eax,dword ptr fs:[00000000h]
012C1670 50 push eax
012C1671 81 EC 14 01 00 00 sub esp,114h
012C1677 53 push ebx
012C1678 56 push esi
012C1679 57 push edi
012C167A 8D BD E0 FE FF FF lea edi,[ebp-120h]
012C1680 B9 45 00 00 00 mov ecx,45h
012C1685 B8 CC CC CC CC mov eax,0CCCCCCCCh
012C168A F3 AB rep stos dword ptr es:[edi]
012C168C A1 00 90 2C 01 mov eax,dword ptr [___security_cookie (12C9000h)]
012C1691 33 C5 xor eax,ebp
012C1693 50 push eax
012C1694 8D 45 F4 lea eax,[ebp-0Ch]
012C1697 64 A3 00 00 00 00 mov dword ptr fs:[00000000h],eax
33: Rectangle rct1;
012C169D 8D 4D E8 lea ecx,[ebp-18h]
#調(diào)用默認(rèn)構(gòu)造函數(shù)管“生”
012C16A0 E8 32 FA FF FF call Rectangle::Rectangle (12C10D7h)
012C16A5 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
34: Rectangle *pRct = new Rectangle(2,3);
012C16AC 6A 08 push 8
012C16AE E8 41 FB FF FF call operator new (12C11F4h)
012C16B3 83 C4 04 add esp,4
012C16B6 89 85 F4 FE FF FF mov dword ptr [ebp-10Ch],eax
012C16BC C6 45 FC 01 mov byte ptr [ebp-4],1
012C16C0 83 BD F4 FE FF FF 00 cmp dword ptr [ebp-10Ch],0
012C16C7 74 17 je main+80h (12C16E0h)
012C16C9 6A 03 push 3 #傳參
012C16CB 6A 02 push 2 #傳參
012C16CD 8B 8D F4 FE FF FF mov ecx,dword ptr [ebp-10Ch]
#調(diào)用參數(shù)化構(gòu)造函數(shù)
012C16D3 E8 B8 FA FF FF call Rectangle::Rectangle (12C1190h)
012C16D8 89 85 E0 FE FF FF mov dword ptr [ebp-120h],eax
012C16DE EB 0A jmp main+8Ah (12C16EAh)
012C16E0 C7 85 E0 FE FF FF 00 00 00 00 mov dword ptr [ebp-120h],0
012C16EA 8B 85 E0 FE FF FF mov eax,dword ptr [ebp-120h]
012C16F0 89 85 E8 FE FF FF mov dword ptr [ebp-118h],eax
012C16F6 C6 45 FC 00 mov byte ptr [ebp-4],0
012C16FA 8B 8D E8 FE FF FF mov ecx,dword ptr [ebp-118h]
012C1700 89 4D DC mov dword ptr [ebp-24h],ecx
35: Rectangle rct2 = rct1;
012C1703 8D 45 E8 lea eax,[ebp-18h]
012C1706 50 push eax
012C1707 8D 4D CC lea ecx,[ebp-34h]
#調(diào)用拷貝構(gòu)造函數(shù)
012C170A E8 3C F9 FF FF call Rectangle::Rectangle (12C104Bh)
36:
37: return 0;
012C170F C7 85 00 FF FF FF 00 00 00 00 mov dword ptr [ebp-100h],0
012C1719 8D 4D CC lea ecx,[ebp-34h]
#調(diào)用析構(gòu)函數(shù),銷毀rct2
012C171C E8 15 FA FF FF call Rectangle::~Rectangle (12C1136h)
012C1721 C7 45 FC FF FF FF FF mov dword ptr [ebp-4],0FFFFFFFFh
012C1728 8D 4D E8 lea ecx,[ebp-18h]
#調(diào)用析構(gòu)函數(shù),銷毀rct1
012C172B E8 06 FA FF FF call Rectangle::~Rectangle (12C1136h)
012C1730 8B 85 00 FF FF FF mov eax,dword ptr [ebp-100h]
38: }
這里引發(fā)幾個(gè)問題:
問題1:為什么先析構(gòu)rct2,后析構(gòu)rct1呢?
這是由于這兩個(gè)對(duì)象在棧上分配內(nèi)存,所以基于棧的特性,顯然rct2位于C++運(yùn)行時(shí)棧的頂部,而rct1位于棧底。
你如不信,將上述代碼修改一下,測(cè)測(cè):
Rectangle::~Rectangle()
{
????cout?<<"當(dāng)前寬為:"?<"矩形埋掉了!"?<}
int?main()
{?
?Rectangle?rct1;
? rct1.width?=?1;
? Rectangle?*pRct?=?new?Rectangle(2,3);
? ?Rectangle?rct2??=?rct1;
? ?rct2.width?=?2;
?return?0;
}
其輸出結(jié)果為:
默認(rèn)矩形誕生了!
指定矩形誕生了!
當(dāng)前寬為:2矩形埋掉了!
當(dāng)前寬為:1矩形埋掉了!
問題2:請(qǐng)問上述代碼,構(gòu)造函數(shù)被調(diào)用了幾次?析構(gòu)函數(shù)又被調(diào)用了幾次?這是經(jīng)常面試會(huì)考察的基礎(chǔ)知識(shí)。顯然前面的打印以及給出了答案。
問題3:該代碼有啥隱患?
答:調(diào)用了new,而沒有調(diào)用delete,為啥這是隱患,new屬于動(dòng)態(tài)申請(qǐng)內(nèi)存,是在堆上為對(duì)象申請(qǐng)內(nèi)存,這屬于典型的管“生”不管“埋”,造成內(nèi)存泄漏,如果整的多了,**必然尸體埋 “堆”!**造成程序引發(fā)不可預(yù)料的崩潰!
所以應(yīng)該修正一下:
Rectangle::~Rectangle()
{
????cout?<<"當(dāng)前寬為:"?<"矩形埋掉了!"?<endl;
}
int?main()
{?
?Rectangle?rct1;
??rct1.width?=?1;
?Rectangle?*pRct?=?new?Rectangle(2,3);
? ?Rectangle?rct2??=?rct1;
? ?rct2.width?=?3;
?delete?pRct;
?cout?<"手動(dòng)埋掉!"?<endl;
?return?0;
}
看看輸出結(jié)果:
默認(rèn)矩形誕生了!
指定矩形誕生了!
當(dāng)前寬為:2矩形埋掉了!
手動(dòng)埋掉!
當(dāng)前寬為:3矩形埋掉了!
當(dāng)前寬為:1矩形埋掉了!
總結(jié)一下
-
OOP的基本單元是類,該類將靜態(tài)屬性和動(dòng)態(tài)行為封裝在一個(gè)黑盒中,并指定使用這些框的公共接口。由于該類具有很好的封裝(與功能相比),因此重用這些類會(huì)更加容易。換句話說(shuō),OOP將同一黑盒中軟件實(shí)體的數(shù)據(jù)結(jié)構(gòu)和算法較好結(jié)合在一起。 -
OOP語(yǔ)言允許更高級(jí)別的抽象來(lái)解決現(xiàn)實(shí)生活中的問題。傳統(tǒng)的過程語(yǔ)言例如C迫使您根據(jù)計(jì)算機(jī)的結(jié)構(gòu)(例如內(nèi)存位和字節(jié),數(shù)組,決策,循環(huán))進(jìn)行思考,而不是根據(jù)您要解決的問題進(jìn)行思考。OOP語(yǔ)言(例如Java,C ++,C#)使您可以在問題空間中進(jìn)行思考,并使用軟件對(duì)象來(lái)表示和抽象問題空間的實(shí)體以解決問題。 -
對(duì)于C++語(yǔ)言,構(gòu)造函數(shù)與析構(gòu)函數(shù)是基礎(chǔ)中的基礎(chǔ),類在運(yùn)行態(tài)并不存在,類以對(duì)象形式在運(yùn)行態(tài)實(shí)現(xiàn)業(yè)務(wù)需求。對(duì)象如何按照類黑盒樣式如何在運(yùn)行態(tài)誕生,利用類的構(gòu)造函數(shù)而誕生,對(duì)象生存期結(jié)束,析構(gòu)函數(shù)管“埋”,銷毀對(duì)象。 -
對(duì)于局部對(duì)象,非new產(chǎn)生的對(duì)象,誕生地為棧,在棧中誕生,編譯器會(huì)插入析構(gòu)函數(shù)使得程序運(yùn)行態(tài)在對(duì)象生命周期結(jié)束時(shí)自動(dòng)管“埋”,而如果利用new動(dòng)態(tài)創(chuàng)建的對(duì)象,則需要手動(dòng)管“埋”,如手動(dòng)管“生”(new),不手動(dòng)管“埋”(delete),對(duì)象必成孤魂野鬼,嚴(yán)重時(shí),對(duì)象尸體滿“堆”。
對(duì)于拷貝構(gòu)造函數(shù),還有一個(gè)所謂深拷貝、淺拷貝的要點(diǎn)沒有涉及,下次學(xué)習(xí)總結(jié)分享一下,敬請(qǐng)關(guān)注期待~,如發(fā)現(xiàn)文中有錯(cuò)誤,敬請(qǐng)留言指正,不勝感激~
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!