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

C語言函數完全指南:聲明定義、參數傳遞、遞歸、內聯、可變參數與最佳實踐

     編輯:關於C語言

前言

函數是 C 語言模塊化、復用、抽象的核心機制。本文系統梳理函數聲明定義、參數傳遞語義(值傳遞本質)、遞歸與尾調用、內聯函數、可變參數(stdarg.h)、函數指針回調、屬性擴展(GCC/Clang),並給出工程級設計原則與常見陷阱。

一、函數聲明與定義

1. 聲明(原型)vs 定義

// 聲明:告知編譯器名稱、返回類型、參數類型
int add(int a, int b);        // 參數名可省略
int add(int, int);            // 等價
// 定義:提供函數體
int add(int a, int b) { return a + b; }

2. 隱式聲明已移除(C99)

C89 允許未聲明直接調用,默認 int func()。C99 起**必須先聲明**,否則編譯錯誤。

3. 空參數列表 vs void 參數

int foo();    // 老式:參數未指定,調用時不檢查(危險)
int bar(void); // 標準:無參數,調用帶參報錯

二、參數傳遞:值傳遞的本質與模擬引用

1. 核心原則:C 只有值傳遞

  • 形參獲得實參副本,函數內修改不影響實參
  • 「引用傳遞」通過**傳遞指針值**模擬

2. 模擬引用傳遞模式

void swap(int *a, int *b) {
    int t = *a; *a = *b; *b = t;
}
int x=1, y=2;
swap(&x, &y);  // 傳遞地址值,函數通過解引用修改原對象

3. 數組參數退化為指針

void foo(int arr[10]);     // 等價於
void foo(int *arr);        // 等價於
void foo(int arr[]);       // 長度信息丟失,必須額外傳 len

4. const 修飾參數:語義契約與優化提示

int strlen(const char *s);  // 承諾不修改 s 指向的內容
void process(const int *arr, size_t n);  // 僅讀數組

三、遞歸與尾調用優化

1. 遞歸三要素

  • 基本情況:終止條件
  • 遞歸步驟:問題規模縮小
  • 縮小保證:最終達到基本情況

2. 經典遞歸:階乘、斐波那契、二叉樹遍歷

unsigned long long fact(unsigned n) {
    return n <= 1 ? 1 : n * fact(n-1);
}

3. 尾遞歸與編譯器優化

特性inline 函數宏
類型安全✓✗
副作用安全✓(參數求值一次)✗(多次求值)
調試可見✓✗
編譯期計算✓(constexpr C++/常量傳播)✓
代碼膨脹編譯器控制完全展開


3. 現代最佳實踐

#include 
void log_msg(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);      // 最後一個固定參數
    vprintf(fmt, ap);       // 傳遞 va_list
    va_end(ap);             // 清理
}

2. 類型安全缺失與替代方案

  • 調用者需保證格式字符串與實參匹配,否則 UB
  • C11 _Generic 可構建類型安全可變參數
  • 現代項目首選 fmt 庫(C++)/ 自定義類型安全日誌 API

六、函數指針與回調模式

1. 聲明語法

int (*cmp)(const void *, const void *);  // 指向函數的指針
void (*handler)(int);                    // 信號處理器類型

2. 標準庫算法回調

qsort(base, nmemb, size, cmp);
bsearch(key, base, nmemb, size, cmp);

3. 面向對象模擬:虛函數表

struct Animal {
    void (*speak)(struct Animal *);
    void (*move)(struct Animal *);
};
void dog_speak(struct Animal *self) { puts("Woof"); }
struct Animal dog = { dog_speak, dog_move };
dog.speak(&dog);  // 多態調用

七、GCC/Clang 函數屬性擴展

屬性用途示例
noreturn永不返回,優化調用點void abort(void) __attribute__((noreturn));
pure無副作用,僅依賴參數/全局讀int square(int) __attribute__((pure));
const純函數+無全局讀,結果可緩存int hash(int) __attribute__((const));
malloc返回非別名指針,優化別名分析void *alloc(size_t) __attribute__((malloc));
warn_unused_result忽略返回值發出警告int open(...) __attribute__((warn_unused_result));
format(printf, m, n)格式字符串檢查void log(const char*,...) __attribute__((format(printf,1,2)));
cleanup變量作用域結束自動調用__attribute__((cleanup(free)))

八、工程級函數設計原則

  1. 單一職責:一個函數只做一件事
  2. 命令查詢分離:修改狀態的不返回值,查詢的不修改狀態
  3. 參數順序:輸出參數在前(目標),輸入參數在後(源),如 memcpy(dst, src, n)
  4. 錯誤處理統一:返回錯誤碼,輸出參數帶回結果,或 errno、_Thread_local 錯誤上下文
  5. const 正確性:能加 const 盡量加
  6. 文檔註釋:Doxygen 風格:/** @brief ... @param ... @return ... */

九、總結

C 語言函數機制樸素而強大。**值傳遞本質、指針模擬引用、遞歸轉迭代、內聯權衡、可變參數類型安全、函數指針多態、屬性引導優化**,構成了系統級開發的函數式工具箱。現代 C 開發建議:頭文件 static inline 小函數、.c 文件 __attribute__ 關鍵函數、回調用函數指針、避免可變參數公共 API、錯誤碼統一風格。

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