果一個(gè)人自稱為程序高手,,卻對(duì)內(nèi)存一無(wú)所知,,那么我可以告訴你,他一定在吹牛,。用C或C++寫程序,,需要更多地關(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ū)。既然如此,,我稱位于它們之中的對(duì)象分別為堆對(duì)象,,棧對(duì)象以及靜態(tài)對(duì)象,。那么這些不同的內(nèi)存對(duì)象有什么區(qū)別了?堆對(duì)象和棧對(duì)象各有什么優(yōu)劣了,?如何禁止創(chuàng)建堆對(duì)象或棧對(duì)象了,?這些便是今天的主題。 一.基本概念 先來(lái)看看棧,。棧,,一般用于存放局部變量或?qū)ο螅缥覀冊(cè)诤瘮?shù)定義中用類似下面語(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)銷可能是很大的,,所以,你也許明白了,,為什么對(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)建和銷毀都要由程序員負(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)然,用類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)備工作寫到一個(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í),,才銷毀該對(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,,該類中有一個(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í),你可以自己寫段簡(jiǎn)單的代碼驗(yàn)證一下,。我要做的是來(lái)解釋為什么會(huì)這樣,? 我們知道,當(dāng)一個(gè)類比如Derived1,,從另一個(gè)類比如Base繼承時(shí),,那么,可以看作一個(gè)Derived1對(duì)象中含有一個(gè)Base型的對(duì)象,,這就是一個(gè)subobject,。一個(gè)Derived1對(duì)象的大致內(nèi)存布局如下: 所有繼承Base類的派生類的對(duì)象都含有一個(gè)Base型的subobject(這是能用Base型指針指向一個(gè)Derived1對(duì)象的關(guān)鍵所在,,自然也是多態(tài)的關(guān)鍵了),而所有的subobject和所有Base型的對(duì)象都共用同一個(gè)s_object對(duì)象,,自然,,從Base類派生的整個(gè)繼承體系中的類的實(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)銷毀,不需要程序員操心,;而且棧對(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)用深度的增加,所需的??臻g也會(huì)線性增加,,當(dāng)所需棧空間不夠時(shí),,便會(huì)導(dǎo)致棧溢出,,這樣就會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。 堆對(duì)象,,其產(chǎn)生時(shí)刻和銷毀時(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ì)象的共享。另外,,相比于??臻g,堆的容量要大得多,。實(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ì)象為類間通信和函數(shù)間通信提供了一種最簡(jiǎn)單的方式,,雖然這種方式并不優(yōu)雅。一般而言,,在完全的面向?qū)ο笳Z(yǔ)言中,,是不存在全局對(duì)象的,比如C#,,因?yàn)槿謱?duì)象意味著不安全和高耦合,在程序中過(guò)多地使用全局對(duì)象將大大降低程序的健壯性,、穩(wěn)定性,、可維護(hù)性和可復(fù)用性。C++也完全可以剔除全局對(duì)象,,但是最終沒(méi)有,,我想原因之一是為了兼容C。 其次是類的靜態(tài)成員,,上面已經(jīng)提到,,基類及其派生類的所有對(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)銷也是巨大的。這是因?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)銷,而且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ì)象,,自然存在于棧中,,所以在堆棧回滾的過(guò)程中,,棧對(duì)象的析構(gòu)函數(shù)會(huì)被執(zhí)行,,從而釋放其所封裝的資源,。除非,除非在析構(gòu)函數(shù)執(zhí)行的過(guò)程中再次拋出異常――而這種可能性是很小的,,所以用棧對(duì)象封裝資源是比較安全的,。基于此認(rèn)識(shí),,我們就可以創(chuàng)建一個(gè)自己的句柄或代理來(lái)封裝資源了,。智能指針(auto_ptr)中就使用了這種技術(shù)。在有這種需要的時(shí)候,,我們就希望我們的資源封裝類只能在棧中創(chuàng)建,,也就是要限制在堆中創(chuàng)建該資源封裝類的實(shí)例。 四.禁止產(chǎn)生堆對(duì)象 上面已經(jīng)提到,,你決定禁止產(chǎn)生某種類型的堆對(duì)象,,這時(shí)你可以自己創(chuàng)建一個(gè)資源封裝類,該類對(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ì)稱,,最好將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ì)象的類了,,如果你寫下如下代碼: NoHashObject* fp = new NoHashObject() ; //編譯期錯(cuò)誤,! 上面代碼會(huì)產(chǎn)生編譯期錯(cuò)誤。好了,,現(xiàn)在你已經(jīng)知道了如何設(shè)計(jì)一個(gè)禁止堆對(duì)象的類了,,你也許和我一樣有這樣的疑問(wèn),難道在類NoHashObject的定義不能改變的情況下,,就一定不能產(chǎn)生該類型的堆對(duì)象了嗎,?不,還是有辦法的,,我稱之為“暴力破解法”,。C++是如此地強(qiáng)大,強(qiáng)大到你可以用它做你想做的任何事情,。這里主要用到的是技巧是指針類型的強(qiáng)制轉(zhuǎn)換,。 void main(void) //強(qiáng)制類型轉(zhuǎn)換,現(xiàn)在ptr是一個(gè)指向NoHashObject對(duì)象的指針 temp = NULL ; //防止通過(guò)temp指針修改NoHashObject對(duì)象 //再一次強(qiáng)制類型轉(zhuǎn)換,,讓rp指針指向堆中NoHashObject對(duì)象的ptr成員 //初始化obj_ptr指向的NoHashObject對(duì)象的ptr成員 delete rp ;//釋放資源 上面的實(shí)現(xiàn)是麻煩的,而且這種實(shí)現(xiàn)方式幾乎不會(huì)在實(shí)踐中使用,,但是我還是寫出來(lái)路,,因?yàn)槔斫馑瑢?duì)于我們理解C++內(nèi)存對(duì)象是有好處的,。對(duì)于上面的這么多強(qiáng)制類型轉(zhuǎn)換,,其最根本的是什么了?我們可以這樣理解: 某塊內(nèi)存中的數(shù)據(jù)是不變的,,而類型就是我們戴上的眼鏡,,當(dāng)我們戴上一種眼鏡后,我們就會(huì)用對(duì)應(yīng)的類型來(lái)解釋內(nèi)存中的數(shù)據(jù),,這樣不同的解釋就得到了不同的信息,。 所謂強(qiáng)制類型轉(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)生某種類型的堆對(duì)象,,那么可以設(shè)計(jì)一個(gè)類,,使之不能產(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è)類不打算作為基類,,通常采用的方案就是將其析構(gòu)函數(shù)聲明為private,。 為了限制棧對(duì)象,卻不限制繼承,,我們可以將析構(gòu)函數(shù)聲明為protected,,這樣就兩全其美了。如下代碼所示: class NoStackObject 接著,,可以像這樣使用NoStackObject類: NoStackObject* hash_ptr = new NoStackObject() ; 呵呵,,是不是覺(jué)得有點(diǎn)怪怪的,我們用new創(chuàng)建一個(gè)對(duì)象,,卻不是用delete去刪除它,,而是要用destroy方法。很顯然,,用戶是不習(xí)慣這種怪異的使用方式的,。所以,我決定將構(gòu)造函數(shù)也設(shè)為private或protected,。這又回到了上面曾試圖避免的問(wèn)題,,即不用new,那么該用什么方式來(lái)生成一個(gè)對(duì)象了,?我們可以用間接的辦法完成,,即讓這個(gè)類提供一個(gè)static成員函數(shù)專門用于產(chǎn)生該類型的堆對(duì)象。(設(shè)計(jì)模式中的singleton模式就可以用這種方式實(shí)現(xiàn),。)讓我們來(lái)看看: class NoStackObject 現(xiàn)在可以這樣使用NoStackObject類了: NoStackObject* hash_ptr = NoStackObject::creatInstance() ; 現(xiàn)在感覺(jué)是不是好多了,,生成對(duì)象和釋放對(duì)象的操作一致了。 ok,,講到這里,,已經(jīng)涉及了較多的東西,如果要把內(nèi)存對(duì)象講得更深入更全面,,那可能需要寫成一本書了,,而就我自己的功力而言,可能是很難完全把握的,。如果上面所寫的能使你有所收獲或啟發(fā),,我就滿足了,。如果你要更進(jìn)一步去了解內(nèi)存對(duì)象方面的知識(shí),,那么我可以推薦你看看《深入探索C++對(duì)象模型》這本書 |
|
來(lái)自: yiherainbow > 《我的圖書館》