原文地址:https://www.yanbinghu.com/2019/01/03/3593.htmlcss
函數指針是什麼?如何使用函數指針?函數指針到底有什麼大用?本文將一一介紹。html
若是有int *類型變量,它存儲的是int類型變量的地址;那麼對於函數指針來講,它存儲的就是函數的地址。函數也是有地址的,函數實際上由載入內存的一些指令組成,而指向函數的指針存儲了函數指令的起始地址。如此看來,函數指針並無什麼特別的。咱們能夠查看程序中函數的地址:數據庫
#include <stdio.h>
int test()
{
printf("this is test function");
return 0;
}
int main(void)
{
test();
return 0;
}
編譯:編程
gcc -o testFun testFun.c
查看test函數相對地址(並不是實際運行時的地址):數組
$ nm testFun |grep test #查看test函數的符號表信息
0000000000400526 T test
聲明普通類型指針時,須要指明指針所指向的數據類型,而聲明函數指針時,也就要指明指針所指向的函數類型,即須要指明函數的返回類型和形參類型。例如對於下面的函數原型:bash
int sum(int,int);
它是一個返回值爲int類型,參數是兩個int類型的函數,那麼如何聲明該類型函數的指針呢?很簡單,將函數名替換成(*pf)形式便可,即咱們把sum替換成(*fp)便可,fp爲函數指針名,結果以下:微信
int (*fp)(int,int);
這樣就聲明瞭和sum函數類型相同的函數指針fp。這裏說明兩點,第一,*和fp爲一體,說明了fp爲指針類型,第二,*fp須要用括號括起來,不然就會變成下面的狀況:數據結構
int *fp(int,int);
這種狀況下,意思就截然不同了,它聲明瞭一個參數爲兩個int類型,返回值爲int類型的指針的函數,而再也不是一個函數指針了。函數
在常用函數指針以後,咱們很快就會發現,每次聲明函數指針都要帶上長長的形參和返回值,很是不便。這個時候,咱們應該想到使用typedef,即爲某類型的函數指針起一個別名,使用起來就方便許多了。例如,對於前面提到的函數可使用下面的方式聲明:oop
typedef int (*myFun)(int,int);//爲該函數指針類型起一個新的名字
myFun f1; //聲明myFun類型的函數指針f1
上面的myFun就是一個函數指針類型,在其餘地方就能夠很方便地用來聲明變量了。typedef的使用不在本文的討論範圍,可是特別強調一句,typedef中聲明的類型在變量名的位置出現,理解了這一句,也就很容易使用typedef了。於是下面的方式是錯誤的:
typedef myFun (int)(int,int); //錯誤
typedef (int)(int,int) *myFun; //錯誤
賦值也很簡單,既然是指針,將對應指針類型賦給它既可。例如:
#include<stdio.h>
int test(int a,int b)
{
/*do something*/
return 0
}
typedef int(*fp)(int,int);
int main(void)
{
fp f1 = test; //表達式1
fp f2 = &test;//表達式2
printf("%p\n",f1);
printf("%p\n",f2);
return 0;
}
在這裏,聲明瞭返回類型爲int,接受兩個int類型參數的函數指針f1和f2,分別給它們進行了賦值。表達式1和表達式2在做用上並無什麼區別。由於函數名在被使用時老是由編譯器把它轉換爲函數指針,而前面加上&不過顯式的說明了這一點罷了。
調用也很容易,把它當作一個普通的函數名便可:
#include<stdio.h>
int test(int a,int b)
{
/*do something*/
printf("%d,%d\n",a,b);
return 0
}
typedef int(*fp)(int,int);
int main(void)
{
fp f = test;
f(1,2);//表達式1
(*f)(3,4);//表達式2
return 0;
}
在函數指針後面加括號,並傳入參數便可調用,其中表達式1和表達式2彷佛均可以成功調用,可是哪一個是正確的呢?ANSI C認爲這兩種形式等價。
函數指針的應用場景比較多,以庫函數qsort排序函數爲例,它的原型以下:
void qsort(void *base,size_t nmemb,size_t size , int(*compar)(const void *,const void *));
看起來很複雜對不對?拆開來看以下:
void qsort(void *base, size_t nmemb, size_t size, );
拿掉第四個參數後,很容易理解,它是一個無返回值的函數,接受4個參數,第一個是void*類型,表明原始數組,第二個是size_t類型,表明數據數量,第三個是size_t類型,表明單個數據佔用空間大小,而第四個參數是函數指針。這第四個參數,即函數指針指向的是什麼類型呢?
int(*compar)(const void *,const void *)
很顯然,這是一個接受兩個const void*類型入參,返回值爲int的函數指針。
到這裏也就很清楚了。這個參數告訴qsort,應該使用哪一個函數來比較元素,即只要咱們告訴qsort比較大小的規則,它就能夠幫咱們對任意數據類型的數組進行排序。
在這裏函數指針做爲了參數,而他一樣能夠做爲返回值,建立數組,做爲結構體成員變量等等,它們的具體應用咱們在後面的文章中會介紹,本文不做展開。本文只介紹一個簡單實例。
咱們經過一個實例來看函數指針怎麼使用。假設有一學生信息,須要按照學生成績進行排序,該如何處理呢?
#include <stdio.h>
#include <stdlib.h>
#define STU_NAME_LEN 16
/*學生信息*/
typedef struct student_tag
{
char name[STU_NAME_LEN]; //學生姓名
unsigned int id; //學生學號
int score; //學生成績
}student_t;
int studentCompare(const void *stu1,const void *stu2)
{
/*強轉成須要比較的數據結構*/
student_t *value1 = (student_t*)stu1;
student_t *value2 = (student_t*)stu2;
return value1->score-value2->score;
}
int main(void)
{
/*建立三個學生信息*/
student_t stu1 = {"one",1,99};
student_t stu2 = {"two",2,77};
student_t stu3 = {"three",3,88};
student_t stu[] = {stu1,stu2,stu3};
/*排序,將studentCompare做爲參數傳入qsort*/
qsort((void*)stu,3,sizeof(student_t),studentCompare);
int loop = 0;
/**遍歷輸出*/
for(loop = 0; loop < 3;loop++)
{
printf("name:%s,id:%u,score:%d\n",stu[loop].name,stu[loop].id,stu[loop].score);
}
return 0;
}
咱們建立了一個學生信息結構,結構成員包括名字,學號和成績。main函數中建立了一個包含三個學生信息的數組,並使用qsort函數對數組按照學生成績進行排序。qsort函數第四個參數是函數指針,所以咱們須要傳入一個函數指針,而且這個函數指針的入參是cont void *類型,返回值爲int。咱們經過前面的學習知道了函數名自己就是指針,所以只須要將咱們本身實現的studentCompare做爲參數傳入便可。
最終運行結果以下:
name:two,id:2,score:77
name:three,id:3,score:88
name:one,id:1,score:99
能夠看到,最終學生信息按照分數從低到高輸出。
本文介紹了函數指針的聲明和簡單使用。更多使用將在後面的文章介紹,本文總結以下:
微信公衆號【編程珠璣】:專一但不限於分享計算機編程基礎,Linux,C語言,C++,Python,數據庫等編程相關[原創]技術文章,號內包含大量經典電子書和視頻學習資源。歡迎一塊兒交流學習,一塊兒修煉計算機「內功」,知其然,更知其因此然。