




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