有人說過:“程序源代碼其實是跟人閱讀的,只是恰好機器可以編譯而已”。編程初學者常常會有這樣一個觀念,就是我的程序只要編譯通過了,運行沒有問題那就萬事大吉了。至於代碼的編寫規不規范,完全就是無關緊要的小事情。如果是處於學習階段,比如為了完成在學校的C語言課的作業,那麼花心思在代碼規范上的確沒有特別的必要,因為這些代碼基本不會進入實用工程,也不會被很多人閱讀到。
但是,如果應用到了工程領域,比如在軟件/互聯網企業的技術研發部門,或者Github等平台上的開源工程,那麼編程的規范性將變得無比重要。因為在這些場合,你寫的代碼將被許多人閱讀,並且可能會成為許多人進行後續開發的基礎。此時,差勁的代碼風格將嚴重拉低其他開發人員的工作效率。因此,我們推薦從一開始學習便養成一個良好的編程習慣,維持一個合理的代碼風格,這樣對未來的工作大有裨益。
C語言編程風格的內容相當龐大,這裡只挑選一部分相對常用而且比較重要的內容作為參考,主要分為5個部分,包括排版、注釋、命名、變量/結構、函數等。
程序排版使得代碼的結構更加清晰明了,而且有助於理解上下文的邏輯關系。
(1)程序塊應根據上下文關系采用縮進風格,縮進的長度根據具體標准規定;
(2)獨立的程序塊之間、變量說明之後必須加空行;比如:
int fun()
{
int nVal1 = 0, nVal2 = 5, nSum;
{
nSum = nVal1 + nVal2;
}
printf("Sum is %d\n", nSum);
}
(3)一條語句占一行,不允許將兩條語句寫在一行中;
(4)對於存在判斷、循環的代碼,像if/for/do/while/case/swith/default等部分獨占一行,且無論執行部分有多少條語句,都必須使用大括號{ };
(5)包裹代碼塊的大括號{ }必須另起一行,不要跟隨上一行代碼的末尾;且大括號也要符合代碼縮進規則;
注釋雖然不影響程序的運行,但仍然是代碼的重要組成部分。完善的代碼注釋對快速理解代碼的功能具有重要意義,相反如果代碼邏輯復雜且沒有注釋,或注釋不完整、不科學,那麼旁人很難理解這段代碼究竟是做什麼的。需注意一點,別人無法理解的程序即使運行良好,也永遠都是垃圾代碼。
添加注釋需要注意,注釋應簡潔、有效,有助於提升對代碼的理解。所以添加注釋應注意不要添加一些完全無意義或者錯誤的信息。通常,我們認為一套代碼按照優劣分為4個等級:
第一等級:不需要注釋,通過優秀的代碼風格、標識符命名和代碼的上下文關系就可以達到高可讀性的代碼;
第二等級:代碼的命名和組織規范、風格稍顯不足,但有完善的注釋;
第三等級:代碼風格和注釋都不夠完善,但是組織了較為完善的文檔在一定程度彌補了這一缺陷;
第四等級:代碼風格、注釋和文檔都不足,這種就屬於其他人難以理解的垃圾代碼。
函數頭部的注釋:
在函數頭部應添加注釋,說明函數的功能、參數、返回值等信息。下面的注釋格式比較完善,不一定要局限與此,但建議保留其中的大部分信息:
/*************************************************
Function: // 函數名稱
Description: // 函數功能、性能等的描述
Calls: // 被本函數調用的函數清單
Called By: // 調用本函數的函數清單
Input: // 輸入參數說明,包括每個參數的作
// 用、取值說明及參數間關系。
Output: // 對輸出參數的說明。
Return: // 函數返回值的說明
Others: // 其它說明
*************************************************/
代碼中的注釋:
語句的注釋應在被注釋語句的正上方或右方。如果是在上方的話,除非十分必要否則不要再代碼和注釋之間插入空格。
對於具有物理含義的常量和變量,以及數據結構,除非命名本身是充分注釋的,在聲明時必須加以注釋。
全局變量要有詳細的注釋,包括對其功能、取值范圍、使用的函數以及存取時的注意事項等。
注釋與上方的代碼用一行空格間隔。
對選擇、循環語句應當添加注釋,說明分支、循環體的意義。
在程序塊結束的大括號右方添加注釋,說明匹配的程序塊開始位置。
如以下代碼:
if (...)
{
program code
while (index < MAX_INDEX)
{
program code
} /* end of while (index < MAX_INDEX) */ // 指明該條while語句結束
} /* end of if (...)*/ // 指明是哪條if語句結束
標識符命名是代碼風格中的重要組成部分,甚至直接決定了代碼可讀性的高低。最常用的標識符無非就是常量名、變量/結構體名、函數名、宏定義、標簽名等。對不同的標識符類型一般適用不同的要求,但有一些基本要求是一致的:標識符的命名必須清晰明了,含義明確,盡量少地使用縮寫;嚴禁使用無意義的單個字母如a, b, i, m, n或者func1, fun等無意義的單詞或縮寫用於命名;
對於變量名和函數名,通常比較常用的有兩種命名法:駝峰命名法和下劃線命名法,這兩種方法的根本區別在於通過怎樣的方式來分隔標識符命名中的邏輯斷點。
駝峰命名法:通過大小寫字母的變化進行分隔,如:int imgWidth = 0; char *studentName = “Jerry”;
下劃線命名法:通過下劃線進行分隔,如:double earth_moon_distance;
對於函數名和變量名,一般可以使用不同的命名方法,但是需要注意的是只要選定的命名規范就要從頭到尾保持不變。通常,我個人的習慣是,變量名和結構體名使用駝峰命名法,變量名用小寫開頭,結構體用大寫開頭;函數名使用下劃線命名法,公有API以大寫字母開頭,私有函數以小寫開頭並聲明為static類型。
另外,對於常量、結構體成員變量、全局變量,還可以參考匈牙利命名法的原則,在變量名前加入前綴c_、m_和g_。
對於宏定義的命名,一律全部使用大寫字母,邏輯斷點采用下劃線分隔,如
#define MAX_ARRAY_LENGTH 256
對於頭文件保護作用的宏定義,則以頭文件的文件名命名,邏輯斷點和擴展名前的點全部用下劃線替代,並且在首位各添加一個下劃線,如
//ImageProcessing.h
#ifndef _IMAGE_PROCESSING_H_
#define _IMAGE_PROCESSING_H_
/*code*/
#endif
通常標簽名配合goto語句一起使用。由於goto本來就是比較冷門的語句,標簽也不是很常用。如果用到,則全部使用小寫字母,並且在結尾加_label,如:
void test()
{
/*code*/
goto end_label;
/*code*/
end_label:
/*code*/
}
變量和結構的使用是編程中最為頻繁的動作,如果能正確規范變量的使用,那麼對整體的編程風格的提升大有幫助。
(1)變量定義之後立刻初始化。通常數值型變量定以後可以立刻初始化為0、某個負數或其他無意義的數值,指針變量定義後立刻初始化為NULL。這樣在後面使用變量時可以更方便地判斷變量是否已經被正確地處理,防止無意中使用了未經初始化的值。
(2)除非特別必要,否則盡量減少全局變量的使用,對於跨文件使用的全局變量更要慎重。全局變量是造成代碼之間耦合的重要因素,通常使用全局變量越多,代碼就越難以維護。
(3)對於數值完全不應當改變的量,一律定義為常量,防止被誤修改。
(4)定義一個結構體的功能應當越具體越好,不應定義一個實現多種功能的結構。另一個體現是,不要定義規模過於龐大的結構,這樣不但在運行時浪費系統資源,而且邏輯上難以理解。
(5)除非特別必要,盡量減少變量類型之間的強制轉換。因為強制轉換實際上也是需要計算機額外操作的,過多的強制轉換對系統資源也是一種浪費。
(6)定義結構體時注意優化成員之間的順序,盡量減少因為字節對齊導致的存儲空間浪費。