模塊化與函數嵌套
計算機的最終走向是模擬人工智能和社會,人類在完成復雜任務都采用分工合作的方式,在計算機內部也可以通過函數來划分各程序的功能來完成一個復雜任務。
main函數就相當於程序里的皇帝,必須要有,並且只有一個。它指揮所有的大臣(子函數)協調工作,大臣又可以調用更底層的子函數,相當於指揮小兵再進行更具體的工作,這就叫函數嵌套:
程序1
函數的嵌套調用
// 21-1簡單函數嵌套.c // #include <stdio.h> a() { printf("a函數開始\n"); b(); printf("a函數結束\n"); } b() { printf("b函數開始\n"); } main() { printf("main函數運行開始\n"); a(); printf("main函數運行結束\n"); }
程序2
打印100-200間所有的素數
// 21-2函數求素數.c #include <stdio.h> //main() //{ // int i, j; //第一層循環變量 第二層循環變量 // int flag; //標志 1為素數 // for ( i = 100; i <= 200; i++) //求素數的范圍 100 - 200 開始 // { // flag = 1; //當做一個標志使用 // for ( j = 2; j < i; j++) // { // if (i%j == 0) //素數的判定方式是能整除就不是素數 // { // flag = 0; // break; // } // } // if (flag == 1)printf("%d\t",i); // } //} //使用函數 int fun(int i) { int j, flag = 1; for (j = 2; j < i; j++) { if (i%j == 0) { flag = 0; break; } } return flag; } main() { int i; for (i = 100; i <= 200; i++) if (fun(i) == 1) printf("%d\t",i); }
再論數據傳遞
int fun(int n)
{
int m;
m=n+2;
return m;
}
main()
{
int i=3,j;
j=fun(2)*fun(i)+6;
}
第一次調用:
定義:fun(int n); /*形參*/
調用:fun(2); /*實參*/
這個實參到形參的傳遞過程可以分解為
int n=2;
第二次調用
定義:fun(int n); /*形參*/
調用:fun(i); /*實參*/
這個實參到形參的傳遞過程可以分解為
int n=i;
返回值沒有形參和實參的說法,返回細節也比參數傳遞復雜,在這里不作詳解。如果在表達式中出現函數調用,直接用它的返回值代替函數即可,如:
j=fun(2)*fun(i)+6;
兩次返回值傳遞后得:
j=4*5+6;
程序3
求3個數中的最大數
// 21-3函數求三個數最大值.c #include <stdio.h> double max(double a,double b) { return a > b ? a : b; } main() { double a, b, c; printf("請輸入三個數:\n"); scanf_s("%lf%lf%lf", &a, &b, &c); printf("%lf\n", (max(a, (max(a, b))))); }
按數據類型的精度來排序,順序如下(不考慮unsigned):
char、short、int、long、float、double、long double,如果形式參數比實際參數精度高,系統會自動轉換成正確的類型,比如整數5會被轉換成浮點的5.0。反過來如果形式參數精度比實際參數低,系統也不會報錯,但是所得到的數值無法預料。
比如:
void fun(int a){…}
下面的代碼:
short s;
fun(s);
沒有問題,但是:
double d;
fun(d);
編譯可以通過,程序在執行卻會有無法預料的后果。
遞歸函數
函數還可以自己調用自己,這樣的函數稱作“遞歸函數”。
程序4
用遞歸函數求10!
// 21-4遞歸函數.c #include <stdio.h> int fun(int n) { int t = 1; if (n > 0) t = n * fun(n - 1); //遞歸調用 return t; } main() { printf("%d\n", fun(10)); }
※注意:遞歸函數必須要設條件返回,否則它會無限遞歸下去,直至程序崩潰。
函數的設計原則
函數在設計時應該遵循兩種原則:一種是自頂向下,也就是在主函數中先設計功能,再設計子函數,如一個煮飯程序:
main()
{
洗米();
下鍋();
打開電飯煲();
}
將中文翻譯成英文后,再在main函數頂部分別再設計各子函數的細節:
void 洗米()
{
……
}
另一種是由下而上:許多通用函數設計出來時根本就不知道哪個父函數誰會調用它,比如我們看到的sqrt、printf和各種庫函數,程序員將它設計好后把函數的接口(聲明部分)交給調用者,調用者不需要知道里面的程序細節,只需按照它的接口調用即可達到目的。
有些函數設計出來,有可能永遠都不會被調用。這就好像if語句的分支一樣,有些分支因為數據值沒有滿足條件,就永遠都不會執行那個模塊。這也是對程序設計資源的一種浪廢。