C++基礎(chǔ)淺談.doc_第1頁(yè)
C++基礎(chǔ)淺談.doc_第2頁(yè)
C++基礎(chǔ)淺談.doc_第3頁(yè)
C++基礎(chǔ)淺談.doc_第4頁(yè)
C++基礎(chǔ)淺談.doc_第5頁(yè)
已閱讀5頁(yè),還剩69頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

_C+基礎(chǔ)知識(shí)第一章C家族的故事以下描述摘自C+對(duì)話系列“最初,計(jì)算機(jī)語(yǔ)言非常混亂,高級(jí)語(yǔ)言根本不存在,連固定的語(yǔ)言形式也沒有。貝爾實(shí)驗(yàn)室的Richard Martin在使用了計(jì)算機(jī)語(yǔ)言的過程中意識(shí)到了高級(jí)語(yǔ)言的必要性。他深入地研究后,開發(fā)出了他認(rèn)為不錯(cuò)的BCPL語(yǔ)言。 “然后Ken Thompson使用了BCPL,雖然他覺得很不錯(cuò),但他認(rèn)為如果想在一臺(tái)PDP-7上使用BCPL,就必須精簡(jiǎn)BCPL。Ken Thompson深入地研究后,他開發(fā)出了一門新的語(yǔ)言,命名為B,它是BCPL的一個(gè)簡(jiǎn)化版本,他認(rèn)為這是一門很好的語(yǔ)言。 “然而B語(yǔ)言沒有類型的概念。Dennis Ritchie意識(shí)到了這一點(diǎn),他深入研究后,對(duì)B語(yǔ)言進(jìn)行了擴(kuò)展。Ritchie 添加了結(jié)構(gòu)和類型,他把這門語(yǔ)言叫作C語(yǔ)言,因?yàn)镃是B的下一個(gè)字母,無(wú)論是在字母表還是在BCPL中。Ritchie 認(rèn)為這門語(yǔ)言已經(jīng)相當(dāng)好了,但是他并不滿足,繼續(xù)投入大量的心血和汗水去完善這門語(yǔ)言。在1978年,Brian Kernighan 和Dennis Ritchie合作出版了The C Programming Language.3這為人們帶來了很多的喜悅,人們看到了C的美妙,耶,這門語(yǔ)言真的很棒!人們紛紛議論。 “C語(yǔ)言很快流傳開來。新的特征不斷的被添加,但并不是被所有的編譯器廠商支持。人們開始感到沮喪,開始呼吁“我們需要標(biāo)準(zhǔn)C!” ANSI響應(yīng)了這一要求,在1989年ANSI 宣布, 請(qǐng)注意,我將給所有的程序員帶來快樂。因?yàn)樵诮裉欤珻的標(biāo)準(zhǔn)X3.159-1989將誕生.接著ISO采納了這一標(biāo)準(zhǔn),發(fā)布了ISO/IEC 9899-1990。這又一次為人們帶來喜悅。 “事情在進(jìn)一步發(fā)展,早在C標(biāo)準(zhǔn)被發(fā)布之前,Bjarne Stroustrup就已經(jīng)致力于改善C語(yǔ)言。Stroustrup致力于在C語(yǔ)言里增加類、函數(shù)參數(shù)類型檢查和其他的一些優(yōu)秀的特征。他繼續(xù)深入,于1980年發(fā)布了C With Classes.這為人們帶來了更多的喜悅和興奮。 Stroustrup 并沒有止步不前。他在對(duì)C語(yǔ)言做了很大的改變后,產(chǎn)生了一門新的語(yǔ)言,他命名這門語(yǔ)言為C+,就是C的增強(qiáng)的意思。他繼續(xù)努力,在1986年出版了The C+ Programming Language,這再一次為人們帶來了喜悅。 象所有的事物一樣,C+語(yǔ)言也在不斷的進(jìn)化著。模板,異常處理(exception handling)以及其它的特征陸續(xù)被添加到C+中,人們?cè)俅螢樾率挛锒d奮。 “然而人們又開始抱怨了。那時(shí)候,不同的編譯器開發(fā)商使用不同的解決方案支持模板和異常以及其它的特征,甚至有些開發(fā)商拒絕支持這些新特性。因此ISO又行動(dòng)了, 在1998年-克林頓上臺(tái)后第六年,克雷蒂安成了除魁北克人之外所有加拿大人的總理。萊溫斯基成了媒體的大紅人,因?yàn)闆]有第二個(gè)辛普森誕生,那年沒有什么大的新聞-在九月的第一天,ISO 宣布“ 請(qǐng)注意,我將給所有的程序員帶來快樂的消息。因?yàn)樵诮裉欤珻+的標(biāo)準(zhǔn)ISO/IEC 14882:1998(E)將誕生。”接著ANSI接受了這一建議,在七月的二十七號(hào)發(fā)布了幾乎相同的標(biāo)準(zhǔn),甚至早于ISO標(biāo)準(zhǔn)的發(fā)布,有時(shí)候事情就是這樣。這又一次為人們帶來喜悅,“啊,太好了,我們可以踩在巨人的肩膀上前進(jìn)了”大家是這樣歡呼的。 故事并未結(jié)束,當(dāng)時(shí)Patrick Naughton為Sun Microsystems工作了一段時(shí)間后,深感厭煩,想離開Sun ,尋求新的發(fā)展。然而公司挽留了他,你可以擁有一支開發(fā)隊(duì)伍,只要你愿意,一切都可以由你指揮,但要給我們帶來點(diǎn)酷的東西。于是一個(gè)名叫Green的團(tuán)隊(duì)產(chǎn)生了。 Green小組孤獨(dú)地在荒野上不斷的探索。他們尋求一種可用于嵌入式設(shè)備的面向?qū)ο笳Z(yǔ)言,他們一開始在C+的基礎(chǔ)上修改,但是C+的龐大使之無(wú)法滿足他們的需要,于是他們?cè)贑+的基礎(chǔ)上創(chuàng)建了一門新的語(yǔ)言O(shè)ak-這個(gè)命名僅僅因?yàn)镴ames Gosling看到了相窗外的一顆橡樹(Oak)。開發(fā)隊(duì)伍仔細(xì)審視了這門語(yǔ)言,認(rèn)為它相當(dāng)?shù)暮谩?也是在那個(gè)時(shí)代,美國(guó)巨型計(jì)算機(jī)應(yīng)用中心開發(fā)出了Mosaic,這為我們帶來了令人興奮的WWW。隨后Bill Joy試圖公開Oak的源代碼,使Oak能使用于網(wǎng)頁(yè)瀏覽。Sun審視了這個(gè)想法,覺得不錯(cuò),但Oak這個(gè)商標(biāo)已經(jīng)被人占用,所以Sun把這門新語(yǔ)言稱為Java,并發(fā)布了the Java programming Language。這又一次使人們激動(dòng),耶!我們又有了一個(gè)真正與開發(fā)平臺(tái)無(wú)關(guān)的語(yǔ)言!我們認(rèn)為,這真是太酷了! 這就是C語(yǔ)言家族的早期故事,故事還在繼續(xù)。2000年左右,微軟宣布有史以來這個(gè)星球上最好的語(yǔ)言出現(xiàn)了-C#。Delphi的作者設(shè)計(jì)了C#。到2007年C#已經(jīng)是一個(gè)成熟的語(yǔ)言,并且廣為流行,C#宣傳它優(yōu)于C+的一個(gè)重要特點(diǎn)就是自動(dòng)的內(nèi)存管理,這將極大的解放程序員的負(fù)擔(dān),并使得程序更加的健壯。然后程序世界里很難有完美的解決方案,垃圾回收器一旦工作,將導(dǎo)致整個(gè)應(yīng)用程序暫停,同時(shí)在內(nèi)存受限系統(tǒng)中,仍然需要依靠程序員高超的技巧去控制內(nèi)存的分配和回收,并且既然可以用C+為Java和C#開發(fā)讓它們引以為傲的垃圾回收器,為什么就不能為C+自己實(shí)現(xiàn)垃圾回收管理呢?Herb Sutter已經(jīng)證實(shí),C+09標(biāo)準(zhǔn)一定會(huì)實(shí)現(xiàn)垃圾回收器。盡管Java、C#聲勢(shì)逼人,還有現(xiàn)在的新貴動(dòng)態(tài)語(yǔ)言,但是C+世界仍然沒有停止前進(jìn)的腳步,泛型是C+對(duì)比其他語(yǔ)言很重要的優(yōu)勢(shì),因?yàn)镃+對(duì)泛型的支持最好。根據(jù)stroustrup的計(jì)劃,2009年,C+09標(biāo)準(zhǔn)將出臺(tái),到時(shí)候?qū)Ψ盒偷闹С謱⑦M(jìn)一步的提升,使得更容易理解和使用,同時(shí)C+世界還在努力做到跨平臺(tái),10多年間推出了很多重量級(jí)的C+庫(kù),包括Boost、Loki、QT、ACE、GSoap等,了解并在合適的時(shí)候使用這些知名庫(kù),對(duì)于我們提高軟件開發(fā)效率、軟件質(zhì)量和個(gè)人的C+修養(yǎng)都極為重要。C+再也不是那個(gè)當(dāng)年因?yàn)椴荒苤С謴V泛的操作系統(tǒng)平臺(tái),而被Sun公司拋棄并創(chuàng)立Java的那個(gè)C+。故事還將繼續(xù)下去,期待C+09標(biāo)準(zhǔn)的到來。第二章C+代表什么?根據(jù)stroustrup(C+之父)的最新定義,C+是多范型語(yǔ)言,它包含了以下四個(gè)子集:1) 標(biāo)準(zhǔn)C語(yǔ)言體現(xiàn)了過程化設(shè)計(jì)思想,優(yōu)點(diǎn)是性能高,缺點(diǎn)是大規(guī)模應(yīng)用難以維護(hù),但是仍然是很重要并且有時(shí)候必須的技術(shù)2) 面向?qū)ο蟮腃+體現(xiàn)了面向?qū)ο蟮脑O(shè)計(jì)思想,符合人類思考問題的習(xí)慣,設(shè)計(jì)模式面向?qū)ο蠹夹g(shù)的一個(gè)濃縮3) 模板C+體現(xiàn)了泛型設(shè)計(jì)思想,是完全不同于面向?qū)ο蟮牧硪粋€(gè)種技術(shù),近年來大放光彩,比如知名的泛型庫(kù),Microsoft的ATL/ATL Server/WTL,徹底影響標(biāo)準(zhǔn)C+的STL等,boost和Loki4) STLSTL這里單獨(dú)提出來,是因?yàn)樗鼘?shí)在太重要了,提供了很多方便的容器、算法、迭代器,直接引起了整個(gè)軟件工業(yè)界的一場(chǎng)革命第三章 內(nèi)存分配(勿在浮沙筑高臺(tái))C+的內(nèi)存區(qū)域分為幾種:常量數(shù)據(jù)、棧、自由存儲(chǔ)、堆、全局或靜態(tài)。第一節(jié) 棧分配學(xué)過數(shù)據(jù)結(jié)構(gòu)的人都知道,棧是一種先進(jìn)后出的連續(xù)內(nèi)存塊。C+中每個(gè)函數(shù)執(zhí)行時(shí),都會(huì)創(chuàng)建一個(gè)私有棧,然后將參數(shù)依次放入棧中(通常由右向左),在這個(gè)函數(shù)中分配的自動(dòng)變量,都會(huì)存入棧中,當(dāng)函數(shù)返回時(shí),棧中的數(shù)據(jù)會(huì)以入棧的相反順序彈出。int main(int argc, _TCHAR* argv)int i=3;int j=9;return 0;在上面的main函數(shù)中,會(huì)存在一個(gè)屬于該函數(shù)的棧,除了參數(shù)外,變量i會(huì)先于j獲得棧中的內(nèi)存并初始化,當(dāng)main返回時(shí),main的私有棧會(huì)將棧里存放的東西逐個(gè)彈出,j會(huì)先于i被清除。由于棧是一塊大小固定的預(yù)先分配好的內(nèi)存,所以在棧中分配一塊內(nèi)存給變量i僅僅是移動(dòng)一個(gè)棧頂?shù)闹羔槪苑峙渌俣纫茸杂纱鎯?chǔ)和堆中快的多。在C+中,棧是無(wú)處不在的,每個(gè)函數(shù)有,每個(gè)對(duì)象也有,棧的基本原理構(gòu)成了智能指針的理論依據(jù)。第二節(jié) 在堆上分配內(nèi)存void *malloc(size_t size);標(biāo)準(zhǔn)C風(fēng)格的內(nèi)存分配方法,以字節(jié)為基本單位,返回分配的一塊內(nèi)存的起始地址(如果成功的話),無(wú)類型概念。如果沒有足夠的內(nèi)存,會(huì)返回NULL。凡是malloc分配的內(nèi)存,在不用的時(shí)候,需要使用free釋放。#include int main(int argc, _TCHAR* argv)size_t length=10;void* p=malloc(length);free(p);return 0;malloc分配內(nèi)存時(shí)遍歷一個(gè)鏈表,鏈表中各個(gè)元素均指向某塊內(nèi)存,通常從小到大排列,找到足夠大的內(nèi)存塊后,該內(nèi)存快將被拆分開來,同時(shí)鏈表上相應(yīng)節(jié)點(diǎn)上的指針進(jìn)行調(diào)整。malloc/freee比較適合用于分配大中型對(duì)象(數(shù)百個(gè)或者數(shù)千個(gè)bytes),但是并沒有對(duì)分配小內(nèi)存做優(yōu)化。所以.Net的內(nèi)存分配機(jī)制通常要快于C的malloc,因?yàn)樗ㄟ^各種手段把內(nèi)存造成一個(gè)連綿不絕沒有盡頭的連續(xù)內(nèi)存塊,分配內(nèi)存只是移動(dòng)一下指針。但是.Net也有自己的問題,必須依靠垃圾回收器回收不用的內(nèi)存,然后重新整理內(nèi)存,保證內(nèi)存塊總是夠用,而垃圾回收器一旦工作,整個(gè)應(yīng)用程序處于停止?fàn)顟B(tài)。第三節(jié) 在自由存儲(chǔ)區(qū)域分配內(nèi)存1. new/delete的幕后通常我們這樣寫代碼:int* p=new int;.delete p;new int語(yǔ)句幕后發(fā)生了什么。首先調(diào)用標(biāo)準(zhǔn)c+提供的void* operator new(std:size_t _Count) throw(bad_alloc)函數(shù)分配sizeof(int)大小的內(nèi)存,int類型的大小通常和機(jī)器內(nèi)存總線大小相同,所以32位機(jī)器上,sizeof(int)=4字節(jié)。然后要看我們分配的是什么類型,如果是預(yù)定義類型,則什么都不做,直接返回一分配的內(nèi)存塊的指針,如果是一個(gè)c+的class,則會(huì)調(diào)用默認(rèn)構(gòu)造函數(shù)在已分配的內(nèi)存上對(duì)對(duì)象初始化,然后返回指針。那么,int*p =new int()又代表什么呢?因?yàn)閕nt是預(yù)定義類型,所以分配完內(nèi)存后,初始化內(nèi)存值為0。所以不用再寫下面的代碼了:int*p =new int*p=0;如果類型是一個(gè)class,則int* p=new string和int* p=new string()是等價(jià)的?,F(xiàn)在我們明白了,平時(shí)我們用new int的時(shí)候,這個(gè)new是c+提供的一個(gè)基本操作符,和operator new函數(shù)不是一回事。當(dāng)我們調(diào)用delete p的時(shí)候,如果p指向string,則先調(diào)用string的析構(gòu)函數(shù),然后調(diào)用operator delete回收內(nèi)存。如果p指向的是預(yù)定義類型如int,則直接調(diào)用operator delete回收內(nèi)存。幕后就這么多么?不是。實(shí)際上,每次使用new分配一塊內(nèi)存的時(shí)候,c+編譯器會(huì)分配一塊內(nèi)存(4-32字節(jié))用于管理,可能保存了要分配內(nèi)存的大小,這樣delete才能保證正確釋放掉合適大小的內(nèi)存。這是一個(gè)很麻煩的問題,如果你只分配了4字節(jié),結(jié)果管理這4字節(jié)的內(nèi)存耗用了16字節(jié),天哪!Andrei說,老練的c+程序員都不會(huì)在這種情況下使用標(biāo)準(zhǔn)new。那么用什么呢?你可以使用Loki:SmallObject、Loki:SmallValueObject或者boost的pool庫(kù),他們使用了各自的策略,避免了這些額外的管理開銷,以及頻繁的new和delete。通常都是先分配一大塊內(nèi)存,然后慢慢用。2. operator new/delete 函數(shù)void* operator new(std:size_t _Count)throw(bad_alloc);void* operator new(std:size_t _Count,const std:nothrow_t&) throw( );void* operator new(std:size_t _Count, void* _Ptr) throw( );很多編譯器在operator new的實(shí)現(xiàn)代碼中使用了malloc,但是c+標(biāo)準(zhǔn)并沒有規(guī)定事情一定會(huì)是這樣,c+標(biāo)準(zhǔn)只規(guī)定了malloc實(shí)現(xiàn)代碼一定不會(huì)調(diào)用operator new。為了不把我們的代碼基于一個(gè)今后可能會(huì)變化的假設(shè)上,邏輯上我們應(yīng)該認(rèn)為operator new/delete和malloc/free并沒有必然的聯(lián)系,所以我們不能使用free來釋放operator new分配的內(nèi)存,或者調(diào)用delete來釋放malloc分配的內(nèi)存。 由于malloc天生的缺陷,我希望看到將來的operator new不用malloc實(shí)現(xiàn)。第一種形式的operator new如果分配內(nèi)存失敗會(huì)拋出bad_alloc異常對(duì)象,請(qǐng)不要檢查返回的指針是否為NULL,那是過時(shí)的做法,不是標(biāo)準(zhǔn)做法。void* operator new(std:size_t _Count,const std:nothrow_t&) throw( );這種形式保證分配內(nèi)存即使失敗也不會(huì)拋出異常,只會(huì)返回NULL。void* p=:operator new(sizeofe(string),std:nothrow);if(p!=NULL).為什么要提供這種重載形式,內(nèi)存分配錯(cuò)誤是個(gè)十分嚴(yán)重的錯(cuò)誤,因此用異常報(bào)告錯(cuò)誤是十分合適的。但是如果你打算在一個(gè)循環(huán)里面分配內(nèi)存,或者在一個(gè)時(shí)間要求極高的場(chǎng)合下分配內(nèi)存,異常對(duì)象的傳遞還是慢了點(diǎn)。除此之外,還是盡量使用第一種重載形式比較好。第三種形式其實(shí)不分配內(nèi)存,而是你提供一塊內(nèi)存,然后它幫助你初始化。void* p=:operator new(sizeof(string);/使用operator new的第一種形式分配內(nèi)存new (p) string; /在已分配的內(nèi)存上初始化一個(gè)string對(duì)象與operator new相對(duì)應(yīng)的,也有三種重載的operator delete。void operator delete(void* _Ptr) throw( );void operator delete(void *, void *) throw( );void operator delete(void* _Ptr,const std:nothrow_t&) throw( );3. operator new/delete 函數(shù)void *operator new(std:size_t _Count)throw(std:bad_alloc);void *operator new(std:size_t _Count,const std:nothrow_t&) throw( );void *operator new(std:size_t _Count, void* _Ptr) throw( );上面第一個(gè)new函數(shù)負(fù)責(zé)分配數(shù)組空間,char* p=new char10; /如果是基本預(yù)定義類型,只分配數(shù)組空間不初始化char* p=new char10(); /如果是基本預(yù)定義類型,先分配數(shù)組空間然后每個(gè)元素初始化為0string* p=new string10;/ 如果是c+類,則不僅分配數(shù)組空間而且調(diào)用默認(rèn)構(gòu)造函數(shù)初始化每個(gè)元素。注意,請(qǐng)使用delete 銷毀用new 創(chuàng)建的內(nèi)存,如果是預(yù)定義類型,則也可以使用delete。這三個(gè)new和上面三個(gè)的區(qū)別就是分配數(shù)組和分配單個(gè)對(duì)象,其余幾乎相同。void operator delete(void* _Ptr) throw( );void operator delete(void *, void *) throw( );void operator delete(void* _Ptr, const std:nothrow_t&) throw( );4. 出錯(cuò)處理void* operator new(std:size_t _Count)throw(bad_alloc);當(dāng)我們調(diào)用上面形式的operator new分配內(nèi)存時(shí),函數(shù)最后面throw(bad_alloc)異常規(guī)格說明代表一旦分配內(nèi)存出錯(cuò),將拋出一個(gè)bad_alloc異常對(duì)象。我們?cè)撛趺刺幚恚靠纯碈+標(biāo)準(zhǔn)委員會(huì)主席Herb Sutter怎么闡述這個(gè)問題?他的觀點(diǎn)概括為: (1) 在絕大多情況下,你都不需要關(guān)心,因?yàn)閚ew通常都會(huì)成功好極了,我們的生活壓力將因?yàn)檫@句話大大減輕。這個(gè)觀點(diǎn)有幾個(gè)理由:有些操作系統(tǒng)只有當(dāng)內(nèi)存真正使用的時(shí)候,才會(huì)真正的分配內(nèi)存,也就是說,new總是工作得很好,等你用那塊內(nèi)存的時(shí)候,才有可能因?yàn)榉峙洳坏絻?nèi)存報(bào)錯(cuò),所以檢查new是否成功無(wú)意義;在一個(gè)虛擬內(nèi)存系統(tǒng)上,new失敗的可能性很?。患幢鉵ew真的失敗了,你又能怎么樣呢?讓程序崩潰實(shí)際上是一個(gè)很好的辦法。 (2 )在某些特定情況下,檢查new是否成功有意義:如果能夠預(yù)見到應(yīng)用程序?qū)⑹褂枚嗌賰?nèi)存,一開始就分配好所有需要的內(nèi)存,如果要失敗,就在一開始失敗巴;如果試圖分配一個(gè)巨大的內(nèi)存塊,new失敗后,也許你要做的事情就是再次調(diào)用new,只不過不要那么貪婪,少要點(diǎn)內(nèi)存。如果你真的有足夠的理由要檢查并處理new的錯(cuò)誤,那就需要了解operator new內(nèi)部錯(cuò)誤處理流程。當(dāng)operator new的第一種重載形式在內(nèi)部分配內(nèi)存失敗,他會(huì)通過指向new-handler函數(shù)的指針調(diào)用new-handler函數(shù),如果new-handler函數(shù)指針為NULL,則拋出異常,如果指針有效,就在一個(gè)循環(huán)里面調(diào)用函數(shù)new-handler函數(shù),直到new_handler函數(shù)1)釋放了一定的內(nèi)存,使得循環(huán)內(nèi)部下一次分配內(nèi)存成功,則滿意退出2)當(dāng)前這個(gè)new-handler函數(shù)認(rèn)為自己沒有辦法,只好設(shè)置另一個(gè)函數(shù)為new-handler函數(shù),循環(huán)會(huì)在下一次分配內(nèi)存失敗使調(diào)用新的new-handler函數(shù)3)如果認(rèn)為沒有辦法繼續(xù)下去,new-handler內(nèi)部可以拋出bad_alloc異?;蛘邔?dāng)前的new-handler設(shè)置為NULL,最終導(dǎo)致退出循環(huán),客戶代碼收到異常對(duì)象。如何編碼的細(xì)節(jié),我們將在講述class專有operator new的時(shí)候描述。這里你們需要知道,因?yàn)槟J(rèn)情況下new-handler函數(shù)的指針為NULL,所以總是在錯(cuò)誤的時(shí)候拋出異常。第四節(jié) 常量區(qū)域顧名思義,只有常量才能存在的區(qū)域,該區(qū)域內(nèi)的變量將存在直到整個(gè)程序結(jié)束,并且不能被修改。第五節(jié) 全局或靜態(tài)區(qū)域程序啟動(dòng)時(shí),全局變量或者靜態(tài)變量將被分配內(nèi)存并初始化,直到整個(gè)程序結(jié)束才銷毀。如果幾個(gè)全局或者靜態(tài)變量在多個(gè)C+文件中使用,他們的初始化順序是不確定的,所以他們之間最好不要有依賴關(guān)系。注意,在常量、全局或者靜態(tài)區(qū)域定義的預(yù)定義變量通常不僅分配了內(nèi)存,而且會(huì)自動(dòng)初始化,比如int變量會(huì)初始化為0,而棧中創(chuàng)建的預(yù)定義變量不會(huì)自動(dòng)初始化。第四章 指針第一節(jié) 指針代表什么?我接觸過的很多對(duì)C+心存敬畏之心的人(他們大多不懂C+)都跟我說“指針是非常容易出錯(cuò)的,但是一旦學(xué)會(huì)了指針也就學(xué)會(huì)了C+”。雖然我不同意這個(gè)觀點(diǎn),但或許這是從某個(gè)角度提出了指針在C+語(yǔ)言中的重要地位。指針是什么?指針是一個(gè)占用四字節(jié)內(nèi)存的特殊整型變量,它里面保存的是另一塊內(nèi)存的起始地址,并通過指針的靜態(tài)類型標(biāo)志這塊內(nèi)存是什么類型??纯聪旅娴拇a:int main(void)int * p=new int(5);短短幾句話,發(fā)生了多少動(dòng)作呢?簡(jiǎn)單分析一下:首先進(jìn)入main函數(shù)前,為該函數(shù)分配了私有棧,棧的大小通常是固定的并且可以配置。進(jìn)入函數(shù)后執(zhí)行new int(5)使得在堆上分配4個(gè)字節(jié),并且將這四個(gè)字節(jié)初始化為整形值5,然后棧頂指針向上移動(dòng)四個(gè)字節(jié),將這四個(gè)字節(jié)作為p變量的空間,這四個(gè)字節(jié)將保存剛才存放5的堆地址。當(dāng)函數(shù)返回時(shí),棧將被銷毀,因此p將不再存在,但是new int(5)獲得的堆上的4字節(jié)空間將依然存在,但是卻沒有辦法再使用或者回收它,因此發(fā)生了最可怕的事情內(nèi)存泄漏。第二節(jié) 野指針好,剛才至少我們明白了指針也就是個(gè)4字節(jié)的變量。現(xiàn)在看下面的代碼:int * p1=NULL;int * p2;p1變量?jī)?nèi)部存放的值為0,這使得它不能指向任何有效地址。p2 由于之分配了4字節(jié)空間,并沒有初始化,因此它里面的值應(yīng)該是上次對(duì)該塊內(nèi)存使用后遺留下來的,是多少誰(shuí)都不知道,或許是0,或許指向某處你絕對(duì)不想讓它指向的地方,這稱為野指針。所以下面的代碼就很危險(xiǎn),*p2=0 ;你都不知道你把什么內(nèi)存給改寫了,結(jié)果是無(wú)法預(yù)料的。野指針相當(dāng)危險(xiǎn),所以比較好的做法是初始化為NULL。但是C+中很難有什么絕對(duì)遵守的準(zhǔn)則,C+給了你很大的權(quán)限去選擇不同的方案。如果在一個(gè)你確信不會(huì)出現(xiàn)問題的地方,并且性能是很關(guān)鍵的地方,我為什么多此一舉要賦初值呢?我本人就有時(shí)候故意不這樣做。我對(duì)大家的建議是了解原理,然后自己控制,在對(duì)自己的控制能力沒有信心的時(shí)候,遵守較安全的做法是明智的。還有一種產(chǎn)生野指針的常見情況:char* p=new char(b);.delete p;.coutpendl;delete語(yǔ)句已經(jīng)把p指向的堆上的一字節(jié)內(nèi)存銷毀了,但是并不會(huì)清空p的值,也就是說p仍然指向堆上的那個(gè)字節(jié),然后coutpendl;會(huì)出現(xiàn)什么情況,無(wú)法預(yù)料。也許堆上的那個(gè)字節(jié)已經(jīng)被改寫,或者沒有。下面的代碼會(huì)對(duì)這種情況有所幫助:char* p=new char(b);.delete p;p=NULL;.if(p!=NULL)/p仍然有效coutpendl;但其實(shí)這是一個(gè)邏輯錯(cuò)誤,既然delete p都指向了,無(wú)論如何,都不應(yīng)該再使用p。修正邏輯才是治本,if(p!=NULL)只是打補(bǔ)丁的做法。第三節(jié) 指針的類型(1)、指針的靜態(tài)類型:int* p=new int(5);這句話里我們的p變量的靜態(tài)類型是int*,這就是告訴編譯器p所指向的內(nèi)存應(yīng)該看作int變量,起始地址是p里面的值,大小是sizeof(int)。char* p=new char100;char* pChar=p;int* pInt=p;+pChar;+pInt;由于pChar的靜態(tài)類型為char*,所以每次執(zhí)行+,都會(huì)向后移動(dòng)一個(gè)字節(jié),由于pInt靜態(tài)類型是int*,所以每次執(zhí)行+,都會(huì)向后移動(dòng)sizeof(int)個(gè)字節(jié)(通常為4字節(jié))。(2)、指針的動(dòng)態(tài)類型:指針的動(dòng)態(tài)類型是指在多態(tài)的情況下,靜態(tài)類型為指向基類的指針,實(shí)際指向的子對(duì)象的類型就是該指針的動(dòng)態(tài)類型。比如class B派生自class A,我們寫了下面的代碼:A* p=new B();這句話說明p的靜態(tài)類型是A*,但是實(shí)際指向的對(duì)象類型是B,該指針得動(dòng)態(tài)類型是B*。動(dòng)態(tài)類型在多態(tài)運(yùn)用中起到十分重要的作用,絕大多數(shù)設(shè)計(jì)模式都以此為基礎(chǔ),微軟的著名技術(shù)COM也是基于此。后面在虛函數(shù)部分我們會(huì)詳細(xì)討論。第四節(jié) 智能指針通常如果我們通過new操作獲得了一個(gè)指針,我們需要記住在不需要使用的時(shí)候使用delete操作。如:void f()string* p=new string(“hello,world”);.delete p;但是,可能會(huì)遇到這種情況,在delete p被執(zhí)行之前的語(yǔ)句里面出錯(cuò)而拋出了一個(gè)異常對(duì)象,f函數(shù)將立刻返回,delete p將不會(huì)被執(zhí)行,這樣內(nèi)存就泄露了。遇到這種情況,我們有幾個(gè)辦法:1)不要使用new/delete,改在棧內(nèi)創(chuàng)建對(duì)象void f()string str(“hello,world”);.這是個(gè)好辦法,而且速度很快,如果能用,盡量用這種.2)寫一個(gè)class,利用棧的機(jī)制來管理class StringManagerpublic:StringManger(string* pStr):_pStr(pStr)StringManager()delete _pStr;private:string* _pStr;void f()StringManager manager(new string(“hello,world”);.無(wú)論函數(shù)f內(nèi)部是否拋出異常,只要f函數(shù)返回,私有棧必然要銷毀,那么棧上分配的StringManager對(duì)象的析構(gòu)函數(shù)一定會(huì)被調(diào)用,所以delete _pStr語(yǔ)句一定會(huì)被執(zhí)行。這就是目前廣為使用的智能指針的基本原理。目前標(biāo)準(zhǔn)C+2003修正版中常用的智能指針有auto_ptr和shared_ptr,我們公司的BFL類庫(kù)中提供了其他的一些智能指針類。在后面我會(huì)逐步介紹,并分析優(yōu)缺點(diǎn),智能指針有其優(yōu)點(diǎn),但是并不是萬(wàn)能的藥方,只有當(dāng)你充分明白了它們的優(yōu)缺點(diǎn),才可以安全的用好它們。第五節(jié) 指針用作參數(shù)在前面我們介紹棧的時(shí)候,說過一個(gè)函數(shù)擁有一個(gè)私有棧,當(dāng)函數(shù)執(zhí)行時(shí),會(huì)先將參數(shù)值拷貝到棧中,比如:void f(int i,int* p)*p=i;int main(void)int a(5);int b;f(a,&b);return 0;f函數(shù)執(zhí)行時(shí),通常從右到左的順序拷貝p和i到棧中,這樣棧中有一個(gè)p的副本變量p和i的副本i,然后通過*p=i 將i的值賦給了p指向的變量b。這就是常說的傳址和傳值,對(duì)于b變量,是傳址,對(duì)于a變量是傳值。這樣使用指針會(huì)帶來什么好處呢?首先可以在函數(shù)f內(nèi)部修改外面b變量的值,其次如果b變量不是簡(jiǎn)單類型,而是復(fù)雜如string的對(duì)象,只傳遞4字節(jié)的指針性能是非??斓?。我們經(jīng)常見到類似這樣的指針參數(shù)void f(int* p),指針的指針,為什么要這么用呢?看下面的示例代碼:void f(int* p)*p=new int(5);int main(void)int* pValue=NULL;f(&pValue);.delete pValue;f函數(shù)在堆上分配了一塊4字節(jié)整數(shù)區(qū)域,初始化為5,然后讓外部的指針變量pValue指向這塊堆上的內(nèi)存。我們來分析一下:一開始,pValue指針變量被創(chuàng)建,但是內(nèi)容為0,即什么都不指向,然后將pValue指針變量所在的內(nèi)存地址作為int* p傳遞給f函數(shù),f函數(shù)將在自己的棧中保存p指針變量的地址副本,寫成偽代碼應(yīng)該是:void f(int* p)int* p=p;*p=new int(5);*p其實(shí)就是pValue,所以等價(jià)于外部pValue=new int(5);然后函數(shù)f返回p被銷毀,但是pValue已經(jīng)指向堆上的有效內(nèi)存了。請(qǐng)注意,涉及這樣的函數(shù)應(yīng)該寫上注釋,告訴用戶是使用什么函數(shù)釋放內(nèi)存delete還是free或者其他,因?yàn)橛锌赡苡脩艨床坏絝內(nèi)部實(shí)現(xiàn)的代碼。微軟的COM總是使用這種策略。第五章 指針和引用引用很有可能就是常指針實(shí)現(xiàn)的,但是引用有特殊的約束。引用不會(huì)為空,所以當(dāng)函數(shù)接收一個(gè)引用參數(shù)時(shí),不需要檢測(cè)該引用所指定的對(duì)象是否為空,指針可以為空,所以當(dāng)某函數(shù)接收指針作為參數(shù)時(shí),你經(jīng)常會(huì)看到這樣的代碼:assert(p!=NULL)引用必須被初始化,而指針變量沒有這個(gè)限制。引用一旦被初始化后,只能代表初始化設(shè)定的對(duì)象,而指針是可以改變指向的對(duì)象。第六章 課后練習(xí)11)設(shè)計(jì)一個(gè)DynamicCharArray類,要求使用char* _pData作為私有成員,實(shí)現(xiàn)下列成員函數(shù):class DynamicCharArraypublic:DynamicCharArray();DynamicCharArray(size_t size); DynamicCharArray();char getAt(size_t index);void setAt(size_t index,char value);private:char* _pData;2)告訴我,下面的代碼執(zhí)行后發(fā)生了哪些事情?void Func(int x,string & str)string strTmp=str;string* pStr=new string(“hello,world”);int i=6;str=”ok”;return;第七章 Const作為一個(gè)基本知識(shí)點(diǎn),要理解const char * const p的含義。第一個(gè)const代表p所指向的存儲(chǔ)區(qū)域是常量,當(dāng)初始化后不可以被修改;第二個(gè)const代表的是變量p一旦指向了某個(gè)存儲(chǔ)區(qū)域后,它就不可以指向別的區(qū)域。在我的文章里面,經(jīng)常會(huì)看到char const* p或者int const a這種用法。他們等價(jià)于const char* p和const int a。const char* p和 char const* p都代表p指向的內(nèi)存區(qū)域是常量,const都是修飾char類型的,但是顯然后者更明顯。我們應(yīng)該把const看成是對(duì)前面類型的修飾,這樣const將char和*p很自然的分隔開來。char * const p中,const修飾char*類型,代表該指針是不可變的常量指針。你不覺得這種做法和修飾函數(shù)的做法一樣么?你肯定見過void A:f() const的用法。在typedef的使用中,后置const的用法不會(huì)產(chǎn)生感覺上的混亂,比如:typedef char* CHARS;typedef const CHARS CPTR;/指向char類型的常量指針,不要覺得奇怪,事實(shí)就是這樣typedef char* CHARS;typedef const CHARS CPTR;int _tmain(int argc, _TCHAR* argv)CPTR p=a;p=b;return 0;error C3892: p : you cannot assign to a variable that is const有一天,我們忽然用CHARS的等價(jià)形式修改了第二句話typedef const char* CPTR;typedef char* CHARS;typedef const char* CPTR;int _tmain(int argc, _TCHAR* argv)CPTR p=a;p=b;return 0;正確。問題出在typedef const char* CPTR;和typedef const CHARS CPTR;居然不等價(jià)。當(dāng)你用后置const來表示,混亂就消失了。typedef char* CHARS;typedef CHARS const CPTR;int _tmain(int argc, _TCHAR* argv)CPTR p=a;p=b;return 0;和下面的typedef char* CHARS;typedef char* const CPTR;int _tmain(int argc, _TCHAR* argv)CPTR p=a;p=b;return 0;都會(huì)報(bào)同樣的錯(cuò)誤,error C3892: p : you cannot assign to a variable that is const。我們應(yīng)該習(xí)慣多用const,并且知道何時(shí)不該濫用。1)如果一個(gè)類的成員函數(shù)不會(huì)修改內(nèi)部數(shù)據(jù),用const修飾這是一種提醒,并且也會(huì)帶來方便。比如下面的代碼中,如果你不加const修飾函數(shù),就會(huì)遇到麻煩:class Apublic:void f();void Sample(A const& a)a.f();int _tmain(int argc, _TCHAR* argv)A a;Sample(a);return 0;error C2662: A:f : cannot convert this pointer from const A to A &而像Sample這樣的函數(shù)你有很大的幾率遇到。2)我們也有可能寫Sample這樣的函數(shù),當(dāng)我們的參數(shù)是值傳遞時(shí),不要用const,因?yàn)檫@不會(huì)帶來好處,還會(huì)帶來誤導(dǎo),當(dāng)我們的參數(shù)是引用或者指針時(shí),使用const修飾,明確告訴用戶傳址以提高效率,但是決不會(huì)在函數(shù)內(nèi)部修改。3)如果你不想讓別人對(duì)你的函數(shù)返回值作修改,給它一個(gè)const修飾符也不錯(cuò)4)哲學(xué)上,const可以被理解為物理常量和邏輯常量,當(dāng)一個(gè)成員函數(shù)被修飾為const,物理常量的理解認(rèn)為該函數(shù)內(nèi)部絕不可以修改類的成員,邏輯常量理論認(rèn)為,出于優(yōu)化的目的,你可以悄悄地修改某個(gè)變量,只要不讓客戶感覺到就可以。我站在邏輯常量這一邊。如果你想要在一個(gè)const成員函數(shù)內(nèi)修改某個(gè)變量,你需要為這個(gè)變量加上一個(gè)修飾符mutable。比如:class Apublic:void f() const_f=2;private:mutable int _f;int _tmain(int argc, _TCHAR* argv)A a;a.f();return 0;5) 我們有時(shí)候需要進(jìn)行const相關(guān)的類型轉(zhuǎn)換從非const引用(或者指針)到const引用是自動(dòng)完成的。例如: char* p=f;char const* p2=p;反過來去掉const約束的時(shí)候,你需要const_cast轉(zhuǎn)換符 char const* p=f;char* p2=const_cast(p);第八章 類的技術(shù)細(xì)節(jié)第一節(jié)struct和classstruct和class最重要的區(qū)別是哲學(xué)上的,struct代表著C風(fēng)格設(shè)計(jì)思想,class代表著面向?qū)ο骳+的設(shè)計(jì)思想,延伸一下,typename代表著c+泛型的思想。class和struct在大多數(shù)情況下可以互換,是因?yàn)閏+要兼容c的代碼所致,就像typename和class可以互換一樣。這里我們有足夠的自由去選擇用struct和class,看你打算在自己的設(shè)計(jì)中使用哪一種編程哲學(xué)。除此之外,struct默認(rèn)訪問權(quán)限是public,而class默認(rèn)訪問權(quán)限是private。還有一點(diǎn),struct里面的成員變量的內(nèi)存布局總是按照聲明的次序排列的,但是class不一樣。在同一個(gè)訪問權(quán)限塊內(nèi)部的成員變量?jī)?nèi)存布局總是按照聲明的次序排列,但是如果多個(gè)訪問權(quán)限塊內(nèi)部的成員變量,順序不能假定一定是連續(xù)的。同樣的情況,如果一個(gè)類派生自另一個(gè)類,我們不能假定這個(gè)子類的成員變量總是排在父類的成員變量之后。第二節(jié) 類的基本元素成員變量分為靜態(tài)和非靜態(tài)成員函數(shù)分為靜態(tài)函數(shù)和非靜態(tài)非虛函數(shù)以及虛函數(shù)同時(shí)還有從基類繼承下來的成員變量和成員函數(shù)采用虛繼承導(dǎo)致的額外的成員變量和數(shù)據(jù)結(jié)構(gòu)(由各編譯器自己實(shí)現(xiàn),標(biāo)準(zhǔn)沒有規(guī)定)例如:類ostream,istream分別代表輸出和輸入流,它們均派生自ios類,由于考慮到iostream從它們兩個(gè)派生以同時(shí)具備輸入和輸出流的能力,因此ostream和istream均虛繼承自ios類,使得iostream對(duì)象中不會(huì)擁有兩份ios對(duì)象的數(shù)據(jù)成員拷貝。class ostream:virtual public iosclass istream:virtual public iosclass iostream:public ostream,public istream虛繼承的語(yǔ)法就是提醒編譯器,不要將父類的數(shù)據(jù)成員放到子類的內(nèi)存空間中。C+標(biāo)準(zhǔn)并沒有規(guī)定具體要怎么做,編譯器可以實(shí)現(xiàn)自己的策略。一種可能的布局是:ostream和istream以及iostream中都有一個(gè)指針,指針指向一個(gè)表格,表格中存放的是虛基類的起始地址。第三節(jié) 虛函數(shù)1. 虛函數(shù)的內(nèi)存布局一個(gè)擁有虛函數(shù)的類內(nèi)部通常會(huì)有一個(gè)成員變量vptr,一個(gè)四字節(jié)大小的指針,指向虛函數(shù)表,虛函數(shù)表中記錄了該類的各個(gè)虛函數(shù)的入口地址,如果該類重寫了繼承的虛函數(shù),那么就存放自己的虛函數(shù)地址,否則就是父類的虛函數(shù)地址。以下是一個(gè)Point類的聲明:class Pointpublic:Point(float);virtual Print(ostream& stream);virtual Point();static int PointCount();static int _point_count;float x();private:float _x;他的可能的內(nèi)存布局如下:在這里我們可以看到,靜態(tài)成員函數(shù)、靜態(tài)成員變量、非靜態(tài)非虛成員函數(shù)都不會(huì)占用對(duì)象的內(nèi)存空間。占用空間的通常是非靜態(tài)成員變量和從父類繼承下來的非靜態(tài)成員變量以及虛函數(shù)指針,當(dāng)然虛繼承導(dǎo)致的額外的成員指針也要占用空間。class Apublic:virtual void f();virtual A();class B:public Avoid f()int i=0;A* pA=new B();pA-f();對(duì)于f的調(diào)用操作編譯器有如下動(dòng)作:void B:f()函數(shù)解釋為void f(B* this);pA-f()解釋為 (*pA-vptr1)(this);/1是f函數(shù)在虛擬函數(shù)表格中的索引所以我們可以看出,雖然指針pA靜態(tài)類型為A類的指針,但是對(duì)于f的調(diào)用是依賴于B對(duì)象內(nèi)部的vptr指向的虛擬函數(shù)表,而此時(shí)函數(shù)表內(nèi)的f函數(shù)已經(jīng)是B類的重載版本,因此這就構(gòu)成了運(yùn)行時(shí)多態(tài),這個(gè)c+的基本特征。虛繼承情況下的一種可能的內(nèi)存布局,摘自c+對(duì)話系列。class parent /* whatever */ ;class child1 : public virtual parent /* whatever */ ;class child2 : public virtual parent /* whatever */ ;class multi : public child1, public child2 /* whatever */ ;parent:vptrparent datachild1:vptrchild1 datachild2:vptrchild2 datamulti:vptrmulti data在這種復(fù)雜的情況下,最底層派生對(duì)象內(nèi)部擁有三個(gè)vptr,指向三個(gè)虛函數(shù)表。注意,經(jīng)過我的實(shí)驗(yàn),這種內(nèi)存布局各種編譯器表現(xiàn)不一樣,比如vc就是先把child1放在最前面,然后是child2,最后是parent,并且至少vc中,我們可以通過調(diào)用static_cast獲得各個(gè)類型的vptr值,這種典型的應(yīng)用是在com的QueryInterface函數(shù)的實(shí)現(xiàn)里面。經(jīng)過上面的分析,我知道上面的虛繼承會(huì)帶來多個(gè)虛函數(shù)表以及多個(gè)vptr,這是內(nèi)存上的額外的開銷,當(dāng)然避免了多個(gè)頂級(jí)父類的內(nèi)存副本和模棱兩可的繼承,也有它的好處。我們可以看看com里面常常出現(xiàn)的多繼承帶來的對(duì)象內(nèi)存布局class CPenguin : public IBird, public ISnappyDresser .;IBird和ISnappyDresser接口都繼承自IUnknown接口,內(nèi)存布局如下圖:2. 純虛函數(shù)和虛函數(shù)的區(qū)別參考 3th Edition,這里作個(gè)簡(jiǎn)單的概括:純虛函數(shù)分為函數(shù)定義和沒有函數(shù)定義-沒有函數(shù)定義的純虛函數(shù)目的是為了讓子類繼承接口,強(qiáng)制子類實(shí)現(xiàn)該函數(shù);有函數(shù)定義的純虛函數(shù)目的是為了讓子類繼承接口,而父類的實(shí)現(xiàn)函數(shù)必須在子類的該函數(shù)中手動(dòng)調(diào)用非純虛函數(shù)目的是讓子類自動(dòng)的繼承接口和函數(shù)實(shí)現(xiàn) 3. 虛函數(shù)與訪問權(quán)限虛函數(shù)的重寫機(jī)制和訪問權(quán)限是相互獨(dú)立的兩套機(jī)制,互相沒有干擾,但是可以結(jié)合使用。一個(gè)private權(quán)限的虛函數(shù)可以被子類重新實(shí)現(xiàn),但是子類不能訪問該虛函數(shù),而父類卻可以通過運(yùn)行時(shí)多態(tài)的

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論