六、函數(shù)
本章專(zhuān)題脈絡(luò)

1、函數(shù)的基本使用
1.1 為什么需要函數(shù)

《街霸》游戲中,每次人物出拳、出腳或跳躍等動(dòng)作都需要編寫(xiě)50-80行的代碼,在每次出拳、出腳或跳躍的地方都需要重復(fù)地編寫(xiě)這50-80行代碼,這樣程序會(huì)變得很臃腫,可讀性也非常差。為了解決代碼重復(fù)編寫(xiě)的問(wèn)題,可以將出拳、出腳或跳躍的代碼提取出來(lái)放在一個(gè){}中,并為這段代碼起個(gè)名字,這樣在每次的出拳、出腳或跳躍的地方通過(guò)這個(gè)名字來(lái)調(diào)用這個(gè){}的代碼就可以了。
提取出來(lái)的代碼可以看作是程序中定義的一個(gè)
函數(shù),程序在需要出拳、出腳或跳躍時(shí)調(diào)用該函數(shù)即可。

-
將特定功能的代碼封裝為函數(shù)的好處:
實(shí)現(xiàn)代碼重用,減少冗余,簡(jiǎn)化代碼。 -
一個(gè)C源程序可以由一個(gè)或多個(gè)源文件構(gòu)成(C文件擴(kuò)展名是“.c”),一個(gè)源文件是一個(gè)編譯單位。一個(gè)源文件可以由若干個(gè)函數(shù)構(gòu)成,函數(shù)之間可以相互調(diào)用。也就是說(shuō),
函數(shù)是C程序基本的組成單位。

練習(xí):
C語(yǔ)言主要是借助以下( )功能來(lái)實(shí)現(xiàn)程序模塊化的。
A.定義函數(shù)
B.定義常量和外部變量
C.三種基本結(jié)構(gòu)語(yǔ)句
D.豐富的數(shù)據(jù)類(lèi)型【答案】A
【解析】C程序的模塊化主要通過(guò)函數(shù)來(lái)實(shí)現(xiàn)。C語(yǔ)言允許對(duì)函數(shù)單獨(dú)進(jìn)行編譯,從而可以實(shí)現(xiàn)模塊化。
1.2 函數(shù)的分類(lèi)
角度1:從程序執(zhí)行的角度看
- 主函數(shù):main()函數(shù)
- 子函數(shù):非main()函數(shù)
每個(gè)C應(yīng)用程序只有一個(gè),且必須有一個(gè)main()主函數(shù)。無(wú)論主函數(shù)寫(xiě)在什么位置,C程序總是從main()函數(shù)開(kāi)始執(zhí)行。main()函數(shù)可以調(diào)用其它的子函數(shù),子函數(shù)之間可以相互調(diào)用任意多次。
角度2:是否允許源文件外調(diào)用角度看
- 內(nèi)部函數(shù)
- 外部函數(shù)
角度3:從用戶使用的角度看
① 庫(kù)函數(shù)(或標(biāo)準(zhǔn)函數(shù))
它是由C系統(tǒng)提供的,用戶不必自己定義,可直接使用它們。注意,不同的C語(yǔ)言編譯系統(tǒng)提供的庫(kù)函數(shù)的數(shù)量和功能會(huì)有一些不同,但是一些基本的函數(shù)是共同的。比如:
- 字符串操作函數(shù)
- 字符操作函數(shù)
- 時(shí)間/日期函數(shù)
- 數(shù)學(xué)函數(shù)
- IO函數(shù)
- 內(nèi)存操作函數(shù)
- 其它庫(kù)函數(shù)
使用庫(kù)函數(shù),必須包含
#include對(duì)應(yīng)的頭文件。
② 用戶自己定義的函數(shù)
它是用以解決用戶特定業(yè)務(wù)需求的函數(shù)。
1.3 函數(shù)的聲明格式
函數(shù)定義的格式:
返回值類(lèi)型 函數(shù)名(數(shù)據(jù)類(lèi)型1 形參1,數(shù)據(jù)類(lèi)型2 形參2,…,數(shù)據(jù)類(lèi)型n 形參n){
函數(shù)體;
}
舉例:
//計(jì)算兩個(gè)整數(shù)的和,并返回
int add(int m,int n) {
return m + n;
}
//計(jì)算兩個(gè)整數(shù)的較大值,并返回
int max(int a, int b){ //定義函數(shù)max()
int c;
c = a > b ? a : b; //求a,b兩個(gè)數(shù)的最大值,賦給c
return c; //將最大值返回
}
void printMax(int x,int y){
int z;
z = x > y ? x : y; //求x,y兩個(gè)數(shù)的最大值,賦給z
printf("%d\n",z);
}
具體說(shuō)明:
1) 返回值類(lèi)型
函數(shù)調(diào)用后,是否需要在主調(diào)函數(shù)(比如main()函數(shù))中得到一個(gè)確定的、返回的值,針對(duì)這個(gè)返回值的描述,就是返回值類(lèi)型。返回值常常是一個(gè)計(jì)算的結(jié)果,或是用來(lái)作為判斷函數(shù)執(zhí)行狀態(tài)(完成還是出錯(cuò))的標(biāo)記。
函數(shù)按是否有返回值來(lái)分類(lèi)的話,分為:
無(wú)返回值的類(lèi)型:針對(duì)函數(shù)無(wú)返回值或明確不需返回值的情況,使用void(即空類(lèi)型)表示。- 舉例:輸出函數(shù) void printf(const char *format, ...)。
有返回值的類(lèi)型:指明具體的類(lèi)型。比如, int、float、char 等。如果省略,默認(rèn)為int類(lèi)型。- 有返回值類(lèi)型,則需要在函數(shù)體內(nèi)與“
return 返回值”搭配使用。返回值需要與返回值類(lèi)型一致。 - 舉例:int rand(),調(diào)用后返回一個(gè)隨機(jī)整數(shù)
- 有返回值類(lèi)型,則需要在函數(shù)體內(nèi)與“
#include <stdio.h>
#include <stdlib.h>
void printHello() {
printf("Hello!\n");
}
int getRandomNumber() {
return rand() % 100;
}
//在main()中調(diào)用printHello()、getRandomNumber(),相對(duì)于這兩個(gè)方法,main()稱(chēng)為主調(diào)函數(shù)
int main() {
//調(diào)用printHello()
printHello();
//調(diào)用getRandomNumber()
int randomNumber = getRandomNumber();
printf("Random number: %d\n", randomNumber);
return 0;
}
特殊的:如果返回值類(lèi)型非 void,但被調(diào)函數(shù)中沒(méi)有 return 語(yǔ)句,函數(shù)會(huì)返回一個(gè)不確定的值。
int test(){
printf("hello");
//return 12;
}
int main(){
int i = test();
printf("%d\n",i);
return 0;
}
運(yùn)行結(jié)果:

2) 函數(shù)名
函數(shù)名,屬于標(biāo)識(shí)符。要遵循標(biāo)識(shí)符的命名規(guī)則,同時(shí)要見(jiàn)名知意,以增強(qiáng)程序的可讀性。
3) 參數(shù)列表
函數(shù)名后面的圓括號(hào)里面,可以聲明參數(shù)的類(lèi)型和參數(shù)名。表示完成函數(shù)體功能時(shí)需要外部提供的數(shù)據(jù)列表。
根據(jù)是否有參數(shù),函數(shù)可以分為:
無(wú)參函數(shù),在調(diào)用無(wú)參函數(shù)時(shí),主調(diào)函數(shù)不向被調(diào)用函數(shù)傳遞數(shù)據(jù)。但函數(shù)名后的()不能省略。- 舉例:abort():立即終止程序的執(zhí)行,不接受任何形參。
有參函數(shù),在調(diào)用函數(shù)時(shí),主調(diào)函數(shù)在調(diào)用被調(diào)用函數(shù)時(shí),通過(guò)參數(shù)向被調(diào)用函數(shù)傳遞數(shù)據(jù)。- 函數(shù)參數(shù)為多個(gè)參數(shù)時(shí),其間用逗號(hào)隔開(kāi)。
- 舉例:add(int m,int n),strcmp(const char *str1, const char *str2)
//打印5行6列的*型矩形
void print(){
for(int i = 0;i < 5;i++){
for(int j = 0;j < 6;j++){
printf("*");
}
printf("\n");
}
}
//打印m行n列的*型矩形
void printGraph(int m,int n){
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
printf("*");
}
printf("\n");
}
}
int main() {
// print();
printGraph(5,10);
}
4) 函數(shù)體
函數(shù)體要寫(xiě)在大括號(hào){}里面,是函數(shù)被調(diào)用后要執(zhí)行的代碼。
對(duì)于調(diào)用者來(lái)說(shuō),不了解函數(shù)體如何實(shí)現(xiàn)的,并不影響函數(shù)的使用。
5) 關(guān)于return 語(yǔ)句
- return語(yǔ)句的作用:① 結(jié)束函數(shù)的執(zhí)行 ②將函數(shù)運(yùn)算的結(jié)果返回。
- return語(yǔ)句后面就不能再寫(xiě)其它代碼了,否則會(huì)報(bào)錯(cuò)。(與break、continue情況類(lèi)似)
- 下面分兩種情況討論:
- 情況1:返回值類(lèi)型不是void時(shí),函數(shù)體中必須保證一定有
return 返回值;語(yǔ)句,并且要求該返回值結(jié)果的類(lèi)型與聲明的返回值類(lèi)型一致或兼容。 - 情況2:返回值類(lèi)型是void時(shí),函數(shù)體中可以沒(méi)有return語(yǔ)句。如果要用return語(yǔ)句提前結(jié)束函數(shù)的執(zhí)行,那么return后面不能跟返回值,直接寫(xiě)
return;就可以。
- 情況1:返回值類(lèi)型不是void時(shí),函數(shù)體中必須保證一定有
#include <stdio.h>
int addInt(int m ,int n){
int sum = m + n;
return sum;
}
double addDouble(double d1,double d2){
double sum = d1 + d2;
return sum;
}
void printNum(int start,int limit){
for(int i = start;i <= limit;i++){
if(i % 5 == 0){
return;
//printf("今天買(mǎi)彩票,一定能中獎(jiǎng)");
}
printf("i = %d\n", i);
}
printf("over!\n", i);
}
int main() {
int result = addInt(10,20);
printf("result = %d\n",result);
printNum(1,20);
}
類(lèi)比舉例:

1.4 聲明注意事項(xiàng)
1、C程序中的所有函數(shù)都是互相獨(dú)立的。一個(gè)函數(shù)并不從屬于另一個(gè)函數(shù),即函數(shù)不能嵌套定義。
//錯(cuò)誤演示
int func1(int a,int b){ //第1個(gè)函數(shù)的定義
...
int func2(int c,int d){ //第2個(gè)函數(shù)的定義
...
}
...
}
2、同一個(gè)程序中函數(shù)不能重名,函數(shù)名用來(lái)唯一標(biāo)識(shí)一個(gè)函數(shù)。即在標(biāo)準(zhǔn)的 C 語(yǔ)言中,并不支持函數(shù)的重載。

什么是函數(shù)的重載?
函數(shù)的重載是一種編程語(yǔ)言特性,像C++、Java等語(yǔ)言都支持。它允許在同一個(gè)作用域內(nèi)(比如同一個(gè)"類(lèi)"中)定義多個(gè)函數(shù)名相同但參數(shù)列表不同(即參數(shù)個(gè)數(shù)不同或參數(shù)類(lèi)型不同)的函數(shù)。此時(shí)的多個(gè)函數(shù)彼此構(gòu)成重載。
調(diào)用時(shí),編譯器會(huì)根據(jù)傳遞的參數(shù)類(lèi)型和數(shù)量來(lái)確定調(diào)用哪個(gè)函數(shù)。
如果想在 C 中模擬函數(shù)的重載有兩種方式。不過(guò),都不如 C++ 中的函數(shù)重載那樣靈活和方便。
- 方式1:使用不同的函數(shù)名來(lái)區(qū)分不同的函數(shù)
- 方式2:為函數(shù)添加后綴來(lái)表示不同的版本。
void multiply(int m){
int result = m * m;
printf("結(jié)果為:%d\n",result);
}
void multiply1(int m,int n){
int result = m * n;
printf("結(jié)果為:%d\n",result);
}
【華南理工大學(xué)2018研】在C語(yǔ)言中,當(dāng)函數(shù)的返回值缺省時(shí),表示該函數(shù)返回值的類(lèi)型是( )。
A.char
B.float
C.long
D.int【答案】D
【解析】在C語(yǔ)言中,當(dāng)函數(shù)的返回值缺省時(shí),函數(shù)返回值的類(lèi)型默認(rèn)為int型。
【華南理工大學(xué)2018研】以下敘述中不正確的是( )。
A.在不同的函數(shù)中可以使用相同名字的變量
B.函數(shù)中的形式參數(shù)是局部變量
C.在一個(gè)函數(shù)內(nèi)定義的變量只能在本函數(shù)范圍內(nèi)有效
D.在一個(gè)函數(shù)的復(fù)合語(yǔ)句中定義的變量在本函數(shù)范圍內(nèi)有效【答案】D
【解析】在一個(gè)函數(shù)的復(fù)合語(yǔ)句中定義的變量只在該復(fù)合語(yǔ)句中有效。
void func(){
int i = 10;
{
int j = 10;
//
}
//不能調(diào)用j
}
【四川大學(xué)2017研】已定義如下函數(shù):
fun(int *p){ return *p; }該函數(shù)的返回值是( )。
A.不確定的值
B.形參p中存放的值
C.形參p所指存儲(chǔ)單元中的值
D.形參p的地址值【答案】C
【解析】p是一個(gè)指向int型的指針變量,*p表示的是p所指向內(nèi)存存放的變量,是一個(gè)int型,所以return *p表示返回p所指存儲(chǔ)單元中的值,答案選C。
【華南理工大學(xué)2018研】下列函數(shù)的功能是( )。
int fun1(char *x) { char *y = x; while (*y++); return (y - x - 1); }A.求字符串的長(zhǎng)度
B.比較兩個(gè)字符串的大小
C.將字符串X復(fù)制到字符串Y
D.將字符串X連接到字符串Y后【答案】A
【解析】while后面的表達(dá)式是指針依次遍歷直到碰到\0,此時(shí)y指向字符串最后一個(gè)元素的后一個(gè)位置,但是由于y++,因此y會(huì)繼續(xù)后移一位,而x指向字符串的頭部,后面的y-x-1顯然是用于計(jì)算字符串的長(zhǎng)度。
1.5 函數(shù)的調(diào)用
調(diào)用函數(shù)時(shí),需要傳入實(shí)際的參數(shù)值。如果沒(méi)有參數(shù),只要在函數(shù)名后面加上圓括號(hào)就可以了。
舉例1:
函數(shù)的聲明
void func() {
printf("這是我的第一個(gè)函數(shù)!\n");
}
函數(shù)的調(diào)用
int main() {
func();
//func(10); // 報(bào)錯(cuò)
//func(10,20); // 報(bào)錯(cuò)
return 0; //程序正常結(jié)束,默認(rèn)返回0
}

