- Keil Cx51 V7.0單片機(jī)高級(jí)語(yǔ)言編程與μVision2應(yīng)用實(shí)踐
- 徐愛(ài)鈞 彭秀華編著
- 1068字
- 2018-12-29 19:18:30
第3章 函數(shù)
函數(shù)是C語(yǔ)言中的一種基本模塊,實(shí)際上,一個(gè)C語(yǔ)言程序就是由若干個(gè)模塊化的函數(shù)所構(gòu)成的。前面我們已經(jīng)看到,C語(yǔ)言程序總是由主函數(shù)main()開(kāi)始,main()函數(shù)是一個(gè)控制程序流程的特殊函數(shù),它是程序的起點(diǎn)。在進(jìn)行程序設(shè)計(jì)的過(guò)程中,如果所設(shè)計(jì)的程序較大,一般應(yīng)將其分成若干個(gè)子程序模塊,每個(gè)模塊完成一種特定的功能。在C語(yǔ)言中,子程序是用函數(shù)來(lái)實(shí)現(xiàn)的。對(duì)于一些需要經(jīng)常使用的子程序可以設(shè)計(jì)成一個(gè)專門的函數(shù)庫(kù),以供反復(fù)調(diào)用。此外,Keil Cx51編譯器還提供了豐富的運(yùn)行庫(kù)函數(shù),用戶可以根據(jù)需要隨時(shí)調(diào)用。這種模塊化的程序設(shè)計(jì)方法,可以大大提高編程效率和速度。
3.1 函數(shù)的定義
從用戶的角度來(lái)看,有兩種函數(shù):標(biāo)準(zhǔn)庫(kù)函數(shù)和用戶自定義函數(shù)。標(biāo)準(zhǔn)庫(kù)函數(shù)是Keil Cx51編譯器提供的,不需要用戶進(jìn)行定義,可以直接調(diào)用。用戶自定義函數(shù)是用戶根據(jù)自己需要編寫的能實(shí)現(xiàn)特定功能的函數(shù),它必須先進(jìn)行定義之后才能調(diào)用。函數(shù)定義的一般形式為:
函數(shù)類型 函數(shù)名(形式參數(shù)表) 形式參數(shù)說(shuō)明 { 局部變量定義 函數(shù)體語(yǔ)句 }
其中,“函數(shù)類型”說(shuō)明了自定義函數(shù)返回值的類型。
“函數(shù)名”是用標(biāo)識(shí)符表示的自定義函數(shù)名字。
“形式參數(shù)表”中列出的是在主調(diào)用函數(shù)與被調(diào)用函數(shù)之間傳遞數(shù)據(jù)的形式參數(shù),形式參數(shù)的類型必須加以說(shuō)明。ANSI C標(biāo)準(zhǔn)允許在形式參數(shù)表中對(duì)形式參數(shù)的類型進(jìn)行說(shuō)明。如果定義的是無(wú)參函數(shù),可以沒(méi)有形式參數(shù)表,但圓括號(hào)不能省略。
“局部變量定義”是對(duì)在函數(shù)內(nèi)部使用的局部變量進(jìn)行定義。
“函數(shù)體語(yǔ)句”是為完成該函數(shù)的特定功能而設(shè)置的各種語(yǔ)句。
如果定義函數(shù)時(shí)只給出一對(duì)花括號(hào){}而不給出其局部變量和函數(shù)體語(yǔ)句,則該函數(shù)為“空函數(shù)”,這種空函數(shù)也是合法的。在進(jìn)行C語(yǔ)言模塊化程序設(shè)計(jì)時(shí),各模塊的功能可通過(guò)函數(shù)來(lái)實(shí)現(xiàn)。開(kāi)始時(shí)只設(shè)計(jì)最基本的模塊,其他作為擴(kuò)充功能在以后需要時(shí)再加上。編寫程序時(shí)可在將來(lái)準(zhǔn)備擴(kuò)充的地方寫上一個(gè)空函數(shù),這樣可使程序的結(jié)構(gòu)清晰,可讀性好,而且易于擴(kuò)充。
例3.1:定義一個(gè)計(jì)算整數(shù)的正整數(shù)次冪的函數(shù)。
int power(x, n) int x, n; { int i, p; p=1; for(i=1; i<=n; ++i) p=p*x; return(p); }
這里定義了一個(gè)返回值為整型值的函數(shù)power(),它有兩個(gè)形式參數(shù):x,n。形式參數(shù)的作用是接受從主調(diào)用函數(shù)傳遞過(guò)來(lái)的實(shí)際參數(shù)的值。上例中形式參數(shù)x和n被說(shuō)明為int類型。花括號(hào)以內(nèi)的部分是自定義函數(shù)的函數(shù)體。上例中在函數(shù)體內(nèi)定義了兩個(gè)局部變量i和p,它們均為整型數(shù)據(jù)。
需要注意的是,形式參數(shù)的說(shuō)明與函數(shù)體內(nèi)的局部變量定義是完全不同的兩個(gè)部分,前者應(yīng)寫在花括號(hào)的外面,而后者是函數(shù)體的一個(gè)組成部分,必須寫在花括號(hào)的里面。為了不發(fā)生混淆,ANSI C標(biāo)準(zhǔn)允許在形式參數(shù)表中對(duì)形式參數(shù)的類型進(jìn)行說(shuō)明,如上例可寫成:int power(int x, int n)。
在函數(shù)體中可以根據(jù)用戶自己的需要,設(shè)置各種不同的語(yǔ)句。這些語(yǔ)句應(yīng)能完成所需要的功能。上例在函數(shù)體中用一個(gè)for循環(huán)結(jié)構(gòu)完成一個(gè)整數(shù)的正整數(shù)次冪的計(jì)算,計(jì)算結(jié)果賦值給變量p。函數(shù)體中最后一條語(yǔ)句return(p)的作用是將p的值返回到主調(diào)用函數(shù)中去。return語(yǔ)句后面圓括號(hào)中的值稱為函數(shù)的返回值,圓括號(hào)可以省略,即return p和return(p)是等價(jià)的。
由于p是函數(shù)的返回值,因此在函數(shù)體中進(jìn)行變量定義時(shí),應(yīng)將變量p的類型定義得與函數(shù)本身的類型相一致。如果二者類型不一致,則函數(shù)調(diào)用時(shí)的返回值可能發(fā)生錯(cuò)誤。如果函數(shù)體中沒(méi)有return語(yǔ)句,則該函數(shù)由函數(shù)體最后面的右閉花括號(hào)“}”返回。在這種情況下,函數(shù)的返回值是不確定的。
對(duì)于不需要有返回值的函數(shù),可以將該函數(shù)定義為void類型(空類型)。對(duì)于上例,如果定義為:void power(int x, int n),則可將函數(shù)體中的return語(yǔ)句去掉,這樣,編譯器會(huì)保證在函數(shù)調(diào)用結(jié)束時(shí)不使函數(shù)返回任何值。為了使程序減少出錯(cuò),保證函數(shù)的正確調(diào)用,凡是不要求有返回值的函數(shù),都應(yīng)將其定義成void類型。
例3.2:不同函數(shù)的定義方法。
char fun1(x, y) /* 定義一個(gè)char型函數(shù) */ int x; /* 說(shuō)明形式參數(shù)的類型 */ char y; { char z; /* 定義函數(shù)內(nèi)部的局部變量 */ z=x+y; /* 函數(shù)體語(yǔ)句 */ return(z); /* 返回函數(shù)的值z(mì),注意變量z與函數(shù)本身 } 的類型均為char型 */ int fun2(float a, float b) /* 定義一個(gè)int型函數(shù),在形式參數(shù)表中說(shuō) { 明形式參數(shù)的類型 */ int x; /* 定義函數(shù)內(nèi)部的局部變量 */ x=a-b; /* 函數(shù)體語(yǔ)句 */ return(x); /* 返回函數(shù)的值x,注意變量x與函數(shù)本身 } 的類型均為int型 */ long fun3() /* 定義一個(gè)long型函數(shù),它沒(méi)有形式參數(shù) */ { long x; /* 定義函數(shù)內(nèi)部的局部變量 */ int i, j; x=i*j; /* 函數(shù)體語(yǔ)句 */ return(x); /* 返回函數(shù)的值x,注意變量x與函數(shù)本身 } 的類型均為long型 */ void fun4(char a, char b) /* 定義一個(gè)無(wú)返回值的void型函數(shù) */ { char x; /* 局部變量定義 */ x=a+b; /* 函數(shù)體語(yǔ)句 */ } /* 函數(shù)不需要返回值,省略return語(yǔ)句 */ void fun5( ) /* 定義一個(gè)空函數(shù) */ { }
3.2 函數(shù)的調(diào)用
3.2.1 函數(shù)的調(diào)用形式
C語(yǔ)言程序中函數(shù)是可以互相調(diào)用的。所謂函數(shù)調(diào)用就是在一個(gè)函數(shù)體中引用另外一個(gè)已經(jīng)定義了的函數(shù),前者稱為主調(diào)用函數(shù),后者稱為被調(diào)用函數(shù)。函數(shù)調(diào)用的一般形式為:
函數(shù)名(實(shí)際參數(shù)表)
其中,“函數(shù)名”指出被調(diào)用的函數(shù)。
“實(shí)際參數(shù)表”中可以包含多個(gè)實(shí)際參數(shù),各個(gè)參數(shù)之間用逗號(hào)隔開(kāi)。實(shí)際參數(shù)的值被傳遞給被調(diào)用函數(shù)中的形式參數(shù)。需要注意的是,函數(shù)調(diào)用中的實(shí)際參數(shù)與函數(shù)定義中的形式參數(shù)必須在個(gè)數(shù)、類型及順序上嚴(yán)格保持一致,以便將實(shí)際參數(shù)的值正確地傳遞給形式參數(shù)。否則在函數(shù)調(diào)用時(shí)會(huì)產(chǎn)生意想不到的結(jié)果。如果調(diào)用的是無(wú)參函數(shù),則可以沒(méi)有實(shí)際參數(shù)表,但圓括號(hào)不能省略。
在C語(yǔ)言中可以采用三種方式完成函數(shù)的調(diào)用。
(1)函數(shù)語(yǔ)句
在主調(diào)函數(shù)中將函數(shù)調(diào)用作為一條語(yǔ)句,例如:
fun1();
這是無(wú)參調(diào)用,它不要求被調(diào)用函數(shù)返回一個(gè)確定的值,只要求它完成一定的操作。
(2)函數(shù)表達(dá)式
在主調(diào)函數(shù)中將函數(shù)調(diào)用作為一個(gè)運(yùn)算對(duì)象直接出現(xiàn)在表達(dá)式中,這種表達(dá)式稱為函數(shù)表達(dá)式。例如:
c = power(x,n) + power(y,m);
這其實(shí)是一個(gè)賦值語(yǔ)句,它包括兩個(gè)函數(shù)調(diào)用,每個(gè)函數(shù)調(diào)用都有一個(gè)返回值,將兩個(gè)返回值相加的結(jié)果,賦值給變量c。因此這種函數(shù)調(diào)用方式要求被調(diào)函數(shù)返回一個(gè)確定的值。
(3)函數(shù)參數(shù)
在主調(diào)函數(shù)中將函數(shù)調(diào)用作為另一個(gè)函數(shù)調(diào)用的實(shí)際參數(shù)。例如:
y=power(power(i, j), k);
其中,函數(shù)調(diào)用power(i, j)放在另一個(gè)函數(shù)調(diào)用power(power(i, j), k)的實(shí)際參數(shù)表中,以其返回值作為另一個(gè)函數(shù)調(diào)用的實(shí)際參數(shù)。這種在調(diào)用一個(gè)函數(shù)的過(guò)程中又調(diào)用了另外一個(gè)函數(shù)的方式,稱為嵌套函數(shù)調(diào)用。在輸出一個(gè)函數(shù)的值時(shí)經(jīng)常采用這種方法,例如:
printf("%d", power(i,j));
其中,函數(shù)調(diào)用power(i,j)是作為printf()函數(shù)的一個(gè)實(shí)際參數(shù)處理的,它也屬于嵌套函數(shù)調(diào)用方式。
3.2.2 對(duì)被調(diào)用函數(shù)的說(shuō)明
與使用變量一樣,在調(diào)用一個(gè)函數(shù)之前(包括標(biāo)準(zhǔn)庫(kù)函數(shù)),必須對(duì)該函數(shù)的類型進(jìn)行說(shuō)明,即“先說(shuō)明,后調(diào)用”。如果調(diào)用的是庫(kù)函數(shù),一般應(yīng)在程序的開(kāi)始處用預(yù)處理命令#include將有關(guān)函數(shù)說(shuō)明的頭文件包含進(jìn)來(lái)。例如前面例子中經(jīng)常出現(xiàn)的預(yù)處理命令#include<stdio.h>,就是將與庫(kù)輸出函數(shù)printf()有關(guān)的頭文件stdio.h包含到程序文件中來(lái)。頭文件“stdio.h”中有關(guān)于庫(kù)輸入輸出函數(shù)的一些說(shuō)明信息,如果不使用這個(gè)包含命令,庫(kù)輸入輸出函數(shù)就無(wú)法被正確地調(diào)用。
如果調(diào)用的是用戶自定義函數(shù),而且該函數(shù)與調(diào)用它的主調(diào)函數(shù)在同一個(gè)文件中,一般應(yīng)該在主調(diào)函數(shù)中對(duì)被調(diào)用函數(shù)的類型進(jìn)行說(shuō)明。函數(shù)說(shuō)明的一般形式為:
類型標(biāo)識(shí)符 被調(diào)用的函數(shù)名(形式參數(shù)表);
其中,“類型標(biāo)識(shí)符”說(shuō)明了函數(shù)返回值的類型。
“形式參數(shù)表”中說(shuō)明各個(gè)形式參數(shù)的類型。
需要注意的是,函數(shù)的說(shuō)明與函數(shù)的定義是完全不同的。函數(shù)的定義是對(duì)函數(shù)功能的確立,它是一個(gè)完整的函數(shù)單位。而函數(shù)的說(shuō)明,只是說(shuō)明了函數(shù)返回值的類型。二者在書寫形式上也不一樣,函數(shù)說(shuō)明結(jié)束時(shí)在圓括號(hào)的后面需要有一個(gè)分號(hào)“;”作為結(jié)束標(biāo)志,而在函數(shù)定義時(shí),被定義函數(shù)名的圓括號(hào)后面沒(méi)有分號(hào)“;”,即函數(shù)定義還未結(jié)束,后面應(yīng)接著書寫形式參數(shù)說(shuō)明和被定義的函數(shù)體部分。
如果被調(diào)函數(shù)是在主調(diào)函數(shù)前面定義的,或者已經(jīng)在程序文件的開(kāi)始處說(shuō)明了所有被調(diào)函數(shù)的類型,在這兩種情況下可以不必再在主調(diào)函數(shù)中對(duì)被調(diào)函數(shù)進(jìn)行說(shuō)明。也可以將所有用戶自定義函數(shù)的說(shuō)明另存為一個(gè)專門的頭文件,需要時(shí)用#include將其包含到主程序中去。
C語(yǔ)言程序中不允許在一個(gè)函數(shù)定義的內(nèi)部包括另一個(gè)函數(shù)的定義,即不允許嵌套函數(shù)定義。但是允許在調(diào)用一個(gè)函數(shù)的過(guò)程中包含另一個(gè)函數(shù)調(diào)用,即嵌套函數(shù)調(diào)用在C語(yǔ)言程序中是允許的。
例3.3:函數(shù)調(diào)用的例子。
#include <stdio.h> int Max(int x, int y); /* 對(duì)被調(diào)用函數(shù)進(jìn)行說(shuō)明 */ void main() { /* 主函數(shù) */ int a, b; /* 主函數(shù)的局部變量定義 */ printf("input a and b: \n"); scanf("%d %d", &a, &b); /* 調(diào)用庫(kù)輸入函數(shù),從鍵盤獲得a、b的值 */ printf("Max=%d", Max(a, b)); /* 調(diào)用庫(kù)輸出函數(shù),輸出a、b中較大者的值*/ while(1); } int Max(int x, int y) { /* 功能函數(shù)定義 */ int z; /* 局部變量定義 */ if(x>y) /* 函數(shù)體語(yǔ)句 */ z=x; else z=y; return(z); }
程序執(zhí)行結(jié)果:
input a and b: 123 456 回車 Max=456
在這個(gè)例子中,主函數(shù)main()先調(diào)用庫(kù)輸入函數(shù)sacnf(),從鍵盤輸入兩個(gè)值分別賦值給局部變量a和b,然后調(diào)用庫(kù)輸出函數(shù)printf()將a、b中較大者輸出。在調(diào)用庫(kù)輸出函數(shù)printf()的過(guò)程中又調(diào)用了自定義功能函數(shù)Max(),將鍵盤輸入的a、b的值作為實(shí)際參數(shù)傳遞給Max()函數(shù)中的形式參數(shù)x、y。在Max()函數(shù)中對(duì)實(shí)際輸入值進(jìn)行比較以獲得較大者的值。這也是一個(gè)嵌套函數(shù)調(diào)用的例子,圖3.1是例3.3程序中函數(shù)調(diào)用的執(zhí)行過(guò)程。

圖3.1 函數(shù)的嵌套調(diào)用過(guò)程
3.2.3 函數(shù)的參數(shù)與返回值
通常在進(jìn)行函數(shù)調(diào)用時(shí),主調(diào)用函數(shù)與被調(diào)用函數(shù)之間具有數(shù)據(jù)傳遞關(guān)系。這種數(shù)據(jù)傳遞是通過(guò)函數(shù)的參數(shù)實(shí)現(xiàn)的。在定義一個(gè)函數(shù)時(shí),位于函數(shù)名后面圓括號(hào)中的變量名稱為“形式參數(shù)”,而在調(diào)用函數(shù)時(shí),函數(shù)名后面括號(hào)中的表達(dá)式稱為“實(shí)際參數(shù)”。形式參數(shù)在未發(fā)生函數(shù)調(diào)用之前,不占用內(nèi)存單元,因而也是沒(méi)有值的。只有在發(fā)生函數(shù)調(diào)用時(shí)才為它分配內(nèi)存單元,同時(shí)獲得從主調(diào)用函數(shù)中實(shí)際參數(shù)傳遞過(guò)來(lái)的值。函數(shù)調(diào)用結(jié)束后,它所占用的內(nèi)存單元也被釋放。
實(shí)際參數(shù)可以是常數(shù),也可以是變量或表達(dá)式,但要求它們具有確定的值。進(jìn)行函數(shù)調(diào)用時(shí),主調(diào)用函數(shù)將實(shí)際參數(shù)的值傳遞給被調(diào)用函數(shù)中的形式參數(shù)。為了完成正確的參數(shù)傳遞,實(shí)際參數(shù)的類型必須與形式參數(shù)的類型一致,如果兩者不一致,則會(huì)發(fā)生“類型不匹配”錯(cuò)誤。
例3.4:計(jì)算一個(gè)整數(shù)的正整數(shù)次冪。
#include<stdio.h> main() { int power(int x, int n); int a, b, c; printf("please input X and n: \n"); scanf("%d %d",&a, &b); c=power(a, b); printf("\n%d to the power of %d is: %d", a, b, c); while(1); } int power(int x, int n) { int i, p; p=1; for(i=1; i<=n; ++i) p=p*x; return(p); }
程序執(zhí)行結(jié)果:
please input X and n: 5, 3 回車 5 to the power of 3 is 125
在這個(gè)程序中定義了一個(gè)計(jì)算整數(shù)的正整數(shù)次冪的函數(shù)int power(int x, int n);它有兩個(gè)整型的形式參數(shù)x和n。在程序開(kāi)始時(shí),變量x和n是不占用內(nèi)存單元的,因此也是沒(méi)有值的。在主函數(shù)main()中先從鍵盤輸入兩個(gè)整數(shù)值a和b,然后通過(guò)函數(shù)調(diào)用語(yǔ)句c=power(a, b);將實(shí)際參數(shù)a和b的值傳遞給被調(diào)用函數(shù)power()中的形式參數(shù)。調(diào)用發(fā)生時(shí),形式參數(shù)變量x和n被賦以實(shí)際參數(shù)a和b的值,從而使函數(shù)power()能按實(shí)際參數(shù)的值進(jìn)行計(jì)算。從這個(gè)例子可以看到,形式參數(shù)和實(shí)際參數(shù)可以不同名,但它們的類型必須要一致。
一般情況下,希望通過(guò)函數(shù)調(diào)用使主調(diào)用函數(shù)獲得一個(gè)確定的值,這就是函數(shù)的返回值。例如,上例中的函數(shù)調(diào)用語(yǔ)句c=power(a, b);就是將函數(shù)power()的返回值賦給變量c。函數(shù)的返回值是通過(guò)return語(yǔ)句獲得的,如果希望從被調(diào)用函數(shù)中帶回一個(gè)值到主調(diào)用函數(shù),被調(diào)用函數(shù)中必須包含有return語(yǔ)句。
一個(gè)函數(shù)中可以有一個(gè)以上的return語(yǔ)句,執(zhí)行到哪一個(gè)return語(yǔ)句,哪一個(gè)return語(yǔ)句起作用。return后面可以跟一個(gè)表達(dá)式,例如,return(x>y? x: y);這種寫法只用一條return語(yǔ)句即可同時(shí)完成表達(dá)式的計(jì)算和函數(shù)值的返回。return后面還可以跟另外一個(gè)已定義了的函數(shù)名,例如:return keyval(rdkey);采用這種寫法可實(shí)現(xiàn)函數(shù)的嵌套調(diào)用,即在函數(shù)返回的同時(shí)調(diào)用另一個(gè)函數(shù)。
函數(shù)返回值的類型確定了該函數(shù)的類型,因此在定義一個(gè)函數(shù)時(shí),函數(shù)本身的類型應(yīng)與return語(yǔ)句中變量或表達(dá)式的類型一致。例如,上例中power()函數(shù)被定義為int類型,return語(yǔ)句中的變量p也被定義為int類型。如果函數(shù)類型與return語(yǔ)句中表達(dá)式的值類型不一致,則以函數(shù)的類型為準(zhǔn)。對(duì)于返回的數(shù)值數(shù)據(jù)可以自動(dòng)進(jìn)行類型轉(zhuǎn)換,即函數(shù)的類型決定返回值的類型。如果不需要被調(diào)用函數(shù)返回一個(gè)確定的值,則可以不要return語(yǔ)句,同時(shí)應(yīng)將被調(diào)用函數(shù)定義成void類型。事實(shí)上,main()函數(shù)就是一個(gè)典型的沒(méi)有返回值的函數(shù),因此可以將其寫成void main()的形式。由于void類型的函數(shù)沒(méi)有return語(yǔ)句,因此在一個(gè)void類型函數(shù)的調(diào)用結(jié)束時(shí),將從該函數(shù)的最后一個(gè)花括號(hào)處返回到主調(diào)用函數(shù)。
例3.5:使用void類型函數(shù)的例子。
#include<stdio.h> void main() { /* void類型的主函數(shù) */ void prn_char(char x); /* 功能函數(shù)說(shuō)明 */ prn_char('w'); /* 功能函數(shù)調(diào)用 */ while(1); } void prn_char(char x) /* 將功能函數(shù)定義為void類型,無(wú)返回值 */ { printf("%c has ASCII value %bd\n",x,x); } /* void型功能函數(shù)從此處返回主調(diào)用函數(shù) */
程序執(zhí)行結(jié)果:
w has ASCII value 119
3.2.4 實(shí)際參數(shù)的傳遞方式
在進(jìn)行函數(shù)調(diào)用時(shí),必須用主調(diào)函數(shù)中的實(shí)際參數(shù)來(lái)替換被調(diào)函數(shù)中的形式參數(shù),這就是所謂的參數(shù)傳遞。在C語(yǔ)言中,對(duì)于不同類型的實(shí)際參數(shù),有三種不同的參數(shù)傳遞方式。
(1)基本類型的實(shí)際參數(shù)傳遞
當(dāng)函數(shù)的參數(shù)是基本類型的變量時(shí),主調(diào)函數(shù)將實(shí)際參數(shù)的值傳遞給被調(diào)函數(shù)中的形式參數(shù),這種方式稱為值傳遞。前面講過(guò),函數(shù)中的形式參數(shù)在未發(fā)生數(shù)調(diào)用之前是不占用內(nèi)存單元的,只有在進(jìn)行函數(shù)調(diào)用時(shí)才為其分配臨時(shí)存儲(chǔ)單元。而函數(shù)的實(shí)際參數(shù)是要占用確定的存儲(chǔ)單元的。值傳遞方式是將實(shí)際參數(shù)的值傳遞到為被調(diào)函數(shù)中形式參數(shù)分配的臨時(shí)存儲(chǔ)單元中,函數(shù)調(diào)用結(jié)束后,臨時(shí)存儲(chǔ)單元被釋放,形式參數(shù)的值也就不復(fù)存在,但實(shí)際參數(shù)所占用的存儲(chǔ)單元保持原來(lái)的值不變。這種參數(shù)傳遞方式在執(zhí)行被調(diào)函數(shù)時(shí),如果形式參數(shù)的值發(fā)生變化,可以不必?fù)?dān)心主調(diào)函數(shù)中實(shí)際參數(shù)的值會(huì)受到影響。因此值傳遞是一種單向傳遞。
(2)數(shù)組類型的實(shí)際參數(shù)傳遞
當(dāng)函數(shù)的參數(shù)是數(shù)組類型的變量時(shí),主調(diào)函數(shù)將實(shí)際參數(shù)數(shù)組的起始地址傳遞到被調(diào)函數(shù)中形式參數(shù)的臨時(shí)存儲(chǔ)單元,這種方式稱為地址傳遞。地址傳遞方式在執(zhí)行被調(diào)函數(shù)時(shí),形式參數(shù)通過(guò)實(shí)際參數(shù)傳來(lái)的地址,直接到主調(diào)函數(shù)中去存取相應(yīng)的數(shù)組元素,故形式參數(shù)的變化會(huì)改變實(shí)際參數(shù)的值。因此地址傳遞是一種雙向傳遞。
(3)指針類型的實(shí)際參數(shù)傳遞
當(dāng)函數(shù)的參數(shù)是指針類型的變量時(shí),主調(diào)函數(shù)將實(shí)際參數(shù)的地址傳遞給被調(diào)函數(shù)中形式參數(shù)的臨時(shí)存儲(chǔ)單元,因此也屬于地址傳遞。在執(zhí)行被調(diào)函數(shù)時(shí),也是直接到主調(diào)函數(shù)中去訪問(wèn)實(shí)際參數(shù)變量,在這種情況下,形式參數(shù)的變化會(huì)改變實(shí)際參數(shù)的值。
前面介紹的一些函數(shù)調(diào)用中所涉及的都是基本類型的實(shí)際參數(shù)傳遞,這種參數(shù)傳遞方式比較容易理解和應(yīng)用。關(guān)于數(shù)組類型和指針類型實(shí)際參數(shù)的傳遞較為復(fù)雜,將在第4章中詳細(xì)介紹。
3.3 函數(shù)的遞歸調(diào)用與再入函數(shù)
如果在調(diào)用一個(gè)函數(shù)的過(guò)程中又間接或直接地調(diào)用該函數(shù)本身,稱為函數(shù)的遞歸調(diào)用。例如,計(jì)算階乘函數(shù)f (n)=n!,可以先計(jì)算f (n-1)=(n-1)!,而計(jì)算f (n-1)時(shí)又可以先計(jì)算f (n-2)=(n-2)!,這就是遞歸算法。再入函數(shù)是一種可以在函數(shù)體內(nèi)直接或間接調(diào)用其自身的一種函數(shù),顯然再入函數(shù)是可以進(jìn)行遞歸調(diào)用的。
Keil Cx51編譯器采用一個(gè)擴(kuò)展關(guān)鍵字reentrant,作為定義函數(shù)時(shí)的選項(xiàng),需要將一個(gè)函數(shù)定義為再入函數(shù)時(shí),只要在函數(shù)名后面加上關(guān)鍵字reentrant即可:
函數(shù)類型 函數(shù)名(形式參數(shù)表)[reentrant]
再入函數(shù)可被遞歸調(diào)用,無(wú)論何時(shí),包括中斷服務(wù)函數(shù)在內(nèi)的任何函數(shù)都可調(diào)用再入函數(shù)。與非再入函數(shù)的參數(shù)傳遞和局部變量的存儲(chǔ)分配方法不同,Cx51編譯器為再入函數(shù)生成一個(gè)模擬棧,通過(guò)這個(gè)模擬棧來(lái)完成參數(shù)傳遞和存放局部變量。模擬棧所在的存儲(chǔ)器空間根據(jù)再入函數(shù)存儲(chǔ)器模式的不同,可以是DATA、PDATA或XDATA存儲(chǔ)器空間。當(dāng)程序中包含有多種存儲(chǔ)器模式的再入函數(shù)時(shí),Cx51編譯器為每種模式單獨(dú)建立一個(gè)模擬棧并獨(dú)立管理各自的棧指針。對(duì)于再入函數(shù)有如下規(guī)定。
① 再入函數(shù)不能傳送bit類型的參數(shù),也不能定義一個(gè)局部位變量,再入函數(shù)不能包括位操作以及8051系列單片機(jī)的可位尋址區(qū)。
② 與PL/M51兼容的函數(shù)不能具有reentrant屬性,也不能調(diào)用再入函數(shù)。
③ 在編譯時(shí)存儲(chǔ)器模式的基礎(chǔ)上為再入函數(shù)在內(nèi)部或外部存儲(chǔ)器中建立一個(gè)模擬堆棧區(qū),稱為再入棧。在Small模式下再入棧位于IDATA區(qū),在compact模式下再入棧位于PDATA區(qū),在Large模式下再入棧位于XDATA區(qū)。再入函數(shù)的局部變量及參數(shù)都被放在再入棧中,從而使再入函數(shù)可以進(jìn)行遞歸調(diào)用。而非再入函數(shù)的局部變量被放在再入棧之外的暫存區(qū)內(nèi),如果對(duì)非再入函數(shù)進(jìn)行遞歸調(diào)用,則上次調(diào)用時(shí)使用的局部變量數(shù)據(jù)將被覆蓋。
④ 在同一個(gè)程序中可以定義和使用不同存儲(chǔ)器模式的再入函數(shù),任意模式的再入函數(shù)不能調(diào)用不同模式的再入函數(shù),但可任意調(diào)用非再入函數(shù)。
⑤ 在參數(shù)的傳遞上,實(shí)際參數(shù)可以傳遞給間接調(diào)用的再入函數(shù)。無(wú)再入屬性的間接調(diào)用函數(shù)不能包含調(diào)用參數(shù),但是可以使用定義的全局變量來(lái)進(jìn)行參數(shù)傳遞。
例3.6:利用函數(shù)的遞歸調(diào)用計(jì)算整數(shù)的階乘。
#include<stdio.h> fac(int n) reentrant { if (n<1) return(1); else return(n*fac(n-1)); } main() { int n; printf("please input a number: \n"); scanf("%d", &n); printf("fac(%d)=%d\n", n, fac(n)); while(1); }
程序執(zhí)行結(jié)果:
please input a number 3 回車 fac(3)=6
在這個(gè)程序中定義了一個(gè)再入函數(shù)fac(n),它是用來(lái)計(jì)算階乘n!的函數(shù)。在fac()的函數(shù)體中又調(diào)用了fac()函數(shù)本身,因此這是一種函數(shù)的遞歸調(diào)用。再入函數(shù)在進(jìn)行遞歸調(diào)用時(shí),新的局部變量和參數(shù)在再入棧中重新分配存儲(chǔ)單元,并以新的變量重新開(kāi)始執(zhí)行。每次遞歸調(diào)用返回時(shí),前面壓入的局部變量和參數(shù)會(huì)從再入棧中彈出,并恢復(fù)到上次調(diào)用自身的地方繼續(xù)執(zhí)行。如果是非再入函數(shù)進(jìn)行遞歸調(diào)用,每次調(diào)用函數(shù)自身時(shí),上次調(diào)用時(shí)使用的局部變量數(shù)據(jù)將被覆蓋,因而在遞歸調(diào)用結(jié)束時(shí)不能得到正確的結(jié)果。對(duì)于例3.6的程序,如果將函數(shù)fac(n)定義成非再入函數(shù),則程序的運(yùn)行結(jié)果為0,顯然這是不正確的。
采用函數(shù)的遞歸調(diào)用可使程序的結(jié)構(gòu)緊湊,但是遞歸調(diào)用要求采用再入函數(shù),以便利用再入棧來(lái)保存有關(guān)的局部變量數(shù)據(jù),從而要占據(jù)較大的內(nèi)存空間。另外遞歸調(diào)用時(shí)對(duì)函數(shù)的處理速度也比較慢,因此一般情況下應(yīng)盡量避免采用函數(shù)遞歸調(diào)用,定義函數(shù)時(shí)應(yīng)盡量避免使用再入屬性。
3.4 中斷服務(wù)函數(shù)與寄存器組定義
Keil Cx51編譯器支持在C語(yǔ)言源程序中直接編寫8051單片機(jī)的中斷服務(wù)函數(shù)程序,從而減輕了采用匯編語(yǔ)言編寫中斷服務(wù)程序的煩瑣程度。為了在C語(yǔ)言源程序中直接編寫中斷服務(wù)函數(shù)的需要,Keil Cx51編譯器對(duì)函數(shù)的定義進(jìn)行了擴(kuò)展,增加了一個(gè)擴(kuò)展關(guān)鍵字interrupt,它是函數(shù)定義時(shí)的一個(gè)選項(xiàng),加上這個(gè)選項(xiàng)即可以將一個(gè)函數(shù)定義成中斷服務(wù)函數(shù)。定義中斷服務(wù)函數(shù)的一般形式為:
函數(shù)類型 函數(shù)名(形式參數(shù)表)[interrupt n][using n]
關(guān)鍵字interrupt后面的n是中斷號(hào),n的取值范圍為0~31。編譯器從8n+3處產(chǎn)生中斷向量,具體的中斷號(hào)n和中斷向量取決于8051系列單片機(jī)芯片型號(hào),常用中斷源和中斷向量如表3-1所示。
表3-1 常用中斷號(hào)與中斷向量

