1. 程式人生 > 其它 >C語言函式

C語言函式

技術標籤:程式語言程式設計c語言堆疊作業系統

1.C語言函式概述

在前面已經介紹過,C源程式是由函式組成的。雖然在前面各章的程式中大都只有一個主函式main(),但實用程式往往由多個函式組成。函式是C源程式的基本模組,通過對函式模組的呼叫實現特定的功能。C語言中的函式相當於其它高階語言的子程式。C語言不僅提供了極為豐富的庫函式(如Turbo C,MS C都提供了三百多個庫函式),還允許使用者建立自己定義的函式。使用者可把自己的演算法編成一個個相對獨立的函式模組,然後用呼叫的方法來使用函式。 可以說C程式的全部工作都是由各式各樣的函式完成的,所以也把C語言稱為函式式語言。 由於採用了函式模組式的結構,C語言易於實現結構化程式設計。使程式的層次結構清晰,便於程式的編寫、閱讀、除錯。 在C語言中可從不同的角度對函式分類。 1) 從函式定義的角度看,函式可分為庫函式和使用者定義函式兩種。
  1. 庫函式:由C系統提供,使用者無須定義,也不必在程式中作型別說明,只需在程式前包含有該函式原型的標頭檔案即可在程式中直接呼叫。在前面各章的例題中反覆用到printf、scanf、getchar、putchar、gets、puts、strcat等函式均屬此類。
  2. 使用者定義函式:由使用者按需要寫的函式。對於使用者自定義函式,不僅要在程式中定義函式本身,而且在主調函式模組中還必須對該被調函式進行型別說明,然後才能使用。
2) 語言的函式兼有其它語言中的函式和過程兩種功能,從這個角度看,又可把函式分為有返回值函式和無返回值函式兩種。
  1. 有返回值函式:此類函式被呼叫執行完後將向呼叫者返回一個執行結果,稱為函式返回值。如數學函式即屬於此類函式。由使用者定義的這種要返回函式值的函式,必須在函式定義和函式說明中明確返回值的型別。
  2. 無返回值函式:此類函式用於完成某項特定的處理任務,執行完成後不向呼叫者返回函式值。這類函式類似於其它語言的過程。由於函式無須返回值,使用者在定義此類函式時可指定它的返回為“空型別”,空型別的說明符為“void”。
3) 從主調函式和被調函式之間資料傳送的角度看又可分為無參函式和有參函式兩種。
  1. 無參函式:函式定義、函式說明及函式呼叫中均不帶引數。主調函式和被調函式之間不進行引數傳送。此類函式通常用來完成一組指定的功能,可以返回或不返回函式值。
  2. 有參函式:也稱為帶參函式。在函式定義及函式說明時都有引數,稱為形式引數(簡稱為形參)。在函式呼叫時也必須給出引數,稱為實際引數(簡稱為實參)。進行函式呼叫時,主調函式將把實參的值傳送給形參,供被調函式使用。
4) C語言提供了極為豐富的庫函式,這些庫函式又可從功能角度作以下分類。
  1. 字元型別分類函式:用於對字元按ASCII碼分類:字母,數字,控制字元,分隔符,大小寫字母等。
  2. 轉換函式:用於字元或字串的轉換;在字元量和各類數字量(整型,實型等)之間進行轉換;在大、小寫之間進行轉換。
  3. 目錄路徑函式:用於檔案目錄和路徑操作。
  4. 診斷函式:用於內部錯誤檢測。
  5. 圖形函式:用於螢幕管理和各種圖形功能。
  6. 輸入輸出函式:用於完成輸入輸出功能。
  7. 介面函式:用於與DOS,BIOS和硬體的介面。
  8. 字串函式:用於字串操作和處理。
  9. 記憶體管理函式:用於記憶體管理。
  10. 數學函式:用於數學函式計算。
  11. 日期和時間函式:用於日期,時間轉換操作。
  12. 程序控制函式:用於程序管理和控制。
  13. 其它函式:用於其它各種功能。
以上各類函式不僅數量多,而且有的還需要硬體知識才會使用,因此要想全部掌握則需要一個較長的學習過程。應首先掌握一些最基本、最常用的函式,再逐步深入。由於課時關係,我們只介紹了很少一部分庫函式,其餘部分讀者可根據需要查閱C語言函式手冊。 還應該指出的是,在C語言中,所有的函式定義,包括主函式main在內,都是平行的。也就是說,在一個函式的函式體內,不能再定義另一個函式,即不能巢狀定義。但是函式之間允許相互呼叫,也允許巢狀呼叫。習慣上把呼叫者稱為主調函式。函式還可以自己呼叫自己,稱為遞迴呼叫。 main 函式是主函式,它可以呼叫其它函式,而不允許被其它函式呼叫。因此,C程式的執行總是從main函式開始,完成對其它函式的呼叫後再返回到main函式,最後由main函式結束整個程式。一個C源程式必須有,也只能有一個主函式main。

