改善程序設(shè)計(jì)技術(shù)的50個(gè)有效做法.ppt_第1頁
改善程序設(shè)計(jì)技術(shù)的50個(gè)有效做法.ppt_第2頁
改善程序設(shè)計(jì)技術(shù)的50個(gè)有效做法.ppt_第3頁
改善程序設(shè)計(jì)技術(shù)的50個(gè)有效做法.ppt_第4頁
改善程序設(shè)計(jì)技術(shù)的50個(gè)有效做法.ppt_第5頁
已閱讀5頁,還剩167頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

改善程序設(shè)計(jì)技術(shù)的 50個(gè)有效做法 第二版2002.3 Scott Meyers 侯 捷 譯,如何完成較好的設(shè)計(jì) 如何避免常見的問題 如何提高效率的一些準(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) /安全向下轉(zhuǎn)型 見39 reinterpret_cast(expression) /函數(shù)指針類型轉(zhuǎn)換 不常用,1. 盡量以const和inline取代#define,#define 是一個(gè)宏,只能被預(yù)處理, 而不被編譯,用它定義的常量甚至不被編 譯器看見,因此不能發(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 /編譯器看不見ratio,只看見1.653 /一旦出錯(cuò),不會(huì)報(bào)告,const double ratio=1.653; const char* const name=“Scott Meyers”; /字符串常量,In Class 常量,用靜態(tài)變量 類內(nèi)聲明,類外定義。 class EngineerConstants private: static const double Factor; ; const double EngineerConstants:Factor=1.35;,2. 盡量以取代,改變舊有的C習(xí)慣(shifting from C to C+) 盡量以const和inline取代#define #define 是一個(gè)宏,只能被預(yù)處理,而不被編譯,用它定義的常量甚至不被編譯器看見,因此不能發(fā)現(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和free,malloc 和free 不能調(diào)用構(gòu)造函數(shù),析構(gòu)函數(shù) new 和delete則可。 不能混用new delete malloc free 必要用C庫函數(shù)時(shí)檢查是否用到malloc 重新用new和delete改過。,4. 盡量使用C+風(fēng)格的注釋形式,/* */ 要保證成對(duì)出現(xiàn),不小心錯(cuò)一大片。 / 好看好讀 可以混合使用 當(dāng)心! define light_speed 3e8 /m/sec(in a vacum),內(nèi)存管理(memory management),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和delete,string *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ù)組類型用typedef, 不容易記住用哪一種delete,6. 記得在析構(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)鱽淼闹羔槨?7. 為內(nèi)存不足的狀況預(yù)作準(zhǔn)備,不能認(rèn)為“檢查內(nèi)存是否分配成功” 是多此一 舉。否則會(huì)出現(xiàn)嚴(yán)重后果。 必須建立一個(gè)錯(cuò)誤處理策略。當(dāng)operator new 無 法滿足需求時(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ù)指針,無參, 無返回值。 函數(shù)set_new_handler用來配置new_handler, 參數(shù)和返回值都是函數(shù)指針,new_handler類型。 它確定新的new_handler函數(shù)(參數(shù)),保留舊的new_handler函數(shù)(返回)。,可以自定義新的new_handler函數(shù), 用set_new_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個(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ā)出警告。 安裝新的new_handler以取代自己。new_handler中調(diào)用C+標(biāo)準(zhǔn)庫函數(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_handler p); static void* operator new(size_t siz); private: static new_handler currentHandler; ;,new_handler X:currentHandler;/初始化為0 new_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: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 /調(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)用globalHandler X: set_new_handler(0); X *px2 = new X; /如果內(nèi)存分配失敗,立即拋出異常,可以做一個(gè)混合風(fēng)格基類 允許 “設(shè)定class專屬new_handler”,template class NewHandlerSupport public: static new_handler set_ new_handler(new_handler p); static void* operator new(size_t siz); private: static new_handler currentHandler; ;,template,new_handler NewHandlerSupport: set_new_handler(new_handler p) new_handler oldHandler = currentHandler;/保留當(dāng)前new_handler currentHandler = p;/再設(shè)置當(dāng)前new_handler return oldHandler; ,template,void * 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 ,new_handler NewHandlerSupport :currentHandler; /初始化為0,class X : public NewHandlerSupport /不必聲明set_new_handler和operator new 類X 不必改動(dòng)原有的程序代碼,就可以繼續(xù)運(yùn)作。,1993年前C+要求operator new在無法滿足內(nèi)存需 求時(shí)返回0,新標(biāo)準(zhǔn)則是拋出一個(gè)bad_alloc類型 異常。 失敗便轉(zhuǎn)為0的傳統(tǒng)被保留為“nothrow”不拋出異 常。頭文件中定義了一個(gè)nothrow對(duì)象 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時(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類型的異常。 不能覆蓋系統(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,找出目前的錯(cuò)誤處理函數(shù) set_new_handler(globalHandler); /重新設(shè)定為原本的函數(shù) if(globalHandler) (*globalHandler)( ) else throw std:bad_alloc( ); ,無窮循環(huán)可以讓更多的內(nèi)存可用, 或安裝一個(gè)不同的new_handler, 或卸載new_handler, 或拋出一個(gè)異常, 或直接結(jié)束程序。,operator new 可以被繼承, 但要小心,否則會(huì)導(dǎo)致問題,class Base public: static void*oprator new(size_t size); ; class Derived : public Base ;/導(dǎo)出類中沒有operator 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 delete,void operator delete(void*rawMemory) if(rawMemory = 0)return; /與C+標(biāo)準(zhǔn)delete保持一致 Deallocate the memory 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); /用標(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 (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; /唯一數(shù)據(jù)成員是指針 ;,Airplane *p=new Airplane; /要求內(nèi)存不大,分配的內(nèi)存比實(shí)際所需要的內(nèi)存要大, 這是為了delete 這塊內(nèi)存時(shí),系統(tǒng)能知道其大小。,pa,為了節(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; Airplane *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 Airplane *newBlock = static_cast(:operator new(BLOCK_SIZE*sizeof(Airplane); for(int i=1; iBLOCKSIZE-1;+i) /保留第一塊 newBlocki.next = ,只有當(dāng) :operator new失敗時(shí),這里的operator new 才失敗。這時(shí):operator new會(huì)調(diào)用new_handler直 到拋出異常,因此我們不需要再寫一次new_handler處理 具體實(shí)現(xiàn)文件中要先對(duì)靜態(tài)成員初始化, Airplane * Airplane:headOfFreeList; / headOfFreeList置0 const int Airplane:BLOCK_SIZE = 512; 這個(gè)版本的operator new 可以運(yùn)作良好,速度快過兩個(gè)數(shù)量級(jí)。,還要在Airplane類中寫一個(gè)operator delete void Airplane:operator delete(void*deadObject, size_t size) if(deadObject=0) return; if(size !=sizeof(Airplane) :operator delete(deadObject); /與operator new處理保持一致 return; Airplane *carcass = static_cast(deadObject); carcass-next =headOfFreeList; HeadOfFreeList = carcass; ,如果沒有定義相應(yīng)的delete函數(shù),而使用了原有的delete, 結(jié)果會(huì)出現(xiàn)意想不到的錯(cuò)誤,有時(shí)是嚴(yán)重的錯(cuò)誤。 如果用member版本不要忘記定義virtual 析構(gòu)函數(shù)。 這里的delete函數(shù)沒有memory leak 問題。 這是因?yàn)橛昧薽emory 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ì)象來配置內(nèi)存,當(dāng)被銷毀時(shí),配置的內(nèi)存自動(dòng)被釋放。 于是memory 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:operator 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)算用來產(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 char*value); String( ); /沒有拷貝構(gòu)造函數(shù), /也沒有賦值運(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;,Hello,World,a,b,data,data,由于沒有自定義的賦值函數(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ù)用來傳值, 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會(huì)造成問題。,解決的辦法就是自己定義拷貝構(gòu)造函數(shù),賦值函數(shù)重載。 如果確信永不使用這些函數(shù),把他們定義為私有函數(shù),而且不實(shí)現(xiàn)。 一旦出錯(cuò),編譯器會(huì)給出錯(cuò)誤提示。,12構(gòu)造函數(shù)中盡量以初始化代替賦值,一個(gè)類中的const成員數(shù)據(jù)和reference引用數(shù)據(jù)只能被初始化,不能被賦值。 即便沒有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 ManyDataMbs,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 ,靜態(tài)數(shù)據(jù)成員static class member不應(yīng)該在構(gòu)造函數(shù)中初始化。 靜態(tài)數(shù)據(jù)成員只能初始化一次,不能初始化多次。,12. 數(shù)據(jù)成員初始化的次序應(yīng)該和類內(nèi)聲明的次序相同,template class 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; /上下界 ;,template Array:Array(int lowBound, int highBound) : size(highBound-lowBound+1), lBound(lowBound), hBound(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 /靜態(tài)成員初始化為0,放在類外,class EnemyTank : public EnemyTarget public: EnemyTank ( )+ numTanks; EnemyTank (const EnemyTank /敵方坦克計(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ù)。 沒有虛函數(shù)的類,有繼承派生類對(duì)象析構(gòu),也要定義虛析構(gòu)函數(shù)。 但虛函數(shù)會(huì)增加內(nèi)存開銷。完全不必要時(shí)不要用虛析構(gòu)函數(shù)。 聲明一個(gè)抽象類,可以加一個(gè)純虛析構(gòu)函數(shù)。,15. 讓operator=返回*this的引用reference,C語言中operator= 的原型 C z.operator=的返回值是y.operator=的實(shí)參。他們應(yīng)該有相同的類型。 但不要讓operator=返回void類型,const類型,Strin,16. 在operator=中為所有的數(shù)據(jù)成員賦值,基類中這不成問題,在派生類中要小心。 正確的賦值運(yùn)算 Derived ,Derived 拷貝構(gòu)造函數(shù)中要調(diào)用基類構(gòu)造函數(shù)。 用第一種方法,在operator=中檢查是否“自己賦值給自己”,class X; X a; X /自己賦值給自己 合法 在賦值函數(shù)中要特別謹(jǐn)慎的處理自己的別名賦值給自己的問題。,提高效率 先做檢查,一發(fā)現(xiàn)自己賦值給自己立即返回。導(dǎo)出類的賦值運(yùn)算重載中一定要先檢查,可以節(jié)省許多工作 確保正確性 賦值運(yùn)算通常要先將左邊對(duì)象的資源釋放,再行賦值。如果有自己賦值給自己的現(xiàn)象,這個(gè)資源可能丟失,不可挽回了。,如何判斷兩個(gè)對(duì)象是同一個(gè)對(duì)象? 不是對(duì)象的內(nèi)容相同,而是看他們的地址是否相同。 X aliasing問題不限于賦值運(yùn)算內(nèi),只要用到指針或引用,就可能出現(xiàn)。這時(shí)我們就要當(dāng)心,不要誤刪了有用的資源。,類和函數(shù)的設(shè)計(jì)和申明,設(shè)計(jì)一個(gè)高效率的類型(class 型別), 必須先回答下列問題 對(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ù)必須禁用 放到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 /傳回?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*a;/出錯(cuò) 只能聲明為非成員函數(shù) const complex operator*(const complex,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)置常字符串 const chr *p; char const *p; /意義相同,函數(shù)中const 可以修飾傳回值,參數(shù),成員函數(shù)時(shí)甚至可以修飾整個(gè)函數(shù)。,函數(shù)返回值用 const, 可以改善函數(shù)的安全性,和效率。 T a2=b;/正確,const T /正確 a2=b;/錯(cuò)誤,const complex operator*(const complex /不允許,參數(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) 新增保留字 mutable class A public: int length( )const; private: mutable int size;/可以在任何地點(diǎn)被改動(dòng), /即使在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: string 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)成本,改用引用參數(shù)傳址by reference const student 引用參數(shù)傳址by reference 不調(diào)用任何構(gòu)造函數(shù)析構(gòu)函數(shù)。 虛函數(shù)的引用參數(shù)是基類時(shí),實(shí)際傳入派生類對(duì)象時(shí)可以調(diào)用派生類的函數(shù)。 傳值參數(shù)沒有這樣的功能。 引用參數(shù)要注意別名(aliasing)問題。,23當(dāng)你必須傳回objebct(傳值)時(shí)不要傳址(引用),盡可能讓事情簡(jiǎn)單,但不要過于簡(jiǎn)單。 A.Einstein 盡可能讓事情有效率,但不要過于有效率。 C+ 函數(shù)必須傳回一個(gè)對(duì)象,就不要傳址不要返回引用。 不能傳回一個(gè)不存在的地址,不能傳回函數(shù)中產(chǎn)生的局部對(duì)象的地址。,const complex operator*(const complex ,& 錯(cuò)誤。返回值地址指向局部對(duì)象,與局部對(duì)象同名,運(yùn)算執(zhí)行完畢,局部對(duì)象被析構(gòu),返回值指向一個(gè)不存在的地址。,const complex 指針temp被析構(gòu),內(nèi)存已丟失。,const complex 如何析構(gòu)這幾個(gè)operator*中間產(chǎn)生的temp指針呢?,24函數(shù)重載和參數(shù)缺省之間,謹(jǐn)慎抉擇,函數(shù)重載和參數(shù)缺省之間容易引起混淆。 如果可以選擇一個(gè)合理的默認(rèn)值,并且只需要一種算法,最好使用缺省參數(shù)。 否則使用重載函數(shù)。,例:求五個(gè)整數(shù)的最大值 #include int 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( ),) 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;/無類型指針 f(NULL);/錯(cuò)誤 類型不符 #define NULL 0 f(NULL);/調(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; classA public: A(const B/錯(cuò)誤 模棱兩可 兩種方法哪種更好?,void f(int); void f(char); double d=6.02; f(d);/模棱兩可 模棱兩可可以潛伏很久,直到爆發(fā)。,多繼承最容易引發(fā)模棱兩可。 class B public: B doit( ); ; class C public: C doit( ); /放在私有成員中同樣不行 ; class Derived :public B, public C ; Derived d; d.doit( );/模棱兩可 d.B:doit( );/正確 d.C;doit( );/正確,27如果不想使用編譯器暗自產(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)的名字。,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;/沒問題只用一次 double d=BOOK_VERSION;/錯(cuò)誤 Handle h=getHandle( );/錯(cuò)誤 ,兩個(gè)namespace中有相同的名字,只要標(biāo)明namespace域名即可區(qū)分。 using namespace stm; using namespace sdm; stm:;BOOK_VERSION; sdm:BOOK_VERSION;,類與函數(shù)的實(shí)現(xiàn) 29避免傳回內(nèi)部數(shù)據(jù)的handles,class 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!”); char * str = B;/str 與 B.data指向同一個(gè)地址 strcpy(str, “I love you?”);/改變str, 也就改變了B.data 字符串常量B 被改變。,去掉operator char*( )const;中 const , 可以令常量B不能調(diào)用operator char *( ); 但String 便不能轉(zhuǎn)換成char*. 安全的做法是: inline String:operator char *( )const char* copy = new charstrlen(data)+1; strcpy(copy, data); return copy; 這個(gè)函數(shù)比較慢,可能產(chǎn)生內(nèi)存丟失。,class String public: String(const char *value); String( ); operator const char *( ) const; private: char * data; ; inline String:operator const char *( )const return data; /又快又安全 傳回一個(gè)常量指針,不可改變。,引用也可能發(fā)生傳回內(nèi)部數(shù)據(jù)的handle問題。 class String public: char /改變了常字符串,編譯器沒發(fā)現(xiàn) 問題出在const 函數(shù)返回值是引用。,即使不是const 函數(shù),“傳回handles”也是有問題的。 /隨機(jī)選擇一個(gè)作家的名字 String someFamousAuthor( ) switch( rand( )%3) /隨機(jī)函數(shù) case 0: return “Margaret Mitchell”; /飄的作者 case 1: return “Stephen King”; /著名通俗小說家 case 3: return “Scott Meyers” /本書作者 return “ ”; ,這個(gè)函數(shù)的返回值是一個(gè)局部指針handle,函數(shù)用畢被銷毀 因此返回值將是無定義的內(nèi)存(dangling pointer)。 盡可能避免讓一個(gè)函數(shù)傳回dangling handles,可以提高程序的可靠性。,避免寫出成員函數(shù)返回一個(gè) non-const pointer或reference(引用) 并以之指向較低存取層級(jí)的成員,class Address; class Person public: address /私有數(shù)據(jù)成員 ;,Person scott(); Address /全局對(duì)象addr與scott.address 同一地址 私有成員公開化,可以從外部改變私有數(shù)據(jù)的值。 變成指針也有同樣的問題。,31千萬不要返回“函數(shù)內(nèi)局部對(duì)象的reference”或“函數(shù)內(nèi)以new獲得的指針?biāo)傅膶?duì)象”,“傳回一個(gè)引用(reference),指向局部對(duì)象”,函數(shù)用畢返回時(shí),局部對(duì)象被析構(gòu),變成引用一個(gè)不存在的對(duì)象。 const complex 錯(cuò)誤。返回值地址指向局部對(duì)象,與局部對(duì)象同名,運(yùn)算執(zhí)行完畢,局部對(duì)象被析構(gòu),返回值指向一個(gè)不存在的地址。,class Address; class Person public: address /指針addr指向scott.address 同一地址 只要把函數(shù)返回值改成const類型即

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(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)論