程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> C語言初學者入門 第十講 函數(2)

C語言初學者入門 第十講 函數(2)

編輯:關於C

一、函數的參數

  前面已經介紹過,函數的參數分為形參和實參兩種。 在本小節中,進一步介紹形參、實參的特點和兩者的關系。 形參出現在函數定義中,在整個函數體內都可以使用, 離開該函數則不能使用。實參出現在主調函數中,進入被調函數後,實參變量也不能使用。 形參和實參的功能是作數據傳送。發生函數調用時, 主調函數把實參的值傳送給被調函數的形參從而實現主調函數向被調函數的數據傳送。

  函數的形參和實參具有以下特點:

  1.形參變量只有在被調用時才分配內存單元,在調用結束時, 即刻釋放所分配的內存單元。因此,形參只有在函數內部有效。 函數調用結束返回主調函數後則不能再使用該形參變量。

  2.實參可以是常量、變量、表達式、函數等, 無論實參是何種類型的量,在進行函數調用時,它們都必須具有確定的值, 以便把這些值傳送給形參。 因此應預先用賦值,輸入等辦法使實參獲得確定值。

  3.實參和形參在數量上,類型上,順序上應嚴格一致, 否則會發生“類型不匹配”的錯誤。

  4.函數調用中發生的數據傳送是單向的。 即只能把實參的值傳送給形參,而不能把形參的值反向地傳送給實參。 因此在函數調用過程中,形參的值發生改變,而實參中的值不會變化。例5.3可以說明這個問題。

void main()
{
int n;
printf("input number\n");
scanf("%d",&n);
s(n);
printf("n=%d\n",n);
}
int s(int n)
{
int i;
for(i=n-1;i>=1;i--)
n=n+i;
printf("n=%d\n",n);
}

  本程序中定義了一個函數s,該函數的功能是求∑ni=1i 的值。在主函數中輸入n值,並作為實參,在調用時傳送給s 函數的形參量n( 注意,本例的形參變量和實參變量的標識符都為n, 但這是兩個不同的量,各自的作用域不同)。 在主函數中用printf 語句輸出一次n值,這個n值是實參n的值。在函數s中也用printf 語句輸出了一次n值,這個n值是形參最後取得的n值0。從運行情況看,輸入n值為100。即實參n的值為100。把此值傳給函數s時,形參 n 的初值也為100,在執行函數過程中,形參n的值變為5050。 返回主函數之後,輸出實參n的值仍為100。可見實參的值不隨形參的變化而變化。

  二、函數的值

  函數的值是指函數被調用之後, 執行函數體中的程序段所取得的並返回給主調函數的值。如調用正弦函數取得正弦值,調用例5.1的max函數取得的最大數等。對函數的值(或稱函數返回值)有以下一些說明:

  1. 函數的值只能通過return語句返回主調函數。return 語句的一般形式為:

  return 表達式;

  或者為:

  return (表達式);

  該語句的功能是計算表達式的值,並返回給主調函數。 在函數中允許有多個return語句,但每次調用只能有一個return 語句被執行, 因此只能返回一個函數值。

  2. 函數值的類型和函數定義中函數的類型應保持一致。 如果兩者不一致,則以函數類型為准,自動進行類型轉換。

  3. 如函數值為整型,在函數定義時可以省去類型說明。

  4. 不返回函數值的函數,可以明確定義為“空類型”, 類型說明符為“void”。如例5.3中函數s並不向主函數返函數值,因此可定義為:

