函 數
1. 函數的定義
函數定義的一般格式如下:
函數類型 函數名(形式參數表) [reentrant][interrupt m][using n]
形式參數說明
{
局部變量定義
函數體
}
前面部件稱爲函數的首部,後面稱爲函數的尾部,格式說明:
1).函數類型
函數類型說明了函數返回值的類型。
2).函數名
函數名是用戶爲自定義函數取的名字以便調用函數時使用。
3).形式參數表
形式參數表用於列錄在主調函數與被調用函數之間進行數據傳遞的形式參數。
【例】定義一個返回兩個整數的最大值的函數max()。
int max(int x,int y)
{ int z;
z=x>y?x:y;
return(z);
}
也可以用成這樣:
int max(x,y)
int x,y;
{ int z;
z=x>y?x:y;
return(z);
}
4).reentrant修飾符
這個修飾符用於把函數定義爲可重入函數。所謂可重入函數就是允許被遞歸調用的函數。函數的遞歸調用是指當一個函數正被調用尚未返回時,又直接或間接調用函數本身。一般的函數不能做到這樣,只有重入函數才允許遞歸調用。
關於重入函數,注意以下幾點:
(1)用reentrant修飾的重入函數被調用時,實參表內不允許使用bit類型的參數。函數體內也不允許存在任何關於位變量的操作,更不能返回bit類型的值。
(2)編譯時,系統爲重入函數在內部或外部存儲器中建立一個模擬堆棧區,稱爲重入棧。重入函數的局部變量及參數被放在重入棧中,使重入函數可以實現遞歸調用。
(3)在參數的傳遞上,實際參數可以傳遞給間接調用的重入函數。無重入屬性的間接調用函數不能包含調用參數,但是可以使用定義的全局變量來進行參數傳遞。
5).interrupt m修飾符
interrupt m是C51函數中非常重要的一個修飾符,這是因爲中斷函數必須通過它進行修飾。在C51程序設計中,當函數定義時用了interrupt m修飾符,系統編譯時把對應函數轉化爲中斷函數,自動加上程序頭段和尾段,並按51系統中斷的處理方式自動把它安排在程序存儲器中的相應位置。
在該修飾符中,m的取值爲0~31,對應的中斷情況如下:
0——外部中斷0
1——定時/計數器T0
2——外部中斷1
3——定時/計數器T1
4——串行口中斷
5——定時/計數器T2
其它值預留。
編寫51中斷函數注意如下:
(1)中斷函數不能進行參數傳遞,如果中斷函數中包含任何參數聲明都將導致編譯出錯。
(2)中斷函數沒有返回值,如果企圖定義一個返回值將得不到正確的結果,建議在定義中斷函數時將其定義爲void類型,以明確說明沒有返回值。
(3)在任何情況下都不能直接調用中斷函數,否則會產生編譯錯誤。因爲中斷函數的返回是由8051單片機的RETI指令完成的,RETI指令影響8051單片機的硬件中斷系統。如果在沒有實際中斷情況下直接調用中斷函數,RETI指令的操作結果會產生一個致命的錯誤。
(4)如果在中斷函數中調用了其它函數,則被調用函數所使用的寄存器必須與中斷函數相同。否則會產生不正確的結果。
(5)C51編譯器對中斷函數編譯時會自動在程序開始和結束處加上相應的內容,具體如下:在程序開始處對ACC、B、DPH、DPL和PSW入棧,結束時出棧。中斷函數未加using n修飾符的,開始時還要將R0~R1入棧,結束時出棧。如中斷函數加using n修飾符,則在開始將PSW入棧後還要修改PSW中的工作寄存器組選擇位。
(6)C51編譯器從絕對地址8m+3處產生一箇中斷向量,其中m爲中斷號,也即interrupt後面的數字。該向量包含一個到中斷函數入口地址的絕對跳轉。
(7)中斷函數最好寫在文件的尾部,並且禁止使用extern存儲類型說明。防止其它程序調用。
【例】編寫一個用於統計外中斷0的中斷次數的中斷服務程序
extern int x;
void int0() interrupt 0 using 1
{
x++;
}
6).using n修飾符
修飾符using n用於指定本函數內部使用的工作寄存器組,其中n的取值爲0~3,表示寄存器組號。
對於using n修飾符的使用,注意以下幾點:
(1)加入using n後,C51在編譯時自動的在函數的開始處和結束處加入以下指令。
{
PUSH PSW ;標誌寄存器入棧
MOV PSW,#與寄存器組號相關的常量
……
POP PSW ;標誌寄存器出棧
}
(2)using n修飾符不能用於有返回值的函數,因爲C51函數的返回值是放在寄存器中的。如寄存器組改變了,返回值就會出錯。
2. 函數的調用與聲明
一.函數的調用
函數調用的一般形式如下:
函數名(實參列表);
對於有參數的函數調用,若實參列表包含多個實參,則各個實參之間用逗號隔開。
按照函數調用在主調函數中出現的位置,函數調用方式有以下三種:
(1)函數語句。把被調用函數作爲主調用函數的一個語句。
(2)函數表達式。函數被放在一個表達式中,以一個運算對象的方式出現。這時的被調用函數要求帶有返回語句,以返回一個明確的數值參加表達式的運算。
(3)函數參數。被調用函數作爲另一個函數的參數。
二.自定義函數的聲明
在C51中,函數原型一般形式如下:
[extern] 函數類型 函數名(形式參數表);
函數的聲明是把函數的名字、函數類型以及形參的類型、個數和順序通知編譯系統,以便調用函數時系統進行對照檢查。函數的聲明後面要加分號。
如果聲明的函數在文件內部,則聲明時不用extern,如果聲明的函數不在文件內部,而在另一個文件中,聲明時須帶extern,指明使用的函數在另一個文件中。
【例】函數的使用
#include <reg52.h> //包含特殊功能寄存器庫
#include <stdio.h> //包含I/O函數庫
int max(int x,int y); //對max函數進行聲明
void main(void) //主函數
{
int a,b;
SCON=0x52; //串口初始化
TMOD=0x20;
TH1=0XF3;
TR1=1;
scanf(“please input a,b:%d,%d”,&a,&b);
printf(“\n”);
printf(“max is:%d\n”,max(a,b));
while(1);
}
int max(int x,int y)
{ int z;
z=(x>=y?x:y);
return(z);
}
【例24】 外部函數的使用
程序serial_initial.c
#include <reg52.h> //包含特殊功能寄存器庫
#include <stdio.h> //包含I/O函數庫
void serial_initial(void) //主函數
{
SCON=0x52; //串口初始化
TMOD=0x20;
TH1=0XF3;
TR1=1;
}
程序y1.c
#include <reg52.h> //包含特殊功能寄存器庫
#include <stdio.h> //包含I/O函數庫
extern serial_initial();
void main(void)
{
int a,b;
serial_initial();
scanf(“please input a,b:%d,%d”,&a,&b);
printf(“\n”);
printf(“max is:%d\n”,a>=b?a:b);
while(1);
}
3. 函數的嵌套與遞歸
一.函數的嵌套
在一個函數的調用過程中調用另一個函數。C51編譯器通常依靠堆棧來進行參數傳遞,堆棧設在片內RAM中,而片內RAM的空間有限,因而嵌套的深度比較有限,一般在幾層以內。如果層數過多,就會導致堆棧空間不夠而出錯。
【例】 函數的嵌套調用
#include <reg52.h> //包含特殊功能寄存器庫
#include <stdio.h> //包含I/O函數庫
extern serial_initial();
int max(int a,int b)
{
int z;
z=a>=b?a:b;
return(z);
}
int add(int c,int d,int e,int f)
{
int result;
result=max(c,d)+max(e,f); //調用函數max
return(result);
}
main()
{
int final;
serial_initial();
final=add(7,5,2,8);
printf(“%d”,final);
while(1);
}
二.函數的遞歸
遞歸調用是嵌套調用的一個特殊情況。如果在調用一個函數過程中又出現了直接或間接調用該函數本身,則稱爲函數的遞歸調用。
在函數的遞歸調用中要避免出現無終止的自身調用,應通過條件控制結束遞歸調用,使得遞歸的次數有限。
下面是一個利用遞歸調用求n!的例子。
【例】遞歸求數的階乘n!。
在數學計算中,一個數n的階乘等於該數本身乘以數n-1的階乘,即n!=n´(n-1)!,用n-1的階乘來表示n的階乘就是一種遞歸表示方法。在程序設計中通過函數遞歸調用來實現。
程序如下:
#include <reg52.h> //包含特殊功能寄存器庫
#include <stdio.h> //包含I/O函數庫
extern serial_initial();
int fac(int n) reentrant
{
int result;
if (n= =0)
result=1;
else
result=n*fac(n-1);
return(result);
}
main()
{
int fac_result;
serial_initial();
fac_result=fac(11);
printf(“%d\n”,fac_result);
}