函數是 C 語言的核心組成部分,本質是一段完成特定功能的可重用代碼塊—— 通過封裝邏輯、接收輸入參數、返回處理結果,實現代碼模塊化、簡化編程、便於維護與複用,是大型 C 語言項目開發的基礎。

一、函數的核心概念與分類

1. 核心術語

  • 函數名:函數的唯一標識,遵循 C 語言標識符規則(字母、數字、下劃線組成,首字符非數字),需見名知義(如sum表示求和,sort表示排序)。
  • 參數列表:函數接收的輸入數據,分為形參(函數定義時的參數,僅在函數內有效)和實參(調用函數時傳入的實際數據,需與形參類型、順序一致)。
  • 返回值:函數執行後的輸出結果,通過return語句返回,類型需與函數定義的返回值類型匹配(無返回值時用void聲明)。
  • 函數體:包裹在{}內的核心邏輯,包含變量定義、語句執行等代碼。

2. 函數分類(按定義主體)

  • 庫函數:C 語言標準庫(如stdio.hmath.h)或第三方庫提供的現成函數,直接包含頭文件即可調用,無需自行定義。示例:printf()(格式化輸出,需#include <stdio.h>)、sqrt()(求平方根,需#include <math.h>)、strlen()(計算字符串長度,需#include <string.h>)。
  • 自定義函數:開發者根據需求自行設計的函數,靈活適配特定業務邏輯(如自定義求和、排序、數據處理函數)。

二、函數的定義與調用流程

1. 自定義函數的定義格式

c運行

// 無返回值無參數
void 函數名() {
    函數體; // 執行特定邏輯,無需return
}

// 無返回值有參數
void 函數名(參數類型1 形參1, 參數類型2 形參2, ...) {
    函數體;
}

// 有返回值有參數
返回值類型 函數名(參數類型1 形參1, 參數類型2 形參2, ...) {
    函數體;
    return 返回值; // 返回值類型需與定義一致
}

2. 函數調用的 3 種方式

  • 直接調用:單獨作為語句執行,適用於無返回值函數或無需使用返回值的場景。示例:printHello();(調用自定義的打印問候語函數)。
  • 表達式調用:將函數調用作為表達式的一部分,使用其返回值參與計算。示例:int result = sum(3, 5);(調用求和函數,返回值賦值給result)。
  • 嵌套調用:在一個函數體內調用另一個函數,實現邏輯分層。示例:int res = sum(sqrt(4), abs(-6));(嵌套調用sqrtabs庫函數,結果作為sum的實參)。

3. 函數聲明(聲明與定義分離)

當函數定義在調用之後(或在其他文件中),需提前聲明函數原型,告知編譯器函數的返回值類型、函數名和參數列表,避免編譯錯誤。格式:返回值類型 函數名(參數類型1, 參數類型2, ...);(形參名可省略)。示例:

c運行

// 函數聲明(提前告知編譯器函數結構)
int sum(int, int); 

int main() {
    int a = 2, b = 3;
    printf("和為:%d\n", sum(a, b)); // 調用函數(此時定義在後面,需聲明)
    return 0;
}

// 函數定義(具體實現)
int sum(int x, int y) {
    return x + y;
}

三、函數參數的傳遞方式

1. 值傳遞(默認傳遞方式)

  • 原理:將實參的值拷貝給形參,形參是獨立變量,修改形參的值不會影響實參。
  • 適用場景:無需修改實參,僅需使用實參的值(如求和、求最大值等)。示例:

c運行

void swap(int x, int y) {
    int temp = x;
    x = y;
    y = temp; // 僅修改形參x、y,實參a、b不受影響
}

int main() {
    int a = 1, b = 2;
    swap(a, b);
    printf("a=%d, b=%d\n", a, b); // 輸出:a=1, b=2(實參未交換)
    return 0;
}

2. 地址傳遞(指針參數)

  • 原理:將實參的內存地址傳遞給形參(指針變量),通過指針間接訪問並修改實參的內存數據,實現 “修改實參” 的需求。
  • 適用場景:需要修改實參的值(如交換兩個變量、修改數組元素等)。示例(修正上面的交換功能):

c運行

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp; // 通過指針修改實參a、b的內存值
}