2.C語言函式的定義

本節講解有參函式和無參函式的定義。

①.無參函式的定義

無參函式定義的一般形式如下: 型別識別符號 函式名(){ 宣告部分 語句 } 其中型別識別符號和函式名稱為函式頭。型別識別符號指明瞭本函式的型別,函式的型別實際上是函式返回值的型別。該型別識別符號與前面介紹的各種說明符相同。函式名是由使用者定義的識別符號,函式名後有一個空括號,其中無引數,但括號不可少。 {}中的內容稱為函式體。在函式體中宣告部分,是對函式體內部所用到的變數的型別說明。 在很多情況下都不要求無參函式有返回值,此時函式型別符可以寫為void。 我們可以改寫一個函式定義:

      
  1. void Hello(){
  2. printf ("Hello,world \n");
  3. }
這裡,只把main改為Hello作為函式名,其餘不變。Hello函式是一個無參函式,當被其它函式呼叫時,輸出Hello world字串。

②.有參函式定義的一般形式

有參函式定義的一般形式如下: 型別識別符號 函式名(形式引數表列){ 宣告部分 語句 } 有參函式比無參函式多了一個內容,即形式引數表列。在形參表中給出的引數稱為形式引數,它們可以是各種型別的變數,各引數之間用逗號間隔。 在進行函式呼叫時,主調函式將賦予這些形式引數實際的值。形參既然是變數,必須在形參表中給出形參的型別說明。 例如,定義一個函式,用於求兩個數中的大數,可寫為:

      
  1. int max(int a, int b){
  2. if (a>b) return a;
  3. else return b;
  4. }
第一行說明max函式是一個整型函式,其返回的函式值是一個整數。形參為a,b,均為整型量。a,b的具體值是由主調函式在呼叫時傳送過來的。在{}中的函式體內,除形參外沒有使用其它變數,因此只有語句而沒有宣告部分。在max函式體中的return語句是把a(或b)的值作為函式的值返回給主調函式。有返回值函式中至少應有一個return語句。 在C程式中,一個函式的定義可以放在任意位置,既可放在主函式main之前,也可放在main之後。例如:可把max 函式置在main之後,也可以把它放在main之前。修改後的程式如下所示。 【例8-1】函式定義示例

      
  1. #include <stdio.h>
  2. int max(int a,int b){
  3. if(a>b)return a;
  4. else return b;
  5. }
  6. int main(void){
  7. int max(int a,int b);
  8. int x,y,z;
  9. printf("input two numbers:\n");
  10. scanf("%d%d",&x,&y);
  11. z=max(x,y);
  12. printf("maxmum=%d",z);
  13. return 0;
  14. }
現在我們可以從函式定義、函式說明及函式呼叫的角度來分析整個程式,從中進一步瞭解函式的各種特點。 程式的第1行至第4行為max函式定義。進入主函式後,因為準備呼叫max函式,故先對max函式進行說明(程式第6行)。函式定義和函式說明並不是一回事,在後面還要專門討論。 可以看出函式說明與函式定義中的函式頭部分相同,但是末尾要加分號。程式第10行為呼叫max函式,並把x、y中的值傳送給max的形參a、b。max函式執行的結果(a或b)將返回給變數z。最後由主函式輸出z的值。

3.C語言函式的呼叫

前面已經說過,在程式中是通過對函式的呼叫來執行函式體的,其過程與其它語言的子程式呼叫相似。C語言中,函式呼叫的一般形式為: 函式名(實際引數表); 對無參函式呼叫時則無實際引數表。實際引數表中的引數可以是常數,變數或其它構造型別資料及表示式。各實參之間用逗號分隔。

①.函式呼叫的方式

在C語言中,可以用以下幾種方式呼叫函式。 1) 函式表示式 函式作為表示式中的一項出現在表示式中,以函式返回值參與表示式的運算。這種方式要求函式是有返回值的。例如: z=max(x,y); 是一個賦值表示式,把max的返回值賦予變數z。 2) 函式語句 函式呼叫的一般形式加上分號即構成函式語句。例如:

      
  1. printf ("%d",a);
  2. scanf ("%d",&b);
都是以函式語句的方式呼叫函式。 3) 函式實參 函式作為另一個函式呼叫的實際引數出現。這種情況是把該函式的返回值作為實參進行傳送,因此要求該函式必須是有返回值的。例如:

      
  1. printf("%d",max(x,y));
