Back
Clang & Wasm 教學

函數應用與封裝

掌握條件判斷與迴圈結構,讓程式具備決策能力與高效執行方式,實現更靈活的控制流程。

目標

  1. 理解函數的基本概念與語法。
  2. 學會如何定義函數、呼叫函數,並理解參數與回傳值的作用。
  3. 實作簡單的函數應用,並理解函數的模組化設計。
  4. 理解遞迴函數的概念與應用。
  5. 學會如何設計遞迴函數,並理解遞迴的執行過程。
  6. 實作遞迴函數解決問題,並理解遞迴與迴圈的差異。
  7. 理解如何將函數封裝成獨立的 API 模組。
  8. 學會使用標頭檔(Header File)與原始碼檔(Source File)來組織程式碼。
  9. 使用 clang 編譯多檔案專案,並測試幾何應用。

1. 函數

1.1 函數的基本概念

  • 函數是什麼?
    • 函數是一段可重複使用的程式碼,可以接受輸入(參數),並回傳輸出(回傳值)。
  • 函數的優點:
    • 提高程式碼的可讀性與可維護性。
    • 減少重複程式碼。
    • 模組化設計,方便測試與除錯。
  • 函數的組成:
    • 函數名稱:用來識別函數。
    • 參數:函數的輸入值。
    • 回傳值:函數的輸出值。
    • 函數主體:函數的執行邏輯。

1.2 函數的語法

  • 函數的定義:
    回傳值類型 函數名稱(參數1類型 參數1, 參數2類型 參數2, ...) {
        // 函數主體
        return 回傳值; // 回傳值必須與回傳值類型一致
    }
    
  • 範例:一個簡單的加法函數
    int add(int a, int b) {
        return a + b;
    }
    

1.3 函數的呼叫

  • 呼叫函數的方式:
    回傳值類型 變數 = 函數名稱(參數1, 參數2, ...);
    
  • 範例:呼叫 add 函數
    int result = add(3, 5); // result 會是 8
    

1.4 參數與回傳值

  • 參數:函數的輸入值,可以是基本型別(如 int, float)或自訂型別。
  • 回傳值:函數執行完畢後回傳的結果,如果不需要回傳值,可以使用 void
  • 範例:無回傳值的函數
    void print_hello() {
        printf("你好,世界!\n");
    }
    

1.5 函數的模組化設計

  • 將功能獨立的程式碼封裝成函數,方便重複使用。
  • 範例:計算圓的面積
    float circle_area(float radius) {
        return 3.14159 * radius * radius;
    }
    

1.6 範例

  • 範例 1:寫一個函數 square,計算一個整數的平方。
    int square(int x) {
        return x * x;
    }
    
  • 範例 2:寫一個函數 max,比較兩個整數並回傳較大的值。
    int max(int a, int b) {
        if (a > b) {
            return a;
        } 
        else {
            return b;
        }
    }
    
  • 範例 3:寫一個函數 print_triangle,根據輸入的高度用*印出一個等腰直角三角形。
    void print_triangle(int height) {
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < i; j++) {
                printf("*");
            }
            printf("\n");
        }
    }
    

1.7 綜合範例

以下是一個完整的程式範例,包含上述的 addsquaremaxprint_triangle 函數:

#include <stdio.h>

// 函數宣告
int add(int a, int b);
int square(int x);
int max(int a, int b);
void print_triangle(int height);

int main() {
    // 呼叫 add 函數
    int sum = add(3, 5);
    printf("3 + 5 = %d\n", sum);

    // 呼叫 square 函數
    int squared = square(4);
    printf("4 的平方是 %d\n", squared);

    // 呼叫 max 函數
    int larger = max(10, 7);
    printf("10 和 7 中較大的數是 %d\n", larger);

    // 呼叫 print_triangle 函數
    printf("印出高度為 5 的直角三角形:\n");
    print_triangle(5);

    return 0;
}

// 函數定義
int add(int a, int b) {
    return a + b;
}

int square(int x) {
    return x * x;
}

int max(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

void print_triangle(int height) {
    for (int i = 1; i <= height; i++) {
        for (int j = 1; j <= i; j++) {
            printf("*");
        }
        printf("\n");
    }
}

2. 函數應用與遞迴

2.1 遞迴函數的基本概念

  • 遞迴是什麼?
    • 遞迴是一種函數呼叫自身的技術,通常用於解決可以分解為相同子問題的問題。
  • 遞迴的兩個關鍵要素:
    • 遞迴條件(Recursive Case):函數呼叫自身的條件。
    • 終止條件(Base Case):停止遞迴的條件,避免無限遞迴。
  • 遞迴的優點:
    • 程式碼簡潔,容易理解。
    • 適合解決具有遞迴性質的問題(如樹狀結構、分治法)。
  • 遞迴的缺點:
    • 可能導致堆疊溢出(Stack Overflow)。
    • 效率較低,因為每次呼叫都會產生額外的函數呼叫開銷。

2.2 遞迴函數的語法

  • 遞迴函數的定義:
    回傳值類型 函數名稱(參數) {
        if (終止條件) {
            return 終止值;
        } else 
        {
            return 函數名稱(修改後的參數);
        }
    }
    
  • 範例:計算階乘的遞迴函數
    int factorial(int n) {
        if (n == 1) { // 終止條件
            return 1;
        } 
        else { // 遞迴條件
            return n * factorial(n - 1);
        }
    }
    

2.3 遞迴的執行過程

  • factorial(3) 為例:
    factorial(3)
    => 3 * factorial(2)
    => 3 * (2 * factorial(1))
    => 3 * (2 * 1)
    => 3 * 2
    => 6
    

2.4 遞迴與迴圈的比較

  • 遞迴和迴圈都可以用來解決重複性問題,但各有優缺點。
  • 範例:用迴圈實現階乘
    int factorial_loop(int n) {
        int result = 1;
        for (int i = 1; i <= n; i++) {
            result *= i;
        }
        return result;
    }
    

2.5 實作範例

  • 範例 1:寫一個遞迴函數 fibonacci,計算第 n 個費波那契數。 PS. F(N) = F(N-1) + F(N-2)
    int fibonacci(int n) {
        if (n <= 0) { // 終止條件 F(0) 或是負數 就 = 0
            return 0;
        } 
        else if (n == 1) { // 終止條件
            return 1;
        } 
        else { // 遞迴條件
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }
    
  • 練習 2:寫一個遞迴函數 sum_digits,計算一個整數的各位數字之和。
    int sum_digits(int n) {
        if (n < 10) { // 終止條件
            return n;
        } 
        else { // 遞迴條件
            return n % 10 + sum_digits(n / 10);
        }
    }
    

2.6 綜合實作範例

以下是一個完整的程式範例,包含上述的 factorialfibonaccisum_digits 函數:

#include <stdio.h>

// 函數宣告
int factorial(int n);
int fibonacci(int n);
int sum_digits(int n);

int main() {
    // 呼叫 factorial 函數
    int fact = factorial(5);
    printf("5 的階乘是 %d\n", fact);

    // 呼叫 fibonacci 函數
    int fib = fibonacci(6);
    printf("第 6 個費波那契數是 %d\n", fib);

    // 呼叫 sum_digits 函數
    int sum = sum_digits(1234);
    printf("1234 的各位數字之和是 %d\n", sum);

    return 0;
}

// 函數定義
int factorial(int n) {
    if (n == 1) {
        return 1;
    } 
    else {
        return n * factorial(n - 1);
    }
}

int fibonacci(int n) {
    if (n <= 0) {
        return 0;
    } 
    else if (n == 1) {
        return 1;
    } 
    else {
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

int sum_digits(int n) {
    if (n < 10) {
        return n;
    } 
    else {
        return n % 10 + sum_digits(n / 10);
    }
}

3. 函數獨立封裝 API

**3.1 函數獨立封裝的概念

  • 為什麼要封裝函數?
    • 提高程式碼的模組化與可重用性。
    • 隱藏實現細節,提供清晰的介面。
  • 如何封裝函數?
    • 將函數宣告放在標頭檔(.h 檔案)。
    • 將函數定義放在原始碼檔(.c 檔案)。
    • 在其他程式中包含標頭檔以使用這些函數。

**3.2 標頭檔與原始碼檔的結構

  • 標頭檔(.h 檔案):
    • 包含函數宣告、常數定義、型別定義等。
    • 範例:geometry.h
      #ifndef GEOMETRY_H
      #define GEOMETRY_H
      
      #define PI 3.14
      
      // 計算圓的面積
      float circle_area(float radius);
      
      // 計算矩形的面積
      float rectangle_area(float width, float height);
      
      // 計算三角形的面積
      float triangle_area(float base, float height);
      
      #endif
      
  • 原始碼檔(.c 檔案):
    • 包含函數的具體實現。
    • 範例:geometry.c
      #include "geometry.h"
      
      // 計算圓的面積
      float circle_area(float radius) {
          return PI * radius * radius;
      }
      
      // 計算矩形的面積
      float rectangle_area(float width, float height) {
          return width * height;
      }
      
      // 計算三角形的面積
      float triangle_area(float base, float height) {
          return 0.5 * base * height;
      }
      

**3.3 使用封裝的 API

  • 在主程式中包含標頭檔,並呼叫封裝的函數。
  • 範例:main.c
    #include <stdio.h>
    #include "geometry.h"
    
    int main() {
        // 計算圓的面積
        float radius = 5.0;
        printf("半徑為 %.2f 的圓的面積是 %.2f\n", radius, circle_area(radius));
    
        // 計算矩形的面積
        float width = 4.0, height = 6.0;
        printf("寬為 %.2f、高為 %.2f 的矩形的面積是 %.2f\n", width, height, rectangle_area(width, height));
    
        // 計算三角形的面積
        float base = 3.0, triangle_height = 4.0;
        printf("底為 %.2f、高為 %.2f 的三角形的面積是 %.2f\n", base, triangle_height, triangle_area(base, triangle_height));
    
        return 0;
    }
    

**3.4 使用 clang 編譯多檔案專案

  • 編譯步驟:
    1. 編譯原始碼檔(.c 檔案)為目標檔(.o 檔案)。
    2. 將目標檔連結成可執行檔。
  • 具體指令:
    # 編譯 geometry.c 為目標檔
    clang -c geometry.c -o geometry.o
    
    # 編譯 main.c 為目標檔
    clang -c main.c -o main.o
    
    # 將目標檔連結成可執行檔
    clang geometry.o main.o -o geometry_program
    
    # 執行可執行檔
    ./geometry_program
    
  • 輸出結果:
    半徑為 5.00 的圓的面積是 78.54
    寬為 4.00、高為 6.00 的矩形的面積是 24.00
    底為 3.00、高為 4.00 的三角形的面積是 6.00
    

作業

作業目標

  1. 運用函數封裝的技巧,將幾何計算功能獨立成模組。
  2. 實作幾何相關的函數,並應用於具體問題。
  3. 培養學生解決實際問題的能力。

作業內容

  1. 目標:計算圓的周長與面積

    • 請寫一個函數 circle_circumference,計算圓的周長。
    • 公式:周長 = 2 * PI * 半徑
    • 請寫一個函數 circle_area ,計算圓的面積。
    • 公式:周長 = PI * 半徑 * 半徑
  2. 目標:計算圓柱體積與表面積

    • 利用目標 1 的2個函數來實現
  3. 目標:計算 1 + 2 ... + N 的總和

    • 利用遞迴實現

作業要求

  1. 將所有幾何函數封裝在一個標頭檔 geometry.h 和一個原始碼檔 geometry.c 中。
  2. 在主程式 main.c 中測試所有函數,並輸出結果。
  3. 程式碼需包含註解,說明每個函數的功能與輸入輸出。

範例輸出

請輸入圓柱的半徑: 10
請輸入圓柱的高度: 5

圓柱體積是: 1570
圓柱表面積是: 942

請輸入N: 10
1+2 ... +N = 55

SNPQ © 2025