void s(int n)
{ ……
}

  一旦函數被定義為空類型後, 就不能在主調函數中使用被調函數的函數值了。例如,在定義s為空類型後,在主函數中寫下述語句 sum=s(n); 就是錯誤的。為了使程序有良好的可讀性並減少出錯, 凡不要求返回值的函數都應定義為空類型。函數說明在主調函數中調用某函數之前應對該被調函數進行說明, 這與使用變量之前要先進行變量說明是一樣的。 在主調函數中對被調函數作說明的目的是使編譯系統知道被調函數返回值的類型, 以便在主調函數中按此種類型對返回值作相應的處理。 對被調函數的說明也有兩種格式,一種為傳統格式,其一般格式為: 類型說明符 被調函數名(); 這種格式只給出函數返回值的類型,被調函數名及一個空括號。

  這種格式由於在括號中沒有任何參數信息, 因此不便於編譯系統進行錯誤檢查,易於發生錯誤。另一種為現代格式,其一般形式為:

  類型說明符 被調函數名(類型 形參,類型 形參…);

  或為:

  類型說明符 被調函數名(類型,類型…);

  現代格式的括號內給出了形參的類型和形參名, 或只給出形參類型。這便於編譯系統進行檢錯,以防止可能出現的錯誤。例5.1 main函數中對max函數的說明若

  用傳統格式可寫為:

int max();

  用現代格式可寫為:

int max(int a,int b);

  或寫為:

int max(int,int);

  C語言中又規定在以下幾種情況時可以省去主調函數中對被調函數的函數說明。

  1. 如果被調函數的返回值是整型或字符型時, 可以不對被調函數作說明,而直接調用。這時系統將自動對被調函數返回值按整型處理。例5.3的主函數中未對函數s作說明而直接調用即屬此種情形。

  2. 當被調函數的函數定義出現在主調函數之前時, 在主調函數中也可以不對被調函數再作說明而直接調用。例如例5.1中, 函數max的定義放在main 函數之前,因此可在main函數中省去對 max函數的函數說明int max(int a,int b)。

  3. 如在所有函數定義之前, 在函數外預先說明了各個函數的類型,則在以後的各主調函數中,可不再對被調函數作說明。例如:

char str(int a);
float f(float b);
main()
{
……
}
char str(int a)
{
……
}
float f(float b)
{
……
}

  其中第一,二行對str函數和f函數預先作了說明。 因此在以後各函數中無須對str和f函數再作說明就可直接調用。

  4. 對庫函數的調用不需要再作說明, 但必須把該函數的頭文件用include命令包含在源文件前部。數組作為函數參數數組可以作為函數的參數使用,進行數據傳送。 數組用作函數參數有兩種形式,一種是把數組元素(下標變量)作為實參使用; 另一種是把數組名作為函數的形參和實參使用。一、數組元素作函數實參數組元素就是下標變量,它與普通變量並無區別。 因此它作為函數實參使用與普通變量是完全相同的,在發生函數調用時, 把作為實參的數組元素的值傳送給形參,實現單向的值傳送。例5.4說明了這種情況。[例5.4]判別一個整數數組中各元素的值,若大於0 則輸出該值,若小於等於0則輸出0值。編程如下:

void nzp(int v)
{
if(v>0)
printf("%d ",v);
else
printf("%d ",0);
}
main()
{
int a[5],i;
printf("input 5 numbers\n");
for(i=0;i<5;i++)
{
scanf("%d",&a[i]);
nzp(a[i]);
}
}void nzp(int v)
{ ……
}
main()
{
int a[5],i;
printf("input 5 numbers\n");
for(i=0;i<5;i++)
{ scanf("%d",&a[i]);
nzp(a[i]);
}
}  

  本程序中首先定義一個無返回值函數nzp,並說明其形參v 為整型變量。在函數體中根據v值輸出相應的結果。在main函數中用一個for 語句輸入數組各元素, 每輸入一個就以該元素作實參調用一次nzp函數,即把a[i]的值傳送給形參v,供nzp函數使用。

  二、數組名作為函數參數

  用數組名作函數參數與用數組元素作實參有幾點不同:

  1. 用數組元素作實參時,只要數組類型和函數的形參變量的類型一致,那麼作為下標變量的數組元素的類型也和函數形參變量的類型是一致的。因此, 並不要求函數的形參也是下標變量。 換句話說,對數組元素的處理是按普通變量對待的。用數組名作函數參數時, 則要求形參和相對應的實參都必須是類型相同的數組,都必須有明確的數組說明。當形參和實參二者不一致時,即會發生錯誤。

  2. 在普通變量或下標變量作函數參數時,形參變量和實參變量是由編譯系統分配的兩個不同的內存單元。在函數調用時發生的值傳送是把實參變量的值賦予形參變量。在用數組名作函數參數時,不是進行值的傳送,即不是把實參數組的每一個元素的值都賦予形參數組的各個元素。因為實際上形參數組並不存在,編譯系統不為形參數組分配內存。那麼,數據的傳送是如何實現的呢? 在第四章中我們曾介紹過,數組名就是數組的首地址。因此在數組名作函數參數時所進行的傳送只是地址的傳送, 也就是說把實參數組的首地址賦予形參數組名。形參數組名取得該首地址之後,也就等於有了實在的數組。實際上是形參數組和實參數組為同一數組,共同擁有一段內存空間。圖5.1說明了這種情形。圖中設a為實參數組,類型為整型。a占有以2000 為首地址的一塊內存區。b為形參數組名。當發生函數調用時,進行地址傳送, 把實參數 組a的首地址傳送給形參數組名b,於是b也取得該地址2000。 於是a,b兩數組共同占有以2000 為首地址的一段連續內存單元。從圖中還可以看出a和b下標相同的元素實際上也占相同的兩個內存單元(整型數組每個元素占二字節)。例如a[0]和b[0]都占用2000和2001單元,當然a[0]等於b[0]。類推則有a[i]等於b[i]。

  [例5.5]數組a中存放了一個學生5門課程的成績,求平均成績。float aver(float a[5])

