在一個程式的編寫過程中,隨著程式碼量的增加,如果把所有的語句都寫到 main 函式中,一方面程式會顯得的比較亂,另外一個方面,當同一個功能需要在不同地方執行時,我們就得再重複寫一遍相同的語句。此時,如果把一些零碎的功能單獨 寫成一個函式,在需要它們時只需進行一些簡單的函式呼叫,這樣既有助於程式結構的清晰條理,又可以避免大塊的程式碼重複。
方法/步驟
在實際工程專案中,一個程式通常都是由很多個子程式模組組成的,一個模組實現一個特定的功能,在 C 語言中,這個模組就用函式來表示。一個 C 程式一般由一個主函式和若干個其他函式構成。主函式可以呼叫其它函式,其它函式也可以相互呼叫,但其它函式不能呼叫主函式。在我們的 51 微控制器程式中,還有中斷服務函式,是當相應的中斷到來後自動呼叫的,不需要也不能由其它函式來呼叫。
函式呼叫的一般形式是: 函式名 (實參列表);函式名就是需要呼叫的函式的名稱,實參列表就是根據實際需求呼叫函式要傳遞給被呼叫函式的引數列表,不需要傳遞引數時只保留括號就可以了,傳遞多個引數時引數之間要用逗號隔開。
那麼我先舉例看一下函式呼叫使程式結構更加條理清晰方面的作用。回顧一下圖 6-1 所示的程式流程圖和為實現它而編寫的程式程式碼,相對來說這個主函式的結構就比較複雜了,
很難一眼看清楚它的執行流程。那麼如果我們把其中最重要的兩件事——秒計數和數碼管動態掃描功能都用單獨的函式來實現會怎樣呢?來看程式。
#include
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //數碼管顯示字元轉換表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //數碼管顯示緩衝區,初值 0xFF 確保啟動時都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
void SecondCount();
void LedRefresh();
void main(){
ENLED = 0; //使能 U3,選擇控制數碼管
ADDR3 = 1; //因為需要動態改變 ADDR0-2 的值,所以不需要再初始化了
TMOD = 0x01; //設定 T0 為模式 1
TH0 = 0xFC; //為 T0 賦初值 0xFC67,定時 1ms
TL0 = 0x67;
TR0 = 1; //啟動 T0
while (1){
if (TF0 == 1){ //判斷 T0 是否溢位
TF0 = 0; //T0 溢位後,清零中斷標誌
TH0 = 0xFC; //並重新賦初值
TL0 = 0x67;
SecondCount(); //呼叫秒計數函式
LedRefresh(); //呼叫顯示重新整理函式
}
}
}
/* 秒計數函式,每秒進行一次秒數+1,並轉換為數碼管顯示字元 */
void SecondCount(){
static unsigned int cnt = 0; //記錄 T0 中斷次數
static unsigned long sec = 0; //記錄經過的秒數
cnt++; //計數值自加 1
if (cnt >= 1000){ //判斷 T0 溢位是否達到 1000 次
cnt = 0; //達到 1000 次後計數值清零
sec++; //秒計數自加 1
LedBuff[0] = LedChar[sec%10];
LedBuff[1] = LedChar[sec/10%10];
LedBuff[2] = LedChar[sec/100%10];
LedBuff[3] = LedChar[sec/1000%10];
LedBuff[4] = LedChar[sec/10000%10];
LedBuff[5] = LedChar[sec/100000%10];
}
}
/* 數碼管動態掃描重新整理函式 */
void LedRefresh(){
static unsigned char i = 0; //動態掃描的索引
switch (i){
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
default: break;
}
}
看一下,主函式的結構是不是清晰的多了——每隔 1ms 就去幹兩件事,至於這兩件事是什麼交由各自的函式去實現。還請大家注意一點:原來程式中的 i、cnt、sec 這三個變數在放到單獨的函式中後,都加了 static 關鍵字而變成了靜態變數。因為原來的 main()永遠不會結束所以它們的值也總是得到保持的,但現在它們在各自的功能函式內,如不加 static 修飾那麼每次函式被呼叫時它們的值就都成了初值了,藉此也把靜態變數再加深一下理解吧。當然,這是我們刻意把程式功能做了這樣的劃分,主要目的還是來講解函式的呼叫,對於這個程式即使你不劃分函式也複雜不到哪裡去,但繼續學下去你就能領會到劃分功能函式的必要了。現在我們還是把注意力放在學習函式呼叫上,有以下幾點需要大家注意:1) 函式呼叫的時候,不需要加函式型別。我們在主函式內呼叫 SecondCount()和LedRefresh()時都沒有加 void。
2) 呼叫函式與被呼叫函式的位置關係,C 語言規定:函式在被呼叫之前,必須先被定義或宣告。意思就是說:在一個檔案中,一個函式應該先定義,然後才能被呼叫,也就是呼叫函式應位於被呼叫函式的下 方。但是作為一種通常的程式設計規範,我們推薦 main 函式寫在最前面(因為它起到提綱挈領的作用),其後再定義各個功能函式,而中斷函式則寫在檔案的最後。那麼主函式要呼叫定義在它之後的函式怎麼辦呢?我們 就在檔案開頭,所有函式定義之前,開闢一塊區域,叫做函式宣告區,用來把被呼叫的函式宣告一下,如此,該函式就可以被隨意呼叫了。如上述例程所示。3) 函式宣告的時候必須加函式型別,函式的形式引數,最後加上一個分號表示結束。函式宣告行與函式定義行的唯一區別就是最後的分號,其它的都必須保持一致。這 點請尤其注意,初學者很容易因粗心大意而搞錯分號或是修改了定義行中的形參卻忘了修改宣告行中的形參,導致程式編譯不過。