1 / 62

第6章 函數與巨集

第6章 函數與巨集. 6-1 由上而下的設計方法 6-2 建立函數 6-3 函數的參數呼叫方式 6-4 變數的有效範圍 6-5 遞迴函數 6-6 C 語言的巨集 6-7 C 語言的標準函式庫. 6-1 由上而下的設計方法-基礎1. 模組化主要是針對解決問題的方法,把一件大型的工作切割成無數的小工作,切割的工作屬於一種結構化分析的範疇,我們最常使用的是「由上而下的設計方法」( Top-down Design), 其主要是使用程序為單位來切割工作,也就是所謂的「程序式程式設計」( Procedural Design)。. 6-1 由上而下的設計方法-基礎2.

milek
Download Presentation

第6章 函數與巨集

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第6章 函數與巨集 • 6-1 由上而下的設計方法 • 6-2 建立函數 • 6-3 函數的參數呼叫方式 • 6-4 變數的有效範圍 • 6-5 遞迴函數 • 6-6 C語言的巨集 • 6-7 C語言的標準函式庫

  2. 6-1 由上而下的設計方法-基礎1 • 模組化主要是針對解決問題的方法,把一件大型的工作切割成無數的小工作,切割的工作屬於一種結構化分析的範疇,我們最常使用的是「由上而下的設計方法」(Top-down Design),其主要是使用程序為單位來切割工作,也就是所謂的「程序式程式設計」(Procedural Design)。

  3. 6-1 由上而下的設計方法-基礎2 • 由上而下的設計方法是在面對問題時,先考慮將整個解決問題的方法分解成數個大「模組」(Modules),然後針對每一個大模組,一一分割成數個小模組,如此一直細分,最後等這些細分小問題的小模組完成後,再將它們組合起來,如此一層層的向上爬,完成整個軟體系統或應用程式的設計。

  4. 6-1 由上而下的設計方法-注意事項 • 獨立性:每一個分割模組間的關聯性愈少,處理起來就會愈快。所謂獨立性,是指當處理某一個子問題時,無需考慮到其它子問題。換一句話說,獨立性是要將每一個問題都定義成一件簡單且明確的問題。 • 結合問題:小心的控制子問題間的結合方法,而且要注意結合這些子問題的邏輯順序,避免語焉不詳的結果。 • 子問題間的溝通:雖然獨立性可以減少各問題間的關聯性,但是並無法避免掉全部的溝通。

  5. 6-1 由上而下的設計方法-實例 • 例如:目前有一個工作是繪出房屋的圖形,如下圖所示:

  6. 6-1 由上而下的設計方法-第一步 • 從房屋繪圖工作可以粗分為三個小工作,如下所示: • 繪出屋頂和外框。 • 繪出窗戶。 • 繪出門。

  7. 6-1 由上而下的設計方法-第二步 • 接著將第一個小工作【繪出屋頂和外框】(Draw Outline)再次進行分割成二個小工作,如下所示: • 繪出屋頂。 • 繪出房屋的外框。

  8. 6-1 由上而下的設計方法-繼續步驟 • 只需重複上述分析,持續一步一步的向下分割工作,例如:因為窗戶共有2個,所以【繪出窗戶】可以分為【繪出窗戶1】和【繪出窗戶2】,而【繪出門】可以分為【繪出門框】和【繪出門把】。 • 最後,在將問題分割成一個個小問題後,每一個小問題就是一個C語言的函數,只需完成這些函數即可解決整個繪出房屋的問題。

  9. 6-2 建立函數 • 6-2-1 函數是一個黑盒子 • 6-2-2 建立C語言的函數 • 6-2-3 函數的原型宣告 • 6-2-4 函數的參數 • 6-2-5 函數的傳回值

  10. 6-2 建立函數 • C語言的模組單位是「函數」(Functions),函數是一個獨立的程式單元,使用函數可以將大工作分割成一個個小型的工作,也可以重複使用以前已經建立的函數或直接呼叫C語言標準函式庫的函數。

  11. 6-2-1 函數是一個黑盒子-說明 • 在C語言的程式敘述執行函數稱為「函數呼叫」(Functions Call),事實上,程式設計者並不需要了解函數內部實際的程式碼,也不想知道其細節,函數如同一個「黑盒子」(Black Box),只要告訴程式設計者如何使用這個黑盒子的「使用介面」(Interface)即可。

  12. 6-2-1 函數是一個黑盒子-圖例 • 圖例可以看出呼叫函數只需知道需要傳入的參數,然後從函數取得什麼傳回值,這就是函數和外部溝通的使用介面,實際函數內容的程式碼是隱藏在使用介面後,函數實際內容的程式碼撰寫稱為「實作」(Implementations)。

  13. 6-2-1 函數是一個黑盒子-規則 • 函數的使用介面需要直接、良好定義和容易了解。 • 在使用函數時,並不需要知道任何有關內部實作的問題,唯一需要知道的是如何使用它的使用介面。 • 在實作程序時,並不用考量或知道到底是誰需要使用此函數,只需滿足使用介面定義的輸入參數和傳回值即可。

  14. 6-2-1 函數是一個黑盒子-語法與語意 • 函數的「語法」(Syntactic)是說明函數需要傳入何種資料型態的「參數」(Parameters)和傳回值。 • 「語意」(Semantic)是指出函數可以作什麼事? • 撰寫函數時,需要了解函數的語法規則,而呼叫函數時需要了解其語意規則,如此才可以正確的呼叫函數。

  15. 6-2-2 建立C語言的函數-語法 • C語言的函數是由函數名稱和程式區塊所組成,其語法格式如下所示: 傳回值型態 函數名稱( 參數列 ) { 程式敘述; …… return 傳回值; } • 傳回值型態是函數傳回值的資料型態,函數名稱如同變數命名方式由設計者自行命名,函數使用return關鍵字傳回函數值。

  16. 6-2-2 建立C語言的函數-範例 • 一個沒有參數列和傳回值的函數,如下所示: void writeString() { printf("歡迎使用C/C++!\n"); } • 在括號內定義傳入的參數列,不過這個函數並沒有任何參數,所以空白,也可以使用void,如下所示: void writeString(void) { }

  17. 6-2-2 建立C語言的函數-呼叫 • 在C語言的程式碼呼叫函數需要使用函數名稱,其語法格式如下所示: 函數名稱( 參數列 ); • 因為前述函數writeString()沒有傳回值和參數列,所以呼叫函數只需使用函數名稱,如下所示: writeString();

  18. 6-2-2 建立C語言的函數-呼叫過程

  19. 6-2-3 函數的原型宣告-語法 • ANSI-C語言的函數分為「宣告」(Declaration)和「定義」(Definition)兩個部分,範例Ch6-2-2.c的函數程式區塊是實際的函數定義,程式並沒有宣告函數,這是因為呼叫函數的程式碼位在定義之後,所以並不需要先行宣告。 • 如果呼叫函數的程式碼是在函數定義之前,就需要在程式開頭宣告函數的原型,其語法格式如下所示: 傳回值型態 函數名稱( 參數列 );

  20. 6-2-3 函數的原型宣告-實例 • 函數原型宣告是程式碼,在最後需加上「;」分號,如下所示: void writeString(void); void one2Five(); • 程式碼是writeString()和one2Five()函數的原型宣告,因為沒有參數,可以使用空白或void表示沒有參數。 • 擁有參數的函數原型宣告,如下所示: void printTriangle(char, int); void one2N(int);

  21. 6-2-4 函數的參數-說明 • 函數的參數列是一個資訊傳遞的機制,可以從外面將資訊送入函數的黑盒子,參數列是函數的使用介面。函數如果擁有參數列,在呼叫時,因為傳入不同的參數值,就可以產生不同的執行結果。

  22. 6-2-4 函數的參數-範例 • 一個擁有參數列的函數範例,如下所示: void printTriangle(char ch, int rows) { /* 變數宣告 */ int i, j; /* 巢狀迴圈列印三角形 */ for ( i = 1; i <= rows; i++ ) { for ( j = 1; j <= i; j++ ) printf("%c", ch); printf("\n"); } }

  23. 6-2-4 函數的參數-正式參數 • printTriangle()函數定義的參數稱為「正式參數」(Formal Parameters)或「假的參數」(Dummy Parameters)。 • 正式參數是識別字,其角色如同變數,需要指定資料型態,並且可以在函數的程式碼區塊中使用,如果參數不只一個請使用「,」符號分隔。

  24. 6-2-4 函數的參數-呼叫參數的函數與實際參數 • 函數擁有參數列,在呼叫時需要加上參數列(或稱為引數),如下所示: printTriangle('*', rows); • 上述呼叫函數的參數稱為「實際參數」(Actual Parameters),參數可以是常數值,例如:'*'、變數或運算式,例如:rows,其運算結果的值需要和正式參數定義的資料型態相同(編譯程式會強迫型態轉換成相同的型態),函數的每一個正式參數都需要對應一個同型態的實際參數。

  25. 6-2-5 函數的傳回值-語法 • C語言函數的傳回值型態不是void,而是指定的資料型態int或char等,就表示這個函數擁有傳回值。 • 因為函數在執行完程式區塊後,需要傳回值,傳回指令的語法格式如下所示: return 運算式;

  26. 6-2-5 函數的傳回值-範例 • 擁有傳回值的函數範例,如下所示: int n2N(int start, int end) { /* 變數宣告 */ int i; int total = 0; /* 迴圈敘述 */ for ( i = start; i <= end; i++ ) total += i; return total; }

  27. 6-2-5 函數的傳回值-呼叫 • 函數擁有傳回值,在呼叫時可以使用指定敘述取得傳回值,如下所示: total = n2N(start, end); • 程式碼的變數total可以取得函數的傳回值,變數total的資料型態與函數傳回值型態是相同的。

  28. 6-3 函數的參數呼叫方式 • 6-3-1 傳值的參數呼叫 • 6-3-2 傳址的參數呼叫

  29. 6-3 函數的參數呼叫方式

  30. 6-3-1 傳值的參數呼叫 • C語言傳值的參數呼叫只是將複製的參數值傳到函數,所以在函數存取參數值並不是原來傳入的變數,當然也就不會更改呼叫的變數值。 void swap(int x, int y) { int temp; temp = x; x = y; y = temp; }

  31. 6-3-2 傳址的參數呼叫 • C語言的傳址呼叫就是傳遞指標變數,指標變數是一個指向其它變數位址的變數,它是一個位址值,在參數的變數名稱前只需使用「*」號標示是指標變數,傳遞進函數的是參數的變數位址,而不是變數值,如下所示: void swap(int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; }

  32. 6-4 變數的有效範圍 • 6-4-1 區域與全域變數 • 6-4-2 靜態變數 • 6-4-3 暫存器變數

  33. 6-4 變數的有效範圍 • C語言名稱的「有效範圍」(Scope)是指該名稱(通常是指變數)在程式中可以存取的的程式碼區域。 • 例如:在函數中宣告的變數或參數都只可以在函數的程式區塊中存取,不同函數的同名變數是毫不相干的不同變數。

  34. 6-4-1 區域與全域變數 • C語言的變數範圍將影響變數值的存取,C語言的變數範圍,如下所示: • 區塊範圍(Block Variable Scope):在程式區塊宣告的變數,變數只能在區塊內使用,在區塊之外的程式碼並不能存取此變數。 • 區域變數範圍(Local Variable Scope):在函數內宣告的變數或參數,變數只能在宣告的程式區塊使用,在函數外的程式碼並無法存取此變數。 • 全域變數範圍(Global Variable Scope):如果是在函數外宣告的變數,在整個程式檔案都可以存取此變數,如果全域變數沒有指定初值,其預設值是0。

  35. 6-4-2 靜態變數 • 如果在函數的程式區塊宣告靜態變數,不同於其它區域變數,在離開函數時會消失,靜態變數會配置固定的儲存位置,在重複呼叫函數時,靜態變數值都會保留。 • 在函數將區域變數宣告成靜態變數,只需在變數前加上static關鍵字,如下所示: static int step = 0;

  36. 6-4-3 暫存器變數-說明 • C語言的暫存器變數是針對那些存取十分頻繁的變數,可以直接將變數置於CPU的暫存器,以便加速程式的執行,通常是使用在迴圈的計數器變數。只需在宣告變數前加上register關鍵字,就可以宣告暫存器變數,如下所示: register int i;

  37. 6-4-3 暫存器變數-限制 • 暫存器變數在使用上有一些限制,如下所示: • 暫存器變數只可以使用在區域變數或函數的參數。 • 暫存器變數允許使用的個數需視CPU的電腦硬體而定,而且只有少數變數可以宣告成暫存器變數。 • 編譯程式對於暫存器變數並不一定處理,不過就算我們將變數宣告成register也無所謂,編譯程式會自行決定是否處理。 • 暫存器變數並不能使用「&」取址運算子取得變數的位址。

  38. 6-5 遞迴函數 • 6-5-1 遞迴的基礎 • 6-5-2 遞迴的階層函數 • 6-5-3 河內塔問題

  39. 6-5-1 遞迴的基礎 • 遞迴的觀念主要是在建立遞迴函數,其基本定義,如下所示: 一個問題的內涵是由本身所定義的話,稱之為遞迴。 • 遞迴函數是由上而下分析方法的一種特殊的情況,因為子問題本身和原來問題擁有相同的特性,只是範圍改變,範圍逐漸縮小到一個終止條件。遞迴函數的特性,如下所示: • 遞迴函數在每次呼叫時,都可以使問題範圍逐漸縮小。 • 函數需要擁有一個終止條件,以便結束遞迴函數的執行,否則遞迴函數並不會結束,而是持續的呼叫自已。

  40. 6-5-2 遞迴的階層函數-說明 • 遞迴函數最簡易的應用是數學的階層函數n!,如下所示:

  41. 6-5-2 遞迴的階層函數-過程1 • 例如:計算4!的值,從上述定義n>0,使用n!定義的第2條計算階層函數4!的值,如下所示: 4!=4*3*2*1=24 • 因為階層函數本身擁有遞迴特性。可以將4!的計算分解成子問題,如下所示: 4!=4*(4-1)!=4*3! • 現在3!的計算成為一個新的子問題,必須先計算出3!值後,才能處理上述的乘法。

  42. 6-5-2 遞迴的階層函數-過程2 • 同理將子問題3!繼續分解,如下所示: 3! = 3*(3-1)! = 3*2! 2! = 2*(2-1)! = 2*1! 1! = 1*(1-1)! = 1*0! = 1*1 = 1 • 最後在知道1!的值後,接著就可以計算出2!~4!的值,如下所示: 2! = 2*(2-1)! = 2*1! = 2 3! = 3(3-1)! = 3*2! = 3*2 = 6 4! = 4*(4-1)! = 4*3! = 24

  43. 6-5-2 遞迴的階層函數-函數 /* 函數: 計算n!的值 */ long factorial(int n) { if ( n == 1 ) /* 終止條件 */ return 1; else return n * factorial(n-1); }

  44. 6-5-3 河內塔問題-說明 • 「河內塔」(Tower of Hanoi)問題是程式語言在說明遞迴觀念時,不可錯過的實例,這是一個流傳在Brahma廟內的遊戲,廟內的僧侶相信完成這個遊戲是一件不可能的任務。河內塔問題共有三根木樁,如下圖所示:

  45. 6-5-3 河內塔問題-規則 • 共有n個盤子放置在第一根木樁,盤子的尺寸由上而下依序遞增。河內塔問題是將所有的盤子從木樁1搬移到木樁3,在搬動的過程中有三項規則,如下所示: • 每次只能移動一個盤子,而且只能從最上面的盤子搬動。 • 任何盤子可以搬到任何一根木樁。 • 必須維持盤子的大小是由上而下依序遞增。

  46. 6-5-3 河內塔問題-步驟 • 歸納出三個步驟,如下所示: • Step 1:將最上面n-1個盤子從木樁1搬移到木樁2。 • Step 2:將最後一個盤子從木樁1搬移到木樁3。 • Step 3:將木樁2的n-1個盤子搬移到木樁3。

  47. 6-5-3 河內塔問題-函數 /* 遞迴函數: 河內塔問題 */ void towerofHanoi(int dishs, int peg1, int peg2, int peg3) { if ( dishs == 1 ) /* 終止條件 */ printf("盤子從%d移到%d\n", peg1, peg3); else { /* 第二步驟 */ towerofHanoi(dishs-1, peg1, peg3, peg2); printf("盤子從%d移到%d\n", peg1, peg3); /* 第三步驟 */ towerofHanoi(dishs-1, peg2, peg1, peg3); } }

  48. 6-6 C語言的巨集 • 6-6-1 含括檔案(File Inclusion) • 6-6-2 巨集指令(Macro Substitution) • 6-6-3 條件式含括檔案或指定常數

  49. 6-6 C語言的巨集 • C語言的巨集是程式碼在編譯前透過「C的前置處理器」(The C PreProcessor)來處理,這是位在C程式檔開頭以「#」字元起頭的指令。 • 目前我們已經使用過#include和#define指令,更進一步還可以使用這些前置處理器指令建立「巨集」(Macro)。

  50. 6-6-1 引入檔案(File Inclusion) • C前置處理器的#include指令可以將其它程式檔案的內容含括到目前的程式檔案,含括的意義是將檔案內容直接複製到程式碼檔案,其指令格式如下所示: #include <檔案名稱.h> • 如果是自行定義的標頭檔案(通常是使用副檔名.h),檔案和C程式檔位在相同目錄時,可以使用引號括起檔案名稱,如下所示: #include "檔案名稱.h"

More Related