即是把max呼叫的返回值又作為printf函式的實參來使用的。在函式呼叫中還應該注意的一個問題是求值順序的問題。所謂求值順序是指對實參表中各量是自左至右使用呢,還是自右至左使用。對此,各系統的規定不一定相同。介紹printf 函式時已提到過,這裡從函式呼叫的角度再強調一下。 【例8-3】在VC6.0下執行以下程式碼。

      
  1. #include <stdio.h>
  2. int main(void){
  3. int i=8;
  4. printf("%d %d %d %d\n",++i,++i,--i,--i);
  5. return 0;
  6. }
執行結果: 8 7 6 7 可見是按照從右至左的順序求值。 如果按照從左至右求值,結果應為: 9 10 9 8 應特別注意的是,無論是從左至右求值, 還是自右至左求值,其輸出順序都是不變的,即輸出順序總是和實參表中實參的順序相同。

②.被呼叫函式的宣告和函式原型

在主調函式中呼叫某函式之前應對該被調函式進行說明(宣告),這與使用變數之前要先進行變數說明是一樣的。在主調函式中對被調函式作說明的目的是使編譯系統知道被調函式返回值的型別,以便在主調函式中按此種類型對返回值作相應的處理。其一般形式為: 型別說明符 被調函式名( 型別 形參, 型別 形參… ); 或為: 型別說明符 被調函式名( 型別, 型別…); 括號內給出了形參的型別和形參名,或只給出形參型別。這便於編譯系統進行檢錯,以防止可能出現的錯誤。 【例8-1】main函式中對max函式的說明為:

      
  1. int max( int a, int b );
或寫為:

      
  1. int max( int, int );
C語言中又規定在以下幾種情況時可以省去主調函式中對被調函式的函式說明。 1) 如果被調函式的返回值是整型或字元型時,可以不對被調函式作說明,而直接呼叫。這時系統將自動對被調函式返回值按整型處理。【例8-2】的主函式中未對函式s作說明而直接呼叫即屬此種情形。 2) 當被調函式的函式定義出現在主調函式之前時,在主調函式中也可以不對被調函式再作說明而直接呼叫。例如【例8-1】中,函式max的定義放在main 函式之前,因此可在main函式中省去對max函式的函式說明int max(int a,int b)。 3) 如在所有函式定義之前,在函式外預先說明了各個函式的型別,則在以後的各主調函式中,可不再對被調函式作說明。例如:

      
  1. char str(int a);
  2. float f(float b);
  3. main(){
  4. /* ...... */
  5. }
  6. char str(int a){
  7. /* ...... */
  8. }
  9. float f(float b){
  10. /* ...... */
  11. }
其中第一,二行對str函式和f函式預先作了說明。因此在以後各函式中無須對str和f函式再作說明就可直接呼叫。對庫函式的呼叫不需要再作說明,但必須把該函式的標頭檔案用include命令包含在原始檔前部。

4.C語言函式的巢狀呼叫

C語言中不允許作巢狀的函式定義。因此各函式之間是平行的,不存在上一級函式和下一級函式的問題。但是C語言允許在一個函式的定義中出現對另一個函式的呼叫。這樣就出現了函式的巢狀呼叫。即在被調函式中又呼叫其它函式。這與其它語言的子程式巢狀的情形是類似的。其關係可表示如圖。 圖表示了兩層巢狀的情形。其執行過程是:執行main函式中呼叫a函式的語句時,即轉去執行a函式,在a函式中呼叫b 函式時,又轉去執行b函式,b函式執行完畢返回a函式的斷點繼續執行,a函式執行完畢返回main函式的斷點繼續執行。 【例8-4】計算s = 22! + 32! 本題可編寫兩個函式,一個是用來計算平方值的函式f1,另一個是用來計算階乘值的函式f2。主函式先調f1計算出平方值,再在f1中以平方值為實參,呼叫 f2計算其階乘值,然後返回f1,再返回主函式,在迴圈程式中計算累加和。

      
  1. #include <stdio.h>
  2. long f1(int p){
  3. int k;
  4. long r;
  5. long f2(int);
  6. k=p*p;
  7. r=f2(k);
  8. return r;
  9. }
  10. long f2(int q){
  11. long c=1;
  12. int i;
  13. for(i=1;i<=q;i++)
  14. c=c*i;
  15. return c;
  16. }
  17. int main(void){
  18. int i;
  19. long s=0;
  20. for (i=2;i<=3;i++)
  21. s=s+f1(i);
  22. printf("\ns=%ld\n",s);
  23. return 0;
  24. }
