C語言參數全攻略:實參、形參、傳遞方式與實用範例徹底解析

1. C語言中的參數基本知識

什麼是參數

參數是指在函式被呼叫時,從外部傳遞給函式的資料。透過使用參數,函式可以接收各種不同的值作為輸入,並依據這些值進行相應處理。熟悉 C 語言中的參數使用方式,是提升程式重用性與彈性不可或缺的一步。

實際參數與形式參數

由呼叫端傳入的值稱為實際參數(實參),而在函式定義中接收該值的則稱為形式參數(形參)。例如,PrintScore(score); 裡的 score 是實際參數,而 void PrintScore(int score)score 則是形式參數。正確理解兩者的差異,對正確使用函式非常重要。

2. 實際參數與形式參數的差異

實際參數

實際參數是在呼叫函式時實際傳遞的值。例如,PrintScore(100); 中的 100 就是實際參數。它會被傳遞給函式,並在該函式內部使用。

形式參數

形式參數是在函式定義時所指定的變數名稱。形參在函式內部代表實參的值,但無法在函式外部修改。例如,void PrintScore(int score) 中的 score 就是形式參數。

3. 參數的傳遞方式

值傳遞(Pass by Value)

值傳遞是將實際參數的值複製給形式參數。在此情況下,即使函式內部改變形參的值,也不會影響到呼叫端的實參。請參考以下範例:

void LevelUp(int lv) {
    lv++;
}

int main() {
    int level = 1;
    LevelUp(level);
    printf("Level: %dn", level); // 輸出: Level: 1
}

本例中,雖然 LevelUp 內的 lv 有被加一,但 main 函式裡的 level 並未受到影響。值傳遞的優點是能保護呼叫端資料,但如果傳遞大量資料時,記憶體使用量會增加,須留意效能。

指標傳遞(Pass by Pointer)

指標傳遞是將實參的記憶體位址傳給形參。這讓函式內部可以直接修改實參的值。

void LevelUp(int *plv) {
    (*plv)++;
}

int main() {
    int level = 1;
    LevelUp(&level);
    printf("Level: %dn", level); // 輸出: Level: 2
}

這個例子中,LevelUp 函式可以直接改變 level 變數的值。指標傳遞的好處是可以在函式間回傳多個結果,但若指標操作不當,易導致 bug 或記憶體洩漏,需小心處理。

4. 參數數量與回傳值的組合

有參數、無回傳值

此類型函式有參數,但不回傳處理結果。例如 void PrintScore(int score),僅負責顯示分數。

無參數、有回傳值

此類型函式無需傳入參數,僅回傳處理結果。例如 int GetCurrentScore(),計算後回傳目前分數。

有參數、有回傳值

同時有參數也有回傳值。例如 int Add(int a, int b),傳入兩數,回傳加總。這類函式彈性高,應用廣泛。

5. 遞迴呼叫與參數

什麼是遞迴呼叫

遞迴呼叫是函式內部再次呼叫自己,用來將複雜問題拆分為較小部分逐步解決。若遞迴結束條件設計不當,容易發生堆疊溢位(Stack Overflow)。

遞迴呼叫範例

下例說明如何利用參數遞迴將數字不斷除以 2:

int funcA(int num) {
    if(num % 2 != 0) {
        return num;
    }
    return funcA(num / 2);
}

int main() {
    int result = funcA(20);
    printf("Result: %dn", result); // 輸出: Result: 5
}

此例中,funcA 會不斷將傳入的數字除以 2,直到遇到奇數時回傳。遞迴雖方便,但須正確設定結束條件,否則易陷入無窮迴圈。

6. 函數樣式巨集與參數

什麼是函數樣式巨集

函數樣式巨集是帶有參數的巨集,在編譯階段會將程式碼做置換,能提升執行效能。

函數樣式巨集範例

以下為取得陣列元素數量的巨集範例:

#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))

int main() {
    int arr[10];
    printf("Array size: %dn", ARRAY_SIZE(arr)); // 輸出: Array size: 10
}

函數樣式巨集在編譯前直接置換,不會產生執行時的額外負擔。但因未做型別檢查,可用於多種資料型態,需注意避免預期外行為。

7. C語言標準函式庫中的函式與參數

善用標準函式庫

C 語言提供了眾多標準函式,這些函式都利用參數來完成各種操作。例如 printf 函式就是一個可變參數函式,可依格式字串顯示資料。

標準函式庫範例

以下是 printf 的使用例子:

printf("Name: %s, Age: %dn", "Alice", 30); // 輸出: Name: Alice, Age: 30

本例利用 printf 同時顯示字串與數值。善用標準函式,可讓程式更易讀且高效。

8. 小結

可變參數的應用

C 語言允許函式接收數量不固定的參數,這稱為可變參數。用省略號(...)在定義時標記。最知名的例子是 printf,能依格式字串傳入任意數量參數。

可變參數範例

以下範例說明如何使用可變參數計算多個整數的總和:

#include <stdarg.h>
#include <stdio.h>

int sum(int count, ...) {
    va_list args;
    va_start(args, count);
    int total = 0;

    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }

    va_end(args);
    return total;
}

int main() {
    printf("Sum: %dn", sum(4, 1, 2, 3, 4)); // 輸出: Sum: 10
}

本例中,sum 可接收任意數量的整數參數並回傳總和。使用 va_listva_startva_argva_end 來處理可變參數。

注意事項

使用可變參數時,必須確保參數數量與型別正確。若呼叫與定義不符,易發生預期外錯誤或程式崩潰。

實用案例與參數的應用

參數的有效使用方式

善用參數能提升程式碼可讀性與重用性。例如,當多個函式需處理同筆資料時,比起使用全域變數,直接透過參數傳遞可提升函式獨立性並降低彼此影響。

記憶體效率與效能

若需傳遞大量資料,建議採用指標傳遞以節省記憶體。例如,傳遞大型陣列或結構時,值傳遞會複製整份資料,指標傳遞則僅傳遞位址,有助於提升效率。

程式設計最佳實踐

設計函式時,建議謹慎規劃所需參數的數量與型別。避免傳遞不必要參數,以免增加複雜度與錯誤風險。將所有所需資料明確地當作參數傳入,有助於提升程式碼的可讀性與維護性。

9. 參數相關進階技巧

回呼函式(Callback)

回呼函式指將函式當作參數傳遞給其他函式,並在特定時機呼叫。這種技巧常用於事件驅動或非同步程式設計中,能靈活擴展功能。

#include <stdio.h>

void executeCallback(void (*callback)(int)) {
    callback(10);
}

void printValue(int val) {
    printf("Value: %dn", val);
}

int main() {
    executeCallback(printValue); // 輸出: Value: 10
}

這個例子中,將 printValue 作為回呼函式傳入 executeCallback,並於該函式中執行。

函式指標

函式指標可讓函式像變數一樣被操作、傳遞。能根據需要動態指定要呼叫的函式,靈活性高,是進階 C 語言技巧。

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    int (*operation)(int, int) = add;
    printf("Result: %dn", operation(2, 3)); // 輸出: Result: 5
}

本例中,add 被指定給函式指標 operation,再經由指標呼叫。

10. 函式參數與記憶體管理

動態記憶體與參數

在 C 語言中,可利用 mallocfree 進行動態記憶體分配。若將動態分配的記憶體指標作為參數傳遞,須特別注意記憶體管理。

#include <stdlib.h>
#include <stdio.h>

void allocateMemory(int **ptr, int size) {
    *ptr = (int *)malloc(size * sizeof(int));
}

int main() {
    int *arr;
    allocateMemory(&arr, 5);
    for (int i = 0; i < 5; i++) {
        arr[i] = i + 1;
    }
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]); // 輸出: 1 2 3 4 5
    }
    free(arr); // 釋放記憶體
}

此例中,allocateMemory 動態分配記憶體,並將指標傳遞給主程式。若未妥善釋放,容易發生記憶體洩漏,需格外小心。