起首,什么是函數?函數(function)是完成特定職責的獨立步驟代碼單位。語法例則界說了函數的布局和使用辦法。固然C中的函數和其他言語中的函數、子步驟、歷程作用相反,但是細節上略有不同。一些函數實行某些舉措,如printf()把數據打印到屏幕上;一些函數找出一個值供步驟使用,如strlen()把指定字符串的長度前往給步驟。尋常而言,函數可以同時具有以上兩種功效。
為什么要使用函數?起首,使用函數可以省去編寫反復代碼的苦差。假如步驟要多次完成某項職責,那么只需編寫一個切合的函數,就可以在必要時使用這個函數,大概在不同的步驟中使用該函數,就像很多步驟中使用putchar()一樣。其次,即使步驟只完成某項職責一次,也值得使用函數。由于函數讓步驟愈加模塊化,從而提高了步驟代碼的可讀性,更便利終期修正、完滿。比如,假定要編寫一個步驟完成以下職責:
可以使用底下的步驟:
int main(void)
{
float list[SIZE];
readlist(list, SIZE);
sort(list, SIZE);
average(list, SIZE);
bargraph(list, SIZE);
return 0;
}
固然,還要編寫4個函數readlist()、sort()、average()和bargraph()的完成細節。形貌性的函數名能清晰地表達函數的用處和構造布局。然后,單獨計劃和測試每個函數,直到函數都能正常完成職責。
假如這些函數夠通用,還可以用于其他步驟。很多步驟員喜好把函數看作是依據傳入信息(輸入)及其天生的值或呼應的舉措(輸入)來界說的“黑盒”。假如不是本人編寫函數,基本不必體貼黑盒的內里舉動。比如,使用printf()時,只需曉得給該函數傳入格式字符串或一些參數以及printf()天生的輸入,無需了解printf()的內里代碼。以這種辦法對待函數有助于把注意力會合在步驟的全體計劃,而不是函數的完成細節上。因此,在入手編寫代碼之前,仔細思索一下函數應該完成什么職責,以及函數和步驟全體的干系。
怎樣了解函數?起主要曉得怎樣準確地界說函數、怎樣調用函數和怎樣創建函數間的通訊。我們從一個簡便的步驟示例開頭,協助讀者理清這些內容,然后再具體解說。
我們的第1個目標是創建一個在一行打印40個星號的函數,并在一個打印表頭的步驟中使用該函數。如步驟清單9.1所示,該步驟由main()和starbar()構成。
/* lethead1.c */
void starbar(void); /* prototype the function */
int main(void)
{
starbar();
printf("%s\n", NAME);
printf("%s\n", ADDRESS);
printf("%s\n", PLACE);
starbar(); /* use the function */
return 0;
}
void starbar(void) /* define the function */
{
int count;
for (count = 1; count <= WIDTH; count++)
putchar('*');
putchar('n');
}
該步驟的輸入如下:
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
該步驟要注意以下幾點。
void starbar(void);
圓括號標明starbar是一個函數名。第1個void是函數典范,void典范標明函數沒有前往值。第2個void(在圓括號中)標明該函數不帶參數。分號標明這是在聲明函數,不是界說函數。也就是說,這行聲明白步驟將使用一個名為starbar()、沒有前往值、沒有參數的函數,并報告編譯器在別處查找該函數的界說。關于不識別ANSI C作風原型的編譯器,只需聲明函數的典范,如下所示:
void starbar();
注意,一些老版本的編譯器乃至連void都識別不了。假如使用這種編譯器,就要把沒有前往值的函數聲明為int典范。固然,最好照舊換一個新的編譯器。
- 尋常而言,函數原型指明白函數的前往值典范和函數承受的參數典范。這些信息稱為該函數的署名(signature)。關于starbar()函數而言,其署名是該函數沒有前往值,沒有參數。
- 步驟把starbar()原型置于main()的前方。固然,也可以放在main()內里的聲明變量處。放在哪個地點都可以。
- 在main()中,實行畢竟下的語句時調用了starbar()函數:
starbar();
這是調用void典范函數的一種情勢。當盤算機實行到starbar();語句時,會找到該函數的界說并實行此中的內容。實行完starbar()中的代碼后,盤算機前往主調函數(calling function)持續實行下一行(本例中,主調函數是main()),見圖9.1(更確切地說,編譯器把C步驟翻譯成實行以上利用的機器言語代碼)。
- 步驟中starbar()和main()的界討情勢相反。起首函數頭包含函數典范、函數名和圓括號,接著是左花括號、變量聲明、函數表達式語句,最初以右花括號完畢。注意,函數頭中的starbar()后方沒有分號,報告編譯器這是界說starbar(),而不是調用函數或聲明函數原型。
Structure of a simple function.
假如把starbar()看作是一個黑盒,那么它的舉動是打印一行星號。不必給該函數提供任何輸入,由于調用它不必要其他信息。并且,它沒有前往值,以是也不給main()提供(或前往)任何信息。簡而言之,starbar()不必要與主調函數通訊。接下去先容一個函數間必要通訊的例子。
上述步驟的輸入中,假如筆墨能居中,信頭會愈加雅觀??梢越涍^在打印筆墨之前打印一定數目標空格來完成,這和打印一定數目標星號(starbar()函數)相似,只不外如今要打印的是一定數目標空格。固然這是兩個職責,但是職責十分相似,與其分散為它們編寫一個函數,不如寫一個更通用的函數,可以在兩種情況下使用。我們計劃一個新的函數show_n_char()(體現一個字符n次)。唯一要改動的是使用內置的值來體現字符和反復的次數,show_n_char()將使用函數參數來轉達這些值。
我們來具體分析。假定可用的空間是40個字符寬。調用show_n_char('*', 40)應該恰好打印一行40個星號,就像starbar()之前做的那樣。第2行GIGATHINK, INT.的空格怎樣處理?GIGATHINK, INT.是15個字符寬,以是第1個版本中,筆墨后方有25個空格。為了讓筆墨居中,筆墨的左側應該有12個空格,右側有13個空格。因此,可以調用show_n_char(' ',12)。
show_n_char()與starbar()很相似,但是show_n_char()帶有參數。從功效上看,前者不會添加換行符,爾后者會,由于show_n_char()要把空格和文本打印成一行。步驟清單9.2是修正后的版本。為重申參數的事情原理,步驟使用了不同的參數情勢。
/* lethead2.c */
void show_n_char(char ch, int num);
int main(void)
{
int spaces;
show_n_char('*', WIDTH); /* using constants as arguments */
putchar('n');
show_n_char(SPACE, 12); /* using constants as arguments */
printf("%sn", NAME);
spaces = (WIDTH - strlen(ADDRESS)) / 2;
/* Let the program calculate */
/* how many spaces to skip */
show_n_char(SPACE, spaces);/* use a variable as argument */
printf("%sn", ADDRESS);
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
/* an expression as argument */
printf("%sn", PLACE);
show_n_char('*', WIDTH);
putchar('n');
return 0;
}
/* show_n_char() definition */
void show_n_char(char ch, int num)
{
int count;
for (count = 1; count <= num; count++)
putchar(ch);
}
該函數的運轉后果如下:
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
底下我們回憶一下怎樣編寫一個帶參數的函數,然后先容這種函數的用法。
函數界說從底下的ANSI C作風的函數頭開頭:
void show_n_char(char ch, int num)
該行見告編譯器show_n_char()使用兩個參數ch和num,ch是char典范,num是int典范。這兩個變量被稱為情勢參數(formal-argument,但是邇來的標準保舉使用formalparameter),簡稱形參。和界說在函數中變量一樣,情勢參數也是局部變量,屬該函數公有。這意味著在其他函數中使用同名變量不會惹起稱呼分歧。每次調用函數,就會給這些變量賦值。
注意,ANSI C要求在每個變量前都聲明其典范。也就是說,不克不及像平凡變量聲明那樣使用同一典范的變量列表:
void dibs(int x, y, z) /* invalid function header */
void dubs(int x, int y, int z) /* valid function header */
ANSI C也承受ANSI C之前的情勢,但是將其視為廢棄不必的情勢:
void show_n_char(ch, num)
char ch;
int num;
這里,圓括號中僅有參數名列表,而參數的典范在后方聲明。注意,平凡的局部變量在左花括號之后聲明,而外表的變量在函數左花括號之前聲明。假如變量是同一典范,這種情勢可以用逗號分開變量名列表,如下所示:
void dibs(x, y, z)
int x, y, z; /* valid */
如今的標準正漸漸鐫汰ANSI之前的情勢。讀者應對此有所了解,以便能看懂從前編寫的步驟,但是本人編寫步驟時應使用如今的標準情勢(C99和C11標準持續告誡這些過時的用法即將被鐫汰)。固然show_n_char()承受來自main()的值,但是它沒有前往值。因此,show_n_char()的典范是void。底下,我們來學習怎樣使用函數。
在使用函數之前,要用ANSI C情勢聲明函數原型:
void show_n_char(char ch, int num);
當函數承受參數時,函數原型用逗號分開的列表指明參數的數目和典范。依據一局部喜好,你也可以省略變量名:
void show_n_char(char, int);
在原型中使用變量名并沒有實踐創建變量,char僅代表了一個char典范的變量,以此類推。
再次提示,ANSI C也承受已往的聲明函數情勢,即圓括號內沒有參數列表:
void show_n_char();
這種情勢終極會從標準中剔除。即使沒有被剔除,如今函數原型的計劃也更有上風(稍后會先容)。了解這種情勢的寫法是為了今后讀得懂從前寫的代碼。
在函數調用中,實踐參數(actual argument,簡稱實參)提供了ch和num的值。思索步驟清單9.2中第1次調用show_n_char():
show_n_char(SPACE, 12);
實踐參數是空格字符和12。這兩個值被賦給show_n_char()中相應的情勢參數:變量ch和num。簡而言之,情勢參數是被調函數(called-function)中的變量,實踐參數是主調函數(calling-function)賦給被調函數的具體值。如上例所示,實踐參數可以是常量、變量,或乃至是更繁復的表達式。無論實踐參數是何種情勢都要被求值,然后該值被拷貝給被調函數相應的情勢參數。以步驟清單9.2中最初一次調用show_n_char()為例:
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
構成該函數第2個實踐參數的是一個很長的表達式,對該表達式求值為10。然后,10被賦給變量num。被調函數不曉得也不體貼傳入的數值是來自常量、變量照舊尋常表達式。再次重申,實踐參數是具體的值,該值要被賦給作為情勢參數的變量(見圖9.3)。由于被調函數使用的值是從主調函數中拷貝而來,以是無論被調函數對拷貝數據舉行什么利用,都不會影響主調函數中的原始數據。
Formal parameters and actual arguments
注意:
實踐參數和情勢參數實踐參數是顯如今函數調用圓括號中的表達式。情勢參數是函數界說的函數頭中聲明的變量。調用函數時,創建了聲明為情勢參數的變量并初始化為實踐參數的求值后果。步驟清單9.2中,'*'和WIDTH都是第1次調用show_n_char()時的實踐參數,而SPACE和11是第2次調用show_n_char()時的實踐參數。在函數界說中,ch和num都是該函數的情勢參數。
從黑盒的視角看show_n_char(),待體現的字符和體現的次數是輸入。實行后的后果是打印指定命量的字符。輸入以參數的情勢被轉達給函數。這些信息清晰地標明白如安在main()中使用該函數。并且,這也可以作為編寫該函數的計劃分析。
黑盒辦法的中心局部是:ch、num和count都是show_n_char()公有的局部變量。假如在main()中使用同名變量,那么它們互相獨立,互不影響。
也就是說,假如main()有一個count變量,那么改動它的值不會改動show_n_char()中的count,反之亦然。黑盒里產生了什么對主調函數是不偏見的。
前方先容了怎樣把信息從主調函數轉達給被調函數。反過去,函數的前往值可以把信息從被調函數傳回主調函數。為進一步分析,我們將創建一個前往兩個參數中較小值的函數。由于函數被計劃用來處理int典范的值,以是被定名為imin()。別的,還要創建一個簡便的main(),用于反省imin()對否正常事情。這種被計劃用于測試函數的步驟偶爾被稱為驅動步驟(driver),該驅動步驟調用一個函數。假如函數告捷經過了測試,就可以安裝在一個更緊張的步驟中使用。步驟清單9.3演示了這個驅動步驟和前往最小值的函數。
/* lesser.c -- finds the lesser of two evils */
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 integers (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;
}
追念一下,scanf()前往告捷讀取數據的個數,以是假如輸入不是兩個整數會招致循環停止。底下是一個運轉示例:
Enter a pair of integers (q to quit):
509 333
The lesser of 509 and 333 is 333.
Enter a pair of integers (q to quit):
-9393 6
The lesser of -9393 and 6 is -9393.
Enter a pair of integers (q to quit):
q
Bye.
緊張字return后方的表達式的值就是函數的前往值。在該例中,該函數前往的值就是變量min的值。由于min是int典范的變量,以是imin()函數的典范也是int。
變量min屬于imin()函數公有,但是return語句把min的值傳回了主調函數。底下這條語句的作用是把min的值賦給lesser:
lesser = imin(n,m);
對否寫成底下如此:
imin(n,m);
lesser = min;
不克不及。由于主調函數乃至不曉得min的存在。記取,imin()中的變量是imin()的局部變量。函數調用imin(evil1, evil2)只是把兩個變量的值拷貝了一份。
前往值不僅可以賦給變量,也可以被用作表達式的一局部。比如,可以如此:
answer = 2 * imin(z, zstar) + 25;
printf("%dn", imin(-32 + answer, LIMIT));
前往值不一定是變量的值,也可以是隨意表達式的值。比如,可以用以下的代碼簡化步驟示例:
/* minimum value function, second version */
imin(int n,int m)
{
return (n < m) ? n : m;
}
條件表達式的值是n和m中的較小者,該值要被前往給主調函數。固然這里不要求用圓括號把前往值括起來,但是假如想讓步驟條理更清晰或一致作風,可以把前往值放在圓括號內。
假如函數前往值的典范與函數聲明的典范不婚配會怎樣?
int what_if(int n)
{
double z = 100.0 / (double) n;
return z; // what happens?
}
實踐取得的前往值相當于把函數中指定的前往值賦給與函數典范相反的變量所取得的值。因此在本例中,相當于把z的值賦給int典范的變量,然后前往int典范變量的值。比如,假定有底下的函數調用:
result = what_if(64);
固然在what_if()函數中賦給z的值是1.5625,但是return語句前往int典范的值1。
使用return語句的另一個作用是,停止函數并把控制前往給主調函數的下一條語句。因此,可以如此編寫imin():
/* minimum value function, third version */
imin(int n,int m)
{
if (n < m)
return n;
else
return m;
}
很多C步驟員都以為只在函數末了使用一次return語句比力好,由于如此做更便利欣賞步驟的人了解函數的控制流。但是,在函數中使用多個return語句也沒有錯。無論怎樣,對用戶而言,這3個版本的函數用起來都一樣,由于一切的輸入和輸入都完全相反,不同的是函數內里的完成細節。底下的版本也沒成績:
/* minimum value function, fourth version */
imin(int n, int m)
{
if (n < m)
return n;
else
return m;
printf("Professor Fleppard is like totally a fopdoodle.n");
}
return語句招致printf()語句永久不會被實行。假如Fleppard傳授在本人的步驟中使用這個版本的函數,約莫永久不曉得編寫這個函數的學生對他的看法。別的,還可以如此使用return:
return;
這條語句會招致停止函數,并把控制前往給主調函數。由于return后方沒有任何表達式,以是沒有前往值,僅有在void函數中才會用到這種情勢。
聲明函數時必需聲明函數的典范。帶前往值的函數典范應該與其前往值典范相反,而沒有前往值的函數應聲明為void典范。假如沒有聲明函數的典范,舊版本的C編譯器會假定函數的典范是int。這一常規源于C的早前,當時的函數絕大大多都是int典范。但是,C99標準不再支持int典范函數的這種假定設置。
典范聲明是函數界說的一局部。要記取,函數典范指的是前往值的典范,不是函數參數的典范。比如,底下的函數頭界說了一個帶兩個int典范參數的函數,但是其前往值是double典范。
double klink(int a, int b)
要準確地使用函數,步驟在第1次使用函數之前必需曉得函數的典范。辦法之一是,把完備的函數界說放在第1次調用函數的前方。但是,這種辦法增長了步驟的閱讀難度。并且,要使用的函數約莫在C庫或其他文件中。因此,通常的做法是事先聲明函數,把函數的信息見告編譯器。
int imin(int, int);
int main(void)
{
int evil1, evil2, lesser;
第2行代碼分析imin是一個函數名,有兩個int典范的形參,且前往int典范的值。如今,編譯器在步驟中調用imin()函數時就曉得應該怎樣處理。
我們把函數的前置聲明放在主調函數外表。固然,也可以放在主調函數內里。比如,重寫lesser.c 的開頭局部:
int main(void)
{
int imin(int, int); /* imin() declaration */
int evil1, evil2, lesser;
注意在這兩種情況中,函數原型都聲明在使用函數之前。
ANSI-C標準庫中,函數被分紅多個系列,每一系列都有各自的頭文件。這些頭文件中除了其他內容,還包含了本系列一切函數的聲明。比如,stdio.h頭文件包含了標準I/O庫函數(如,printf()和scanf())的聲明。math.h頭文件包含了種種數學函數的聲明。比如,底下的聲明:
double sqrt(double);
見告編譯器sqrt()函數有一個double典范的形參,并且前往double典范的值。不要殽雜函數的聲明和界說。函數聲明見告編譯器函數的典范,而函數界說則提供實踐的代碼。在步驟中包含math.h頭文件見告編譯器:sqrt()前往double典范,但是sqrt()函數的代碼在另一個庫函數的文件中。
版權聲明:本文來自互聯網整理發布,如有侵權,聯系刪除
原文鏈接:http://www.freetextsend.comhttp://www.freetextsend.com/qingganjiaoliu/36245.html