嵌入式C語言基礎編程—5年程式設計師給你講函數,你真的懂函數嗎?

it百科大學堂 發佈 2020-05-18T02:29:27+00:00

本文主要是對C基礎編程關於函數的初步講解,後續會深入講解C高級相關的概念(C大神可先略過)。 本人近期會陸續上傳IT編程相關的資料和視頻教程,可以關注一下互相交流:C C++ Java python linux ARM 嵌入式 物聯網等。

本文主要是對C基礎編程關於函數的初步講解,後續會深入講解C高級相關的概念(C大神可先略過)。 本人近期會陸續上傳IT編程相關的資料和視頻教程,可以關注一下互相交流:C C++ Java python linux ARM 嵌入式 物聯網等。想學編程的朋友進入主頁即可看到相關教程和資料。

本文主要講述的內容:

1函數概述

2函數定義的一般形式

3函數的參數和函數的值

3.1形式參數和實際參數

3.2函數的返回值

4函數的調用

4.1函數調用的一般形式

4.2函數調用的方式

4.3被調用函數的聲明和函數原型

4.4函數的嵌套調用

4.5函數的遞歸調用

5數組作為函數參數

6局部變量和全局變量

6.1局部變量

6.2全局變量

7變量的存儲類別

7.1動態存儲方式與靜態動態存儲方式

7.2auto 變量

7.3用 static 聲明局部變量

7.4register 變量

7.5用 extern 聲明外部變量

8小結

9.1 函數概述


在前面已經介紹過,C源程序是由函數組成的。雖然在前面各章的程序中大都只有一個主函數 int main(int argc, char **argv),但實用程序往往由多個函數組成。函數是C源程序的基本模塊,通過對函數模塊的調用實現特定的功能。C語言中的函數相當於其它高級語言的子程序。C語言不僅提供了極為豐富的庫函數(如 Turbo C,MS C 都提供了三百多個庫函數),還允許用戶建立自己定義的函數。用戶可把自己的算法編成一個個相對獨立的函數模塊,然後用調用的方法來使用函數。可以說C程序的全部工作都是由各式各樣的函數完成的, 所以也把C語言稱為函數式語言。

由於採用了函數模塊式的結構,C語言易於實現結構化程序設計。使程序的層次結構清 晰,便於程序的編寫、閱讀、調試。

在C語言中可從不同的角度對函數分類。

1. 從函數定義的角度看,函數可分為庫函數和用戶定義函數兩種

1) 庫函數:由C系統提供,用戶無須定義,也不必在程序中作類型說明,只需在程序 前包含有該函數原型的頭文件即可在程序中直接調用。在前面各章的例題中反覆用 到 printf、scanf、getchar、putchar、gets、puts、strcat 等函數均屬此類。

2) 用戶定義函數:由用戶按需要寫的函數。對於用戶自定義函數,不僅要在程序中定 義函數本身,而且在主調函數模塊中還必須對該被調函數進行類型說明,然後才能 使用。

2. C語言的函數兼有其它語言中的函數和過程兩種功能,從這個角度看,又可把函數分為有返回值函數和無返回值函數兩種

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語言中,所有的函數定義,包括主函數 main 在內,都是平行的。也就是說,在一個函數的函數體內,不能再定義另一個函數,即不能嵌套定義。但是函數之間允許相互調用,也允許嵌套調用。習慣上把調用者稱為主調函數。函數還可以自己調用自己,稱為遞歸調用。

main 函數是主函數,它可以調用其它函數,而不允許被其它函數調用。因此,C程序的執行總是從 main 函數開始,完成對其它函數的調用後再返回到 main 函數,最後由 main 函數結束整個程序。一個C源程序必須有,也只能有一個主函數 main。

9.2 函數定義的一般形式


1. 無參函數的定義形式類型標識符 函數名(){聲明部分語句}

其中類型標識符和函數名稱為函數頭。類型標識符指明了本函數的類型,函數的類型實

際上是函數返回值的類型。 該類型標識符與前面介紹的各種說明符相同。函數名是由用戶定義的標識符,函數名後有一個空括號,其中無參數,但括號不可少。

{}中的內容稱為函數體。在函數體中聲明部分,是對函數體內部所用到的變量的類型說明。

在很多情況下都不要求無參函數有返回值,此時函數類型符可以寫為 void。我們可以改寫一個函數定義:

這裡,只把 main 改為 Hello 作為函數名,其餘不變。Hello 函數是一個無參函數,當被其它函數調用時,輸出 Hello world 字符串。

2. 有參函數定義的一般形式

類型標識符 函數名(形式參數表列)

{

聲明部分語句

}

有參函數比無參函數多了一個內容,即形式參數表列。在形參表中給出的參數稱為形式參數,它們可以是各種類型的變量,各參數之間用逗號間隔。在進行函數調用時,主調函數 將賦予這些形式參數實際的值。形參既然是變量,必須在形參表中給出形參的類型說明。

例如,定義一個函數,用於求兩個數中的大數,可寫為:


第一行說明 max 函數是一個整型函數,其返回的函數值是一個整數。形參為 a,b,均為整型量。

a,b 的具體值是由主調函數在調用時傳送過來的。在{}中的函數體內,除形參外沒有使用其它變量,因此只有語句而沒有聲明部分。在 max 函數體中的 return 語句是把 a(或 b)的值作為函數的值返回給主調函數。有返回值函數中至少應有一個 return 語句。

在C程序中,一個函數的定義可以放在任意位置,既可放在主函數 main 之前,也可放在 main 之後。

例如:

可把 max 函數置在 main 之後,也可以把它放在 main 之前。修改後的程序如下所示。

【例 9.1】

現在我們可以從函數定義、函數說明及函數調用的角度來分析整個程序,從中進一步了 解函數的各種特點。

程序的第 1 行至第 5 行為 max 函數定義。進入主函數後,因為準備調用 max 函數,故先對 max 函數進行說明(程序第 8 行)。函數定義和函數說明並不是一回事,在後面還要專門討論。 可以看出函數說明與函數定義中的函數頭部分相同,但是末尾要加分號。程序第 12 行為調用 max 函數,並把 x, y 中的值傳送給 max 的形參 a, b。max 函數執行的結果(a 或 b)將返回給變量 z。最後由主函數輸出 z 的值。

9.3 函數的參數和函數的值

9.3.1 形式參數和實際參數


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

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

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

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

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

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


【例 9.2】可以說明這個問題。

本程序中定義了一個函數 s,該函數的功能是求∑ni 的值。在主函數中輸入 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。可見實參的值不隨形參的變化而變化。

9.3.2 函數的返回值


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

1) 函數的值只能通過 return 語句返回主調函數。

return 語句的一般形式為:

return 表達式; 或者為:

return (表達式); 該語句的功能是計算表達式的值,並返回給主調函數。在函數中允許有多個 return

語句,但每次調用只能有一個 return 語句被執行,因此只能返回一個函數值。

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

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

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

void s(int n)

{

……

}

一旦函數被定義為空類型後,就不能在主調函數中使用被調函數的函數值了。 例如,在定義 s 為空類型後,在主函數中寫下述語句

sum = s(n); 就是錯誤的。

為了使程序有良好的可讀性並減少出錯,凡不要求返回值的函數都應定義為空 類型。

9.4 函數的調用

9.4.1 函數調用的一般形式


前面已經說過,在程序中是通過對函數的調用來執行函數體的,其過程與其它語言的子 程序調用相似。

C語言中,函數調用的一般形式為:

函數名(實際參數表)

對無參函數調用時則無實際參數表。實際參數表中的參數可以是常數,變量或其它構造類型數據及表達式。各實參之間用逗號分隔。

9.4.2 函數調用的方式


在C語言中,可以用以下幾種方式調用函數:

1. 函數表達式:函數作為表達式中的一項出現在表達式中,以函數返回值參與表達式的運 算。這種方式要求函數是有返回值的。例如:z=max(x,y)是一個賦值表達式,把 max 的返回值賦予變量 z。

2. 函數語句: 函數調用的一般形式加上分號即構成函數語句。例如: printf ("%d",a);scanf ("%d",&b);都是以函數語句的方式調用函數。

3. 函數實參:函數作為另一個函數調用的實際參數出現。這種情況是把該函數的返回值作為實參進行傳送,因此要求該函數必須是有返回值的。例如: printf("%d",max(x,y)); 即是把 max 調用的返回值又作為 printf 函數的實參來使用的。在函數調用中還應該注意的一個問題是求值順序的問題。所謂求值順序是指對實參表中各量是自左至右使用呢, 還是自右至左使用。對此,各系統的規定不一定相同。介紹 printf 函數時已提到過, 這裡從函數調用的角度再強調一下。

【例 9.3】

如按照從右至左的順序求值。運行結果應為: 8

7

7

8

如對 printf 語句中的++i,--i,i++,i--從左至右求值,結果應為: 9

8

8

9

應特別注意的是,無論是從左至右求值, 還是自右至左求值,其輸出順序都是不變的, 即輸出順序總是和實參表中實參的順序相同。有些編譯器,如 Turbo C,是自右至左求值, 所以結果為 8,7,7,8。上述問題如還不理解,上機一試就明白了。

9.4.3 被調用函數的聲明和函數原型


在主調函數中調用某函數之前應對該被調函數進行說明(聲明),這與使用變量之前要先 進行變量說明是一樣的。在主調函數中對被調函數作說明的目的是使編譯系統知道被調函數 返回值的類型,以便在主調函數中按此種類型對返回值作相應的處理。

其一般形式為:

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

或為:

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

括號內給出了形參的類型和形參名,或只給出形參類型。這便於編譯系統進行檢錯,以防止可能出現的錯誤。

例 9.1 main 函數中對 max 函數的說明為: int max(int a,int b);

或寫為:

int max(int,int);

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

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

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

3) 如在所有函數定義之前,在函數外預先說明了各個函數的類型,則在以後的各主調

函數中,可不再對被調函數作說明。例如: char str(int a);

float f(float b);

int main(int argc, char **argv)

{

……

}

char str(int a)

{

……

}

float f(float b)

{

……

}

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

4) 對庫函數的調用不需要再作說明,但必須把該函數的頭文件用 include 命令包含在源文件前部。

9.4.4 函數的嵌套調用


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

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

【例 9.4】計算 s=22!+32!

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


在程序中,函數 f1 和 f2 均為長整型,都在主函數之前定義,故不必再在主函數中對 f1 和 f2 加以說明。在主程序中,執行循環程序依次把 i 值作為實參調用函數 f1 求 i2 值。在 f1中又發生對函數 f2 的調用,這時是把 i2 的值作為實參去調 f2,在 f2 中完成求 i2!的計算。

f2 執行完畢把 C 值(即 i2!)返回給 f1,再由 f1 返回主函數實現累加。至此,由函數的嵌套調用實現了題目的要求。由於數值很大,所以函數和一些變量的類型都說明為長整型,否則會 造成計算錯誤。

9.4.5 函數的遞歸調用


一個函數在它的函數體內調用它自身稱為遞歸調用。這種函數稱為遞歸函數。C語言允 許函數的遞歸調用。在遞歸調用中,主調函數又是被調函數。執行遞歸函數將反覆調用其自 身,每調用一次就進入新的一層。

例如有函數 f 如下:

這個函數是一個遞歸函數。但是運行該函數將無休止地調用其自身,這當然是不正確的。 為了防止遞歸調用無終止地進行,必須在函數內有終止遞歸調用的手段。常用的辦法是加條件判斷,滿足某種條件後就不再作遞歸調用,然後逐層返回。下面舉例說明遞歸調用的執行過程。