8051系列單片機(jī)可以在片內(nèi)RAM中使用4個(gè)不同的工作寄存器組,每個(gè)寄存器組中包含8個(gè)工作寄存器(R0~R7)。Keil Cx51編譯器擴(kuò)展了一個(gè)關(guān)鍵字using,專門用來(lái)選擇8051單片機(jī)中不同的工作寄存器組。using后面的n是一個(gè)0~3的常整數(shù),分別選中4個(gè)不同的工作寄存器組。在定義一個(gè)函數(shù)時(shí)using是一個(gè)選項(xiàng),如果不用該選項(xiàng),則由編譯器自動(dòng)選擇一個(gè)寄存器組作絕對(duì)寄存器組訪問(wèn)。需要注意的是,關(guān)鍵字using和interrupt的后面都不允許跟帶運(yùn)算符的表達(dá)式。
關(guān)鍵字using對(duì)函數(shù)目標(biāo)代碼的影響如下:在函數(shù)的入口處將當(dāng)前工作寄存器組保護(hù)到堆棧中;指定的工作寄存器內(nèi)容不會(huì)改變;函數(shù)退出之前將被保護(hù)的工作寄存器組從堆棧中恢復(fù)。
使用關(guān)鍵字using在函數(shù)中確定一個(gè)工作寄存器組時(shí)必須十分小心,要保證任何寄存器組的切換都只在仔細(xì)控制的區(qū)域內(nèi)發(fā)生,如果不做到這一點(diǎn)將產(chǎn)生不正確的函數(shù)結(jié)果。另外還要注意,帶using屬性的函數(shù)原則上不能返回bit類型的值。并且關(guān)鍵字using不允許用于外部函數(shù)。
關(guān)鍵字interrupt也不允許用于外部函數(shù),它對(duì)中斷函數(shù)目標(biāo)代碼的影響如下:在進(jìn)入中斷函數(shù)時(shí),特殊功能寄存器ACC、B、DPH、DPL、PSW將被保存入棧;如果不使用關(guān)鍵字using進(jìn)行工作寄存器組切換,則將中斷函數(shù)中所用到的全部工作寄存器都入棧保存;函數(shù)退出之前所有的寄存器內(nèi)容出棧恢復(fù);中斷函數(shù)由8051單片機(jī)指令RETI結(jié)束。
下面給出一個(gè)帶有寄存器組切換的中斷函數(shù)定義的例子,該例中還給出了C51編譯器所生成的8051單片機(jī)的指令代碼。
例3.7:帶有寄存器組切換的中斷函數(shù)定義。
stmt level source 1 #pragma cd 2 #include <reg51.h> 3 extern void alfunc(bit b0); 4 extern bit alarm; 5 int DTIMES; 6 char bdata flag; 7 sbit flag0=flag^0; 8 int dtime1=0x0a; 9 10 void int0 () interrupt 0 using 1 { 11 1 TR1=0; 12 1 flag0=!flag0; 13 1 DTIMES=dtime1; 14 1 dtime1=0; 15 1 TR1=1; 16 1 } 17 18 void timer1 () interrupt 3 using 3 { 19 1 alfunc(alarm=1); 20 1 TH1=0x3c; 21 1 TL1=0xB0; 22 1 dtime1=dtime1+1; 23 1 if (dtime1==0) 24 1 { 25 2 P0=0; 26 2 } 27 1 } 28 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION int0 (BEGIN) ;SOURCE LINE # 10 ;SOURCE LINE # 11 0000 C28E CLR TR1 ;SOURCE LINE # 12 0002 B200 R CPL flag0 ;SOURCE LINE # 13 0004850000 R MOV DTIMES,dtime1 0007850000 R MOV DTIMES+01H,dtime1+01H ;SOURCE LINE # 14 000A 750000 R MOV dtime1,#00H 000D 750000 R MOV dtime1+01H,#00H ;SOURCE LINE # 15 0010 D28E SETB TR1 ;SOURCE LINE # 16 0012 32 RETI ;FUNCTION int0 (END) ;FUNCTION timer1 (BEGIN) 0000 C0E0 PUSH ACC 0002 C0F0 PUSH B 0004 C083 PUSH DPH 0006 C082 PUSH DPL 0008 C0D0 PUSH PSW 000A 75D018 MOV PSW,#018H ;SOURCE LINE # 18 ;SOURCE LINE # 19 000D D3 SETB C 000E 9200 E MOV alarm,C 00109200 E MOV ?alfunc?BIT,C 0012120000 E LCALL alfunc ;SOURCE LINE # 20 0015758D3C MOV TH1,#03CH ;SOURCE LINE # 21 0018758BB0 MOV TL1,#0B0H ;SOURCE LINE # 22 001B 0500 R INC dtime1+01H 001D E500 R MOV A,dtime1+01H 001F 7002 JNZ ?C0004 00210500 R INC dtime1 0023 ?C0004: ;SOURCE LINE # 23 00234500 R ORL A,dtime1 00257002 JNZ ?C0003 ;SOURCE LINE # 24 ;SOURCE LINE # 25 0027 F580 MOV P0,A ;SOURCE LINE # 26 ;SOURCE LINE # 27 0029 ?C0003: 0029 D0D0 POP PSW 002B D082 POP DPL 002D D083 POP DPH 002F D0F0 POP B 0031 D0E0 POP ACC 0033 32 RETI ;FUNCTION timer1 (END)
編寫8051單片機(jī)中斷函數(shù)時(shí)應(yīng)遵循以下規(guī)則。
① 中斷函數(shù)不能進(jìn)行參數(shù)傳遞,如果中斷函數(shù)中包含任何參數(shù)聲明都將導(dǎo)致編譯出錯(cuò)。
② 中斷函數(shù)沒(méi)有返回值,如果企圖定義一個(gè)返回值將得到不正確的結(jié)果。因此建議在定義中斷函數(shù)時(shí)將其定義為void類型,以明確說(shuō)明沒(méi)有返回值。
③ 在任何情況下都不能直接調(diào)用中斷函數(shù),否則會(huì)產(chǎn)生編譯錯(cuò)誤。因?yàn)橹袛嗪瘮?shù)的退出是由8051單片機(jī)指令RETI完成的,RETI指令影響8051單片機(jī)的硬件中斷系統(tǒng)。如果在沒(méi)有實(shí)際中斷請(qǐng)求的情況下直接調(diào)用中斷函數(shù),RETI指令的操作結(jié)果會(huì)產(chǎn)生一個(gè)致命的錯(cuò)誤。
④ 如果在中斷函數(shù)中調(diào)用了其他函數(shù),則被調(diào)用函數(shù)所使用的寄存器組必須與中斷函數(shù)相同。用戶必須保證按要求使用相同的寄存器組,否則會(huì)產(chǎn)生不正確的結(jié)果,這一點(diǎn)必須引起足夠的注意。如果定義中斷函數(shù)時(shí)沒(méi)有使用using選項(xiàng),則由編譯器自動(dòng)選擇一個(gè)寄存器組作絕對(duì)寄存器組訪問(wèn)。另外,由于中斷的產(chǎn)生不可預(yù)測(cè),中斷函數(shù)對(duì)其他函數(shù)的調(diào)用可能形成遞規(guī)調(diào)用,需要時(shí)可將被中斷函數(shù)所調(diào)用的其他函數(shù)定義成再入函數(shù)。
⑤ Keil Cx51編譯器從絕對(duì)地址8n+3處產(chǎn)生一個(gè)中斷向量,其中n為中斷號(hào)。該向量包含一個(gè)到中斷函數(shù)入口地址的絕對(duì)跳傳。在對(duì)源程序編譯時(shí),可用編譯控制命令NOINTVECTOR抑制中斷向量的產(chǎn)生,從而使用戶有能力從獨(dú)立的匯編程序模塊中提供中斷向量。
3.5 函數(shù)變量的存儲(chǔ)方式
3.5.1 局部變量與全局變量
按照變量的有效作用范圍可劃分為局部變量和全局變量。局部變量是在一個(gè)函數(shù)內(nèi)部定義的變量,該變量只在定義它的那個(gè)函數(shù)范圍以內(nèi)有效。在此函數(shù)之外局部變量即失去意義,因而也就不能使用這些變量了。不同的函數(shù)可以使用相同的局部變量名,由于它們的作用范圍不同,不會(huì)相互干擾。函數(shù)的形式參數(shù)也屬于局部變量。在一個(gè)函數(shù)內(nèi)部的復(fù)合語(yǔ)句中也可以定義局部變量,該局部變量只在該復(fù)合語(yǔ)句中有效。
全局變量是在函數(shù)外部定義的變量,又稱為外部變量。全局變量可以為多個(gè)函數(shù)共同使用,其有效作用范圍是從它定義的位置開(kāi)始到整個(gè)程序文件結(jié)束。如果全局變量定義在一個(gè)程序文件的開(kāi)始處,則在整個(gè)程序文件范圍內(nèi)都可以使用它。如果一個(gè)全局變量不是在程序文件的開(kāi)始處定義的,但又希望在它的定義點(diǎn)之前的函數(shù)中引用該變量,這時(shí)應(yīng)在引用該變量的函數(shù)中用關(guān)鍵字extern將其說(shuō)明為“外部變量”。另外,如果在一個(gè)程序模塊文件中引用另一個(gè)程序模塊文件中定義的變量時(shí),也必須用extern進(jìn)行說(shuō)明。
外部變量說(shuō)明與外部變量定義是不相同的。外部變量定義只能有一次,定義的位置在所有函數(shù)之外。而同一個(gè)程序文件中的外部變量說(shuō)明可以有多次,說(shuō)明的位置在需要引用該變量的函數(shù)之內(nèi)。外部變量說(shuō)明的作用只是聲明該變量是一個(gè)已經(jīng)在外部定義過(guò)了的變量而已。
如果在同一個(gè)程序文件中,全局變量與局部變量同名,則在局部變量的有效作用范圍之內(nèi),全局變量不起作用。換句話說(shuō),局部變量的優(yōu)先級(jí)比全局變量高。在編寫C語(yǔ)言程序時(shí),不是特別必要的地方一般不要使用全局變量,而應(yīng)當(dāng)盡可能地使用局部變量。這是因?yàn)榫植孔兞恐辉谑褂盟鼤r(shí),才為其分配內(nèi)存單元,而全局變量在整個(gè)程序的執(zhí)行過(guò)程中都要占用內(nèi)存單元。另外,如果使用全局變量過(guò)多,在各個(gè)函數(shù)執(zhí)行時(shí)都有可能改變?nèi)肿兞康闹担谷藗冸y以清楚地判斷出在各個(gè)程序執(zhí)行點(diǎn)處全局變量的值,這樣會(huì)使降低程序的通用性和可讀性。
還有一點(diǎn)需要說(shuō)明,如果程序中的全局變量在定義時(shí)賦給了初值,按ANSI C標(biāo)準(zhǔn)規(guī)定,在程序進(jìn)入main()函數(shù)之前必須先對(duì)該全局變量進(jìn)行初始化。這是由連接定位器BL51對(duì)目標(biāo)程序連接定位時(shí),在最后生成的目標(biāo)代碼中自動(dòng)加入一段運(yùn)行庫(kù)“INIT.OBJ”來(lái)實(shí)現(xiàn)的。由于增加了這么一段代碼,程序的長(zhǎng)度會(huì)增加,運(yùn)行速度也會(huì)受到影響。因此要限制使用全局變量。
下面通過(guò)一個(gè)例子來(lái)說(shuō)明局部變量與全局變量的區(qū)別。
例3.8:局部變量與全局變量的區(qū)別。
#include<stdio.h> int a=3, b=5; /* 定義a、b為全局變量,并賦以初值 */ max(int a, int b) { /* 形參a、b為局部變量 */ int c; /* 定義c為局部變量 */ c=a>b? a:b; return(c); } main() { int a=8; /* 定義a為局部變量 */ printf("%d", max(a,b)); while(1); }
程序執(zhí)行結(jié)果:
8
這個(gè)程序中故意使用了相同的變量名a和b,請(qǐng)讀者仔細(xì)區(qū)別它們的作用范圍。程序的第一行將a和b定義成全局變量并且賦了初值,由于具有初值的全局變量需要先行初始化,因此讀者如果用dScope51對(duì)這個(gè)例子程序進(jìn)行調(diào)試,可以看到程序在進(jìn)入main()函數(shù)之前,除了要執(zhí)行一段啟動(dòng)程序STARTUP的代碼之外,還需要執(zhí)行一段全局變量初始化程序INIT的代碼。
第二行開(kāi)始是定義一個(gè)求最大值函數(shù)max(),其作用是求得a和b中較大者的值。這里的a和b是max()函數(shù)的形式參數(shù),屬于局部變量。外部變量a和b在函數(shù)max()內(nèi)部不起作用,即形式參數(shù)a和b的值不再是3和5,它們的值是通過(guò)主調(diào)函數(shù)中的實(shí)際參數(shù)傳遞過(guò)來(lái)的。
程序的最后四行是main()函數(shù),在main()函數(shù)內(nèi)部定義了一個(gè)局部變量a并賦值為8,全局變量a在這里不起作用,而全局變量b在此范圍內(nèi)有效。因此printf()函數(shù)中的max(a, b)相當(dāng)于max(8, 5),故程序的最后執(zhí)行結(jié)果為8。
3.5.2 變量的存儲(chǔ)種類
按變量的有效作用范圍可以將其劃分為局部變量和全局變量;還可以按變量的存儲(chǔ)方式為其劃分存儲(chǔ)種類。在C語(yǔ)言中變量有四種存儲(chǔ)種類,即自動(dòng)變量(auto)、外部變量(extern)、靜態(tài)變量(static)和寄存器變量(register)。這四種存儲(chǔ)種類與全局變量和局部變量之間的關(guān)系如圖3.2所示。

圖3.2 變量的存儲(chǔ)種類
1.自動(dòng)變量(auto)
定義一個(gè)變量時(shí),在變量名前面加上存儲(chǔ)種類說(shuō)明符“auto”,即將該變量定義為自動(dòng)變量。自動(dòng)變量是C語(yǔ)言中使用最為廣泛的一類變量。按照默認(rèn)規(guī)則,在函數(shù)體內(nèi)部或復(fù)合語(yǔ)句內(nèi)部定義的變量,如果省略存儲(chǔ)種類說(shuō)明,該變量即為自動(dòng)變量。習(xí)慣上通常采用默認(rèn)形式,例如:
{ char x; int y; ... }
等價(jià)于
{ auto char x; auto int y; ... }
自動(dòng)變量的作用范圍在定義它的函數(shù)體或復(fù)合語(yǔ)句內(nèi)部,只有在定義它的函數(shù)被調(diào)用,或是定義它的復(fù)合語(yǔ)句被執(zhí)行時(shí),編譯器才為其分配內(nèi)存空間,開(kāi)始其生存期。當(dāng)函數(shù)調(diào)用結(jié)束返回,或復(fù)合語(yǔ)句執(zhí)行結(jié)束時(shí),自動(dòng)變量所占用的內(nèi)存空間就被釋放,變量的值當(dāng)然也就不復(fù)存在,其生存期結(jié)束。當(dāng)函數(shù)被再次調(diào)用或復(fù)合語(yǔ)句被再次執(zhí)行,編譯器又會(huì)為它們內(nèi)部的自動(dòng)變量重新分配內(nèi)存空間,但它不會(huì)保留上次運(yùn)行時(shí)的值,而必須被重新賦值。因此自動(dòng)變量始終是相對(duì)于函數(shù)或復(fù)合語(yǔ)句的局部變量。
2.外部變量(extern)
使用存儲(chǔ)種類說(shuō)明符“extern”定義的變量稱為外部變量。按照默認(rèn)規(guī)則,凡是在所有函數(shù)之前,在函數(shù)外部定義的變量都是外部變量,定義時(shí)可以不寫extern說(shuō)明符。但是,在一個(gè)函數(shù)體內(nèi)說(shuō)明一個(gè)已在該函數(shù)體外或別的程序模塊文件中定義過(guò)的外部變量時(shí),則必須使用extern說(shuō)明符。一個(gè)外部變量被定義之后,它就被分配了固定的內(nèi)存空間。外部變量的生存期為程序的整個(gè)執(zhí)行時(shí)間,即在程序的執(zhí)行期間外部變量可被隨意使用,當(dāng)一條復(fù)合語(yǔ)句執(zhí)行完畢或是從某一個(gè)函數(shù)返回時(shí),外部變量的存儲(chǔ)空間并不被釋放,其值也仍然保留。因此外部變量屬于全局變量。
C語(yǔ)言允許將大型程序分解為若干個(gè)獨(dú)立的程序模塊文件,各個(gè)模塊可分別進(jìn)行編譯,然后再將它們連接在一起。在這種情況下,如果某個(gè)變量需要在所有程序模塊文件中使用,只要在一個(gè)程序模塊文件中將該變量定義成全局變量,而在其他程序模塊文件中用extern說(shuō)明該變量是已被定義過(guò)的外部變量就可以了。
函數(shù)是可以相互調(diào)用的,因此函數(shù)都具有外部存儲(chǔ)種類的屬性。定義函數(shù)時(shí)如果冠以關(guān)鍵字extern即將其明確定義為一個(gè)外部函數(shù)。例如,extern int func2(char a, b)。如果在定義函數(shù)時(shí)省略關(guān)鍵字extern,則隱含為外部函數(shù)。如果要調(diào)用一個(gè)在本程序模塊文件以外的其他模塊文件所定義的函數(shù),則必須用關(guān)鍵字extern說(shuō)明被調(diào)用函數(shù)是一個(gè)外部函數(shù)。對(duì)于具有外部函數(shù)相互調(diào)用的多模塊程序,利用μVision51集成開(kāi)發(fā)環(huán)境很容易完成編譯連接。
這個(gè)例子中有兩個(gè)程序模塊文件“ex1.c”和“ex2.c”,可以在μVision51環(huán)境下將它們分別添加到一個(gè)項(xiàng)目文件“ex.prj”中,然后執(zhí)行Project菜單中的Make:Updat Project選項(xiàng)即可將它們連接在一起,生成OMF51絕對(duì)目標(biāo)文件ex,絕對(duì)目標(biāo)文件可以裝入dScope51中進(jìn)行仿真調(diào)試。
例3.9:多模塊程序。
(程序模塊1 文件名為ex1.c) #include<stdio.h> int x = 5; void main() { extern void fun1(); /* 說(shuō)明函數(shù)fun1在其他文件中定義 */ extern int fun2(int y); /* 說(shuō)明函數(shù)fun2在其他文件中定義 */ fun1(); fun1(); fun1(); printf("\n%d %d\n",x,fun2(x)); while(1); } (程序模塊2 文件名為ex2.c) #include <stdio.h> extern int x; /* 說(shuō)明變量x在其他文件中定義 */ void fun1() { static int a = 5; int b = 5; printf("%d %d %d | ",a,b,x); a -= 2; b -= 2; x -= 2; printf("%d %d %d\n",a,b,x); } int fun2(int y) { return( 35 * x * y ); }
程序的執(zhí)行結(jié)果為:
5 5 5 | 3 3 3 3 5 3 | 1 3 1 1 5 1 | -1 3 -1 -1 35
由于C語(yǔ)言不允許在一個(gè)函數(shù)體內(nèi)嵌套定義另一個(gè)函數(shù),為了能夠訪問(wèn)不同文件中各個(gè)函數(shù)的變量,除了可以采用我們?cè)谇懊娼榻B過(guò)的參數(shù)傳遞方法之外,還可以采用外部變量的方法。上面的例子就說(shuō)明了這一點(diǎn)。需要指出的是,盡管使用外部變量在不同函數(shù)之間傳遞數(shù)據(jù)有時(shí)比使用函數(shù)的參數(shù)更為方便,但是當(dāng)外部變量較多時(shí),會(huì)增加程序調(diào)試排錯(cuò)時(shí)的困難,使程序不便于維護(hù)。另外,不通過(guò)參數(shù)傳遞而直接在函數(shù)中改變?nèi)肿兞康闹担袝r(shí)還會(huì)發(fā)生一些意想不到的副作用。因此一般情況下最好還是使用函數(shù)的參數(shù)來(lái)傳遞數(shù)據(jù)。
3.靜態(tài)變量(static)
使用存儲(chǔ)種類說(shuō)明符“static”定義的變量稱為靜態(tài)變量。在例3.9的模塊2程序文件中使用了一個(gè)靜態(tài)變量:static int a=5。由于這個(gè)變量是在函數(shù)fun1()內(nèi)部定義的,因此稱為內(nèi)部靜態(tài)變量或局部靜態(tài)變量。局部靜態(tài)變量不像自動(dòng)變量那樣只有當(dāng)函數(shù)調(diào)用它時(shí)才存在,退出函數(shù)后它就消失,局部靜態(tài)變量始終都是存在的,但只能在定義它的函數(shù)內(nèi)部進(jìn)行訪問(wèn),退出函數(shù)之后,變量的值仍然保持,但不能進(jìn)行訪問(wèn)。
還有一種全局靜態(tài)變量,它是在函數(shù)外部被定義的,作用范圍從它的定義點(diǎn)開(kāi)始,一直到程序結(jié)束。當(dāng)一個(gè)C語(yǔ)言程序由若干個(gè)模塊文件所組成時(shí),全局靜態(tài)變量始終存在,但它只能在被定義的模塊文件中訪問(wèn),其數(shù)據(jù)值可為該文件內(nèi)的所有函數(shù)共享,退出該文件后,雖然變量的值仍然保持著,但不能被其他模塊文件訪問(wèn)。
局部靜態(tài)變量是一種在兩次函數(shù)調(diào)用之間仍能保持其值的局部變量。有些程序需要在多次調(diào)用之間仍然保持變量的值,使用自動(dòng)變量無(wú)法實(shí)現(xiàn)這一點(diǎn),使用全局變量有時(shí)又會(huì)帶來(lái)意外的副作用,這時(shí)就可采用局部靜態(tài)變量。
例3.10:局部靜態(tài)變量的使用——計(jì)算并輸出1~5的階乘值。
#include<stdio.h> int fac(int n) { static int f=1; f=f*n; return(f); } main() { int i; for (i=1; i<=5; i++) printf(“%d! = %d\n”, i, fac(i)); while(1); }
程序執(zhí)行結(jié)果:
1! = 1 2! = 2 3! = 6 4! = 24 5! = 120
在這個(gè)程序中,一共調(diào)用了5次計(jì)算階乘的函數(shù)fac(i),每次調(diào)用后輸出一個(gè)階乘值i!,同時(shí)保留這個(gè)i!值,以便下次再乘(i+1)。由此可見(jiàn),如果要保留函數(shù)上一次調(diào)用結(jié)束時(shí)的值,或是在初始化之后變量只被引用而不改變其值,則這時(shí)使用局部靜態(tài)變量較為方便,以免在每次調(diào)用時(shí)都要重新進(jìn)行賦值。但是,使用局部靜態(tài)變量需要占用較多的內(nèi)存空間,而且降低了程序的可讀性,當(dāng)調(diào)用次數(shù)較多時(shí)往往弄不清局部靜態(tài)變量的當(dāng)前值是什么。因此,建議不要多用局部靜態(tài)變量。
全局靜態(tài)變量是一種作用范圍受限制的外部變量,它的有效作用范圍從其定義點(diǎn)開(kāi)始直至程序文件的末尾,而且只有在定義它的程序模塊文件中才能對(duì)它進(jìn)行訪問(wèn)。全局靜態(tài)變量與我們?cè)谇懊娼榻B過(guò)的單純?nèi)肿兞渴怯袇^(qū)別的。全局靜態(tài)變量有一個(gè)特點(diǎn),就是只有在定義它的程序文件中才可以使用它,其他文件不能改變其內(nèi)容。
C語(yǔ)言允許進(jìn)行多模塊程序設(shè)計(jì),一個(gè)較大型的程序可被分成若干個(gè)模塊,分別由幾個(gè)人來(lái)完成。如果各人在獨(dú)立設(shè)計(jì)各自的程序模塊時(shí),有些變量可能只希望在自己的程序模塊文件中使用,而不希望被別的模塊文件引用,對(duì)于這種變量就可以定義為全局靜態(tài)變量。
需要指出的是,全局靜態(tài)變量和單純?nèi)肿兞慷际窃诰幾g時(shí)就已經(jīng)分配了固定的內(nèi)存空間的變量,只是它們的作用范圍不同而已。
對(duì)于函數(shù)也可以定義成具有靜態(tài)存儲(chǔ)種類的屬性。定義函數(shù)時(shí)在函數(shù)名前面冠以關(guān)鍵字static即將其定義為一個(gè)靜態(tài)函數(shù)。例如,static int func1(char x, int y)。使用靜態(tài)函數(shù)可使該函數(shù)只局限于其所在的模塊文件。由于函數(shù)都是外部型的,因此靜態(tài)外部函數(shù)定義就限制了該函數(shù)只能在定義它的模塊文件中使用,其他模塊文件是不能調(diào)用它的。換句話說(shuō),在其他模塊文件中可以定義與靜態(tài)函數(shù)完全同名的另一個(gè)函數(shù),分別編譯并連接成為一個(gè)可執(zhí)行程序之后,不會(huì)由于程序中存在相同的函數(shù)名而發(fā)生函數(shù)調(diào)用時(shí)的混亂。這一特點(diǎn)在進(jìn)行模塊化程序設(shè)計(jì)時(shí)是十分有用的。
4.寄存器變量(register)
為了提高程序的執(zhí)行效率,C語(yǔ)言允許將一些使用頻率最高的那些變量,定義為能夠直接使用硬件寄存器的所謂寄存器變量。定義一個(gè)變量時(shí)在變量名前面冠以存儲(chǔ)種類符號(hào)“register”即將該變量定義成為了寄存器變量。寄存器變量可以被認(rèn)為是自動(dòng)變量的一種,它的有效作用范圍也與自動(dòng)變量相同。
由于計(jì)算機(jī)中的寄存器是有限的,不能將所有變量都定義成寄存器變量。通常在程序中定義的寄存器變量時(shí)只是給編譯器一個(gè)建議,該變量是否能真正成為寄存器變量,要由編譯器根據(jù)實(shí)際情況來(lái)確定。另一方面,Cx51編譯器能夠識(shí)別程序中使用頻率最高的變量,在可能的情況下,即使程序中并未將該變量定義為寄存器變量,編譯器也會(huì)自動(dòng)將其作為寄存器變量處理。下面來(lái)看一個(gè)帶有匯編碼的程序例子。
例3.11:使用寄存器變量的例子——計(jì)算以整數(shù)為底的指數(shù)的冪。
stmt level source 1 #include<stdio.h> 2 int_power(m, e) 3 int m; 4 register int e; 5 { 6 1 register int temp; 7 1 temp=1; 8 1 for (; e; e--) 9 1 temp*=m; 10 1 return(temp); 11 1 } 12 13 main() { 14 1 int x, y; 15 1 printf("please input X Y\n"); 16 1 scanf("%d %d", &x, &y); 17 1 printf("%d to the power of %d = %d",x,y,int_power(x, y)); 18 1 } 19 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION _int_power (BEGIN) ;SOURCE LINE # 2 0000 8E00 R MOV m,R6 0002 8F00 R MOV m+01H,R7 ;---- Variable 'e' assigned to Register 'R2/R3' ---- 0004 AB05 MOV R3,AR5 0006 AA04 MOV R2,AR4 ;SOURCE LINE # 3 ;SOURCE LINE # 7 ;---- Variable 'temp' assigned to Register 'R6/R7' ---- 0008 7F01 MOV R7,#01H 000A 7E00 MOV R6,#00H ;SOURCE LINE # 8 000C ?C0001: 000C EB MOV A,R3 000D 4A ORL A,R2 000E 600E JZ ?C0002 ;SOURCE LINE # 9 0010 AC00 R MOV R4,m 0012 AD00 R MOV R5,m+01H 0014120000 E LCALL ?C?IMUL 0017 EB MOV A,R3 0018 1B DEC R3 0019 70F1 JNZ ?C0001 001B 1A DEC R2 001C ?C0006: 001C 80EE SJMP ?C0001 001E ?C0002: ;SOURCE LINE # 10 ;SOURCE LINE # 11 001E ?C0004: 001E 22 RET ;FUNCTION _int_power (END) ;FUNCTION main (BEGIN) ;SOURCE LINE # 13 ;SOURCE LINE # 15 0000 7BFF MOV R3,#0FFH 0002 7A00 R MOV R2,#HIGH ?SC_0 00047900 R MOV R1,#LOW ?SC_0 0006120000 E LCALL _printf ;SOURCE LINE # 16 0009750000 E MOV ?_scanf?BYTE+03H,#00H 000C 750000 R MOV ?_scanf?BYTE+04H,#HIGH x 000F 750000 R MOV ?_scanf?BYTE+05H,#LOW x 0012750000 E MOV ?_scanf?BYTE+06H,#00H 0015750000 R MOV ?_scanf?BYTE+07H,#HIGH y 0018750000 R MOV ?_scanf?BYTE+08H,#LOW y 001B 7BFF MOV R3,#0FFH 001D 7A00 R MOV R2,#HIGH ?SC_19 001F 7900 R MOV R1,#LOW ?SC_19 0021120000 E LCALL _scanf ;SOURCE LINE # 17 0024 AD00 R MOV R5,y+01H 0026 AC00 R MOV R4,y 0028 AF00 R MOV R7,x+01H 002A AE00 R MOV R6,x 002C 120000 R LCALL _int_power 002F 8E00 E MOV ?_printf?BYTE+07H,R6 0031 8F00 E MOV ?_printf?BYTE+08H,R7 0033 7BFF MOV R3,#0FFH 0035 7A00 R MOV R2,#HIGH ?SC_26 00377900 R MOV R1,#LOW ?SC_26 0039850000 E MOV ?_printf?BYTE+03H,x 003C 850000 E MOV ?_printf?BYTE+04H,x+01H 003F 850000 E MOV ?_printf?BYTE+05H,y 0042850000 E MOV ?_printf?BYTE+06H,y+01H 0045020000 E LJMP _printf ;FUNCTION main (END)
程序執(zhí)行結(jié)果:
please input X Y 5 3 回車 5 to the power of 3 = 125
在這個(gè)程序中定義了一個(gè)計(jì)算以整數(shù)為底的指數(shù)冪的函數(shù)int_power(),該函數(shù)中有兩個(gè)形式參數(shù)int m和register int e。它們都是int類型的變量,但參數(shù)e前面帶有存儲(chǔ)種類說(shuō)明符register,被特別說(shuō)明為寄存器變量。另外,在該函數(shù)體中還定義了一個(gè)int類型的寄存器變量register int temp。從編譯得到的匯編碼可以看到,形式參數(shù)e被分配給了8051單片機(jī)的工作寄存器R2和R3,但變量temp則未分配到工作寄存器,而是作為臨時(shí)工作單元被存放到內(nèi)存之中。由此可見(jiàn),盡管可以在程序中定義寄存器變量,但實(shí)際上被定義的變量是否真能成為寄存器變量最終是由編譯器決定的。
3.5.3 函數(shù)的參數(shù)和局部變量的存儲(chǔ)器模式
Keil Cx51編譯器允許采用三種存儲(chǔ)器模式:small、compact和large。一個(gè)函數(shù)的存儲(chǔ)器模式確定了函數(shù)的參數(shù)和局部變量在內(nèi)存中的地址空間。處于small模式下函數(shù)的參數(shù)和局部變量位于8051單片機(jī)的內(nèi)部RAM中,處于compact和large模式下函數(shù)的參數(shù)和局部變量則使用8051單片機(jī)的外部RAM。在定義一個(gè)函數(shù)時(shí)可以明確指定該函數(shù)的存儲(chǔ)器模式,一般形式為:
函數(shù)類型 函數(shù)名(形式參數(shù)表)[存儲(chǔ)器模式]
其中,“存儲(chǔ)器模式”是Keil Cx51編譯器擴(kuò)展的一個(gè)選項(xiàng)。不用該選項(xiàng)時(shí)即沒(méi)有明確指定函數(shù)的存儲(chǔ)器模式,這時(shí)該函數(shù)按編譯時(shí)的默認(rèn)存儲(chǔ)器模式處理。
例3.12:函數(shù)的存儲(chǔ)器模式。
#pragma large /* 默認(rèn)存儲(chǔ)器模式為L(zhǎng)ARGE */ extern int calc(char i, int b) small; /* 指定SMALL模式 */ extern int func(int i, float f) large; /* 指定LARGE模式 */ extern void * tcp(char xdata *xp, int ndx) small; /* 指定SMALL模式 */ int mtest(int i, int y) small /* 指定SMALL模式 */ { return(i*y+y*i+func(-1, 4.75))' } int large_func(int i, int k) /* 未指定模式,按默認(rèn)的LARGE模式處理 */ { return(mtest(i,k)+2); }
這個(gè)例子程序的第一行用了一個(gè)預(yù)編譯命令“#pragma”,它的意思是告訴Keil Cx51編譯器在對(duì)程序進(jìn)行編譯時(shí),按該預(yù)編譯命令后面給出的編譯控制指令“l(fā)arge”進(jìn)行編譯,即本例程序編譯時(shí)的默認(rèn)存儲(chǔ)器模式為large。程序中一共有五個(gè)函數(shù):calc()、func()、*tcp()、mtest()和large_func(),其中前面四個(gè)函數(shù)都在定義時(shí)明確指定了其存儲(chǔ)器模式,只有最后一個(gè)函數(shù)未指定。在用Cx51進(jìn)行編譯時(shí),只有最后一個(gè)函數(shù)按large存儲(chǔ)器模式處理,其余四個(gè)函數(shù)則分別按它們各自指定的存儲(chǔ)器模式處理。
這個(gè)例子說(shuō)明,Keil Cx51編譯器允許采用所謂存儲(chǔ)器的混合模式,即允許在一個(gè)程序中某個(gè)(或幾個(gè))函數(shù)使用一種存儲(chǔ)器模式,另一個(gè)(或幾個(gè))函數(shù)使用另一種存儲(chǔ)器模式。采用存儲(chǔ)器混合模式編程,可以充分利用8051系列單片機(jī)中有限的存儲(chǔ)器空間,同時(shí)還可加快程序的執(zhí)行速度。
- Java面向?qū)ο笏枷肱c程序設(shè)計(jì)
- Mastering Concurrency in Go
- Android Application Development Cookbook(Second Edition)
- Learn Swift by Building Applications
- Mastering KnockoutJS
- Linux:Embedded Development
- Node.js Design Patterns
- Python算法指南:程序員經(jīng)典算法分析與實(shí)現(xiàn)
- Android Wear Projects
- 常用工具軟件立體化教程(微課版)
- C/C++數(shù)據(jù)結(jié)構(gòu)與算法速學(xué)速用大辭典
- Julia for Data Science
- Web性能實(shí)戰(zhàn)
- Java高手是怎樣煉成的:原理、方法與實(shí)踐
- WCF全面解析