舉例2:
void func(int x, int y) {
int sum = x + y;
printf("x+y=%d\n", sum);
}
int main() {
func(3, 5);
return 0;
}

舉例3:
int func(int x, int y) {
return x + y;
}
int main() {
int sum = func(3, 5);
printf("x+y=%d\n", sum);
return 0;
}

說(shuō)明:
1、調(diào)用時(shí),參數(shù)個(gè)數(shù)必須與函數(shù)聲明里的參數(shù)個(gè)數(shù)一致,參數(shù)過(guò)多或過(guò)少都會(huì)報(bào)錯(cuò)。
2、函數(shù)間可以相互調(diào)用,但不能調(diào)用main函數(shù),因?yàn)閙ain函數(shù)是被操作系統(tǒng)調(diào)用的,作為程序的啟動(dòng)入口。反之,main() 函數(shù)可以調(diào)用其它函數(shù)。
3、函數(shù)的參數(shù)和返回值類(lèi)型,會(huì)根據(jù)需要進(jìn)行自動(dòng)類(lèi)型轉(zhuǎn)換。
void func(int num){ printf("%d\n",num); } char func1() { int a = 65; return a; //變量a的類(lèi)型由int自動(dòng)轉(zhuǎn)換為char了。 } int main() { //函數(shù)體 short b = 10; func(b); char c = func1(); printf("%c\n",c); }
1.6 練習(xí)
練習(xí)1:
① 編寫(xiě)程序,聲明一個(gè)print1()函數(shù),在函數(shù)中打印一個(gè)10*8的*型矩形。
② 編寫(xiě)程序,聲明一個(gè)print2()函數(shù),除打印一個(gè)10*8的*型矩形外,再計(jì)算該矩形的面積,并將其作為函數(shù)返回值。
③ 編寫(xiě)程序,聲明一個(gè)print3()函數(shù),函數(shù)提供m和n兩個(gè)參數(shù),函數(shù)中打印一個(gè)m*n的*型矩形,并計(jì)算該矩形的面積, 將其作為返回值。
void print1(){
for(int i = 0;i < 10;i++){
for(int j = 0;j < 8;j++){
printf("* ");
}
printf("\n");
}
}
int print2(){
for(int i = 0;i < 10;i++){
for(int j = 0;j < 8;j++){
printf("* ");
}
printf("\n");
}
return 10 * 8;
}
int print3(int m,int n){
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
printf("* ");
}
printf("\n");
}
return m * n;
}
練習(xí)2:定義函數(shù)max(),求兩個(gè)double型變量的最大值,求三個(gè)double型變量的最大值
#include <stdio.h>
double dualMax(double x, double y) {
return x > y ? x : y;
}
double triMax(double x, double y, double z) {
return dualMax(dualMax(x, y), z); //嵌套調(diào)用
}
int main() {
double a, b, c;
printf("a,b,c: ");
scanf("%lf,%lf,%lf", &a, &b, &c);
printf("%.2lf和%.2lf的最大值是:%.2lf\n",a,b,dualMax(a, b));
printf("%.2lf、%.2lf、%.2lf的最大值是:%.2lf\n",a,b,c,triMax(a,b,c));
return 0;
}
練習(xí)3:定義求和函數(shù)getSum,求 1+2+…+n,并返回結(jié)果
#include<stdio.h>
int getSum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
int main() {
int n = 100;
int result = getSum(n);
printf("1+2+...+%d = %d\n", n, result);
}
練習(xí)4:哥德巴赫猜想
任一大于 2 的偶數(shù)都可寫(xiě)成兩個(gè)素?cái)?shù)之和。利用判斷素?cái)?shù)的函數(shù) prime()驗(yàn)證哥德巴赫猜想。
#include <stdio.h>
#include <math.h>
int prime(int n) { //判斷素?cái)?shù)函數(shù)
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0)
return 0;
}
return 1;
}
int main() {
int num;
printf("請(qǐng)輸入一個(gè)大于2的偶數(shù):");
scanf("%d", &num);
for (int i = 2; i <= num / 2; i++) {
if (prime(i) && prime(num - i)) {
printf("%d = %d + %d\n", num, i, num - i);
break;
}
}
return 0;
}
練習(xí)5:
編寫(xiě)一組函數(shù),用于處理日期和時(shí)間相關(guān)的操作。使用這些函數(shù),可以進(jìn)行日期和時(shí)間相關(guān)的計(jì)算。
① 編寫(xiě)一個(gè)函數(shù) char *getWeekName(int week),該函數(shù)接收一個(gè)代表星期的整數(shù)(1表示星期一,7表示星期日),并返回相應(yīng)的星期名稱(chēng)。如果輸入無(wú)效,則返回空字符串。
② 編寫(xiě)一個(gè)函數(shù) char *getMonthName(int month),該函數(shù)接收一個(gè)代表月份的整數(shù)(1表示一月,12表示十二月),并返回相應(yīng)的月份名稱(chēng)。如果輸入無(wú)效,則返回空字符串。
③ 編寫(xiě)一個(gè)函數(shù) int isLeapYear(int year),該函數(shù)接收一個(gè)年份,并檢查它是否為閏年。如果是閏年,則返回1,否則返回0。如果輸入年份為負(fù)數(shù),則返回-1作為錯(cuò)誤標(biāo)志。
提示:能被4整除但不能被100整除,或者能被400整除的年份,即為閏年。
④ 編寫(xiě)一個(gè)函數(shù) int getTotalDaysOfMonth(int year, int month),該函數(shù)接收一個(gè)年份和月份,并返回指定月份的總天數(shù)。如果輸入無(wú)效,則返回-1作為錯(cuò)誤標(biāo)志。
⑤ 編寫(xiě)一個(gè)函數(shù) int getTotalDaysOfYear(int year),該函數(shù)接收一個(gè)年份,并返回指定年份的總天數(shù)。如果輸入無(wú)效,則返回-1作為錯(cuò)誤標(biāo)志。
模板:
// 函數(shù):getWeekName
// 描述:根據(jù)輸入的星期數(shù),返回相應(yīng)的星期名稱(chēng)
// 參數(shù):week - 代表星期的整數(shù)(1表示星期一,7表示星期日)
// 返回值:返回表示星期名稱(chēng)的字符串,如果輸入無(wú)效則返回空字符串
char *getWeekName(int week) {
}
// 函數(shù):getMonthName
// 描述:根據(jù)輸入的月份數(shù),返回相應(yīng)的月份名稱(chēng)
// 參數(shù):month - 代表月份的整數(shù)(1表示一月,12表示十二月)
// 返回值:返回表示月份名稱(chēng)的字符串,如果輸入無(wú)效則返回空字符串
char *getMonthName(int month) {
}
// 函數(shù):isLeapYear
// 描述:檢查輸入的年份是否為閏年
// 參數(shù):year - 待檢查的年份
// 返回值:如果是閏年則返回1,否則返回0,如果輸入為負(fù)數(shù)則返回-1作為錯(cuò)誤標(biāo)志
//提示:能被4整除但不能被100整除,或者能被400整除的年份,即為閏年。
int isLeapYear(int year) {
}
// 函數(shù):getTotalDaysOfMonth
// 描述:獲取指定年份和月份的總天數(shù)
// 參數(shù):year - 年份,month - 月份
// 返回值:返回該月份的總天數(shù),如果輸入無(wú)效則返回-1作為錯(cuò)誤標(biāo)志
int getTotalDaysOfMonth(int year, int month) {
}
// 函數(shù):getTotalDaysOfYear
// 描述:獲取指定年份的總天數(shù)
// 參數(shù):year - 年份
// 返回值:返回該年份的總天數(shù),如果輸入無(wú)效則返回-1作為錯(cuò)誤標(biāo)志
int getTotalDaysOfYear(int year) {
}
完整代碼:
#include <stdio.h>
// 函數(shù):getWeekName
// 描述:根據(jù)輸入的星期數(shù),返回相應(yīng)的星期名稱(chēng)
// 參數(shù):week - 代表星期的整數(shù)(1表示星期一,7表示星期日)
// 返回值:返回表示星期名稱(chēng)的字符串,如果輸入無(wú)效則返回空字符串
char *getWeekName(int week) {
switch (week) {
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
case 7:
return "Sunday";
}
return "";
}
// 函數(shù):getMonthName
// 描述:根據(jù)輸入的月份數(shù),返回相應(yīng)的月份名稱(chēng)
// 參數(shù):month - 代表月份的整數(shù)(1表示一月,12表示十二月)
// 返回值:返回表示月份名稱(chēng)的字符串,如果輸入無(wú)效則返回空字符串
char *getMonthName(int month) {
char *months[] = {"January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"};
if (month >= 1 && month <= 12) {
return months[month - 1];
}
return "";
}
// 函數(shù):isLeapYear
// 描述:檢查輸入的年份是否為閏年
// 參數(shù):year - 待檢查的年份
// 返回值:如果是閏年則返回1,否則返回0,如果輸入為負(fù)數(shù)則返回-1作為錯(cuò)誤標(biāo)志
// 提示:能被4整除但不能被100整除,或者能被400整除的年份,即為閏年。
int isLeapYear(int year) {
if (year >= 0) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}
return -1; // 輸入年份為負(fù)數(shù),返回錯(cuò)誤標(biāo)志
}
// 函數(shù):getTotalDaysOfMonth
// 描述:獲取指定年份和月份的總天數(shù)
// 參數(shù):year - 年份,month - 月份
// 返回值:返回該月份的總天數(shù),如果輸入無(wú)效則返回-1作為錯(cuò)誤標(biāo)志
int getTotalDaysOfMonth(int year, int month) {
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (isLeapYear(year)) {
days[1]++; // 閏年2月有29天
}
if (month >= 1 && month <= 12) {
return days[month - 1];
}
return -1; // 輸入月份無(wú)效,返回錯(cuò)誤標(biāo)志
}
// 函數(shù):getTotalDaysOfYear
// 描述:獲取指定年份的總天數(shù)
// 參數(shù):year - 年份
// 返回值:返回該年份的總天數(shù),如果輸入無(wú)效則返回-1作為錯(cuò)誤標(biāo)志
int getTotalDaysOfYear(int year) {
int result = isLeapYear(year);
switch(result){
case 1:
return 366;
case 0:
return 365;
}
return -1;
}
int main() {
// 在主函數(shù)中使用這些函數(shù)的示例代碼
int result = getTotalDaysOfMonth(2023, 2);
printf("Total days in February 2023: %d\n", result);
char *monthName = getMonthName(4);
printf("Month 4: %s\n", monthName);
return 0;
}
【武漢科技大學(xué)2019研】函數(shù)fun和實(shí)參數(shù)組的聲明形式為:void fun(char ch,float x[]); float a[5];
以下對(duì)函數(shù)的調(diào)用語(yǔ)句中,正確的是( )。
A.fun("a",a[]);
B.fun('D',a);
C.fun('65',2.8);
D.fun(32,a[5]);【答案】B
【解析】調(diào)用函數(shù)fun需要傳入兩個(gè)實(shí)參,第一個(gè)實(shí)參的類(lèi)型是char字符型,第二個(gè)實(shí)參的類(lèi)型是float數(shù)組型,只有選項(xiàng)B滿足條件。其中C項(xiàng)'65'本身有語(yǔ)法錯(cuò)誤,D項(xiàng)在傳遞數(shù)組時(shí)只需要數(shù)組名即可。
【武漢科技大學(xué)2019研】設(shè)有定義int a[3][3];和函數(shù)調(diào)用語(yǔ)句sort(a,3);則正確的函數(shù)聲明是( )。
A.void sort(int a,n);
B.void sort(int a[][],int n);
C.void sort(int a[][3],int n);
D.void sort(int a[][3],n);【答案】C
【解析】根據(jù)函數(shù)調(diào)用語(yǔ)句可以知道sort函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)的類(lèi)型是一個(gè)二維int型數(shù)組,第二個(gè)參數(shù)是一個(gè)int型數(shù)據(jù),所以A錯(cuò)誤;B中因?yàn)槎S數(shù)組一定要指定列數(shù),B錯(cuò)誤;D中形參n沒(méi)有指定數(shù)據(jù)類(lèi)型,錯(cuò)誤;答案選C。
2、進(jìn)一步認(rèn)識(shí)函數(shù)
2.1 關(guān)于main()
main()的作用
C 語(yǔ)言規(guī)定, main() 是程序的入口函數(shù),即所有的程序一定要包含一個(gè) main() 函數(shù)。程序總是從這個(gè)函數(shù)開(kāi)始執(zhí)行,如果沒(méi)有該函數(shù),程序就無(wú)法啟動(dòng)。
main()函數(shù)可以調(diào)用其它函數(shù),但其它函數(shù)不能反過(guò)來(lái)調(diào)用main()函數(shù)。main()函數(shù)也不能調(diào)用自己。
main() 的一般格式
int main() {
//函數(shù)體(略)
return 0;
}
C 語(yǔ)言約定:返回值 0 表示函數(shù)運(yùn)行成功;返回其它非零整數(shù)值,表示運(yùn)行失敗,代碼出了問(wèn)題。系統(tǒng)根據(jù) main() 的返回值,作為整個(gè)程序的返回值,確定程序是否運(yùn)行成功。
正常情況下,如果 main() 里面省略 return 0 這一行,編譯器會(huì)自動(dòng)加上,即 main() 的默認(rèn)返回值為0。所以,也可以聲明如下:
int main() {
//函數(shù)體(略)
}
注意,C 語(yǔ)言只會(huì)對(duì) main() 函數(shù)默認(rèn)添加返回值,對(duì)其它函數(shù)不會(huì)這樣做,所以建議書(shū)寫(xiě)時(shí)保留 return 語(yǔ)句,以便形成統(tǒng)一的代碼風(fēng)格。
main()函數(shù)的其它寫(xiě)法
main()的聲明中可以帶有兩個(gè)參數(shù),格式如下。
int main(int argc, char *argv[]) {
//函數(shù)體
}
其中,形參argc,全稱(chēng)是argument count,表示傳給程序的參數(shù)個(gè)數(shù),其值至少是1;而argv,全稱(chēng)是argument value,argv[]則是指向字符串的指針數(shù)組。
這種方式可以通過(guò)命令行的方式,接收指定的字符串傳給參數(shù)argv。舉例:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("argc = %d\n",argc);
//函數(shù)體
for(int i = 0;i < argc;i++){
printf("%s\n",argv[i]);
}
return 0;
}
命令行執(zhí)行參考:

或者

練習(xí):
以下敘述中錯(cuò)誤的是( )。
A.一個(gè)C程序可以包含多個(gè)不同名的函數(shù)
B.一個(gè)C程序只能有一個(gè)主函數(shù)
C.C程序在書(shū)寫(xiě)時(shí),有嚴(yán)格的縮進(jìn)要求,否則不能編譯通過(guò)
D.C程序的主函數(shù)必須用main作為函數(shù)名【答案】C
【解析】一個(gè)C程序有且只有一個(gè)主函數(shù)main。一個(gè)C程序可以包含多個(gè)不同名字的子函數(shù)。C程序在書(shū)寫(xiě)時(shí)沒(méi)有嚴(yán)格的縮進(jìn)要求。答案選擇C選項(xiàng)。
【北京航空航天大學(xué)2018研】已知有以下sample.c程序的定義:
/*sample.c*/ #include <stdio.h> int main(int argc,char *argv[]){ printf("%c",*++argv[2]); return 0; }將該程序編譯成可執(zhí)行文件sample后,若在命令行下輸入如下命令:
sample January February March
則該命令正確的輸出是( )。
A.J
B.a(chǎn)
C.F
D.e【答案】D
【解析】根據(jù)命令行的輸入可知,四個(gè)字符串賦給相應(yīng)的指針數(shù)組,所以argv[2]表示第三個(gè)字符串February,再進(jìn)行++操作,指針指向字符串的第二個(gè)字符e,最后進(jìn)行取值操作,結(jié)果為e,答案為D。
2.2 關(guān)于exit()
exit() 函數(shù)用來(lái)終止整個(gè)程序的運(yùn)行。一旦執(zhí)行到該函數(shù),程序就會(huì)立即結(jié)束。該函數(shù)的原型定義在頭文件 stdlib.h 里面。
exit() 可以向程序外部返回一個(gè)值,它的參數(shù)就是程序的返回值。一般來(lái)說(shuō),使用兩個(gè)常量作為它的參數(shù),這兩個(gè)常量也是定義在 stdlib.h 里面:
-
EXIT_SUCCESS (相當(dāng)于 0)表示程序運(yùn)行成功,正常結(jié)束;
-
EXIT_FAILURE (相當(dāng)于 1)表示程序異常中止。
// 程序運(yùn)行成功
// 等同于 exit(0);
exit(EXIT_SUCCESS);
// 程序異常中止
// 等同于 exit(1);
exit(EXIT_FAILURE);
在main()函數(shù)結(jié)束時(shí)也會(huì)隱式地調(diào)用exit()函數(shù),exit() 等價(jià)于使用 return 語(yǔ)句。其它函數(shù)使用 exit() ,就是終止整個(gè)程序的運(yùn)行,沒(méi)有其它作用。
C 語(yǔ)言還提供了一個(gè) atexit() 函數(shù),用來(lái)登記 exit() 執(zhí)行時(shí)額外執(zhí)行的函數(shù),用來(lái)做一些退出程序時(shí)的收尾工作。該函數(shù)的原型也是定義在頭文件 stdlib.h 。
int atexit(void (*func)(void));
atexit() 的參數(shù)是一個(gè)函數(shù)指針。注意,它的參數(shù)函數(shù)(下例的 print )不能接受參數(shù),也不能有返回值。
void print(void) {
printf("something wrong!\n");
}
atexit(print);
exit(EXIT_FAILURE);
上例中, exit() 執(zhí)行時(shí)會(huì)先自動(dòng)調(diào)用 atexit() 注冊(cè)的 print() 函數(shù),然后再終止程序。
2.3 函數(shù)原型
函數(shù)必須先聲明,后使用。由于程序總是先運(yùn)行 main() 函數(shù),所以其它函數(shù)都必須在main()之前聲明。
#include <stdio.h>
void func1() {
//...
}
void func2() {
//...
}
int main() {
func1();
func2();
return 0;
}
上面代碼中, main() 函數(shù)必須在最后聲明,否則編譯時(shí)會(huì)產(chǎn)生警告,找不到 func1() 或 func2() 的聲明。
#include <stdio.h>
int main() {
func1(); //報(bào)錯(cuò)
func2(); //報(bào)錯(cuò)
return 0;
}
void func1() {
//...
}
void func2() {
//...
}
對(duì)于函數(shù)較多的程序,保證每個(gè)函數(shù)的順序正確,會(huì)變得很麻煩。C 語(yǔ)言提供的解決方法是,只要在程序開(kāi)頭處給出函數(shù)原型,函數(shù)就可以先使用、后聲明。
int add(int, int); //函數(shù)原型
int main() {
int m = 10, n = 20;
int sum = add(m, n);
printf("sum = %d\n", sum);
}
int add(int num1, int num2) {
return num1 + num2;
}
所謂函數(shù)原型(function prototype),就是函數(shù)在調(diào)用前提前告訴編譯器每個(gè)函數(shù)的基本信息(它包括了返回值類(lèi)型、函數(shù)名、參數(shù)個(gè)數(shù)、參數(shù)類(lèi)型和參數(shù)順序),其它信息都不需要(不用包括函數(shù)體、參數(shù)名),函數(shù)具體的實(shí)現(xiàn)放在哪里,就不重要了。在函數(shù)調(diào)用時(shí),檢查函數(shù)原型和函數(shù)聲明是否一致,只要一致就可以正確編譯、調(diào)用。
函數(shù)原型中包括參數(shù)名也可以,雖然這樣對(duì)于編譯器是多余的,但是閱讀代碼時(shí),有助于理解函數(shù)的意圖。
int add(int,int);
// 等同于
int add(int num1,int num2);
舉例:
#include <stdio.h>
// 函數(shù)原型
int add(int, int);
int main() {
int num1 = 5;
int num2 = 3;
int result = add(num1, num2); // 調(diào)用函數(shù)
printf("Sum: %d\n", result);
return 0;
}
// 函數(shù)定義
int add(int a, int b) {
return a + b;
}
有時(shí),也會(huì)看到如下的寫(xiě)法。雖然理論上可以在 main() 函數(shù)的首行放置函數(shù)原型,但這不是推薦的做法。
#include <stdio.h>
int main() {
int add(int, int); // 函數(shù)原型
int num1 = 5;
int num2 = 3;
int result = add(num1, num2); // 調(diào)用函數(shù)
printf("Sum: %d\n", result);
return 0;
}
// 函數(shù)定義
int add(int a, int b) {
return a + b;
}
在C語(yǔ)言中,通常在main()函數(shù)之前或是程序源碼文件的開(kāi)頭,給出當(dāng)前腳本使用的所有函數(shù)的原型。以確保在main()函數(shù)內(nèi)部調(diào)用其他函數(shù)時(shí)編譯器已經(jīng)了解這些函數(shù)的信息。
3、參數(shù)傳遞機(jī)制
3.1 復(fù)習(xí):函數(shù)內(nèi)變量的傳遞
int main() {
//情況1:針對(duì)于基本數(shù)據(jù)類(lèi)型的變量,將變量的值傳遞過(guò)去
int a = 10;
int b = a;
printf("a = %d\n",a);
b = 20;
printf("a = %d\n",a);
//情況2:針對(duì)于數(shù)組,將數(shù)組的地址傳遞過(guò)去
int arr1[5] = {1,2,3,4,5};
int *arr2;
arr2 = arr1;
//遍歷數(shù)組arr1
for(int i = 0;i < 5;i++){
printf("%d ",arr1[i]);
}
printf("\n");
arr2[0] = 10;
//遍歷數(shù)組arr1
for(int i = 0;i < 5;i++){
printf("%d ",arr1[i]);
}
printf("\n");
//情況3:針對(duì)于指針,將指針保存的地址傳遞過(guò)去
int i = 10;
int *p = &i;
printf("%d\n",*p);
int *q = p;
*q = 20;
printf("%d\n",*q);
return 0;
}
3.2 形參、實(shí)參
形參(formal parameter):在定義函數(shù)時(shí),函數(shù)名后面括號(hào)()中聲明的變量稱(chēng)為形式參數(shù),簡(jiǎn)稱(chēng)形參。實(shí)參(actual parameter):在調(diào)用函數(shù)時(shí),函數(shù)名后面括號(hào)()中使用的值/變量/表達(dá)式稱(chēng)為實(shí)際參數(shù),簡(jiǎn)稱(chēng)實(shí)參。
舉例:
int add(int x, int y) { //x,y是add()中的形參
int z;
z = x + y;
return (z);
}
int main() {
int c;
int a = 10,b = 20;
c = add(a, b); //此時(shí)將實(shí)參a,b賦值給add()的形參
printf("sum is %d\n", c);
return 0;
}
說(shuō)明:
1、實(shí)參與形參的類(lèi)型應(yīng)相同或賦值兼容,個(gè)數(shù)相等、一一對(duì)應(yīng)。
2、形參只是一個(gè)形式,在調(diào)用之前并不分配內(nèi)存。函數(shù)調(diào)用時(shí),系統(tǒng)為形參分配內(nèi)存單元,然后將主調(diào)函數(shù)中的實(shí)參傳遞給被調(diào)函數(shù)的形參。被調(diào)函數(shù)執(zhí)行完畢,通過(guò)return語(yǔ)句返回結(jié)果,系統(tǒng)將形參的內(nèi)存單元釋放。
形參和實(shí)參的功能主要是數(shù)據(jù)傳遞,按照傳遞的是“數(shù)據(jù)”還是“地址”,分為“值傳遞”和“地址傳遞”兩種方式。
3.3 參數(shù)傳遞機(jī)制1:值傳遞
值傳遞,又稱(chēng)傳值方式、數(shù)據(jù)復(fù)制方式,就是把主調(diào)函數(shù)的實(shí)參值復(fù)制給被調(diào)用函數(shù)的形參,使形參獲得初始值。接著在函數(shù)內(nèi)對(duì)形參值的修改,不影響實(shí)參值。
值傳遞,是單向傳遞,只能把實(shí)參的值傳遞給形參,而不能把形參的值再傳回給實(shí)參。
默認(rèn)傳遞值的類(lèi)型:基本數(shù)據(jù)類(lèi)型 (整型類(lèi)型、浮點(diǎn)類(lèi)型,字符類(lèi)型)、結(jié)構(gòu)體、共用體、枚舉類(lèi)型。
舉例1:
void increment(int a) {
a++;
printf("a = %d\n",a); // a = 11
}
int main(){
int i = 10;
printf("i = %d\n", i); // i = 10
increment(i);
printf("i = %d\n", i); // i = 10
return 0;
}
調(diào)用 increment(i),因?yàn)閭魅牒瘮?shù)的是 i 的數(shù)據(jù)值,將數(shù)據(jù)值賦給遍歷a,而不是 i 本身。修改a數(shù)據(jù)值,影響不到原始變量 i。
如果希望獲取變化的參數(shù)值,可以把它作為返回值傳回。
int increment(int a) {
a++;
printf("a = %d\n", a);// a = 11
return a;
}
int main() {
int i = 10;
printf("i = %d\n", i); // i = 10
i = increment(i);
printf("i = %d\n", i); // i = 11
return 0;
}
體會(huì):形參、實(shí)參各占獨(dú)立的存儲(chǔ)空間。函數(shù)在被調(diào)用時(shí),給形參動(dòng)態(tài)分配臨時(shí)存儲(chǔ)空間,函數(shù)返回釋放。
舉例2:交換兩個(gè)變量的值
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
//printf("a = %d,b = %d\n", a, b); //輸出交換后的結(jié)果
}
int main() {
int x = 6, y = 8;
printf("調(diào)用函數(shù)之前:\n");
printf("x = %d,y = %d\n", x, y); //輸出調(diào)用swap()函數(shù)之前x,y的值
swap(x, y); //調(diào)用swap()函數(shù)
printf("調(diào)用函數(shù)之后:\n");
printf("x = %d,y = %d\n", x, y); //輸出調(diào)用swap()函數(shù)之后x,y的值
}
分析:為什么在swap()函數(shù)內(nèi)變量a和b的值互換了,而主調(diào)函數(shù)main()中實(shí)參x和y卻沒(méi)有交換呢?這是參數(shù)按值傳遞的緣故。

如果想要實(shí)現(xiàn)值的交換,那就需要傳入變量本身,這就需要傳入變量的地址,即地址傳遞。
3.4 參數(shù)傳遞機(jī)制2:地址傳遞
地址傳遞,又稱(chēng)傳地址方式、地址復(fù)制方式、指針傳遞,就是把實(shí)參地址常量進(jìn)行復(fù)制,傳送給形參。
默認(rèn)傳遞地址的類(lèi)型:指針、數(shù)組。實(shí)參將地址傳遞給形參,二者地址值相同。
-
比如1:當(dāng)指針作為函數(shù)的形參時(shí),實(shí)參傳遞給形參的是地址,在函數(shù)中通過(guò)形參保存的地址訪問(wèn)實(shí)參,進(jìn)而在函數(shù)中通過(guò)地址對(duì)實(shí)參的修改影響到實(shí)參的值。這也稱(chēng)為
雙向傳遞。 -
比如2:當(dāng)傳遞數(shù)組首元素地址時(shí),即把實(shí)參數(shù)組的起始地址傳遞給形參。這樣形參和實(shí)參數(shù)組就占用了共同的存儲(chǔ)空間。在被調(diào)函數(shù)中,如果通過(guò)形參修改了數(shù)組元素值,調(diào)用函數(shù)后實(shí)參數(shù)組元素值也發(fā)生相應(yīng)變化。
3.4.1 簡(jiǎn)單變量指針作為形參
當(dāng)函數(shù)的形參類(lèi)型是指針類(lèi)型時(shí),使用該函數(shù)時(shí),需要傳遞指針,或者地址,或者數(shù)組給該形參。函數(shù)內(nèi)以指針的方式操作變量(*指針)。
舉例1:
int num = 100;
int *p1 = #
int *p2 = p1;
*p2 = 50;

舉例2:
void increment(int *a) {
(*a)++;
printf("a = %d\n", *a); // a = 11
}
int main() {
int i = 10;
printf("i = %d\n", i); // i = 10
increment(&i);
printf("i = %d\n", i); // i = 11
return 0;
}
因?yàn)閭魅氲氖堑刂罚瘮?shù)體內(nèi)部對(duì)該地址包含的值的操作,會(huì)影響到函數(shù)外部變量的值。
舉例3:針對(duì)上節(jié)舉例2,交換兩個(gè)變量的值
void swap(int *a, int *b) { //函數(shù)參數(shù)為指針類(lèi)型
int temp = *a;
*a = *b;
*b = temp;
printf("*a = %d,*b = %d\n", *a, *b); //輸出交換后的結(jié)果
}
int main() {
int x = 6, y = 8;
printf("調(diào)用函數(shù)之前:\n");
printf("x = %d,y = %d\n", x, y); //輸出調(diào)用swap()函數(shù)之前x,y的值
swap(&x, &y); //調(diào)用swap()函數(shù)
printf("調(diào)用函數(shù)之后:\n");
printf("x = %d,y = %d\n", x, y); //輸出調(diào)用swap()函數(shù)之后x,y的值
}
通過(guò)傳入變量 x 和 y 的地址,函數(shù)內(nèi)部就可以直接操作該地址,從而實(shí)現(xiàn)交換兩個(gè)變量的值。

錯(cuò)誤:
錯(cuò)誤方式1:
void swap(int *p1, int *p2) {
int *temp;
*temp = *p1;
*p1 = *p2;
*p2 = *temp;
}
上述代碼中存在錯(cuò)誤。問(wèn)題出在臨時(shí)指針 temp 沒(méi)有分配內(nèi)存空間,因此不能正確保存變量的值。因此對(duì)*temp的賦值就沒(méi)有意義。
錯(cuò)誤方式2:
void swap(int *p1, int *p2) { //形參是指針變量
int *temp;
temp = p1;
p1 = p2;
p2 = temp;
//printf("*p1 = %d,*p2 = %d\n", *p1, *p2); //*p1 = 8,*p2 = 6
}
在函數(shù)內(nèi)部,只是交換了指針變量本身的值,而沒(méi)有影響到原始調(diào)用函數(shù)時(shí)傳遞給 swap 函數(shù)的指針變量。
復(fù)習(xí):雖然跟傳參無(wú)關(guān),這里特別提醒,函數(shù)不要返回內(nèi)部變量的指針。
int* f() {
int i;
// ...
return &i;
}
函數(shù)返回內(nèi)部變量 i 的指針,這種寫(xiě)法是錯(cuò)的。因?yàn)楫?dāng)函數(shù)結(jié)束運(yùn)行時(shí),內(nèi)部變量就失效了,這時(shí)指向內(nèi)部變量 i 的內(nèi)存地址就是無(wú)效的,再使用這個(gè)地址是錯(cuò)誤的。
3.4.2 數(shù)組作為形參
數(shù)組名本身就代表該數(shù)組首地址,傳數(shù)組的本質(zhì)就是傳地址。
因此,把數(shù)組名傳入一個(gè)函數(shù),就等同于傳入一個(gè)指針變量。在函數(shù)內(nèi)部,就可以通過(guò)這個(gè)指針變量獲得整個(gè)數(shù)組。
舉例1:對(duì)比傳值與傳址的方式
void test1(int a, int b, int c) {
a += 1;
b += 1;
c += 1;
}
void test2(int arr[], int n) {
for (int i = 0; i < n; i++) {
arr[i] += 1;
}
}
int main() {
//測(cè)試test1()
int arr1[3] = {1, 2, 3};
printf("調(diào)用函數(shù)前數(shù)組各元素值為: ");
printf("%d,%d,%d\n", arr1[0], arr1[1], arr1[2]); //1,2,3
test1(arr1[0], arr1[1], arr1[2]);
printf("調(diào)用函數(shù)后數(shù)組各元素值為: ");
printf("%d,%d,%d\n", arr1[0], arr1[1], arr1[2]); //1,2,3
//測(cè)試test2()
int arr2[3] = {1, 2, 3};
printf("調(diào)用函數(shù)前數(shù)組各元素值為:");
printf("%d,%d,%d\n", arr2[0], arr2[1], arr2[2]); //1,2,3
test2(arr2, 3);
printf("調(diào)用函數(shù)后數(shù)組各元素值為: ");
printf("%d,%d,%d\n", arr2[0], arr2[1], arr2[2]); //2,3,4
return 0;
}
舉例2:定義一個(gè)數(shù)組,通過(guò)函數(shù)給數(shù)組元素賦值
#include <stdio.h>
#define MAXLEN 5
void setValue(int vals[], int len) {
int i;
for (i = 0; i < len; i++) {
vals[i] = i * 10;
}
}
int main() {
int nums[MAXLEN] = {0}; //數(shù)組初始化
printf("調(diào)用函數(shù)前輸出結(jié)果:\n");
for (int i = 0; i < MAXLEN; i++) //遍歷數(shù)組元素
printf("nums[%d] = %d\n", i, nums[i]);
setValue(nums, MAXLEN); //調(diào)用函數(shù),傳遞數(shù)組名和簡(jiǎn)單變量
printf("調(diào)用函數(shù)后輸出結(jié)果:\n");
for (int i = 0; i < MAXLEN; i++) //遍歷數(shù)組元素
printf("nums[%d] = %d\n", i, nums[i]);
return 0;
}
上例中,傳入一個(gè)整數(shù)數(shù)組,與傳入一個(gè)整數(shù)指針是同一回事,數(shù)組符號(hào) [] 與指針?lè)?hào) * 是可以互換的。比如,
void setValue1(int *vals, int len) {
int i;
for (i = 0; i < len; i++) {
vals[i] = i * 10;
}
}
舉例3:數(shù)組的元素的反轉(zhuǎn)操作
#include<stdio.h>
#define N 5
//遍歷數(shù)組
void print(int arr[],int len){
for(int i = 0;i < len;i++){
printf("%d ",arr[i]);
}
printf("\n");
}
//反轉(zhuǎn)數(shù)組元素
void reverse(int arr[],int len){
for(int i = 0,j = len - 1;i < j;i++,j--){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int main() {
int data[N] = {23,54,96,7,8};
print(data,N);
reverse(data,N);
print(data,N);
return 0;
}
說(shuō)明:
void f(int x[], int n){
…………;
}參數(shù)1:數(shù)組的定義,只需寫(xiě)出中括號(hào)即可,不需要限定數(shù)組長(zhǎng)度。
參數(shù)2:數(shù)組作為參數(shù)的習(xí)慣操作。將函數(shù)中要操作的數(shù)組元素的長(zhǎng)度傳入(并不是指數(shù)組的總長(zhǎng)度)。
由于數(shù)組名就是一個(gè)指針,如果只傳數(shù)組名,那么函數(shù)只知道數(shù)組開(kāi)始的地址,不知道結(jié)束的地址,所以才需要把數(shù)組長(zhǎng)度也一起傳入。
小結(jié):實(shí)參與形參的對(duì)應(yīng)關(guān)系有以下4種情況:
(1)形參和實(shí)參都用數(shù)組名
(2)實(shí)參形參都用指針變量
(3)實(shí)參用數(shù)組名,形參用指針變量
(4)實(shí)參為指針變量,形參為數(shù)組名

3.4.3 字符串(字符指針)作為形參
字符串(或字符指針)作為函數(shù)的參數(shù),與數(shù)組指針作為函數(shù)參數(shù)沒(méi)有本質(zhì)的區(qū)別,傳遞的都是地址值,所不同的僅是指針指向?qū)ο蟮念?lèi)型不同而已。
舉例1:定義函數(shù),要求字符串作函數(shù)參數(shù),統(tǒng)計(jì)數(shù)字字符出現(xiàn)的個(gè)數(shù)。
#include<stdio.h>
#define N 100
int digitalCount(char *p) {
int count = 0;
for (; *p != '\0'; p++)
if (*p >= '0' && *p <= '9')
count++;
return count;
}
int main() {
char strs[N] = "a12bc43hec22b68o";
printf("數(shù)字字符的個(gè)數(shù)為 % d個(gè)\n", digitalCount(strs)); //8
return 0;
}
3.4.4 指針數(shù)組作為形參
指針數(shù)組的元素是指針變量,用指針數(shù)組能夠?qū)崿F(xiàn)一組字符串的處理。
舉例:編寫(xiě)能對(duì)多個(gè)字符串排序的函數(shù)
#include <stdio.h>
#include <string.h>
void stringSort(char *[], int);
void stringPrint(char *[], int);
int main() {
char *days[7] = {"Sunday", "Monday",
"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
stringSort(days, 7);
stringPrint(days, 7);
return 0;
}
void stringSort(char *string[], int n) {
char *temp;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++)
if (strcmp(string[j], string[j + 1]) > 0) {
temp = string[j];
string[j] = string[j + 1];
string[j + 1] = temp;
}
}
}
void stringPrint(char *string[], int n) {
for (int i = 0; i < n; i++)
printf("%s ", string[i]);
}
3.5 舉例
舉例1:定義函數(shù),求一維數(shù)組元素的最大值
原型:int pMax(int *p,int n)
功能:在長(zhǎng)度為 n、由 p 指向的一維數(shù)組中求元素最大值
#include<stdio.h>
#define N 5
int pMax(int *p, int n) {
int max = *p;
for (int i = 1; i < n; i++){
if (max < *(p + i)){
max = *(p + i);
}
}
return max;
}
int main() {
int a[N];
for (int i = 0; i < N; i++)
scanf("%d", &a[i]);
printf("Max = %d\n", pMax(a, N));
return 0;
}
舉例2:多維數(shù)組名作為形參
有一個(gè)3×4的矩陣,求所有元素中的最大值。
#include <stdio.h>
#define N 3
int maxValue(int array[][4], int n) { //n:第一維的長(zhǎng)度
int max = array[0][0];
for (int i = 0; i < n; i++)
for (int j = 0; j < 4; j++)
if (max < array[i][j])
max = array[i][j]; //把較大值重新賦值給max
return max;
}
int main() {
int a[N][4] = {{11, 33, 3, 17},
{32, 54, 6, 68},
{24, 17, 34, 12}};
printf("Max value is %d\n", maxValue(a, N));
return 0;
}
說(shuō)明:
如果函數(shù)的參數(shù)是二維數(shù)組,那么除了第一維的長(zhǎng)度可以當(dāng)作參數(shù)傳入函數(shù),其他維的長(zhǎng)度需要寫(xiě)入函數(shù)的定義。也就是說(shuō),在定義二維數(shù)組時(shí),必須指定列數(shù)(即一行中包含幾個(gè)元素) ,由于形參數(shù)組與實(shí)參數(shù)組類(lèi)型相同,所以它們是由具有相同長(zhǎng)度的一維數(shù)組所組成的。所以必須指定第2維(列數(shù))。
在第2維大小相同的前提下,形參數(shù)組的第1維可以與實(shí)參數(shù)組不同。因?yàn)镃語(yǔ)言編譯系統(tǒng)不檢查第一維的大小。例如,實(shí)參數(shù)組定義為
int score[5][10];而形參數(shù)組定義為int array[][10];或int array[8][10];均可以。這時(shí)形參數(shù)組和實(shí)參數(shù)組都是由相同類(lèi)型和大小的一維數(shù)組組成的。
練習(xí):
void f(int x[][5]){
…………;
}
//1、int a[10][5]; 可以傳給f函數(shù)嗎? 可以!
//2、int b[10][3];可以傳給f函數(shù)嗎? 不可以!
舉例3:變長(zhǎng)數(shù)組作為參數(shù)
變長(zhǎng)數(shù)組作為函數(shù)參數(shù)時(shí),寫(xiě)法略有不同。
#include <stdio.h>
//int sum_array(int a[n],int n) { //報(bào)錯(cuò)
// // ...
//}
int sumArray(int n, int a[n]) {
// ...
}
int main() {
int a[] = {1, 3, 5, 7};
int sum = sumArray(4, a);
return 0;
}
數(shù)組 a[n] 是一個(gè)變長(zhǎng)數(shù)組,它的長(zhǎng)度取決于變量 n 的值,只有運(yùn)行時(shí)才能知道。所以,變量 n 作為參數(shù)時(shí),順序一定要在變長(zhǎng)數(shù)組前面,這樣運(yùn)行時(shí)才能確定數(shù)組 a[n] 的長(zhǎng)度,否則就會(huì)報(bào)錯(cuò)。
因?yàn)楹瘮?shù)原型可以省略參數(shù)名,所以變長(zhǎng)數(shù)組的原型中,可以使用 * 代替變量名,也可以省略變量名。
int sumArray(int, int [*]);
int sumArray(int, int []);
變長(zhǎng)數(shù)組作為函數(shù)參數(shù)有一個(gè)好處,就是多維數(shù)組的參數(shù)聲明,可以把后面的維度省掉了。
// 原來(lái)的寫(xiě)法
int sumArray(int a[][4], int n);
// 變長(zhǎng)數(shù)組的寫(xiě)法
int sumArray(int n, int m, int a[n][m]);
說(shuō)明:函數(shù) sum_array() 的參數(shù)是一個(gè)多維數(shù)組,按照原來(lái)的寫(xiě)法,一定要聲明第二維的長(zhǎng)度。但是使用變長(zhǎng)數(shù)組的寫(xiě)法,就不用聲明第二維長(zhǎng)度了,因?yàn)樗梢宰鳛閰?shù)傳入函數(shù)。
3.6 C++中的引用傳遞
void f(int x){
x++;
}
int main() {
int a = 1;
f(a);
printf("a = %d",a); // a = 1
return 0;
}
如果想讓a實(shí)現(xiàn)+1,使用C語(yǔ)言的話,可以如下操作:
void f1(int *x){
//(*x)++;
*x = *x + 1;
}
int main() {
int a = 1;
f1(&a);
printf("a = %d",a); // a = 2
return 0;
}
C語(yǔ)言是靠傳入變量地址的方式實(shí)現(xiàn)的,相對(duì)麻煩且容易出錯(cuò)。這里還可以使用C++的語(yǔ)法來(lái)操作。
情況1:傳入數(shù)值類(lèi)型變量
#include <iostream>
void f2(int &x) {
x = x + 1;
}
int main() {
int a = 1;
f2(a);
std::cout << "a = " << a << std::endl; // a = 2
return 0;
}
上述代碼在c語(yǔ)言環(huán)境中執(zhí)行會(huì)報(bào)錯(cuò),必須在c++環(huán)境中執(zhí)行。
在這個(gè)C++版本中,我們使用了C++的
#include <iostream>來(lái)代替C的<stdio.h>,并使用std::cout來(lái)替代printf函數(shù)。此外,f1函數(shù)的參數(shù)類(lèi)型也從指針int *x改為引用int &x,這是C++的引用特性,允許更直觀地操作變量,而不需要使用指針。其他部分保持不變,代碼仍然具有相同的功能,將整數(shù)a的值增加1并打印結(jié)果。
情況2:傳入結(jié)構(gòu)體變量
如果傳入的是結(jié)構(gòu)體變量,如果內(nèi)部需要改變結(jié)構(gòu)體的成員,則需要如下聲明:
void insert(SqList &L,int x){
//修改L內(nèi)部data[]數(shù)組的內(nèi)容,則認(rèn)為修改了L,因此需要傳入引用型L
//....
}
情況3:傳入指針型變量
上述是普通變量的傳入方式,如果傳入的變量是指針型變量,且在函數(shù)內(nèi)部需要對(duì)傳入的指針進(jìn)行改變,則:
void f(int *&x){ //指針型變量在函數(shù)體中需要改變的寫(xiě)法
x++; //將指針 x 向后移動(dòng)一個(gè)整數(shù)的大小。
}
int *&x:這里int *表示指向整數(shù)的指針,&表示引用,因此int *&x表示引用指向整數(shù)的指針。這個(gè)參數(shù)允許傳遞一個(gè)指向整數(shù)的指針,并且在函數(shù)內(nèi)部可以改變指針的值。
舉例1:
#include <iostream>
void f(int *&x) {
x++;
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr; // 將指針指向數(shù)組的第一個(gè)元素
std::cout << "Original value: " << *ptr << std::endl;
f(ptr); // 傳遞指針給函數(shù),函數(shù)將指針移動(dòng)到下一個(gè)元素
std::cout << "New value: " << *ptr << std::endl;
return 0;
}
舉例2:將A、B兩個(gè)鏈表合并成一個(gè)C,此時(shí)C發(fā)生了改變,需要引用型。A、B沒(méi)發(fā)生改變,不需要引用型。
void merge(LNode *A,LNode *B,LNode *&C){
LNode *p = A->next;
LNode *q = B->next;
LNode *r;
C = A;
//...
}
上述寫(xiě)法在樹(shù)、圖的算法中應(yīng)用廣泛。
4、函數(shù)的高級(jí)應(yīng)用
4.1 遞歸(recursion)函數(shù)
舉例1:

舉例2:
從前有座山,山上有座廟,廟里有個(gè)老和尚,老和尚在給小和尚講故事,講的啥?
從前有座山,山上有座廟,廟里有個(gè)老和尚,老和尚在給小和尚講故事,講的啥?
從前有座山,山上有座廟,廟里有個(gè)老和尚,老和尚在給小和尚講故事,講的啥?
從前有座山,山上有座廟,廟里有個(gè)老和尚,老和尚在給小和尚講故事,講的啥?...
...
老和尚沒(méi)了,廟塌了,小和尚還俗結(jié)婚了。
遞歸函數(shù)調(diào)用:函數(shù)自己調(diào)用自己的現(xiàn)象就稱(chēng)為遞歸。
遞歸的分類(lèi):直接遞歸、間接遞歸。
-
直接遞歸:函數(shù)自身調(diào)用自己。
int func(int a){ int b,c; … c=func(b); … } -
間接遞歸:可以理解為A()函數(shù)調(diào)用B()函數(shù),B()函數(shù)調(diào)用C()函數(shù),C()函數(shù)調(diào)用A()函數(shù)。
void A(){ B(); } void B(){ C(); } void C(){ A(); }
說(shuō)明:
- 遞歸函數(shù)包含了一種
隱式的循環(huán)。 - 遞歸函數(shù)會(huì)
重復(fù)執(zhí)行某段代碼,但這種重復(fù)執(zhí)行無(wú)須循環(huán)控制。 - 遞歸一定要向
已知方向遞歸,否則這種遞歸就變成了無(wú)窮遞歸,停不下來(lái),類(lèi)似于死循環(huán)。最終發(fā)生棧內(nèi)存溢出。 - C語(yǔ)言支持函數(shù)的遞歸調(diào)用。
舉例1:計(jì)算1 ~ n的和
// 遞歸函數(shù),計(jì)算1到n的和
int getSum(int n) {
// 基本情況:當(dāng)n為1時(shí),返回1
if (n == 1) {
return 1;
} else {
// 遞歸情況:將n與1到n-1的和相加
return n + getSum(n - 1);
}
}
int main() {
int n;
printf("輸入一個(gè)正整數(shù):");
scanf("%d", &n);
// 調(diào)用遞歸函數(shù)計(jì)算1到n的和
int result = getSum(n);
printf("1到%d的和為%d\n", n, result);
return 0;
}
圖示:

舉例2:遞歸函數(shù)計(jì)算n!
// 遞歸函數(shù),計(jì)算n的階乘
int factorial(int n) {
// 基本情況:當(dāng)n為0或1時(shí),階乘為1
if (n == 0 || n == 1) {
return 1;
} else {
// 遞歸情況:n! = n * (n-1)!
return n * factorial(n - 1);
}
}
int main() {
int n = 5;
// 調(diào)用遞歸函數(shù)計(jì)算n的階乘
int result = factorial(n);
printf("%d! = %d\n", n, result);
return 0;
}
圖示:

舉例3:計(jì)算斐波那契數(shù)列(Fibonacci)的第n個(gè)值,斐波那契數(shù)列滿足如下規(guī)律,
1,1,2,3,5,8,13,21,34,55,....
即前兩個(gè)數(shù)都是1,從第三個(gè)數(shù)開(kāi)始,每個(gè)數(shù)等于前兩個(gè)數(shù)之和。假設(shè)f(n)代表斐波那契數(shù)列的第n個(gè)值,那么f(n)滿足:
f(n) = f(n-2) + f(n-1); 其中,n >= 3。
// 遞歸函數(shù),計(jì)算第n個(gè)斐波那契數(shù)
int fibonacciRecursion(int n) {
if(n == 1 || n == 2){
return 1;
}else{
return FibonacciRecursion(n - 1) + FibonacciRecursion(n - 2);
}
}
如果不使用遞歸函數(shù),而是使用迭代的方式計(jì)算第n個(gè)斐波那契數(shù)列,如下:
int FibonacciIteration(int n){
if(n == 1 || n == 2){
return 1;
}
int a = 1;
int b = 1;
int temp;
for(int i = 3;i <= n;i++){
temp = a + b;
a = b;
b = temp;
}
return b;
}
總結(jié):
1、使用遞歸函數(shù)大大簡(jiǎn)化了算法的編寫(xiě)。
2、遞歸調(diào)用會(huì)占用大量的系統(tǒng)堆棧,內(nèi)存耗用多,在遞歸調(diào)用層次多時(shí)速度要比循環(huán)
慢的多,所以在使用遞歸時(shí)要慎重。3、在要求高性能的情況下盡量避免使用遞歸,遞歸調(diào)用既
花時(shí)間又耗內(nèi)存。考慮使用循環(huán)迭代
練習(xí)1:
有5個(gè)學(xué)生坐在一起,問(wèn)第5個(gè)學(xué)生多少歲,他說(shuō)比第4個(gè)學(xué)生大2歲。問(wèn)第4個(gè)學(xué)生歲數(shù),他說(shuō)比第3個(gè)學(xué)生大2歲。問(wèn)第3個(gè)學(xué)生,又說(shuō)比第2個(gè)學(xué)生大2歲。問(wèn)第2個(gè)學(xué)生,說(shuō)比第1個(gè)學(xué)生大2歲。最后問(wèn)第1個(gè)學(xué)生,他說(shuō)是10歲。請(qǐng)問(wèn)第5個(gè)學(xué)生多大。即:


