寧波商城網(wǎng)站建設淘寶指數(shù)查詢官網(wǎng)
目錄
9.1 函數(shù)的概念
9.2 庫函數(shù)
9.2.1 標準庫與庫函數(shù)
示例:常見庫函數(shù)
9.2.2?標準庫與頭文件的關(guān)系
?參考資料和學習工具
如何使用庫函數(shù)
?編輯
9.3 ?定義函數(shù)
9.3.1 函數(shù)的語法形式
9.3.2函數(shù)的舉例
9.4 實參與形參
9.4.1 什么是實參?
9.4.2 什么是形參?
9.4.3 實參和形參的關(guān)系
9.4.4?實參與形參的傳遞方式
9.5 return語句
9.5.1?return 后可以是數(shù)值或表達式
9.5.2?return 語句可以沒有返回值
9.5.3 返回值類型自動轉(zhuǎn)換
9.5.4?return 語句結(jié)束函數(shù)執(zhí)行
9.5.5 分支語句中的 return 要確保每條路徑都返回
9.5.6. 總結(jié)
9.6 數(shù)組做函數(shù)參數(shù)
9.6.1 傳遞數(shù)組的注意事項
9.6.2 設計 set_arr 和 print_arr 函數(shù)
9.7 嵌套調(diào)?和鏈式訪問
9.7.1 嵌套調(diào)用
9.7.2 鏈式訪問
9.8 函數(shù)的聲明和定義
9.8.1 單文件中的函數(shù)聲明和定義
示例:判斷閏年
當函數(shù)定義在調(diào)用之后
函數(shù)聲明的使用
總結(jié)
?9.8.2 多個文件
代碼模塊化的重要性
示例:多文件結(jié)構(gòu)中的函數(shù)聲明與定義
文件 1:add.c(源文件)
文件 2:add.h(頭文件)
文件 3:test.c(主程序文件)
編譯與鏈接
9.8.3 static 和 extern
作用域和生命周期
9.8.3.1 static 修飾局部變量
代碼示例 1:未使用 static?修飾的局部變量
代碼示例 2:使用 static 修飾的局部變量
9.8.3.2 static 修飾全局變量
代碼示例 1:未使用 static 修飾的全局變量
代碼示例 2:使用 static 修飾的全局變量
9.8.3.3 static 修飾函數(shù)
代碼示例 1:未使用 static 修飾的函數(shù)
代碼示例 2:使用 static 修飾的函數(shù)
小結(jié)
9.1 函數(shù)的概念
? 在數(shù)學中,函數(shù)是一種根據(jù)輸入值得到輸出結(jié)果的關(guān)系,例如:一次函數(shù)
y = kx + b
中,k
和b
是常數(shù),給定任意x
值,我們可以計算出相應的y
值。? ?在C語言中,函數(shù)(function)的概念類似,也被稱為子程序。函數(shù)是一小段代碼,專門用于完成特定任務。C語言的程序其實就是由許多這樣的函數(shù)組合而成的。通過將大任務拆分成小任務,每個小任務由一個函數(shù)完成,代碼不僅更易管理,而且可以重復使用,提高了開發(fā)效率。
在C語言中,我們會遇到兩類函數(shù):
1.庫函數(shù)
2.自定義函數(shù)
9.2 庫函數(shù)
9.2.1 標準庫與庫函數(shù)
C語言的標準定義了一系列語法規(guī)則,但并不提供具體的庫函數(shù)實現(xiàn)。為了讓程序員能夠方便地實現(xiàn)常見的功能,國際標準ANSI C規(guī)定了一些常用的函數(shù),稱為標準庫。標準庫中的函數(shù)由不同的編譯器廠商根據(jù)ANSI C標準實現(xiàn),這些函數(shù)統(tǒng)稱為庫函數(shù)。
示例:常見庫函數(shù)
我們之前學到的 printf
和 scanf
就是典型的庫函數(shù)。它們已經(jīng)被實現(xiàn)好了,程序員只需學習并使用這些函數(shù),而不必自己去實現(xiàn)相關(guān)功能。庫函數(shù)不僅提升了開發(fā)效率,還保證了功能的質(zhì)量和執(zhí)行效率。
9.2.2?標準庫與頭文件的關(guān)系
?參考資料和學習工具
要深入了解庫函數(shù)及其對應的頭文件,可以參考以下資源:
- C/C++ 官方文檔?https://zh.cppreference.com/w/c/header
- cplusplus.com:?ssC library - C++ Reference
不同的庫函數(shù)被根據(jù)其功能分配到不同的頭文件中。每個頭文件中聲明了相關(guān)的函數(shù)、類型等信息。舉例來說:
1.數(shù)學相關(guān)的庫函數(shù)聲明在 math.h
中。
2.字符串處理相關(guān)的庫函數(shù)聲明在 string.h
中
如何使用庫函數(shù)
要使用庫函數(shù),需要先包含相應的頭文件。例如:
#include <math.h> // 記得包含對應的頭文件 double sqrt(double x);
- 函數(shù)名:
sqrt
- 參數(shù):
x
,類型為double
,表示輸入一個浮點數(shù)。 - 返回值類型:
double
,表示函數(shù)計算的結(jié)果也是一個浮點數(shù)。
在使用時,只需傳入一個 double
類型的參數(shù),函數(shù)會返回該數(shù)的平方根。
實踐
#include <stdio.h>
#include <math.h>int main()
{double d = 16.0;double r = sqrt(d);printf("%lf\n", r);return 0;
}
9.3 ?定義函數(shù)
9.3.1 函數(shù)的語法形式
ret_type fun_name(形式參數(shù))
{
}
9.3.2函數(shù)的舉例
#include <stdio.h>
int main()
{int x = 0;int y = 0;int r;scanf("%d%d", &x, &y);r = x + y;printf("x+y=%d", r);return 0;}

#include <stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int x = 0;int y = 0;int r;scanf("%d%d", &x, &y);r = Add(x, y);printf("x+y=%d", r);return 0;}
9.4 實參與形參
在編寫和使用函數(shù)的過程中,參數(shù)的概念至關(guān)重要。我們將參數(shù)分為兩類:實際參數(shù)(實參) 和 形式參數(shù)(形參)。了解它們的區(qū)別和聯(lián)系是掌握函數(shù)調(diào)用機制的關(guān)鍵。
9.4.1 什么是實參?
實參指的是在函數(shù)調(diào)用時,傳遞給函數(shù)的實際值。這些值可以是變量,也可以是常量。實參是真實存在的,它們占用內(nèi)存空間,并且在函數(shù)調(diào)用時將值傳遞給形參。
讓我們看下面的例子:
#include <stdio.h>int Add(int x, int y) {int z = x + y;return z;
}int main() {int a, b;scanf("%d %d", &a, &b);int result = Add(a, b);printf("Result: %d\n", result);return 0;
}
在這個例子中,a
和 b
是實參。當我們調(diào)用 Add(a, b)
時,a
和 b
的值(由用戶輸入)被傳遞給 Add
函數(shù)。這里的 a
和 b
是實際參與計算的數(shù)值。
9.4.2 什么是形參?
形參指的是在函數(shù)定義中,用于接收實參的變量。形參只是一個占位符,表示將來在函數(shù)調(diào)用時,實參的值將傳遞給它們。形參在函數(shù)定義中不會實際占用內(nèi)存,只有當函數(shù)被調(diào)用時,形參才會被實例化,占用內(nèi)存以存放實參的值。
繼續(xù)看上面的代碼:
int Add(int x, int y) {int z = x + y;return z;
}
?這里的 x
和 y
就是形參。它們在函數(shù)被調(diào)用之前只是名義上的變量,并沒有具體的值。當我們調(diào)用 Add(a, b)
時,a
和 b
的值分別被傳遞給 x
和 y
,此時形參才真正生效,開始參與計算。
9.4.3 實參和形參的關(guān)系
實參和形參的關(guān)系類似于值的拷貝。實參的值傳遞給形參,但它們在內(nèi)存中是獨立的。這意味著,形參只是實參的副本,函數(shù)內(nèi)部對形參的修改不會影響到實參的值。
我們可以通過調(diào)試工具清晰地看到,形參 x
和 y
的地址與實參 a
和 b
的地址是不一樣的。這說明形參和實參分配了不同的內(nèi)存空間。例如:
#include <stdio.h>int Add(int x, int y) {printf("Address of x: %p\n", (void*)&x);printf("Address of y: %p\n", (void*)&y);return x + y;
}int main() {int a = 5, b = 10;printf("Address of a: %p\n", (void*)&a);printf("Address of b: %p\n", (void*)&b);Add(a, b);return 0;
}
?運行結(jié)果中會顯示 x
和 y
的地址不同于 a
和 b
,這驗證了形參與實參是獨立的
9.4.4?實參與形參的傳遞方式
在C語言中,函數(shù)的參數(shù)傳遞通常是值傳遞。也就是說,函數(shù)接收到的是實參的值,而不是實參本身。這種傳遞方式確保了實參的安全性,因為無論函數(shù)內(nèi)部如何修改形參,實參的值都不會受到影響。
然而,如果我們希望函數(shù)能夠直接修改實參的值,可以使用指針進行參數(shù)傳遞。指針傳遞的是變量的內(nèi)存地址,這使得函數(shù)可以訪問并修改原始數(shù)據(jù)。例如:
void Add(int* x, int* y) {*x += *y;
}int main() {int a = 5, b = 10;Add(&a, &b);printf("New value of a: %d\n", a);return 0;
}
?在這個例子中,通過傳遞 a
和 b
的地址,Add
函數(shù)能夠修改 a
的值。
9.5 return語句
9.5.1?return
后可以是數(shù)值或表達式
return
語句可以返回一個具體的數(shù)值,也可以返回一個表達式的結(jié)果。如果是表達式,首先會計算該表達式的值,然后將結(jié)果作為函數(shù)的返回值。例如:
int Add(int x, int y) {return x + y; // 返回表達式x + y的結(jié)果
}
在上面的例子中,x + y
是一個表達式,它的結(jié)果會先被計算出來,然后通過 return
返回給調(diào)用函數(shù)。表達式的使用使得代碼更加簡潔靈活,支持動態(tài)計算和條件返回。
9.5.2?return
語句可以沒有返回值
在返回類型為 void
的函數(shù)中,return
語句后面可以什么都不寫。這種用法表明函數(shù)不需要返回任何值,僅僅是提前結(jié)束函數(shù)的執(zhí)行流程:
void PrintMessage() {printf("Hello, World!\n");return; // 直接結(jié)束函數(shù),不返回值
}
當函數(shù)的返回類型為 void
時,return
可以省略,也可以寫 return;
明確結(jié)束函數(shù)。這種用法常見于處理控制流程的函數(shù),比如顯示消息、執(zhí)行某些操作但不需要反饋結(jié)果的函數(shù)。
9.5.3 返回值類型自動轉(zhuǎn)換
當函數(shù)的返回值類型與 return
語句返回的類型不一致時,編譯器會進行隱式類型轉(zhuǎn)換。例如,如果函數(shù)的返回類型是 double
,但 return
返回一個 int
值,系統(tǒng)會自動將 int
轉(zhuǎn)換為 double
。雖然這種隱式轉(zhuǎn)換可以避免類型不匹配的編譯錯誤,但程序員應該謹慎使用,以避免潛在的精度損失或不必要的性能消耗。
double GetArea(int radius) {return 3.14 * radius * radius; // radius是int類型,但返回類型是double,自動轉(zhuǎn)換
}
盡管這種轉(zhuǎn)換通常不會引發(fā)錯誤,但如果數(shù)據(jù)類型的差異較大(例如從 double
轉(zhuǎn)換為 int
),可能會丟失重要的信息或精度,因此推薦確保返回值類型與函數(shù)的聲明一致。
9.5.4?return
語句結(jié)束函數(shù)執(zhí)行
一旦 return
語句被執(zhí)行,函數(shù)將立即停止運行,后續(xù)的代碼將不再執(zhí)行。對于需要根據(jù)條件終止函數(shù)的場景,return
是一種非常有效的手段。例如:
int CheckPositive(int num) {if (num < 0) {return -1; // 如果num為負數(shù),提前返回}return 1; // 否則返回正數(shù)
}
在這個例子中,return -1
使得函數(shù)在檢測到負數(shù)時立刻返回,而不執(zhí)行后續(xù)的代碼。這種邏輯控制方式在避免不必要的計算和提高效率方面非常有效。
9.5.5 分支語句中的 return
要確保每條路徑都返回
在使用 if
、switch
等條件分支時,應該確保函數(shù)在每種可能的情況下都有返回值。否則,編譯器可能會拋出編譯錯誤,因為某些路徑可能導致函數(shù)未返回任何值。
例如,下面的代碼會出錯:
int Max(int a, int b) {if (a > b) {return a;}// 如果a <= b,沒有返回值,編譯器會報錯
}
正確的寫法是
int Max(int a, int b) {if (a > b) {return a;} else {return b;}
}
或者更加簡潔的寫法
int Max(int a, int b) {return (a > b) ? a : b;
}
這種寫法確保在所有情況下都有返回值,避免潛在的編譯錯誤。
9.5.6. 總結(jié)
return
可以返回數(shù)值或表達式的結(jié)果,返回前會先計算表達式。- 對于
void
類型的函數(shù),return
可以沒有返回值,或簡單結(jié)束函數(shù)執(zhí)行。- 返回值類型不一致時,系統(tǒng)會自動進行隱式類型轉(zhuǎn)換,但應注意潛在的精度損失。
return
語句一旦執(zhí)行,函數(shù)的剩余代碼不再運行。- 在分支語句中,確保所有路徑都有返回值,避免編譯錯誤。
9.6 數(shù)組做函數(shù)參數(shù)
在使用函數(shù)解決問題時,通常會將數(shù)組作為參數(shù)傳遞給函數(shù),從而可以在函數(shù)內(nèi)部對數(shù)組進行操作。比如,寫一個函數(shù)將整型數(shù)組的所有元素設置為 -1
,再寫一個函數(shù)打印數(shù)組的內(nèi)容。
下面是這個程序的基本結(jié)構(gòu):
#include <stdio.h>int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};set_arr(arr, 10); // 將數(shù)組內(nèi)容設置為 -1print_arr(arr, 10); // 打印數(shù)組內(nèi)容return 0;
}
9.6.1 傳遞數(shù)組的注意事項
為了能夠操作數(shù)組,我們需要將數(shù)組作為參數(shù)傳遞給 set_arr
函數(shù),同時為了遍歷數(shù)組,還需要知道數(shù)組的元素個數(shù)。因此,我們需要向 set_arr
函數(shù)傳遞兩個參數(shù):一個是數(shù)組本身,另一個是數(shù)組的元素個數(shù)。對于 print_arr
函數(shù),也是同樣的道理。
#include <stdio.h>int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int sz = sizeof(arr) / sizeof(arr[0]); // 計算數(shù)組元素個數(shù)set_arr(arr, sz); // 設置數(shù)組內(nèi)容為 -1print_arr(arr, sz); // 打印數(shù)組內(nèi)容return 0;
}
9.6.2 設計 set_arr
和 print_arr
函數(shù)
要實現(xiàn)這兩個函數(shù),首先需要了解數(shù)組傳參的幾個重點知識:
- 函數(shù)的形式參數(shù)要和實際參數(shù)的個數(shù)匹配。
- 當實參是數(shù)組時,形參可以寫成數(shù)組形式。
- 如果形參是一維數(shù)組,數(shù)組大小可以省略。
- 如果形參是二維數(shù)組,可以省略行數(shù),但列數(shù)不能省略。
- 數(shù)組傳參時,形參不會創(chuàng)建新的數(shù)組。
- 形參操作的數(shù)組和實參的數(shù)組是同一個數(shù)組。
根據(jù)這些要點,我們可以實現(xiàn)如下兩個函數(shù):
設置數(shù)組內(nèi)容為 -1 的函數(shù)
void set_arr(int arr[], int sz) {for(int i = 0; i < sz; i++) {arr[i] = -1;}
}
打印數(shù)組內(nèi)容的函數(shù)
void print_arr(int arr[], int sz) {for(int i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}
這段代碼展示了如何將數(shù)組作為參數(shù)傳遞給函數(shù),并在函數(shù)內(nèi)部對數(shù)組進行操作。
9.7 嵌套調(diào)?和鏈式訪問
在編程中,函數(shù)之間的互相調(diào)用就像積木拼接一樣,多個函數(shù)組合起來可以實現(xiàn)復雜的功能。這種互相調(diào)用可以分為嵌套調(diào)用和鏈式訪問。接下來,我們來詳細探討這兩個概念。
9.7.1 嵌套調(diào)用
嵌套調(diào)用指的是一個函數(shù)內(nèi)部調(diào)用另一個函數(shù)。通過多個函數(shù)的協(xié)同工作,可以解決較為復雜的問題。比如,我們可以設計兩個函數(shù)來計算某一年某月的天數(shù):
is_leap_year()
:根據(jù)年份判斷是否為閏年。get_days_of_month()
:調(diào)用is_leap_year()
判斷是否為閏年,再根據(jù)月份計算天數(shù)。
示例代碼如下:
#include <stdio.h>int is_leap_year(int y) {if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) {return 1; // 閏年返回1}return 0; // 平年返回0
}int get_days_of_month(int y, int m) {int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int day = days[m]; // 獲取該月天數(shù)if (is_leap_year(y) && m == 2) {day += 1; // 如果是閏年的2月,多加1天}return day;
}int main() {int y, m;scanf("%d %d", &y, &m); // 輸入年份和月份int d = get_days_of_month(y, m); // 計算該月天數(shù)printf("%d\n", d); // 打印天數(shù)return 0;
}
在這段代碼中,main
函數(shù)調(diào)用了 scanf()
、printf()
以及 get_days_of_month()
,而 get_days_of_month()
函數(shù)內(nèi)部又調(diào)用了 is_leap_year()
。通過函數(shù)嵌套調(diào)用,我們可以逐步解決問題。
注意:雖然函數(shù)可以嵌套調(diào)用,但 C 語言中不允許函數(shù)嵌套定義。
9.7.2 鏈式訪問
鏈式訪問是指一個函數(shù)的返回值直接作為另一個函數(shù)的參數(shù)。通過這種方式,可以將多個函數(shù)像鏈條一樣連接起來,簡化代碼邏輯。
例如:
#include <stdio.h>int main() {int len = strlen("abcdef"); // 計算字符串長度printf("%d\n", len); // 打印長度return 0;
}
如果將 strlen()
的返回值直接作為 printf()
的參數(shù),代碼可以進一步簡化,變?yōu)殒準皆L問的形式:
#include <stdio.h>int main() {printf("%d\n", strlen("abcdef")); // 鏈式訪問return 0;
}
鏈式訪問中的有趣現(xiàn)像來看一個有趣的例子:
#include <stdio.h>int main() {printf("%d", printf("%d", printf("%d", 43)));return 0;
}
這里的關(guān)鍵在于理解 printf()
的返回值。printf()
函數(shù)的返回值是成功打印的字符個數(shù)。
分析這個例子:
- 最內(nèi)層的
printf("%d", 43)
打印了數(shù)字43
,字符數(shù)為 2,因此返回值是2
。 - 中間的
printf("%d", 2)
打印返回的字符數(shù)2
,字符數(shù)為 1,因此返回值是1
。 - 最外層的
printf("%d", 1)
打印返回的字符數(shù)1
,字符數(shù)為 1。
最終屏幕上會打印 4321
。
通過嵌套調(diào)用和鏈式訪問,我們可以編寫更加靈活、高效的代碼,同時也增強了代碼的可讀性與擴展性。這兩者的結(jié)合讓函數(shù)在程序設計中如同樂高積木,能夠創(chuàng)造出復雜而精妙的程序結(jié)構(gòu)。
9.8 函數(shù)的聲明和定義
在C語言中,函數(shù)的聲明和定義是編寫可維護代碼的基礎之一。我們常見的情況是將函數(shù)的定義直接寫在函數(shù)調(diào)用之前,這種方式能夠確保編譯器在編譯過程中可以順利找到該函數(shù)。但在更復雜的場景下,我們需要將函數(shù)的聲明和定義分開,這不僅能夠提升代碼的可讀性,還能讓我們更靈活地組織代碼。
9.8.1 單文件中的函數(shù)聲明和定義
函數(shù)聲明和函數(shù)定義是兩個密切相關(guān)的概念。函數(shù)定義提供了函數(shù)的完整實現(xiàn),包括邏輯和功能的具體代碼,而函數(shù)聲明則提前告訴編譯器函數(shù)的名稱、返回類型和參數(shù)類型。這樣做的好處是,無論函數(shù)定義在文件中的什么位置,編譯器都能夠識別并正確處理函數(shù)調(diào)用。
示例:判斷閏年
以下是一個函數(shù)用于判斷給定年份是否為閏年:
#include <stdio.h>// 判斷一年是否是閏年(函數(shù)定義)
int is_leap_year(int y) {if(((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) {return 1;} else {return 0;}
}int main() {int y = 0;scanf("%d", &y);int r = is_leap_year(y);if (r == 1) {printf("閏年\n");} else {printf("非閏年\n");}return 0;
}
在上面的代碼中,橙色部分為函數(shù)的定義,綠色部分為函數(shù)的調(diào)用。函數(shù)的定義位于調(diào)用之前,編譯器能夠順利找到is_leap_year
函數(shù),并正常編譯運行。
當函數(shù)定義在調(diào)用之后
如果我們將函數(shù)定義放在main
函數(shù)的后面,如下
#include <stdio.h>int main() {int y = 0;scanf("%d", &y);int r = is_leap_year(y); // 調(diào)用is_leap_year函數(shù)if (r == 1) {printf("閏年\n");} else {printf("非閏年\n");}return 0;
}// 判斷一年是否是閏年(函數(shù)定義)
int is_leap_year(int y) {if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) {return 1;} else {return 0;}
}
在編譯過程中,編譯器在處理到is_leap_year
函數(shù)調(diào)用時,并沒有找到其定義,可能會拋出警告甚至錯誤提示。為了解決這個問題,我們需要在函數(shù)調(diào)用之前聲明函數(shù),這樣編譯器就能提前知道函數(shù)的存在。
函數(shù)聲明的使用
函數(shù)聲明的格式非常簡單,只需要告知編譯器函數(shù)的返回類型、函數(shù)名以及參數(shù)的類型(參數(shù)名可以省略)。下面是改進后的代碼:
#include <stdio.h>// 函數(shù)聲明
int is_leap_year(int y);int main() {int y = 0;scanf("%d", &y);int r = is_leap_year(y); // 調(diào)用is_leap_year函數(shù)if (r == 1) {printf("閏年\n");} else {printf("非閏年\n");}return 0;
}// 判斷一年是否是閏年(函數(shù)定義)
int is_leap_year(int y) {if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) {return 1;} else {return 0;}
}
通過在函數(shù)調(diào)用前添加聲明,編譯器在處理函數(shù)調(diào)用時,就能夠識別該函數(shù)的存在,即使實際的定義在后面,這樣就能避免編譯器的報錯。
總結(jié)
- 在函數(shù)調(diào)用之前進行函數(shù)聲明是確保編譯器能夠順利編譯代碼的關(guān)鍵。
- 函數(shù)聲明只需包含函數(shù)的返回類型、名稱和參數(shù)類型,參數(shù)名可以省略。
- 將函數(shù)定義放在調(diào)用之前也可以,但如果定義在調(diào)用之后,則一定要在調(diào)用前進行聲明。
?9.8.2 多個文件
?在實際的企業(yè)開發(fā)中,程序規(guī)模通常較大,不可能將所有代碼都集中在一個文件中。為了提高代碼的可維護性和可擴展性,我們常常根據(jù)功能對代碼進行模塊化處理,將其拆分到多個文件中。函數(shù)的聲明、類型定義等通常存放在頭文件(.h
),而具體的函數(shù)實現(xiàn)則存放在源文件(.c
)。這種分離有助于代碼的復用、維護與管理。
代碼模塊化的重要性
在復雜系統(tǒng)中,代碼模塊化不僅有助于功能的分離,還可以使團隊協(xié)作更加順暢。通過合理地將代碼分散在多個文件中,開發(fā)者可以專注于各自負責的模塊,減少了代碼沖突和維護困難。通常,我們會將函數(shù)的聲明和類型定義集中到頭文件(.h
)中,而將函數(shù)的具體實現(xiàn)保留在源文件(.c
)中。這樣一來,其他文件只需要通過包含頭文件,就能輕松調(diào)用相關(guān)功能,而不必關(guān)心函數(shù)的具體實現(xiàn)細節(jié)。
示例:多文件結(jié)構(gòu)中的函數(shù)聲明與定義
假設我們有一個簡單的加法函數(shù),該函數(shù)的實現(xiàn)與調(diào)用分別位于不同的文件中:
文件 1:add.c
(源文件)
// 函數(shù)的定義
int Add(int x, int y) {return x + y;
}
在這個源文件中,函數(shù) Add
實現(xiàn)了兩個整數(shù)相加的功能。源文件中只包含函數(shù)的具體實現(xiàn)細節(jié)。
文件 2:add.h
(頭文件)
// 函數(shù)的聲明
int Add(int x, int y);
頭文件 add.h
中只包含函數(shù)的聲明,它告訴編譯器該函數(shù)存在,并提供了函數(shù)的名稱、返回類型和參數(shù)類型。頭文件起到了接口的作用,方便其他文件引用。
文件 3:test.c
(主程序文件)
#include <stdio.h>
#include "add.h" // 包含頭文件int main() {int a = 10;int b = 20;// 調(diào)用Add函數(shù)int c = Add(a, b);printf("%d\n", c);return 0;
}
test.c
是我們的主程序文件,它通過包含頭文件 add.h
,成功調(diào)用了 Add
函數(shù)。此時,主程序并不需要關(guān)心 Add
函數(shù)的具體實現(xiàn),而是依賴于頭文件提供的聲明。編譯器在鏈接階段會將 add.c
中的實現(xiàn)與 test.c
中的調(diào)用結(jié)合起來。
編譯與鏈接
在這種多文件的結(jié)構(gòu)中,編譯過程分為多個步驟:
- 編譯:每個
.c
文件分別編譯為目標文件(.o
或.obj
)。 - 鏈接:編譯器將這些目標文件鏈接在一起,生成最終的可執(zhí)行文件。
以常見的 gcc
編譯器為例,編譯命令如下:
gcc -c add.c // 將add.c編譯為目標文件add.o
gcc -c test.c // 將test.c編譯為目標文件test.o
gcc add.o test.o -o program // 將目標文件鏈接為可執(zhí)行文件program
?通過這種方式,我們可以輕松管理多個文件之間的依賴關(guān)系。
多文件同時還可以是適當?shù)碾[藏代碼,若我們完成一個代碼功能的實現(xiàn),現(xiàn)在要被其其他人使用,我們可以通過靜態(tài)庫的方式,使他人只能使用其功能,而不能看到源代碼
例如下面是一個Add函數(shù),我么可以將加法函數(shù)的代碼轉(zhuǎn)換成靜態(tài)庫文件
點擊項目名稱
右鍵選擇屬性?
?
在常規(guī)中選擇配置類型,選擇靜態(tài)庫?
?在項目文件中會生成一個X64文件,點擊里面的debug,里面后有一個Add.lib
?在代碼中可以就直接引用Add.lib文件,實現(xiàn)相應的代碼功能
#include <stdio.h>
#include "add.h" // 包含頭文件
#pragma comment(lib,"Add.lib")
int main() {int a = 10;int b = 20;// 調(diào)用Add函數(shù)int c = Add(a, b);printf("%d\n", c);return 0;
}
9.8.3 static 和 extern
在C語言中,static
和 extern
是兩個非常重要的關(guān)鍵字,分別用于控制變量和函數(shù)的作用域(scope)與鏈接屬性(linkage)。理解這兩個關(guān)鍵字的作用,對于編寫高質(zhì)量、模塊化的代碼至關(guān)重要。
在深入討論 static
和 extern
之前,我們需要先了解兩個重要的概念:作用域和生命周期。
作用域和生命周期
- 作用域(scope):定義了變量或函數(shù)在程序中可見的范圍,即在哪些代碼區(qū)域可以訪問到該變量或函數(shù)。
- 局部變量的作用域僅限于其所在的代碼塊或函數(shù)內(nèi)部。
- 全局變量的作用域則擴展至整個程序,即所有源文件都能訪問到它。
- 生命周期(lifetime):指的是變量從創(chuàng)建(內(nèi)存分配)到銷毀(內(nèi)存回收)之間的時間段。
- 局部變量的生命周期在進入其作用域時開始,離開作用域時結(jié)束。
- 全局變量的生命周期貫穿整個程序的執(zhí)行過程,直到程序結(jié)束。
9.8.3.1 static 修飾局部變量
通過 static
關(guān)鍵字,我們可以改變局部變量的生命周期。來看下面的兩個代碼示例:
代碼示例 1:未使用 static
?修飾的局部變量
#include <stdio.h>void test() {int i = 0; // 每次進入函數(shù)時重新創(chuàng)建并初始化i++;printf("%d ", i);
}int main() {for (int i = 0; i < 5; i++) {test(); // 調(diào)用5次}return 0;
}
?輸出結(jié)果:
?在這個例子中,test
函數(shù)中的局部變量 i
在每次進入函數(shù)時都會重新創(chuàng)建并初始化為0,因此每次調(diào)用函數(shù)時,i
的值都會重新開始累加。
代碼示例 2:使用 static
修飾的局部變量
#include <stdio.h>void test() {static int i = 0; // 僅在第一次調(diào)用時初始化i++;printf("%d ", i);
}int main() {for (int i = 0; i < 5; i++) {test(); // 調(diào)用5次}return 0;
}
輸出結(jié)果?
在這個例子中,i
變量被 static
修飾,生命周期被擴展到整個程序的執(zhí)行期間。即使離開 test
函數(shù),i
也不會被銷毀,下一次進入函數(shù)時,i
的值將保留并繼續(xù)累加。
結(jié)論:static
修飾局部變量后,變量的存儲位置從棧區(qū)轉(zhuǎn)移到靜態(tài)存儲區(qū),生命周期從局部函數(shù)的作用域擴展到整個程序執(zhí)行期。這樣我們可以保留變量的值,即使函數(shù)多次調(diào)用,也能繼續(xù)使用上次的計算結(jié)果。
使用建議:當需要局部變量在函數(shù)退出后保持其值,下次進入函數(shù)時繼續(xù)使用時,建議使用 static
修飾該變量。
9.8.3.2 static 修飾全局變量
全局變量默認具有外部鏈接屬性,可以在其他源文件中通過 extern
關(guān)鍵字聲明并使用。但是,使用 static
修飾全局變量后,該變量的鏈接屬性會變?yōu)?strong>內(nèi)部鏈接屬性,只能在定義它的源文件中使用。
代碼示例 1:未使用 static
修飾的全局變量
add.c
文件:
int g_val = 2018; // 全局變量
test.c
文件:
#include <stdio.h>extern int g_val; // 聲明外部變量int main() {printf("%d\n", g_val); // 輸出2018return 0;
}
在這個例子中,全局變量 g_val
可以在 test.c
文件中通過 extern
關(guān)鍵字進行引用。
代碼示例 2:使用 static
修飾的全局變量
add.c
文件:
static int g_val = 2018; // 靜態(tài)全局變量
test.c
文件:
#include <stdio.h>extern int g_val; // 嘗試聲明外部變量int main() {printf("%d\n", g_val); // 鏈接錯誤return 0;
}
在這個例子中,由于 g_val
被 static
修飾,其鏈接屬性被限制為內(nèi)部鏈接,因此無法在其他源文件中通過 extern
聲明使用,編譯時會出現(xiàn)鏈接錯誤。
結(jié)論:static
修飾全局變量后,該變量只能在定義它的源文件中使用,其他文件無法通過 extern
進行訪問。
使用建議:當一個全局變量只需要在定義它的源文件中使用時,可以使用 static
修飾,以避免其他文件誤用該變量,確保數(shù)據(jù)的封裝性和安全性。
9.8.3.3 static 修飾函數(shù)
與全局變量類似,函數(shù)默認具有外部鏈接屬性,可以在其他源文件中通過 extern
聲明調(diào)用。然而,當函數(shù)被 static
修飾后,鏈接屬性變?yōu)?strong>內(nèi)部鏈接屬性,該函數(shù)只能在定義它的源文件中使用。
代碼示例 1:未使用 static
修飾的函數(shù)
add.c
文件:
int Add(int x, int y) {return x + y;
}
test.c
文件:?
#include <stdio.h>extern int Add(int x, int y); // 聲明外部函數(shù)int main() {printf("%d\n", Add(2, 3)); // 輸出5return 0;
}
在這個例子中,Add
函數(shù)可以在 test.c
文件中通過 extern
關(guān)鍵字進行引用。
代碼示例 2:使用 static
修飾的函數(shù)
add.c
文件:
static int Add(int x, int y) {return x + y;
}
test.c
文件:?
#include <stdio.h>extern int Add(int x, int y); // 聲明外部函數(shù)int main() {printf("%d\n", Add(2, 3)); // 鏈接錯誤return 0;
}
?
由于 Add
函數(shù)被 static
修飾,其鏈接屬性變?yōu)閮?nèi)部鏈接,因此無法在其他源文件中通過 extern
聲明調(diào)用,編譯時會出現(xiàn)鏈接錯誤。
結(jié)論:static
修飾函數(shù)后,該函數(shù)只能在定義它的源文件中調(diào)用,其他文件無法引用該函數(shù)。
使用建議:當一個函數(shù)只需要在定義它的源文件中使用時,可以使用 static
修飾,以避免函數(shù)暴露給外部文件,確保代碼模塊化和安全性。
小結(jié)
static
和extern
關(guān)鍵字在C語言中用于控制變量和函數(shù)的作用域與鏈接屬性。通過合理地使用這些關(guān)鍵字,我們可以有效地控制代碼的可見性與數(shù)據(jù)的封裝性,提升程序的安全性和可維護性。在實際開發(fā)中,理解并合理應用static
和extern
是編寫高效、模塊化代碼的重要基礎。