個人主頁-愛因斯晨
文章專欄-C語言
引言
最近偷懶了,迷上了三國和李賀。給大家分享一下最喜歡的一句詩:吾不識青天高黃地厚,唯見月寒日暖來煎人壽。我還不是很理解27歲的李賀,如何寫出如此絕筆。
正文開始,今天我們來探討一下關于C語言中的函數部分
一、函數的概念:代碼的 “模塊化” 基石
1.1 函數的定義與意義
- 定義:函數是一段可重復使用的代碼塊,具有輸入(參數)、處理邏輯(函數體)**和**輸出(返回值)。
- 意義:
- 復用性:避免重復編寫相同邏輯(如多次計算最大值,只需調用
max
函數)。 - 可讀性:通過函數名(如
sortArray
)直觀理解功能,降低代碼復雜度。 - 可維護性:修改函數內部邏輯時,只需更新一處,不影響其他調用處。
- 復用性:避免重復編寫相同邏輯(如多次計算最大值,只需調用
1.2 函數的基本結構
返回類型 函數名(參數列表) {// 函數體:實現具體功能return 返回值; // 非void類型必須返回對應類型的值
}
-
示例:計算兩數之和
int add(int a, int b) { // 返回int,參數a、b為intreturn a + b; // 返回和 }
二、庫函數:“開箱即用” 的工具集
關于庫函數和其他語言中封裝的函數在上篇文章中已經講到了,詳情請看[從庫函數到API接口,深挖不同語言背后的“封裝”與“調用”思想-CSDN博客]()
2.1 庫函數的分類與頭文件
-
標準庫:C 語言內置的函數集合,分為:
- 輸入輸出(
stdio.h
):printf
(輸出)、scanf
(輸入)。 - 字符串處理(
string.h
):strlen
(字符串長度)、strcpy
(字符串復制)。 - 數學運算(
math.h
):sqrt
(開平方)、pow
(冪運算)。 - 內存管理(
stdlib.h
):malloc
(動態內存分配)、free
(釋放內存)。
- 輸入輸出(
-
頭文件:包含庫函數的聲明
(告訴編譯器函數的存在、參數和返回值)。使用庫函數前必須包含對應頭文件,例如:
#include <stdio.h> // 包含printf的聲明 int main() {printf("Hello, World!"); // 調用庫函數return 0; }
2.2 庫函數的使用步驟(以 fgets
為例)
-
查閱文檔:
fgets
從文件中讀取字符串,原型為char *fgets(char *s, int size, FILE *stream);
。 -
包含頭文件:
#include <stdio.h>
(fgets
聲明在此頭文件中)。 -
調用函數
#include <stdio.h> int main() {char str[100];fgets(str, 100, stdin); // 從標準輸入(鍵盤)讀取最多99個字符(含'\0')printf("輸入內容:%s", str);return 0; }
-
注意事項:
size
參數需小于數組長度(避免緩沖區溢出)。- 返回值為
NULL
表示讀取失敗(如文件結束)。
三、自定義函數:“按需定制” 的代碼塊
3.1 函數定義的詳細語法
- 返回類型:
void
:無返回值(如僅打印信息的函數)。- 基本類型(
int
、float
等):返回對應類型的值。
- 參數列表:
- 無參數:
void func()
或func()
(C99 后允許省略void
)。 - 有參數:
int add(int a, int b)
(a
、b
為形參,接收實參的值)。
- 無參數:
- 函數體:包含實現邏輯的代碼,可使用
return
提前結束函數(void
函數用return;
)。
3.2 示例:實現 “判斷素數” 函數
#include <stdio.h>
#include <math.h>// 自定義函數:判斷n是否為素數(返回1是,0否)
int isPrime(int n) {if (n <= 1) return 0; // 1及以下不是素數for (int i=2; i<=sqrt(n); i++) { // 優化:只需檢查到平方根if (n % i == 0) return 0; // 能整除,不是素數}return 1; // 是素數
}int main() {int num;printf("輸入一個整數:");scanf("%d", &num);if (isPrime(num)) {printf("%d是素數\n", num);} else {printf("%d不是素數\n", num);}return 0;
}
- 解釋:
- 形參
n
接收實參(用戶輸入的num
)。 - 通過循環判斷是否有因數,提前返回結果(提高效率)。
- 形參
四、形參和實參:“值的傳遞與拷貝”
4.1 實參(實際參數)
- 定義:調用函數時傳遞的具體值或變量(如
isPrime(num)
中的num
)。 - 特點:
- 可以是常量(
isPrime(7)
)、變量(isPrime(num)
)、表達式(isPrime(2+3)
)。 - 傳遞方式:值傳遞(形參是實參的拷貝,修改形參不影響實參,除非傳遞地址)。
- 可以是常量(
4.2 形參(形式參數)
- 定義:函數定義時占位的參數(如
isPrime(int n)
中的n
)。 - 特點:
- 函數調用時分配內存,調用結束后釋放(形參是臨時變量)。
- 值傳遞本質:形參是實參的副本(如
n
是num
的拷貝,修改n
不影響num
)。
4.3 地址傳遞(突破值傳遞限制)
// 交換兩數(通過地址傳遞,修改實參)
void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}int main() {int x=10, y=20;swap(&x, &y); // 實參是x、y的地址(傳遞指針)printf("x=%d, y=%d\n", x, y); // 輸出x=20, y=10(實參被修改)return 0;
}
- 解釋:
- 形參
*a
、*b
接收實參的地址(&x
、&y
),通過解引用(*a
)直接修改原變量的值。 - 這是 值傳遞的特殊情況(傳遞地址,實現 “引用傳遞” 效果)。
- 形參
五、return 語句:“函數的出口與結果”
5.1 return 的兩種用法
- 返回值:給調用者一個結果(如
return a + b;
返回和)。 - 結束函數:提前退出函數(如
void
函數中的return;
,跳過后續代碼)。
5.2 規則與示例
-
void 函數
void printMessage() {printf("Hello!\n");return; // 可省略(函數體結束自動返回) }
-
非 void 函數
int max(int a, int b) {if (a > b) return a; // 返回a,結束函數return b; // 必有一個執行(確保返回值) }
-
錯誤處理
int divide(int a, int b) {if (b == 0) {printf("除數不能為0!\n");return -1; // 錯誤碼(調用者根據返回值判斷是否出錯)}return a / b; }
六、數組作為函數參數:“傳遞指針與內存”
6.1 數組傳參的本質
-
數組名作為參數時,傳遞的是首元素的地址(即指針),函數內對數組的修改會影響原數組(因為操作同一塊內存)。
-
語法
void printArray(int arr[], int size) { // 等價于int *arrfor (int i=0; i<size; i++) {printf("%d ", arr[i]); // 等價于*(arr+i)} }
6.2 示例:數組排序(冒泡排序)
#include <stdio.h>void bubbleSort(int arr[], int size) {for (int i=0; i<size-1; i++) {for (int j=0; j<size-i-1; j++) {if (arr[j] > arr[j+1]) { // 交換相鄰元素int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}
}int main() {int nums[] = {5, 3, 8, 1, 2};int size = sizeof(nums) / sizeof(nums[0]); // 計算數組長度bubbleSort(nums, size); // 傳遞數組名(首地址)和長度for (int i=0; i<size; i++) {printf("%d ", nums[i]); // 輸出1 2 3 5 8(原數組已排序)}return 0;
}
- 注意:函數無法自動獲取數組長度(需手動傳遞
size
),因為形參arr
是指針(丟失長度信息)。
七、函數調用:嵌套與鏈式
7.1 嵌套調用(函數內調用其他函數)
void printHeader() {printf("===== 歡迎使用系統 =====\n");
}void printMenu() {printHeader(); // 嵌套調用printHeaderprintf("1. 登錄\n2. 注冊\n3. 退出\n");
}int main() {printMenu(); // 輸出:===== 歡迎使用系統 ===== → 菜單選項return 0;
}
7.2 鏈式訪問(函數返回值作為參數)
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }int main() {// 先算add(2,3)=5,再算mul(5,4)=20(鏈式調用)int result = mul(add(2, 3), 4); printf("結果:%d\n", result); // 輸出20return 0;
}
八、函數的聲明與定義:“多文件開發”
8.1 單個文件中的聲明
-
定義在前:直接調用(無需聲明)。
-
定義在后:需先聲明(告訴編譯器函數存在)。
int add(int, int); // 聲明(參數名可省略,只寫類型) int main() {int res = add(3,5); // 調用時,編譯器通過聲明知道add存在return 0; } int add(int a, int b) { return a + b; } // 定義在后
8.2 多文件開發(模塊化)
-
步驟:
-
創建頭文件(
func.h
):聲明函數#ifndef FUNC_H #define FUNC_H int add(int a, int b); // 聲明 #endif
-
創建源文件(
func.c
):定義函數#include "func.h" // 包含頭文件(雙引號表示當前目錄) int add(int a, int b) { return a + b; } // 定義
-
主文件(
main.c
):調用函數#include <stdio.h> #include "func.h" // 包含頭文件,獲取聲明 int main() {printf("%d\n", add(3,5)); // 調用func.c中的addreturn 0; }
-
-
編譯:需同時編譯
main.c
和func.c
(如gcc main.c func.c -o main
)。
8.3 static 關鍵字:“限制作用域”
-
靜態函數(static 修飾函數)
-
作用:僅當前文件可見(其他文件無法調用,避免命名沖突)。
-
示例(
func.c
)static int add(int a, int b) { return a + b; } // main.c調用會報錯(未定義)
-
-
靜態變量
-
靜態局部變量(函數內)
生命周期為程序運行期(保留值,如計數器)。
void count() {static int num = 0; // 第一次調用初始化,后續保留值num++;printf("第%d次調用\n", num); } // 調用:count() → 第1次,count() → 第2次(num保留1)
-
靜態全局變量(文件內,函數外):僅當前文件可見(同文件內函數可訪問,其他文件不可見)。
-