函數是 C 語言模組化程式設計的基石。透過將重複邏輯封裝為可重複使用的函數,不僅能縮減程式碼體積,更能提升可讀性與維護性。本文系統整理自訂函數的四大類型、參數傳遞機制、聲明與調用規則,並提供實戰範例與常見錯誤避坑指南。
依「是否有參數」與「是否有回傳值」劃分:
| 類型 | 語法範本 | 適用情境 |
|---|---|---|
| 無參無回傳 | void func(void) { ... } | 單純執行固定流程(如印出選單、初始化硬體) |
| 無參有回傳 | int func(void) { return value; } | 取得系統狀態、計算固定值(如取得當前時間、隨機數) |
| 有參無回傳 | void func(int a, float b) { ... } | 對傳入資料進行處理但不需回傳結果(如列印陣列、寫入檔案) |
| 有參有回傳 | int func(int a, int b) { return a+b; } | 通用計算、資料轉換(如加法、字串比對、最大值) |
#include <stdio.h>
// 1. 無參無回傳
void printHeader(void) {
printf("=== 歡迎使用計算機 ===\n");
}
// 2. 無參有回傳
int getRandomNumber(void) {
return rand() % 100;
}
// 3. 有參無回傳
void printSum(int a, int b) {
printf("%d + %d = %d\n", a, b, a + b);
}
// 4. 有參有回傳
int add(int a, int b) {
return a + b;
}
int main(void) {
printHeader();
int x = getRandomNumber();
int y = getRandomNumber();
printSum(x, y);
printf("透過回傳值相加:%d\n", add(x, y));
return 0;
}
// 定義:x、y 為形式參數
int max(int x, int y) {
return (x > y) ? x : y;
}
int main(void) {
int a = 10, b = 20;
// 呼叫:a、b、a+b 均為實際參數
int m1 = max(a, b);
int m2 = max(100, 200);
int m3 = max(a + 5, b - 3);
return 0;
}
C 語言永遠採用傳值呼叫。實參的值被複製給形參,函數內對形參的修改不會影響實參。
void swap(int x, int y) {
int tmp = x; x = y; y = tmp; // 僅交換區域副本
}
int main(void) {
int a = 5, b = 10;
swap(a, b);
printf("a=%d, b=%d\n", a, b); // 輸出 a=5, b=10,未被交換
return 0;
}
若需修改實參,必須傳遞指標:
void swapPtr(int *x, int *y) {
int tmp = *x; *x = *y; *y = tmp; // 透過指標間接修改實參
}
int main(void) {
int a = 5, b = 10;
swapPtr(&a, &b);
printf("a=%d, b=%d\n", a, b); // 輸出 a=10, b=5
return 0;
}
C 編譯器採單通過編譯,若呼叫端在定義之前使用函數,編譯器無法得知函數簽名,會產生隱式宣告警告甚至錯誤。聲明(原型宣告)告訴編譯器「這個函數存在、長什麼樣」。
// 完整原型(建議)
int add(int a, int b);
// 或省略參數名(僅保留型別)
int add(int, int);
#include "myheader.h" 即可使用。/* mymath.h */
#ifndef MYMATH_H
#define MYMATH_H
int add(int, int);
int multiply(int, int);
#endif
/* mymath.c */
#include "mymath.h"
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
/* main.c */
#include <stdio.h>
#include "mymath.h"
int main(void) {
printf("3+4=%d, 3*4=%d\n", add(3,4), multiply(3,4));
return 0;
}
int result = add(10, 20) * 2 + multiply(3, 4); // 合法
int max3(int a, int b, int c) {
return max(max(a, b), c); // 內層 max 先算,結果傳給外層
}
函數直接或間接呼叫自身,必須具備遞迴出口(基底案例)與遞迴步驟。
// 階乘
unsigned long long factorial(unsigned int n) {
if (n == 0) return 1; // 出口
return n * factorial(n - 1); // 步驟
}
將函數指標作為參數傳遞,實現泛型演算法或事件驅動。
void processArray(int *arr, int len, void (*callback)(int)) {
for (int i = 0; i < len; i++) callback(arr[i]);
}
void printInt(int x) { printf("%d ", x); }
void squareInt(int x) { printf("%d ", x * x); }
int main(void) {
int data[] = {1,2,3,4,5};
processArray(data, 5, printInt); // 印出 1 2 3 4 5
putchar('\n');
processArray(data, 5, squareInt); // 印出 1 4 9 16 25
return 0;
}
| 錯誤現象 | 原因 | 修正方法 |
|---|---|---|
| 「implicit declaration」警告 | 呼叫前未聲明、也未定義 | 在檔案頂部或標頭檔加上函數原型宣告 |
| 實參修改無效 | 誤以為傳址呼叫 | 改傳指標 &var,形參改 int *p |
| 陣列長度在函數內變成 1 | 陣列退化為指標,sizeof(arr) 只得指標大小 | 額外傳入長度參數,或使用結構體封裝長度 |
| 遞迴導致 Stack Overflow | 缺少出口或出口條件錯誤 | 檢查基底案例,確保每次遞迴逼近出口 |
| 函數名與區域變數同名 | int sum = sum(3,5); 遮蔽函數名 | 變數命名避開函數名,如 int total = sum(3,5); |
掌握自訂函數的分類、參數傳遞本質(傳值)、聲明與定義分離、進階調用模式(遞迴、回調),是邁向 C 語言中高階開發的關鍵。建議開發者養成「先寫標頭檔聲明、再寫實作、最後在主程式調用」的模組化習慣,並善用指標突破傳值限制,靈活運用函數指標實現泛型邏輯,寫出既高效又優雅的 C 程式碼。