9.1 函數概述程序員
首先,什麼是函數?函數(funcation)是用於完成特定任務的程序代碼的自包含單元。數組
爲何使用函數?第一,函數的使用能夠省去重複代碼的編寫。第二,即便某種功能在程序中只使用一次,將其以函數的形式實現也是有必要的,由於函數使得程序更加模塊化,從而有利於程序的閱讀、修改和完善。less
許多程序員喜歡把函數看做「黑盒子」,即對應必定的輸入會產生特定的結果或返回某個數值,而黑盒子的內部行爲並不須要考慮,除非是該函數的編寫者。以這種方式看待函數有助於把精力投入到程序總體設計而不是其實現細節。所以,編寫函數代碼以前首先要考慮的是函數的功能 以及函數和程序總體上的關係。模塊化
9.11 編寫和使用一個簡單的函數函數
編寫一個在一行中輸出40個星號的函數,而後咱們使用該函數打印一個簡單的信頭。程序清單9.1給出了完整的程序,它由main()函數和starbar()函數組成。測試
程序清單 9.1 lethead1.c程序ui
/*lethead1.c*/ #include <stdio.h> #define NAME "GIGATHINK,INC." #define ADDRESS "101 Megabuck plaza" #define PLACE "Megapolis,CA 94904" #define WIDTH 40 void starbar(void); /*聲明函數原型*/ int main (void) { starbar(); /*使用函數*/ printf("%s\n",NAME); printf("%s\n",ADDRESS); printf("%s\n",PLACE); starbar(); return 0; } void starbar(void) /*定義函數*/ { int count; for (count=1;count<=WIDTH;count++) putchar('*'); putchar('\n'); }
9.1.2 程序分析spa
關於函數有如下幾點須要注意:prototype
一、starbar標識符在不一樣位置使用了3次:函數原型(funcation prototype)告知編譯器starbar()的函數類型;函數調用(funcation call)致使該函數的執行,而函數定義(funcation definition)則確切指定了該函數的具體功能。設計
二、函數同變量同樣有多種類型。任何程序在使用函數以前都須要聲明該函數的類型。
void starbar(void)
圓括號代表starbar是一個函數名。第一個void指的是函數類型,它的意思是該函數沒有返回值。第二個void位於圓括號內代表該函數不接受任何參數;分號的做用是表示該語句是進行函數聲明而不是函數定義。也就是說,這一行聲明瞭程序將使用一個名爲starbar()且函數類型爲void的函數,同時通知編譯器須要在其餘位置找到該函數的定義。對於不識別ANSI C原型的編譯器,只須要聲明函數的類型,就像下面這樣:void starbar();
注意:一些老版本的編譯器不能識別void類型。這時,須要把沒有返回值的函數聲明爲INT類型。
三、程序把starbar()原型置於main()以前,也能夠置於main()以內,能夠放置變量聲明的任何位置,這兩種方法都正確。
四、程序在main()中經過使用函數名後跟圓括號和分號的格式調用函數starbar(),語句以下:starbar();這是void類型函數的通常調用形式。當計算機執行到starbar();語句時,它找到starbar()函數並執行其中的指令。執行完starbar()中的代碼後,計算機返回到調用函數(calling funcation)的下一行繼續執行。在本例中,調用函數是main()。
五、程序中starbar()和main()具備相同的定義格式,即首先以類型、名稱和圓括號開始,接着是開始花括號、變量聲明、函數語句定義以及結束花括號。注意此處的starbar()後沒有分號,這告訴編譯器您是在定義函數starbar()而不是在調用它或聲明它的原型。
六、能夠將starbar()和main()放在同一個文件中,也能夠將它們放在不一樣的文件中。單文件形式比較容易編譯,而使用兩個文件則有利於在不一樣程序中使用相同的函數。若是您把函數寫在了另一個單獨的文件中,則在那個文件中必須加入#define和#include指令。
七、starbar()中的變量count是一個局部變量。這意味着該變量只在starbar()中可用。即便您在其餘函數包括main()函數中使用名稱count,也不會出現任何衝突,您將獲得具備同一名稱的多個單獨的、互不相關的變量。
由於不須要來自調用函數的任何信息,因此它沒有輸入參數。同時它不向main()提供任何信息,所以starbar()也沒有返回值。簡言之,starbar()不須要同調用函數進行任何通訊。
9.1.3 函數參數
在上例中,若是文字居中顯示那麼信頭就會更漂亮。能夠經過在打印文字以前打印必定數目的空格 來達到此目的 。這和starbar()函數相似。在starbar()中打印的是必定數量的星號,而如今要打印的是必定數目的空格。遵循c的設計思想,咱們不該爲每一個任務編寫一個單獨的函數,而應該編寫一個能夠同時勝任這兩個任務的更爲通用的函數。新函數將命名爲show_n_char()。惟一改變是要顯示的字符和顯示次數將被做爲參數傳遞給函數show_n_char(),而不是將它們置於函數內部。
具體一點說,假如一行是40個字符寬。40個星號填滿一行,調用函數show_n_char('*',40)能夠同starbar()同樣實現該功能。而將GIGATHINK,INC.居中須要多少個空格?由於 GIGATHINK,INC. 是15個字符寬,所以在前面的例子中該短語後跟有25個空格。爲使其居中,必須先輸出12個空格,這樣該短語兩邊就會分別有13個和12個空格。因此,能夠調用 show_n_char(' ',12)輸出12個空格。
除了使用參數外,在其餘方面show_n_char()函數和starbar()很是類似。二者的一個不一樣之處是show_n_char()不像starbar()那樣輸出換行符,由於在同一行中可能還須要輸出其餘文字。程序清單9.2給出了改進後的程序。爲了強調參數的使用,程序中使用了多種參數形式。
程序清單 9.2 lethead2.c 程序
#include <stdio.h> #include <string.h> /*爲strlen()提供原型*/ #define NAME "GIGATHINK, INC. " #define ADDRESS "101 Megabuck plaza" #define PLACE "Megapolis,CA 94904" #define WIDTH 40 #define SPACE ' ' void show_n_char(char ch,int num); int main(void) { int spaces; show_n_char('*',WIDTH); /*使用常量做爲參數*/ putchar('\n'); show_n_char(SPACE,12); /*使用常量做爲參數*/ printf("%s\n",NAME); spaces=(WIDTH-strlen(ADDRESS))/2; /*讓程序計算須要多少個空格*/ show_n_char(SPACE,spaces); /*使用變量做爲參數*/ printf("%s\n",ADDRESS); show_n_char(SPACE,(WIDTH-strlen(PLACE))/2); /*用一個表達式做爲參數*/ printf("%s\n",PLACE); show_n_char('*',WIDTH); putchar('\n'); return 0; } void show_n_char(char ch,int num) { int count ; for (count=1;count <= num; count++) putchar(ch); }
9.1.4 定義帶有參數的函數:形式參量
函數定義如下面的ANSI C 函數頭開始
void show_n_char(char ch,int num)
這行代碼通知編譯器show_n_char()使用名爲ch和num的兩個參數,而且這兩個參數的類型分別是char和int。變量ch和num被稱爲形式參數(formal argument)或形式參量(formal parameter,如今這個名稱更爲正式)。如同函數內部定義的變量同樣,形式參量是局部變量,它們是函數私有的。這意味着能夠其餘函數中使用相同的變量名。每當調用函數時,這此變量就會被賦值。
注意,ANSI C 形式要求在每一個變量前聲明其類型。也就是說,不能像一般的變量聲明那樣使用變量列表來聲明同一類型的變量,以下所示:
void dibs (int x,y,z) /*不正確的函數頭*/
void dubs (int x, int y,int z) /*正確的函數頭*/
儘管show_n_char()接收來自main()的數值,可是它沒有返回值。所以,show_n_char()的類型是void。
9.1.5 帶參數函數的原型聲明
使用函數以前須要用ANSI原型聲明該函數:
void show_n_char(char ch,int num);
當函數接受參數時,函數原型經過使用一個逗號分隔的類型列表指明參數的個數和類型。在函數原型中,能夠根據我的的喜愛省略變量名:
void show_n_char(char,int);
在原型中使用變量名並無實際的建立變量。這只是說明char表明了一個char類型變量,依此類推。
9.1.6 調用帶有參數的函數:實際參數
函數調用中,經過使用實際參數(actual argument)對ch 和 num賦值。
show_n_char(SPACE,12);
實際參數是空格字符和12。這兩個數值被賦給show_n_char()中相應的形式參量:變量ch和num。換句話說,形式參量是被調函數中的變量,而實際參數是調用函數分配給被調用函數變量的特定數值。實際參數能夠是常量、變量或一個複雜的表達式。可是不管何種形式的實際參數,執行時首先要計算其值,而後將該值複製給被調函數中相應的形式參量。
再次,實際參數是賦給被稱爲形式參量的函數變量的具體值。由於被調函數使用的值是從調用函數中複製而來的,因此無論在被調函數中對複製數值進行什麼操做,調用函數中的原數值不會受到任何影響。
*當一個函數被調用時,將建立被聲明爲形式參量的變量,而後用計算後獲得的實際參數的值初始化該變量。
9.1.7 黑盒子觀點
黑盒子方法的核心部分在於ch num 和count都是show_n_char()私有的局部變量。也就說,若是在main()中使用相同名字的變量,它們相互獨立,互不影響。例如,若是在main()中存在一個count變量,那麼該 變量值的改變不會影響show_n_char()中的count變量,其他變量也是如此。黑盒子的一切操做對調用函數來講是不可見的。
9.1.8 使用return從函數中返回一個值
前面討論了從調用函數到被調用函數的通訊方法。須要沿相反方向傳遞信息時,可使用函數返回值。爲了進一步說明,咱們將構建一個比較兩個參數大小並將小數值返回的函數。由於比較的是int類型的數值,因此函數被命名爲imin()。同時,爲了檢查imin()的執行結果,須要編寫一個簡單的main()函數。這種用來測試函數的程序有時被稱爲驅動程序。驅動程序實際調用了被測試的函數。若是該函數成功經過了測試,那麼它就能夠在一個更爲重要的程序中使用。
程序清單9.3
/*lesser.c --找出兩個整數中的較小者*/ #include <stdio.h> int imin(int,int); int main(void) { int evil1,evil2; printf("Enter a pair of integers (q to quit): \n"); while(scanf("%d %d",&evil1,&evil2)==2) { printf("The lesser of %d and %d is %d.\n", evil1,evil2,imin(evil1,evil2)); printf("Enter a pair of integer (q to quit): \n"); } printf("Bye.\n"); return 0; } int imin(int n,int m) { int min; if(n<m) min=n; else min=m; return min; }
關鍵字return指明瞭其後的表達式的數值便是該函數的返回值。在本例子中,函數返回變量min的數值。由於min的類型是int,因此函數imin()的類型也是int。
變量min是imin()私有的,可是return語句把min的數值返回給了調用函數。下面這個語句的做用至關於把min的值賦給lesser:
lesser=imin(n,m);
可否用下廁所這個語句代替上句:
imin(n,m); lesser=min;
答案是否認的,由於調用函數並不知道min變量的存在。imin()中的變量是該函數的局部變量。函數調用imin(evil1,evil2)只是複製了兩個變量的數值。
返回值不只能夠被賦給 一個變量,也能夠被用做表達式的一部分。例如:
answer=2*imin(z,zstar)+25;
printf("%d\n",imin(-32+answer,LIMIT));
返回值能夠由任何表達式計算得出,而不是僅僅來自於變量。例如:
/*最小函數值的第2個版本*/
imin(int n,int m)
{
return ((n<m) ? n : m) ;
}
當函數返回值的類型和聲明的類型不相同時會有什麼結果呢?
這時,實際返回值是當把指定要返回的值賦給 一個具備所聲明的返回類型的變量時獲得的數值。
return語句的另外一做用是終止執行函數,並把控制返回給調用函數的下一個語句。即便return語句不是函數最後一個語句,其執行結果也是如此。所以,能夠用下面的方式編寫imin()函數:
/*最小值函數的第3個版本*/
imin(int n,int m)
{
if (n<m)
return n;
else
return m;
}
許多C程序員更傾向於只在函數結尾使用一次return語句,由於這樣更有利於閱讀程序的人明白函數的執行流程。
您也可使用如下語句:
return ;
這個語句會終止執行函數並把控制返回給調用函數。由於return後沒有任何表達式,因此沒有返回值,這種形式只能用於void類型的函數之中。
9.1.9 函數類型
函數應該進行類型聲明。同時其類型應該和返回值類型相同。而無返回值的函數應該被聲明爲void類型。
類型聲明是函數定義的一部分。但須要注意的是該 類型是返回值類型,而不是函數參數類型。
爲正確的使用函數,程序在首次調用函數以前須要知道該函數的類型。途徑之一是在第一次調用以前進行完整的函數定義。可是,這種方式會使得程序難於閱讀。並且,須要的函數可能在C庫中或其餘文件中。所以,一般的作法是預先對函數進行聲明,以便將函數的信息通知給編譯器。
在上例的代碼中,函數的預先聲明被放在調用函數以外。也能夠在調用函數內部預先聲明被調函數。
在以上兩種形式中,須要重點注意的是函數聲明要在使用函數以前進行。
不要把函數聲明和函數定義混淆。函數聲明只是將函數類型告訴編譯器,而函數定義部分則是函數的實際實現代碼。引用math.h頭文件只向編譯器說明了sqrt()的返回值類型double,可是sqrt()的實現代碼則位於另一個庫函數文件中。