【例 9.5】用遞歸法計算 n!

用遞歸法計算 n!可用下述公式表示:

n!=1 (n=0,1)

n×(n-1)! (n>1)

按公式可編程如下:


程序中給出的函數 factorial 是一個遞歸函數。主函數調用 factorial 後即進入函數

factorial 執行,如果 n<0,n==0 或 n=1 時都將結束函數的執行,否則就遞歸調用 factorial 函數自身。由於每次遞歸調用的實參為 n-1,即把 n-1 的值賦予形參 n,最後當 n-1 的值為 1 時再作遞歸調用,形參 n 的值也為 1,將使遞歸終止。然後可逐層退回。

下面我們再舉例說明該過程。設執行本程序時輸入為 5,即求 5!。在主函數中的調用語句即為 y=factorial(5) , 進入 factorial 函數後, 由於 n=5, 不等於 0 或 1 , 故應執行f=factorial(n-1)*n, 即 f=factorial(5-1)*5 。 該 語 句 對 factorial 作 遞 歸 調 用 即

factorial(4)。

進行四次遞歸調用後,factorial 函數形參取得的值變為 1,故不再繼續遞歸調用而開始逐層返回主調函數。factorial(1)的函數返回值為 1,factorial(2)的返回值為 1*2=2,

factorial(3)的返回值為2*3=6,factorial(4)的返回值為6*4=24,最後返回值factorial(5)為 24*5=120。

例 9.5 也可以不用遞歸的方法來完成。如可以用遞推法,即從 1 開始乘以 2,再乘以 3… 直到 n。遞推法比遞歸法更容易理解和實現。但是有些問題則只能用遞歸算法才能實現。

9.5 數組作為函數參數


數組可以作為函數的參數使用,進行數據傳送。數組用作函數參數有兩種形式,一種是 把數組元素(下標變量)作為實參使用;另一種是把數組名作為函數的形參和實參使用。

1. 數組元素作函數實參

數組元素就是下標變量,它與普通變量並無區別。 因此它作為函數實參使用與普通變量是完全相同的,在發生函數調用時,把作為實參的數組元素的值傳送給形參,實現單向的值 傳送。例 5.4 說明了這種情況。

【例 9.6】判別一個整數數組中各元素的值,若大於 0 則輸出該值,若小於等於 0 則輸出 0 值。編程如下:


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

2. 數組名作為函數參數

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

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]。

【例 9.7】數組 a 中存放了一個學生 5 門課程的成績,求平均成績。

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

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

【例 9.8】題目同 8.7 例。改用數組名作函數參數。

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

用數組名作為函數參數時還應注意以下幾點:

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

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

【例 9.9】如把例 9.8 修改如下:

本程序與例 9.8 程序比,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 的值由主調函數的實參進行傳送。

由此,例 9.9 又可改為例 9.10 的形式。

【例 9.10】

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

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

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

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

9.6 局部變量和全局變量


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

9.6.1 局部變量


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

例如:

int f1(int a) /*函數 f1*/

{

int b,c;

……

}

a,b,c 有效

int f2(int x) /*函數 f2*/

{

int y,z;

……

}

x,y,z 有效

int main(int argc, char **argv)

{

int m,n;

……

}

m,n 有效

在函數 f1 內定義了三個變量,a 為形參,b,c 為一般變量。在 f1 的範圍內 a,b,c 有效, 或者說 a,b,c 變量的作用域限於 f1 內。同理,x,y,z 的作用域限於 f2 內。m,n 的作用域限於main 函數內。關於局部變量的作用域還要說明以下幾點:

1) 主函數中定義的變量也只能在主函數中使用,不能在其它函數中使用。同時,主函 數中也不能使用其它函數中定義的變量。因為主函數也是一個函數,它與其它函數 是平行關係。這一點是與其它語言不同的,應予以注意。

