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

下載本文檔

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

文檔簡介

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

2、向下轉(zhuǎn)型 見39reinterpret_cast(expression) /函數(shù)指針類型轉(zhuǎn)換 不常用1. 盡量以const和inline取代#define#define 是一個宏,只能被預(yù)處理,而不被編譯,用它定義的常量甚至不被編譯器看見,因此不能發(fā)現(xiàn)使用中的錯誤。用#define定義一個簡單函數(shù),必須為每一個參數(shù)加上一個括號,容易造成錯誤。用內(nèi)聯(lián)函數(shù)高效準(zhǔn)確。define ratio 1.653/編譯器看不見ratio,只看見1.653/一旦出錯,不會報告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ò)充用來輸入輸出自定義類型的變量。cinix;coutix;可以擴(kuò)展,方便得多改變舊有的C習(xí)慣(shifting from C to C+)盡量以const和inline取代#define#define 是一個宏,只能被預(yù)處理,而不被編譯,用它定義的常量甚至不被編譯器看見,因此不能發(fā)

4、現(xiàn)使用中的錯誤。用#define定義一個簡單函數(shù),必須為每一個參數(shù)加上一個括號,容易造成錯誤。用內(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庫函數(shù)時檢查是否用到malloc重新用new和delete改過。4. 盡量使用C+風(fēng)格的注釋形式/* */ 要保證成對出現(xiàn),不小心錯一大片。 / 好看好讀 可以混合使用當(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. 不小心運用new 和delete會導(dǎo)致各種錯誤。5. 使用相同形式的new和deletestring *a=new string10;delete a;/出錯delete a;/正確string *b=new string;delete b;/出錯delete b;/正確typedef string addresslines4;string *a=new addresslines;delete a;/出錯delete a;/正確不要對數(shù)組類型用ty

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

7、 new 無法滿足需求時,在拋出異常之前,會調(diào)用一個內(nèi)存不足處理函數(shù)new handler,這個函數(shù)由頭文件提供。 typedef void (*new_handle)( );new_handler set_new_handler(new_handler p)throw( ); new_handler是一個函數(shù)指針,無參, 無返回值。函數(shù)set_new_handler用來配置new_handler,參數(shù)和返回值都是函數(shù)指針,new_handler類型。它確定新的new_handler函數(shù)(參數(shù)),保留舊的new_handler函數(shù)(返回)。可以自定義新的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 無法配置10000000個整數(shù)空間時,系統(tǒng)調(diào)用nomoreMemory,然后結(jié)束。設(shè)計new_handler函數(shù),令其完成如下任務(wù): - 讓更多的內(nèi)存可用。預(yù)留一塊內(nèi)存, new_handler第一次被調(diào)用時釋放, 同時發(fā)出警告。安裝