在程式中,函式f1和f2均為長整型,都在主函式之前定義,故不必再在主函式中對f1和f2加以說明。在主程式中,執行迴圈程式依次把i值作為實參呼叫函式f1求i2值。在f1中又發生對函式f2的呼叫,這時是把i2的值作為實參去調f2,在f2 中完成求i2!的計算。f2執行完畢把C值(即i2!)返回給f1,再由f1返回主函式實現累加。至此,由函式的巢狀呼叫實現了題目的要求。由於數值很大,所以函式和一些變數的型別都說明為長整型,否則會造成計算錯誤。

5.C語言函式的遞迴呼叫

一個函式在它的函式體內呼叫它自身稱為遞迴呼叫。這種函式稱為遞迴函式。C語言允許函式的遞迴呼叫。在遞迴呼叫中,主調函式又是被調函式。執行遞迴函式將反覆呼叫其自身,每呼叫一次就進入新的一層。例如有函式f如下:

      
  1. int f(int x){
  2. int y;
  3. z=f(y);
  4. return z;
  5. }
這個函式是一個遞迴函式。但是執行該函式將無休止地呼叫其自身,這當然是不正確的。為了防止遞迴呼叫無終止地進行,必須在函式內有終止遞迴呼叫的手段。常用的辦法是加條件判斷,滿足某種條件後就不再作遞迴呼叫,然後逐層返回。下面舉例說明遞迴呼叫的執行過程。 【例8-5】用遞迴法計算n! 用遞迴法計算n!可用下述公式表示: n!=1 (n=0,1) n*(n-1)! (n>1) 按公式可程式設計如下:

      
  1. #include <stdio.h>
  2. long ff(int n){
  3. long f;
  4. if(n<0) printf("n<0,input error");
  5. else if(n==0||n==1) f=1;
  6. else f=ff(n-1)*n;
  7. return(f);
  8. }
  9. int main(void){
  10. int n;
  11. long y;
  12. printf("\ninput a inteager number:\n");
  13. scanf("%d",&n);
  14. y=ff(n);
  15. printf("%d!=%ld",n,y);
  16. return 0;
  17. }
程式中給出的函式ff是一個遞迴函式。主函式呼叫ff 後即進入函式ff執行,如果n<0,n==0或n=1時都將結束函式的執行,否則就遞迴呼叫ff函式自身。由於每次遞迴呼叫的實參為n-1,即把n-1的值賦予形參n,最後當n-1的值為1時再作遞迴呼叫,形參n的值也為1,將使遞迴終止。然後可逐層退回。 下面我們再舉例說明該過程。設執行本程式時輸入為5,即求5!。在主函式中的呼叫語句即為y=ff(5),進入ff函式後,由於n=5,不等於0或1,故應執行f=ff(n-1)*n,即f=ff(5-1)*5。該語句對ff作遞迴呼叫即ff(4)。 進行四次遞迴呼叫後,ff函式形參取得的值變為1,故不再繼續遞迴呼叫而開始逐層返回主調函式。ff(1)的函式返回值為1,ff(2)的返回值為1*2=2,ff(3)的返回值為2*3=6,ff(4)的返回值為6*4=24,最後返回值ff(5)為24*5=120。

6.C語言陣列作為函式引數

陣列用作函式引數有兩種形式,一種是把陣列元素(下標變數)作為實參使用;另一種是把陣列名作為函式的形參和實參使用。

①.陣列元素作函式實參

陣列元素就是下標變數,它與普通變數並無區別。 因此它作為函式實參使用與普通變數是完全相同的,在發生函式呼叫時,把作為實參的陣列元素的值傳送給形參,實現單向的值傳送。【例5-4】說明了這種情況。 【例8-7】判別一個整數陣列中各元素的值,若大於0 則輸出該值,若小於等於0則輸出0值。程式設計如下:

      
  1. #include <stdio.h>
  2. void nzp(int v){
  3. if(v>0)
  4. printf("%d ",v);
  5. else
  6. printf("%d ",0);
  7. }
  8. int main(void){
  9. int a[5],i;
  10. printf("input 5 numbers\n");
  11. for(i=0;i<5;i++){
  12. scanf("%d",&a[i]);
  13. nzp(a[i]);
  14. }
  15. return 0;
  16. }
本程式中首先定義一個無返回值函式nzp,並說明其形參v為整型變數。在函式體中根據v值輸出相應的結果。在main函式中用一個for語句輸入陣列各元素,每輸入一個就以該元素作實參呼叫一次nzp函式,即把a[i]的值傳送給形參v,供nzp函式使用。

②.陣列名作為函式引數

用陣列名作函式引數與用陣列元素作實參有幾點不同。 1) 用陣列元素作實參時,只要陣列型別和函式的形參變數的型別一致,那麼作為下標變數的陣列元素的型別也和函式形參變數的型別是一致的。因此,並不要求函式的形參也是下標變數。換句話說,對陣列元素的處理是按普通變數對待的。 用陣列名作函式引數時,則要求形參和相對應的實參都必須是型別相同的陣列,都必須有明確的陣列說明。當形參和實參二者不一致時,即會發生錯誤。 2) 在普通變數或下標變數作函式引數時,形參變數和實參變數是由編譯系統分配的兩個不同的記憶體單元。在函式呼叫時發生的值傳送是把實參變數的值賦予形參變數。在用陣列名作函式引數時,不是進行值的傳送,即不是把實引數組的每一個元素的值都賦予形引數組的各個元素。因為實際上形引數組並不存在,編譯系統不為形引數組分配記憶體。那麼,資料的傳送是如何實現的呢?在我們曾介紹過,陣列名就是陣列的首地址。因此在陣列名作函式引數時所進行的傳送只是地址的傳送,也就是說把實引數組的首地址賦予形引數組名。形引數組名取得該首地址之後,也就等於有了實在的陣列。實際上是形引數組和實引數組為同一陣列,共同擁有一段記憶體空間。 上圖說明了這種情形。圖中設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]。 【例8-8】陣列a中存放了一個學生5門課程的成績,求平均成績。

      
  1. #include <stdio.h>
  2. float aver(float a[5]){
  3. int i;
  4. float av,s=a[0];
  5. for(i=1;i<5;i++)
  6. s=s+a[i];
  7. av=s/5;
  8. return av;
  9. }
  10. int main(void){
  11. float sco[5],av;
  12. int i;
  13. printf("\ninput 5 scores:\n");
  14. for(i=0;i<5;i++)
  15. scanf("%f",&sco[i]);
  16. av=aver(sco);
  17. printf("average score is %5.2f",av);
  18. return 0;
  19. }
本程式首先定義了一個實型函式aver,有一個形參為實型陣列a,長度為5。在函式aver中,把各元素值相加求出平均值,返回給主函式。主函式main 中首先完成陣列sco的輸入,然後以sco作為實參呼叫aver函式,函式返回值送av,最後輸出av值。 從執行情況可以看出,程式實現了所要求的功能。 3) 前面已經討論過,在變數作函式引數時,所進行的值傳送是單向的。即只能從實參傳向形參,不能從形參傳回實參。形參的初值和實參相同,而形參的值發生改變後,實參並不變化,兩者的終值是不同的。而當用陣列名作函式引數時,情況則不同。由於實際上形參和實參為同一陣列,因此當形引數組發生變化時,實引數組也隨之變化。當然這種情況不能理解為發生了“雙向”的值傳遞。但從實際情況來看,呼叫函式之後實引數組的值將由於形引數組值的變化而變化。為了說明這種情況,把【例5.4】改為【例5.6】的形式。 【例8-9】題目同【例8.7】。改用陣列名作函式引數。

      
  1. #include <stdio.h>
  2. void nzp(int a[5]){
  3. int i;
  4. printf("\nvalues of array a are:\n");
  5. for(i=0;i<5;i++){
  6. if(a[i]<0) a[i]=0;
  7. printf("%d ",a[i]);
  8. }
  9. }
  10. int main(void){
  11. int b[5],i;
  12. printf("\ninput 5 numbers:\n");
  13. for(i=0;i<5;i++)
  14. scanf("%d",&b[i]);
  15. printf("initial values of array b are:\n");
  16. for(i=0;i<5;i++)
  17. printf("%d ",b[i]);
  18. nzp(b);
  19. printf("\nlast values of array b are:\n");
  20. for(i=0;i<5;i++)
  21. printf("%d ",b[i]);
  22. return 0;
  23. }
本程式中函式nzp的形參為整陣列a,長度為5。主函式中實引數組b也為整型,長度也為5。在主函式中首先輸入陣列b的值,然後輸出陣列b的初始值。然後以陣列名b為實參呼叫nzp函式。在nzp中,按要求把負值單元清0,並輸出形引數組a的值。 返回主函式之後,再次輸出陣列b的值。從執行結果可以看出,陣列b的初值和終值是不同的,陣列b的終值和陣列a是相同的。這說明實參形參為同一陣列,它們的值同時得以改變。 用陣列名作為函式引數時還應注意以下幾點: ① 形引數組和實引數組的型別必須一致,否則將引起錯誤。形引數組和實引數組的長度可以不相同,因為在呼叫時,只傳送首地址而不檢查形引數組的長度。當形引數組的長度與實引數組不一致時,雖不至於出現語法錯誤(編譯能通過),但程式執行結果將與實際不符,這是應予以注意的。 【例8.10】如把例8.9修改如下:

      
  1. #include <stdio.h>
  2. void nzp(int a[8]){
  3. int i;
  4. printf("\nvalues of array aare:\n");
  5. for(i=0;i<8;i++){
  6. if(a[i]<0)a[i]=0;
  7. printf("%d ",a[i]);
  8. }
  9. }
  10. int main(void){
  11. int b[5],i;
  12. printf("\ninput 5 numbers:\n");
  13. for(i=0;i<5;i++)
  14. scanf("%d",&b[i]);
  15. printf("initial values of array b are:\n");
  16. for(i=0;i<5;i++)
  17. printf("%d ",b[i]);
  18. nzp(b);
  19. printf("\nlast values of array b are:\n");
  20. for(i=0;i<5;i++)
  21. printf("%d ",b[i]);
  22. return 0;
  23. }