2) 形參變量是屬於被調函數的局部變量,實參變量是屬於主調函數的局部變量。

3) 允許在不同的函數中使用相同的變量名,它們代表不同的對象,分配不同的單元, 互不干擾,也不會發生混淆。如在前例中,形參和實參的變量名都為 n,是完全允許的。

4) 在複合語句中也可定義變量,其作用域只在複合語句範圍內。 例如:

int main(int argc, char **argv)

{

int s,a;

……

{

int b; s=a+b;

…… /*b 作用域*/

}

…… /*s,a 作用域*/

}

【例 9.11】

本程序在 main 中定義了 i,j,k 三個變量,其中 k 未賦初值。而在複合語句內又定義了一個變量 k,並賦初值為 8。應該注意這兩個 k 不是同一個變量。在複合語句外由 main 定義的

k 起作用,而在複合語句內則由在複合語句內定義的 k 起作用。因此程序第 4 行的 k 為 main 所定義,其值應為 5。第 7 行輸出 k 值,該行在複合語句內,由複合語句內定義的 k 起作用, 其初值為 8,故輸出值為 8,第 9 行輸出 i,k 值。i 是在整個程序中有效的,第 7 行對 i 賦值為 3,故以輸出也為 3。而第 9 行已在複合語句之外,輸出的 k 應為 main 所定義的 k,此 k 值由第 4 行已獲得為 5,故輸出也為 5。

9.6.2 全局變量


全局變量也稱為外部變量,它是在函數外部定義的變量。它不屬於哪一個函數,它屬於一個源程序文件。其作用域是整個源程序。在函數中使用全局變量,一般應作全局變量說明。 只有在函數內經過說明的全局變量才能使用。全局變量的說明符為 extern。但在一個函數之前定義的全局變量,在該函數內使用可不再加以說明。

例如:

int a,b; /*外部變量*/

void f1() /*函數 f1*/

{

……

}

float x,y; /*外部變量*/

int fz() /*函數 fz*/

{

……

}

int main(int argc, char **argv) /*主函數*/

{

……

}

從上例可以看出 a、b、x、y 都是在函數外部定義的外部變量,都是全局變量。但 x,y 定義在函數 f1 之後,而在 f1 內又無對 x,y 的說明,所以它們在 f1 內無效。a,b 定義在源程序最前面,因此在 f1,f2 及 main 內不加說明也可使用。

【例 9.12】輸入正方體的長寬高 l,w,h。求體積及三個面 x*y,x*z,y*z 的面積。


9.7 變量的存儲類別

9.7.1 動態存儲方式與靜態動態存儲方式


前面已經介紹了,從變量的作用域(即從空間)角度來分,可以分為全局變量和局部變量。

從另一個角度,從變量值存在的作時間(即生存期)角度來分,可以分為靜態存儲方式和動態存儲方式。

靜態存儲方式:是指在程序運行期間分配固定的存儲空間的方式。

動態存儲方式:是在程序運行期間根據需要進行動態的分配存儲空間的方式。 用戶存儲空間可以分為三個部分:

1) 程序區;

2) 靜態存儲區;

3)動態存儲區;

全局變量全部存放在靜態存儲區,在程序開始執行時給全局變量分配存儲區,程序行完 畢就釋放。在程序執行過程中它們占據固定的存儲單元,而不動態地進行分配和釋放;

動態存儲區存放以下數據:

1) 函數形式參數;

2) 自動變量(未加 static 聲明的局部變量);

3) 函數調用實的現場保護和返回地址;

對以上這些數據,在函數開始調用時分配動態存儲空間,函數結束時釋放這些空間。 在 c 語言中,每個變量和函數有兩個屬性:數據類型和數據的存儲類別。

9.7.2 auto 變量