9、新的new_handler以取代自己。new_handler中調(diào)用C+標(biāo)準(zhǔn)庫函數(shù)set_new_handler即可。卸載這個new_handler,返回NULL指針,并拋出bad_alloc (或其繼承)類型的異常。直接調(diào)用abort或exit終止程序。C+不支持class中專用的new_handler,但仍可以在一個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)存分配失敗,立即拋出異??梢宰鲆粋€混合風(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 不必改動原有的程序代碼,就可以繼續(xù)運作。1993年前C+要求operator new在無法滿足內(nèi)存需求時返回0,新標(biāo)準(zhǔn)則是拋出一個bad_alloc類型異常。失敗便轉(zhuǎn)為0的傳統(tǒng)被保留為“nothrow”不拋

16、出異常。頭文件中定義了一個nothrow對象 class Widget; Widget *pw1 = new Widget; /如果失敗拋出std:bad_alloc異常 if(pw1=0)/無效 widget *wp2 = new(nothrow) Widget; /如果失敗,返回0 if(wp2=0)/有效8. 撰寫operator new和operator delete時應(yīng)遵守的公約當(dāng)你有必要重載operator new時,你的new函數(shù)的行為應(yīng)該與系統(tǒng)原有的new函數(shù)的行為保持一致。應(yīng)該有正確的返回值:返回一個指針指向分配的內(nèi)存。如果內(nèi)存不足,拋出一個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)存需求,避免與無內(nèi)存混淆 while(true)/無窮循環(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,找出目前的錯誤處理函數(shù) set_n

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

19、rator new 函數(shù)Derived *p = new Derived; /調(diào)用Base類中的operator new出錯這里導(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) /如果大小錯誤 : operator delete(rawMemory)

21、; /用標(biāo)準(zhǔn)版delete處理 return; deallocate the memory pointed to by rawmeMemory; return;9. 避免覆蓋new的正規(guī)形式解決辦法 (1) 再寫的一個專用的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的每一個參數(shù)提供默認(rèn)值 (缺省值)10. 如果寫了一個operator new 不要忘記寫一個operator delete需要動態(tài)分配大量小額內(nèi)存空間的應(yīng)用程序,有時需要重載operator new。class AirplaneRep ;class Airplane public:private: AirplaneRep *rep;

23、 /唯一數(shù)據(jù)成員是指針;Airplane *p=new Airplane; /要求內(nèi)存不大分配的內(nèi)存比實際所需要的內(nèi)存要大,這是為了delete 這塊內(nèi)存時,系統(tǒng)能知道其大小。 pa 紀(jì)錄內(nèi)存大小的 數(shù)據(jù)Airplane 對象所需的 內(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; ;/兩個指針公用一個內(nèi)存 static const int BLOCK_SIZE; static Airplane * headOfFreeList; /用鏈表配置一片內(nèi)存,整個類只須一個鏈;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失敗時,這里的operator new 才失敗。這時:operator new會調(diào)用new_handle

26、r直到拋出異常,因此我們不需要再寫一次new_handler處理具體實現(xiàn)文件中要先對靜態(tài)成員初始化, Airplane * Airplane:headOfFreeList; / headOfFreeList置0 const int Airplane:BLOCK_SIZE = 512;這個版本的operator new 可以運作良好,速度快過兩個數(shù)量級。還要在Airplane類中寫一個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;如果沒有定義相應(yīng)的delete函數(shù),而使用了原有的delete, 結(jié)果會出現(xiàn)意想不到的錯誤,有時是嚴(yán)重的錯誤。如果用member版本不要忘記定義virtual 析構(gòu)函數(shù)。這里的delete函數(shù)沒有memory leak 問題。 這是因為用了

28、memory pool 一次分配一塊內(nèi)存,逐步使用逐步釋放,不必再專門釋放memory pool.定義一個memory pool 類,使每一個pool對象都是一個內(nèi)存配置器。class Pool public: Pool(size_t n); void* alloc( size_t n);/為一個對象配置足夠 /的內(nèi)存遵循operator new的規(guī)矩 void free(void* p, size_t n);/將p的內(nèi)存送回 /pool遵循operator delete的規(guī)矩 pool( );/釋放pool中所有內(nèi)存;用Pool 對象來配置內(nèi)存,當(dāng)被銷毀時,配置的內(nèi)存自動被釋放。于是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 類實現(xiàn)的文件里Pool Airplane:memPool(sizeof(Airplane);構(gòu)造函數(shù)、析構(gòu)函數(shù)和賦值運算符 構(gòu)造函數(shù)、析構(gòu)函數(shù)和賦值運算用來產(chǎn)生一個新對象并初始化,撤銷一個對象并收回占有的內(nèi)存,為已有的對象賦一個新值。 不能有錯,必須將他們徹底搞清楚。11. class內(nèi)有成員指針并動態(tài)配置內(nèi)存時,一定要有拷貝構(gòu)造函數(shù),賦值運算符重載class String public: String(const c

31、har*value); String( ); /沒有拷貝構(gòu)造函數(shù), /也沒有賦值運算符重載 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、沒有自定義的賦值函數(shù),只能用C+產(chǎn)生的默認(rèn)賦值函數(shù),它簡單地將b的成員指針data指向a.data,引起 字符串“World”占有的內(nèi)存遺失。而且a.data與b.data指向同一個內(nèi)存,其中一個被析構(gòu)時另一個就丟失了??截悩?gòu)造函數(shù)用來傳值,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不再使用,將來又一次析構(gòu)la會造成問題。解決的辦法就是自己定義拷貝構(gòu)造函數(shù),賦值函數(shù)重載。如果確信永不使用這些函數(shù),把他們定

33、義為私有函數(shù),而且不實現(xiàn)。一旦出錯,編譯器會給出錯誤提示。12構(gòu)造函數(shù)中盡量以初始化代替賦值一個類中的const成員數(shù)據(jù)和reference引用數(shù)據(jù)只能被初始化,不能被賦值。即便沒有const成員數(shù)據(jù)和reference引用數(shù)據(jù),初始化也比賦值效率高。構(gòu)造函數(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ù),修改。有一種例外: 一個類內(nèi)有大量數(shù)據(jù)成員時,賦值比初始化效率高。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 ( );/用來將數(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ù)存儲于一個vector對象data中 size_t size; /數(shù)組中元素的個數(shù) int lBound, hBound; /上下界;templateArray:Array(int lowBound, int highBound): size(highBound-lowBound+1), lBound(lowBound), hBoun

36、d(highBound), data(size) 實際初始化中,data先被初始化,然后 size, lBound, hBound.這樣數(shù)組中,究竟有多少個元素?zé)o法確定?;惓蓡T總是比導(dǎo)出類先初始化。多重繼承時初始化的先后次序要十分小心。14. 總是讓基類擁有虛析構(gòu)函數(shù)一個軍事應(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; /對象計數(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; /敵方坦克計數(shù)器 ;EnemyTarget *targetPtr = new EnemyTank;delete targetPtr; /未定義,計數(shù)出錯,影響戰(zhàn)斗勝敗 解決辦法,把EnemyTarget類中的析構(gòu)函數(shù)定義為virtual即可。幾乎所有的基類都有虛函數(shù),只要有一個虛函數(shù),就要把析構(gòu)函數(shù)定義為虛函數(shù)。沒有虛函數(shù)的類,有繼承派生類對象析構(gòu),也要定義虛析構(gòu)函數(shù)。但虛函數(shù)會增加內(nèi)存開銷。完

39、全不必要時不要用虛析構(gòu)函數(shù)。聲明一個抽象類,可以加一個純虛析構(gòu)函數(shù)。15. 讓operator=返回*this的引用referenceC語言中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=的實參。他們應(yīng)該有相同的類型。但不要讓operator=返回void類型,const類型Strin& String:operator=(const String& rhs) return *this; /返

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

41、his = = &rhs) return *this; Base:operator=(rhs); /調(diào)用基類的賦值運算 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)慎的處理自己的別名賦值給自己的問題。提高效率 先做檢查,一發(fā)現(xiàn)自己賦值給自己立即返回。導(dǎo)出類的賦值運算重載中一定要先檢查,可以節(jié)省許多工作確保正確性 賦值運算通常要先將左邊對象的資源釋放,再行賦值。如果有自己賦值給自己的現(xiàn)象,這個資源可能丟失,不可挽回了。如何判斷兩個對象是同一個對象? 不是對象的內(nèi)容相同,而是看他們的地址是否相同。 X& X:operator=(const X& rhs) if(this=&rhs) return *this; aliasing問題

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

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

45、onst; /傳回數(shù)組的一個元素,可讀,不可寫19區(qū)分成員函數(shù)、非成員函數(shù) 和友元函數(shù)成員函數(shù)可以動態(tài)綁定,可以用virtual非成員函數(shù)不能用virtual,非成員函數(shù)能不做友元盡量不做友元函數(shù)。非成員函數(shù)要調(diào)用類中私有數(shù)據(jù)成員或私有函數(shù),則一定要聲明為友元。不要讓operaor成為類的成員函數(shù), 必要時作友元。要讓函數(shù)式左邊對象做類型轉(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;/出錯只能聲明為非成員函數(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可以讓編譯器知道某值不能改變,編譯器會確保這個條件不會被改變。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ù)。函數(shù)返回值用 const, 可以改善函數(shù)的安全性,和效率。T& A:operator (int index); /傳回數(shù)組的一個元素,可讀,可寫A a(8); couta2; a2=b;/正確 const T& A:operator (int index); /傳回數(shù)組的一個元素,可讀,不可寫 A a(8); couta2; /正確 a2=b;/錯誤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; /錯誤 不能改變?nèi)魏螖?shù)據(jù)成員 return size; 新C+標(biāo)準(zhǔn) 新增保留字 mutableclass A public: int length( )const; private: mutable int size;/可以在任何地點被改動, /即使

