函數 Function ●什麼是函數?函數就是一段獨立程式,用來處理獨立的工作。 這些工作可以是數值的計算、資料的輸出與輸入、繪圖的控制、字串的處理等等,只要是你想做的任何程式動作,都可用函數來設計。 ●函數的使用,牽涉到函數的定義、宣告與呼叫,傳入函數的引數與傳回的函數值。 ● 一般的C程式發展工具都提供了超過數百個常用的函數,涵蓋了數值資料及字元資料的處理、輸入與輸出的控制,記憶體空間的配置、各種系統設定的訊息、繪圖的應用等等
函數定義(Definition) 定義一個函數的語法如下: 資料型態 函數名稱 ( 引數部份 ) { 函數內部變數宣告 主體敘述 } 資料型態 函數名稱 ( 引數部份 ) { 函數內部變數宣告 主體敘述 } 例 int add1 (int num_in) int num_out; num_out=num_in+1; return num_out;
圖 2-1 C 程式的架構 #include <stdio.h> #include <math.h> 前端處理指令 #define PI 3.14159 float rv; float degree_to_radius(int); 變數及函數宣告 main (int argc, char *argv[ ] ) 主程式 { int deg; deg=45; rv=degree_to_radius(deg); printf("Degree %d equals to Radius scale %f \n",deg, rv); } float degree_to_radius(int d) 其他函數程式 float rad; rad=d*PI/180; return d;
函數的宣告 通常,我們會把主程式 main 放前面,其他的函數排在程式的後面,因為在主程式中先呼叫函數,而定義在後,所以要先宣告函數傳回值的資料型態,以便 編譯程式安排空間來儲存函數的傳回值。 float degree_to_radius(int); 函數宣告 main () { rv = degree_to_radius(deg); 函數呼叫 } float degree_to_radius(int d) 函數定義 ... }
函數宣告、呼叫、定義 int add1 (int); 宣告(declare) main() { int a=9,b; b = add1(a); 呼叫(call) } int add1 (int num_in) 定義(define) { int num_out; num_out=num_in+1; return num_out;
函數的原型宣告 (prototype) 完整宣告函數的引數和傳回值,就知道如何呼叫這個函數,這就是所謂函數的原型宣告 (prototype)。使用內建的函數時,應注意其原型宣告要 #include 哪個 .h 標頭檔,例如 #include <math.h> double sqrt (double x); 如果函數沒有宣告或敲錯函數名稱,程式編譯時,通常會得到 Function 'XXX' should have a prototype 的錯誤訊息。
引數的傳遞 呼叫函數時,應注意該函數的定義中,所指定的引數個數,各個引數的資料型態,以及引數排列的次序 , 例如 定義 int max (int a, int b) { if (a>=b) return a; else return b; } 呼叫時 int p,q; r = max(p,q); 每個引數都要個別宣告,所以 max (int a, int b) 不可寫成 max (int a,b),這是初學者常犯的錯誤
傳值呼叫 (call by value) 實際引數的 “值” 會 “拷貝” 一份給型式引數,進入函數之內運用,而實際引數本身不受函數執行的影響 square (int a) // 形式引數 a { a = a*a; return a; } main() { int x=3; y = square( x ); // 實際引數 x printf("x=%d, y=%d",x,y); 執行結果 x=3 y=9
函數的傳回值 ● 一個函數可以有一個或數個輸入引數,或是不帶任何輸入引數,但卻只有一個函數傳回值 (return value) ● C 語言有一項特殊的規定,任何未宣告資料型態的函數,其傳回值皆被預設為 int 型態 ● C++ 語言規定: 所有的函數都應該指定引數及傳回值資料型態 ,所以該加上的 #include <stdio.h> 或 #include <conio.h> 等決不可少 ●有一種特殊的資料型態稱為 void,意思是 “不指定”資料型態。如果某一個函數只是進行一些列印、繪圖、音樂等動作,不需要傳回任何值,就可以指定其傳回值為 void 型態
【範例 6-2-1】 p1/2 設計一個函數 abs,傳入一個整數值,傳回它的絕對值。設計一個簡單的程式,測試 abs 函數的效果。 6 int abs ( n ) 7 int n; 8 { 9 if (n < 0) 10 return -n; 11 else 12 return n; 13 }
【範例 6-2-1】 p2/2 15 main() 16 { 17 int num, value; 18 16 { 17 int num, value; 18 19 printf("Enter an integer : "); 20 scanf("%d",&num); 21 value = abs( num ); 22 printf("absolute value of %d is %d\n",num,value); 23 }
【範例 6-2-2】 p1/2 6 main() 7 { 8 int sum( int ); // update for C++ 設計一個函數 sum,傳入一個整數值 n,傳回 1+2+....+n 的值。並設計一個簡單的程式,測試 sum 函數的效果。 6 main() 7 { 8 int sum( int ); // update for C++ 9 int num; 10 11 printf("Enter an positive integer : "); 12 scanf("%d",&num); 13 printf("sum of 1+2+...+%d is %d\n", num, sum(num) ); 14 }
【範例 6-2-2】 p2/2 15 int sum ( n ) // this is for K&R C 16 int n; //int sum (int n)for ANSI C 17 { 18 int value=0, i; 19 20 for (i=1; i<=n; i++) 21 value += i; 22 return value; 23 }
遞迴函數 (Recursive Function) 遞迴函數是一種特殊的函數,在函數的定義時呼叫函數本身 遞迴函數的設計,必注意兩個要點: 1. 何時終止遞迴的條件。 2. 每次函數呼叫自己時輸入引數的 "降格"。
【範例 6-3-1】 設計一個階乘函數 fac,傳入一個整數 n,傳回 1*2*...*n (也就是 n!) 的值,請使用遞迴 (recursive) 的方式來設計此函數。 n! = n * (n-1)! 0! = 1 16 long fac ( int n ) 17 { 18 if (n <= 1) return 1; 19 20 return n*fac(n-1) ; 21 }
【範例 6-3-2】非遞迴 n! = n*(n-1)*(n-2)* …*1 16 long fac ( int n ) 17 { 17 { 18 long product = 1L; 19 20 while (n > 1) 21 { 22 product *= n; 23 n--; 24 } 25 return product;
【範例 6-3-3】p1/2 Fibonacci 數列是個著名的數列,由 1,1,2,3,5,8,13,..... 所構成,其法則是第 n 個數由前兩數 (第 n-1 個和第 n-2 個)相加而得。試使用遞迴的方式,設計一個函數 fib,傳入整數 n,傳回第 n 個Fibonacci數的數值。 Fn=Fn-1+Fn-2 F0=1 F1=1 16 int fib ( int n ) 17 { 18 if (n <= 1) return 1; 19 20 return fib(n-1)+fib(n-2);
【範例 6-3-3】p2/2 6 main() 7 { 8 int fib( int ); 9 int num; 10 7 { 8 int fib( int ); 9 int num; 10 11 printf("Enter a positive integer : "); 12 scanf("%d",&num); 13 printf("fib(%d) = %d\n", num, fib(num)); 14 }
變數的類別 ●所謂的變數 (variable),是指記憶體中某一個儲存資料的地方,如果儲存的是整數資料,就稱為整數變數,如果是浮點數資料,就稱為浮點數變數,依此類推。例如,宣告 int a; 說明變數 a 是一個整數變數,這是依照其資料型態 (data type) 來分,不同的資料型態會影響所佔據的記憶體大小 ●變數名稱,是程式中用來指明那個變數;變數的位址,是指變數在記憶體中被安排的地址;變數的值,是指那個記憶體位址中所儲存的資料值:變數的資料型態,必須在宣告變數時指定
全域變數與區域變數 ●區域 (local) 變數,就是指這個變數是定義在某個函數之內,它的勢力範圍 (scope) 只涵蓋這函數的區域之內。只有這個函數內的程式片段能夠使用到這個內在變數,其他的函數包括主程式在內,都不能存取到這個變數 ●全域 (global) 變數,則是指這個變數是定義在所有函數之外,並不屬於任何特定的函數。有點類似教室的公佈欄,任何學生(函數)都看得到它,可以對它進行讀寫的控制,而內在變數就像學生筆記本的資料,只有擁有者才看得到。所有函數都可以直接取到全域變數 ●函數和外在變數一樣,都具有全域的性質
【範例 6-4-1】 p1/2 6 int ball; /* global ball */ 7 int i; /* global i */ 9 main() 10 { 11 int k; 12 13 ball = 0; 14 k = ++ball; 15 printf("main() : ball=%d\n",ball); 16 printf("main() : k=%d\n",k); 17 add1(); 18 doubleball(); 19 }
【範例 6-4-1】 p2/2 21 add1() 22 { 23 int i=0; /* local i */ 24 25 ball++; 22 { 23 int i=0; /* local i */ 24 25 ball++; 26 i++; 27 printf("add1() : ball=%d\n",ball); 28 printf("add1() : i=%d\n",i); 29 } 31 doubleball() 32 { 33 int ball=99; /* local ball */ 34 int k=ball+1; 35 36 ball *= 2; 37 k++; 38 printf("doubleball() : ball=%d\n",ball); 39 printf("doubleball() : k=%d\n",k); 40 }
【範例 6-4-1】執行結果 main() : ball=1 main() : k=1 add1() : ball=2 add1() : i=1 doubleball() : ball=198 doubleball() : k=101
自動變數與靜態變數 ●宣告 auto (自動變數) 是指那個變數所佔據的記憶體位置,會在函數執行時自動被安排出來,結束執行時自動被收回 ●宣告為 static (靜態變數) 的變數,是指那個變數的值是靜態不動的,變數的內容值會一直保存,每次執行函數時,上次的變數值仍然存在 ●如果將 static 宣告用於某函數內的區域變數,那麼,雖然這個變數的值會一直保存,但是不會被其他函數看到。 因此 static 變數比 auto 變數,具有較長的生命期 (life time ) ●如果區域變數不指定 auto 或 static,就被認為是自動變數 ,所以 auto 通常是被省略的
static 宣告 ●將 static 宣告用於某函數內的區域變數,那麼,雖然這個變數的值會一直保存,但是不會被其他函數看到
register 宣告與 extern 宣告 ●register 宣告用於通知編譯程式,說明這個變數會使用頻繁,儘可能把這些 register 變數放到 CPU 的暫存器中,以加快程式的執行,但是編譯程式卻不一定會接受這一點要求 ●extern 宣告是說明某個函數內所使用特定的變數或函數,是定義這個函數之外的,對全域變數而言,加上 extern 宣告,通常是指這個檔案中使用到這個全域變數或函數,而這個全域變數或函數,是定義在其他檔案之中 。否則,程式連結 (link) 時,就會被系統察覺出來,發出錯誤訊息 (undefined)
全域變數的 extern 宣告 ●全域變數的宣告 (declaration) 與定義 (definition) 之間的區別是相當重要的,宣告說明了變數的型別,而定義不但含有宣告的意味,更會為這個變數實際上安排所應佔的記憶體位置,來儲存其資料。任何全域變數,可能在數個檔案中用 extern 宣告,但只能有一個定義,否則就會出現 redefine 的錯誤訊息
巨集(macro) 巨集 (macro) 是程式中的常數、字串、或是一段程式,為了能方便地重覆使用,或是讓程式更能望文生義,所採取的指令代換寫法。巨集定義的格式如下: #define 名稱 代換內容 意思是請編譯程式在進行編譯前,先將程式所有出現這個名稱的部份,都用 “代換內容” 來取代 例如: #define PI 3.1416 這個巨集指令是指示編譯程式將目前所編譯的原始程式中,所有出現 PI的地方,全部換成 3.1416。如此,原始程式就不必直接寫出數值 3.1416,而用符號名稱 PI 來撰寫,這樣較能清楚地表現程式所使用"圓周率的"的意義
函數與巨集 巨集指令也可以帶參數,例如 #define abs(x) ((x)>=0)﹖(x):-(x) 是將程式中的 巨集指令也可以帶參數,例如 #define abs(x) ((x)>=0)﹖(x):-(x) 是將程式中的 a=abs(p+q); 自動代換成 a=((p+q)>=0)﹖(p+q):-(p+q); 程式片段的巨集定義看起來和函數很像,但是他們是不一樣的。函數呼叫時,是跳到那個函數程式所在位址去執行,原始程式只編譯成一個函數呼叫的機器指令。而巨集的呼叫,是將那個名稱及參數,"展開" 成所定義的程式片段,安插在所呼叫的地方,所以如果呼叫多次程式會變得較長,所產生的可執行檔會較大。但是,因為省略了函數呼叫及返回時的處理動作,所以執行速度較快
常用的 ANSI C函數 ●使用系統提供的函數時,應注意函數原型所引用的標頭檔 (.h),所傳入的引數個數與資料型態,以及函數傳回值的資料型態。例如,使用求正弦(sine)值的函數,其用法如下: #include <math.h> double sin(double x); ●ANSI C 所包括的函數名稱和用法,可以在網際網路上很容易搜尋到,例如: 在 Google 搜尋ANSI C function可以找到 http://cermics.enpc.fr/~ts/C/FUNCTIONS/function.ref.html