int main() {
    int a = 1, b = 2;
    swap(&a, &b); // 傳遞實參的地址
    printf("a=%d, b=%d\n", a, b); // 輸出:a=2, b=1(實參已交換)
    return 0;
}

注意:數組作為參數的特殊性

數組名本質是數組首元素的地址,因此數組作為函數參數時,默認是 “地址傳遞”,函數內修改數組元素會直接影響原數組。示例:

c運行

void modifyArray(int arr[], int len) { // 等價於 int *arr
    for (int i = 0; i < len; i++) {
        arr[i] *= 2; // 直接修改原數組元素
    }
}

int main() {
    int arr[] = {1, 2, 3};
    modifyArray(arr, 3); // 傳遞數組名(首元素地址)
    for (int i = 0; i < 3; i++) {
        printf("%d ", arr[i]); // 輸出:2 4 6(原數組已修改)
    }
    return 0;
}

四、函數的高級應用

1. 遞歸函數(函數調用自身)

  • 原理:將複雜問題拆解為與原問題結構一致的子問題,通過遞歸調用逐步簡化,直到觸發 “遞歸終止條件”(避免無限遞歸)。
  • 適用場景:階乘計算、斐波那契數列、樹 / 圖遍歷等。示例(計算 n 的階乘):

c運行

int factorial(int n) {
    if (n == 1) return 1; // 遞歸終止條件
    return n * factorial(n - 1); // 遞歸調用自身,拆解問題
}

int main() {
    printf("5的階乘:%d\n", factorial(5)); // 輸出:120
    return 0;
}

2. 函數指針(指向函數的指針)

  • 原理:函數名是函數的入口地址,函數指針存儲該地址,可通過指針調用函數,實現 “函數回調”(如排序算法中自定義比較規則)。示例:

c運行

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

// 函數指針:指向“返回值為int、參數為兩個int”的函數
int (*funcPtr)(int, int); 

int main() {
    funcPtr = add; // 指針指向add函數
    printf("3+5=%d\n", funcPtr(3, 5)); // 輸出:8(通過指針調用add)
    
    funcPtr = sub; // 指針指向sub函數
    printf("7-2=%d\n", funcPtr(7, 2)); // 輸出:5(通過指針調用sub)
    return 0;
}

3. 多文件編程(函數的分文件組織)

大型項目中,將函數按功能拆分到不同.c文件(源文件),通過.h頭文件聲明函數,實現代碼模塊化管理。示例結構:

  • sum.h(頭文件,聲明函數):int sum(int a, int b);
  • sum.c(源文件,定義函數):#include "sum.h" int sum(int a, int b) { return a + b; }
  • main.c(主文件,調用函數):#include "sum.h" #include <stdio.h> int main() { printf("%d\n", sum(2,3)); return 0; }

五、常見注意事項

  1. 函數名不能重複:同一作用域內,自定義函數名不可與其他函數(含庫函數)重名。
  2. 參數匹配:調用函數時,實參的類型、數量、順序需與形參完全一致,否則會觸發編譯錯誤或隱式類型轉換(可能導致數據異常)。
  3. 遞歸深度:遞歸函數需明確終止條件,避免棧溢出(如階乘計算中 n 過大可能導致棧溢出,可改用迭代實現)。
  4. 指針安全:地址傳遞時,避免傳遞野指針(未初始化的指針)或 NULL 指針,否則會導致程序崩潰。
  5. 頭文件保護:分文件編程時,頭文件需添加 “防止重複包含” 宏(如#ifndef SUM_H #define SUM_H ... #endif),避免編譯衝突。

C 語言函數的核心價值在於 “模塊化複用”,通過合理拆分函數,可讓代碼結構更清晰、維護更便捷,是編寫高效、可靠 C 語言程序的基礎。無論是簡單的小程序,還是複雜的嵌入式系統、工業軟件,函數都是核心組織單元。