C語言的語法風格與代碼書寫規(guī)范指南
C代碼:
#include <stdio.h> int main(void) { printf("That is Right Style\n"); return 0; }
在一個標準的C語言程序中,最特殊的莫過于main函數(shù)了,而說到底它就是一個函數(shù)而已,僅僅因為它地位特殊擁有第一執(zhí)行權(quán)力,換句話說,難道因為一個人是省長它就不是人類了?所以函數(shù)該有的它都應(yīng)該有,那么函數(shù)還有什么呢?
函數(shù)大體上分為內(nèi)聯(lián)函數(shù)(C99)(內(nèi)聯(lián)函數(shù)并非C++專屬,C語言亦有,具體見前方鏈接)和非內(nèi)聯(lián)的普通函數(shù),它們之間有一個很明顯的特點(一般情況下),那就是不寫原型直接在main函數(shù)上方定義,即使不加'inline'關(guān)鍵字,也能被編譯器默認為內(nèi)聯(lián)函數(shù),但之后帶來的某些并發(fā)問題就不是編譯器考慮的了。
普通函數(shù)正確的形式應(yīng)該為聲明與定義分離,聲明就是一個函數(shù)原型,函數(shù)原型應(yīng)該有一個函數(shù)名字,一個參數(shù)列表,一個返回值類型和一個分號。定義就是函數(shù)的內(nèi)在,花括號內(nèi)的就是函數(shù)的定義:
//... int function(int arg_1, float arg_2); //... int main(int argc, char* argv[]) { int output = function(11, 22.0); printf("%d\n",output); return 0; } int function(int arg_1, float arg_2) { int return_value = arg_1; float temp_float = arg_2; return return_value; }
依上所述,當非必要時,在自己編寫函數(shù)的時候請注意在開頭(main函數(shù)之前)寫上你的函數(shù)的原型,并且在末尾(main函數(shù)之后)寫上你的函數(shù)定義,這是一個很好的習慣以及規(guī)范。所謂代碼整潔之道,就是如此。
函數(shù)的另一種分類是,有返回值和無返回值,返回值的類型可以是內(nèi)建(build-in)的也可以是自己定義的(struct, union之類),無返回值則是void。
為什么我們十分譴責void main()這種寫法?因為這完全是中國式教育延伸出來的譚式寫法,main函數(shù)的返回值看似無用,實際上是由操作系統(tǒng)接收,在Windows操作系統(tǒng)下也許無甚"大礙"(實際上有),當你使用Linux的過程中你會清晰的發(fā)現(xiàn)一個C語言程序的main返回值關(guān)系到一個系統(tǒng)是否能正常,高效的運行,這里稍微提一句,0在Linux程序管道通信間代表著無錯可行的意思。所以請扔掉void main這種寫法。
為什么我們對 main()這種省略返回值的寫法置有微詞?能發(fā)明這種寫法的人,必定是了解了,在C語言中,如果一個函數(shù)不顯式聲明自己的返回值,那么會被缺省認為是int,但這一步是由編譯器掌控,然而C語言設(shè)計之初便是讓我們對一切盡可能的掌握,而一切不確定因子我們都不應(yīng)該讓它存在。其次有一個原則,能自己做的就不要讓編譯器做。
為什么我們對參數(shù)放空置有不滿(int main())?在C語言中,一個函數(shù)的參數(shù)列表有三種合法形態(tài):
int function(); int function(void); int function(int arg_n); int function(int arg_n, ...);
第一種代表擁有未知個參數(shù),第二種代表沒有參數(shù),第三種代表有一個參數(shù),第四種代表擁有未知個參數(shù),并且第一個參數(shù)類型為int,未知參數(shù)在C語言中有一個解決方案就是,可變長的參數(shù)列表,具體參考C標準庫,在此我們解釋的依據(jù)就是,我們要將一切都掌控在自己的手中,我們不在括號內(nèi)填寫參數(shù),代表著我們認為一開始的意思是它為空,正因此我們就應(yīng)該明確說明它為void,而不該讓它成為一個未知參數(shù)長度的函數(shù),如此在你不小心傳入?yún)?shù)的時候,編譯器也無法發(fā)現(xiàn)錯誤。
int main(int argc, char* argv[]) 和 int main(void)才是我們該寫的C語言標準形式
對于縮進,除了編譯器提供的符號縮進之外,我們可以自己給自己一個規(guī)范(請少用或者不用Tab),比如每一塊代碼相教上一個代碼塊有4格的縮進。
對于學習C語言,請使用.c文件以及C語言編譯器練習以及編寫C程序,請不要再使用C++的文件編寫C語言程序,并且自圓其說為了效率而使用C++的特性在C語言中,我們是祖國的下一代,是祖國的未來,請不要讓自己毀在當下,珍愛編程,遠離清華大學出版社。
之所以如此敘述,并不是因為情緒,而是當真如此,下方代碼:
/*file: test.c*/ #include <stdio.h> #define SIZES 5 int main(void) { int* c_pointer = malloc(SIZES * sizeof(int)); /*發(fā)生了一些事情*/ free(c_pointer); return 0; }
這是一段標準的C語言程序,但是它能在C++個編譯器下編譯運行嗎?換句話說當你將文件擴展名由.c改為.cpp之后,它能編譯通過嗎?答案是不能。
為什么?答案是C++并不支持void*隱式轉(zhuǎn)換為其他類型的指針,但是C語言允許。還有許許多多C于C++不相同的地方,興許有人說C++是C的超集,但我并不這么認為,一門語言的出現(xiàn)便有它的意義所在,關(guān)鍵在于我們?nèi)绾伟l(fā)揮它的最大優(yōu)勢,而不是通過混淆概念來增強實用性。
程序式子的寫法
一個人活在世界上,時時刻刻都注意著自己的言行舉止,而寫程序也是如此,對于一個規(guī)范的能讓別人讀懂的程序而言,我們應(yīng)該盡可能減少阻礙因子,例如:
int main(void) {int complex_int=100; int i,j,k,x; for(int temp=0;temp<complex_int;++temp){k=temp; x=k+complex_int;} printf(complex_int="%d is k=%d x=%d\n",complex_int,k,x); return 0;}
對于上述的代碼,我總是在班級里的同學手下出現(xiàn),但這段代碼除了讓別人困惑以外,自己在調(diào)試的時候也是十分不方便,每每遇到問題了,即便IDE提示了在某處錯誤,你也找不到問題所在,經(jīng)常有人來問我哪里錯了,大部分情況都是少了分號,括號,或者作用域超過,原因在哪?
要是一開始將代碼寫清楚了,這種情況簡直是鳳毛麟角,想遇上都難。對于一個代碼而言,我們應(yīng)該注意讓其變得清晰。
等號兩邊使用空格:
int complex_int = 100;
使用多個變量的聲明定義,或者函數(shù)聲明定義,函數(shù)使用時,注意用空格分開變量:
int i, j, k, x;//但是十分不建議這么聲明難以理解意義的變量 printf("complex_int = %d is k = %d x = %d\n", complex_int, k, x); void present(int arg_1, double arg_2);
對于一個清晰的程序而言,我們要讓每一個步驟清晰且有意義,這就要求我們在編寫程序的時候盡量能讓代碼看起來結(jié)構(gòu)化,或者整體化。盡量讓每個程序式子為一行,如果有特別的需要讓多個式子寫在同一行,可以使用,操作符進行組合,但是會讓程序更難理解,日后調(diào)試的時候也更難發(fā)現(xiàn)錯誤。
/*Style 1*/ for(int temp = 0;temp < complex_int;++temp) { k = temp; x = k + complex_int; } /*Style 2*/ for(int temp = 0;temp < complex_int;++temp){ k = temp; x = k + complex_int; }
對于上方的代碼,是C語言代碼花括號的兩種風格,最好能選擇其中一種作為自己的編程風格,這樣能讓你的程序看起來更加清晰,混合使用的利弊并不好說,關(guān)鍵還是看個人風格。
對于作用域而言,在C語言中有一個經(jīng)常被使用的特例,當一個條件語句,或者循環(huán)只有一條語句的時候,我們常常省略了花括號{},而是僅僅使用一個分號作為結(jié)尾,這在很多情況下讓代碼不再啰嗦:
if(pointo_int == NULL) fprintf(stderr, "The pointer is NULL!\n"); else { printf("%d\n",*pointo_int); pointo_int = pointo_int->next; }
在這段代碼中if語句下方的代碼并沒有使用{}運算符進行指明,但是根據(jù)語法,該語句的確是屬于if語句的作用范圍內(nèi),如果我們此時寫上了{}反而會令代碼看起來過于啰嗦。但是有的時候,這條特性并不是那么的有趣,當使用嵌套功能的時候,還是建議使用{}進行顯式的范圍規(guī)定,而不是使用默認的作用域:
for(int i = 0;i< 10;++i) for(int k = 0;k < 10;++k) while(flag != 1) set_value(arr[i][k]);
這段代碼,看起來十分簡潔,但是確實是一個很大的隱患,當我們要調(diào)試這段代碼的時候,總是需要修改它的構(gòu)造,而這就帶來了潛在的隱患。所以建議在使用嵌套的時候,無論什么情況,都能使用{}進行包裝。
綜上所述,在開始編寫一個標準C語言程序的時候,請先把下面這些東西寫上:
#include <stdio.h> int main(void) { return 0; }
C代碼規(guī)范
命名
只要提到代碼規(guī)范,就不得不說的一個問題。
在一些小的演示程序中,也許費盡心思去構(gòu)思一個 命名 是一件十分傻的行為,但是只要程序上升到你需要嚴正設(shè)計,思考,復查的層次,你就需要好好考慮 命名 這個問題。
函數(shù)命名:
C語言中,我們可以讓下劃線或者詞匯幫助我們表達函數(shù)功能:
前綴:
- set 可以表示設(shè)置一個參數(shù)為某值
- get 可以表示獲取某一個參數(shù)的值
- is 可以表示詢問是否是這種情況
后綴:
- max/min 可以表示某種操作的最大(小)次數(shù)
- cnt 可以表示當前的操作次數(shù)
- key 某種關(guān)鍵值
size_t get_counts(); size_t retry_max(); int is_empty();
需要注意的只是,不要讓命名過于贅述其義,只簡單保留動作以及目的即可,詳細功能可以通過文檔來進行進一步的解釋。
結(jié)構(gòu)體命名:
由于結(jié)構(gòu)體的 標簽,不會污染命名,即標簽不在命名搜索范圍之內(nèi),所以可以放心使用:
有人習慣使用 typedef, 而有人喜歡使用 struct tag obj,后者比較多,但是前者也不失為一種好方法,仁者見仁智者見智。
/*方法1*/ struct inetaddr_4{ int port; char * name; }; struct inetaddr_4 *addr_info; /*方法2*/ typedef struct _addr{ int port; char * name; }inetaddr_4; inetaddr_4 *addr_info_2;
兩者同處一個文件內(nèi)亦不會發(fā)生編譯錯誤。
變量命名
- 所有字符都使用小寫
- 含義多的可以用 _ 進行輔助
- 以 = 為標準進行對齊
- 類型, 變量名左對齊。
等號左右兩端,最少有一個空格。
int main(void) { int counts = 0; inetaddr_4 *addr = NULL; return 0; }
為了防止指針聲明定義時候出錯,將 * 緊貼著變量名總不會出錯。
inetaddr_4 *addr, object, *addr_2;
其中 addr 和 addr_2 是指針,而 object 則是一個棧上的完整對象,并不是指針。
全局變量能少用就少用,必須要用的情況下,可以考慮添加前綴 g_
int g_counts;
#define 命名
- 所有字符都是用大寫,并用 _ 進行分割。
- 如果多于一個語句,使用 do{...}while(0) 進行包裹,防止 ; 錯誤。
#define SWAP(x, y) \ do{ \ x = x + y; \ y = x - y; \ x = x - y; \ }while(0)
當然這個交換宏實際上有一點缺陷,在大后方會提出。此處是代碼規(guī)范,就不重復強調(diào)。
enum 命名
- 所有字符都是用大寫,并用 _ 進行分割
- 與 define 相比,enum適用于同一類型的常量聲明,而不是單一獨立的常量。往往出現(xiàn)都是成組。
格式化代碼
花括號 {}
- 混合使用符合節(jié)儉思想,但會稍微有一點結(jié)構(gòu)紊亂。
- 單一使用能更好讓代碼結(jié)構(gòu)清晰。
- 所謂混合,單一指的是是否一直使用 {} 進行代碼包裹。
- 有人認為 當單一語句的時候不必要添加 {},有的人則習慣添加
- 當作用域超過一個屏幕的時候,可以適當?shù)氖褂米⑨寔碇该?{} 作用域
while(1){ if(tmp == NULL){ break; } else if(fanny == 1){ ... 大概超過了一個屏幕的代碼 } /*else if fanny*/ }/*end while*/
如果是代碼量少的情況下,但嵌套比較多,也可以使用這個方式進行注釋。
括號 ()
有人建議除了函數(shù)調(diào)用以外,在條件語句等類似情況下使用 () 要在關(guān)鍵字后空一格,再接上 ()語句,對于這一點,我個人習慣是不空格,但總有這種說法。
if (space == NULL) { /**TODO**/ } while(1){ /**我習慣于如此寫**/ } strcpy(str1, str2); /**第一種寫法是為了和函數(shù)調(diào)用寫法進行區(qū)分**/ return 0; switch
一定要放一個 default 在最后,即使它永遠不會用到。
每個 case 如果需要使用新變量,可以用 {} 包裹起來,并在里面完成所有操作。
switch(...) { case 1: /**TODO**/ break; case 2: { int new_vari; /**創(chuàng)建新變量則用 {} 包裹起來**/ } break; default: call_error(); }
goto
雖然許多人,許多書都提醒不再使用 goto 關(guān)鍵字,而是使用 setjmp 和 longjmp來取代它,但是這還是那句話,仁者見仁智者見智,如果 goto 能夠讓代碼清晰,那何樂而不為呢,這個觀點也是最近才體會到的(并非我一己之言)。
具體使用可以查詢官方文檔。
語句
- 應(yīng)該讓完整的語句在每一行中,只出現(xiàn)一次。
- 對于變量聲明定義亦是如此
- 原因是這樣能讓文檔更有針對性
頭文件保護
對于頭文件而言,在一個程序中有可能被多次包含(#include),如果缺少頭文件保護,則會發(fā)生編譯錯誤
不要將 _ 作為宏的開頭或者結(jié)尾。
#ifndef VECTOR_H_INCLUDE #define VECTOR_H_INCLUDE /**TODO**/ #endif
宏
C語言的宏有諸多弊端,所以盡量使用 inline 函數(shù)來代替宏。在大后方會有解釋
但是,請不要因此拋棄了宏,比如在 C11 中有一個新興的宏。
變量
第一時刻初始化所有所聲明的變量,因為這么做總沒有壞處,而且能減少出錯的可能。
函數(shù)
函數(shù)應(yīng)該盡可能的短小,一個ANSI屏幕的為最佳。
如果某個循環(huán)帶著空語句,使用 {} 進行掛載,以免出現(xiàn)意外。
while(*is_end++ != '\0') { ; }
雖然是空的循環(huán)體,但是寫出來以免造成誤循環(huán)。
盡量不要讓函數(shù)返回值直接作為條件語句的判斷,這樣會極大降低可讀性
if(is_eof(file) == 0) 好過 if(!is_eof(file))
不要為了方便或者一點點的所謂速度提升(也許根本沒有),而放棄可讀性,使用嵌入式的賦值語句
int add = 10; int num = 11; int thr = 20; add = add + thr; num = add + 20;
不要寫成
num = (add = add + thr) + 20;
浮點數(shù)
- 萬萬記住不要再使用浮點數(shù)比較彼此是否相等或不等。
- 如果把浮點數(shù)用在離散性的數(shù)據(jù)上,比如循環(huán)計數(shù)器,那就...
其他
使用 #if 而不是 #ifdef
可以使用 define() 來代替 #ifdef的功能
#if !define(USERS_DEFINE) #define USERS_DEFINE ... #endif
對于某些大段需要消除的代碼,我們不能使用注釋 /**/,因為注釋不能內(nèi)嵌著注釋(//除外),我們可以使用黑魔法:
#if NOT_DECLARATION /**想要注釋的代碼**/ #endif
不要使用純數(shù)字
意味著,不在使用毫無標記的數(shù)字,因為可能你過了幾個月再看源代碼的時候,你根本不知道這個數(shù)字代表著什么。
而應(yīng)該使用#define 給它一個名字,來說明這個數(shù)字的意義。
您可能感興趣的文章
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用函數(shù)刪除字符
- 04-02c語言的正則匹配函數(shù) c語言正則表達式函數(shù)庫
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言中對數(shù)函數(shù)的表達式 c語言中對數(shù)怎么表達
- 04-02c語言用函數(shù)寫分段 用c語言表示分段函數(shù)
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排序法函數(shù)
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段函數(shù)
- 04-02C語言中怎么打出三角函數(shù) c語言中怎么打出三角函數(shù)的值
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求階乘


閱讀排行
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對數(shù)函數(shù)的表達式 c語言中對
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機閱讀
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 04-02jquery與jsp,用jquery
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-10C#中split用法實例總結(jié)
- 01-10delphi制作wav文件的方法