49、在const成員函數(shù)中;int A:length( )const if(size0)return 0; /正確 return size; 22盡量使用引用參數(shù)傳址pass by reference拷貝構(gòu)造函數(shù)用來傳值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對象中兩個string數(shù)據(jù)對象要構(gòu)造,基類Person中兩個string數(shù)據(jù)對象也要構(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ù)是基類時,實際傳入派生類對象時可以調(diào)用派生類的函數(shù)。傳值參數(shù)沒有這樣的功能。引用參數(shù)要注意別名(aliasing)問題。 23當(dāng)你必須傳回objebct(傳值)時不要傳址(引用)盡可能讓事情簡單,但不要過于簡單。 A.Einstein盡可能讓事情有效率,但不要過于有效率。 C+函數(shù)必須傳回一個對象,就不要傳址不要返回引用。不能傳回一個不存在的地址,不能傳回函數(shù)中

52、產(chǎn)生的局部對象的地址。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; &錯誤。返回值地址指向局部對象,與局部對象同名,運算執(zhí)行完畢,局部對象被析構(gòu),返回值指向一個不存在的地址。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沒有析構(gòu),將來誰來析構(gòu)呢。內(nèi)存可能丟失。complex one(1), two(2), three(3), four(4);complex product;p

54、roduct=one*two*three*four;如何析構(gòu)這幾個operator*中間產(chǎn)生的temp指針呢?24函數(shù)重載和參數(shù)缺省之間,謹(jǐn)慎抉擇函數(shù)重載和參數(shù)缺省之間容易引起混淆。如果可以選擇一個合理的默認(rèn)值,并且只需要一種算法,最好使用缺省參數(shù)。否則使用重載函數(shù)。例:求五個整數(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ù)對兩個參數(shù),三個參數(shù),直至五個參數(shù)都有效。但是,計算平均數(shù)就找不到合適的默認(rèn)值,只好重載。一般,構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)的算法不同,需要重載。25避免對指針類型和數(shù)值類型進(jìn)行重載void f(int x);void f(string *ps);f(0); /調(diào)用那一個?調(diào)用f(int)void * const NULL=0;/無類型指針f(NULL);/錯誤 類型不符#define NULL 0f(N

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

