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

C語言自定義函數相互調用與參數傳遞深度解析

     編輯:C 語言問答

C語言自定義函數相互調用與參數傳遞深度解析

「自定義函數之間如何調用?形參實參有什麼注意點?」這是C語言模組化程式設計的核心技能。本文從調用機制、參數傳遞語義、作用域規則、聲明與定義分離、遞歸與相互遞歸、工程化最佳實踐六個維度,系統講解函數調用全景。

一、函數調用機制:棧幀視角

調用者棧幀                    被調用者棧幀 (callee)
┌─────────────────────┐       ┌─────────────────────┐
│ 返回地址 (RA)        │ ←──── │ (由call指令壓入)    │
├─────────────────────┤       ├─────────────────────┤
│ 實參3 (右到左壓棧)    │       │ 形參3 (副本/引用)    │
│ 實參2                │       │ 形參2                │
│ 實參1                │       │ 形參1                │
├─────────────────────┤       ├─────────────────────┤
│ 保存的寄存器 (callee-saved) │  局部變量、臨時值      │
├─────────────────────┤       ├─────────────────────┤
│ ...                 │       │ ...                 │
└─────────────────────┘       └─────────────────────┘
      棧增長方向 (高地址 → 低地址)

關鍵點:

  • 實參求值順序:C標準未規定 (通常右到左,但不可依賴)
  • 參數傳遞:僅支持傳值,所謂「傳址」是傳遞指針值
  • 返回值:通過寄存器 (RAX/EAX) 或棧傳遞,大結構體隱式傳遞指針

二、形參與實參:核心區別與陷阱

場景實參形參效果注意事項
基本類型int x = 10;void f(int p)值拷貝,函數內修改不影響外部需修改外部值必須傳指針 f(&x) / void f(int *p)
指針類型int *ptr = &x;void f(int *p)指針值拷貝,指向同一地址,可修改指向對象函數內修改指針本身 (如p = malloc(...)) 不影響外部指針
數組int arr[10];void f(int arr[]) 或 void f(int *arr)退化為指向首元素指針,長度信息丟失必須額外傳遞長度參數,或約定結束標記 (如\0)
結構體struct S s;void f(struct S s)整體拷貝 (淺拷貝),開銷大,修改不影響外部大結構體必須傳指針 void f(const struct S *s)
函數指針int (*cmp)(int,int);void f(int (*cmp)(int,int))回調函數,實現多態/策略模式簽名必須完全匹配,可用typedef簡化

三、函數聲明、定義與調用順序


// 文件: math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

// 函數聲明 (原型) - 告訴編譯器函數簽名
int max(int a, int b);           // 最大公約數 (此處應為gcd)
int min(int a, int b);           // 最小公倍數 (此處應為lcm)

// 更好的命名
int gcd(int a, int b);           // Greatest Common Divisor
int lcm(int a, int b);           // Least Common Multiple

#endif

// 文件: math_utils.c
#include "math_utils.h"

int gcd(int a, int b) {          // 定義
    int m = (a < b) ? a : b;
    for (; a % m != 0 || b % m != 0; m--);
    return m;
}

int lcm(int a, int b) {
    // 注意:a*b可能溢出,工程上應用長整型或先除後乘
    return a / gcd(a, b) * b;    // 調用同文件/其他文件函數
}

// 文件: main.c
#include 
#include "math_utils.h"           // 包含聲明

int main() {
    int x, y;
    printf("輸入兩個正整數: ");
    if (scanf("%d %d", &x, &y) != 2) return 1;
    
    printf("最大公約數: %d\n", gcd(x, y));  // 調用
    printf("最小公倍數: %d\n", lcm(x, y));
    return 0;
}

四、相互遞歸與前向聲明


// 間接遞歸:even/odd 相互調用
// 必須先聲明其中一個
int odd(int n);  // 前向聲明

int even(int n) {
    if (n == 0) return 1;
    return odd(n - 1);
}

int odd(int n) {   // 定義
    if (n == 0) return 0;
    return even(n - 1);
}

// 直接遞歸:階乘
int factorial(int n) {
    if (n <= 1) return 1;  // 基線條件
    return n * factorial(n - 1); // 遞歸調用
}

五、工程化最佳實踐:函數設計準則

  1. 單一職責:一個函數只做一件事,<50行,循環嵌套<3層。
  2. 接口契約:前置條件(斷言/文檔)、後置條件、副作用說明。
    /**
     * @brief 計算兩正整數最大公約數 (歐幾里得算法)
     * @param a > 0, b > 0 (違反導致未定義行為)
     * @return gcd(a,b) > 0
     * @note 無副作用,線程安全
     */
    int gcd(int a, int b);
    
  3. 參數設計:
    • 輸入參數:const修飾、按值傳遞小對象、按const *傳大對象
    • 輸出參數:非const指針,文檔標註 `[out]`
    • 輸入輸出:非const指針,標註 `[in,out]`
  4. 錯誤處理:返回錯誤碼枚舉,或輸出參數返回結果+返回錯誤碼。
    typedef enum { OK=0, ERR_NULL_PTR, ERR_INVALID_ARG, ERR_OVERFLOW } ErrCode;
    
    ErrCode safe_lcm(int a, int b, int *result) {
        if (!result) return ERR_NULL_PTR;
        if (a <= 0 || b <= 0) return ERR_INVALID_ARG;
        int g = gcd(a, b);
        if (a / g > INT_MAX / b) return ERR_OVERFLOW; // 溢出檢查
        *result = a / g * b;
        return OK;
    }
    
  5. 靜態內聯:static inline用於頭文件小函數,避免調用開銷,防止多定義錯誤。
  6. 測試友好:純函數(無副作用、確定性)易於單元測試,依賴外部狀態的函數封裝接口便於Mock。

六、常見錯誤避坑指南

錯誤代碼問題修正
void f(int a[]) { sizeof(a) }數組退化為指針,`sizeof`得指針大小額外傳遞長度參數 `void f(int *a, size_t n)`
int *p; f(p); // f分配內存傳值調用,外部p不變int **p; f(p); // void f(int **p) { *p = malloc(...); }
return &local_var;返回棧地址,函數返回後失效返回動態分配內存、靜態變量、或輸出參數
遞歸無基線條件/不收斂棧溢出崩潰必須有基線條件,且每次遞歸向基線推進
頭文件放函數定義多文件包含導致重複定義鏈接錯誤頭文件只放聲明,定義放`.c`,或用`static inline`

掌握「聲明分離、傳值本質、指針傳址、契約設計、錯誤碼返回、測試友好」這套函數工程化體系,是從「寫出能跑的代碼」邁向「構建可維護系統」的關鍵一步。

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