C語言學習之函數知識總結
前言
函數是C語言的基本單位,學好函數有利於程序的模塊化以及避免寫出重復的代碼從而減少代碼量,並且可以提高程序的復用性與可讀性。
一、引入
引例:定義6個整型變量a,b,c,d,e,f,並對它們任意賦值。分別輸出a,b的最大值,c,d的最大值和e,f的最大值
#include <stdio.h> int main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; if (a > b) printf("%d\n", a); else if (a < b) printf("%d\n", b); if (c > d) printf("%d\n", c); else if (c < d) printf("%d\n", d); if (e > f) printf("%d\n", e); else if (e < f) printf("%d\n", f); return 0; }
通過觀察可以發現,代碼中有大量重復的部分,如果我們定義一萬個變量,並兩兩比較求出其中的最大值,那麼if…else if…else語句就要寫一萬次,這顯然非常累贅。在編程過程中,經常會發現,雖然數據不一樣,但是對這些數據的操作卻是一樣的,例如,引例中求a,b中的最大值與求c,d的最大值所做的操作是一樣的,唯獨隻有被操作的數據不同而已。所以如果程序中有大量重復的作操,但隻是針對的數據不一樣時,我們可以通過定義函數來解決代碼重復的問題。上述引例可以通過如下代碼實現對應的功能:
#include <stdio.h> void get_max(int i, int j) // 自定義的求最大值的函數 { if (i > j) printf("%d\n", i); else if (i < j) printf("%d\n", j); else printf("Equal!\n"); } int main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; get_max(a, b); get_max(c, d); get_max(e, f); return 0; }
二、認識函數
void表示該函數沒有返回值,get_max是該函數的名字,函數名後括號中的變量i和j被稱為該函數的形式參數(簡稱形參)。
void get_max(int i, int j) { if (i > j) printf("%d\n", i); else if (i < j) printf("%d\n", j); }
所有的語言程序的入口都是main函數,從main函數進入則開始順序逐行執行main函數中的代碼。下面代碼塊中定義完變量並完成賦值後,程序執行到get_max(a, b);,此時程序便會從main函數的上方查找一個名叫get_max函數並將括號中的變量a和b的值分別傳輸到get_max函數名後括號中的形參i和j中,然後程序會跳轉到get_max函數的內部執行,待get_max函數執行完畢後,再跳轉回main函數繼續執行main函數中的下一條語句。
int main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; get_max(a, b); get_max(c, d); get_max(e, f); return 0; }
三、函數的作用
函數是一種工具,它是能夠完成特定功能的獨立代碼塊,它不是為某個特定的問題設計的,而是為解決大量同類型的問題設計的;雖然函數處理的數據是不同的,但是對這些數據的操作是相同的;函數的使用可以避免寫大量重復的代碼,減少程序員的編碼量;同時函數的存在有利於整個程序的模塊化,函數可以將復雜的問題剖解為一個個簡單的問題;函數可以被視為一個“黑匣子”,我們隻需要知道某個函數的用法即可使用該函數,但我們並不一定知道該函數是怎麼實現的,例如,我們雖然經常使用的printf函數,但我們並不知道其中到底是如何實現輸出功能的。同樣的,當我們編寫函數的時候,也可以把具體實現的過程隱藏起來,因為這可能是商業機密。
四、函數的返回值
函數不僅可以接收數據,並對接收到的數據進行處理,而且也可以將數據處理的結果返回,例如下述程序:
#include <stdio.h> int f(void) // int表示函數的返回值是int類型的值;括號中的void表示該函數不能接收任何數據 { return 10; // return語句表示向主調函數返回一個值 } int main() { int i = 88; i = f(); // 調用f函數後,f函數就會返回10這個數據,所以這行代碼就相當於把10賦值給變量i printf("%d\n", i); return 0; }
思考:下述程序是否正確?
#include <stdio.h> void g(void) // 函數名前的void表示該函數沒有返回值 { } void h(void) { return 10; // 編譯時如果沒有調用該函數,則不會報錯,一旦調用便會報錯,因為h函數的返回值為空 } int main() { int i = 88; i = g(); // 由於g函數沒有返回值,所以不能將其賦值給變量i,編譯時會報錯 printf("%d\n", i); return 0; }
五、定義函數
六、函數的類型
函數返回值的類型也稱為函數的類型,如果函數名前的返回值類型與函數執行體中的return表達式的返回的類型不同,則最終函數返回值以函數名前的返回值類型為準。
思考:下述程序輸出的結果是多少?
#include <stdio.h> int f() // 如果函數名後的括號裡是空的,等同於在括號裡寫void { return 10.5; } int main() { double x = 6.6; x = f(); printf("%lf\n", x); return 0; }
通過程序可得,變量x輸出的值為10.000000,這就說明,函數最終的返回值與函數名前的返回值類型相同,而不是以return表達式為準。所以,函數的類型取決於函數名前的返回值的類型。
七、return語句與break語句的區別
break語句的作用是終止當前循環和switch語句,而return語句與break語句有本質性的差別,return語句的作用是終止當前正在執行的函數。當被調函數的返回值為空時,則直接終止被調函數然後跳回主調函數繼續順序執行主調函數中的代碼;如果被調函數的返回值不為空時,則先將返回值返回給主調函數,然後再終止被調函數並跳回主調函數繼續順序執行主調函數中的代碼。
思考:以下兩個程序的輸出結果是否相同?如果不同,這兩個程序的輸出結果分別是什麼?
#incldude <stdio.h> void f() { for (i = 0; i < 5; i++) { printf("AAAA\n"); break; } printf("BBBB\n"); } int main() { f(); return 0; }
#incldude <stdio.h> void f() { for (i = 0; i < 5; i++) { printf("AAAA\n"); return; } printf("BBBB\n"); } int main() { f(); return 0; }
八、函數的分類
根據是否有形參可以將函數分為,有參函數和無參函數;根據是否有返回值可以將函數分為有返回值函數和無返回值函數;同時,也可以將函數分為庫函數和自定義函數,例如,printf函數就屬於庫函數,因為是系統提供給我們的,而這篇文章中的get_max函數,f函數,g函數等均屬於自定義函數。
九、主函數
不管一個程序中有多少個函數,但主函數(也就是main函數)隻能有一個,並且main函數是整個程序的入口,也是整個程序的出口,主函數可以調用任何其他函數,其他函數之間也可以互相調用,但是其他函數不能調用主函數。
十、函數使用舉例
定義6個整型變量a,b,c,d,e,f,並對它們任意賦值。分別輸出a,b的最大值,c,d的最大值和e,f的最大值
要求:自定義函數並且不能與引例中的函數執行體相同
#include <stdio.h> int get_max(int i, int j) { if (i > j) return i; else if (i < j) return j; } int main() { int a, b, c, d, e, f; a = 5, b = 3, c = 6, d = 87, e = 12, f = 49; printf("%d\n", get_max(a, b)); printf("%d\n", get_max(c, d)); printf("%d\n", get_max(e, f)); return 0; }
輸入三個int類型的數字,並判斷這三個數字是否是素數。如果是素數輸出Yes,如果不是輸出No
要求:使用自定義函數實現
提示:素數(又稱質數)是大於1的自然數並且素數隻能被1和它本身整除
#include <stdio.h> int is_prime(int val) { int i; for (i = 2; i < val; i++) if (val % i == 0) break; if (i == val) return 1; else return 0; } int main() { int val1, val2, val3, i; scanf("%d%d%d", &val1, &val2, &val3); if ( is_prime(val1) ) printf("Yes!\n"); else printf("No!\n"); if ( is_prime(val2) ) printf("Yes!\n"); else printf("No!\n"); if ( is_prime(val3) ) printf("Yes!\n"); else printf("No!\n"); return 0; }
十一、函數的聲明
觀察可以發現,文章中所有自定義的函數都放在瞭main函數之上,那我們如果將自定義的函數放在main函數的下面會出現什麼情況呢?如果我們在main函數中調用瞭自定義函數,並且將自定義的函數放在瞭main函數的下方,此時整個程序就會編譯報錯,這是因為main函數執行到調用自定義函數的語句時,它隻會向上查找對應的函數,而如果在它上面沒有對應的函數,程序就會出錯。
思考1:下面兩個程序是否可以運行?
#include <stdio.h> int main() { f(); return 0; } void f() { printf("AAAA\n"); }
那如果我們要把被調函數放在主調函數之後,我們應該怎麼辦呢?這需要在主調函數的上方加上函數聲明即可,如下程序所示:
#include <stdio.h> void f(); // 這是函數聲明,後面的分號不可以省略 int main() { f(); return 0; } void f() { printf("AAAA\n"); }
思考2:下面的程序是否可以運行?如果有錯誤應該如何改正?
#include <stdio.h> void g(void) { f(); } void f(void) { printf("AAAA\n"); } int main() { g(); return 0; }
十二、函數的形參與實參
形參是指定義函數時括號中定義的變量,而實參是指在調用函數時向被調函數傳輸的具體的數據或變量。如下代碼所示,變量i就是形參,而主函數中調用f函數時在後面的括號中寫的5就是實參。需要註意的是,形參和實參的個數必須是對應的,數據類型也必須相互兼容。
#include <stdio.h> void f(int i) { printf("%d\n", i); } int main() { f(5); return 0; }
十三、合理設計函數
在掌握瞭以上知識之後,我們該如何設計函數讓整個程序更像是開發軟件?答案很簡單,如果僅需要使用一次某個功能,則不需要將該功能設計成一個函數,但如果需要多次使用該功能,則設計一個該功能對應的函數是不二選擇。這段話通過以下三個程序體會:
示例:輸入一個int類型的數字n,求1到n之間(包括n)所有的素數並輸出。不考慮輸入的值小於等於1的情況
解法1:不定義其它函數,僅在main函數中實現
#include <stdio.h> int main() { int n; int i, j; scanf("%d", &n); for (i = 2; i <= n; i++) { for (j = 2; j < i; j++) if (i % j == 0) break; if (j == i) printf("%d\n", i); } return 0; }
這樣寫程序雖然實現瞭功能,但代碼的重用性非常低,比如,我們要分別判斷100個不同數字從1到它本身之間的所有素數,則需要將下面的代碼塊重復寫100次,這樣就會導致代碼大部分是重復的,而且代碼量會顯得非常大。
for (i = 2; i <= n; i++) { for (j = 2; j < i; j++) if (i % j == 0) break; if (j == i) printf("%d\n", i); }
考慮到要將1到n之間所有的數字都進行判斷素數這一操作,所以可以單獨設計出一個函數實現判斷素數這個功能,從而解決代碼重復的問題。
解法2:自定義一個函數實現
#include <stdio.h> int is_prime(int val) { int i; for (i = 2; i < val; i++) if (val % i == 0) break; if (i == val) return 1; else return 0; } int main() { int n; int i; scanf("%d", &n); for (i = 2; i <= n; i++) if ( is_prime(i) ) printf("%d\n", i); return 0; }
相比較第一個程序,該程序更容易讓人理解,代碼的可重用性也得以提高,並且多次判斷時所需要寫的代碼也相對較少,但存在的問題與第一個程序相同,如果要分別判斷100個不同數字從1到它本身之間的所有素數,則需要將下面的代碼塊重復寫100次。
for (i = 2; i <= n; i++) if ( is_prime(i) ) printf("%d\n", i);
又考慮到判斷完是否是素數後,輸出也是重復性操作,所以同樣可以將輸出這一操作通過設計函數來實現。
解法3:自定義兩個函數實現
#include <stdio.h> // 本函數的功能是判斷val是否是素數 int is_prime(int val) { int i; for (i = 2; i < val; i++) if (val % i == 0) break; if (i == val) return 1; else return 0; } // 本函數的功能是將1到n之間(包括n)所有的素數輸出 void traverse(int n) { int i; for (i = 2; i <= n; i++) if ( is_prime(i) ) printf("%d\n", i); } int main() { int n; int i; scanf("%d", &n); traverse(n); return 0; }
第三個程序相較前兩個程序而言,代碼量更少,且可重用性更高,如果需要判斷多個數字並輸出的話,隻需要多次調用traverse函數即可實現功能,而且整個程序更容易讓人理解。
總結:一個函數的功能盡可能獨立單一,不要將多個功能放在一個函數中實現,這樣不僅可以避免代碼的重復而且還可以增加整個程序的可重用性與可讀性。
十四、變量的作用域
變量按作用域劃分可以分為全局變量和局部變量。
全局變量
在所有函數外部定義的變量被稱為全局變量。如下程序所示,變量k就是一個全局變量,需要註意的是,全局變量隻能在定義全局變量的位置之後的函數中使用,在全局變量之上的函數不能使用。
#include <stdio.h> int k = 10; void f() { printf("%d\n", k); } int main() { f(); return 0; }
局部變量
在函數內部定義的變量,以及函數的形參都屬於局部變量。如下程序所示,f函數中的形參i以及f函數中定義的變量j都是f函數的局部變量,main函數中定義的變量i也是main函數的局部變量。除此之外,初學時可能會有這樣的困惑:f函數中定義瞭i變量,main函數中也定義瞭i變量,這樣程序是否存在問題?答案是不存在問題的,因為main函數中變量i隻在main函數中發揮作用,而f函數中的變量i隻在f函數中發揮作用,所以它們並不會起沖突,也就意味著,所有的局部變量隻能在函數的內部使用,這也正是局部變量的含義。
#include <stdio.h> void f(int i) { int j = 20; } int main() { int i = 10; return 0; }
全局變量名與局部變量名沖突的問題
#include <stdio.h> int i = 99; void f(int i) { printf("i = %d\n", i); } int main() { f(8); return 0; }
推測1:程序中有錯誤,因為全局變量名與局部變量名一樣,無法分辨f函數中到底是全局變量i還是局部變量i
推測2:程序無誤,如果輸出結果為99,則此時的變量i是全局變量;如果輸出結果為8,則此時的變量i是局部變量
結果:輸出結果為8
結論:如果全局變量名與局部變量名相同,則局部變量會屏蔽全局變量的存在
十五、函數內存的分配
在使用函數之前,操作系統會給函數中所有的變量分配內存空間,但當函數被執行完畢後,其中所有變量的內存均會被釋放掉,等到再次使用時,會重新給函數中的變量分配內存空間。大傢可以思考一下這樣做的合理性。
以上就是C語言學習之函數知識總結的詳細內容,更多關於C語言 函數 的資料請關註WalkonNet其它相關文章!