首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言自訂函數完全指南:定義、參數傳遞、聲明與調用實戰

     編輯:關於C語言

C語言自訂函數完全指南

函數是 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;
}

二、函數參數深度解析

2.1 形式參數 vs 實際參數

  • 形式參數 (Parameter):函數定義時括號內的變數宣告,僅在函數被呼叫時分配記憶體,函數結束即釋放。
  • 實際參數 (Argument):呼叫函數時傳入的具體值(常數、變數、運算式)。
// 定義: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;
}

2.2 參數傳遞機制:傳值呼叫

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;
}

2.3 參數傳遞規則總結

  1. 實參與形參在數量、型別、順序上必須嚴格對應。
  2. 實參可以是常數、變數、運算式,但必須具備確定值。
  3. 形參變數在函數呼叫時才分配記憶體,返回時釋放(自動儲存期)。
  4. 陣列作為參數時退化為指標,傳遞的是首元素位址,故函數內可修改陣列原始內容。

三、函數聲明與定義分離

3.1 為什麼需要聲明?

C 編譯器採單通過編譯,若呼叫端在定義之前使用函數,編譯器無法得知函數簽名,會產生隱式宣告警告甚至錯誤。聲明(原型宣告)告訴編譯器「這個函數存在、長什麼樣」。

3.2 聲明語法

// 完整原型(建議)
int add(int a, int b);
// 或省略參數名(僅保留型別)
int add(int, int);

3.3 專案架構最佳實踐

  • 標頭檔 (.h):放置函數聲明、結構體定義、巨集常數。
  • 原始檔 (.c):實作函數定義。
  • 主程式:#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;
}

四、函數調用進階用法

4.1 函數作為運算式元素

int result = add(10, 20) * 2 + multiply(3, 4); // 合法

4.2 巢狀調用

int max3(int a, int b, int c) {
    return max(max(a, b), c); // 內層 max 先算,結果傳給外層
}

4.3 遞迴調用

函數直接或間接呼叫自身,必須具備遞迴出口(基底案例)與遞迴步驟。

// 階乘
unsigned long long factorial(unsigned int n) {
    if (n == 0) return 1; // 出口
    return n * factorial(n - 1); // 步驟
}

4.4 回調函數

將函數指標作為參數傳遞,實現泛型演算法或事件驅動。

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 程式碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved