C程序怎樣組織更有結(jié)構(gòu)性(2007-07-21 17:12:08)
在C語言的應(yīng)用領(lǐng)域,如通訊領(lǐng)域和嵌入式系統(tǒng)領(lǐng)域,一個的軟件項目通常包含很多復(fù)雜的功能,實現(xiàn)這個項目不是一個程序員單槍匹馬可以勝任的,往往需要一個團(tuán)隊的有效合作,另外,在一個以C代碼為主的完整的項目中,經(jīng)常也需要加入一些其他語言的代碼,例如,C代碼和匯編代碼的混合使用,C文件和C++的同時使用。這些都增加了一個軟件項目的復(fù)雜程度,為了提高軟件質(zhì)量,合理組織的各種代碼和文件是非常重要的。
組織代碼和文件的目的是為了使團(tuán)隊合作更加有效,使軟件項目有良好的可擴(kuò)展性、可維護(hù)性、可移植性、可裁減、可測試性,防止錯誤發(fā)生,提高軟件的穩(wěn)定性。通常情況下,軟件項目采用層次化結(jié)構(gòu)和模塊化開發(fā)的方法,例如,一個嵌入式軟件項目可能有驅(qū)動層,操作系統(tǒng)層,功能層,應(yīng)用程序?qū)?每一個層使用它的下層提供的接口,并為它的上層提供調(diào)用接口,模塊則是每一個層中完成一個功能的單元,例如驅(qū)動層的每一個設(shè)備的驅(qū)動就是一個模塊,應(yīng)用層的每個應(yīng)用程序就是一個模塊,模塊使用下層提供的接口和同層其他模塊提供的接口,完成特定功能,為上層和同層的其他模塊提供調(diào)用接口。
這里的接口是指一個功能模塊暴露出來的,提供給其他模塊的訪問具體功能的方法。根據(jù)C語言的特點,使用*.c文件實現(xiàn)模塊的功能,使用*.h文件暴露單元的接口,在*.h文件里聲明外部其他模塊可能是用的函數(shù),數(shù)據(jù)類型,全局變量,類型定義,宏定義和常量定義.外部模塊只需包含*.h文件就可以使用相應(yīng)的功能.當(dāng)然,模塊可以在細(xì)化為子模塊.雖然我們這里說的接口和COM(通用組件模型)里定義的接口不同,但是,根據(jù)COM里對接口的討論,為了使軟件在修改時,一個模塊的修改不會影響到其他模塊的一個模塊的修改不會導(dǎo)致其他模塊也需要修改,所以,接口第一次發(fā)布后,修改*.h文件不能導(dǎo)致使用這個接口的其他模塊需要重新編寫.
根據(jù)C語言的特點,并借鑒一些成熟軟件項目代碼,總結(jié)C項目中代碼文件組織的基本建議:
1,使用層次化和模塊化的軟件開發(fā)模型.每一個模塊只能使用所在層和下一層模塊提供的接口.
2,每個模塊的文件包存在獨立的一個文件夾中.通常情況下,實現(xiàn)一個模塊的文件不止一個,這些相關(guān)的文件應(yīng)該保存在一個文件夾中.
3,用于模塊裁減的條件編譯宏保存在一個獨立的文件里,便于軟件裁減.
4,硬件相關(guān)代碼和操作系統(tǒng)相關(guān)代碼與純C代碼相對獨立保存,以便于軟件移植.
5,聲明和定義分開,使用*.h文件暴露模塊需要提供給外部的函數(shù),宏,類型,常量,全局變量,盡量做到模塊對外部透明,用戶在使用模塊功能時不需要了解具體的實現(xiàn),文件一旦發(fā)布,要修改一定要很慎重,
6,文件夾和文件命名要能夠反映出模塊的功能.
7,正式版本和測試版本使用統(tǒng)一文件,使用宏控制是否產(chǎn)生測試輸出。
8,必要的注釋不可缺少。
理想的情況下,一個可執(zhí)行的模塊提供一個公開的接口,即使用一個*.h文件暴露接口,但是,有時候,一個模塊需要提供不止一個接口,這時,就要為每個定義的接口提供一個公開的接口。在C語言的里,每個C文件是一個模塊,頭文件為使用這個模塊的用戶提供接口,用戶只要包含相應(yīng)的頭文件就可以使用在這個頭文件中暴露的接口。所有的頭文件都建議參考以下的規(guī)則:
1,頭文件中不能有可執(zhí)行代碼,也不能有數(shù)據(jù)的定義,只能有宏、類型(typedef,struct,union,menu),數(shù)據(jù)和函數(shù)的聲明。例如以下的代碼可以包含在頭文件里:
#define NAMESTRING “name”
typedef unsign long word;
menu{
flag1;
flag2;
};
typedef struct{
int x;
int y;
} Piont;
extent Fun(void);
extent int a;
全局變量和函數(shù)的定義不能出現(xiàn)在*.h文件里。例如下面的代碼不能包含在頭文件:
int a;
void Fun1(void)
{
a++;
}
2,頭文件中不能包本地數(shù)據(jù)(模塊自己使用的數(shù)據(jù)或函數(shù),不被其他模塊使用)。這一點相當(dāng)于面向?qū)ο蟪绦蛟O(shè)計里的私有成員,即只有模塊自己使用的函數(shù),數(shù)據(jù),不要用extent在頭文件里聲明,只有模塊自己使用的宏,常量,類型也不要在頭文件里聲明,應(yīng)該在自己的*.c文件里聲明。
3,含一些需要使用的聲明。在頭文件里聲明外部需要使用的數(shù)據(jù),函數(shù),宏,類型。
4,防止被重復(fù)包含。使用下面的宏防止一個頭文件被重復(fù)包含。
#ifndef MY_INCLUDE_H
#define MY_INCLUDE_H
<頭文件內(nèi)容>
#endif
5,包含extern "C",使的程序可以在C++編譯器被編譯
#ifdef __cplusplus
extern "C"{
#endif
<函數(shù)聲明>
#ifdef __cplusplus
}
#enfif
被extern "C"修飾的變量和函數(shù)是按照C語言方式編譯和連接的;未加extern “C”聲明時的編譯方式,作為一種面向?qū)ο蟮恼Z言,C++支持函數(shù)重載,而過程式語言C則不支持。函數(shù)被C++編譯后在符號庫中的名字與C語言的不同。例如,假設(shè)某個函數(shù)的原型為:
void foo( int x, int y );該函數(shù)被C編譯器編譯后在符號庫中的名字為_foo,而C++編譯器則會產(chǎn)生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機(jī)制,生成的新名字稱為“mangled name”)。_foo_int_int這樣的名字包含了函數(shù)名、函數(shù)參數(shù)數(shù)量及類型信息,C++就是靠這種機(jī)制來實現(xiàn)函數(shù)重載的。例如,在C++中,函數(shù)void