日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

[转载]高质量c/c++编程指南读书笔记

發(fā)布時間:2023/12/20 c/c++ 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [转载]高质量c/c++编程指南读书笔记 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一個strcpy函數(shù)的代碼 能考查三個方面 (1) 編程風格 (2) 出錯處理 (3) 算法復(fù)雜度分析(用于提供性能)

定義編程老手和編程高手 定義1:能長期穩(wěn)定地編寫出高質(zhì)量程序的程序員稱為編程老手 定義2:能長期穩(wěn)定地編寫出高難度、高質(zhì)量的程序與稱為編程高手

?

第一章 文件結(jié)構(gòu)

1.1 版權(quán)和版本的聲明 版權(quán)和版本的聲明位于頭文件和定義文件的開頭,主要內(nèi)容有: (1) 版權(quán)信息 (2) 文件名稱、標識符、摘要 (3) 當前版本號、作者/修改者、完成日期 (4) 版本歷史信息

/* * Copyright (c) 2001,上海貝爾有限公司網(wǎng)絡(luò)應(yīng)用事業(yè)部 * All rights reserved. *? * 文件名稱:filename.h * 文件標識:見配置管理計劃書 * 摘??? 要:簡要描述本文件的內(nèi)容 *? * 當前版本:1.1? * 作??? 者:輸入作者(或修改者)名字 * 完成日期:2001年7 月20日 * * 取代版本:1.0?? * 原作者? :輸入原作者(或修改者)名字 * 完成日期:2001年5 月10日 */

1.2 頭文件結(jié)構(gòu) ??? 頭文件由三部分內(nèi)容組成 (1) 頭文件開頭處的版權(quán)和版本聲明 (2) 預(yù)處理塊 (3) 函數(shù)和類結(jié)構(gòu)聲明等 ??? 為了防止頭文件被重復(fù)用in用,應(yīng)當用ifndef/define/endif結(jié)構(gòu)產(chǎn)生預(yù)處理塊 ??? 頭文件中只存放"聲明"而不存放"定義" ? 在C++語法中,類的成員函數(shù)可以在聲明的同時被定義,并且自動成為內(nèi)聯(lián)函數(shù)。這雖然會帶來書寫上的方便,但卻造成了風格不一致,弊大于利。建議將成員函數(shù)的定義與聲明分開,不論該函數(shù)體有多么小。 ??? 不提倡使用全局變量,盡量不要再頭文件中出現(xiàn)像extern int value這類聲明

1.3 頭文件的作用 (1) 通過頭文件來調(diào)用庫功能。在很多場合,源代碼不便(或不準)向用戶公布,只要向用戶提供頭文件和二進制庫即可。用戶只需要按照頭文件中的接口聲明來調(diào)用庫功能,而不必關(guān)系接口怎么實現(xiàn)的。編譯器會從庫中提取相應(yīng)的代碼。

(2) 頭文件能加強類型安全監(jiān)察。如果某個接口被實現(xiàn)或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一見到的規(guī)則能大大減輕程序員調(diào)試、改錯的負擔。

1.4 目錄結(jié)構(gòu) ??? 如果一個軟件的頭文件數(shù)目比較多(如超過十個),通常應(yīng)將頭文件和定義文件分別保存于不同的目錄,以便于維護。 ??? 例如可將頭文件保存于include目錄,將定義文件保存于source目錄(可以是多級目錄)。 ??? 如果某些頭文件是私有的,他不會被用戶的程序直接引用,則沒有必要公開其"聲明"。為了加強信息隱藏,這些私有的頭文件可以和定義文件存放在同一目錄。

?

第二章 程序的版式 ??? 版式雖然不會影響程序的功能,但會影響可讀性。程序的版式追求清晰、美觀,是程序風格的重要構(gòu)成因素。

2.1 空行 ??? 空行起著分隔程序段落的作用。空行得體(不過多也不過少)將使程序的布局更加清晰。空行不會浪費內(nèi)存,雖然打印含有空行的程序是會多消耗一些紙張,但是值得。 (1) 在每個類聲明之后、每個函數(shù)定義結(jié)束之后都要加空行 void Function1(...) {}

vodi Function2(...) {}

(2) 在一個函數(shù)體內(nèi),邏輯上密切相關(guān)的語句之間不加空行,其他地方應(yīng)加空行分隔 while( flag ) { ??? statement1; ??? // 空行 ??? if( condition ) ??? { ?????? statement2; ??? } ??? else ??? { ?????? statement3; ??? } ??? // 空行 ??? statement4; }

2.2 代碼行 (1) 一行代碼只做一件事情,如之定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,并且方便于寫注釋 int width; int height;

(2) if、for、while、do等語句自占一行,執(zhí)行語句不得緊跟其后。不論執(zhí)行語句有多少都要加{}。這樣可以防止書寫失誤

2.3 代碼行內(nèi)的空格 (1) 關(guān)鍵字之后要留空格。像const、virtual、inline、case等關(guān)鍵字之后至少要留一個空格,否則無法辨析關(guān)鍵字。想if、for、while等關(guān)鍵字之后應(yīng)留一個空格再跟左括號'(',以突出關(guān)鍵字

(2) 代碼行最大長度宜控制在70至80個字符以內(nèi)。代碼行不要過長,否則眼睛看不過來,也不便于打印

(3) 長表達式要在低優(yōu)先級操作符處拆分成新航,操作符放在新行之首(以便突出操作符)。拆分出的新行要進行適當?shù)目s進,使排版整齊,語句刻度 if (( very_longer_variable1 >= very_longer_variable2 ) ?? && (very_longer_variable3 <= very_longer_variable4 ) ?? && (very_longer_variable5 <= very_longer_variable6 )) {...}

2.4 修飾符的位置 ??? 應(yīng)當將修飾符*和&緊靠變量名

2.5 注釋 ??? C語言的注釋符為"/*...*/"。C++語言中,程序塊的注釋常采用"/*...*/",行注釋一般采用"//..."。注釋通常用于: 版本、版權(quán)聲明 函數(shù)接口說明 重要的代碼行或段落提示 ??? 雖然注釋有助于理解代碼,但注意不可過多地使用注釋 (1) 注釋是對代碼的“提示”,而不是文檔。程序中的注釋不可喧賓奪主,注釋太多了會讓人眼花繚亂。注釋的花樣要少

(2) 如果代碼本來就是清楚的,則不必加注釋。否則多此一舉,令人厭煩例如 ??? i++; // i加1,多余的注釋

(3) 邊寫代碼邊注釋,修改代碼同時修改相應(yīng)的注釋,以保證注釋與代碼的一致性。不再有用的注釋要刪除 /* *? 函數(shù)介紹: *? 輸入?yún)?shù): *? 輸出參數(shù): *? 返回值? : */ void Function(float x, float y, float z) { ? … }

2.6 類的版式 ??? 類可以將數(shù)據(jù)和函數(shù)封裝在一起,其中函數(shù)表示了類的行為(或稱服務(wù))。類提供關(guān)鍵字public、protected和private。這樣可以達到信息隱藏的目的,即讓類僅僅公開必須要讓外界知道的內(nèi)容,而隱藏其他一切內(nèi)容。不可以濫用類的封裝功能,不要把它當成火鍋,什么東西都往里扔。 ??? 將public類型的函數(shù)寫在前面,而將private類型的數(shù)據(jù)寫在后面,采用這種版式的程序員主張類的設(shè)計“以行為為中心”,重點關(guān)注的是類應(yīng)該提供什么樣的接口(或服務(wù)) class A { ? public: ? void Func1(void); ? void Func2(void); ? … private: ? int??? i, j; ? float? x, y; ? … }

?

第三章 命名規(guī)則 ??? 比較著名的命名規(guī)則當推MS公式的“匈牙利”法,該命名規(guī)則的主要思想是“在變量和函數(shù)名中加入前綴以增進人們對程序的理解”。例如所有的字符變量均以ch為前綴,若是指針變量則追加前綴p。如果一個變量有ppch開頭,則表明它是指向字符指針的指針。 ??? 匈牙利法最大缺點是繁瑣

3.1 共性規(guī)則 (1) 標識符應(yīng)當直觀且可以品讀,可望文知意,不必進行“解碼” 標識符最好采用英文單詞或其組合,便于記憶和閱讀。切忌使用漢語拼音來命名。程序中的英文檔次一般不會太復(fù)雜,用詞應(yīng)當準確。例如不要把CrurentValue寫成NowValue

(2) 標識符的長度應(yīng)當符合“min-length” && “max-information”原則。 幾十年前老ANSI C規(guī)定名字不準超過6個字符,現(xiàn)今的C++/C不再有此限制。一般來說,長名字能更好地表達含義,所以函數(shù)名、變量名、類名長達十幾個字符不足為奇。那么名字是否越長越好?不見得!例如變量名maxVal就比maxValueUntilOverflow好用。大字符的名字也是有用的,常見的如i,jk等,它們通常可以用作函數(shù)內(nèi)的局部變量

(3) 命名規(guī)則盡量與所采用的操作系統(tǒng)或開發(fā)工具的風格保持一致。例如windows應(yīng)用程序的標識符通常采用“大小寫”混排的方式,如AddChild,而Unix應(yīng)用程序的標識符通常采用“小寫加下劃線”的方式,如add_child。別把這兩類風格混在一起用。

(4) 程序中不要出現(xiàn)緊靠大小寫區(qū)分的相似的標識符。例如 int x, X; // 變量x與X容易混淆

(5) 變量的名字應(yīng)當使用“名詞”或者“形容詞+名詞”。例如 float value; float oldValue; float newValue;

(6) 全局函數(shù)的名字應(yīng)當使用“動詞”或者“動詞+名詞”(動賓詞組)。類的成員函數(shù)應(yīng)當只使用“動詞”,被省略掉的名詞就是對象本身。例如 DrawBox();?? // 全局函數(shù) bos->Draw(); // 類的成員函數(shù)

3.2 簡單的windows應(yīng)用程序命名規(guī)則 (1) 類名和函數(shù)名用大寫字母開頭的單詞組合而成,例如: class Node;???? // 類名 class LeadNode; // 類名 void Draw( void );????????? // 函數(shù)名 void SetValue( int value ); // 函數(shù)名

(2) 變量和參數(shù)用小寫字母開頭的單詞組合而成 BOOL flag; int drawNode;

(3) 常量全用大寫的字母,用下劃線分隔單詞,例如: const int MAX = 100; const int MAX_LENGTH = 100;

(4) 靜態(tài)變量加前綴s_(表示static),例如: vodi Init(...) { ??? static int s_initValue; // 靜態(tài)變量 }

(5) 如果不得已需要全局變量,則使全局變量加前綴g_(表示global),例如: int g_howManyPeople; // 全局變量 int g_howMuchMoney;? // 全局變量

(6) 類的數(shù)據(jù)成員加前綴m_(表示member),這樣可以避免數(shù)據(jù)成員與成員函數(shù)的參數(shù)同名。例如: void Object:L:SetValue( int width, int height ) { ??? m_width = width; ??? m_height = height; }

?

第四章 表達式和基本語句 4.1 if語句 a、布爾變量與零值比較 ??? 不可將不二變量直接與TRUE、FALSE或者1、0進行比較 ??? 根據(jù)布爾類型的語義,零值為“假”(記為FALSE),任何非零值都是“真”(記為TRUE)。TRUE的值究竟是什么病沒有統(tǒng)一的標準。例如Virtual C++將TRUE定義為1,而VB則就愛那個TRUE定義為-1 假設(shè)布爾變量名字為flag,它與零值比較的標準if語句如下: if ( flag )? // 表示flag為真 if ( !flag ) // 表示flag為假 其他的用法都屬于不良風格,例如: if ( TRUE == flag ) if ( 1 == flag )

b、整型變量與零值比較 if ( 0 == value ) if ( 0 != value )

c、浮點變量與零值比較 ??? 不可將浮點變量用"=="或"!="與任何數(shù)字比較 ??? 千萬要留意,無論是float還是double類型的表露,都有精度限制。所以一定要避免將浮點變量用"=="或"!="與數(shù)字比較,應(yīng)該設(shè)法轉(zhuǎn)化成">="或"<="形式 假設(shè)浮點變量的名字為x,應(yīng)當將 if ( 0.0 == x ) // 隱含錯誤的比較 轉(zhuǎn)化為 if ( ( x >= -ESPINON) && ( x <= EPSINON) ) 其中EPSINON是允許的誤差(即精度)

d、指針變量與零值比較 if ( NULL == p ) if ( NULL != p )

4.2 循環(huán)語句的效率 C/C++循環(huán)語句中,for語句使用頻率最高,while語句其次,do語句很少用。

a、在多重循環(huán)中,如果有可能,應(yīng)當將最長的循環(huán)放在最內(nèi)層,最短的循環(huán)放在最外層,以減少CPU跨切循環(huán)層的次數(shù)。例如 for ( row = 0; row < 100; row ++) { ??? for ( col = 0; col < 5; col++ ) ??? { ??????? sum = sum + a[ row ][ col ]; ??? } } 這里的效率就比較低:長循環(huán)在最外層,應(yīng)改成: for ( col = 0; col < 5; col++ ) { ??? for ( row = 0; row < 100; row ++) ??? { ??????? sum = sum + a[ row ][ col ]; ??? } }

b、如果循環(huán)體內(nèi)存在邏輯判斷,并且循環(huán)次數(shù)很大,宜將邏輯判斷移到循環(huán)體的外面。比如(1)中的程序比(2)的程序多執(zhí)行了N-1次邏輯判斷。并且由于前者老妖進行邏輯判斷,打斷了循環(huán)“流水線”作業(yè),是的編譯器不能對循環(huán)進行優(yōu)化處理,降低了小路。如果N非常大,最好采用(2)的寫法,可以提高小路。如果N非常小,兩者效率差別并不明顯,但采用(1)的寫法比較好,因為程序更加簡潔 (1) for ( i = 0; i < N; i++ ) { ??? if ( condition ) ??? { ??????? DoSomething(); ??? } ??? else ??? { ??????? DoOtherthing(); ??? } } // 效率低但程序簡潔 (2) if ( condition ) { ??? for ( i = 0; i < N; i++ ) ??? { ??????? DoSomething(); ??? } } else { ??? for ( i = 0; i < N; i++ ) ??? { ??????? DoOtherthing(); ??? } } // 效率高但程序簡潔

4.3 for語句的循環(huán)控制變量 建議for語句的循環(huán)控制變量的取值采用“半開半閉區(qū)間”寫法 (1) for ( int i = 0; i < N; i++ ) {...} x值屬于半開半閉區(qū)間“0 =< x < N”,起點到終點的間隔為N,循環(huán)次數(shù)為N (2) for ( int i = 0 ; i <= N -1; i++ ) {...} x值屬于閉區(qū)間“0 =< x <= N-1”,起點到終點的間隔為N-1,循環(huán)次數(shù)為N 相比之下(1)的寫法更加直觀,盡管兩者的功能是相同的

?

第五章 常量 ??? 常量是一種標識符,它的值在運行期間恒定不變。C語言用#define來定義常量(成為宏常量)。C++語言除了#define外還可以用const來定義常量(成為const常量)

5.1 為什么需要常量 ??? 如果不適用常量,直接在程序中填寫數(shù)字或字符串,將會有什么麻煩? (1) 程序的可讀性(可理解性)變差。程序員自己會放假那些數(shù)字或字符串是什么意思,用戶則更加不知他們從何而來、表示什么 (2) 在程序的很多地方輸入同樣的數(shù)字或字符串,難保不發(fā)生書寫錯誤 (3) 如果要修改數(shù)字或字符串,則會在很多地方改動,既麻煩又容易出錯

5.2 const與#define的比較 ??? C++語言可以用const來定義常量,也可以用#define來定義常量。但是前者比后者有更多的有點: (1) const常量有數(shù)據(jù)類型,而宏常量沒有數(shù)據(jù)類型。編譯器可以對前者進行類型安全監(jiān)察。而對后者只進行字符替換,沒有類型安全監(jiān)察,并且在字符替換可能會產(chǎn)生意料不到的錯誤(邊界效應(yīng)) (2) 有些集成化調(diào)試工具可以對const常量進行調(diào)試,但是不能對宏常量進行調(diào)試

5.3 類的常量 ??? 不能在類聲明中初始化const數(shù)據(jù)成員。以下用法是錯誤的,因為累的對象未被創(chuàng)建時,編譯器不知道SIZE的值是什么 class Test_A { ??? ... private: ??? const int SIZE = 100; // 錯誤,企圖在類聲明中初始化const數(shù)據(jù)成員 ??? int array[ SIZE ];??? // 錯誤,未知而SIZE };

const數(shù)據(jù)成員的初始化只能在類構(gòu)造函數(shù)的初始化表中進行,例如: class Test_A { public: ??? Test_A( int size );?? // 構(gòu)造函數(shù) ??? const int SIZE; };

Test_A::Test_A( int size) : SIZE( size ) // 構(gòu)造函數(shù)的初始化表 {}

怎樣才能簡歷在整個類中都恒定的常量呢?別指望const數(shù)據(jù)成員了,應(yīng)該用類中的枚舉常量來實現(xiàn)。例如: class Test_A { ??? ... public: ??? enum { SIZE1 = 100, SIZE2 = 200 }; // 枚舉常量 ??? int array1[ SIZE]; ??? int array2[ SIZE];?? } 枚舉常量不會占用對象的存儲空間,他們在編譯時被全部求職。枚舉常量的缺點是:他的隱含數(shù)據(jù)類型是整數(shù),其最大值優(yōu)先,且不能表示浮點數(shù)(如PI = 3.14159)

?

第六章 函數(shù)設(shè)計 6.1 參數(shù)的規(guī)則 a、參數(shù)額書寫要完整,不要貪圖省事致謝參數(shù)的類型而省略參數(shù)名字,如果函數(shù)沒有參數(shù),則用void填充,例如: void GetValue(void);

b、如果參數(shù)是指針,且僅作輸入用,則應(yīng)在類型前加const,以防止該指針在函數(shù)體內(nèi)被意外修改

c、如果輸入?yún)?shù)以值傳遞的方式傳遞對象,則宜改用“const &”方式來傳遞,這樣可以省去臨時對象的構(gòu)造和析構(gòu)過程,從而提高效率

6.2 返回值的規(guī)則 a、函數(shù)名字與返回值類型在語義上不可沖突 違反這條規(guī)則的典型帶便是C標準庫函數(shù)getchar,例如: char c; c = getchar(); if ( EOF == c ) {...} 按照getchar名字的意思,將變量c聲明為char類型是很自然的事情。但不幸的是getchar的確不是char類型,而是int類型,其原型如下: int getchar( void ); 由于c是char類型,取值范圍是[ -128, 127 ],如果宏EOF的值在char的取值范圍之外,那么if語句將總是失敗,這種“危險”人們一般不會料到,導(dǎo)致本例錯誤的責任是函數(shù)getchar誤導(dǎo)了使用者

b、不要將正常值和錯誤標識混在一起返回。正常值用輸出參數(shù)獲得,而錯誤標志用return語句返回 ??? 回顧上例,C標準庫函數(shù)的設(shè)計者為什么要將getchar聲明為令人迷糊的int類型? ??? 在正常情況下,getchar的確返回單個字符。但如果getchar碰到文件結(jié)束標志或發(fā)生讀錯誤,它必須返回一個標志EOF。為了區(qū)別于正常的字符,志豪將EOF定義為負數(shù)(通常為-1)。因此函數(shù)getchar就成了int類型。

c、有時候函數(shù)原本不需要返回值,但為了增加靈活性如支持鏈式表達,可以附加返回值 例如字符串拷貝函數(shù)strcpy的原型: char *strcpy( char *strDest, const char *strSrc ); strcpy函數(shù)將strSrc拷貝至輸出參數(shù)strDest中,同時函數(shù)的返回值又是strDest。這樣做并非多次一舉,可以獲得如下靈活性: char str[ 20 ]; int length = strlen( strcpy( str, "hello world" ));

6.3 函數(shù)內(nèi)部實現(xiàn)的規(guī)則 ??? 不同功能的函數(shù)其內(nèi)部實現(xiàn)各不相同,看起來似乎無法就“內(nèi)部實現(xiàn)”達成一致的觀點。但根據(jù)經(jīng)驗,可以在函數(shù)體的“入口處”和“出口處”從嚴把關(guān),從而提高函數(shù)的質(zhì)量 a、在函數(shù)體的“入口處”,對參數(shù)的有效性進行檢查 看6.5

b、在函數(shù)體的“出口處”,對return語句的正確性和效率進行檢查 如果函數(shù)有返回值,那么函數(shù)的“出口處”是return語句。如果return語句寫得不好,函數(shù)要么出錯,要么效率低下。 注意事項如下: (1) return語句不可反悔指向“棧內(nèi)存”的“指針”或者“引用”,因為該內(nèi)存在函數(shù)體結(jié)束時被自動銷毀,例如: char *Func( void ) { ??? char str[] = "hello world"; // str的內(nèi)存位于棧上 ??? return str;??? // 將導(dǎo)致錯誤 } (2) 如果函數(shù)返回值是一個對象,要考慮return語句的效率。例如: return String( s1 + s2 ); ??? 這是臨時對象的語法,表示“創(chuàng)建一個臨時對象并返回他”。不要以為他與“先創(chuàng)建一個局部對象temp并返回他的結(jié)果”是等價的,如: String temp( s1 + s2 ); return temp; 是指不然,上述代碼發(fā)生三件事。首先,temp對象被創(chuàng)建,同時完成初始化;然后拷貝夠咱函數(shù)吧temp拷貝到保存返回值存儲單元中;最后,temp在函數(shù)結(jié)束時被銷毀(調(diào)用析構(gòu)函數(shù))。然后“創(chuàng)建一個臨時對象并返回他”的過程是不同的,編譯器直接把臨時對象創(chuàng)建并初始化在外部存儲單元中,省去了拷貝和析構(gòu)的花費,提高了效率

6.4 其他建議 (1) 函數(shù)的功能要單一,不要涉及多用途的函數(shù) (2) 函數(shù)體的規(guī)模要小,盡量控制在50行代碼之內(nèi) (3) 函數(shù)中盡量少用static局部變量,除非必要 (4) 不僅要檢查輸入蠶食的有效性,還要檢查通過其他途徑進入函數(shù)體內(nèi)的變量的有效性,例如全局變量、文件句柄等 (5) 用于出錯處理的返回值一定要清楚,讓使用者不容易忽視或誤解錯誤情況

6.5 使用斷言 ??? 程序一般分為Debug版本和Release版本。 ??? 斷言assert是盡在Debug版本其作用的宏,他用于檢測“不應(yīng)該”發(fā)生的情況。 void *memory( void *pvTo, const void *pvFrom, size_t size ) { ??? assert( ( NULL != pvTo) && ( NULL != pvFrom ) ); // 使用斷言 ??? byte *pbTo = ( byte * )pvTo;???? // 防止改變pvTo的地址 ??? byte *pbFrom = ( byte * )pvFrom; // 防止改變pvFrom的地址 ??? while( size-- > 0 ) ??? { ??????? *pbTo++ = *pbFrom++; ??? } ??? return pvTo; } ??? 在函數(shù)的入口處,使用斷言檢查參數(shù)的有效性(合法性) ??? 在編寫函數(shù)時,要進行反復(fù)的考查,并且自問:“我打算做哪些假定?”,一旦確定了假定,就要使用斷言對假定進行檢查

6.6 引用于指針的比較 引用的一些規(guī)則 (1) 引用被創(chuàng)建的同事必須被初始化(指針則可以在任何時候被初始化) (2) 不能有NULL引用,引用必須與合法的存儲單元關(guān)聯(lián)(指針則可以是NULL) (3) 一旦引用被初始化,就不能改變引用的關(guān)系(指針則可以隨時改變所指的對象) ??? 指針能夠毫無約束地操作內(nèi)存中的任何東西,盡管指針功能強大,但是非常危險。就像一把刀,他可以用來砍樹、裁紙、修指甲、理發(fā)等等,誰敢這樣用? ??? 如果的確只需要借用一下某個對象的“別名”,那么就用“引用”,而不要用“指針”,以免發(fā)生意外。比如說,某人需要一份證明,本來在文件上蓋上公章的印子就行了,如果把取公章的鑰匙交給他,那么他就獲得了不該有的權(quán)利。

?

第七章 內(nèi)存管理 7.1 內(nèi)存分配方式 內(nèi)存的分配方式有三種: (1) 從靜態(tài)存儲區(qū)域分配。內(nèi)存在程序編譯的時候就已經(jīng)分配好,這塊內(nèi)存在程序的整個運行期間都存在。例如全局變量,static變量 (2) 在棧上創(chuàng)建。在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。棧內(nèi)存分配運算內(nèi)置于處理器的執(zhí)行集中,效率很高,但是分配的內(nèi)存容量有限 (3) 從堆上分配,亦稱動態(tài)內(nèi)存分配。程序在運行的時候用malloc或new申請任意多少的內(nèi)存,程序員自己負責在何時用free或delete釋放內(nèi)存。動態(tài)內(nèi)存的生存期由我們決定,使用非常靈活,但問題也最多

7.2 常見的內(nèi)存錯誤及其對策 (1) 內(nèi)存分配未成功,卻使用了它 ??? 常用的解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL。如果指針p是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert(NULL != p)進行檢查。如果是malloc或者new來申請內(nèi)存,應(yīng)該用if ( NULL == p )或if ( NULL != p )進行放錯處理

(2) 內(nèi)存分配雖然成功,但是尚未初始化就引用它 ??? 犯這種錯誤主要有兩個原因:一時沒有初始化的觀念;二是誤以為內(nèi)存的缺省初值全為零,導(dǎo)致引用初值錯誤(例如數(shù)組) ??? 內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標準,盡管有些時候全為零值,我們寧可信其無不可信其有。所以無論用何種方式創(chuàng)建數(shù)組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩

(3) 釋放了內(nèi)存卻繼續(xù)使用它 ??? 有三種情況: a、程序中的對象調(diào)用關(guān)系過于復(fù)雜,是在難以搞清楚某個對象究竟是否已經(jīng)釋放了內(nèi)存。此時應(yīng)該重新設(shè)計數(shù)據(jù)結(jié)構(gòu),從根本上解決對象管理的混亂局面 b、函數(shù)的return語句寫錯了,注意不要返回指向“棧內(nèi)存”的“指針”或者“引用”,因為該內(nèi)存在函數(shù)體結(jié)束時被自動銷毀 c、使用free或delete釋放了內(nèi)存后,沒有將指針設(shè)置為NULL。導(dǎo)致產(chǎn)生“野指針”

(4) 用free或delete釋放了內(nèi)存之后,立即將指針設(shè)置為NULL,防止產(chǎn)生“野指針”

7.3 指針與數(shù)組的對比 (1) 例 char *p = "hello world"; p[ 0 ] = 'X'; cout << p << endl; 指針p指向常量字符串"hello world"(位于靜態(tài)存儲區(qū),內(nèi)容為hello world\0),常量字符串的內(nèi)容是不可以被修改的。從語法上看,編譯器并不覺得語句p[ 0 ] = 'X'有什么不妥,但是該語句企圖修改常量字符串的內(nèi)容而導(dǎo)致運行錯誤

(2) 把數(shù)組中的數(shù)據(jù)賦給指針p int Length = strlen( a ); char *p = ( char *)malloc( sizeof( char ) * ( len + 1 ) ); strcpy( p, a ); if( 0 == strcmp( p, a ) ) 語句p = a并不能把a的內(nèi)容復(fù)制到指針p,而是把a的地址賦給了p。要像復(fù)制a的內(nèi)容,可以先用庫函數(shù)malloc為p申請一塊容量為strlen( a ) + 1個字符的內(nèi)容,再用strcpy進行字符串復(fù)制。同理,語句if( p == a )比較的不是內(nèi)容而是地址,應(yīng)該用庫函數(shù)strcmp來比較 注意:strcpy自動會給字符串的末尾加上\0;memcpy卻不會

一個例子: char str[ 6 ] = "world"; cout << strlen( str ) << endl; char *p = new char[ strlen( str ) + 1 ]; memcpy( p, str, strlen( str ) + 1 ); cout << p << endl; cout << strlen( p ) << endl;

另一個例子: char str[ 6 ] = "world"; cout << strlen( str ) << endl; char *p = new char[ strlen( str ) + 1 ]; strcpy( p, str ); cout << p << endl; cout << strlen( p ) << endl;

(3) 計算內(nèi)存容量 ??? 用運算符sizeof可以計算出數(shù)組的容量(字節(jié)數(shù))。但是sizeof( p )的值卻是4.這是因為sizeof( p )得到的是一個指針變量的字節(jié)數(shù),相當于sizeof( char * ),而不是p所指向的內(nèi)存容量。C/C++語言沒有辦法知道指針所指的內(nèi)存容量,除非在申請內(nèi)存時記住它。 ??? 注意當數(shù)組作為函數(shù)的參數(shù)繼續(xù)參數(shù)傳遞時,該數(shù)組自動退化為同類型的指針,例如 void Func( char a[ 100 ]) { ??? cout << sizeof(a ) << endl; // 4字節(jié)而不是100字節(jié)} 不論數(shù)組a的容量是多少,sizeof( a )始終是等于sizeof( char *)

(4) 指針參數(shù)是如何傳遞內(nèi)存的 ??? 如果函數(shù)的參數(shù)是一個指針,不要指望用該指針去申請動態(tài)內(nèi)存 void GetMemory( char *p, int num ) { ??? p = ( char * )malloc( sizeof( char ) * num ); } void Test( void ) { ??? char *str = { 0 }; ??? GetMemory( str, 100 );? // str仍然為NULL ??? strcpy( str, "hello" ); // 運行錯誤 } 毛病出在函數(shù)GetMemory中。編譯器總是要為函數(shù)的每個參數(shù)制作臨時副本,指針參數(shù)p的副本是_p,編譯器使_p = p。如果函數(shù)體內(nèi)的程序修改了_p的內(nèi)容,就導(dǎo)致參數(shù)p的內(nèi)容作相應(yīng)的修改。這就是指針可以用作輸出參數(shù)的原因。在上面的例子中,_p申請了新的內(nèi)存,只是_p所指的內(nèi)存地址改變了,但是p絲毫未變。所以函數(shù)GetMemory并不能輸出任何東西。事實上,每執(zhí)行一次GetMemory就會泄露一塊內(nèi)存,因為沒有用free釋放掉。

??? 如果非得要用指針參數(shù)去申請內(nèi)存,那么應(yīng)該改用“指向指針的指針” void GetMemory( char **p, int num ) { ??? *p = ( char * )malloc( sizeof( char ) * num ); } void Test( void ) { ??? char *str = { 0 }; ??? GetMemory( &str, 100 ); ??? strcpy( str, "hello" ); ??? free( str ); }

??? 由于“指向指針的指針”這個概念不容易理解,我們可以用函數(shù)返回值i來傳遞動態(tài)內(nèi)存。這種方法更加簡單: char *GetMemory( int num ) { ??? char *p = ( char * )malloc( sizeof( char ) * num ); ??? return p; } void Test( void ) { ??? char *str = { 0 }; ??? str = GetMemory( 100 ); ??? strcpy( str, "hello" ); ??? free( str ); }

??? 用函數(shù)返回值來傳遞動態(tài)內(nèi)存這種方法雖然好用,但是常常有人把return語句用錯了。這里強調(diào)不要用return語句返回指向“棧內(nèi)存”的指針,因為該內(nèi)存在函數(shù)結(jié)束時自動消亡: char *GetString( void ) { ??? char p[] = "hello world"; ??? return p; // 編譯器將發(fā)出警告 } void Test( void ) { ??? char *str = NULL; ??? str = GetString(); // str的內(nèi)容是垃圾 ??? cout << str << endl; } 用調(diào)試器逐步跟蹤Test,發(fā)現(xiàn)執(zhí)行str = GetString語句后str不再是NULL指針,但是str的內(nèi)容不是"hello world"而是垃圾。

??? 如果改成如下: char *GetString( void ) { ??? char *p = "hello world"; ??? return p; // 編譯器將發(fā)出警告 } void Test( void ) { ??? char *str = NULL; ??? str = GetString(); // str的內(nèi)容是垃圾 ??? cout << str << endl; } 函數(shù)Test運行雖然不會出錯但是函數(shù)GetString的設(shè)計概念卻是錯誤的。因為GetString內(nèi)的"hello world"是常量字符串,位于靜態(tài)存儲區(qū),他在程序生命期內(nèi)恒定不變。無論什么時候調(diào)用GetString,他返回的始終是同一個"只讀"的內(nèi)存塊

(5) free和delete把指針怎么啦? ??? 別看free和delete的名字惡狠狠的(尤其是delete),他們只是把指針所指的內(nèi)存給釋放掉,但并沒有把指針本身干掉 char *p = new char[ 5 ]; strcpy( p, "zeng" ); cout << p << endl; delete p; if ( NULL !=? p ) { cout << "p is not NULL, p value is:" << p << endl; } 可以看到在delete之后if ( NULL !=? p )進行放錯處理并不起作用,因為即便p不是NULL指針,他也不指向合法的內(nèi)存塊

(6) 動態(tài)內(nèi)存會被自動釋放嗎? void Func( void ) { ??? char *p = ( char * )malloc( 100 ); // 動態(tài)內(nèi)存會自動釋放嗎? } ??? 當函數(shù)Func結(jié)束時,指針消亡了,并不表示它所指向的內(nèi)存會被自動釋放;內(nèi)存被釋放了,并不表示指針會消亡或者成了NULL指針。

(7) 杜絕“野指針” ??? “野指針”不是NULL指針,是指向“垃圾”內(nèi)存的指針。“野指針”的成因主要有兩種: a、指針變量沒有被初始化。任何賀子珍變量剛被創(chuàng)建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創(chuàng)建的同事應(yīng)當被初始化,要么將指針設(shè)置為NULL,要么讓他指向合法的內(nèi)存。例如: char *p = NULL; char *str = ( char * )malloc( 100 );

b、指針p被free或者delete之后,沒有被置為NULL,讓人誤以為p是個合法的指針

c、指針操作超越了變量的作用范圍。這種情況讓人防不勝防: class TestA { public: ??? void Func( void ){ cout << "Func of class TestA" << endl; } }; void Test( void ) { ??? A *p; ??? { ??????? A a; ??????? p = &a; // 注意a的生命期 ??? } ??? p->Func();? // p是“野指針” } 函數(shù)Test在執(zhí)行語句p->Func()時面對像a已經(jīng)消失,而p是指向a的,所以p就成了“野指針”。但是要論編譯器而定,留意指針的生命期即可

?

第八章 C++函數(shù)的高級特性 8.1 如果C++程序要調(diào)用已經(jīng)被編譯后的C函數(shù),該怎么辦? 假設(shè)某個C函數(shù)的聲明如下: void foo( int x, int y ); 該函數(shù)被C編譯器編譯后在庫中的名字為_foo,而C++編譯器則會產(chǎn)生像_foo_int_int之類的名字用來指出函數(shù)重載和類型安全連接。由于編譯后的名字不同,C++程序不能直接調(diào)用C函數(shù)。C++提供了一個C連接交換指定符號extern "C"來解決這個問題。例如: extern "C" { ??? void foo( int x, int y ); ??? ... // 其他函數(shù) } 或者寫成 extern "C" { ??? #include "myheader.h" ??? ... // 其他C頭文件 } 這就告訴C++編譯器,函數(shù)foo是個C連接,應(yīng)該到庫中找名字_foo而不是找_foo_int_int。C++編譯器開發(fā)商 依舊對C標準庫頭文件做了extern "C"處理,所以可以用#include直接引用這些頭文件

8.2 成員函數(shù)的重載、覆蓋與隱藏 (1) 重載與覆蓋 ??? 成員函數(shù)被重載的特征 a、相同的范圍(在同一個類中) b、函數(shù)名字相同 c、參數(shù)不同 d、virtual關(guān)鍵字可有可無

??? 覆蓋是指派生類函數(shù)覆蓋基類函數(shù),特征是: a、不同的范圍(分別位于派生類與基類) b、函數(shù)名字相同 c、參數(shù)相同 d、積累函數(shù)必須有virtual關(guān)鍵字

(2) 令人迷惑的隱藏規(guī)則 ??? 本來僅僅區(qū)別重載與覆蓋并不算困難,但是C++的隱藏規(guī)則使問題復(fù)雜性突然增加。這里“隱藏”是指派生類的函數(shù)屏蔽了與其同名的基類函數(shù),規(guī)則如下: a、如果派生類的函數(shù)與基類函數(shù)同名,但是參數(shù)不同。此時,不論有無virtual關(guān)鍵字,基類的函數(shù)將被影藏 b、如果派生類的函數(shù)與基類的函數(shù)同名,并且參數(shù)也相同,但是基類函數(shù)沒有virtual關(guān)鍵字。此時,基類的函數(shù)被隱藏 #include <iostream> class Base { public: ??? virtual void f( float x ){ cout << "Base::f( float ) " << endl; } ??????????? void g( float x ){ cout << "Base::g( float ) " << endl; } ??????????? void h( float x ){ cout << "Base::h( float ) " << endl; } }; class Derived : public Base { public: ??? virtual void f( float x ){ cout << "Derived::f( float ) " << endl; } ??????????? void g( int x ){ cout << "Derived::g( float ) " << endl; } ??????????? void h( float x ){ cout << "Derived::h( float ) " << endl; } }; void main( void ) { ??? Derived d; ??? Base *pb = &d; ??? Derived? *pd = &d;

??? pb->f( 3.14f ); // Derived::f( float ) 3.14 ??? pd->f( 3.14f ); // Derived::f( float ) 3.14

??? pb->g( 3.14f ); // Base::g( float ) 3.14 ??? pd->g( 3.14f ); // Derived::g( int ) 3

??? pb->h( 3.14f ); // Base::h( float ) 3.14 ??? pd->h( 3.14f ); // Derived::h( int ) 3 }

函數(shù)Derived::f( float )覆蓋了Base::f( float ) 函數(shù)Derived::g( float )隱藏了Base::g( float ),而不是重載 函數(shù)Derived::h( float )隱藏了Base::h( float ),而不是覆蓋 根據(jù)類的virtual很容易就能判斷出來

(3) 擺脫隱藏 ??? 隱藏規(guī)則引起來不少麻煩 class Base { public: ??? void f( int x ); }; class Derived : public Base { public: ??? void f( char *str ); }; void Test( void ) { ??? Derived *pd = new Derived; ??? pd->f( 10 ); // error } 語句pd->f( 10 )的本意是想調(diào)用函數(shù)Base::f( int ),但是Base::f( int )不行被Derived::f( char * )隱藏了。由于數(shù)字10不能被隱式地轉(zhuǎn)化為字符串,所以在編譯時出錯。 ??? 隱藏規(guī)則至少有兩個存在的理由: a、寫語句pd->f( 10 )的人可能真的想調(diào)用Derived::f( char * )函數(shù),只是他誤將函數(shù)參數(shù)寫錯了。有了隱藏規(guī)則,編譯器就可以明確指出錯誤,這未必不是好事。否則,編譯器會靜悄悄地將錯就錯,程序員將很難發(fā)現(xiàn)這個錯誤,留下禍根。 b、加入類Derived有多個基類(多重繼承),有時搞不清楚哪些基類定義了函數(shù)f。如果沒有隱藏規(guī)則,那么pd->f( 10 )可能會調(diào)用一個出乎意料的基類函數(shù)f,盡管隱藏規(guī)則看起來不怎么有道理,但它的確能消滅這些意外

如果要調(diào)用基類的函數(shù),需要使用域操作符Base::f( int );

8.3 參數(shù)的缺省值 ??? 參數(shù)缺省值的使用規(guī)則: (1) 參數(shù)缺省值只能出現(xiàn)在函數(shù)的聲明中,而不能出現(xiàn)在定義體重。例如: void Foo( int x = 0, int y = 0 ); // 正確,缺省值出現(xiàn)在函數(shù)的聲明中 void Foo( int x = 0, int y = 0 )? // 錯誤,缺省值出現(xiàn)在函數(shù)的定義體重 { ??? ... } 可能的原因: 一、函數(shù)的實現(xiàn)(定義)本來就與參數(shù)是否有缺省值無關(guān),所以沒有必要讓缺省值出現(xiàn)在函數(shù)的定義體重 二、參數(shù)的缺省值可能會改動,顯然修改函數(shù)的聲明比修改函數(shù)的定義要方便

8.4 不能被重載的運算符 ??? 在C++運算符集合中,有一些運算符是不允許被重載的。這種限制是出于安全方面的考慮,可防止錯誤和混亂 (1) 不能改變C++內(nèi)部數(shù)據(jù)類型(如int、float等)的運算符 (2) 不能重載'.',因為'.'在類中對任何成員都有意義,已成成為標準用法 (3) 不能重載目前C++運算符集合中沒有的符號,如#、@、$等。原因有兩點,一時難以理解,而是難以確定優(yōu)先級 (4) 對已存在的運算符進行重載時,不能改變優(yōu)先級規(guī)則,否則將引起混亂

8.5 內(nèi)聯(lián)函數(shù) ??? 對于任何內(nèi)聯(lián)函數(shù),編譯器在符號表里放入函數(shù)的聲明(包括名字、參數(shù)類型、返回值)。如果編譯器沒有發(fā)現(xiàn)內(nèi)聯(lián)函數(shù)存在錯誤,那么該函數(shù)的代碼也被放入符號表里。在調(diào)用一個內(nèi)聯(lián)函數(shù)時,編譯器首先檢查調(diào)用是否正確(進行類型安全檢查,或者進行自動類型串行,當然對所有的函數(shù)都一樣)。如果正確,內(nèi)聯(lián)函數(shù)的代碼就會直接替換函數(shù)調(diào)用,于是省去了函數(shù)調(diào)用的開銷。這個過程與預(yù)處理有顯著的不同,因為預(yù)處理器不能進行類型安全檢查,或者進行自動類型轉(zhuǎn)換。加入內(nèi)聯(lián)函數(shù)是成員函數(shù),對象的地址(this)會被放在合適的地方,這也是預(yù)處理器辦不到

(1) inline是一種“用于實現(xiàn)的關(guān)鍵字”,而不是一種“用于聲明的關(guān)鍵字”。 (1) 慎用內(nèi)聯(lián)內(nèi)聯(lián)能提供函數(shù)的執(zhí)行效率,但是內(nèi)聯(lián)是以代碼膨脹(復(fù)制)為代價,僅僅省去了函數(shù)調(diào)用的開銷,從而提高函數(shù)的執(zhí)行效率。如果執(zhí)行函數(shù)體內(nèi)代碼的時間,相比于函數(shù)調(diào)用的開銷較大,那么效率的收獲會很少。另一方面,每一次內(nèi)聯(lián)函數(shù)的的調(diào)用都要復(fù)制代碼,將使程序的總代碼量增大,消耗更多的內(nèi)存空間。以下情況不宜使用內(nèi)聯(lián): a、如果函數(shù)體內(nèi)的代碼比較長,使用內(nèi)聯(lián)將導(dǎo)致內(nèi)存消耗代價較高 b、如果函數(shù)體內(nèi)出現(xiàn)循環(huán),那么執(zhí)行函數(shù)體內(nèi)代碼的時機要比函數(shù)調(diào)用的開銷大

?

第九章 類的構(gòu)造函數(shù)、析構(gòu)函數(shù)與賦值函數(shù) 9.1 示例:類String的構(gòu)造函數(shù) String::String( const char *str ) { ??? if( NULL == str ) ??? { ??????? m_data = new char[ 1 ]; ??????? *m_date = '\0'; ??? } ??? else ??? { ??????? int length = strlen( str ); ??????? m_data = new char[ length + 1 ]; ??????? strcpy( m_data, str ); ??? } }

?

9.2 不要輕視拷貝構(gòu)造函數(shù)與賦值函數(shù) ??? 由于并非所有的對象都會使用拷貝構(gòu)造函數(shù)和賦值函數(shù),程序員可能對這兩個函數(shù)有些輕視。 (1) 如果不主動編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),編譯器將以“位拷貝”的方式自動生成缺省值的函數(shù)。倘若類中含有指針變量,那么這兩個缺省的函數(shù)就隱含了錯誤。 (2) 拷貝構(gòu)造函數(shù)和賦值函數(shù)非常容易混淆,常導(dǎo)致錯寫、錯用。拷貝構(gòu)造函數(shù)是在對象被創(chuàng)建時調(diào)用的,而賦值函數(shù)只能被已經(jīng)存在了的對象調(diào)用。 String a( "hello" ); String b( "world" ); String c = a; // 調(diào)用了拷貝構(gòu)造函數(shù),最好寫成c( a ); ?????? c = b; // 調(diào)用了賦值函數(shù) 第三個語句的風格較差,宜改寫成String c( a )以區(qū)別于第四個語句

?

9.3 類String的拷貝構(gòu)造函數(shù)與賦值函數(shù) // 拷貝構(gòu)造函數(shù) String::String( const String &other ) { ??? int length = strlen( other.m_data ); ??? m_data = new char[ length + 1]; ??? strcpy( m_data, other.m_data ); } // 賦值函數(shù) String & String::operator=( const String &other ) { ??? // 檢查自賦值 ??? if( this == &other ) ??? { ??????? return *this; ??? }

?

??? // 釋放原有的內(nèi)存資源 ??? delete[] m_data;

?

??? // 分配新的內(nèi)存資源,并復(fù)制內(nèi)容 ??? int length = strlen( other.m_data ); ??? m_data = new char[ length + 1]; ??? strcpy( m_data, other.m_data );

?

??? // 返回本對象的引用 ??? return *this; } 類String拷貝構(gòu)造函數(shù)與普通構(gòu)造函數(shù)的區(qū)別是:在函數(shù)入口處無需與NULL進行比較,這是因為“引用”不可能為NULL,而“指針”可以為NULL ??? 注意函數(shù)strlen返回的是有效字符串長度,不保護結(jié)束符'\0'。函數(shù)strcpy則連'\0'一起復(fù)制 ??? 返回本對象的引用,目的是為了實現(xiàn)像a = b = c注意的鏈式表達。不能寫成return other,因為不知道參數(shù)other的生命期。有可能other是個臨時對象,在賦值結(jié)束后它馬上消失,那么return other返回的將是垃圾

?

9.4 偷懶的辦法處理拷貝構(gòu)造函數(shù)與賦值函數(shù) ??? 只需將拷貝構(gòu)造函數(shù)與賦值函數(shù)聲明為私有函數(shù),不用編寫代碼,例如: class A { private: ??? A( const A &a );????????????? // 私有的拷貝構(gòu)造函數(shù) ??? A & operator =( const A &a ); // 私有的賦值函數(shù) }; 如果試圖編寫: A b( a ); // 調(diào)用了私有的拷貝構(gòu)造函數(shù) b = a;??? // 調(diào)用了私有的賦值函數(shù) 編譯器將指出錯誤,因為外界不可以操作A的私有函數(shù)

?

9.5 如何在派生類中實現(xiàn)類的基本函數(shù) ??? 基類的構(gòu)造函數(shù)、析構(gòu)函數(shù)、賦值函數(shù)都不能被派生類繼承。如果類之間存在繼承關(guān)系,在編寫上述基本函數(shù)時應(yīng)注意: a、派生類的構(gòu)造函數(shù)應(yīng)在其初始化表里調(diào)用基類的構(gòu)造函數(shù) b、基類與派生類的析構(gòu)函數(shù)應(yīng)該為虛(即加virtual關(guān)鍵字) c、在編寫派生類的賦值函數(shù)時,不要忘記對基類的數(shù)據(jù)成員重新賦值 class Base { public: ??? ... ??? Base & operator =( const Base &other ); // 類Base的賦值函數(shù) private: ??? int m_i, m_j, m_k; }; class Derived : public Base { public: ??? ... ??? Derived & operator = ( const Derived &other ); // 類Derived的賦值函數(shù) private: ??? int m_x, m_y, m_z; };

?

Derived & Derived::operator = ( const Derived &other ) { ??? // 檢查自賦值 ??? if( this == &other ) ??? { ??????? return *this; ??? }

?

??? // 對基類的數(shù)據(jù)成員重新賦值 ??? Base::operator = ( other ); // 因為不能直接操作私有數(shù)據(jù)成員

?

??? // 對派生類的數(shù)據(jù)成員賦值 ??? m_x = other.m_x; ??? m_y = other.m_y; ??? m_z = other.m_z;

?

??? // 返回本對象的引用 ??? return *this; }

?

?

?

第十章 類的繼承與組合 10.1 繼承 (1) 如果類A和類B毫不相關(guān),不可以為了使B的功能更多些而讓B繼承A的功能和屬性 (2) 若在邏輯上B市A的“一種”( a kind of ),則允許B繼承A的功能和屬性。例如男人是人的一種,男孩是男人的一種。那么累Man可以從類Human派生,類Boy可以從類Man派生

?

10.2 組合 若在邏輯上A是B的“一部分”( a part of ),則不允許B從A派生,而是要用A和其他東西組合出B,例如眼睛(Eye)、鼻子(Nose)、口(Mouth)、耳朵(Ear)是頭(Head)的一部分,所以類Head應(yīng)該有類Eye、Nose、Mouth、Ear組合而成,不是派生而成 class Eye { public: ??? void Look( void ); }; class Nose { public: ??? void Smell( void ); }; class Mouth { public: ??? void Eat( void ); }; class Ear { public: ??? void Listen( void ); };

?

// 正確的Head設(shè)計,雖然代碼冗長 class Head { public: ??? void Look( void )? { m_eye.Look(); } ??? void Smell( void ) { m_nose.Smell(); } ??? void Eat( void )?? { m_mouth.Eat(); } ??? void Listen( void ){ m_ear.Listen(); } private: ??? Eye m_eye; ??? Nose m_nose; ??? Mouth m_mouth; ??? Ear m_ear; };

?

?

?

第十一章 其他編程經(jīng)驗 11.1 使用const提高函數(shù)的健壯性 const更大的魅力是它可以修飾函數(shù)的參數(shù)、返回值,甚至函數(shù)的定義提 (1) 用const修飾函數(shù)的參數(shù) ??? 如果參數(shù)做輸出用,不論它是什么數(shù)據(jù)類型,也不論他采用“指針傳遞”還是“引用傳遞”,都不能加const修飾,否則該參數(shù)將失去輸出功能 ??? const只能修飾輸入?yún)?shù): a、如果輸出參數(shù)采用“指針傳遞”,那么假const修飾可以防止意外地改動該指針,起到保護作用 b、如果輸入?yún)?shù)采用“值傳遞”,由于函數(shù)將自動產(chǎn)生臨時變量用于復(fù)制該參數(shù),該輸入?yún)?shù)本來就無須保護,所以不要加const修飾,例如不要將函數(shù)void Func1( int x )寫成void Func1( const int x ),同理不要將函數(shù)void Func2( A a )寫成void Func2( const A a )。其中A為用戶自定義數(shù)據(jù)類型 c、void Func2( A a )效率比較低可以使用A &a,但是注意要加上const;但如果將void Func1( int x )寫成void Func1( const int &x ),完全沒有必要,因為內(nèi)部數(shù)據(jù)類型的參數(shù)不存在構(gòu)造、析構(gòu)的過程,而復(fù)制也非常快,“值傳遞”和“引用傳遞”的效率幾乎相當

?

(2) 用const修飾函數(shù)的返回值 a、如果給以“指針傳遞”方式的函數(shù)返回值加const修飾,那么函數(shù)返回值(即指針)的內(nèi)容不能被修改,該返回值只能被賦給const修飾的同類型指針 例如函數(shù):const char *GetString( void ); 如下語句將會出現(xiàn)編譯錯誤 char *str = GetString(); 正確的用法是 const char *str = GetString(); b、如果函數(shù)返回值采用“值傳遞方式”,由于函數(shù)會把返回值復(fù)制到外部臨時的存儲單元中,加const修飾沒有任何價值 例如不要把函數(shù)int GetInt( void )寫成const int GetInt( void ) 同理不要把函數(shù)A GetA( void )寫成const A GetA( void ),其中A為用戶自定義數(shù)據(jù)類型 c、如果返回值不是內(nèi)部數(shù)據(jù)類型,將函數(shù)A GetA( void )改寫成const A & GetA( void )的確能提高效率。但此時千萬要小心,一定要搞清楚函數(shù)究竟是想返回一個對象的“拷貝”還是僅返回“別名”就可以了,否則程序會出錯

?

(3) const成員函數(shù) ??? 任何不會修改數(shù)據(jù)成員的函數(shù)都應(yīng)該聲明為const類型。如果在編寫const成員函數(shù)時,不慎修改了數(shù)據(jù)成員,或者調(diào)用了其他非const成員函數(shù),編譯器將指出錯誤,這無疑會提高成員的健壯性 class Stack { public: ??? void Push( int elem ); ??? int Pop( void ); ??? int GetCount( void ) const; // const成員函數(shù) private: ??? int m_num; ??? int m_data[ 100 ]; };

?

int Stack::GetCount( void ) const { ??? ++m_num; // 編譯錯誤,企圖修改數(shù)據(jù)成員m_num ??? Pop();?? // 編譯錯誤,企圖調(diào)用非const函數(shù) ??? return m_num; } const成員函數(shù)的聲明看起來很怪;const關(guān)鍵字只能放在函數(shù)聲明的尾部,大概是因為其他地方都已經(jīng)被占用了

?

11.2 提高程序的效率 ??? 程序的時間效率是指運行速度,空間效率是指程序占用內(nèi)存或者外存的狀況 (1) 不要一味地追求程序的效率,應(yīng)當在滿足正確性、可靠性、健壯性、可讀性等質(zhì)量因素的前提下,設(shè)法提高程序的效率 (2) 在優(yōu)化程序的效率時,應(yīng)當先找出限制效率的“瓶頸”,不要再無關(guān)緊要之處優(yōu)化 (3) 先優(yōu)化數(shù)據(jù)結(jié)構(gòu)和算法,再優(yōu)化執(zhí)行代碼 (4) 不要追求緊湊的代碼,因為緊湊的代碼并不能產(chǎn)生高效的機器碼

?

11.3 一些有益的建議 (1) 當心數(shù)據(jù)類型轉(zhuǎn)換發(fā)生錯誤。盡量使用顯示的數(shù)據(jù)結(jié)構(gòu)類型轉(zhuǎn)換(讓人們知道發(fā)生了什么事),避免讓編譯器悄悄地進行隱式的數(shù)據(jù)類型轉(zhuǎn)換 (2) 避免編寫技術(shù)性很高的代碼 (3) 不要設(shè)計面面俱到、非常靈活的數(shù)據(jù)結(jié)構(gòu) (4) 如果原有的代碼直來那個比較好,盡量復(fù)用他。但是不要修補很差勁的代碼,應(yīng)當重新編寫 (5) 盡量使用標準庫函數(shù),不要“發(fā)明”已經(jīng)存在的庫函數(shù) (6) 盡量不要使用與具體硬件或軟件環(huán)境關(guān)系密切的變量 (7) 把編譯器的選項設(shè)置為最嚴格狀態(tài) (8) 如果可能的話,使用PC-Lint、LogiScope等工具進行代碼審查

?

?

?

第十二章 補充 12.1 仔細設(shè)計結(jié)構(gòu)中玉薩怒的布局與排列順序,使結(jié)構(gòu)容易理解、節(jié)省占用空間,并減少引起誤用現(xiàn)象 示例:如下結(jié)構(gòu)中的位域排列,將占較大空間,可讀性也稍差 typedef struct EXAMPLE_STRU { ??? unsigned int valid : 1; ??? PERSON person; ??? unsigned int set_flg : 1; }EXAMPLE; 若改成如下形式,不僅可節(jié)省1字節(jié)空間,可讀性也變好了 typedef struct EXAMPLE_STRU { ??? unsigned int valid : 1; ??? unsigned int set_flg : 1; ??? PERSON person; }EXAMPLE;

?

12.2 結(jié)構(gòu)的設(shè)計要盡量考慮向前兼容和以后的版本升級,并未某些未來可能的應(yīng)用保留余地(如預(yù)留一些空間等) ???? 說明:軟件向前兼容的特性,是軟件產(chǎn)品是否成功的重要標志之一。如果要想使產(chǎn)品具有較好的前向兼容,那么在產(chǎn)品設(shè)計之初就應(yīng)為以后版本升級保留一定余地,并且在產(chǎn)品升級時必須考慮前一版本的各種特性。

?

12.3 對自定義數(shù)據(jù)類型進行恰當命名,使它成為自描述性的,以提高代碼可讀性。注意其命名方式在同一產(chǎn)品中的統(tǒng)一。 ???? 說明:使用自定義類型,可以彌補編程語言提供類型少、信息量不足的缺點,并能使程序清晰、簡潔。 下面的聲明可使數(shù)據(jù)類型的使用簡潔、明了 typedef unsigned char? BYTE; typedef unsigned short WORD; typedef unsigned int?? DWORD; 下面的聲明可使數(shù)據(jù)類型具有更豐富的含義: typedef float DISTANCE; typedef float SCORE;

?

12.4 函數(shù)、過程 (1) 對所調(diào)用函數(shù)的錯誤返回碼要仔細、全面地處理

?

(2) 編寫可重入函數(shù)時,若使用全局變量,則應(yīng)通過關(guān)中斷、信號量(即P、V操作)等手段對其加以保護 ??? 說明:若對所使用的全局變量不加以保護,則此函數(shù)就不具有課重入性,即當多個進行調(diào)用此函數(shù)時,很有可能使有關(guān)全局變量變?yōu)椴豢芍獱顟B(tài) 示例:假設(shè)Exam是int型全局變量,函數(shù)Squre_Exam返回Exam平方值。那么如下函數(shù)不具有可重入性 unsigned int example( int para ) { ??? unsigned int temp;

?

??? Exam = para; // (**) ??? temp = Square_Exam(); ?? ??? return temp; } 此函數(shù)若被多個進程調(diào)用的話,其結(jié)果可能是位置的,因為當(**)語句剛執(zhí)行完畢后,另外一個使用本函數(shù)的進程可能正好被激活,那么當激活的進程執(zhí)行到此函數(shù)時,將使Exam賦與另一個不同的para值,所以當控制重新回到"temp = Square_Exam();"后,計算出的temp很可能不是預(yù)想中的結(jié)果。此函數(shù)應(yīng)如下改進: unsigned int example( int para ) { ??? unsigned int temp;

?

??? [申請信號量操作]????? // 若申請不到“信號量”,說明另外的進程正處理給Exam賦值并計算其 ??? Exam = para; // (**)? // 平方過程中(即正在使用此信號),本進程必須等待其釋放信號后,才 ??? temp = Square_Exam(); // 可繼續(xù)執(zhí)行,但其他進程必須等待本進程釋放信號量后,才能在使用 ??? [釋放信號量操作]????? // 本信號 ?? ??? return temp; }

?

(3) 在同一項目組應(yīng)明確規(guī)定對接口函數(shù)參數(shù)的合法性檢查應(yīng)有函數(shù)逇調(diào)用者負責還是接口函數(shù)本身負責,缺省是由函數(shù)調(diào)用者負責 ??? 說明:對于模塊接口函數(shù)的參數(shù)逇合法性檢查這一問題,往往有兩個極端現(xiàn)象,即:要么是調(diào)用者和被調(diào)用者對參數(shù)不作合法性檢查,結(jié)果就遺漏了合法性檢查這一必要的處理過程,造成問題隱患;要么就是調(diào)用者和被調(diào)用者均對參數(shù)進行合法性檢查,這種情況雖不會造成問題,但產(chǎn)生了冗余代碼,降低了效率。

?

(4) 防止就愛那個函數(shù)的參數(shù)作為工作便利 ??? 說明:將函數(shù)逇參數(shù)作為工作便利,有可能錯誤地改變參數(shù)內(nèi)容,所以很危險。對必須改變的參數(shù),最好先用局部變量代之,最后再講該局部變量的內(nèi)容賦給該參數(shù) 示例:下函數(shù)的實現(xiàn)不太好 void sum_date( unsigned int num, int *data, int *sum ) { ??? unsigned int count; ??? ??? *sum = 0; ??? for ( count = 0; count < num; count++ ) ??? { ??????? *sum += data[ count ]; // sum成了工作變量,很不好 ??? } } 若改為如下,則更好些: void sum_date( unsigned int num, int *data, int *sum ) { ??? unsigned int count; ??? int sum_temp; ??? ??? sum_temp = 0; ??? for ( count = 0; count < num; count++ ) ??? { ??????? sum_temp += data[ count ]; // sum成了工作變量,很不好 ??? }

?

??? *sum = sum_temp; }

?

(5) 函數(shù)的規(guī)模盡量限制在200行以內(nèi) ??? 說明:不包括注釋和空格行 (6) 一個那還俗僅完成一個功能 (7) 不要設(shè)計多用途面面俱到的函數(shù) ??? 說明:多功能集于一身的函數(shù),很可能使函數(shù)的理解、測試、維護等變得困難

?

(8) 函數(shù)的功能應(yīng)該是可以預(yù)測的,也就是只要輸入數(shù)據(jù)相同就應(yīng)產(chǎn)生同樣的輸出 ??? 說明:帶有內(nèi)部“存儲器”的函數(shù)功能可能是不可測的,因為他的輸出可能取決于內(nèi)部存儲器(如某標志)的狀態(tài)。這樣的函數(shù)既不易于理解有不利用測試和維護。在C/C++語言中,函數(shù)的static局部變量是函數(shù)的內(nèi)部存儲器,有可能使函數(shù)的功能不可預(yù)測,然而,當某函數(shù)的返回值i為指針類型是,則必須是static的局部變量的地址作為返回值,若為auto類,則返回為指針 示例:如下函數(shù),其返回值是不可預(yù)測的 unsigned int integer_sum( unsigned int base ) { ??? unsigned int index; ??? static unsigned int sum = 0; // 注意,是static類型的,若改為auto類型,則函數(shù)即變?yōu)榭深A(yù)測

?

??? for ( index = 1; index <= base; index++ ) ??? { ??????? sum += index; ??? }

?

??? return sum; }

?

(9) 避免設(shè)計多參數(shù)函數(shù),不使用的參數(shù)從接口中去掉 ??? 說明:目的減少函數(shù)間接口的復(fù)雜度

?

(10) 非調(diào)度函數(shù)應(yīng)減少或防止控制參數(shù),盡量只是用數(shù)據(jù)參數(shù) ???? 說明:本建議目的是防止函數(shù)間的控制耦合。調(diào)度函數(shù)是指根據(jù)輸入的消息類型或控制命令,來啟動相應(yīng)的功能實體(即函數(shù)或過程),而本身并不完成具體功能。控制參數(shù)是指改變函數(shù)功能行為的參數(shù),即函數(shù)要根據(jù)此參數(shù)來決定具體怎樣工作。非調(diào)度函數(shù)的控制參數(shù)增加了函數(shù)間的控制耦合,很可能使函數(shù)間的耦合度增大,并使函數(shù)的功能不唯一。 示例:如下函數(shù)構(gòu)造不太合理 int add_sub( int a, int b, unsigned char add_sub_flg ) { ??? if ( INTEGER_ADD == add_sub_flg ) ??? { ??????? return ( a + b ); ??? } ??? else ??? { ??????? return ( a - b ); ??? } } 不如分為如下兩個函數(shù)清晰: int add( int a, int b ) { ??? return ( a + b ); } int sub( int a, int b ) { ??? return ( a - b ); }

?

(11) 檢查函數(shù)所有參數(shù)輸入的有效性 (12) 檢查函數(shù)所有非參數(shù)輸入的有效性,如數(shù)據(jù)文件、公共變量等 ???? 說明:函數(shù)的輸入主要有兩種:一種是參數(shù)輸入;另一種是全局變量、數(shù)據(jù)文件的輸入,即非參數(shù)輸入。函數(shù)在使用輸入之前,應(yīng)進行必要的檢查。 (13) 使用動賓詞組為執(zhí)行某操作的函數(shù)命名。如果是OOP方法,可以只有動詞(名詞是對象本身) 示例:參照如下方式命名函數(shù) void print_record( unsigned int rec_ind ); int input_record( void );

?

(14) 函數(shù)的返回值清楚、明了,讓使用者不容易忽視錯誤情況 ???? 說明:函數(shù)的美中出錯返回值的意義要清晰、明了、準確,防止使用者誤用、理解錯誤或忽視錯誤返回碼

?

(15) 防止把沒有關(guān)聯(lián)的語句放到一個函數(shù)中 ???? 說明:防止函數(shù)或過程內(nèi)出現(xiàn)隨機內(nèi)聚。隨機內(nèi)聚是指將沒有關(guān)聯(lián)或關(guān)聯(lián)很弱的語句放到同一個函數(shù)或過程中。隨機內(nèi)聚給函數(shù)火鍋城的維護、測試及以后的升級造成了不變,同事也使函數(shù)或過程的功能不明確。使用隨機內(nèi)聚函數(shù),常常容易出現(xiàn)在一種應(yīng)用場合需要改進此函數(shù),而另一種應(yīng)用場合又不允許這種改進,從而陷入困境。 ???? 在編程時,經(jīng)常遇到在不同韓式中使用相同的代碼,許多開發(fā)人員都愿把這些代碼提出來,并構(gòu)成一個新函數(shù)。若這些代碼關(guān)聯(lián)較大并且是完成一個功能的,那么這種構(gòu)造是合理的,否則這種構(gòu)造將產(chǎn)生隨機內(nèi)聚的函數(shù) 示例: void Init_Var( void ) { ??? Rect.length = 0; ??? Rect.width = 0;

?

??? Point.x = 10; ??? Point.y = 10; } 舉行的長、寬和點的左邊基本沒有任何關(guān)系,故以上函數(shù)是隨機內(nèi)聚 應(yīng)如下分為兩個函數(shù): void Init_Rect( void ) { ??? Rect.length = 0; ??? Rect.width = 0; } void Init_Point( void ) { ??? Point.x = 10; ??? Point.y = 10; }

?

(16) 如果多段代碼重復(fù)做同一件事情,那么在函數(shù)的劃分上可能存在問題 ???? 說明:若此段代碼個語句之間有實質(zhì)性關(guān)聯(lián)并且是完成同一件功能的,那么可考慮把此段代碼構(gòu)成一個新的函數(shù) (15) 功能不明確較小的函數(shù), 特別是僅有一個上級函數(shù)調(diào)用它時,應(yīng)考慮把它合并到商機函數(shù)中,而不必單獨存在 ???? 說明:模塊中函數(shù)劃分的過多,一般會使函數(shù)間的接口變得復(fù)雜。所以過小的函數(shù),特別是閃入很低或功能不明確的函數(shù),不值得單獨存在 (16) 仔細分析模塊的功能及性能需求,并進一步細分,同時若有必要畫出有關(guān)數(shù)據(jù)流圖,據(jù)此來進行木的函數(shù)劃分與組織 ???? 說明:函數(shù)的劃分與組織是模塊的實現(xiàn)過程中很關(guān)鍵的步驟,如何劃分出合理的函數(shù)結(jié)構(gòu),關(guān)系到模塊的最終效率和可維護性、可測性等。格局模塊的功能圖或/數(shù)據(jù)流圖映射出函數(shù)結(jié)構(gòu)是常用的方法之一

?

(17) 改進模塊中的函數(shù)結(jié)構(gòu),降低函數(shù)間的耦合度,并提供函數(shù)的獨立性以及代碼可讀性、效率和可維護性。優(yōu)化函數(shù)結(jié)構(gòu)時,要遵守以下原則: a、不能影響模塊功能的實現(xiàn) b、仔細考察模塊或函數(shù)出錯處理及模塊的性能要求并進行完善 c、通過分級或合并函數(shù)來改進軟件結(jié)構(gòu) d、考察函數(shù)的規(guī)模,過大的要進行分解 e、降低函數(shù)間接口的復(fù)雜度 f、不同層次的函數(shù)調(diào)用要有較合理的扇入、扇出 g、函數(shù)功能應(yīng)可預(yù)測 h、提高函數(shù)內(nèi)聚(單一功能的函數(shù)內(nèi)聚最高) 說明:對初步劃分后的函數(shù)結(jié)構(gòu)應(yīng)進行改進、優(yōu)化,使之更為合理

?

(18) 避免使用BOOL參數(shù) ???? 說明:原因有而,其一是BOOL參數(shù)值無意義,TRUE/FALSE的含義是非常模糊的,在調(diào)用時很難知道該蠶食到底傳達的是什么意思;其二是BOOL參數(shù)值不利于擴充。還有NULL夜市一個無意義的單詞。

?

(19) 當一個過程(函數(shù))中對較長變量(一般是結(jié)構(gòu)的成員)有較多引用時,可以用一個意義相當?shù)暮甏????? 說明:這樣可以增加編程效率和程序的可讀性 示例:在某過程中較多引用TheReceiveBuffer[ FirstSocker ].buDataPtr 則可以通過以下宏定義來替換: #define pSOCKDATA TheReceiveBuffer[ FirstSocker ].buDataPtr

?

12.5 可測性 (1) 在同一項目組或產(chǎn)品組中,要有一套同一的為集成測試與系統(tǒng)聯(lián)調(diào)準備的調(diào)測開關(guān)及相應(yīng)的打印函數(shù),并且要有詳細的說明 (2) 編程的同時要為單元測試選擇恰當?shù)臏y試點,并仔細構(gòu)造測試代碼、測試用例,同時給出明確的注釋說明。測試代碼部分應(yīng)作為(模塊中的)一個子模塊,以網(wǎng)編測試代碼在模塊中的安裝于卸載(通過調(diào)測開關(guān)) (3) 在進行集成測試/系統(tǒng)聯(lián)調(diào)之前,要構(gòu)造好測試環(huán)境、測試項目及測試用例,同時仔細分析并優(yōu)化測試用例,以提高測試效率 ??? 說明:好的測試用例應(yīng)盡可能模擬出程序所遇到的邊界值、各種復(fù)雜環(huán)境及一些極端情況等 (4) 使用斷言來發(fā)現(xiàn)軟件問題,提高代碼可測性 ??? 說明: (5) 使用斷言來發(fā)現(xiàn)軟件問題,提高代碼可測性 ??? 說明:斷言是對某種假設(shè)條件進行檢查(可理解為若條件成立則無動作,否則應(yīng)報告),他可以快速發(fā)現(xiàn)并定位軟件問題,同時對系統(tǒng)錯誤進行自動報警。斷言可以對在系統(tǒng)中隱藏很深,用其他手段極難發(fā)現(xiàn)的問題進行定位,從而縮短軟件問題定位時間,提高系統(tǒng)的可測性。實際應(yīng)用時,可根據(jù)具體情況靈活地設(shè)計斷言。 示例:下面是C語言中的一個斷言,用宏來設(shè)計的(其中NULL為0L) #ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試

?

void exam_assert( char *file_numa, unsigned int line_no ) { ??? printf( "\n[EXAM]Assert failed:%s, line %u\n", file_name, line_no ); ??? abort(); }

?

#define EXAM_ASSERT( condition ) { ??? if ( condition ) // 若條件成立,則無動作 ??? { ??????? NULL; ??? } ??? else ??? { ??????? exam_assert( __FILE__, __LINE__) ??? } } #else // 若不使用斷言測試

?

#define EXAM_ASSERT( condition ) NULL

?

#endif /* end of ASSERT */ (6) 不能用斷言來檢查最終產(chǎn)品肯定會出現(xiàn)且必須處理的錯誤情況 ??? 說明:斷言是用來處理不應(yīng)該發(fā)生的錯誤情況的,對于可能會發(fā)生的且必須處理的情況要寫防錯程序,而不是斷言。如某模塊收到其他模塊或鏈路上的消息后,要對消息的合理性進行檢查,此過程為正常的錯誤檢查,不能用斷言來實現(xiàn)

?

(7) 對較復(fù)雜的斷言加上明確的注釋 ??? 說明:為復(fù)雜的斷言加注釋,可澄清斷言含義并減少不必要的誤用

?

(8) 用斷言保證沒有定義的特性或功能不被使用 示例:假設(shè)某通信模塊在設(shè)計時,準備提供“無連接”和“連接”這兩種業(yè)務(wù)。但當前的版本中僅實現(xiàn)了“無連接”業(yè)務(wù),且在此版本的正式發(fā)行版中,用戶(上層模塊)不用產(chǎn)生“連接”業(yè)務(wù)的請求,那么在測試時可用斷言檢查用戶是否使用“連接”業(yè)務(wù)。如下: #define EXAM_CONNECTIONLESS 0 // 無連接業(yè)務(wù) #define EXAM_CONNECTION???? 1 // 連接業(yè)務(wù)

?

int msg_process( EXAM_MESSAGE *msg ) { ??? unsigned char service;

?

??? EXAM_ASSERT( NULL != msg );

?

??? service = get_msg_service_class( msg );

?

??? EXAM_ASSERT( service != EXAM_CONNECTION); // 假設(shè)不使用連接業(yè)務(wù) .... }

?

(8) 正式軟件產(chǎn)品中應(yīng)把斷言及其他調(diào)測代碼去掉(即把有關(guān)的調(diào)測開關(guān)關(guān)掉) ??? 說明:加快軟件運行速度

?

(9) 在編寫代碼之前,應(yīng)先設(shè)計好程序調(diào)試與測試的方法和手段,并設(shè)計好各種調(diào)測開關(guān)及相應(yīng)測試代碼如打印函數(shù)等 ??? 說明:程序的調(diào)試與測試是軟件生命周期中很重要的一個階段,如何對軟件進行較全面、高效的測試并盡可能低找出軟件中的錯誤就成為很關(guān)鍵的問題。因此在編寫源代碼之前,除了要有一套比較完善的測試計劃外,還應(yīng)設(shè)計出一系列代碼測試手段,為單元測試、集成測試及系統(tǒng)聯(lián)調(diào)提供方便

?

(10) 調(diào)測開關(guān)應(yīng)分為不同級別和類型 ???? 說明:調(diào)測開關(guān)的設(shè)置及分類應(yīng)從以下幾方面考慮:針對模塊或系統(tǒng)某部分代碼的調(diào)測;針對模塊或系統(tǒng)某功能的調(diào)測;處于某種其他目的,如對性能、容量等的測試、這樣做便于軟件功能的調(diào)測,并且便于模塊的單元測試、系統(tǒng)聯(lián)調(diào)等

?

12.6 程序效率 (1) 編程時要經(jīng)常注意代碼的效率 ??? 說明:代碼效率分為全局效率、局部效率、時間效率及空間效率。

?

(2) 循環(huán)體內(nèi)工作量最小化 ??? 說明:應(yīng)仔細考慮循環(huán)體內(nèi)的語句是否可以放在循環(huán)體之外,是循環(huán)體內(nèi)工作量最小,從而提高程序的時間效率

?

(3) 對模塊中函數(shù)的劃分及組織方式進行分析、優(yōu)化,改進模塊中函數(shù)的組織結(jié)構(gòu),提高程序效率 ??? 說明:軟件系統(tǒng)的效率主要與算法、處理任務(wù)方式、系統(tǒng)功能及函數(shù)結(jié)構(gòu)有很大關(guān)系,盡在代碼上下功夫一般不能解決根本問題 (4) 編程時,要隨時留心代碼效率;優(yōu)化代碼時,要考慮周全

?

(5) 要仔細的構(gòu)造或直接用匯編編寫調(diào)用頻繁或性能要求極高的函數(shù) ??? 說明:只有對編譯系統(tǒng)產(chǎn)生機器碼的方式以及硬件系統(tǒng)較為熟悉時,才可以使用匯編嵌入方式。嵌入?yún)R編可提高時間及空間效率,但也存在一定風險

?

(6) 在多重循環(huán)中,應(yīng)將最忙的循環(huán)放在最外層 ??? 說明:減少cpu切入循環(huán)層的次數(shù)

?

(7) 避免循環(huán)體內(nèi)含判斷語句,應(yīng)將循環(huán)語句至于判斷語句的代碼塊之中 ??? 說明:目的是減少判斷次數(shù)。循環(huán)體重的判斷語句是否可以移到循環(huán)體外,要視程序的具體情況而言,一般情況,與循環(huán)變量無關(guān)的判斷語句可以移到循環(huán)體外,而有關(guān)的則不可以。

?

(8) 盡量用乘法或其他方法代替除法,特別是浮點運算中的觸發(fā) ??? 說明:浮點運算觸發(fā)要占用較多cpu資源 示例:如下表達式運算可能要占較多cpu資源 #define PAI 3.1416 radius = circle_length / ( 2 * PAI );

?

應(yīng)如下把浮點除法改為浮點乘法 #define PAI_RECIPROCAL ( 1 / 3.1416 ); // 編譯器編譯時,將發(fā)生具體浮點數(shù) radius = circle_length * PAI_RECIPROCAL / 2;

?

12.7 代碼編輯、編譯、審查 (1) 在產(chǎn)品軟件(項目組)中,要統(tǒng)一編譯開關(guān)選項 (2) 通過代碼走讀及審查方式對代碼進行檢查 ??? 說明:代碼走讀主要是對程序的編譯風格如注釋、命名等以及編程時易出錯的內(nèi)容進行檢查,可由開發(fā)人員自己或開發(fā)人員交叉的方式進行;代碼審查主要是對程序?qū)崿F(xiàn)的功能及程序的穩(wěn)定性、安全性、可靠性等進行檢查及評審,可通過自審、交叉審核或指定部門抽查等方式進行

?

12.8 代碼測試、維護 (1) 單元測試要求至少達到語句覆蓋 (2) 單元測試開始要跟蹤每一條語句,并觀察數(shù)據(jù)流及變量的變化 (3) 清理、整理或優(yōu)化后的代碼要經(jīng)過審查及測試 (4) 代碼版本升級要經(jīng)過嚴格測試 (5) 使用工具軟件對代碼版本進行維護 (6) 正式版本上軟件的任何修改都應(yīng)有詳細的文檔記錄 (7) 關(guān)鍵的代碼在匯編級跟蹤 (8) 仔細設(shè)計并分析測試用例,使測試用例覆蓋盡可能多的情況,以提高測試用例的效率 (9) 盡可能模擬出程序的各種出錯情況,對出錯處理代碼進行充分的測試 (10) 仔細測試代碼處理數(shù)據(jù)、變量的邊界情況 (11) 保留測試信息,以便分析、總結(jié)經(jīng)驗及進行更充分的測試 (12) 不應(yīng)通過“試”來解決問題,應(yīng)尋找問題的根本原因 (13) 對自動消失的錯誤進行分析,搞清楚錯誤時如何消失的 (14) 修改錯誤不僅要治標,更要治本 (15) 測試時應(yīng)設(shè)法使很少發(fā)生的事件經(jīng)常發(fā)生 (16) 明確模塊或函數(shù)處理哪些事件,并使他們經(jīng)常發(fā)生 (17) 堅持在編碼階段就對代碼進行徹底的單元測試,不要等以后的測試工作來發(fā)現(xiàn)問題 (18) 去除代碼運行的隨機性(如去掉誤用的數(shù)據(jù)、代碼及盡可能防止并注意函數(shù)中的“內(nèi)部寄存器”等),讓函數(shù)運行的結(jié)果可預(yù)測,并使出現(xiàn)的錯誤可再現(xiàn)

?

轉(zhuǎn)載于:https://www.cnblogs.com/studynote/articles/3448859.html

總結(jié)

以上是生活随笔為你收集整理的[转载]高质量c/c++编程指南读书笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。