概述
指針函數(shù)和函數(shù)指針是C語(yǔ)言里兩個(gè)比較繞的概念。但是不僅面試題愛(ài)考,實(shí)際應(yīng)用中也比較廣泛濒旦。很多人因?yàn)楦悴磺暹@兩個(gè)概念,干脆就避而遠(yuǎn)之再登,我剛接觸C語(yǔ)言的時(shí)候?qū)@兩個(gè)概念也比較模糊尔邓,特別是當(dāng)指針函數(shù)、函數(shù)指針锉矢、函數(shù)指針變量梯嗽、函數(shù)指針數(shù)組放在一塊的時(shí)候,能把強(qiáng)迫癥的人活活逼瘋沽损。
其實(shí)如果理解了這些概念的本質(zhì)灯节,是不需要死記硬背的,理解起來(lái)也比較容易绵估。
指針函數(shù)
指針函數(shù): 顧名思義炎疆,它的本質(zhì)是一個(gè)函數(shù),不過(guò)它的返回值是一個(gè)指針国裳。其聲明的形式如下所示:
ret *func(args, ...);
其中形入,func
是一個(gè)函數(shù),args
是形參列表缝左,ret *
作為一個(gè)整體亿遂,是 func
函數(shù)的返回值,是一個(gè)指針的形式渺杉。
下面舉一個(gè)具體的實(shí)例來(lái)做說(shuō)明:
文件:pointer_func.c
# include <stdio.h>
# include <stdlib.h>
int * func_sum(int n)
{
if (n < 0)
{
printf("error:n must be > 0\n");
exit(-1);
}
static int sum = 0;
int *p = ∑
for (int i = 0; i < n; i++)
{
sum += i;
}
return p;
}
int main(void)
{
int num = 0;
printf("please input one number:");
scanf("%d", &num);
int *p = func_sum(num);
printf("sum:%d\n", *p);
return 0;
}
上例就是一個(gè)指針函數(shù)的例子蛇数,其中,int * func_sum(int n)
就是一個(gè)指針函數(shù)是越, 其功能十分簡(jiǎn)單耳舅,是根據(jù)傳入的參數(shù)n
,來(lái)計(jì)算從0到n的所有自然數(shù)的和英妓,其結(jié)果通過(guò)指針的形式返回給調(diào)用方挽放。
以上代碼的運(yùn)行結(jié)果如下所示:
如果上述代碼使用普通的局部變量來(lái)實(shí)現(xiàn),也是可以的蔓纠,如下所示:
文件:pointer_func2.c
# include <stdio.h>
# include <stdlib.h>
int func_sum2(int n)
{
if (n < 0)
{
printf("error:n must be > 0\n");
exit(-1);
}
int sum = 0;
int i = 0;
for (i = 0; i < n; i++)
{
sum += i;
}
return sum;
}
int main(void)
{
int num = 0;
printf("please input one number:");
scanf("%d", &num);
int ret = func_sum2(num);
printf("sum2:%d\n", ret);
return 0;
}
本案例中辑畦,func_sum2
函數(shù)的功能與指針函數(shù)所實(shí)現(xiàn)的功能完全一樣。
不過(guò)在使用指針函數(shù)時(shí)腿倚,需要注意一點(diǎn)纯出,相信細(xì)心地讀者已經(jīng)發(fā)現(xiàn)了,對(duì)比
func_sum
和func_sum2
函數(shù),除了返回值不一樣之外暂筝,還有一個(gè)不同的地方在于箩言,在func_sum
中,變量sum
使用的是靜態(tài)局部變量焕襟,而func_sum2
函數(shù)中陨收,變量sum
使用的則是普通的變量。如果我們把指針函數(shù)的
sum
定義為普通的局部變量鸵赖,會(huì)是什么結(jié)果呢务漩?不妨來(lái)試驗(yàn)一下:
文件:pointer_func3.c
# include <stdio.h>
# include <stdlib.h>
int * func_sum(int n)
{
if (n < 0)
{
printf("error:n must be > 0\n");
exit(-1);
}
int sum = 0;
int *p = ∑
for (int i = 0; i < n; i++)
{
sum += i;
}
return p;
}
int main(void)
{
int num = 0;
printf("please input one number:");
scanf("%d", &num);
int *p = func_sum(num);
printf("sum:%d\n", *p);
return 0;
}
執(zhí)行以上程序,發(fā)現(xiàn)仍然能得到正確的結(jié)果:
可是如果我們把
main
函數(shù)里面稍微改動(dòng)一下:
int main(void)
{
int num = 0;
printf("please input one number:");
scanf("%d", &num);
int *p = func_sum(num);
printf("wait for a while...\n"); //此處加一句打印
printf("sum:%d\n", *p);
return 0;
}
我們?cè)谳敵?code>sum之前打印一句話它褪,這時(shí)看到得到的結(jié)果完全不是我們預(yù)先想象的樣子饵骨,得到的并不是我們想要的答案。
為什么會(huì)出現(xiàn)上面的結(jié)果呢茫打?
其實(shí)原因在于居触,一般的局部變量是存放于棧區(qū)的,當(dāng)函數(shù)結(jié)束老赤,棧區(qū)的變量就會(huì)釋放掉轮洋,如果我們?cè)诤瘮?shù)內(nèi)部定義一個(gè)變量,在使用一個(gè)指針去指向這個(gè)變量抬旺,當(dāng)函數(shù)調(diào)用結(jié)束時(shí)砖瞧,這個(gè)變量的空間就已經(jīng)被釋放,這時(shí)就算返回了該地址的指針嚷狞,也不一定會(huì)得到正確的值。上面的示例中荣堰,在返回該指針后床未,立即訪問(wèn),的確是得到了正確的結(jié)果振坚,但這只是十分巧合的情況薇搁,如果我們等待一會(huì)兒再去訪問(wèn)該地址,很有可能該地址已經(jīng)被其他的變量所占用渡八,這時(shí)候得到的就不是我們想要的結(jié)果啃洋。甚至更嚴(yán)重的是,如果因此訪問(wèn)到了不可訪問(wèn)的內(nèi)容屎鳍,很有可能造成段錯(cuò)誤等程序崩潰的情況宏娄。
因此,在使用指針函數(shù)的時(shí)候逮壁,一定要避免出現(xiàn)返回局部變量指針的情況孵坚。
那么為什么用了
static
就可以避免這個(gè)問(wèn)題呢?原因是一旦使用了
static
去修飾變量,那么該變量就變成了靜態(tài)變量卖宠。而靜態(tài)變量是存放在數(shù)據(jù)段的巍杈,它的生命周期存在于整個(gè)程序運(yùn)行期間,只要程序沒(méi)有結(jié)束扛伍,該變量就會(huì)一直存在筷畦,所以該指針就能一直訪問(wèn)到該變量。因此刺洒,還有一種解決方案是使用全局變量鳖宾,因?yàn)槿肿兞恳彩欠旁跀?shù)據(jù)段的,但是并不推薦使用全局變量作媚。
函數(shù)指針
與指針函數(shù)不同攘滩,函數(shù)指針 的本質(zhì)是一個(gè)指針,該指針的地址指向了一個(gè)函數(shù)纸泡,所以它是指向函數(shù)的指針漂问。
我們知道,函數(shù)的定義是存在于代碼段女揭,因此蚤假,每個(gè)函數(shù)在代碼段中,也有著自己的入口地址吧兔,函數(shù)指針就是指向代碼段中函數(shù)入口地址的指針磷仰。
其聲明形式如下所示:
ret (*p)(args, ...);
其中,ret
為返回值境蔼,*p
作為一個(gè)整體灶平,代表的是指向該函數(shù)的指針,args
為形參列表箍土。其中p
被稱為函數(shù)指針變量 逢享。
關(guān)于函數(shù)指針的初始化
與數(shù)組類似,在數(shù)組中吴藻,數(shù)組名即代表著該數(shù)組的首地址瞒爬,函數(shù)也是一樣,函數(shù)名即是該數(shù)組的入口地址沟堡,因此侧但,函數(shù)名就是該函數(shù)的函數(shù)指針。
因此航罗,我們可以采用如下的初始化方式:
函數(shù)指針變量 = 函數(shù)名;
下面還是以一個(gè)簡(jiǎn)單的例子來(lái)具體說(shuō)明一下函數(shù)指針的應(yīng)用:
文件:func_pointer.c
#include <stdio.h>
int max(int a, int b)
{
return a > b ? a : b;
}
int main(void)
{
int (*p)(int, int); //函數(shù)指針的定義
//int (*p)(); //函數(shù)指針的另一種定義方式禀横,不過(guò)不建議使用
//int (*p)(int a, int b); //也可以使用這種方式定義函數(shù)指針
p = max; //函數(shù)指針初始化
int ret = p(10, 15); //函數(shù)指針的調(diào)用
//int ret = (*max)(10,15);
//int ret = (*p)(10,15);
//以上兩種寫(xiě)法與第一種寫(xiě)法是等價(jià)的,不過(guò)建議使用第一種方式
printf("max = %d \n", ret);
return 0;
}
上面這個(gè)函數(shù)的功能也十分簡(jiǎn)單伤哺,就是求兩個(gè)數(shù)中較大的一個(gè)數(shù)燕侠。值得注意的是通過(guò)函數(shù)指針調(diào)用的方式者祖。
首先代碼里提供了3種函數(shù)指針定義的方式,這三種方式都是正確的绢彤,比較推薦第一種和第三種定義方式七问。然后對(duì)函數(shù)指針進(jìn)行初始化,前面已經(jīng)提到過(guò)了茫舶,直接將函數(shù)名賦值給函數(shù)指針變量名即可械巡。
上述代碼運(yùn)行的結(jié)果如下:
調(diào)用的時(shí)候,既可以直接使用函數(shù)指針調(diào)用饶氏,也可以通過(guò)函數(shù)指針?biāo)赶虻闹等フ{(diào)用讥耗。
(*p)
所代表的就是函數(shù)指針?biāo)赶虻闹担簿褪呛瘮?shù)本身疹启,這樣調(diào)用自然不會(huì)有問(wèn)題古程。有興趣的同學(xué)可以去試一試。
為什么要使用函數(shù)指針喊崖?
那么挣磨,有不少人就覺(jué)得,本來(lái)很簡(jiǎn)單的函數(shù)調(diào)用荤懂,搞那么復(fù)雜干什么茁裙?其實(shí)在這樣比較簡(jiǎn)單的代碼實(shí)現(xiàn)中不容易看出來(lái),當(dāng)項(xiàng)目比較大节仿,代碼變得復(fù)雜了以后晤锥,函數(shù)指針就體現(xiàn)出了其優(yōu)越性。
舉個(gè)例子廊宪,如果我們要實(shí)現(xiàn)數(shù)組的排序矾瘾,我們知道,常用的數(shù)組排序方法有很多種箭启,比如快排霜威,插入排序,冒泡排序册烈,選擇排序等,如果不管內(nèi)部實(shí)現(xiàn)婿禽,你會(huì)發(fā)現(xiàn)赏僧,除了函數(shù)名不一樣之外,返回值扭倾,包括函數(shù)入?yún)⒍际窍嗤牡砹悖@時(shí)候如果要調(diào)用不同的排序方法,就可以使用指針函數(shù)來(lái)實(shí)現(xiàn)膛壹,我們只需要修改函數(shù)指針初始化的地方驾中,而不需要去修改每個(gè)調(diào)用的地方(特別是當(dāng)調(diào)用特別頻繁的時(shí)候)唉堪。
回調(diào)函數(shù)
函數(shù)指針的一個(gè)非常典型的應(yīng)用就是回調(diào)函數(shù)。
什么是回調(diào)函數(shù)肩民?
回調(diào)函數(shù)就是一個(gè)通過(guò)指針函數(shù)調(diào)用的函數(shù)唠亚。其將函數(shù)指針作為一個(gè)參數(shù),傳遞給另一個(gè)函數(shù)持痰。
回調(diào)函數(shù)并不是由實(shí)現(xiàn)方直接調(diào)用灶搜,而是在特定的事件或條件發(fā)生時(shí)由另外一方來(lái)調(diào)用的。
同樣我們來(lái)看一個(gè)回調(diào)函數(shù)的例子:
文件:callback.c
#include<stdio.h>
#include<stdlib.h>
//函數(shù)功能:實(shí)現(xiàn)累加求和
int func_sum(int n)
{
int sum = 0;
if (n < 0)
{
printf("n must be > 0\n");
exit(-1);
}
for (int i = 0; i < n; i++)
{
sum += i;
}
return sum;
}
//這個(gè)函數(shù)是回調(diào)函數(shù)工窍,其中第二個(gè)參數(shù)為一個(gè)函數(shù)指針割卖,通過(guò)該函數(shù)指針來(lái)調(diào)用求和函數(shù),并把結(jié)果返回給主調(diào)函數(shù)
int callback(int n, int (*p)(int))
{
return p(n);
}
int main(void)
{
int n = 0;
printf("please input number:");
scanf("%d", &n);
printf("the sum from 0 to %d is %d\n", n, callback(n, func_sum)); //此處直接調(diào)用回調(diào)函數(shù)患雏,而不是直接調(diào)用func_sum函數(shù)
return 0;
}
上面這個(gè)簡(jiǎn)單的demo就是一個(gè)比較典型的回調(diào)函數(shù)的例子鹏溯。在這個(gè)程序中,回調(diào)函數(shù)callback
無(wú)需關(guān)心func_sum
是怎么實(shí)現(xiàn)的淹仑,只需要去調(diào)用即可丙挽。
這樣的好處就是,如果以后對(duì)求和函數(shù)有優(yōu)化攻人,比如新寫(xiě)了個(gè)func_sum2
函數(shù)的實(shí)現(xiàn)取试,我們只需要在調(diào)用回調(diào)函數(shù)的地方將函數(shù)指針指向func_sum2
即可,而無(wú)需去修改callback
函數(shù)內(nèi)部怀吻。
以上代碼的輸出結(jié)果如下:
回調(diào)函數(shù)廣泛用于開(kāi)發(fā)場(chǎng)景中瞬浓,比如信號(hào)函數(shù)、線程函數(shù)等蓬坡,都使用到了回調(diào)函數(shù)的知識(shí)猿棉。