?
?
引言
當項目小組做一個相對較復雜的工程時,意味著你不再獨自單干。而是和小組成員分工合作,這就要求小組成員各自負責一部分工程。比如你可能只是負責通訊或者顯示這一塊。 ?這個時候,你就應該將自己的這一塊程序寫成一個模塊,單獨調試,留出接口供其它模塊調用。最后,小組成員都將自己負責的模塊寫完并調試無誤后,由項目組長進行組合調試。 ?像這些場合就要求程序必須模塊化。模塊化的好處是很多的,不僅僅是便于分工,它還有助于程序的調試,有利于程序結構的劃分,還能增加程序的可讀性和可移植性。
要說的話
?初學者往往搞不懂如何模塊化編程,其實它是簡單易學,而且又是組織良好程序結構行之有效的方法之一。 ?本文將先大概講一下模塊化的方法和注意事項,最后將以初學者使用最廣的keil c編譯器為例,給出模塊化編程的詳細步驟。 ?模塊化程序設計應該理解以下概述:模塊即是一個.c 文件和一個.h 文件的結合,頭文件(.h)中是對于該模塊接口的聲明;
這一條概括了模塊化的實現方法和實質:將一個功能模塊的代碼單獨編寫成一個.c文件,然后把該模塊的接口函數放在.h文件中.舉例:假如你用到液晶顯示,那么你可能會寫一個液晶驅動模塊,以實現字符、漢字和圖像的現實,命名為: led_device.c,該模塊的.c文件大體可以寫成: ?#include … … //定義變量 unsigned char value;//全局變量 … //定義函數 //這是本模塊第一個函數,起到延時作用,只供本模塊的函數調用,所以用到static關鍵字修飾 /********************延時子程序************************/ static void delay (uint us) //delay time {} //這是本模塊的第二個函數,要在其他模塊中調用 /*********************寫字符程序************************** ** 功能:向LCD寫入字符 ** 參數:dat_comm 為1寫入的是數據,為0寫入的是指令 content 為寫入的數字或指令 ******************************************************/ void wr_lcd (uchar dat_comm,uchar content) {} …… …… /***************************** END Files***********************************/ 注:此處只寫出這兩個函數,第一個延時函數的作用范圍是模塊內,第二個,它是其它模塊需要的。為了簡化,此處并沒有寫出函數體. ?.h文件中給出模塊的接口.在上面的例子中, 向LCD寫入字符函數:wr_lcd (uchar dat_comm,uchar content)就是一個接口函數,因為其它模塊會調用它,那么.h文件中就必須將這個函數聲明為外部函數(使用extrun關鍵字修飾),另一個延時函數:void delay (uint us)只是在本模塊中使用(本地函數,用static關鍵字修飾),因此它是不需要放到.h文件中的。 ?.h文件格式如下:
//聲明全局變量 extern unsigned char value; //聲明接口函數 extern void wr_lcd (uchar dat_comm,uchar content); //向LCD寫入字符 …… /***************************** END Files***********************************/ 這里注意三點: ?
-
在keil 編譯器中,extern這個關鍵字即使不聲明,編譯器也不會報錯,且程序運行良好,但不保證使用其它編譯器也如此。強烈建議加上,養成良好的編程規范。
-
.c文件中的函數只有其它模塊使用時才會出現在.h文件中,像本地延時函數static void delay (uint us)即使出現在.h文件中也是在做無用功,因為其它模塊根本不去調用它,實際上也調用不了它(static關鍵字的限制作用)。
-
注意本句最后一定要加分號”;”,相信有不少同學遇到過這個奇怪的編譯器報錯: error C132: 'xxxx': not in formal parameter list,這個錯誤其實是.h的函數聲明的最后少了分號的緣故。
#include“lcd_device.h //包含液晶驅動程序頭文件,之后就可以在該.c文件中調用//lcd_device.h中的全局函數,使用液晶驅動程序里的全局//變量(如果有的話)。 … //調用向LCD寫入字符函數 wr_lcd (0x01,0x30); … //對全局變量賦值 value=0xff; …
某模塊提供給其它模塊調用的外部函數及數據需在.h 中文件中冠以extern 關鍵字聲明;
這句話在上面的例子中已經有體現,即某模塊提供給其它模塊調用的外部函數和全局變量需在.h 中文件中冠以extern 關鍵字聲明。 ?下面重點說一下全局變量的使用。使用模塊化編程的一個難點(相對于新手)就是全局變量的設定,初學者往往很難想通模塊與模塊公用的變量是如何實現的,常規的做法就是本句提到的,在.h文件中外部數據冠以extern關鍵字聲明。比如上例的變量value就是一個全局變量,若是某個模塊也使用這個變量,則和使用外部函數一樣,只需在使用的模塊.c文件中包含#include“lcd_device.h”即可。 ?另一種處理模塊間全局變量的方法來自于嵌入式操作系統uCOS-II,這個操作系統處理全局變量的方法比較特殊,也比較難以理解,但學會之后妙用無窮,這個方法只需用在頭文件中定義一次。方法為: ?在定義所有全局變量(uCOS-II將所有全局變量定義在一個.h文件內)的.h頭文件中:#ifdef xxx_GLOBALS #define xxx_EXT #else #define xxx_EXT extern #endif ?.H 文件中每個全局變量都加上了xxx_EXT的前綴。xxx 代表模塊的名字。 ?該模塊的.C文件中有以下定義:
#define xxx_GLOBALS #include "includes.h" ?當編譯器處理.C文件時,它強制xxx_EXT(在相應.H文件中可以找到)為空,(因為xxx_GLOBALS已經定義)。 ?所以編譯器給每個全局變量分配內存空間,而當編譯器處理其他.C 文件時,xxx_GLOBAL沒有定義,xxx_EXT 被定義為extern,這樣用戶就可以調用外部全局變量。為了說明這個概念,可以參見uC/OS_II.H,其中包括以下定義: ?
#ifdef OS_GLOBALS #define OS_EXT #else #define OS_EXT extern #endif OS_EXT INT32U OSIdleCtr; OS_EXT INT32U OSIdleCtrRun; OS_EXT INT32U OSIdleCtrMax; ?同時,uCOS_II.H 中有以下定義:
#define OS_GLOBALS #include “includes.h” ?當編譯器處理uCOS_II.C 時,它使得頭文件變成如下所示,因為OS_EXT 被設置為空。
INT32U OSIdleCtr; INT32U OSIdleCtrRun; INT32U OSIdleCtrMax; ?這樣編譯器就會將這些全局變量分配在內存中。當編譯器處理其他.C 文件時,頭文件變成了如下的樣子,因為OS_GLOBAL沒有定義,所以OS_EXT 被定義為extern。
extern INT32U OSIdleCtr; extern INT32U OSIdleCtrRun; extern INT32U OSIdleCtrMax; ?在這種情況下,不產生內存分配,而任何 .C文件都可以使用這些變量。這樣的就只需在 .H文件中定義一次就可以了。
模塊內的函數和全局變量需在.c 文件開頭冠以static 關鍵字聲明;
這句話主要講述了關鍵字static的作用。Static是一個相當重要的關鍵字,他能對函數和變量做一些約束,而且可以傳遞一些信息。比如上例在LCD驅動模塊.c文件中定義的延時函數static void delay (uint us),這個函數冠以static修飾,一方面是限定了函數的作用范圍只是在本模塊中起作用,另一方面也給人傳達這樣的信息:該函數不會被其他模塊調用。 ?下面詳細說一下這個關鍵字的作用,在C 語言中,關鍵字static 有三個明顯的作用:-
在函數體,一個被聲明為靜態的變量在這一函數被調用過程中維持其值不變。
-
在模塊內(但在函數體外),一個被聲明為靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
-
在模塊內,一個被聲明為靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地范圍內使用。
永遠不要在.h 文件中定義變量!
?比較一下代碼: ?代碼一:/*module1.h*/ int a = 5; /* 在模塊1 的.h 文件中定義int a */ /*module1 .c*/ #include "module1.h" /* 在模塊1 中包含模塊1 的.h 文件 */ /*module2 .c*/ #include "module1.h" /* 在模塊2 中包含模塊1 的.h 文件 */ /*module3 .c*/ #include "module1.h" /* 在模塊3 中包含模塊1 的.h 文件 */ ?以上程序的結果是在模塊1、2、3 中都定義了整型變量a,a 在不同的模塊中對應不同的地址元,這個世界上從來不需要這樣的程序。正確的做法是: ?代碼二:
/*module1.h*/ extern int a; /* 在模塊1 的.h 文件中聲明int a */ /*module1 .c*/ #include "module1.h" /* 在模塊1 中包含模塊1 的.h 文件 */ int a = 5; /* 在模塊1 的.c 文件中定義int a */ /*module2 .c*/ #include "module1.h" /* 在模塊2 中包含模塊1 的.h 文件 */ /*module3 .c*/ #include "module1.h" /* 在模塊3 中包含模塊1 的.h 文件 */ ?這樣如果模塊1、2、3 操作a 的話,對應的是同一片內存單元。 ?注:一個嵌入式系統通常包括兩類(注意是兩類,不是兩個)模塊: ?
-
硬件驅動模塊,一種特定硬件對應一個模塊;
-
軟件功能模塊,其模塊的劃分應滿足低偶合、高內聚的要求。
?
?
審核編輯:湯梓紅
評論
查看更多