本程式與【例8.9】程式比,nzp函式的形引數組長度改為8,函式體中,for語句的迴圈條件也改為i<8。因此,形引數組a和實引數組b的長度不一致。編譯能夠通過,但從結果看,陣列a的元素a[5]、a[6]、a[7]顯然是無意義的。 ③ 在函式形參表中,允許不給出形引數組的長度,或用一個變數來表示陣列元素的個數。例如,可以寫為: void nzp(int a[]) 或寫為 void nzp( int a[], int n ) 其中形引數組a沒有給出長度,而由n值動態地表示陣列的長度。n的值由主調函式的實參進行傳送。由此,【例8-10】又可改為【例8-11】的形式。 【例8-11】

      
  1. #include <stdio.h>
  2. void nzp(int a[],int n){
  3. int i;
  4. printf("\nvalues of array a are:\n");
  5. for(i=0;i<n;i++){
  6. if(a[i]<0) a[i]=0;
  7. printf("%d ",a[i]);
  8. }
  9. }
  10. int main(void){
  11. int b[5],i;
  12. printf("\ninput 5 numbers:\n");
  13. for(i=0;i<5;i++)
  14. scanf("%d",&b[i]);
  15. printf("initial values of array b are:\n");
  16. for(i=0;i<5;i++)
  17. printf("%d ",b[i]);
  18. nzp(b,5);
  19. printf("\nlast values of array b are:\n");
  20. for(i=0;i<5;i++)
  21. printf("%d ",b[i]);
  22. return 0;
  23. }
本程式nzp函式形引數組a沒有給出長度,由n 動態確定該長度。在main函式中,函式呼叫語句為nzp(b,5),其中實參5將賦予形參n作為形引數組的長度。 ④ 多維陣列也可以作為函式的引數。在函式定義時對形引數組可以指定每一維的長度,也可省去第一維的長度。因此,以下寫法都是合法的: int MA(int a[3][10]) 或 int MA(int a[][10])。

7.C語言區域性變數和全域性變數

在討論函式的形參變數時曾經提到,形參變數只在被呼叫期間才分配記憶體單元,呼叫結束立即釋放。這一點表明形參變數只有在函式內才是有效的,離開該函式就不能再使用了。 這種變數有效性的範圍稱變數的作用域。不僅對於形參變數,C語言中所有的量都有自己的作用域。變數說明的方式不同,其作用域也不同。 C語言中的變數,按作用域範圍可分為兩種,即區域性變數和全域性變數。

①.區域性變數

區域性變數也稱為內部變數。區域性變數是在函式內作定義說明的。其作用域僅限於函式內, 離開該函式後再使用這種變數是非法的。例如:

      
  1. int f1(int a){
  2. int b,c; /* a,b,c僅在函式f1()內有效 */
  3. }
  4. int f2(int x){
  5. int y,z; /* x,y,z僅在函式f2()內有效 */
  6. }
  7. main(){
  8. int m,n; /* m,n僅在函式main()內有效 */
  9. }
在函式f1內定義了三個變數,a為形參,b、c為一般變數。在 f1的範圍內a、b、c有效,或者說a、b、c變數的作用域限於f1內。同理,x、y、z的作用域限於f2內。m、n的作用域限於main函式內。 關於區域性變數的作用域還要說明以下幾點:
  1. 主函式中定義的變數也只能在主函式中使用,不能在其它函式中使用。同時,主函式中也不能使用其它函式中定義的變數。因為主函式也是一個函式,它與其它函式是平行關係。這一點是與其它語言不同的,應予以注意。
  2. 形參變數是屬於被調函式的區域性變數,實參變數是屬於主調函式的區域性變數。
  3. 允許在不同的函式中使用相同的變數名,它們代表不同的物件,分配不同的單元,互不干擾,也不會發生混淆。如在前例中,形參和實參的變數名都為n,是完全允許的。
  4. 在複合語句中也可定義變數,其作用域只在複合語句範圍內。例如:

      
  1. main(){
  2. int s,a;
  3. /* …… */
  4. {
  5. int b;
  6. s=a+b;
  7. /* ……*/ /*b作用域*/
  8. }
  9. /* …… */ /*s,a作用域*/
  10. }
【例8-12】

      
  1. #include <stdio.h>
  2. int main(void){
  3. int i=2, j=3, k;
  4. k=i+j;
  5. {
  6. int k=8;
  7. printf("%d\n",k);
  8. }
  9. printf("%d\n",k);
  10. return 0;
  11. }
本程式在main中定義了i、j、k三個變數,其中k未賦初值。而在複合語句內又定義了一個變數k,並賦初值為8。應該注意這兩個k不是同一個變數。在複合語句外由main定義的k起作用,而在複合語句內則由在複合語句內定義的k起作用。因此程式第3行的k為main所定義,其值應為5。第6行輸出k值,該行在複合語句內,由複合語句內定義的k起作用,其初值為8,故輸出值為8,第8行輸出i,k值。i是在整個程式中有效的,第6行對i賦值為3,故以輸出也為3。而第8行已在複合語句之外,輸出的k應為main所定義的k,此k值由第3 行已獲得為5,故輸出也為5。

②.全域性變數

全域性變數也稱為外部變數,它是在函式外部定義的變數。它不屬於哪一個函式,它屬於一個源程式檔案。其作用域是整個源程式。 在函式中使用全域性變數,一般應作全域性變數說明。只有在函式內經過說明的全域性變數才能使用。全域性變數的說明符為extern。但在一個函式之前定義的全域性變數,在該函式內使用可不再加以說明。例如: 從上例可以看出a、b、x、y 都是在函式外部定義的外部變數,都是全域性變數。但x、y 定義在函式f1之後,而在f1內又無對x、y的說明,所以它們在f1內無效。a、b定義在源程式最前面,因此在f1、f2及main內不加說明也可使用。 【例8-13】輸入正方體的長寬高l、w、h。求體積及三個面x*y、x*z、y*z的面積。

      
  1. #include <stdio.h>
  2. int s1,s2,s3;
  3. int vs( int a,int b,int c){
  4. int v;
  5. v=a*b*c;
  6. s1=a*b;
  7. s2=b*c;
  8. s3=a*c;
  9. return v;
  10. }
  11. int main(void){
  12. int v,l,w,h;
  13. printf("input length,width and height: ");
  14. scanf("%d %d %d",&l,&w,&h);
  15. v=vs(l,w,h);
  16. printf("v=%d, s1=%d, s2=%d, s3=%d\n",v,s1,s2,s3);
  17. return 0;
  18. }
【例8-14】外部變數與區域性變數同名。

      
  1. #include <stdio.h>
  2. int a=3, b=5; /* a,b為外部變數 */
  3. int max(int a,int b){ /* a,b為外部變數 */
  4. int c;
  5. c=a>b ? a : b;
  6. return c;
  7. }
  8. int main(void){
  9. int a=8;
  10. printf("%d\n",max(a,b));
  11. return 0;
  12. }
如果同一個原始檔中,外部變數與區域性變數同名,則在區域性變數的作用範圍內,外部變數被“遮蔽”,即它不起作用。

8.C語言變數的儲存類別

①.動態儲存方式與靜態動態儲存方式

前面已經介紹了,從變數的作用域(即從空間)角度來分,可以分為全域性變數和區域性變數。 從另一個角度,從變數值存在的作時間(即生存期)角度來分,可以分為靜態儲存方式和動態儲存方式。
  1. 靜態儲存方式:是指在程式執行期間分配固定的儲存空間的方式。
  2. 動態儲存方式:是在程式執行期間根據需要進行動態的分配儲存空間的方式。
使用者儲存空間可以分為三個部分: 程式區; 靜態儲存區; 動態儲存區。 全域性變數全部存放在靜態儲存區,在程式開始執行時給全域性變數分配儲存區,程式行完畢就釋放。在程式執行過程中它們佔據固定的儲存單元,而不動態地進行分配和釋放。 動態儲存區存放以下資料: 函式形式引數; 自動變數(未加static宣告的區域性變數); 函式呼叫實的現場保護和返回地址。 對以上這些資料,在函式開始呼叫時分配動態儲存空間,函式結束時釋放這些空間。 在C語言中,每個變數和函式有兩個屬性:資料型別和資料的儲存類別。

②.auto變數

函式中的區域性變數,如不專門宣告為static儲存類別,都是動態地分配儲存空間的,資料儲存在動態儲存區中。 函式中的形參和在函式中定義的變數(包括在複合語句中定義的變數),都屬此類,在呼叫該函式時系統會給它們分配儲存空間,在函式呼叫結束時就自動釋放這些儲存空間。 這類區域性變數稱為自動變數。自動變數用關鍵字auto作儲存類別的宣告。例如:

      
  1. int f(int a){ /* 定義f函式,a為引數 */
  2. auto int b,c=3; /*定義b,c自動變數*/
  3. /* …… */
  4. }
a是形參,b,c是自動變數,對c賦初值3。執行完f函式後,自動釋放a,b,c所佔的儲存單元。 關鍵字auto可以省略,auto不寫則隱含定為“自動儲存類別”,屬於動態儲存方式。

③.用static宣告區域性變數

有時希望函式中的區域性變數的值在函式呼叫結束後不消失而保留原值,這時就應該指定區域性變數為“靜態區域性變數”,用關鍵字static進行宣告。 【例8-15】考察靜態區域性變數的值。

      
  1. #include <stdio.h>
  2. int f(int a){
  3. auto int b=0;
  4. static int c=3;
  5. b=b+1;
  6. c=c+1;
  7. return (a+b+c);
  8. }
  9. int main(void){
  10. int a=2,i;
  11. for(i=0;i<3;i++)
  12. printf("%d\n",f(a));
  13. return 0;
  14. }
對靜態區域性變數的說明:
  1. 靜態區域性變數屬於靜態儲存類別,在靜態儲存區內分配儲存單元。在程式整個執行期間都不釋放。而自動變數(即動態區域性變數)屬於動態儲存類別,佔動態儲存空間,函式呼叫結束後即釋放。
  2. 靜態區域性變數在編譯時賦初值,即只賦初值一次;而對自動變數賦初值是在函式呼叫時進行,每呼叫一次函式重新給一次初值,相當於執行一次賦值語句。
  3. 如果在定義區域性變數時不賦初值的話,則對靜態區域性變數來說,編譯時自動賦初值0(對數值型變數)或空字元(對字元變數)。而對自動變數來說,如果不賦初值則它的值是一個不確定的值。
【例8-16】列印1到5的階乘值。

      
  1. #include <stdio.h>
  2. int fac(int n){
  3. static int f=1;
  4. f=f*n;
  5. return f;
  6. }
  7. int main(void){
  8. int i;
  9. for(i=1;i<=5;i++)
  10. printf("%d!=%d\n",i,fac(i));
  11. return 0;
  12. }

④.register變數

為了提高效率,C語言允許將區域性變數得值放在CPU中的暫存器中,這種變數叫“暫存器變數”,用關鍵字register作宣告。 【例8-17】使用暫存器變數。

      
  1. #include <stdio.h>
  2. int fac(int n){
  3. register int i,f=1;
  4. for(i=1;i<=n;i++)
  5. f=f*i;
  6. return f;
  7. }
  8. int main(void){
  9. int i;
  10. for(i=0;i<=5;i++)
  11. printf("%d!=%d\n",i,fac(i));
  12. return 0;
  13. }
對暫存器變數的幾點說明:
  1. 只有區域性自動變數和形式引數可以作為暫存器變數;
  2. 一個計算機系統中的暫存器數目有限,不能定義任意多個暫存器變數;
  3. 區域性靜態變數不能定義為暫存器變數。

⑤.用extern宣告外部變數

外部變數(即全域性變數)是在函式的外部定義的,它的作用域為從變數定義處開始,到本程式檔案的末尾。如果外部變數不在檔案的開頭定義,其有效的作用範圍只限於定義處到檔案終了。 如果在定義點之前的函式想引用該外部變數,則應該在引用之前用關鍵字extern對該變數作“外部變數宣告”。表示該變數是一個已經定義的外部變數。有了此宣告,就可以從“宣告”處起,合法地使用該外部變數。 【例8-18】用extern宣告外部變數,擴充套件程式檔案中的作用域。

      
  1. #include <stdio.h>
  2. int max(int x,int y){
  3. int z;
  4. z=x>y?x:y;
  5. return z;
  6. }
  7. int main(void){
  8. extern A,B;
  9. printf("%d\n",max(A,B));
  10. return 0;
  11. }
  12. int A=13, B=-8;
說明:在本程式檔案的最後1行定義了外部變數A、B,但由於外部變數定義的位置在函式main之後,因此本來在main函式中不能引用外部變數A、B。現在我們在main函式中用extern對A和B進行“外部變數宣告”,就可以從“宣告”處起,合法地使用該外部變數A和B。

來自為知筆記(Wiz)