總結一下我我的的編程風格及這樣作的緣由吧,實際上是爲了給實驗室寫一個統一的C語言編程規範才寫的。首先聲明,我下面提到的編程規範,是本身給本身定的,不是c語言裏面規定的。程序員
一件事情,作成和作好中間可能隔了十萬八千里。編程
一樣的,代碼的質量也極大程度上反映了編程者的水平高低。爲了讓你們從學習的開始就養成良好的編程習慣,創做出優質的代碼,實驗室編輯這個文檔,做爲你們編程的參考,同時也是對之後編程風格的硬性規定。數組
對於一個團隊來說,制定統一的編程規範,好處是顯而易見的。一般一個項目是由多個成員共同完成,在項目中,常常互相調用組內成員的代碼。若是兩我的的編程習慣和風格差別顯著,那麼將會浪費大量時間在讀懂代碼上。相反,一致而良好的編程規範,會讓合做開發變得輕鬆而高效。異步
衆所周知,C語言是面向過程的語言。也就是說,程序員要對程序的每一步有精準的把握,知道每一條程序語句的執行內容及其結果。於是,代碼的可讀性就顯得尤其重要。這裏的可讀,不只僅是對本身可讀,也要對其餘人可讀。一段只有本身能讀懂的代碼,能夠說價值很低,並且這樣的代碼隨着時間的推移每每本身也讀不懂。而可讀性強的代碼,不只方便移植與交流,更給調試帶來了難以估量的便利。函數
讀一段好的代碼,會有一種讀英語文章的流暢感。儘管C語言提供了有限的32個關鍵字,可是變量、函數等的命名卻提供了較大的自由,這也是咱們將代碼語句化的基礎。試想,若是一段代碼有了主謂賓結構,即便不懂編程的人,也能明白代碼的功能。而這正是咱們代碼編輯者追求的目標。學習
因此,寫好一段代碼,從把你的代碼讀者當編程小白開始!測試
1、文件管理spa
每個作技術的人,不管軟硬件,計算機裏都應該有一個純英文的盤符,注意我是說英文,而不是pinyin。在這個純英文的盤符裏,固然是存放各類技術相關的軟件、程序以及文檔。而這些內容的命名也應該是英文的,包括各個子文件夾。其餘諸如即時通信軟件、遊戲文件等應該放在其餘盤符內。一方面,這樣是對本身英文水平的鍛鍊;另外一方面,也能避免不少在使用國外軟件的時候出現的各類BUG。指針
每個軟件,都應該放在一個獨立的文件夾中。這樣既方便查找,又避免混亂。由於咱們都知道每個軟件完成後,都不只僅是一個exe文件那麼簡單,一般還有各類後綴的文件,而這些咱們都不能刪除。若是打開D盤時,映入眼簾的是幾萬個由不一樣軟件安裝時生成的各類文件,相信給誰都會一臉大寫的懵逼。所以,將不一樣的軟件放在單獨的文件夾下是很是有必要的。調試
不一樣IDE下編寫的程序,也都應該存放在獨立的一個文件夾下。文件夾內,不一樣的工程也應該分別創建文件夾,併合理而精準命名。這樣爲往後的查找帶來極大的便利。
不少IDE在編寫程序文件時,除了要創建Project(工程),還要創建Workspace,即工做空間。工做空間一般是指定一個空間(也就是文件夾),IDE啓動時,自動打開該空間下的各個Project。所以,一個Workspace能夠存放多個Project。這樣咱們就能夠利用Workspace管理本身在該IDE下編寫的各個Project。前提是你創建了Workspace,而Project存放在這個Workspace下。
每個獨立的項目都應該是一個獨立的Project。例如,分別練習編寫流水燈和數碼管的程序時,要分別創建Project,而不能放在一個Project下,除非你的項目同時用到了流水燈和數碼管。這樣作的好處是你能夠Project名稱上精確得到其內容信息,而不會出現程序寫完過一段時間後無從查找的狀況。
2、命名規則
首先說一下總的命名規則:命名必定要用英文。並非由於拼音不能夠,而是由於咱們要與國際接軌,要養成良好的英文書寫習慣。其次,命名中除了「\/:*?」<>|「等系統不容許的字符外,也不能出現除英文字母、下劃線、數字外的其餘字符。若是你想命名成flash LED.c,中間的空格要用下劃線」_「來代替,寫成flash_LED.c。另外,命名中能夠出現必要的數字。
一、文件/文件夾命名
文件命名要精確,文件名要準確反映文件內容。寫的是
文件命名一概使用小寫字母,如keyboard.c。
若有縮寫單詞,則必須大寫,如flash_LED.c、UART.c。其中LED是Light-Emitting Diode(發光二極管)的縮寫,UART是Universal Asynchronous Receiver/Transmitter(通用異步收發器,也就是串口)的縮寫。對於有約定俗成縮寫的單詞,就使用縮寫詞彙。
文件名應使用名詞,而不該該使用動詞。若是文件內容是數據採集,應該命名爲data_collection.c而非data_collect.c。
二、標識符命名
C語言中,能夠定義各類標識符做爲變量名、數組名、函數名、標號及用戶定義對象的名稱。ANSI C規定標識符必須由字母和下劃線開始,隨後能夠出現字母、下劃線和數字。
1)變量命名
變量命名一概小寫,縮寫詞彙用大寫,且所有使用名詞,可使用形容詞修飾,用「_」表從屬關係。由於變量名做爲一個變量的名字,就應該是一個名詞。
局部循環體控制變量用i,j,k。如for(i=0;i<100;i++)。
指針變量用「p_」開頭,後面接指向內容。如指向高度變量的指針,命名爲「*p_height」。請讀者自行區分指針和指針變量的區別。
局部變量儘可能用一個單詞表達清其含義。
全局變量命名時首先寫所屬模塊名稱。例如如一個傳感器文件sensor.c裏面的一個全局變量要表明溫度,則命名爲sensor_temperature。又例如LCD(液晶顯示屏)文件LCD.c中表示LCD狀態的全局變量命名爲LCD_status。由於全局變量每每跨文件調用,如不寫清變量定義位置,當程序龐大,而IDE又不支持一鍵定位時,查找起來很麻煩。即便IDE支持一鍵定位,一個清楚明白的命名,能讓人瞬間讀懂該變量的含義。
2)數組命名
數組命名各單詞首字母大寫,其餘同變量。
讀者可能會有疑問,數組名後面會有[]符號,與變量區別明顯,爲何要用首字母大寫的方式。實際上,在數組名做爲實參傳遞數組首地址時,每每會省略[]符號,應該數組名就是數組的首地址。例如:
unsigned char string[]=」abcdefg」;
printf(「%s」,string);
在以上代碼中,string是一個8位數組(爲何是8位?),在使用printf()函數輸出時,只寫了數組名,顯然這種方式是被容許的。而此時就沒有寫[],在這種狀況下,並不能瞬間知道string是變量仍是數組,而須要參考前面的格式控制符「%s」。在其餘函數中,或許沒有「%s」這樣的格式控制符幫助咱們判斷string究竟是數組仍是變量,咱們只有找到函數的聲明或定義才能知道答案,嚴重影響閱讀。所以有必要對數組和變量加以區分。
3)函數命名
函數命名各單詞首字母大寫,寫成主謂語形式,主語用名詞,謂語用動詞,縮寫詞彙用大寫,用「_」表從屬關係。主語一般爲模塊名,而謂語是描述模塊的動做。由於函數自己就是用來執行一系列的動做的, 結合函數參數,能夠表達通順的語句。舉個簡單的例子:延時函數。定義一個ms級延時函數爲:
void Delay(unsigned int ms);(這個實際上是聲明,函數體不想寫了)
調用時寫:
Delay(500);
很顯然是延時了500ms。而若是再用個宏定義:
#define MS500 500
Delay(MS500);
是否是更一目瞭然呢?
另外還好比串口發送函數命名UART_TX( ),調用時寫成:
UART_TX(time); (一般發送數據Transmit Data簡寫爲TXD)
顯然意思是串口發送時間數據。
再好比設置參考值的函數命名爲REF_Set( ),調用時寫成:
REF_Set(current_voltage);(一般參考值Reference簡寫爲REF)
顯然意思是將當前的電壓設置爲參考值。
主謂格式的命名大大增長了代碼的可能性。
固然,函數命名中必要時能夠出現賓語。這種狀況多出如今函數沒有參數的狀況下。如一個函數的功能是LCD顯示時間,而時間是全局變量,所以這個函數就不須要參數,此時直接定義成void LCD_Display_Time(void)(實際上是聲明,由於沒寫函數體)。
命名時首字母大寫不會和數組混淆嗎?顯然不會,由於函數不管是在定義、聲明仍是調用的時候後面都必須跟着」( )」。
4)標號命名
因爲在硬件編程中標號能夠用循環來代替,因此不多用到。咱們規定標號的命名格式基本同變量,使用所有小寫的名詞,可是隻用一個單詞表示便可。由於標號時候的時候或者前面加了goto,或者後面加了「:」,很容易與變量區分開。何況只是一個定位標誌,因此一個單詞足夠了。
5)自定義類型命名
自定義類型命名主要指使用typedef定義的新類型名,以及結構體類型、共用體類型的類型名(而非該類型的變量名)。
自定義的新類型名,只用一個單詞,首字母大寫。可是定義這種新類型的變量時,命名規則與變量命名規則徹底相同。
請自行體會新類型名與新類型變量的區別。
6)宏定義命名
宏定義命名所有使用大寫字母,單詞數不限。能夠加入數字和下劃線,可是數字不能開頭 。
因爲宏定義的特殊性,對其使用名詞或動詞不做規定。由於宏定義一個函數時,應該是動詞性質,而宏定義一個常數時,應該是名詞性質。
3、表達式書寫
表達式書寫時,最重要的是意義明確。因爲C語言不一樣運算符有着不一樣的結合順序和優先級,所以很容易形成歧義,即實際運算順序與設想運算順序不一樣。除了徹底理解並熟記結合順序與優先級,最簡單的方法就是用括號來明確運算順序——在表達式中,括號的優先級是最高的。
另外,運算符與其操做數之間要空格。如:
a=a+b;
應寫成:
a = a + b;
這樣作可讓表達式顯得不那麼擁擠而增長可讀性,但這不是重點。這樣作的重點是幫咱們避免不少不易識別的錯誤。如:
a=a/*b;
咱們的本意是a除以指針變量b指向的內容,而後將商賦給a。然而殘酷的現實是,編譯器發現了連起來的「/*」,沒錯,這是註釋符。因此,後面的內容都會被註釋掉,直到找到最近的「*/」。
因此咱們應該寫成:
a = a / *b; //指針運算符*應該緊跟指針變量b
或者:
a=a/(*b); //不過即使這樣寫也應該加入空格,便於閱讀
有人會說,如今的IDE會用不一樣的顏色提示註釋內容,因此這樣的錯誤應該不會出現。可是我想說的是,做爲一個立志作合格的工程師的你,會容許本身有不嚴謹的習慣嗎?何況自己咱們的文檔是爲了在C語言語法、詞法基礎上,制定一個編程規範。
另外,有些老版本的C編譯器容許用=+來代替+=的含義,即複合賦值號的兩個符號順序能夠是反的。這樣的話,若是寫出:
a=-1;
本意是將-1賦給a,可是編譯器卻會理解成:
a = a - 1;
顯然意義徹底變了。
有人又會說了,你不是說老版本的C編譯器嘛,我不用不就好了嗎。然而,咱們要考慮代碼的可移植性,就毫不應該容許這樣的想法。
所以,在書寫表達式的時候,不要吝惜你的空格和括號。
還有一點值得說明的是,複合賦值運算符的兩個運算符不能分開。如「+=」不能寫成「+ =」。
4、文件編寫
一、文件劃分
一個簡單的程序,只有幾行到幾十行,放在一個文件內一目瞭然。可是一個較大的項目中可能會有成千上萬行代碼,更有大型程序代碼數以百萬行計。這樣規模的代碼,存放在一個文件內,其恐怖程度請自行想象。
當一個函數的代碼量超過幾十行時,就應該考慮有沒有可能把其中某些代碼提取出來打包成另外一個函數而後調用。一樣的,當一個文件的代碼量超過幾百行時,就應該考慮有沒有可能把一些函數分出來放到別的文件中去。這樣作都是爲了程序的可讀性和方便調試,畢竟一個較短的函數功能測試要比一個長函數容易得多。
然而,一個更好的劃分文件的依據應該是按模塊劃分。固然,相應的劃分函數的依據應該是按功能劃分。也就是說,一個文件存放一個模塊的內容,一個函數完成單一的功能。
二、文件內容
在C語言編程時,有兩種文件。一種是源文件(source file,後綴爲.c),另外一種是頭文件(head file,後綴爲.h)。
C語言的編譯是以c文件爲單位的,所以只有h文件時是沒法編譯的。根據項目規模大小,一個項目能夠由單個c文件構成,也能夠有多個c文件和h文件共同構成。
C語言編譯器在編譯時,一般經歷如下步驟:
預處理→語法、詞法分析→編譯→彙編→連接。
預處理階段,將根據預處理指令來修改c文件內容。其中,預處理指令包括宏定義(#define)、條件編譯指令(#ifdef、#ifndef、#endif等)、頭文件包含指令(#include)、特殊符號(LINE、FILE等)。對於頭文件包含指令來說,其做用是將所包含h文件中的內容替換到包含指令處,固然若是內容中有其餘預處理指令,也會作相應處理。
所以,h文件在編譯時將插入到c文件中。因而可知,h文件能夠出現任何符合c語言語法的內容,可是在實際編程中,咱們顯然不會這樣作,由於這樣作就失去了區分c文件和h文件的意義。
h文件最大的意義是做爲對外接口使用,在發佈庫文件時做用更是明顯。也就是說,h文件的內容用來提供供其餘文件或函數調用的函數原型、變量等內容。下面具體來規定c文件和h文件中應該出現的內容:
源文件(.c) |
頭文件(.h) |
頭文件包含指令(#include) |
頭文件包含指令(#include) |
|
宏定義(#define) |
全部函數定義(必須有函數體,即{ }) 內部函數聲明(static,不能有函數體) |
外部函數聲明(extern,不能有函數體) |
外部變量定義(必須賦初值) 靜態外部變量定義(static,必須賦初值) |
外部變量聲明(extern,不能賦值)
|
|
自定義類型(typedef) |
外部數組定義 靜態內部數組定義(static) |
外部數組聲明(const) |
條件編譯 |
條件編譯 |
由上表能夠看出,h文件內存放的都是對外可見的變量、函數數組等的聲明,宏定義則是對內對外均可以使用,放在這裏主要爲了修改方便。在定義外部變量、數組和函數時,不須要寫extern,由於缺省時默認extern。而聲明外部變量、數組和函數時,必須用extern顯式聲明,這樣是爲了讓代碼更直觀。
函數說明是必需要寫的,寫清函數的入口、出口參數及其功能,以及其它說明,對於代碼維護和改寫能帶來極大的方便。
一般,若是h文件中所有是對外接口,而對應c文件中各函數均不調用本文件中的其餘內容(變量、函數等),也能夠不用包含自身的h文件。
另外,程序編寫時,縮進要規範,要能表達所屬層次關係。每次縮進4個字符,不能隨意縮進。
關於函數體或組合語句使用{}的格式,常見的有兩種格式:
int main( ){
}
或者:
int main( )
{
}
本人比較偏向第一種,由於能夠節省行數,讓程序緊湊。可是這個問題見仁見智,有人以爲第一種不如第二種對齊方式井井有條。因此這個就讓兩種方式並存吧。由於其餘問題不涉及審美習慣,只要規定好你們執行就行了,這個畢竟涉及到每一個人的審美不一樣。
h文件中必須在開頭和末尾寫條件編譯:
#ifndef __全大寫文件名_H__ (或者寫成:全大寫文件名_H__)
#ifndef __全大寫文件名_H__
…(文件內容)
#enif
這樣作是爲了防止屢次包含,保證在編譯時前面已經替換過該頭文件,後面將再也不替換,不然有些內容可能重複定義。
下面用代碼示例:
<protocol.h>:(每個h文件中必須有√標註的內容)
#include <msp430x14x.h> //頭文件包含
#ifndef __PROTOCOL_H__ //條件編譯 √
#define __PROTOCOL_H__ //條件編譯 √
//#define MONITOR_TERMINAL //條件編譯
#define MONITOR_NODE1 //條件編譯
//#define MONITOR_NODE2 //條件編譯
#define MATCHING_CODE 0x55 //宏定義
#define HOST_ADDRESS 0x40 //宏定義
#define NODE_1 0x41 //宏定義
#define NODE_2 0x42 //宏定義
typedef struct { //自定義類型
float start_bit;
float TXD_data;
float stop_bit;
}TX_Data;
extern unsigned char Tx_Data_Packet[]; //外部數組聲明
extern unsigned char Rx_Data_Packet[]; //外部數組聲明
extern unsigned char protocol_set_flag; //外部變量聲明
extern unsigned char Extract_Data(void); //外部函數聲明
#endif //條件編譯 √
<protocol.c>:
#include<math.h> //頭文件包含,系統庫函數用<>
#include"protocol.h" //頭文件包含,系統庫函數用「」
Static unsigned char easy_delay(void); //內部函數聲明
unsigned char protocol_set_flag = 0; //外部變量定義
unsigned char Tx_Data_Packet[6] = {'0','1','2','3','4','5'}; //外部數組定義
unsignedchar Rx_Data_Packet[6] = {'0','1','2','3','4','5'}; //外部數組定義
Static char temp_Packet[6] = {'0','1','2','3','4','5'}; //靜態外部數組定義,只能本文件使用
/********************************************************
*名 稱:Extract_Data()
*功 能:提取接收到的數據幀
*入口參數:無
*出口參數:1-成功,0-失敗
*說 明:
********************************************************/
unsigned char Extract_Data(void){
unsigned char temp = 0;
temp = Rx_FIFO_ReadChar();
if( temp == MATCHING_CODE ){
UART_TX_OPEN();
Rx_Data_Packet[0] = temp;
Rx_Data_Packet[1] = Rx_FIFO_ReadChar(); //來源
Rx_Data_Packet[2] = Rx_FIFO_ReadChar(); //去向
Rx_Data_Packet[3] = Rx_FIFO_ReadChar(); //光照+溫度高
Rx_Data_Packet[4] = Rx_FIFO_ReadChar(); //溫度低
Rx_Data_Packet[5] = '\0';
return (1);
}
else return (0);
} //外部函數定義,必須在前面寫函數說明
/********************************************************
*名 稱: easy_delay()
*功 能:簡單延時
*入口參數:無
*出口參數:無
*說 明:
********************************************************/
Static unsigned char easy_delay(void){
unsigned int i = 0;
for( i=0; i<1000 ; i++);
} //內部函數定義,必須在前面寫函數說明,且在本文件前部聲明以便閱讀
這兩個文件都是從編者曾經寫的代碼中截取出來的,有些部分是爲了演示內容如今添加進去的,源代碼中不存在,請你們沒必要在乎細節,關鍵領會兩個文件中應該出現的內容,均在後面用註釋的方式做了說明。
Notice:
本文中出現的不少字符,爲了美觀和直觀,中英文輸入法混用,或者加多個空格。你們在編程時,切記使用英文半角輸入法,並且無論你加多少空格或製表符,編譯器都按一個處理。