{
int i;
float av,s=a[0];
for(i=1;i<5;i++)
s=s+a[i];
av=s/5;
return av;
}
void main()
{
float sco[5],av;
int i;
printf("\ninput 5 scores:\n");
for(i=0;i<5;i++)
scanf("%f",&sco[i]);
av=aver(sco);
printf("average score is %5.2f",av);
}
float aver(float a[5])
{ ……
}
void main()
{
……
for(i=0;i<5;i++)
scanf("%f",&sco[i]);
av=aver(sco);
……
}  

  本程序首先定義了一個實型函數aver,有一個形參為實型數組a,長度為5。在函數aver中,把各元素值相加求出平均值,返回給主函數。主函數main 中首先完成數組sco的輸入,然後以sco作為實參調用aver函數,函數返回值送av,最後輸出av值。 從運行情況可以看出,程序實現了所要求的功能

  3. 前面已經討論過,在變量作函數參數時,所進行的值傳送是單向的。即只能從實參傳向形參,不能從形參傳回實參。形參的初值和實參相同, 而形參的值發生改變後,實參並不變化, 兩者的終值是不同的。例5.3證實了這個結論。 而當用數組名作函數參數時,情況則不同。 由於實際上形參和實參為同一數組, 因此當形參數組發生變化時,實參數組也隨之變化。 當然這種情況不能理解為發生了“雙向”的值傳遞。但從實際情況來看,調用函數之後實參數組的值將由於形參數組值的變化而變化。為了說明這種情況,把例5.4改為例5.6的形式。[例5.6]題目同5.4例。改用數組名作函數參數。

void nzp(int a[5])
{
int i;
printf("\nvalues of array a are:\n");
for(i=0;i<5;i++)
{
if(a[i]<0) a[i]=0;
printf("%d ",a[i]);
}
}
main()
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b[i]);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
nzp(b);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
}
void nzp(int a[5])
{ ……
}
main()
{
int b[5],i;
……
nzp(b);
……
}

  本程序中函數nzp的形參為整數組a,長度為 5。 主函數中實參數組b也為整型,長度也為5。在主函數中首先輸入數組b的值,然後輸出數組b的初始值。 然後以數組名b為實參調用nzp函數。在nzp中,按要求把負值單元清0,並輸出形參數組a的值。 返回主函數之後,再次輸出數組b的值。從運行結果可以看出,數組b 的初值和終值是不同的,數組b 的終值和數組a是相同的。這說明實參形參為同一數組,它們的值同時得以改變。 用數組名作為函數參數時還應注意以下幾點:

  a. 形參數組和實參數組的類型必須一致,否則將引起錯誤。

  b. 形參數組和實參數組的長度可以不相同,因為在調用時,只傳送首地址而不檢查形參數組的長度。當形參數組的長度與實參數組不一致時,雖不至於出現語法錯誤(編譯能通過),但程序執行結果將與實際不符,這是應予以注意的。如把例5.6修改如下:

void nzp(int a[8])
{
int i;
printf("\nvalues of array aare:\n");
for(i=0;i<8;i++)
{
if(a[i]<0)a[i]=0;
printf("%d",a[i]);
}
}
main()
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b[i]);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d",b[i]);
nzp(b);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d",b[i]);
}

  本程序與例5.6程序比,nzp函數的形參數組長度改為8,函數體中,for語句的循環條件也改為i<8。因此,形參數組 a和實參數組b的長度不一致。編譯能夠通過,但從結果看,數組a的元素a[5],a[6],a[7]顯然是無意義的。c. 在函數形參表中,允許不給出形參數組的長度,或用一個變量來表示數組元素的個數。

  例如:可以寫為:

void nzp(int a[])

  或寫為

void nzp(int a[],int n)

  其中形參數組a沒有給出長度,而由n值動態地表示數組的長度。n的值由主調函數的實參進行傳送。

  由此,例5.6又可改為例5.7的形式。

  [例5.7]

void nzp(int a[],int n)
{
int i;
printf("\nvalues of array a are:\n");
for(i=0;i<n;i++)
{
if(a[i]<0) a[i]=0;
printf("%d ",a[i]);
}
}
main()
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b[i]);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
nzp(b,5);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
}
void nzp(int a[],int n)
{ ……
}
main()
{
……
nzp(b,5);
……
}


  本程序nzp函數形參數組a沒有給出長度,由n 動態確定該長度。在main函數中,函數調用語句為nzp(b,5),其中實參5將賦予形參n作為形參數組的長度。

  d. 多維數組也可以作為函數的參數。 在函數定義時對形參數組可以指定每一維的長度,也可省去第一維的長度。因此,以下寫法都是合法的。

int MA(int a[3][10])

  或

int MA(int a[][10])

  函數的嵌套調用

  C語言中不允許作嵌套的函數定義。因此各函數之間是平行的,不存在上一級函數和下一級函數的問題。 但是C語言允許在一個函數的定義中出現對另一個函數的調用。 這樣就出現了函數的嵌套調用。即在被調函數中又調用其它函數。 這與其它語言的子程序嵌套的情形是類似的。其關系可表示如圖5.2。

  圖5.2表示了兩層嵌套的情形。其執行過程是:執行main函數中調用a函數的語句時,即轉去執行a函數,在a函數中調用b 函數時,又轉去執行b函數,b函數執行完畢返回a函數的斷點繼續執行,a 函數執行完畢返回main函數的斷點繼續執行。

  [例5.8]計算s=22!+32!

  本題可編寫兩個函數,一個是用來計算平方值的函數f1, 另一個是用來計算階乘值的函數f2。主函數先調f1計算出平方值, 再在f1中以平方值為實參,調用 f2計算其階乘值,然後返回f1,再返回主函數,在循環程序中計算累加和。

long f1(int p)
{
int k;
long r;
long f2(int);
k=p*p;
r=f2(k);
return r;
}
long f2(int q)
{
long c=1;
int i;
for(i=1;i<=q;i++)
c=c*i;
return c;
}
main()
{
int i;
long s=0;
for (i=2;i<=3;i++)
s=s+f1(i);
printf("\ns=%ld\n",s);
}
long f1(int p)
{
……
long f2(int);
r=f2(k);
……
}
long f2(int q)
{
……
}
main()
{ ……
s=s+f1(i);
……
}

  在程序中,函數f1和f2均為長整型,都在主函數之前定義, 故不必再在主函數中對f1和f2加以說明。在主程序中, 執行循環程序依次把i值作為實參調用函數f1求i2值。在f1中又發生對函數f2的調用,這時是把i2的值作為實參去調f2,在f2 中完成求i2! 的計算。f2執行完畢把C值(即i2!)返回給f1,再由f1 返回主函數實現累加。至此,由函數的嵌套調用實現了題目的要求。 由於數值很大, 所以函數和一些變量的類型都說明為長整型,否則會造成計算錯誤。

,最大的網絡編程教程基地

*
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved