指針函數(shù)與函數(shù)指針

概述

指針函數(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 = &sum;
    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é)果如下所示:

運(yùn)行結(jié)果1

如果上述代碼使用普通的局部變量來(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)的功能完全一樣。

運(yùn)行結(jié)果2

不過(guò)在使用指針函數(shù)時(shí)腿倚,需要注意一點(diǎn)纯出,相信細(xì)心地讀者已經(jīng)發(fā)現(xiàn)了,對(duì)比func_sumfunc_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 = &sum;
    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é)果:

運(yùn)行結(jié)果3

可是如果我們把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ù)先想象的樣子饵骨,得到的并不是我們想要的答案。

運(yùn)行結(jié)果4

為什么會(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é)果如下:

運(yùn)行結(jié)果5

調(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é)果如下:

運(yùn)行結(jié)果6

回調(diào)函數(shù)廣泛用于開(kāi)發(fā)場(chǎng)景中瞬浓,比如信號(hào)函數(shù)、線程函數(shù)等蓬坡,都使用到了回調(diào)函數(shù)的知識(shí)猿棉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屑咳,隨后出現(xiàn)的幾起案子萨赁,更是在濱河造成了極大的恐慌,老刑警劉巖兆龙,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杖爽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡紫皇,警方通過(guò)查閱死者的電腦和手機(jī)慰安,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)聪铺,“玉大人化焕,你說(shuō)我怎么就攤上這事×逄蓿” “怎么了撒桨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵查刻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我凤类,道長(zhǎng)穗泵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任踱蠢,我火速辦了婚禮火欧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茎截。我一直安慰自己苇侵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布企锌。 她就那樣靜靜地躺著榆浓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撕攒。 梳的紋絲不亂的頭發(fā)上陡鹃,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音抖坪,去河邊找鬼萍鲸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛擦俐,可吹牛的內(nèi)容都是我干的脊阴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蚯瞧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘿期!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起埋合,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤备徐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后甚颂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜜猾,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年振诬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓣铣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贷揽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梦碗,到底是詐尸還是另有隱情禽绪,我是刑警寧澤蓖救,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站印屁,受9級(jí)特大地震影響循捺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雄人,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一从橘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧础钠,春花似錦恰力、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至很钓,卻和暖如春香府,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背码倦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工企孩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人袁稽。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓勿璃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親运提。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝗柔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355