果一個(gè)人自稱(chēng)為程序高手,卻對(duì)內(nèi)存一無(wú)所知,那么我可以告訴你,他一定在吹牛。用C或C++寫(xiě)程序,需要更多地關(guān)注內(nèi)存,這不僅僅是因?yàn)閮?nèi)存的分配是否合理直接影響著程序的效率和性能,更為主要的是,當(dāng)我們操作內(nèi)存的時(shí)候一不小心就會(huì)出現(xiàn)問(wèn)題,而且很多時(shí)候,這些問(wèn)題都是不易發(fā)覺(jué)的,比如內(nèi)存泄漏,比如懸掛指針。筆者今天在這里并不是要討論如何避免這些問(wèn)題,而是想從另外一個(gè)角度來(lái)認(rèn)識(shí)C++內(nèi)存對(duì)象。
我們知道,C++將內(nèi)存劃分為三個(gè)邏輯區(qū)域:堆、棧和靜態(tài)存儲(chǔ)區(qū)。既然如此,我稱(chēng)位于它們之中的對(duì)象分別為堆對(duì)象,棧對(duì)象以及靜態(tài)對(duì)象。那么這些不同的內(nèi)存對(duì)象有什么區(qū)別了?堆對(duì)象和棧對(duì)象各有什么優(yōu)劣了?如何禁止創(chuàng)建堆對(duì)象或棧對(duì)象了?這些便是今天的主題。 一.基本概念 先來(lái)看看棧。棧,一般用于存放局部變量或?qū)ο?,如我們?cè)诤瘮?shù)定義中用類(lèi)似下面語(yǔ)句聲明的對(duì)象: Type stack_object ; stack_object便是一個(gè)棧對(duì)象,它的生命期是從定義點(diǎn)開(kāi)始,當(dāng)所在函數(shù)返回時(shí),生命結(jié)束。 另外,幾乎所有的臨時(shí)對(duì)象都是棧對(duì)象。比如,下面的函數(shù)定義: Type fun(Type object) ; 這個(gè)函數(shù)至少產(chǎn)生兩個(gè)臨時(shí)對(duì)象,首先,參數(shù)是按值傳遞的,所以會(huì)調(diào)用拷貝構(gòu)造函數(shù)生成一個(gè)臨時(shí)對(duì)象object_copy1 ,在函數(shù)內(nèi)部使用的不是使用的不是object,而是object_copy1,自然,object_copy1是一個(gè)棧對(duì)象,它在函數(shù)返回時(shí)被釋放;還有這個(gè)函數(shù)是值返回的,在函數(shù)返回時(shí),如果我們不考慮返回值優(yōu)化(NRV),那么也會(huì)產(chǎn)生一個(gè)臨時(shí)對(duì)象object_copy2,這個(gè)臨時(shí)對(duì)象會(huì)在函數(shù)返回后一段時(shí)間內(nèi)被釋放。比如某個(gè)函數(shù)中有如下代碼: Type tt ,result ; //生成兩個(gè)棧對(duì)象 上面的第二個(gè)語(yǔ)句的執(zhí)行情況是這樣的,首先函數(shù)fun返回時(shí)生成一個(gè)臨時(shí)對(duì)象object_copy2 ,然后再調(diào)用賦值運(yùn)算符執(zhí)行 tt = object_copy2 ; //調(diào)用賦值運(yùn)算符 看到了嗎?編譯器在我們毫無(wú)知覺(jué)的情況下,為我們生成了這么多臨時(shí)對(duì)象,而生成這些臨時(shí)對(duì)象的時(shí)間和空間的開(kāi)銷(xiāo)可能是很大的,所以,你也許明白了,為什么對(duì)于“大”對(duì)象最好用const引用傳遞代替按值進(jìn)行函數(shù)參數(shù)傳遞了。 接下來(lái),看看堆。堆,又叫自由存儲(chǔ)區(qū),它是在程序執(zhí)行的過(guò)程中動(dòng)態(tài)分配的,所以它最大的特性就是動(dòng)態(tài)性。在C++中,所有堆對(duì)象的創(chuàng)建和銷(xiāo)毀都要由程序員負(fù)責(zé),所以,如果處理不好,就會(huì)發(fā)生內(nèi)存問(wèn)題。如果分配了堆對(duì)象,卻忘記了釋放,就會(huì)產(chǎn)生內(nèi)存泄漏;而如果已釋放了對(duì)象,卻沒(méi)有將相應(yīng)的指針置為NULL,該指針就是所謂的“懸掛指針”,再度使用此指針時(shí),就會(huì)出現(xiàn)非法訪問(wèn),嚴(yán)重時(shí)就導(dǎo)致程序崩潰。 那么,C++中是怎樣分配堆對(duì)象的?唯一的方法就是用new(當(dāng)然,用類(lèi)malloc指令也可獲得C式堆內(nèi)存),只要使用new,就會(huì)在堆中分配一塊內(nèi)存,并且返回指向該堆對(duì)象的指針。 再來(lái)看看靜態(tài)存儲(chǔ)區(qū)。所有的靜態(tài)對(duì)象、全局對(duì)象都于靜態(tài)存儲(chǔ)區(qū)分配。關(guān)于全局對(duì)象,是在main()函數(shù)執(zhí)行前就分配好了的。其實(shí),在main()函數(shù)中的顯示代碼執(zhí)行之前,會(huì)調(diào)用一個(gè)由編譯器生成的_main()函數(shù),而_main()函數(shù)會(huì)進(jìn)行所有全局對(duì)象的的構(gòu)造及初始化工作。而在main()函數(shù)結(jié)束之前,會(huì)調(diào)用由編譯器生成的exit函數(shù),來(lái)釋放所有的全局對(duì)象。比如下面的代碼: void main(void) 實(shí)際上,被轉(zhuǎn)化成這樣: void main(void) 所以,知道了這個(gè)之后,便可以由此引出一些技巧,如,假設(shè)我們要在main()函數(shù)執(zhí)行之前做某些準(zhǔn)備工作,那么我們可以將這些準(zhǔn)備工作寫(xiě)到一個(gè)自定義的全局對(duì)象的構(gòu)造函數(shù)中,這樣,在main()函數(shù)的顯式代碼執(zhí)行之前,這個(gè)全局對(duì)象的構(gòu)造函數(shù)會(huì)被調(diào)用,執(zhí)行預(yù)期的動(dòng)作,這樣就達(dá)到了我們的目的。 剛才講的是靜態(tài)存儲(chǔ)區(qū)中的全局對(duì)象,那么,局部靜態(tài)對(duì)象了?局部靜態(tài)對(duì)象通常也是在函數(shù)中定義的,就像棧對(duì)象一樣,只不過(guò),其前面多了個(gè)static關(guān)鍵字。局部靜態(tài)對(duì)象的生命期是從其所在函數(shù)第一次被調(diào)用,更確切地說(shuō),是當(dāng)?shù)谝淮螆?zhí)行到該靜態(tài)對(duì)象的聲明代碼時(shí),產(chǎn)生該靜態(tài)局部對(duì)象,直到整個(gè)程序結(jié)束時(shí),才銷(xiāo)毀該對(duì)象。 還有一種靜態(tài)對(duì)象,那就是它作為class的靜態(tài)成員??紤]這種情況時(shí),就牽涉了一些較復(fù)雜的問(wèn)題。 第一個(gè)問(wèn)題是class的靜態(tài)成員對(duì)象的生命期,class的靜態(tài)成員對(duì)象隨著第一個(gè)class object的產(chǎn)生而產(chǎn)生,在整個(gè)程序結(jié)束時(shí)消亡。也就是有這樣的情況存在,在程序中我們定義了一個(gè)class,該類(lèi)中有一個(gè)靜態(tài)對(duì)象作為成員,但是在程序執(zhí)行過(guò)程中,如果我們沒(méi)有創(chuàng)建任何一個(gè)該class object,那么也就不會(huì)產(chǎn)生該class所包含的那個(gè)靜態(tài)對(duì)象。還有,如果創(chuàng)建了多個(gè)class object,那么所有這些object都共享那個(gè)靜態(tài)對(duì)象成員。 第二個(gè)問(wèn)題是,當(dāng)出現(xiàn)下列情況時(shí): class Base Base example ; 請(qǐng)注意上面標(biāo)為黑體的三條語(yǔ)句,它們所訪問(wèn)的s_object是同一個(gè)對(duì)象嗎?答案是肯定的,它們的確是指向同一個(gè)對(duì)象,這聽(tīng)起來(lái)不像是真的,是嗎?但這是事實(shí),你可以自己寫(xiě)段簡(jiǎn)單的代碼驗(yàn)證一下。我要做的是來(lái)解釋為什么會(huì)這樣? 我們知道,當(dāng)一個(gè)類(lèi)比如Derived1,從另一個(gè)類(lèi)比如Base繼承時(shí),那么,可以看作一個(gè)Derived1對(duì)象中含有一個(gè)Base型的對(duì)象,這就是一個(gè)subobject。一個(gè)Derived1對(duì)象的大致內(nèi)存布局如下: 所有繼承Base類(lèi)的派生類(lèi)的對(duì)象都含有一個(gè)Base型的subobject(這是能用Base型指針指向一個(gè)Derived1對(duì)象的關(guān)鍵所在,自然也是多態(tài)的關(guān)鍵了),而所有的subobject和所有Base型的對(duì)象都共用同一個(gè)s_object對(duì)象,自然,從Base類(lèi)派生的整個(gè)繼承體系中的類(lèi)的實(shí)例都會(huì)共用同一個(gè)s_object對(duì)象了。上面提到的example、example1、example2的對(duì)象布局如下圖所示: 二.三種內(nèi)存對(duì)象的比較 棧對(duì)象的優(yōu)勢(shì)是在適當(dāng)?shù)臅r(shí)候自動(dòng)生成,又在適當(dāng)?shù)臅r(shí)候自動(dòng)銷(xiāo)毀,不需要程序員操心;而且棧對(duì)象的創(chuàng)建速度一般較堆對(duì)象快,因?yàn)榉峙涠褜?duì)象時(shí),會(huì)調(diào)用operator new操作,operator new會(huì)采用某種內(nèi)存空間搜索算法,而該搜索過(guò)程可能是很費(fèi)時(shí)間的,產(chǎn)生棧對(duì)象則沒(méi)有這么麻煩,它僅僅需要移動(dòng)棧頂指針就可以了。但是要注意的是,通常??臻g容量比較小,一般是1MB~2MB,所以體積比較大的對(duì)象不適合在棧中分配。特別要注意遞歸函數(shù)中最好不要使用棧對(duì)象,因?yàn)殡S著遞歸調(diào)用深度的增加,所需的棧空間也會(huì)線性增加,當(dāng)所需??臻g不夠時(shí),便會(huì)導(dǎo)致棧溢出,這樣就會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。 堆對(duì)象,其產(chǎn)生時(shí)刻和銷(xiāo)毀時(shí)刻都要程序員精確定義,也就是說(shuō),程序員對(duì)堆對(duì)象的生命具有完全的控制權(quán)。我們常常需要這樣的對(duì)象,比如,我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象,能夠被多個(gè)函數(shù)所訪問(wèn),但是又不想使其成為全局的,那么這個(gè)時(shí)候創(chuàng)建一個(gè)堆對(duì)象無(wú)疑是良好的選擇,然后在各個(gè)函數(shù)之間傳遞這個(gè)堆對(duì)象的指針,便可以實(shí)現(xiàn)對(duì)該對(duì)象的共享。另外,相比于棧空間,堆的容量要大得多。實(shí)際上,當(dāng)物理內(nèi)存不夠時(shí),如果這時(shí)還需要生成新的堆對(duì)象,通常不會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤,而是系統(tǒng)會(huì)使用虛擬內(nèi)存來(lái)擴(kuò)展實(shí)際的物理內(nèi)存。 首先是全局對(duì)象。全局對(duì)象為類(lèi)間通信和函數(shù)間通信提供了一種最簡(jiǎn)單的方式,雖然這種方式并不優(yōu)雅。一般而言,在完全的面向?qū)ο笳Z(yǔ)言中,是不存在全局對(duì)象的,比如C#,因?yàn)槿謱?duì)象意味著不安全和高耦合,在程序中過(guò)多地使用全局對(duì)象將大大降低程序的健壯性、穩(wěn)定性、可維護(hù)性和可復(fù)用性。C++也完全可以剔除全局對(duì)象,但是最終沒(méi)有,我想原因之一是為了兼容C。 其次是類(lèi)的靜態(tài)成員,上面已經(jīng)提到,基類(lèi)及其派生類(lèi)的所有對(duì)象都共享這個(gè)靜態(tài)成員對(duì)象,所以當(dāng)需要在這些class之間或這些class objects之間進(jìn)行數(shù)據(jù)共享或通信時(shí),這樣的靜態(tài)成員無(wú)疑是很好的選擇。 接著是靜態(tài)局部對(duì)象,主要可用于保存該對(duì)象所在函數(shù)被屢次調(diào)用期間的中間狀態(tài),其中一個(gè)最顯著的例子就是遞歸函數(shù),我們都知道遞歸函數(shù)是自己調(diào)用自己的函數(shù),如果在遞歸函數(shù)中定義一個(gè)nonstatic局部對(duì)象,那么當(dāng)遞歸次數(shù)相當(dāng)大時(shí),所產(chǎn)生的開(kāi)銷(xiāo)也是巨大的。這是因?yàn)閚onstatic局部對(duì)象是棧對(duì)象,每遞歸調(diào)用一次,就會(huì)產(chǎn)生一個(gè)這樣的對(duì)象,每返回一次,就會(huì)釋放這個(gè)對(duì)象,而且,這樣的對(duì)象只局限于當(dāng)前調(diào)用層,對(duì)于更深入的嵌套層和更淺露的外層,都是不可見(jiàn)的。每個(gè)層都有自己的局部對(duì)象和參數(shù)。 在遞歸函數(shù)設(shè)計(jì)中,可以使用static對(duì)象替代nonstatic局部對(duì)象(即棧對(duì)象),這不僅可以減少每次遞歸調(diào)用和返回時(shí)產(chǎn)生和釋放nonstatic對(duì)象的開(kāi)銷(xiāo),而且static對(duì)象還可以保存遞歸調(diào)用的中間狀態(tài),并且可為各個(gè)調(diào)用層所訪問(wèn)。 三.使用棧對(duì)象的意外收獲 前面已經(jīng)介紹到,棧對(duì)象是在適當(dāng)?shù)臅r(shí)候創(chuàng)建,然后在適當(dāng)?shù)臅r(shí)候自動(dòng)釋放的,也就是棧對(duì)象有自動(dòng)管理功能。那么棧對(duì)象會(huì)在什么會(huì)自動(dòng)釋放了?第一,在其生命期結(jié)束的時(shí)候;第二,在其所在的函數(shù)發(fā)生異常的時(shí)候。你也許說(shuō),這些都很正常啊,沒(méi)什么大不了的。是的,沒(méi)什么大不了的。但是只要我們?cè)偕钊胍稽c(diǎn)點(diǎn),也許就有意外的收獲了。 棧對(duì)象,自動(dòng)釋放時(shí),會(huì)調(diào)用它自己的析構(gòu)函數(shù)。如果我們?cè)跅?duì)象中封裝資源,而且在棧對(duì)象的析構(gòu)函數(shù)中執(zhí)行釋放資源的動(dòng)作,那么就會(huì)使資源泄漏的概率大大降低,因?yàn)闂?duì)象可以自動(dòng)的釋放資源,即使在所在函數(shù)發(fā)生異常的時(shí)候。實(shí)際的過(guò)程是這樣的:函數(shù)拋出異常時(shí),會(huì)發(fā)生所謂的stack_unwinding(堆?;貪L),即堆棧會(huì)展開(kāi),由于是棧對(duì)象,自然存在于棧中,所以在堆?;貪L的過(guò)程中,棧對(duì)象的析構(gòu)函數(shù)會(huì)被執(zhí)行,從而釋放其所封裝的資源。除非,除非在析構(gòu)函數(shù)執(zhí)行的過(guò)程中再次拋出異常――而這種可能性是很小的,所以用棧對(duì)象封裝資源是比較安全的?;诖苏J(rèn)識(shí),我們就可以創(chuàng)建一個(gè)自己的句柄或代理來(lái)封裝資源了。智能指針(auto_ptr)中就使用了這種技術(shù)。在有這種需要的時(shí)候,我們就希望我們的資源封裝類(lèi)只能在棧中創(chuàng)建,也就是要限制在堆中創(chuàng)建該資源封裝類(lèi)的實(shí)例。 四.禁止產(chǎn)生堆對(duì)象 上面已經(jīng)提到,你決定禁止產(chǎn)生某種類(lèi)型的堆對(duì)象,這時(shí)你可以自己創(chuàng)建一個(gè)資源封裝類(lèi),該類(lèi)對(duì)象只能在棧中產(chǎn)生,這樣就能在異常的情況下自動(dòng)釋放封裝的資源。 那么怎樣禁止產(chǎn)生堆對(duì)象了?我們已經(jīng)知道,產(chǎn)生堆對(duì)象的唯一方法是使用new操作,如果我們禁止使用new不就行了么。再進(jìn)一步,new操作執(zhí)行時(shí)會(huì)調(diào)用operator new,而operator new是可以重載的。方法有了,就是使new operator 為private,為了對(duì)稱(chēng),最好將operator delete也重載為private?,F(xiàn)在,你也許又有疑問(wèn)了,難道創(chuàng)建棧對(duì)象不需要調(diào)用new嗎?是的,不需要,因?yàn)閯?chuàng)建棧對(duì)象不需要搜索內(nèi)存,而是直接調(diào)整堆棧指針,將對(duì)象壓棧,而operator new的主要任務(wù)是搜索合適的堆內(nèi)存,為堆對(duì)象分配空間,這在上面已經(jīng)提到過(guò)了。好,讓我們看看下面的示例代碼: #include <stdlib.h> //需要用到C式內(nèi)存分配函數(shù) NoHashObject現(xiàn)在就是一個(gè)禁止堆對(duì)象的類(lèi)了,如果你寫(xiě)下如下代碼: NoHashObject* fp = new NoHashObject() ; //編譯期錯(cuò)誤! 上面代碼會(huì)產(chǎn)生編譯期錯(cuò)誤。好了,現(xiàn)在你已經(jīng)知道了如何設(shè)計(jì)一個(gè)禁止堆對(duì)象的類(lèi)了,你也許和我一樣有這樣的疑問(wèn),難道在類(lèi)NoHashObject的定義不能改變的情況下,就一定不能產(chǎn)生該類(lèi)型的堆對(duì)象了嗎?不,還是有辦法的,我稱(chēng)之為“暴力破解法”。C++是如此地強(qiáng)大,強(qiáng)大到你可以用它做你想做的任何事情。這里主要用到的是技巧是指針類(lèi)型的強(qiáng)制轉(zhuǎn)換。 void main(void) //強(qiáng)制類(lèi)型轉(zhuǎn)換,現(xiàn)在ptr是一個(gè)指向NoHashObject對(duì)象的指針 temp = NULL ; //防止通過(guò)temp指針修改NoHashObject對(duì)象 //再一次強(qiáng)制類(lèi)型轉(zhuǎn)換,讓rp指針指向堆中NoHashObject對(duì)象的ptr成員 //初始化obj_ptr指向的NoHashObject對(duì)象的ptr成員 delete rp ;//釋放資源 上面的實(shí)現(xiàn)是麻煩的,而且這種實(shí)現(xiàn)方式幾乎不會(huì)在實(shí)踐中使用,但是我還是寫(xiě)出來(lái)路,因?yàn)槔斫馑瑢?duì)于我們理解C++內(nèi)存對(duì)象是有好處的。對(duì)于上面的這么多強(qiáng)制類(lèi)型轉(zhuǎn)換,其最根本的是什么了?我們可以這樣理解: 某塊內(nèi)存中的數(shù)據(jù)是不變的,而類(lèi)型就是我們戴上的眼鏡,當(dāng)我們戴上一種眼鏡后,我們就會(huì)用對(duì)應(yīng)的類(lèi)型來(lái)解釋內(nèi)存中的數(shù)據(jù),這樣不同的解釋就得到了不同的信息。 所謂強(qiáng)制類(lèi)型轉(zhuǎn)換實(shí)際上就是換上另一副眼鏡后再來(lái)看同樣的那塊內(nèi)存數(shù)據(jù)。 另外要提醒的是,不同的編譯器對(duì)對(duì)象的成員數(shù)據(jù)的布局安排可能是不一樣的,比如,大多數(shù)編譯器將NoHashObject的ptr指針成員安排在對(duì)象空間的頭4個(gè)字節(jié),這樣才會(huì)保證下面這條語(yǔ)句的轉(zhuǎn)換動(dòng)作像我們預(yù)期的那樣執(zhí)行: Resource* rp = (Resource*)obj_ptr ; 但是,并不一定所有的編譯器都是如此。 既然我們可以禁止產(chǎn)生某種類(lèi)型的堆對(duì)象,那么可以設(shè)計(jì)一個(gè)類(lèi),使之不能產(chǎn)生棧對(duì)象嗎?當(dāng)然可以。 五.禁止產(chǎn)生棧對(duì)象 前面已經(jīng)提到了,創(chuàng)建棧對(duì)象時(shí)會(huì)移動(dòng)棧頂指針以“挪出”適當(dāng)大小的空間,然后在這個(gè)空間上直接調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù)以形成一個(gè)棧對(duì)象,而當(dāng)函數(shù)返回時(shí),會(huì)調(diào)用其析構(gòu)函數(shù)釋放這個(gè)對(duì)象,然后再調(diào)整棧頂指針收回那塊棧內(nèi)存。在這個(gè)過(guò)程中是不需要operator new/delete操作的,所以將operator new/delete設(shè)置為private不能達(dá)到目的。當(dāng)然從上面的敘述中,你也許已經(jīng)想到了:將構(gòu)造函數(shù)或析構(gòu)函數(shù)設(shè)為私有的,這樣系統(tǒng)就不能調(diào)用構(gòu)造/析構(gòu)函數(shù)了,當(dāng)然就不能在棧中生成對(duì)象了。 這樣的確可以,而且我也打算采用這種方案。但是在此之前,有一點(diǎn)需要考慮清楚,那就是,如果我們將構(gòu)造函數(shù)設(shè)置為私有,那么我們也就不能用new來(lái)直接產(chǎn)生堆對(duì)象了,因?yàn)閚ew在為對(duì)象分配空間后也會(huì)調(diào)用它的構(gòu)造函數(shù)啊。所以,我打算只將析構(gòu)函數(shù)設(shè)置為private。再進(jìn)一步,將析構(gòu)函數(shù)設(shè)為private除了會(huì)限制棧對(duì)象生成外,還有其它影響嗎?是的,這還會(huì)限制繼承。 如果一個(gè)類(lèi)不打算作為基類(lèi),通常采用的方案就是將其析構(gòu)函數(shù)聲明為private。 為了限制棧對(duì)象,卻不限制繼承,我們可以將析構(gòu)函數(shù)聲明為protected,這樣就兩全其美了。如下代碼所示: class NoStackObject 接著,可以像這樣使用NoStackObject類(lèi): NoStackObject* hash_ptr = new NoStackObject() ; 呵呵,是不是覺(jué)得有點(diǎn)怪怪的,我們用new創(chuàng)建一個(gè)對(duì)象,卻不是用delete去刪除它,而是要用destroy方法。很顯然,用戶(hù)是不習(xí)慣這種怪異的使用方式的。所以,我決定將構(gòu)造函數(shù)也設(shè)為private或protected。這又回到了上面曾試圖避免的問(wèn)題,即不用new,那么該用什么方式來(lái)生成一個(gè)對(duì)象了?我們可以用間接的辦法完成,即讓這個(gè)類(lèi)提供一個(gè)static成員函數(shù)專(zhuān)門(mén)用于產(chǎn)生該類(lèi)型的堆對(duì)象。(設(shè)計(jì)模式中的singleton模式就可以用這種方式實(shí)現(xiàn)。)讓我們來(lái)看看: class NoStackObject 現(xiàn)在可以這樣使用NoStackObject類(lèi)了: NoStackObject* hash_ptr = NoStackObject::creatInstance() ; 現(xiàn)在感覺(jué)是不是好多了,生成對(duì)象和釋放對(duì)象的操作一致了。 ok,講到這里,已經(jīng)涉及了較多的東西,如果要把內(nèi)存對(duì)象講得更深入更全面,那可能需要寫(xiě)成一本書(shū)了,而就我自己的功力而言,可能是很難完全把握的。如果上面所寫(xiě)的能使你有所收獲或啟發(fā),我就滿(mǎn)足了。如果你要更進(jìn)一步去了解內(nèi)存對(duì)象方面的知識(shí),那么我可以推薦你看看《深入探索C++對(duì)象模型》這本書(shū) |
|
來(lái)自: yiherainbow > 《我的圖書(shū)館》