#include <stdio.h>
int age(int n); //對(duì)age函數(shù)的聲明
int main() {
printf("NO.5:age is %d\n", age(5)); //輸出第5個(gè)學(xué)生的年齡
return 0;
}
int age(int n){ //定義遞歸函數(shù)
int c; //c用作存放函數(shù)的返回值的變量
if (n == 1)
c = 10;
else
c = age(n - 1) + 2; //年齡是前一個(gè)學(xué)生的年齡加2(如第4個(gè)學(xué)生年齡是第3個(gè)學(xué)生年齡加2)
return c;
}
練習(xí)2:走臺(tái)階問(wèn)題
假如有10階樓梯,小朋友每次只能向上走1階或者2階,請(qǐng)問(wèn)對(duì)于n階臺(tái)階一共有多少種不同的走法呢?
階數(shù):1 2 3 4
走法:1 2 3 5
fun(n) = fun(n - 1) + fun(n - 2)
【奇妙的屬性】隨著數(shù)列的增加,斐波那契數(shù)列前一個(gè)數(shù)與后一個(gè)數(shù)的比值越來(lái)越逼近黃金分割的數(shù)值0.618。
4.2 可變參數(shù)
有些函數(shù)的參數(shù)數(shù)量是不確定的,此時(shí)可以使用C語(yǔ)言提供的可變參數(shù)函數(shù)(Variadic Functions)。
聲明可變參數(shù)函數(shù)的時(shí)候,使用省略號(hào) ... 表示可變數(shù)量的參數(shù)。最常見(jiàn)的例子:
#include <stdarg.h>
int printf(const char* format, ...);
這里的 ... 表示可以傳遞任意數(shù)量的參數(shù),但是它們都需要與format字符串中的格式化標(biāo)志相匹配。
注意, ... 符號(hào)必須放在
參數(shù)序列的結(jié)尾,否則會(huì)報(bào)錯(cuò)。
可變參數(shù)函數(shù)的使用:
- 為了使用可變參數(shù),你需要引入
<stdarg.h>頭文件。 - 在函數(shù)中,需要聲明一個(gè)
va_list類(lèi)型的變量來(lái)存儲(chǔ)可變參數(shù)。它必須在操作可變參數(shù)時(shí),首先使用。 - 使用
va_start函數(shù)來(lái)初始化va_list類(lèi)型的變量。它接受兩個(gè)參數(shù),參數(shù)1是可變參數(shù)對(duì)象,參數(shù)2是原始函數(shù)里面,可變參數(shù)之前的那個(gè)參數(shù),用來(lái)為可變參數(shù)定位。 - 使用
va_arg函數(shù)來(lái)逐個(gè)獲取可變參數(shù)的值。每次調(diào)用后,內(nèi)部指針就會(huì)指向下一個(gè)可變參數(shù)。它接受兩個(gè)參數(shù),參數(shù)1是可變參數(shù)對(duì)象,參數(shù)2是當(dāng)前可變參數(shù)的類(lèi)型。 - 使用
va_end函數(shù)來(lái)結(jié)束可變參數(shù)的處理。
舉例:
#include <stdio.h>
#include <stdarg.h>
// 可變參數(shù)函數(shù),計(jì)算多個(gè)整數(shù)的平均值
double average(int count, ...) {
va_list args; // 聲明一個(gè)va_list變量,存儲(chǔ)可變參數(shù)
va_start(args, count); // 初始化va_list,指向可變參數(shù)的位置
double sum = 0;
for (int i = 0; i < count; i++) {
int num = va_arg(args, int); // 逐個(gè)獲取整數(shù)參數(shù)
sum += num;
}
va_end(args); // 結(jié)束可變參數(shù)的處理
return sum / count;
}
int main() {
double avg = average(5, 10, 20, 30, 40, 50); // 調(diào)用可變參數(shù)函數(shù)
printf("Average: %lf\n", avg);
return 0;
}
小結(jié):
可變參數(shù)函數(shù),在編寫(xiě)各種工具函數(shù)和格式化輸出函數(shù)時(shí)非常有用。但要小心確保傳遞的參數(shù)數(shù)量和類(lèi)型與函數(shù)的預(yù)期相匹配,以避免運(yùn)行時(shí)錯(cuò)誤。
4.3 指針函數(shù)(返回值是指針)
C語(yǔ)言允許函數(shù)的返回值是一個(gè)指針(地址),這樣的函數(shù)稱(chēng)為指針函數(shù)。
指針函數(shù)的定義的一般格式
返回值類(lèi)型 *函數(shù)名(形參列表) {
函數(shù)體
}
函數(shù)體中的 return 命令須返回一個(gè)地址。
舉例1:獲取兩個(gè)字符串中較長(zhǎng)的那個(gè)字符串
#include <stdio.h>
#include <string.h>
char *maxLengthStr(char *str1, char *str2) { //函數(shù)返回char * (指針)
printf("\nstr1的長(zhǎng)度%d,str2的長(zhǎng)度%d", strlen(str1), strlen(str2));
if (strlen(str1) >= strlen(str2)) {
return str1;
} else {
return str2
}
}
int main() {
char str1[30], str2[30];
printf("請(qǐng)輸入第1個(gè)字符串:");
gets(str1);
printf("請(qǐng)輸入第2個(gè)字符串:");
gets(str2);
char *str;
str = maxLengthStr(str1, str2);
printf("\nLonger string: %s \n", str);
return 0;
}
拓展:編寫(xiě)函數(shù)char *maxLlen (char *string[],int n),用于查找多個(gè)字符串中的最長(zhǎng)字符串,并返回該字符串的地址。
#include <stdio.h>
#include <string.h>
char *maxLen(char *[], int);
int main() {
char *pString[5] = {"Atlanta1996", "Sydney2000", "Beijing2008", "London2012", "RIO2016"};
puts(maxLen(pString, 5));
return 0;
}
char *maxLen(char *string[], int n) {
int posion, maxLen;
maxLen = strlen(string[0]);
for (int i = 1; i < n; i++){
if (maxLen < strlen(string[i])){
maxLen = strlen(string[i]);
posion = i;
}
}
return string[posion];
}
舉例2:
#include <stdio.h>
#include <stdlib.h>
int *func() {
int *n = (int *)malloc(sizeof(int)); //分配動(dòng)態(tài)內(nèi)存
if (n != NULL) {
*n = 100;
}
return n;
}
int main() {
int *p = func();
int n = *p;
printf("value = %d\n", n);
free(p); // 釋放動(dòng)態(tài)分配的內(nèi)存,以免出現(xiàn)內(nèi)存泄漏
return 0;
}
注意,上述操作很容易錯(cuò)寫(xiě)成如下方式:
int *func() {
int n = 100;
return &n;
}
int main() {
int *p = func();
int n = *p;
printf("value = %d\n", n);
return 0;
}
在 func() 函數(shù)中,聲明了一個(gè)整數(shù)變量 n,然后返回其地址 &n。但是,一旦 func() 函數(shù)執(zhí)行完畢,局部變量 n將被銷(xiāo)毀,它的地址也將變得無(wú)效。這意味著在 main() 函數(shù)中,嘗試訪問(wèn) p 指向的地址時(shí),它實(shí)際上已經(jīng)不再是一個(gè)有效的內(nèi)存位置,這會(huì)導(dǎo)致未定義的行為。
如果確實(shí)希望返回局部變量的地址,除了使用malloc()函數(shù)的方式之外,還可以定義局部變量為 static 的,此時(shí)數(shù)據(jù)空間在靜態(tài)數(shù)據(jù)區(qū)分配,靜態(tài)變量在程序的生命周期內(nèi)都存在,不會(huì)像局部變量那樣在函數(shù)執(zhí)行完畢后被銷(xiāo)毀。比如:
int *func() {
static int n = 100;
return &n;
}
int main() {
int *p = func();
int n = *p;
printf("value = %d\n", n);
return 0;
}
4.4 函數(shù)指針(指向函數(shù)的指針)
一個(gè)函數(shù)本身就是一段內(nèi)存里面的代碼,總是占用一段連續(xù)的內(nèi)存區(qū)域。這段內(nèi)存區(qū)域也有首地址,把函數(shù)的這個(gè)首地址(或稱(chēng)入口地址)賦予一個(gè)指針變量,使指針變量指向函數(shù)所在的內(nèi)存區(qū)域,然后通過(guò)指針變量就可以找到并調(diào)用該函數(shù)。這種指針就是函數(shù)指針。
簡(jiǎn)單來(lái)說(shuō),函數(shù)指針,就是指向函數(shù)的指針。
格式:
返回值類(lèi)型 (*指針變量名)(參數(shù)列表);
其中,參數(shù)列表中可以同時(shí)給出參數(shù)的類(lèi)型和名稱(chēng),也可以只給出參數(shù)的類(lèi)型,省略參數(shù)的名稱(chēng)。
舉例:
void print(int a) {
printf("%d\n", a);
}
int main() {
void (*print_ptr)(int); //1
print_ptr = &print; //2
return 0;
}
注釋1處,變量 print_ptr 是一個(gè)函數(shù)指針,它可以指向函數(shù)返回值類(lèi)型為void且有1個(gè)整型參數(shù)的函數(shù)。
注釋2處,print_ptr 指向函數(shù) print() 的地址。函數(shù) print() 的地址可以用 &print 獲得。
注意,
(*print_ptr)的小括號(hào)一定不能省略,否則因?yàn)楹瘮?shù)參數(shù) (int) 的優(yōu)先級(jí)高于 * ,整個(gè)結(jié)構(gòu)就變成了函數(shù)原型:void *print_ptr(int),void *成了返回值類(lèi)型了。
有了函數(shù)指針,通過(guò)它也可以調(diào)用函數(shù)。
(*print_ptr)(10);
// 等同于
print(10);
舉例:用函數(shù)指針來(lái)實(shí)現(xiàn)對(duì)函數(shù)的調(diào)用,返回兩個(gè)整數(shù)中的最大值。
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int x, y;
int (*pmax)(int, int) = &max; // 使用函數(shù)指針
printf("輸入兩個(gè)整數(shù):");
scanf("%d %d", &x, &y);
int maxVal = (*pmax)(x, y);
printf("較大值為: %d\n", maxVal);
return 0;
}
拓 展
C 語(yǔ)言規(guī)定,函數(shù)名本身就是指向函數(shù)代碼的指針,通過(guò)函數(shù)名就能獲取函數(shù)地址。也就是說(shuō), print 和 &print 是一回事。
if (print == &print) // true
因此,上面代碼的 print_ptr 等同于 print 。
void (*print_ptr)(int) = &print;
// 或
void (*print_ptr)(int) = print;
if (print_ptr == print) // true
注意:
1、對(duì)指向函數(shù)的指針變量不能進(jìn)行算術(shù)運(yùn)算,如p+n,p++,p--等運(yùn)算是無(wú)意義的。
2、用函數(shù)名調(diào)用函數(shù),只能調(diào)用所指定的一個(gè)函數(shù),而通過(guò)指針變量調(diào)用函數(shù)比較靈活,可以根據(jù)不同情況先后調(diào)用不同的函數(shù)。
【武漢科技大學(xué)2019研】有函數(shù)定義:int func(int *p),x和y是int型變量,則正確的調(diào)用是( )。
A.y=func(x);
B.func(x);
C.func()=x;
D.y=func(&x);【答案】D
【解析】根據(jù)func函數(shù)的定義可以知道調(diào)用func函數(shù)需要傳入一個(gè)指針,且該指針的指向類(lèi)型是int型,只有D傳入的是指向int型數(shù)據(jù)的指針,答案選D。
4.5 回調(diào)函數(shù)
指向函數(shù)a的指針變量的一個(gè)重要用途是把函數(shù)a的入口地址作為參數(shù)傳遞到其它函數(shù)b中,此時(shí)的函數(shù)b就稱(chēng)為回調(diào)函數(shù)。在此基礎(chǔ)上,我們就可以在回調(diào)函數(shù)b中使用實(shí)參函數(shù)a。
它的原理可以簡(jiǎn)述如下: 有一個(gè)函數(shù)(假設(shè)函數(shù)名為fun),它有兩個(gè)形參(x1和x2),定義x1和x2為指向函數(shù)的指針變量。在調(diào)用函數(shù)fun時(shí),實(shí)參為兩個(gè)函數(shù)名f1和f2,給形參傳遞的是函數(shù)f1和f2的入口地址。這樣在函數(shù)fun中就可以調(diào)用f1和f2函數(shù)了。

舉例1:使用回調(diào)函數(shù)的方式,給一個(gè)整型數(shù)組int arr[10] 賦10個(gè)隨機(jī)數(shù)。
#include <stdio.h>
#include <stdlib.h>
// 回調(diào)函數(shù)
void initArray(int *array, int arrayLen, int (*f)()) {
for (int i = 0; i < arrayLen; i++)
array[i] = (*f)();
}
// 獲取隨機(jī)值
int getRandomValue() {
return rand();
}
int main() {
int arrLen = 10;
int myArray[arrLen];
initArray(myArray, arrLen, &getRandomValue);
//遍歷數(shù)組
for (int i = 0; i < 10; i++) {
printf("%d ", myArray[i]);
}
printf("\n");
return 0;
}
舉例2:有兩個(gè)整數(shù)a和b,由用戶輸入1,2或3。如輸入1,程序就給出a和b中的大者,輸入2,就給出a和b中的小者,輸入3,則求a與b之和。
#include <stdio.h>
int fun(int x, int y, int (*p)(int, int)); //fun函數(shù)聲明
int max(int, int); //max函數(shù)聲明
int min(int, int); //min函數(shù)聲明
int add(int, int); //add函數(shù)聲明
int main() {
int a = 10, b = 20, n;
printf("please choose 1,2 or 3:");
scanf("%d", &n); //輸入1,2或3之一
switch(n){
case 1:
fun(a, b, max); //輸入1時(shí)調(diào)用max函數(shù)
break;
case 2:
fun(a, b, min); //輸入2時(shí)調(diào)用min函數(shù)
break;
case 3:
fun(a, b, add); //輸入3時(shí)調(diào)用add函數(shù)
break;
}
return 0;
}
int fun(int x, int y, int (*p)(int, int)){ //定義fun函數(shù)
int result;
result = (*p)(x, y);
printf("%d\n", result); //輸出結(jié)果
}
int max(int x, int y){ //定義max函數(shù)
int z;
if (x > y)
z = x;
else
z = y;
printf("max=");
return z; //返回值是兩數(shù)中的大者
}
int min(int x, int y){ //定義min函數(shù)
int z;
if(x < y)
z = x;
else
z = y;
printf("min=");
return z; //返回值是兩數(shù)中的小者
}
int add(int x, int y){ //定義add函數(shù)
int z;
z = x + y;
printf("sum=");
return z; //返回值是兩數(shù)之和
}
4.6 函數(shù)說(shuō)明符
C 語(yǔ)言提供了一些函數(shù)說(shuō)明符,讓函數(shù)用法更加明確。
函數(shù)一旦定義,就可以被其它函數(shù)調(diào)用。但是當(dāng)一個(gè)源程序由多個(gè)源文件組成時(shí),在一個(gè)源文件中定義的函數(shù)能否被其他源文件中的函數(shù)調(diào)用呢?因此,C語(yǔ)言又把函數(shù)分為兩類(lèi)——內(nèi)部函數(shù)和外部函數(shù)。
① 內(nèi)部函數(shù)(靜態(tài)函數(shù))
如果在一個(gè)源文件中定義的函數(shù)只能被本文件中的函數(shù)調(diào)用,而不能被同一源程序其他文件中的函數(shù)調(diào)用,這種函數(shù)稱(chēng)為內(nèi)部函數(shù)。此時(shí),內(nèi)部函數(shù)需要使用static修飾。
定義內(nèi)部函數(shù)的一般形式是:
static 類(lèi)型說(shuō)明符 函數(shù)名(<形參表>)
舉例:
static int f(int a,int b){
…
}
說(shuō)明:f()函數(shù)只能被本文件中的函數(shù)調(diào)用,在其他文件中不能調(diào)用此函數(shù)。
但此處static的含義并不是指存儲(chǔ)方式,而是指對(duì)函數(shù)的調(diào)用范圍只局限于本文件。因此在不同的源文件中定義同名的內(nèi)部函數(shù)不會(huì)引起混淆,互不影響。
② 外部函數(shù)
外部函數(shù)在整個(gè)源程序中都有效,只要定義函數(shù)時(shí),在前面加上extern關(guān)鍵字即可。
其定義的一般形式為:
extern 類(lèi)型說(shuō)明符 函數(shù)名(<形參表>)
例如:
extern int f(int a,int b){
…
}
因?yàn)楹瘮?shù)與函數(shù)之間都是并列的,函數(shù)不能嵌套定義,所以函數(shù)在本質(zhì)上都具有外部性質(zhì)。因此在定義函數(shù)省去extern說(shuō)明符時(shí),則隱含為外部函數(shù)。所以說(shuō),本節(jié)之前定義的使用的函數(shù)都是外部函數(shù)。
如果定義為外部函數(shù),則它不僅可被定義它的源文件調(diào)用,而且可以被其他文件中的函數(shù)調(diào)用,即其作用范圍不只局限于其源文件,而是整個(gè)程序的所有文件。在一個(gè)源文件的函數(shù)中調(diào)用其他源文件中定義的外部函數(shù)時(shí),通常使用extern說(shuō)明被調(diào)函數(shù)為外部函數(shù)。
練習(xí):
(1)新建名稱(chēng)為“ExternFunction”的項(xiàng)目,在項(xiàng)目中創(chuàng)建下面的3個(gè)文件。
(2)新建名為“file1.c”的文件,并在代碼編輯區(qū)域輸入以下代碼。
#include<stdio.h>
extern void add(int c, int d) { //定義外部函數(shù)add(),extern可省略不寫(xiě)
printf("%d+%d=%d\n", c, d, c + d);
}
(3)新建名為“file2.c”的文件,并在代碼編輯區(qū)域輸入以下代碼。
#include<stdio.h>
extern void sub(int c, int d) { //定義外部函數(shù)sub(),extern可省略不寫(xiě)
printf("%d-%d=%d\n", c, d, c - d);
}
(4)新建名為“file3.c”的文件,并在代碼編輯區(qū)域輸入以下代碼。
#include<stdio.h>
//由于函數(shù)原型默認(rèn)就是 extern ,所以這里不加 extern ,效果是一樣的。
extern void add(int arg1, int arg2);
extern void sub(int arg1, int arg2);
int main() {
int a = 100, b = 20;
add(a, b);
sub(a, b);
return 0;
}
整個(gè)程序是由3個(gè)文件組成的,file3.c文件的主函數(shù)中使用了4個(gè)函數(shù)的調(diào)用語(yǔ)句。其中,printf()、scanf()是庫(kù)函數(shù),另外兩個(gè)是用戶自定義的函數(shù),它們都被定義為外部函數(shù)。
在當(dāng)前file3.c文件里面,main()函數(shù)使用前需要給出外部函數(shù)的原型,并用 extern 說(shuō)明該函數(shù)的定義來(lái)自其它文件。
5、再談變量

5.1 按聲明位置的不同分類(lèi)
① 局部變量(Local Variable)
在函數(shù)體內(nèi)定義的變量或函數(shù)的形參,都是內(nèi)部變量,稱(chēng)為局部變量。局部變量只能在定義它的函數(shù)中使用。
舉例1:
#include <stdio.h>
void printStar();
int main() {
int i;
for (i = 1; i <= 5; i++) {
printStar();
putchar('\n');
}
return 0;
}
void printStar() {
int i;
for (i = 1; i <= 10; i++)
putchar('*');
}
舉例2:

② 全局變量(Global Variable)
在函數(shù)之外定義的變量就是外部變量,稱(chēng)為全局變量(或全程變量)。
一個(gè)程序中,凡是在全局變量之后定義的函數(shù),都可以使用在函數(shù)之前定義的全局變量。也就是說(shuō),一個(gè)全局變量,可以被多個(gè)函數(shù)使用,但并不一定能被所在程序中的每一個(gè)函數(shù)使用。

注意:
如果全局變量與函數(shù)中定義的局部變量重名,則在函數(shù)內(nèi)部調(diào)用此同名的變量,默認(rèn)是局部變量(就近原則)。
#include <stdio.h>
int counter = 10;
void add(){
counter++;
printf("counter = %d\n",counter);
}
int main() {
add();
add();
printf("counter = %d\n",counter); //counter = 12
int counter = 100;
printf("counter = %d\n",counter); //counter = 100
return 0;
}
利用全局變量傳遞數(shù)據(jù):
利用全局變量進(jìn)行函數(shù)間的數(shù)據(jù)傳遞,簡(jiǎn)單而運(yùn)行效率高。
全局變量使用過(guò)多增加了函數(shù)間聯(lián)系的復(fù)雜性,降低了函數(shù)的獨(dú)立性。
局部變量與全局變量的對(duì)比:
1、作用域
- 局部變量:它的作用域只能在其定義的函數(shù)或代碼塊內(nèi)部,超出該范圍將無(wú)法訪問(wèn)。
- 全局變量:它的作用域默認(rèn)是整個(gè)程序,也就是所有的代碼文件。
2、訪問(wèn)權(quán)限
- 局部變量:由于局部變量的作用域僅限于定義它們的函數(shù)或代碼塊,只有在該范圍內(nèi)才能訪問(wèn)它們。其他函數(shù)無(wú)法直接訪問(wèn)局部變量。
- 全局變量:全局變量可以被程序中的任何函數(shù)訪問(wèn),只要它們?cè)诒辉L問(wèn)之前已經(jīng)被聲明。
3、生命周期
- 局部變量:局部變量的生存周期僅限于定義它們的函數(shù)或代碼塊的執(zhí)行時(shí)間。它們?cè)诤瘮?shù)或代碼塊執(zhí)行結(jié)束后會(huì)被銷(xiāo)毀。
- 全局變量:全局變量的生命周期從程序開(kāi)始運(yùn)行直到程序結(jié)束。它們?cè)诔绦蛘麄€(gè)運(yùn)行期間都存在。
4、初始值
-
局部變量:系統(tǒng)不會(huì)對(duì)其默認(rèn)初始化,必須對(duì)局部變量初始化后才能使用,否則,程序運(yùn)行后可能會(huì)異常退出。
-
全局變量:如果沒(méi)有顯式初始化,它們會(huì)被自動(dòng)、默認(rèn)初始化為零或空值,具體取決于數(shù)據(jù)類(lèi)型。
數(shù)據(jù)類(lèi)型 默認(rèn)初始化值 int 0 char '\0' 或 0 float 0.0f double 0.0 指針 NULL
5、內(nèi)存中的位置
- 局部變量:保存在
棧中,函數(shù)被調(diào)用時(shí)才動(dòng)態(tài)地為變量分配存儲(chǔ)單元。 - 全局變量:保存在內(nèi)存的
全局存儲(chǔ)區(qū)中,占用靜態(tài)的存儲(chǔ)單元。
全局變量使用建議:不在必要時(shí)不要使用全局變量。
原因如下:
① 占用內(nèi)存時(shí)間長(zhǎng):全局變量在程序的全部執(zhí)行過(guò)程中都占用存儲(chǔ)單元,而不是僅在需要時(shí)才開(kāi)辟單元。
② 降低了函數(shù)、程序的可靠性和通用性:如果在函數(shù)中引用了全局變量,那么執(zhí)行情況會(huì)受到有關(guān)的外部變量的影響;如果將一個(gè)函數(shù)移到另一個(gè)文件中,還要考慮把有關(guān)的外部變量及其值一起移過(guò)去。但是若該外部變量與其它文件的變量同名時(shí),就會(huì)出現(xiàn)問(wèn)題。
一般要求把C程序中的函數(shù)做成一個(gè)相對(duì)的封閉體,除了可以通過(guò)“實(shí)參—形參”的渠道與外界發(fā)生聯(lián)系外,沒(méi)有其它渠道。這樣的程序移植性好,可讀性強(qiáng)。
③ 程序容易出錯(cuò):使用全局變量過(guò)多,人們往往難以清楚地判斷出每個(gè)瞬時(shí)各個(gè)外部變量的值。由于在各個(gè)函數(shù)執(zhí)行時(shí)都可能改變外部變量的值,程序容易出錯(cuò)。因此,要限制使用全局變量。
5.2 按存儲(chǔ)方式的不同分類(lèi)
在C語(yǔ)言中,每一個(gè)變量都有兩個(gè)屬性: 數(shù)據(jù)類(lèi)型和數(shù)據(jù)的存儲(chǔ)類(lèi)別。存儲(chǔ)類(lèi)別指的是數(shù)據(jù)在內(nèi)存中存儲(chǔ)的方式(如靜態(tài)存儲(chǔ)和動(dòng)態(tài)存儲(chǔ))。在聲明變量時(shí),一般應(yīng)同時(shí)指定其數(shù)據(jù)類(lèi)型和存儲(chǔ)類(lèi)別,也可以采用默認(rèn)方式指定(即如果用戶不指定,系統(tǒng)會(huì)隱含地指定為某一種存儲(chǔ)類(lèi)別)。
變量的存儲(chǔ)有兩種不同的方式: 靜態(tài)存儲(chǔ)方式和動(dòng)態(tài)存儲(chǔ)方式。
從變量值存在的時(shí)間(即生命周期)來(lái)觀察,有的變量在程序運(yùn)行的整個(gè)過(guò)程都是存在的,而有的變量則是在調(diào)用其所在的函數(shù)時(shí)才臨時(shí)分配存儲(chǔ)單元,而在函數(shù)調(diào)用結(jié)束后該存儲(chǔ)單元就馬上釋放了,變量不存在了。
① 動(dòng)態(tài)(自動(dòng))存儲(chǔ)方式
動(dòng)態(tài)存儲(chǔ)方式:在程序運(yùn)行期間根據(jù)需要進(jìn)行動(dòng)態(tài)的分配存儲(chǔ)空間的方式,數(shù)據(jù)存放在動(dòng)態(tài)存儲(chǔ)區(qū)。
在動(dòng)態(tài)存儲(chǔ)區(qū)中存放以下數(shù)據(jù):
- 函數(shù)形參:在調(diào)用函數(shù)時(shí)給形參分配存儲(chǔ)空間。
- 函數(shù)中定義的局部變量且沒(méi)有用關(guān)鍵字static聲明的變量,即自動(dòng)變量。
- 函數(shù)調(diào)用時(shí)的返回地址等。
在調(diào)用該函數(shù)時(shí),系統(tǒng)會(huì)給這些變量分配存儲(chǔ)空間,在函數(shù)調(diào)用結(jié)束時(shí)就自動(dòng)釋放這些存儲(chǔ)空間。因此這類(lèi)局部變量稱(chēng)為自動(dòng)變量。自動(dòng)變量用關(guān)鍵字auto作存儲(chǔ)類(lèi)別的聲明。

實(shí)際上,關(guān)鍵字auto可以省略,不寫(xiě)auto則隱含指定為“自動(dòng)存儲(chǔ)類(lèi)別”,它屬于動(dòng)態(tài)存儲(chǔ)方式。程序中大多數(shù)變量屬于自動(dòng)變量。每個(gè)函數(shù)中的局部變量的生命周期與函數(shù)的執(zhí)行周期相匹配。
auto int b = 3; //等價(jià)于int b = 3;
如果在一個(gè)程序中兩次調(diào)用同一函數(shù),而在此函數(shù)中定義了局部變量,在兩次調(diào)用時(shí),函數(shù)的內(nèi)部變量都會(huì)重新初始化,不會(huì)保留上一次運(yùn)行的值。分配給這些局部變量的存儲(chǔ)空間的地址可能是不相同的。
② 靜態(tài)存儲(chǔ)方式
靜態(tài)存儲(chǔ)方式:在程序運(yùn)行期間數(shù)據(jù)存放在靜態(tài)存儲(chǔ)區(qū)。它們?cè)诔绦蛘麄€(gè)運(yùn)行期間都不釋放,故生命周期存在于程序的整個(gè)運(yùn)行過(guò)程。
局部變量,使用static修飾以后,則使用靜態(tài)存儲(chǔ)方式。
-
有時(shí)希望函數(shù)中的局部變量的值在函數(shù)調(diào)用結(jié)束后不消失而繼續(xù)保留原值,即其占用的存儲(chǔ)單元不釋放,在下一次再調(diào)用該函數(shù)時(shí),該變量已有值(就是上一次函數(shù)調(diào)用結(jié)束時(shí)的值)。這時(shí)就應(yīng)該指定該局部變量為“靜態(tài)局部變量”,用關(guān)鍵字
static進(jìn)行聲明。 -
靜態(tài)局部變量在聲明時(shí)未賦初值,編譯器也會(huì)把它初始化為0。
static int a; // 等同于 static int a = 0;
全局變量大多存放在靜態(tài)存儲(chǔ)區(qū)中(不包括extern修飾和malloc函數(shù)分配的方式),在程序開(kāi)始執(zhí)行時(shí)給全局變量分配存儲(chǔ)區(qū),程序執(zhí)行完畢就釋放。在程序執(zhí)行過(guò)程中它們占據(jù)固定的存儲(chǔ)單元,而不是動(dòng)態(tài)地進(jìn)行分配和釋放。
- 普通全局變量對(duì)整個(gè)工程可見(jiàn),其他文件可以使用extern外部聲明后直接使用。也就是說(shuō)其他文件不能再定義一個(gè)與其相同名字的變量了(否則編譯器會(huì)認(rèn)為它們是同一個(gè)變量)。
- 而全局變量使用static修飾,則稱(chēng)為靜態(tài)全局變量,靜態(tài)全局變量
僅對(duì)當(dāng)前文件可見(jiàn),其他文件不可訪問(wèn),其他文件可以定義與其同名的變量,兩者互不影響。定義不需要與其他文件共享的全局變量時(shí),加上static關(guān)鍵字能夠有效地降低程序模塊之間的耦合,避免不同文件同名變量的沖突,且不會(huì)誤使用。
舉例1:
#include <stdio.h>
void nonStaticFun() {
int n = 10; //動(dòng)態(tài)存儲(chǔ)方式
printf("n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
void staticFun() {
static int n = 10; //靜態(tài)存儲(chǔ)方式
printf("static n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
int main(void) {
nonStaticFun();
staticFun();
printf("\n");
nonStaticFun();
staticFun();
printf("\n");
return 0;
}
運(yùn)行結(jié)果:
n=10
n++=11
static n=10
n++=11
n=10
n++=11
static n=11
n++=12
說(shuō)明:
1、對(duì)靜態(tài)局部變量是在編譯時(shí)賦初值的,即只賦初值一次,在程序運(yùn)行時(shí)它已有初值。以后每次調(diào)用函數(shù)時(shí)不再重新賦初值而只是保留上次函數(shù)調(diào)用結(jié)束時(shí)的值。
2、對(duì)自動(dòng)變量賦初值,不是在編譯時(shí)進(jìn)行的,而是在函數(shù)調(diào)用時(shí)進(jìn)行的,每調(diào)用一次函數(shù)就執(zhí)行一次賦值語(yǔ)句。
舉例2:
#include <stdio.h>
int f(int); //函數(shù)原型
int main() {
int a = 2, i; //(自動(dòng))局部變量
for (i = 0; i < 3; i++)
printf("%d\n", f(a)); //輸出f(a)的值
return 0;
}
int f(int a) {
auto int b = 0; //(自動(dòng))局部變量
static int c = 3; //靜態(tài)局部變量
printf("c = %d\n",c);
b++;
c++;
return (a + b + c);
}
圖示:

運(yùn)行結(jié)果:
c = 3
7
c = 4
8
c = 5
9
補(bǔ)充說(shuō)明:
1、雖然靜態(tài)局部變量在函數(shù)調(diào)用結(jié)束后仍然存在,但其它函數(shù)是不能引用它的。因?yàn)樗蔷植孔兞浚荒鼙槐竞瘮?shù)引用,而不能被其它函數(shù)引用。
2、如果在定義局部變量時(shí)不賦初值的話,則對(duì)靜態(tài)局部變量來(lái)說(shuō),編譯時(shí)自動(dòng)賦初值0(對(duì)數(shù)值型變量)或空字符′\0′(對(duì)字符變量)。而對(duì)自動(dòng)變量來(lái)說(shuō),它的值是一個(gè)不確定的值。這是由于每次函數(shù)調(diào)用結(jié)束后存儲(chǔ)單元已釋放,下次調(diào)用時(shí)又重新另分配存儲(chǔ)單元,而所分配的單元中的內(nèi)容是不可知的。
使用靜態(tài)存儲(chǔ)的弊端及建議:
- 靜態(tài)存儲(chǔ)要久占內(nèi)存(長(zhǎng)期占用不釋放,而不能像動(dòng)態(tài)存儲(chǔ)那樣一個(gè)存儲(chǔ)單元可以先后為多個(gè)變量使用,節(jié)約內(nèi)存)
- 降低了程序的可讀性,當(dāng)調(diào)用次數(shù)多時(shí)往往弄不清靜態(tài)局部變量的當(dāng)前值是什么。
因此,若非必要,不要頻繁使用靜態(tài)局部變量。
【華南理工大學(xué)2018研】設(shè)有下列程序
#include <stdio.h> void ff() { int c = 9; static int a = 1, b = 4; if (b == 4) { a += c; b++; } else { a += c; b--; } printf("a=%d,b=%d\n", a, b); } int main() { ff(); ff(); }則該程序執(zhí)行后,顯示的結(jié)果為( )。
A.a(chǎn)=10,b=5
a=19,b=5
B.a(chǎn)=10,b=4
a=19,b=5
C.a(chǎn)=10,b=4
a=19,b=4
D.a(chǎn)=10,b=5
a=19,b=4【答案】D
【解析】第一次調(diào)用ff()時(shí),c=9,a=1,b=4,程序進(jìn)入if語(yǔ)句塊,執(zhí)行完后a=10,b=5;第二次調(diào)用ff(),因?yàn)閍、b是靜態(tài)變量,所以a、b的值不會(huì)重新初始化,所以進(jìn)入else語(yǔ)句塊,執(zhí)行完后a=19,b=4,答案選D。
5.3 其它變量修飾符(了解)
5.3.1 寄存器變量(register變量)
一般情況下,變量(包括靜態(tài)存儲(chǔ)方式和動(dòng)態(tài)存儲(chǔ)方式)的值是存放在內(nèi)存中的。當(dāng)程序中用到哪一個(gè)變量的值時(shí),由控制器發(fā)出指令將內(nèi)存中該變量的值送到運(yùn)算器中。 經(jīng)過(guò)運(yùn)算器進(jìn)行運(yùn)算,如果需要存數(shù),再?gòu)倪\(yùn)算器將數(shù)據(jù)送到內(nèi)存存放。
如果有一些變量使用頻繁(例如,在一個(gè)函數(shù)中執(zhí)行10 000次循環(huán),每次循環(huán)中都要引用某局部變量),則為存取變量的值要花費(fèi)不少時(shí)間。為提高執(zhí)行效率,允許將局部變量的值放在CPU中的寄存器中,需要用時(shí)直接從寄存器取出參加運(yùn)算,不必再到內(nèi)存中去存取。由于對(duì)寄存器的存取速度遠(yuǎn)高于對(duì)內(nèi)存的存取速度,因此這樣做可以提高執(zhí)行效率。這種變量叫做寄存器變量,用關(guān)鍵字register作聲明。如
register int f; //定義f為寄存器變量
由于現(xiàn)在的計(jì)算機(jī)的速度愈來(lái)愈快,性能愈來(lái)愈高, 優(yōu)化的編譯系統(tǒng)能夠識(shí)別使用頻繁的變量,從而自動(dòng)地將這些變量放在寄存器中,而不需要程序設(shè)計(jì)者指定。因此,現(xiàn)在實(shí)際上用register聲明變量的必要性不大。
3種局部變量的存儲(chǔ)位置是不同的:
(自動(dòng))局部變量存儲(chǔ)在動(dòng)態(tài)存儲(chǔ)區(qū);
靜態(tài)局部變量存儲(chǔ)在靜態(tài)存儲(chǔ)區(qū);
寄存器存儲(chǔ)在CPU中的寄存器中。
5.3.2 extern修飾變量
一般來(lái)說(shuō),外部變量是在函數(shù)的外部定義的全局變量,它的作用域是從變量的定義處開(kāi)始,到本程序文件的末尾。在此作用域內(nèi),全局變量可以為程序中各個(gè)函數(shù)所引用。但有時(shí)程序員希望能擴(kuò)展外部變量的作用域。
在一個(gè)文件內(nèi)擴(kuò)展全局變量的作用域
如果外部變量不在文件的開(kāi)頭定義,其有效的作用范圍只限于定義處到文件結(jié)束。 在定義點(diǎn)之前的函數(shù)不能引用該外部變量。如果由于某種考慮,在定義點(diǎn)之前的函數(shù)需要引用該外部變量,則應(yīng)該在引用之前用關(guān)鍵字extern對(duì)該變量作“外部變量聲明”,表示把該外部變量的作用域擴(kuò)展到此位置。有了此聲明,就可以從“聲明”處起,合法地使用該外部變量。
舉例:調(diào)用函數(shù),求3個(gè)整數(shù)的最大值。
#include <stdio.h>
int max(); //函數(shù)原型
extern int a, b, c; //把外部變量a,b,c的作用域擴(kuò)展到從此處開(kāi)始
int main() {
printf("輸入3個(gè)整數(shù):");
scanf("%d %d %d", &a, &b, &c); //輸入3個(gè)整數(shù)給a,b,c
printf("max is %d\n", max());
return 0;
}
int a, b, c; //定義外部變量a,b,c
int max() {
int m;
m = (a > b) ? a : b; //把a(bǔ)和b中的大者放在m中
m = (m > c) ? m : c; //將a,b,c三者中的大者放在m中
return m; //返回m的值
}
注意:
1、提倡將外部變量的定義放在引用它的所有函數(shù)之前,這樣可以避免在函數(shù)中多加一個(gè)extern聲明。
2、用extern聲明外部變量時(shí),類(lèi)型名可以寫(xiě)也可以省寫(xiě)。例如,“extern int a,b,c;”也可以寫(xiě)成“extern a,b,c;”。因?yàn)樗皇嵌x變量,可以不指定類(lèi)型,只須寫(xiě)出外部變量名即可。
將全局變量的作用域擴(kuò)展到其它文件
如果一個(gè)程序包含兩個(gè)文件,在兩個(gè)文件中都要用到同一個(gè)外部變量num,不能分別在兩個(gè)文件中各自定義一個(gè)外部變量num,否則在進(jìn)行程序的連接時(shí)會(huì)出現(xiàn)“重復(fù)定義”的錯(cuò)誤。正確的做法是: 在任一個(gè)文件中定義外部變量num,而在另一文件中用extern對(duì)num作“外部變量聲明”,即“extern num; ”。
在編譯和連接時(shí),系統(tǒng)會(huì)由此知道num有“外部鏈接”,可以從別處找到已定義的外部變量num,并將在另一文件中定義的外部變量num的作用域擴(kuò)展到本文件,在本文件中可以合法地引用外部變量num。
舉例:給定b的值,輸入a和m,求a*b和a^m的值。
【file1.c】
#include <stdio.h>
int A; //定義外部變量
int power(int); //函數(shù)聲明
int main() {
int b = 3, c, d, m;
printf("enter the number a and its power m:\n");
scanf("%d,%d", &A, &m);
c = A * b;
printf("%d*%d=%d\n", A, b, c);
d = power(m);
printf("%d**%d=%d\n", A, m, d);
return 0;
}
【file2.c】
extern A;
//把file1中定義的外部變量的作用域擴(kuò)展到本文件
int power(int n) {
int i, y = 1;
for (i = 1; i <= n; i++)
y *= A;
return (y);
}
用這種方法擴(kuò)展全局變量的作用域應(yīng)十分慎重,因?yàn)樵趫?zhí)行一個(gè)文件中的操作時(shí),可能會(huì)改變?cè)撊肿兞康闹担瑫?huì)影響到另一文件中全局變量的值,從而影響該文件中函數(shù)的執(zhí)行結(jié)果。
extern既可以用來(lái)擴(kuò)展外部變量在本文件中的作用域,又可以使外部變量的作用域從一個(gè)文件擴(kuò)展到程序中的其它文件,系統(tǒng)在編譯過(guò)程中遇到extern時(shí),
-
先在本文件中找外部變量的定義,如果找到,就在本文件中擴(kuò)展作用域;
-
如果找不到,就在連接時(shí)從其它文件中找外部變量的定義。如果從其它文件中找到了,就將作用域擴(kuò)展到本文件;
-
如果再找不到,就按出錯(cuò)處理。
將外部變量的作用域限制在本文件中
有時(shí)在程序設(shè)計(jì)中希望某些外部變量只限于被本文件引用,而不能被其它文件引用。這時(shí)可以在定義外部變量時(shí)加一個(gè)static聲明。

這種加上static聲明、只能用于本文件的全局變量稱(chēng)為靜態(tài)全局變量。在程序設(shè)計(jì)中,常由若干人分別完成各個(gè)模塊,各人可以獨(dú)立地在其設(shè)計(jì)的文件中使用相同的外部變量名而互不相干。只須在每個(gè)文件中定義外部變量時(shí)加上static即可。
這就為程序的模塊化、通用性提供方便。如果已確認(rèn)其它文件不需要引用本文件的外部變量,就可以對(duì)本文件中的外部變量都加上static,成為靜態(tài)外部變量,以免被其它文件誤用。
5.3.3 const修飾變量
在C語(yǔ)言中,const 關(guān)鍵字用于創(chuàng)建常量,它指示編譯器將標(biāo)識(shí)符(變量、參數(shù)、函數(shù)等)視為不可修改的值。const關(guān)鍵字可以應(yīng)用于不同的上下文,以下是它的主要用途和使用方法:
1、常量變量聲明:
void func1() {
const int myConstant = 42;
myConstant = 23; //報(bào)錯(cuò),因?yàn)閙yConstant是常量
}
在上面的示例中,myConstant被聲明為一個(gè)整數(shù)常量,它的值在程序的執(zhí)行期間不能被修改。任何嘗試修改它的操作都會(huì)導(dǎo)致編譯器錯(cuò)誤。
2、指向常量的指針:
void func2() {
int num1 = 10;
const int *ptr;
ptr = &num1;
*ptr = 20; //報(bào)錯(cuò)
}
這里,ptr 是一個(gè)指向常量整數(shù)的指針,這意味著你可以使用 ptr 訪問(wèn)整數(shù) x,但不能通過(guò) ptr 修改x的值。
上面這種寫(xiě)法,const 只限制 *ptr 不能修改,而 ptr 本身的地址是可以修改的。
3、常量指針:
如果想限制 ptr不能修改 ,可以把 const 放在 ptr 前面,此時(shí)即為常量指針
void func3() {
int num1 = 10;
int *const ptr = &num1;
int num2 = 20;
ptr = &num2; //報(bào)錯(cuò)
*ptr = 20; //未報(bào)錯(cuò)
}
或
void func(int* const p) {
int x = 13;
p = &x; // 報(bào)錯(cuò)
}
如果想同時(shí)限制修改 ptr 和 *ptr ,需要使用兩個(gè) const 。
void func3() {
int num1 = 10;
const int *const ptr = &num1;
int num2 = 20;
ptr = &num2; //報(bào)錯(cuò)
*ptr = 20; //報(bào)錯(cuò)
}
4、常量參數(shù):
void func4(const int param) {
param = 20; //報(bào)錯(cuò)
}
這個(gè)函數(shù)聲明中,param 是一個(gè)常量參數(shù),這意味著在函數(shù)內(nèi)部不能修改傳遞給它的參數(shù)的值。
5、常量數(shù)組:
void func5() {
const int arr[] = {1, 2, 3, 4};
arr[0] = 10; //報(bào)錯(cuò)
arr[1] = 20; //報(bào)錯(cuò)
}
numbers 數(shù)組被聲明為一個(gè)包含整數(shù)常量的數(shù)組。這表示數(shù)組的每個(gè)元素都是常量,不能被修改。
6、常量結(jié)構(gòu)體:
struct Point {
const int x;
const int y;
};
在結(jié)構(gòu)體聲明中使用 const 可以創(chuàng)建包含常量成員的結(jié)構(gòu)體,這表示結(jié)構(gòu)體的每個(gè)成員都不能被修改。
總結(jié):
const 關(guān)鍵字有助于編程中的可讀性和代碼的安全性,因?yàn)樗沟镁幾g器能夠在編譯時(shí)捕獲對(duì)常量的非法修改。在編寫(xiě)代碼時(shí),使用 const 可以明確表達(dá)你的意圖,告訴其他人或?qū)?lái)的自己這個(gè)值不應(yīng)該被修改。
【四川大學(xué)2017研】在C語(yǔ)言中,形參的缺省存儲(chǔ)類(lèi)型是( )。
A.a(chǎn)uto
B.register
C.static
D.extern【答案】A
【解析】形參是局部變量,缺省類(lèi)型為auto型。
小結(jié):


浙公網(wǎng)安備 33010602011771號(hào)