捌
***預處理***
C預處理器是一種簡單的宏處理器。它在編譯器讀取源程序之前對C程序的源文本進行處理。預處理器一般從源文件中刪除所有的預處理器命令行,并在源文件中執行這些預處理命令所指定的轉換操作。
【宏只是進行簡單的文本替換】
**續行:**
所有的源文件行(包括預處理器命令行)都可以在行末加個反斜杠( \ )進行續行。這個操作發生在對預處理器命令進行掃描之前。
【注意:續行符反斜杠之后不能有任何字符,尤其注意檢查不能有空格等空白符。】
***普通宏定義:***
~~~
#define 命令有兩種形式,取決于被定義的宏名后面是不是緊隨一個左括號。若沒有左括號,則為無參宏定義。
~~~
無參宏定義常用于:
1、在程序中引入名稱常量。這樣,可以在一個地方編寫,然后通過名稱在其它地方被引用,這樣,以后修改這個數字就非常方便了。
2、改變外部定義的函數名或變量名。(有些外部函數的函數名過于簡短或是與當前程序的命名風格不符,我們可以用宏定義一個新的函數名來代替它)
例:#define? error_handler ?eh73
//我們用一個更具描述性的函數名error_handler來表示外部函數eh73
***帶參數的宏:***
左括號必須緊隨宏名之后,中間不能有空格。如果宏名和左括號之間被一個空格所分隔,則這個宏被定義為不接受任何參數,并且宏體從左括號開始。
**注意**:
1、為了保證宏展開的正確性,應該給每個宏參數加上括號,且給整個表達式也加上括號。
(多余的括號保證了復雜的實際參數不會被編譯器錯誤的解釋)
2、使用類似函數的宏,可能存在一些陷阱。我們在調用宏函數時,會習慣地加一個分號,而額外的分號可能引發錯誤。
例:#define? SWAP(type, x, y)? { type _temp=x; x=y; y=_temp; }
若 if( x > y) SWAP(int , x, y) ;
????? elsex = y ;
//這將產生錯誤,宏展開后有一個多余的分號,將導致else懸空。
**★為了避免這個問題,可以把宏函數體定義為一條do-while語句**,后者可以接受在末尾添加分號。
~~~
#define?SWAP(type, x, y)? \
do { type _temp=x; x=y; y=_temp; } while(0)
~~~
3、宏參數的副作用。(當宏參數含++、--操作符時一定要小心)
例:#define SQUARE(x)? ((x)*(x))
若 b =SQUARE(a++) ; ????? //則結果是未定義的,因為(a++)*(a++)的行為取決于編譯器。
真正的函數調用不會出現這樣的問題,真正的函數調用是先計算參數值,然后再調用函數。而宏函數,只是簡單的文本替換。
【宏是與類型無關的,即**其可以用類型做參數**。故:宏有時可以完成無法用函數實現的任務】
例:#defineMY_MALLOC( n, type )? ((type*)malloc((n)*sizeof(type)))
**取消宏定義:**
~~~
# undef命令可以取消定義一個名稱為宏:undef? name
~~~
***條件編譯***
條件編譯指令允許預處理器根據一個經過計算所得出的條件,來選擇不同的語句參加編譯。
if? 常量表達式
????? 文本行組1
else
????? 文本行組2
endif
常量表達式包括整數常量以及所有的整數算術、關系、位和邏輯操作符。
如果它的值不是0,則“文本行組1”則被編譯器進行編譯,而“文本行組2”則被丟棄。
**defined操作符**
defined 操作符只能在#if和#elif表達式中使用,而不能用于別處。
形式:definedname 或 defined(name)
~~~
#if?defined( VAX )? 可等同于 #ifdef VAX
~~~
但defined的使用更加靈活一些:
例:#ifdefined(VAX) && !defined(UNIX) && debugging
***預定義的宏***
標準C的預處理器定義了一些宏,這些宏的名稱都是以兩個下劃線字符開始和結束的。程序員不能取消這些預定義宏的定義或對它們進行重新定義。
幾個常用的預定義宏:
__LINE__???????????? 當前源程序行的行號,用十進制整數常量表示
__FILE__????????????? 當前源文件的名稱,用字符串常量表示
__DATA__???????????? 編譯時的日期,用“Mmm dd yyyy”形式的字符串常量表示
__TIME__???????????? 編譯時的時間,用“hh:mm:ss”形式的字符串常量表示。
***程序調試***
**一、使用斷點和單步執行**
詳情請參閱具體的IDE使用說明
**二、條件編譯**
~~~
#ifdef?DEBUG
????? printf(“File:%s line:%d, x=%d, y=%d”, __FILE__, __LINE__, x, y ) ;
~~~
endif
如果要編譯它,只要使用#defineDEBUG 即可,如果要忽略它,注釋掉即可。
C99引入了一個預定義標識符:__func__
這個標識符可以由調試工具使用,打印出外層函數的名稱。
例:if(failed)? printf(“Function %s failed \n”, __func__) ;
**三、使用斷言<assert.h>**
斷言就是聲明某種東西應該為真。(預測某個值為多少,符合條件則繼續,否則中止程序)
void?assert( int express ) ;
當它被執行時,對表達式參數進行測試。
如果它的值為假(零),它就向標準錯誤打印一條診斷信息并中止程序。
否則它不打印任何東西,程序繼續執行。
例:assert(value != NULL ) ;
//如果它接受了一個NULL參數,則打印類似:assertfailed :value != NULL.file.c line 273
【注意:**斷言只是在測試階段,防御性地測試某個變量值的方法**,不要再斷言中寫一些會對程序造成影響的表達式。因為在release版編譯器會刪除斷言,若斷言中的表達式對程序有影響,可能會產生錯誤!】
**刪除斷言**:
當程序被完整地測試完畢之后,在源文件的頭文件assert.h被包含之前,增加定義:
~~~
#define NDEBUG
~~~
當NDEBUG被定以后,預處理器將會丟棄所有斷言。
***編程風格:***
以下內容摘自《代碼大全》
**變量命名**:
該名字要完全、準確地表述出該變量所代表的事物。
一個好名字通常表達的是“什么”(what),而不是“如何”(how)。
如果一個名字反映了計算機的某些方面而不是問題本身,那么它反映的就是“how”而非“what”了,
請避免選取這樣的名字,而應該**在名字中反映問題本身**!
(一條員工數據:稱作:inputRec或employeeData。inputRec是一個反映輸入、記錄 這些計算機術語的,不能反映問題特征)
當變量名的長度在10到16個字符時,調試程序所花的力氣是最小的。
記住**把限定詞加到名字最后**,變量名最重要的部分,即**為變量賦予主要含義的部分應當位于最前面**。
特例: Num的限定詞的位置是約定俗成的。
Num放在變量的開始位置代表一個總數;例:numCustomers表示員工總數
Num放在變量名的結束位置代表一個序號;例:customerNum表示員工號
避免此問題的方法:
用Conut或**Total來代表總數,用Index來代表序號**。
例:customerCount員工總數 customerIndex 員工序號
命名的一致性可提高可讀性,簡化維護工作。
**如果你發現自己需要猜測某段代碼的含義時,就該考慮為變量重新命名。**
***變量名中的對仗詞***:
next/previous
source/destination?
。。。。。
1、為狀態變量命名
標記的名字中不應該含有flag。標記應該用枚舉類型、具名常量。
dataReady recalaNeeded 都是好名字
2、為布爾變量命名
以下是幾個推薦的布爾變量名(可在其前加上具體的描述名稱)
done:用done表示某件事已經完成。(在事情完成之前把done設為false,在完成之后設為true)
error:用error表示有錯誤發生。(在錯誤發生之前把變量值設置為false,在錯誤已經發生時把它設置為true)
found:用found來表示某個值已經找到了。(在還沒有找到該值的時候把它設為false,找到之后設為true)
success或ok 用來表明一項操作時候成功。
(不要在布爾變量的前面加上Is)
命名規則可以根據局部數據、類數據、全局數據的不同而有所差別。
**命名規則可強調相關變量之間的關系**。
**★命名規則的指導原則**:
????? 區分**變量名和子程序名**:
????? 變量名和對象名以小寫字母開始,子程序名以大寫字母開頭。
????? 區分**類和對象**:
????? 1、通過對對象采用更明確的名字區分類型和變量
????? 例:Widget employWidget ;
????? 2、通過給變量加"a"前綴區分類型和變量
????? 例:Widget aWidget????? ;
????? 標識**全局變量**:
????? 在全局變量名前加上"g_"前綴
????? 標識**成員變量**:
????? 在成員變量名前加上"m_"前綴,可明確表示該變量既不是局部變量,也不是全局變量。
????? 標識**自定義類型**:
????? 在自定義類型名前加上"t_"前綴,可明確表示一個名字是類型名,可避免類型名與變量名的沖突
????? 標識**枚舉類型**:
????? 在枚舉類型名前加"e_"前綴,同時為該類型的成員名增加特定類型的前綴。
????? 例:Color_或Planet_
????? 標識**只讀參數**:
????? 在其前加上 const前綴,可防止給只讀變量賦值的錯誤。例:constMax
**變量名要包含以下三類信息**:
????? 1、變量的內容(它代表什么)
????? 2、數據的種類(具名變量、簡單變量、用戶自定義類型、類)
????? 3、變量的作用域(局部的、類的、全局的)
?????
**關于子程序**
**好的子程序名**:
????? 給子程序命名的重點是盡可能含義清晰,即:子程序的長短要視該名字是否清晰易懂而定。
????? 子程序的名字應當描述其所有輸出結果以及副作用。
????? (例:一個子程序的作用是計算報表總額并打開一個輸出文件。若把它命名為computeReportTotals()還不算完整。computeReportTotalsAndOpenOutputFile()很完整但是名字太長。解決方法是 你應該換一種方式編寫程序,直截了當地解決問題而不產生副作用。[即:UNIX的哲學,讓一個模塊只干一件事!])?????
????? 給子程序起名時要用動詞加賓語的形式。例:PrintDocument()
????? 在面向對象語言中,不必在過程名中加入對象的名字,因為對象本身就已經包含在調用語句中了。例:Document.Print() ;
【子程序的名字是它質量的指示器,如果名字糟糕且又不準確,那么它就反映不出程序是干什么的。糟糕的名字都意味著程序需要修改】
**正確地使用輸入參數**:
????? 1、對于在函數體中不變更的參數,用const關鍵字來限制。
????? 2、如果你假定了傳遞給子程序的參數具有某種特征,那就要對這種假定進行說明。比注釋還好的方法是在代碼中使用斷言(assertions)
[對參數接口的假定進行說明:
1、參數是僅用于輸入的、要被修改的、還是僅用于輸出的
2、表示數量的參數的單位(英寸,米等)
3、所能接受的數值范圍
4、不該出現的特定數值
5、說明狀態代碼和錯誤值的含義]
如果你向很多不同的子程序傳遞數據,就請把這些子程序組成一個類,并把那些經常使用的數據用作類的內部數據。
如果你覺得把輸入、修改、輸出參數區分開很重要,那么就建立一種命名規則來對它們進行區分。
可在這些參數名之前加上i_m_ o_ 前綴。也可以用Input_Modify_ Output_ 來當前綴
把對子程序的調用和對狀態值的判斷清楚地分開。把對子程序的調用和狀態值的判斷寫在一行代碼中,增加了該條語句的密度,也相應增加了其復雜度。
應該這樣:
~~~
ouputStatus = report.FormatOutput(formattedReport ) ;
if( outputStatus = Success ) then ...
~~~
**關于宏:**
通常認為,用宏來代替函數調用的做法具有風險,而且不易理解,因此,除非必要,否則應該避免使用這種技術。
用給子程序命名的方法給宏函數命名,以便在需要時可以用子程序來替換宏。
宏對于支持條件編譯非常有用,但對于細心的程序員來說,除非萬不得已,否則是不會用宏來代替子程序的。(可用內聯函數來實現宏函數的效果)
節制使用inline子程序!
**其它**:
建議在真正需要用空語句時這樣寫:
NULL ;
而不是單用一個分號,這就好比匯編里面的空指令,這樣做可以明顯的區分真正必須的空語句和不小心多寫的分號。
在多重循環中,如果有可能,應當將最長的循環放在最內層,最短的循環放在最外層,以減少CPU跨切循環的次數。
循環要盡可能的短,要使代碼清晰,一目了然。
(如果你寫的一個循環的代碼超過一屏,那么會讓讀代碼的人抓狂的。解決的辦法有兩個:
第一:重新設計這個循環。確認是否這些操作都必須放在這個循環里;
第二:將這些代碼改寫成一個子函數。循環中只調用這個子函數即可)
對于全局數據(全局變量、常量定義等)必須要加注釋。
注釋代碼段時應注重“為何做(why)”,而不是“怎么做(how)”
對于函數的入口出口參數及函數的功能給出注釋。
如果你的全局變量不用來多文件共享,那么就加上static,防止同一個載入模塊的兩個不同外部對象的命名沖突。