57、/B可以轉(zhuǎn)換成A;void f(const A&);B b;f(b);/錯誤 模棱兩可 兩種方法哪種更好?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ù),明確地拒絕不允許一個函數(shù)存在,只要不把它放進(jìn) class中。但賦值函數(shù),拷貝構(gòu)造函數(shù)例外,系統(tǒng)會自行產(chǎn)生一個這種函數(shù)。不許對象調(diào)用某個函數(shù),把它放在私有成員中。但公有函數(shù),友元可以調(diào)用。聲明一個函數(shù),而不定義它,調(diào)用它編譯器會指出錯誤。28嘗試切割global namespace (全局命名空間)標(biāo)識符重名會引起混亂。同類名詞冠以同一詞頭會使名字太長。建議使用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;/匯入單個名字 coutBOOK_VERSION;/正確 Handle h=getHandle( );/錯誤 void f3( ) cout sdm: BOOK_VERSION;/沒問題只用一次 double d=BOOK_VERSION;/錯誤 Handle h=getHandle( );/錯誤 兩個namespace中有相同的名字,只要標(biāo)明namespace域名即

60、可區(qū)分。 using namespace stm; using namespace sdm; stm:;BOOK_VERSION; sdm:BOOK_VERSION;類與函數(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; /潛伏著危險const String B(“I love you!”

溫馨提示

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

最新文檔

評論

0/150

提交評論