函數中的局部變量,如不專門聲明為 static 存儲類別,都是動態地分配存儲空間的,數據存儲在動態存儲區中。函數中的形參和在函數中定義的變量(包括在複合語句中定義的變 量),都屬此類,在調用該函數時系統會給它們分配存儲空間,在函數調用結束時就自動釋放 這些存儲空間。這類局部變量稱為自動變量。自動變量用關鍵字 auto 作存儲類別的聲明。

例如:

int f(int a) /*定義 f 函數,a 為參數*/

{auto int b,c=3; /*定義 b,c 自動變量*/

……

}

a 是形參,b,c 是自動變量,對 c 賦初值 3。執行完 f 函數後,自動釋放 a,b,c 所占的存儲單元。

關鍵字 auto 可以省略,auto 不寫則隱含定為"自動存儲類別",屬於動態存儲方式。

9.7.3 用 static 聲明局部變量


有時希望函數中的局部變量的值在函數調用結束後不消失而保留原值,這時就應該指定局部變量為"靜態局部變量",用關鍵字 static 進行聲明。

【例 9.13】考察靜態局部變量的值。


對靜態局部變量的說明:

1) 靜態局部變量屬於靜態存儲類別,在靜態存儲區內分配存儲單元。在程序整個運行期間 都不釋放。而自動變量(即動態局部變量)屬於動態存儲類別,占動態存儲空間,函數 調用結束後即釋放。

2) 靜態局部變量在編譯時賦初值,即只賦初值一次;而對自動變量賦初值是在函數調用時 進行,每調用一次函數重新給一次初值,相當於執行一次賦值語句。

3) 如果在定義局部變量時不賦初值的話,則對靜態局部變量來說,編譯時自動賦初值 0(對數值型變量)或空字符(對字符變量)。而對自動變量來說,如果不賦初值則它的值是一個不確定的值。

【例 9.14】列印 1 到 5 的階乘值。


9.7.4 register 變量


為了提高效率,C 語言允許將局部變量得值放在 CPU 中的寄存器中,這種變量叫"寄存器變量",用關鍵字 register 作聲明。

【例 9.15】使用寄存器變量。

說明:

1) 只有局部自動變量和形式參數可以作為寄存器變量;

2) 一個計算機系統中的寄存器數目有限,不能定義任意多個寄存器變量;

3) 局部靜態變量不能定義為寄存器變量。

9.7.5 用 extern 聲明外部變量

外部變量(即全局變量)是在函數的外部定義的,它的作用域為從變量定義處開始,到 本程序文件的末尾。如果外部變量不在文件的開頭定義,其有效的作用範圍只限於定義處到 文件終了。如果在定義點之前的函數想引用該外部變量,則應該在引用之前用關鍵字 extern 對該變量作"外部變量聲明"。表示該變量是一個已經定義的外部變量。有了此聲明,就可以 從"聲明"處起,合法地使用該外部變量。

【例 9.16】用 extern 聲明外部變量,擴展程序文件中的作用域。


說明:在本程序文件的最後 1 行定義了外部變量 A,B,但由於外部變量定義的位置在函數 main之後,因此本來在 main 函數中不能引用外部變量 A,B。現在我們在 main 函數中用 extern 對 A 和 B 進行"外部變量聲明",就可以從"聲明"處起,合法地使用該外部變量 A 和 B。

9.8 本章小結

1.函數可分為庫函數和用戶定義函數兩種

2.形參出現在函數定義中,在整個函數體內都可以使用,離開該函數則不能使用。實參出現 在主調函數中,進入被調函數後,實參變量也不能使用。形參和實參的功能是作數據傳送。

3.函數調用方式:函數表達式、函數語句、函數實參。

4. 數組可以作為函數的參數使用,進行數據傳送,有兩種形式,一種是把數組元素(下標變 量)作為實參使用;另一種是把數組名作為函數的形參和實參使用。

5. 變量有效性的範圍稱變量的作用域, 按作用域範圍可分為兩種,即局部變量和全局變量。變量有效性的範圍稱變量的生存期,可以分為靜態存儲方式和動態存儲方式

關鍵字: