改善程序設(shè)計(jì)技術(shù)的課件_第1頁(yè)
改善程序設(shè)計(jì)技術(shù)的課件_第2頁(yè)
改善程序設(shè)計(jì)技術(shù)的課件_第3頁(yè)
改善程序設(shè)計(jì)技術(shù)的課件_第4頁(yè)
改善程序設(shè)計(jì)技術(shù)的課件_第5頁(yè)
已閱讀5頁(yè),還剩168頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

1、改善程序設(shè)計(jì)技術(shù)的50個(gè)有效做法 第二版2002.3 Scott Meyers 侯 捷 譯 如何完成較好的設(shè)計(jì)如何避免常見(jiàn)的問(wèn)題如何提高效率的一些準(zhǔn)則不是放之四海而皆準(zhǔn)的唯一真理C+新標(biāo)準(zhǔn)新的類型bool 有兩個(gè)值true, false.typedef int bool;const bool false=0;const bool true=1;新的轉(zhuǎn)型動(dòng)作static_cast(expression) /將表達(dá)式expression轉(zhuǎn)為type類型const_cast(expression) /將常數(shù)類型expression轉(zhuǎn)為非常數(shù)類型dynamic_cast(expression) /安全

2、向下轉(zhuǎn)型 見(jiàn)39reinterpret_cast(expression) /函數(shù)指針類型轉(zhuǎn)換 不常用1. 盡量以const和inline取代#define#define 是一個(gè)宏,只能被預(yù)處理,而不被編譯,用它定義的常量甚至不被編譯器看見(jiàn),因此不能發(fā)現(xiàn)使用中的錯(cuò)誤。用#define定義一個(gè)簡(jiǎn)單函數(shù),必須為每一個(gè)參數(shù)加上一個(gè)括號(hào),容易造成錯(cuò)誤。用內(nèi)聯(lián)函數(shù)高效準(zhǔn)確。define ratio 1.653/編譯器看不見(jiàn)ratio,只看見(jiàn)1.653/一旦出錯(cuò),不會(huì)報(bào)告const double ratio=1.653;const char* const name=“Scott Meyers”;/字符串常量

3、In Class 常量 用靜態(tài)變量類內(nèi)聲明,類外定義。class EngineerConstants private: static const double Factor; ;const double EngineerConstants:Factor=1.35;2. 盡量以取代scanf printf 函數(shù) 不能擴(kuò)充用來(lái)輸入輸出自定義類型的變量。cinix;coutix;可以擴(kuò)展,方便得多改變舊有的C習(xí)慣(shifting from C to C+)盡量以const和inline取代#define#define 是一個(gè)宏,只能被預(yù)處理,而不被編譯,用它定義的常量甚至不被編譯器看見(jiàn),因此不能發(fā)

4、現(xiàn)使用中的錯(cuò)誤。用#define定義一個(gè)簡(jiǎn)單函數(shù),必須為每一個(gè)參數(shù)加上一個(gè)括號(hào),容易造成錯(cuò)誤。用內(nèi)聯(lián)函數(shù)高效準(zhǔn)確。3. 盡量以new和delete取代malloc和freemalloc 和free 不能調(diào)用構(gòu)造函數(shù),析構(gòu)函數(shù) new 和delete則可。不能混用new delete malloc free 必要用C庫(kù)函數(shù)時(shí)檢查是否用到malloc重新用new和delete改過(guò)。4. 盡量使用C+風(fēng)格的注釋形式/* */ 要保證成對(duì)出現(xiàn),不小心錯(cuò)一大片。 / 好看好讀 可以混合使用當(dāng)心!define light_speed 3e8 /m/sec(in a vacum) 內(nèi)存管理(memory m

5、anagement)new 隱式調(diào)用構(gòu)造函數(shù),delete 隱式調(diào)用析構(gòu)函數(shù), 可以重載operator new和operator delete. 不小心運(yùn)用new 和delete會(huì)導(dǎo)致各種錯(cuò)誤。5. 使用相同形式的new和deletestring *a=new string10;delete a;/出錯(cuò)delete a;/正確string *b=new string;delete b;/出錯(cuò)delete b;/正確typedef string addresslines4;string *a=new addresslines;delete a;/出錯(cuò)delete a;/正確不要對(duì)數(shù)組類型用ty

6、pedef, 不容易記住用哪一種delete6. 記得在析構(gòu)函數(shù)中以delete對(duì)付指針成員如果類中有指針數(shù)據(jù)成員,在每個(gè)構(gòu)造函數(shù)中為指針成員配置內(nèi)存,否則將它初始化為0(NULL指針)。若構(gòu)造函數(shù)中用new配置了內(nèi)存,一定要 在析構(gòu)函數(shù)中用delete釋放在賦值運(yùn)算重載時(shí)要將原有指針的內(nèi)存刪除,重新分配內(nèi)存。要在析構(gòu)函數(shù)中刪除這個(gè)指針。不要用delete刪除一個(gè)未完成初始化的指針,不要?jiǎng)h除一個(gè)未分配內(nèi)存的指針。不要delete從一個(gè)類外面?zhèn)鱽?lái)的指針。7. 為內(nèi)存不足的狀況預(yù)作準(zhǔn)備 不能認(rèn)為“檢查內(nèi)存是否分配成功” 是多此一舉。否則會(huì)出現(xiàn)嚴(yán)重后果。 必須建立一個(gè)錯(cuò)誤處理策略。當(dāng)operator

7、 new 無(wú)法滿足需求時(shí),在拋出異常之前,會(huì)調(diào)用一個(gè)內(nèi)存不足處理函數(shù)new handler,這個(gè)函數(shù)由頭文件提供。 typedef void (*new_handle)( );new_handler set_new_handler(new_handler p)throw( ); new_handler是一個(gè)函數(shù)指針,無(wú)參, 無(wú)返回值。函數(shù)set_new_handler用來(lái)配置new_handler,參數(shù)和返回值都是函數(shù)指針,new_handler類型。它確定新的new_handler函數(shù)(參數(shù)),保留舊的new_handler函數(shù)(返回)??梢宰远x新的new_handler函數(shù),用set_n

8、ew_handler確認(rèn)。void nomoreMemory( ) cerr “Unable to satisfy for memeoryn” abort( );/exit int main( ) set_new_handler(nomoreMemory); int *pBigDataArray = new int100000000; 當(dāng)operator new 無(wú)法配置10000000個(gè)整數(shù)空間時(shí),系統(tǒng)調(diào)用nomoreMemory,然后結(jié)束。設(shè)計(jì)new_handler函數(shù),令其完成如下任務(wù): - 讓更多的內(nèi)存可用。預(yù)留一塊內(nèi)存, new_handler第一次被調(diào)用時(shí)釋放, 同時(shí)發(fā)出警告。安裝

9、新的new_handler以取代自己。new_handler中調(diào)用C+標(biāo)準(zhǔn)庫(kù)函數(shù)set_new_handler即可。卸載這個(gè)new_handler,返回NULL指針,并拋出bad_alloc (或其繼承)類型的異常。直接調(diào)用abort或exit終止程序。C+不支持class中專用的new_handler,但仍可以在一個(gè)class中重載operator new, 和set_new_handler函數(shù),讓它調(diào)用特定的new_handler函數(shù),而不用系統(tǒng)給出的全局new_handler。class X public: static new_handler set_new_handler(new_h

10、andler p); static void* operator new(size_t siz); private: static new_handler currentHandler;new_handler X:currentHandler;/初始化為0new_handler X:set_new_handler(new_handler p) new_handler oldHandler = currentHandler; /保留當(dāng)前new_handler currentHandler = p;/再設(shè)置當(dāng)前new_handler return oldHandler;void *X:operat

11、or new(size_t size) newHandler globalHandler= std:set_new_handler(currentHandler); /配置新new_handler 保存globalHandler void *memory; try memory = :oprator new(size); /試分配內(nèi)存 catch(std:bad_alloc&) std:set_new_handler(globalHandler); /恢復(fù)原有處理方法 throw; /傳播異常 std:set_new_handler(globalHandler): /恢復(fù)原有處理方法 retu

12、rn memory; /調(diào)用一次特定處理方法,用畢恢復(fù)/應(yīng)用void nomoreMemory( );X:set_new_handler(nomoreMemory);X *px1 = new X; /如果內(nèi)存分配失敗,調(diào)用nomoreMemory( )string *ps = new string; /如果內(nèi)存分配失敗,調(diào)用globalHandlerX: set_new_handler(0); X *px2 = new X; /如果內(nèi)存分配失敗,立即拋出異??梢宰鲆粋€(gè)混合風(fēng)格基類允許 “設(shè)定class專屬new_handler”templateclass NewHandlerSupport p

13、ublic: static new_handler set_ new_handler(new_handler p); static void* operator new(size_t siz); private: static new_handler currentHandler;templatenew_handler NewHandlerSupport: set_new_handler(new_handler p) new_handler oldHandler = currentHandler;/保留當(dāng)前new_handler currentHandler = p;/再設(shè)置當(dāng)前new_han

14、dler return oldHandler;templatevoid * NewHandlerSupport:operator new(size_t size) newHandler globalHandler= std:set_new_handler(currentHandler); /配置新new_handler 保存globalHandler void *memory; try memory = :oprator new(size); /試分配內(nèi)存 catch(std:bad_alloc&) std:set_new_handler(globalHandler); /恢復(fù)原有處理方法 t

15、hrow; /傳播異常 std:set_new_handler(globalHandler): /恢復(fù)原有處理方法 return memory; new_handler NewHandlerSupport :currentHandler; /初始化為0class X : public NewHandlerSupport /不必聲明set_new_handler和operator new類X 不必改動(dòng)原有的程序代碼,就可以繼續(xù)運(yùn)作。1993年前C+要求operator new在無(wú)法滿足內(nèi)存需求時(shí)返回0,新標(biāo)準(zhǔn)則是拋出一個(gè)bad_alloc類型異常。失敗便轉(zhuǎn)為0的傳統(tǒng)被保留為“nothrow”不拋

16、出異常。頭文件中定義了一個(gè)nothrow對(duì)象 class Widget; Widget *pw1 = new Widget; /如果失敗拋出std:bad_alloc異常 if(pw1=0)/無(wú)效 widget *wp2 = new(nothrow) Widget; /如果失敗,返回0 if(wp2=0)/有效8. 撰寫operator new和operator delete時(shí)應(yīng)遵守的公約當(dāng)你有必要重載operator new時(shí),你的new函數(shù)的行為應(yīng)該與系統(tǒng)原有的new函數(shù)的行為保持一致。應(yīng)該有正確的返回值:返回一個(gè)指針指向分配的內(nèi)存。如果內(nèi)存不足,拋出一個(gè)bad_alloc類型的異常。不能

17、覆蓋系統(tǒng)原有的new函數(shù)。/new 函數(shù)的偽碼void * operator new(size_t size) if(size=0)size=1; /將0內(nèi)存需求,看成1內(nèi)存需求,避免與無(wú)內(nèi)存混淆 while(true)/無(wú)窮循環(huán) 直到內(nèi)存被分配 或拋出異常 attempt to allocate size bytes; if(the allocation was successful) return (a pointer to the memory); new_handle globalHandle = set_new_handler(0) /利用NULL,找出目前的錯(cuò)誤處理函數(shù) set_n

18、ew_handler(globalHandler); /重新設(shè)定為原本的函數(shù) if(globalHandler) (*globalHandler)( ) else throw std:bad_alloc( ); 無(wú)窮循環(huán)可以讓更多的內(nèi)存可用,或安裝一個(gè)不同的new_handler,或卸載new_handler,或拋出一個(gè)異常,或直接結(jié)束程序。operator new 可以被繼承, 但要小心,否則會(huì)導(dǎo)致問(wèn)題class Base public: static void*oprator new(size_t size); ;class Derived : public Base;/導(dǎo)出類中沒(méi)有ope

19、rator new 函數(shù)Derived *p = new Derived; /調(diào)用Base類中的operator new出錯(cuò)這里導(dǎo)出類內(nèi)存比基類要大。改進(jìn)的辦法:void *operator new(size_t size) if(size != sizeof (Base) return : oprator new(size); /回到標(biāo)準(zhǔn)operator new函數(shù) 重寫operator deletevoid operator delete(void*rawMemory) if(rawMemory = 0)return; /與C+標(biāo)準(zhǔn)delete保持一致 Deallocate the mem

20、ory pointed to by rawMemory; return;/member版class Base public: static void *operator new(size_t size); static void operator delete(void* rawMemory,size_t size); void Base:operator delete( void* rawMemory,size_t size); if(rawMemory =0)return; if(size!=sizeof(Base) /如果大小錯(cuò)誤 : operator delete(rawMemory)

21、; /用標(biāo)準(zhǔn)版delete處理 return; deallocate the memory pointed to by rawmeMemory; return;9. 避免覆蓋new的正規(guī)形式解決辦法 (1) 再寫的一個(gè)專用的operator new函數(shù),讓它支 持正規(guī)的new class X public: void f( ); static void* operator new(size_t size, new_handler p); static void* operator new(sise_t size) return :operator new(size); ;X *p1=new (

22、specialErrorHandler) X;/調(diào)用X: operator new(size_t size, new_handler p);X *p2=new X;/調(diào)用X: operator new(size_t size); (2) 為operator new的每一個(gè)參數(shù)提供默認(rèn)值 (缺省值)10. 如果寫了一個(gè)operator new 不要忘記寫一個(gè)operator delete需要?jiǎng)討B(tài)分配大量小額內(nèi)存空間的應(yīng)用程序,有時(shí)需要重載operator new。class AirplaneRep ;class Airplane public:private: AirplaneRep *rep;

23、 /唯一數(shù)據(jù)成員是指針;Airplane *p=new Airplane; /要求內(nèi)存不大分配的內(nèi)存比實(shí)際所需要的內(nèi)存要大,這是為了delete 這塊內(nèi)存時(shí),系統(tǒng)能知道其大小。 pa 紀(jì)錄內(nèi)存大小的 數(shù)據(jù)Airplane 對(duì)象所需的 內(nèi)存 為了節(jié)省內(nèi)存需要定制內(nèi)存管理。定制內(nèi)存管理。class Airplane public: static void* operator new( size_t size); static void operator delete(void*deadObject, size_t size); private: union AirplaneRep *rep; Ai

24、rplane *next; ;/兩個(gè)指針公用一個(gè)內(nèi)存 static const int BLOCK_SIZE; static Airplane * headOfFreeList; /用鏈表配置一片內(nèi)存,整個(gè)類只須一個(gè)鏈;void* Airplane :operator new( size_t size); if(size != sizeof(Airplane) return :operator new(size); Airplane *p= headOfFreeList; /p指向鏈表頭 if(p) headOfFreeList = p-next; /表頭后移, p可用 else Airpla

25、ne *newBlock = static_cast(:operator new(BLOCK_SIZE*sizeof(Airplane); for(int i=1; iBLOCKSIZE-1;+i) /保留第一塊 newBlocki.next = &newBlocki+1; newBlockBLOCK_SIZE-1.next = 0; /置表尾 p = newBlok; / p可用 headOfFreeList =&newBlock1; return p; 只有當(dāng) :operator new失敗時(shí),這里的operator new 才失敗。這時(shí):operator new會(huì)調(diào)用new_handle

26、r直到拋出異常,因此我們不需要再寫一次new_handler處理具體實(shí)現(xiàn)文件中要先對(duì)靜態(tài)成員初始化, Airplane * Airplane:headOfFreeList; / headOfFreeList置0 const int Airplane:BLOCK_SIZE = 512;這個(gè)版本的operator new 可以運(yùn)作良好,速度快過(guò)兩個(gè)數(shù)量級(jí)。還要在Airplane類中寫一個(gè)operator delete void Airplane:operator delete(void*deadObject, size_t size) if(deadObject=0) return; if(siz

27、e !=sizeof(Airplane) :operator delete(deadObject); /與operator new處理保持一致 return; Airplane *carcass = static_cast(deadObject); carcass-next =headOfFreeList; HeadOfFreeList = carcass;如果沒(méi)有定義相應(yīng)的delete函數(shù),而使用了原有的delete, 結(jié)果會(huì)出現(xiàn)意想不到的錯(cuò)誤,有時(shí)是嚴(yán)重的錯(cuò)誤。如果用member版本不要忘記定義virtual 析構(gòu)函數(shù)。這里的delete函數(shù)沒(méi)有memory leak 問(wèn)題。 這是因?yàn)橛昧?/p>

28、memory pool 一次分配一塊內(nèi)存,逐步使用逐步釋放,不必再專門釋放memory pool.定義一個(gè)memory pool 類,使每一個(gè)pool對(duì)象都是一個(gè)內(nèi)存配置器。class Pool public: Pool(size_t n); void* alloc( size_t n);/為一個(gè)對(duì)象配置足夠 /的內(nèi)存遵循operator new的規(guī)矩 void free(void* p, size_t n);/將p的內(nèi)存送回 /pool遵循operator delete的規(guī)矩 pool( );/釋放pool中所有內(nèi)存;用Pool 對(duì)象來(lái)配置內(nèi)存,當(dāng)被銷毀時(shí),配置的內(nèi)存自動(dòng)被釋放。于是memo

29、ry leak 就可以避免。class Airplane public: static void* operator new( size_t size); static void operator delete(void*p, size_t size); private: AirplaneRep *rep; static Pool memPool; /Airplane的memory pool ;inline void Airline:operator new(size_t size) return memPool.alloc(size); inline void Airline:operato

30、r delete(void* p, size_t size) memPool.free(p, size); 為Airplane 的memPool初始化, 要放在Airplane 類實(shí)現(xiàn)的文件里Pool Airplane:memPool(sizeof(Airplane);構(gòu)造函數(shù)、析構(gòu)函數(shù)和賦值運(yùn)算符 構(gòu)造函數(shù)、析構(gòu)函數(shù)和賦值運(yùn)算用來(lái)產(chǎn)生一個(gè)新對(duì)象并初始化,撤銷一個(gè)對(duì)象并收回占有的內(nèi)存,為已有的對(duì)象賦一個(gè)新值。 不能有錯(cuò),必須將他們徹底搞清楚。11. class內(nèi)有成員指針并動(dòng)態(tài)配置內(nèi)存時(shí),一定要有拷貝構(gòu)造函數(shù),賦值運(yùn)算符重載class String public: String(const c

31、har*value); String( ); /沒(méi)有拷貝構(gòu)造函數(shù), /也沒(méi)有賦值運(yùn)算符重載 private: char*data;String:String(const char *value) if(value) data = new charstrlen(value)+1; strcopy(data, value); else data = new char1; *data = “0”; inline String:String( )delete data;String a( “Hello” );String b(“World” );b = a;HelloWorldabdatadata由于

32、沒(méi)有自定義的賦值函數(shù),只能用C+產(chǎn)生的默認(rèn)賦值函數(shù),它簡(jiǎn)單地將b的成員指針data指向a.data,引起 字符串“World”占有的內(nèi)存遺失。而且a.data與b.data指向同一個(gè)內(nèi)存,其中一個(gè)被析構(gòu)時(shí)另一個(gè)就丟失了??截悩?gòu)造函數(shù)用來(lái)傳值,void donothing(String la) String s= “the truth is out of there”;donothing(s);當(dāng)函數(shù)donothing完成任務(wù)后,參數(shù)s所含的指針被析構(gòu),la被刪除。即便la不再使用,將來(lái)又一次析構(gòu)la會(huì)造成問(wèn)題。解決的辦法就是自己定義拷貝構(gòu)造函數(shù),賦值函數(shù)重載。如果確信永不使用這些函數(shù),把他們定

33、義為私有函數(shù),而且不實(shí)現(xiàn)。一旦出錯(cuò),編譯器會(huì)給出錯(cuò)誤提示。12構(gòu)造函數(shù)中盡量以初始化代替賦值一個(gè)類中的const成員數(shù)據(jù)和reference引用數(shù)據(jù)只能被初始化,不能被賦值。即便沒(méi)有const成員數(shù)據(jù)和reference引用數(shù)據(jù),初始化也比賦值效率高。構(gòu)造函數(shù)分兩個(gè)階段實(shí)現(xiàn): 1. 數(shù)據(jù)成員初始化。 2. 調(diào)用構(gòu)造函數(shù)。數(shù)據(jù)成員賦值要調(diào)用構(gòu)造函數(shù),再調(diào)用賦值函數(shù),做兩次調(diào)用影響效率。初始化也容易維護(hù),修改。有一種例外: 一個(gè)類內(nèi)有大量數(shù)據(jù)成員時(shí),賦值比初始化效率高。class ManyDataMbs public: ManyDataMbs() ManyDataMbs(const ManyDat

34、aMbs& x); private: int a, b, c, d, e, f, g, h; double i, j, k, l, m; void init ( );/用來(lái)將數(shù)據(jù)成員初始化,不做他用;void ManyDataMbs:init( ) a=b=c=d=e=f=g=h=1; i=j=k=l=m=0;ManyDataMbs:ManyDataMbs( ) init( ); ManyDataMbs:ManyDataMbs(const ManyDataMbs& x) init( );靜態(tài)數(shù)據(jù)成員static class member不應(yīng)該在構(gòu)造函數(shù)中初始化。靜態(tài)數(shù)據(jù)成員只能初始化一次,不能

35、初始化多次。12. 數(shù)據(jù)成員初始化的次序應(yīng)該和類內(nèi)聲明的次序相同templateclass Array /有上下界的數(shù)組 public: Array(int lowBound, int highBound); private: vector data; /數(shù)組數(shù)據(jù)存儲(chǔ)于一個(gè)vector對(duì)象data中 size_t size; /數(shù)組中元素的個(gè)數(shù) int lBound, hBound; /上下界;templateArray:Array(int lowBound, int highBound): size(highBound-lowBound+1), lBound(lowBound), hBoun

36、d(highBound), data(size) 實(shí)際初始化中,data先被初始化,然后 size, lBound, hBound.這樣數(shù)組中,究竟有多少個(gè)元素?zé)o法確定。基類成員總是比導(dǎo)出類先初始化。多重繼承時(shí)初始化的先后次序要十分小心。14. 總是讓基類擁有虛析構(gòu)函數(shù)一個(gè)軍事應(yīng)用軟件class EnemyTarget public: EnemyTarget( )+ numTargets; EnemyTarget(const EnemyTarget&) + numTargets; EnemyTarget( )- numTargets; static size_t numberOfTargets

37、( ) return numTargets; virtual bool destroy( ); /摧毀敵方目標(biāo)是否成功 private: static size_t numTargets; /對(duì)象計(jì)數(shù)器 ;size_t EnemyTarget:numTargets; /靜態(tài)成員初始化為0,放在類外class EnemyTank : public EnemyTarget public: EnemyTank ( )+ numTanks; EnemyTank (const EnemyTank&)+ numTanks; EnemyTank ( )- numTanks; static size_t nu

38、mberOfTanks( )return numTanks; virtual bool destroy( ); /摧毀敵方坦克是否成功 private: static size_t numTanks; /敵方坦克計(jì)數(shù)器 ;EnemyTarget *targetPtr = new EnemyTank;delete targetPtr; /未定義,計(jì)數(shù)出錯(cuò),影響戰(zhàn)斗勝敗 解決辦法,把EnemyTarget類中的析構(gòu)函數(shù)定義為virtual即可。幾乎所有的基類都有虛函數(shù),只要有一個(gè)虛函數(shù),就要把析構(gòu)函數(shù)定義為虛函數(shù)。沒(méi)有虛函數(shù)的類,有繼承派生類對(duì)象析構(gòu),也要定義虛析構(gòu)函數(shù)。但虛函數(shù)會(huì)增加內(nèi)存開銷。完

39、全不必要時(shí)不要用虛析構(gòu)函數(shù)。聲明一個(gè)抽象類,可以加一個(gè)純虛析構(gòu)函數(shù)。15. 讓operator=返回*this的引用referenceC語(yǔ)言中operator= 的原型C& C:operator=(const C&);char x, y, z;x=y=z= a;x,operator=(y.operator=(z.operator= a);z.operator=的返回值是y.operator=的實(shí)參。他們應(yīng)該有相同的類型。但不要讓operator=返回void類型,const類型Strin& String:operator=(const String& rhs) return *this; /返

40、回一個(gè)引用指向左側(cè)對(duì)象Strin& String:operator=(const String& rhs) return rhs; /返回一個(gè)引用指向右側(cè)對(duì)象,錯(cuò)誤后一個(gè)返回值,編譯器無(wú)法編譯,無(wú)法返回const類型. 如果參數(shù)中去掉const變成:Strin& String:operator=( String& rhs);X= a; /無(wú)法編譯 rhs應(yīng)該是一個(gè)變量。結(jié)論:必須返回 *this;16. 在operator=中為所有的數(shù)據(jù)成員賦值基類中這不成問(wèn)題,在派生類中要小心。正確的賦值運(yùn)算Derived& Derived:operator=(const Drived& rhs) if(t

41、his = = &rhs) return *this; Base:operator=(rhs); /調(diào)用基類的賦值運(yùn)算 data = rhs.data; return *this; Derived& Derived:operator=(const Drived& rhs) if(this = = &rhs) return *this; static_cast(*this) = rhs; /*this強(qiáng)制轉(zhuǎn)換成基類的引用賦值基類成員 data = rhs.data; return *this; 拷貝構(gòu)造函數(shù)中要調(diào)用基類構(gòu)造函數(shù)。 用第一種方法在operator=中檢查是否“自己賦值給自己”cla

42、ss X;X a; X&b = a;/b是a的別名 (aliasing)a = b; /自己賦值給自己 合法 在賦值函數(shù)中要特別謹(jǐn)慎的處理自己的別名賦值給自己的問(wèn)題。提高效率 先做檢查,一發(fā)現(xiàn)自己賦值給自己立即返回。導(dǎo)出類的賦值運(yùn)算重載中一定要先檢查,可以節(jié)省許多工作確保正確性 賦值運(yùn)算通常要先將左邊對(duì)象的資源釋放,再行賦值。如果有自己賦值給自己的現(xiàn)象,這個(gè)資源可能丟失,不可挽回了。如何判斷兩個(gè)對(duì)象是同一個(gè)對(duì)象? 不是對(duì)象的內(nèi)容相同,而是看他們的地址是否相同。 X& X:operator=(const X& rhs) if(this=&rhs) return *this; aliasing問(wèn)題

43、不限于賦值運(yùn)算內(nèi),只要用到指針或引用,就可能出現(xiàn)。這時(shí)我們就要當(dāng)心,不要誤刪了有用的資源。類和函數(shù)的設(shè)計(jì)和申明設(shè)計(jì)一個(gè)高效率的類型(class 型別), 必須先回答下列問(wèn)題對(duì)象如何產(chǎn)生和銷毀? 確定構(gòu)造函數(shù)和析構(gòu)函數(shù)的設(shè)計(jì)。對(duì)象的初始化和賦值有什么不同? 決定構(gòu)造函數(shù)和賦值函數(shù)的設(shè)計(jì)。對(duì)象如何傳值 決定拷貝構(gòu)造函數(shù)的設(shè)計(jì)確定合法的范圍 成員數(shù)據(jù)的定義域 確定做什么檢查,何時(shí)拋出異常判斷是否能從已有的類繼承 如果能繼承,注意受基類哪些約束,哪些要用虛函數(shù)。允許那種類型轉(zhuǎn)換 構(gòu)造函數(shù)可以用作隱式類型轉(zhuǎn)換,顯式類型轉(zhuǎn)換要自定義。新類型需要哪些運(yùn)算和函數(shù) 確定class的接口。哪些運(yùn)算和函數(shù)必須禁用

44、放到private成員中。新類型的對(duì)象可調(diào)用哪些函數(shù) 確定公有成員函數(shù),保護(hù)成員函數(shù), 私有成員函數(shù)。是否通用類型 確定是否要用類模板18努力讓接口完滿(complete)且最小化客戶端接口(client interface)指公有成員,一般只有公有函數(shù),不要有公有數(shù)據(jù)。完滿接口 允許客戶做合理要求的任意事情。最小化接口 盡量讓函數(shù)個(gè)數(shù)最少。不能有功能重疊的函數(shù)。太多函數(shù)不容易被理解,不易維護(hù),浪費(fèi)資源。如果增加一個(gè)函數(shù),使新類型更方便使用,就可以增加。T& operator (int index); /傳回?cái)?shù)組的一個(gè)元素,可讀,可寫const T& operator (int index)c

45、onst; /傳回?cái)?shù)組的一個(gè)元素,可讀,不可寫19區(qū)分成員函數(shù)、非成員函數(shù) 和友元函數(shù)成員函數(shù)可以動(dòng)態(tài)綁定,可以用virtual非成員函數(shù)不能用virtual,非成員函數(shù)能不做友元盡量不做友元函數(shù)。非成員函數(shù)要調(diào)用類中私有數(shù)據(jù)成員或私有函數(shù),則一定要聲明為友元。不要讓operaor成為類的成員函數(shù), 必要時(shí)作友元。要讓函數(shù)式左邊對(duì)象做類型轉(zhuǎn)換,就不能做成員函數(shù)。例子class complex complex operator*(complex rhs)const; private: float x, y;complex a(1,2),b(1.5,4);a=a*b;/正確a=a*2;/可以a=2

46、*a;/出錯(cuò)只能聲明為非成員函數(shù)const complex operator*(const complex& lhs,const complex& rhs);20避免將數(shù)據(jù)成員設(shè)置為公有數(shù)據(jù) 讓公有成員都是函數(shù),可以保持一致性。 將數(shù)據(jù)成員聲明為私有成員或保護(hù)成員,可以確保數(shù)據(jù)的安全。21盡可能使用const使用const可以讓編譯器知道某值不能改變,編譯器會(huì)確保這個(gè)條件不會(huì)被改變。const char* p; /指針,指向常值字符串char* const p;/常指針,指向固定地址,地址內(nèi)字符串不一定是常量const char* const p;/ 常指針,指向固定地址,內(nèi)置常字符串cons

47、t chr *p; char const *p; /意義相同函數(shù)中const 可以修飾傳回值,參數(shù),成員函數(shù)時(shí)甚至可以修飾整個(gè)函數(shù)。函數(shù)返回值用 const, 可以改善函數(shù)的安全性,和效率。T& A:operator (int index); /傳回?cái)?shù)組的一個(gè)元素,可讀,可寫A a(8); couta2; a2=b;/正確 const T& A:operator (int index); /傳回?cái)?shù)組的一個(gè)元素,可讀,不可寫 A a(8); couta2; /正確 a2=b;/錯(cuò)誤const complex operator*(const complex& lhs,const complex&

48、rhs);complex a,b,c; (a*b)=c; /不允許參數(shù)用const 可以保證參數(shù)值不變,讓編譯器作檢查。const成員函數(shù)保證this指針不變。class A public: int length( )const; private: int size;;int A:length( )const if(size0)return 0; /錯(cuò)誤 不能改變?nèi)魏螖?shù)據(jù)成員 return size; 新C+標(biāo)準(zhǔn) 新增保留字 mutableclass A public: int length( )const; private: mutable int size;/可以在任何地點(diǎn)被改動(dòng), /即使

49、在const成員函數(shù)中;int A:length( )const if(size0)return 0; /正確 return size; 22盡量使用引用參數(shù)傳址pass by reference拷貝構(gòu)造函數(shù)用來(lái)傳值pass by value,為函數(shù)的參數(shù)傳值,為函數(shù)的返回值傳值。傳值要占用許多資源。class Person public: Person( ); Person( ); private: string name, address;class student : public Person public: student( ); student( ); private: strin

50、g schoolname,schoolAddress;student returnstudent(student s) return s;student plato; returnstudent(plato); 函數(shù)調(diào)用中copy構(gòu)造函數(shù)被調(diào)用兩次,將plato傳給參數(shù)s, 再將函數(shù)值返回,析構(gòu)函數(shù)調(diào)用兩次,析構(gòu)s,析構(gòu)函數(shù)返回值。更有甚者,基類Person 的構(gòu)造函數(shù)也要調(diào)用兩次。student對(duì)象中兩個(gè)string數(shù)據(jù)對(duì)象要構(gòu)造,基類Person中兩個(gè)string數(shù)據(jù)對(duì)象也要構(gòu)造,plato給s構(gòu)造四次, 返回傳值構(gòu)造四次總共調(diào)用12次構(gòu)造函數(shù),當(dāng)然還有12次析構(gòu)函數(shù)要調(diào)用。免除這些不當(dāng)成

51、本,改用引用參數(shù)傳址by referenceconst student& returnstudent(const student& s)return s;引用參數(shù)傳址by reference 不調(diào)用任何構(gòu)造函數(shù)析構(gòu)函數(shù)。虛函數(shù)的引用參數(shù)是基類時(shí),實(shí)際傳入派生類對(duì)象時(shí)可以調(diào)用派生類的函數(shù)。傳值參數(shù)沒(méi)有這樣的功能。引用參數(shù)要注意別名(aliasing)問(wèn)題。 23當(dāng)你必須傳回objebct(傳值)時(shí)不要傳址(引用)盡可能讓事情簡(jiǎn)單,但不要過(guò)于簡(jiǎn)單。 A.Einstein盡可能讓事情有效率,但不要過(guò)于有效率。 C+函數(shù)必須傳回一個(gè)對(duì)象,就不要傳址不要返回引用。不能傳回一個(gè)不存在的地址,不能傳回函數(shù)中

52、產(chǎn)生的局部對(duì)象的地址。const complex operator*(const complex& lhs,const complex& rhs)complex temp(lhs.x*rhs.x-lhs.y*rhs.y, lhs.x*rhs.y+lhs.y*rhs.x); return temp; &錯(cuò)誤。返回值地址指向局部對(duì)象,與局部對(duì)象同名,運(yùn)算執(zhí)行完畢,局部對(duì)象被析構(gòu),返回值指向一個(gè)不存在的地址。const complex& operator*(const complex& lhs,const complex& rhs)complex *temp(lhs.x*rhs.x-lhs.y*rh

53、s.y, lhs.x*rhs.y+lhs.y*rhs.x); return *temp; 指針temp被析構(gòu),內(nèi)存已丟失。const complex& operator*(const complex& lhs,const complex& rhs)complex *temp=new complex(lhs.x*rhs.x-lhs.y*rhs.y, lhs.x*rhs.y+lhs.y*rhs.x); return *temp; 指針temp沒(méi)有析構(gòu),將來(lái)誰(shuí)來(lái)析構(gòu)呢。內(nèi)存可能丟失。complex one(1), two(2), three(3), four(4);complex product;p

54、roduct=one*two*three*four;如何析構(gòu)這幾個(gè)operator*中間產(chǎn)生的temp指針呢?24函數(shù)重載和參數(shù)缺省之間,謹(jǐn)慎抉擇函數(shù)重載和參數(shù)缺省之間容易引起混淆。如果可以選擇一個(gè)合理的默認(rèn)值,并且只需要一種算法,最好使用缺省參數(shù)。否則使用重載函數(shù)。例:求五個(gè)整數(shù)的最大值#includeint max(int a, int b=std:numeric_limits:min( ), int c=std:numeric_limits:min( ), int d=std:numeric_limits:min( ), int e=std:numeric_limits:min( ),)

55、 int temp=ab?a:b; int temp=tempc? temp:c; int temp= temp d? temp:d int temp= temp e? temp:e;使用max函數(shù)對(duì)兩個(gè)參數(shù),三個(gè)參數(shù),直至五個(gè)參數(shù)都有效。但是,計(jì)算平均數(shù)就找不到合適的默認(rèn)值,只好重載。一般,構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)的算法不同,需要重載。25避免對(duì)指針類型和數(shù)值類型進(jìn)行重載void f(int x);void f(string *ps);f(0); /調(diào)用那一個(gè)?調(diào)用f(int)void * const NULL=0;/無(wú)類型指針f(NULL);/錯(cuò)誤 類型不符#define NULL 0f(N

56、ULL);/調(diào)用f(int)#define NULL(void*) 0)f(NULL);/錯(cuò)誤 類型不符class NULLClass/類型名可以隱去 public: template operator T*( ) return 0;/為任意類型T傳回一個(gè)NULL指針NULL;f(string*ps);f(NULL);/NULL被轉(zhuǎn)換為string*調(diào)用f(string*ps)盡可能避免對(duì)指針類型和數(shù)值類型進(jìn)行重載26防備隱性二義性狀態(tài)class B;classApublic: A(const B&); /由B可以造出A來(lái);class Bpublic: operator A( )const;

57、/B可以轉(zhuǎn)換成A;void f(const A&);B b;f(b);/錯(cuò)誤 模棱兩可 兩種方法哪種更好?void f(int);void f(char);double d=6.02;f(d);/模棱兩可模棱兩可可以潛伏很久,直到爆發(fā)。多繼承最容易引發(fā)模棱兩可。class Bpublic: B doit( ); ;class Cpublic: C doit( ); /放在私有成員中同樣不行 ;class Derived :public B, public C;Derived d;d.doit( );/模棱兩可d.B:doit( );/正確d.C;doit( );/正確27如果不想使用編譯器暗自

58、產(chǎn)生的成員函數(shù),明確地拒絕不允許一個(gè)函數(shù)存在,只要不把它放進(jìn) class中。但賦值函數(shù),拷貝構(gòu)造函數(shù)例外,系統(tǒng)會(huì)自行產(chǎn)生一個(gè)這種函數(shù)。不許對(duì)象調(diào)用某個(gè)函數(shù),把它放在私有成員中。但公有函數(shù),友元可以調(diào)用。聲明一個(gè)函數(shù),而不定義它,調(diào)用它編譯器會(huì)指出錯(cuò)誤。28嘗試切割global namespace (全局命名空間)標(biāo)識(shí)符重名會(huì)引起混亂。同類名詞冠以同一詞頭會(huì)使名字太長(zhǎng)。建議使用namespace名字空間namespace sdm const int BOOK_VERSION = 2.0; class Handle; Handle getHandle( );有三種方法取用namespace內(nèi)的名字

59、。void f1( ) using namespace sdm;/匯入所有名字 coutBOOK_VERSION; Handle h=getHandle( );void f2( ) using sdm: BOOK_VERSION;/匯入單個(gè)名字 coutBOOK_VERSION;/正確 Handle h=getHandle( );/錯(cuò)誤 void f3( ) cout sdm: BOOK_VERSION;/沒(méi)問(wèn)題只用一次 double d=BOOK_VERSION;/錯(cuò)誤 Handle h=getHandle( );/錯(cuò)誤 兩個(gè)namespace中有相同的名字,只要標(biāo)明namespace域名即

60、可區(qū)分。 using namespace stm; using namespace sdm; stm:;BOOK_VERSION; sdm:BOOK_VERSION;類與函數(shù)的實(shí)現(xiàn) 29避免傳回內(nèi)部數(shù)據(jù)的handlesclass Stringpublic: String(const char *value); String( ); operator char *( ) const; private: char * data; inline String:operator char *( )const return data; /潛伏著危險(xiǎn)const String B(“I love you!”

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝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ù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 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)論