C語(yǔ)言-函數(shù)指針(Function Pointer)及進(jìn)階

前言


初學(xué)C語(yǔ)言的童鞋游添,通常在學(xué)完函數(shù)和指針的知識(shí)后况褪,已經(jīng)是萌萌噠,學(xué)習(xí)到了函數(shù)指針(請(qǐng)注意不是函數(shù)和指針)渊季,更是整個(gè)人都不好了朋蔫,這篇文章的目的,就是幫助我的童鞋們理解函數(shù)指針却汉。?

函數(shù)指針概述


首先我們需要回顧一下函數(shù)的作用:完成某一特定功能的代碼塊驯妄。
再來(lái)回憶一下指針的作用:一種特殊的變量,用來(lái)保存地址值合砂,某類型的指針指向某類型的地址青扔。
下面定義了一個(gè)求兩個(gè)數(shù)最大值的函數(shù):

int maxValue (int a, int b) {
    return a > b ? a : b;
}     

而這段代碼編譯后生成的CPU指令存儲(chǔ)在代碼區(qū),而這段代碼其實(shí)是可以獲取其地址的翩伪,而其地址就是函數(shù)名微猖,我們可以使用指針存儲(chǔ)這個(gè)函數(shù)的地址——函數(shù)指針。
函數(shù)指針其實(shí)就是一種特殊的指針——指向一個(gè)函數(shù)的指針缘屹。在很多高級(jí)語(yǔ)言中凛剥,它的思想是很重要的,尤其是它的“回調(diào)函數(shù)”轻姿,所以理解它是很有必要的当悔。

函數(shù)指針定義與使用


任何變量定義都包含三部分: 變量類型 + 變量名 = 初值,那么定義一個(gè)函數(shù)指針踢代,首先我們需要知道要定義一個(gè)什么樣的函數(shù)指針(指針類型)盲憎,那么問(wèn)題來(lái)了,函數(shù)的類型又是什么呢胳挎?我們繼續(xù)分析這段代碼:

int maxValue (int a, int b) {
    return a > b ? a : b;
}    

這個(gè)函數(shù)的類型是有兩個(gè)整型參數(shù)饼疙,返回值是個(gè)整型。對(duì)應(yīng)的函數(shù)指針類型:

int (*) (int a, int b);  

對(duì)應(yīng)的函數(shù)指針定義:

int (*p)(int x, int  y);    

參數(shù)名可以去掉,并且通常都是去掉的窑眯。這樣指針p就可以保存函數(shù)類型為兩個(gè)整型參數(shù)屏积,返回值是整型的函數(shù)地址了。

int (*p)(int, int);

通過(guò)函數(shù)指針調(diào)用函數(shù):

int (*p)(int, int) = NULL;
p = maxValue;
p(20, 45);

回調(diào)函數(shù)


上述內(nèi)容是函數(shù)指針的基礎(chǔ)用法磅甩,然而我們可以看得出來(lái)炊林,直接使用函數(shù)maxValue豈不是更方便?沒(méi)錯(cuò)卷要,其實(shí)函數(shù)指針更重要的意義在于函數(shù)回調(diào)渣聚,而上述內(nèi)容只是一個(gè)鋪墊。
舉個(gè)例子:
現(xiàn)在我們有這樣一個(gè)需求:實(shí)現(xiàn)一個(gè)函數(shù)僧叉,將一個(gè)整形數(shù)組中比50大的打印在控制臺(tái)奕枝,我們可能這樣實(shí)現(xiàn):

void compareNumberFunction(int *numberArray, int count, int compareNumber) {
    for (int i = 0; i < count; i++) {
        if (*(numberArray + i) > compareNumber) {
            printf("%d\n", *(numberArray + i));
        }
    }
}
int main() {

    int numberArray[5] = {15, 34, 44, 56, 64};
    int compareNumber = 50;
    compareNumberFunction(numberArray, 5, compareNumber);

    return 0;
}   

這樣實(shí)現(xiàn)是沒(méi)有問(wèn)題的,然而現(xiàn)在我們又有這樣一個(gè)需求:實(shí)現(xiàn)一個(gè)函數(shù)瓶堕,將一個(gè)整形數(shù)組中比50小的打印在控制臺(tái)隘道。”What the fuck!”對(duì)于提需求者郎笆,你可能此時(shí)的心情是這樣:


</img>
然而回到現(xiàn)實(shí)谭梗,這種需求是不可避免的,你可能想過(guò)復(fù)制粘貼宛蚓,更改一下判斷條件默辨,然而作為開(kāi)發(fā)者,我們要未雨綢繆苍息,要考慮到將來(lái)可能添加更多類似的需求,那么你將會(huì)有大量的重復(fù)代碼壹置,使你的項(xiàng)目變得臃腫竞思,所以這個(gè)時(shí)候我們需要冷靜下來(lái)思考,其實(shí)這兩個(gè)需求很多代碼都是相同的钞护,只要更改一下判斷條件即可盖喷,而判斷條件我們?nèi)绾巫兊酶屿`活呢?這時(shí)候我們就用到回調(diào)函數(shù)的知識(shí)了难咕,我們可以定義一個(gè)函數(shù)课梳,這個(gè)函數(shù)需要兩個(gè)int型參數(shù),函數(shù)內(nèi)部實(shí)現(xiàn)代碼是將兩個(gè)整形數(shù)字做比較余佃,將比較結(jié)果的bool值作為函數(shù)的返回值返回出來(lái)暮刃,以大于被比較數(shù)字的情況為例:

BOOL compareGreater(int number, int compareNumber) {
    return number > compareNumber;
}   

同理,小于被比較的數(shù)字函數(shù)定義如下:

BOOL compareLess(int number, int compareNumber) {
    return number < compareNumber;
}

接下來(lái)爆土,我們可以將這個(gè)函數(shù)作為compareNumberFunction的一個(gè)參數(shù)進(jìn)行傳遞(沒(méi)錯(cuò)椭懊,函數(shù)可以作為參數(shù)),那么我們就需要一個(gè)函數(shù)指針獲取函數(shù)的地址步势,從而在compareNumberFunction內(nèi)部進(jìn)行對(duì)函數(shù)的調(diào)用氧猬,于是背犯,compareNumberFunction函數(shù)的定義變成了這樣:

void compareNumberFunction(int *numberArray, int count, int compareNumber, BOOL (*p)(int, int)) {
    for (int i = 0; i < count; i++) {
        if (p(*(numberArray + i), compareNumber)) {
            printf("%d\n", *(numberArray + i));
        }
    }
}

具體使用時(shí)代嗎如下:

int main() {

    int numberArray[5] = {15, 34, 44, 56, 64};
    int compareNumber = 50;
    // 大于被比較數(shù)字情況:
    compareNumberFunction(numberArray, 5, compareNumber, compareGreater);
    // 小于被比較數(shù)字情況:
    compareNumberFunction(numberArray, 5, compareNumber, compareLess);

    return 0;
}

根據(jù)上述案例,我們可以得出結(jié)論:函數(shù)回調(diào)本質(zhì)為函數(shù)指針作為函數(shù)參數(shù)盅抚,函數(shù)調(diào)用時(shí)傳入函數(shù)地址漠魏,這使我們的代碼變得更加靈活,可復(fù)用性更強(qiáng)妄均。

動(dòng)態(tài)排序


上面的案例如果你已經(jīng)理解的話那么動(dòng)態(tài)排序其實(shí)你已經(jīng)懂了柱锹。首先我們應(yīng)該理解動(dòng)態(tài)這個(gè)詞,我的理解就是不同時(shí)刻丛晦,不同場(chǎng)景奕纫,發(fā)生不同的事,這就是動(dòng)態(tài)烫沙。話不多說(shuō)匹层,直接上案例。
需求: 有30個(gè)學(xué)生需要排序
按成績(jī)排
按年齡排

這種無(wú)法預(yù)測(cè)的需求變更锌蓄,就是我們上文說(shuō)的動(dòng)態(tài)場(chǎng)景升筏,那么解決方案就是函數(shù)回調(diào):

typedef struct student{
    char name[20];
    int age;
    float score;
}Student;

//比較兩個(gè)學(xué)生的年齡
BOOL compareByAge(Student stu1, Student stu2) {
    return stu1.age > stu2.age ? YES : NO;
}
//比較兩個(gè)學(xué)生的成績(jī)
BOOL compareByScore(Student stu1, Student stu2) {
    return stu1.score > stu2.score ? YES : NO;
}
void sortStudents(Student *array, int n, BOOL(*p)(Student, Student)) {
    Student temp;
    int flag = 0;
    for (int i = 0; i < n - 1 && flag == 0; i++) {
        flag = 1;
        for (int j = 0; j < n - i - 1; j++) {
            if (p(array[j], array[j + 1])) {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
                flag = 0;
            }
        }
    }
}
int main() {

    Student stu1 = {"小明", 19, 98};
    Student stu2 = {"小紅", 20, 78};
    Student stu3 = {"小白", 21, 88};
    Student stuArray[3] = {stu1, stu2, stu3};
    sortStudents(stuArray, 3, compareByScore);

    return 0;
}

沒(méi)錯(cuò),動(dòng)態(tài)排序就是這么簡(jiǎn)單瘸爽!

函數(shù)指針作為函數(shù)返回值

沒(méi)錯(cuò)您访,既然函數(shù)指針可以作為參數(shù),自然也可以作為返回值剪决。再接著上案例灵汪。
需求:定義一個(gè)函數(shù),通過(guò)傳入功能的名稱獲取到對(duì)應(yīng)的函數(shù)柑潦。



整理一下發(fā)型享言,然后我們分析下需求,當(dāng)前我們需要定義一個(gè)叫做findFunction的函數(shù)渗鬼,這個(gè)函數(shù)傳入一個(gè)字符串之后會(huì)返回一個(gè) int (*)(int, int)類型的函數(shù)指針览露,那么我們這個(gè)函數(shù)的聲明是不是可以寫成這樣呢?

int (*)(int, int) findFunction(char *);   

這看起來(lái)很符合我們的理解譬胎,然而差牛,這并不正確,編譯器無(wú)法識(shí)別兩個(gè)完全并行的包含形參的括號(hào)(int, int)和(char *),真正的形式其實(shí)是這樣:

int (*findFunction(char *))(int, int);  

這種聲明從外觀上看更像是臉滾鍵盤出來(lái)的結(jié)果堰乔,現(xiàn)在讓我們來(lái)逐步的分析一下這個(gè)聲明的組成步驟:

  1. findFunction是一個(gè)標(biāo)識(shí)符
  • findFunction()是一個(gè)函數(shù)
  • findFunction(char *)函數(shù)接受一個(gè)類型為char *的參數(shù)
  • *findFunction(char *)函數(shù)返回一個(gè)指針
  • (*findFunction(char*))()這個(gè)指針指向一個(gè)函數(shù)
  • (*findFunction(char*))(int, int)指針指向的函數(shù)接受兩個(gè)整形參數(shù)
  • int (*findFunction(char *))(int, int)指針指向的函數(shù)返回一個(gè)整形

現(xiàn)在我們的分析已經(jīng)完成了偏化,編譯器可以通過(guò)了,現(xiàn)在程序員瘋了镐侯,這對(duì)我們來(lái)說(shuō)就像鯡魚(yú)罐頭一樣難以下咽夹孔,那么我們是不是有更好的書(shū)寫方式呢?(老司機(jī)友情提示:typedef)

最終代碼演變成了這樣:

// 重定義函數(shù)指針類型
typedef int (*FUNC)(int, int);

// 求最大值函數(shù)
int maxValue(int a, int b) {
    return a > b ? a : b;
}

// 求最小值函數(shù)
int minValue(int a, int b) {
    return a < b ? a : b;
}
// findFunction函數(shù)定義
FUNC findFunction(char *name) {
    if (0 == strcmp(name, "max")) {
        return maxValue;
    } else if (0 == strcmp(name, "min")) {
        return minValue;
    }

    printf("Function name error");
    return NULL;
}   

int main() {

    int (*p)(int, int) = findFunction("max");
    printf("%d\n", p(3, 5));

    int (*p1)(int, int) = findFunction("min");
    printf("min = %d\n", p1(3, 5));

    return 0;
}

到了這里,函數(shù)指針的基礎(chǔ)內(nèi)容已經(jīng)結(jié)束了搭伤,有的同學(xué)還有可能困惑只怎,為什么我要以函數(shù)去獲取函數(shù)呢,直接使用maxValue和minValue不就好了么怜俐,其實(shí)在以后的編程過(guò)程中身堡,很有可能maxValue和minValue被封裝了起來(lái),類的外部是不能直接使用的拍鲤,那么我們就需要這種方式贴谎,如果你學(xué)習(xí)了Objective-C你會(huì)發(fā)現(xiàn),所有的方法調(diào)用的實(shí)現(xiàn)原理都是如此季稳。

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


現(xiàn)在我們應(yīng)該清楚表達(dá)式“char * (*pf)(char * p)”定義的是一個(gè)函數(shù)指針pf擅这。既然pf 是一個(gè)指針,那就可以儲(chǔ)存在一個(gè)數(shù)組里景鼠。把上式修改一下:
char * (*pf[3])(char * p);
這是定義一個(gè)函數(shù)指針數(shù)組仲翎。它是一個(gè)數(shù)組,數(shù)組名為pf铛漓,數(shù)組內(nèi)存儲(chǔ)了3 個(gè)指向函數(shù)的指針溯香。這些指針指向一些返回值類型為指向字符的指針、參數(shù)為一個(gè)指向字符的指針的函數(shù)浓恶。這念起來(lái)似乎有點(diǎn)拗口玫坛。不過(guò)不要緊,關(guān)鍵是你明白這是一個(gè)指針數(shù)組包晰,是數(shù)組湿镀。

函數(shù)指針數(shù)組怎么使用呢?給一個(gè)非常簡(jiǎn)單的例子伐憾,只要真正掌握了使用方法勉痴,再?gòu)?fù)雜的問(wèn)題都可以應(yīng)對(duì)。如下:

char * fun1(char * p)
{
   printf("%s\n",p);
   return p;
}
char * fun2(char * p)
{
   printf("%s\n",p);
   return p;
}
char * fun3(char * p)
{
   printf("%s\n",p);
   return p;
}
int main(){
   char * (*pf[3])(char * p);
   pf[0] = fun1; // 可以直接用函數(shù)名
   pf[1] = &fun2; // 可以用函數(shù)名加上取地址符
   pf[2] = &fun3;
   pf[0]("fun1");
   pf[0]("fun2");
   pf[0]("fun3");
   return 0;
}

是不是感覺(jué)上面的例子太簡(jiǎn)單塞耕,不夠刺激?好嘴瓤,那就來(lái)點(diǎn)刺激的扫外。

函數(shù)指針數(shù)組的指針

看著這個(gè)標(biāo)題沒(méi)發(fā)狂吧?函數(shù)指針就夠一般初學(xué)者折騰了廓脆,函數(shù)指針數(shù)組就更加麻煩筛谚,現(xiàn)在的函數(shù)指針數(shù)組指針就更難理解了。

其實(shí)停忿,沒(méi)這么復(fù)雜驾讲。前面詳細(xì)討論過(guò)數(shù)組指針的問(wèn)題,這里的函數(shù)指針數(shù)組指針不就是一個(gè)指針嘛。只不過(guò)這個(gè)指針指向一個(gè)數(shù)組吮铭,這個(gè)數(shù)組里面存的都是指向函數(shù)的指針时迫。僅此而已。

下面就定義一個(gè)簡(jiǎn)單的函數(shù)指針數(shù)組指針:
char * (*(*pf)[3])(char * p);
注意谓晌,這里的pf 和上面的pf 就完全是兩碼事了掠拳。上一節(jié)的pf 并非指針,而是一個(gè)數(shù)組名纸肉;這里的pf 確實(shí)是實(shí)實(shí)在在的指針溺欧。這個(gè)指針指向一個(gè)包含了3 個(gè)元素的數(shù)組;這個(gè)數(shù)字里面存的是指向函數(shù)的指針柏肪;這些指針指向一些返回值類型為指向字符的指針姐刁、參數(shù)為一個(gè)指向字符的指針的函數(shù)。這比面的函數(shù)指針數(shù)組更拗口烦味。其實(shí)你不用管這么多聂使,明白這是一個(gè)指針就ok 了。其用法與前面講的數(shù)組指針沒(méi)有差別拐叉。下面列一個(gè)簡(jiǎn)單的例子:

char * fun1(char * p)
{
   printf("%s\n",p);
   return p;
}
char * fun2(char * p)
{
   printf("%s\n",p);
   return p;
}
char * fun3(char * p)
{
   printf("%s\n",p);
   return p;
}
int main(){
   char * (*a[3])(char * p);
   char * (*(*pf)[3])(char * p);
   pf = &a;
   a[0] = fun1;
   a[1] = &fun2;
   a[2] = &fun3;
   pf[0][0]("fun1");
   pf[0][1]("fun2");
   pf[0][2]("fun3");
   return 0;
}

好了岩遗,到了這里C語(yǔ)言已經(jīng)沒(méi)有什么能阻擋你了。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凤瘦,一起剝皮案震驚了整個(gè)濱河市宿礁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蔬芥,老刑警劉巖梆靖,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異笔诵,居然都是意外死亡返吻,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門乎婿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)测僵,“玉大人,你說(shuō)我怎么就攤上這事谢翎『纯浚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵森逮,是天一觀的道長(zhǎng)榨婆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)褒侧,這世上最難降的妖魔是什么良风? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任谊迄,我火速辦了婚禮,結(jié)果婚禮上烟央,老公的妹妹穿的比我還像新娘统诺。我一直安慰自己,他們只是感情好吊档,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布篙议。 她就那樣靜靜地躺著,像睡著了一般怠硼。 火紅的嫁衣襯著肌膚如雪鬼贱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天香璃,我揣著相機(jī)與錄音这难,去河邊找鬼。 笑死葡秒,一個(gè)胖子當(dāng)著我的面吹牛姻乓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播眯牧,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蹋岩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了学少?” 一聲冷哼從身側(cè)響起剪个,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎版确,沒(méi)想到半個(gè)月后扣囊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绒疗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年侵歇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吓蘑。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惕虑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出磨镶,到底是詐尸還是另有隱情溃蔫,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布棋嘲,位于F島的核電站酒唉,受9級(jí)特大地震影響矩桂,放射性物質(zhì)發(fā)生泄漏沸移。R本人自食惡果不足惜痪伦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雹锣。 院中可真熱鬧网沾,春花似錦、人聲如沸蕊爵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)攒射。三九已至醋旦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間会放,已是汗流浹背饲齐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咧最,地道東北人捂人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像矢沿,于是被迫代替她去往敵國(guó)和親滥搭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容