函數指針和指針函數

一、函數指針(指向函數的指針)

在c語言中,一個函數老是佔用一段連續的內存區,而函數名就是該函數所佔內存區的首地址(入口地址),因此函數名跟數組名很相似,都是指針常量。node

函數指針就是指向這個入口地址的指針變量,注意函數指針是一個變量。數組

#include<stdio.h>

void f(int);

int main()
{
    //定義函數指針pf並給pf賦值使其指向函數f的入口地址
    //pf先跟*結合,說明pf是一個指針,而後與括號結合,說明這個指針指向函數
    void (*pf)(int)=f;    //等價於void (*pf)(int)=&f;
    pf(1);
    (*pf)(2);//把函數指針轉換成函數名,這個轉換並不須要
    f(3);
    return 0;
}

void f(int a)
{
    printf("%d\n",a);
}

void (*pf)(int)=&f;爲何咱們能夠這樣定義函數指針呢?來自《c和指針》給出了這樣的解釋:函數名被使用時老是由編譯器把它轉換爲函數指針,&操做符只是顯示地說明了編譯器將隱式執行的任務 。函數


二、妙用函數指針一: 函數指針數組

c語言協會按期集中討論一次,每次討論有一個主持者,每一個主持者對應一個函數(函數功能能夠是輸出主持者姓名及討論主題或者完成其餘功能)。如今要編寫這樣一段程序,輸入一個整數i(i>=0),根據輸入的i調用不一樣主持者的函數。很快就能寫出以下代碼:測試

#include<stdio.h>

//各個主持者對應的函數聲明
void Touch();
void DuanJiong();
void MeiKai();
void YinJun();
void JiangHaiLong();

void main()
{
    int i;
    scanf("%d",&i);
    switch(i){
    case 0:
        Touch();
        break;
    case 1:
        DuanJiong();
        break;
    case 2:
        MeiKai();
        break;
    case 3:
        YinJun();
        break;
    case 4:
        JiangHaiLong();
        break;
    }
}

void Touch()
{
    puts("我是Touch");
}

void DuanJiong()
{
    puts("我是段炯");
}

void MeiKai()
{
    puts("我是梅凱");
}

void YinJun()
{
    puts("我是殷俊");
}

void JiangHaiLong()
{
    puts("我是木子");
}

這段代碼有錯誤嗎,確定木有,運行結果徹底正確。可是注意這裏只列出了5種狀況,若是總共有不少種狀況呢,那麼咱們就要寫一大堆的case語句。並且每次都是從case 1 開始判斷。那麼是否能夠簡化代碼而且能讓程序不作這麼多判斷呢?這就引出了函數指針數組,顧名思義,就是存放函數指針的數組。現主函數修改以下所示:spa

void main()
{
    int i;
    void (*p[])()={Touch,DuanJiong,MeiKai,YinJun,JiangHaiLong};
    scanf("%d",&i);
    p[i]();
}

void (*p[])()={Touch,DuanJiong,MeiKai,YinJun,JiangHaiLong};聲明瞭一個函數指針數組並賦值。把每一個函數的入口地址存入這個數組,這樣就不須要用switch語句了,根據下標i直接找到函數入口,省去了判斷的時間。.net

 

三、妙用函數指針二: 回調函數

什麼是回調函數,來着百度百科的解釋:回調函數就是一個經過函數指針調用的函數。若是你把函數的指針(地址)做爲參數傳遞給另外一個函數,當這個指針被用爲調用它所指向的函數時,咱們就說這是回調函數。這裏函數指針是做爲參數傳遞給另外一個函數。設計

你們都寫過冒泡排序吧,其代碼以下:指針

//冒泡排序
void bubbleSort(int *a,int n)
{
    int i,j;
    for(i=1;i<n;i++)
        for(j=1;j<n-i+1;j++){
            if(a[j+1]<a[j]){
                a[j]=a[j]+a[j+1];
                a[j+1]=a[j]-a[j+1];
                a[j]=a[j]-a[j+1];
            }
        }
}

請注意到這樣一個不足,這個冒泡排序只能對int型數組進行排序。若是咱們想寫這樣一個函數,能同時對int型、float型、double型、char型、結構體類型...數組進行排序,該怎麼寫呢?也許你會想到函數重載,可是C語言沒有這個概念。這裏能夠用函數指針來實現,其代碼比重載更簡潔,更高效這也是函數指針的最大用處,參考代碼:code

 

//回調函數對多種數據類型數組進行冒泡排序
//a表示待排序數組
//n表示數組長度
//size表示數組元素大小(即每一個數組元素佔用的字節數)
//int (*compare)(void *,void *) 聲明瞭一個函數指針,在此做爲參數
//void *類型的指針表示指向未知類型的指針,編譯器並不會給void類型的指針分配空間,但咱們能夠把它進行強制類型轉換
void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *))
{
    int i,j,k;
    char *p,*q;
    char temp;//交換時暫存一個字節的數據
    for(i=0;i<n;i++)
        for(j=0;j<n-i-1;j++){
            //注意p,q都是字符類型的指針,加一都只移動一個字節
            p=(char*)a+j*size;
                        q=(char*)a+(j+1)*size;
          if(compare(p,q)>0){
                //一個一個字節的交換,從而實現了一個數據類型數據的交換
                for(k=0;k<size;k++){
                    temp=*p;
                    *p=*q;
                    *q=temp;
                    p++;
                    q++;
            }
            }
        }
}         

請注意代碼中紅色部分代碼,要看懂這段代碼需明確兩個問題:(1)void*類型的指針未分配空間的,咱們能夠把它進行強制類型轉換成char*。(2)對數組元素進行交換時,並非一次就把兩個數交換了,由於咱們並不知道數據的確切類型。但知道數組元素的大小,這樣就能夠逐個字節進行交換。好比對int類型(佔用四個字節)的值a、b進行交換,先交換a、b的第一個字節,而後第二個字節...blog

理解了這個代碼,該怎麼用呢?參數要傳入一個函數指針,因而必需要寫一個比較兩個數大小的函數,且函數原型必須與int (*compare)(void *,void *)相匹配。下面是測試各類類型數組排序的代碼:

#include<stdio.h>

typedef struct{
    int data;
}Node;

//函數聲明
int charCompare(void *a,void *b);
int intCompare(void *a,void *b);
int floatCompare(void *a,void *b);
int doubleCompare(void *a,void *b);
int nodeCompare(void *a,void *b);
void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *));

//比較兩個char類型的數據的大小,a>b返回1,a<b返回-1,a==b返回0
int charCompare(void *a,void *b)
{
    if(*(char*)a==*(char*)b)
        return 0;
    return *(char*)a>*(char*)b?1:-1;
}
//比較兩個int類型的數據的大小
int intCompare(void *a,void *b)
{
    if(*(int*)a==*(int*)b)
        return 0;
    return *(int*)a>*(int*)b?1:-1;
}
//比較兩個float類型的數據的大小
int floatCompare(void *a,void *b)
{
    if(*(float*)a==*(float*)b)
        return 0;
    return *(float*)a>*(float*)b?1:-1;
}
//比較兩個double類型的數據的大小
int doubleCompare(void *a,void *b)
{
    if(*(double*)a==*(double*)b)
        return 0;
    return *(double*)a>*(double*)b?1:-1;
}
//比較兩個結構體類型(Node)的數據的大小
int nodeCompare(void *a,void *b)
{
    if(((Node*)a)->data == ((Node*)b)->data)
        return 0;
    return ((Node*)a)->data > ((Node*)b)->data ? 1 : -1;
}

void main()
{
    int i=0;
    //用於測試的各類類型數組
    char c[]={'d','a','c','e','b'};
    int a[]={3,2,4,0,1};
    float f[]={4.4,5.5,3.3,0,1};
    double b[]={4.4,5.5,3.3,0,1};
     Node n[]={{2},{0},{1},{4},{3}};

    //對各類數組進行排序
    puts("對char類型數組進行排序:");
    bubbleSort(c,5,sizeof(char),charCompare);
    for(i=0;i<5;i++)
        printf("%c ",c[i]);
    puts("");

    puts("對int類型數組進行排序:");
    bubbleSort(a,5,sizeof(int),intCompare);
    for(i=0;i<5;i++)
        printf("%d ",a[i]);
    puts("");

    puts("對float類型數組進行排序:");
    bubbleSort(f,5,sizeof(float),floatCompare);
    for(i=0;i<5;i++)
        printf("%.2f ",f[i]);
    puts("");

    puts("對double類型數組進行排序:");
    bubbleSort(b,5,sizeof(double),doubleCompare);
    for(i=0;i<5;i++)
        printf("%.2lf ",b[i]);
    puts("");

    puts("對結構體(Node)類型數組進行排序:");
    bubbleSort(n,5,sizeof(Node),nodeCompare);
    for(i=0;i<5;i++)
        printf("%d ",n[i].data);
    puts("");
}


//回調函數對多種數據類型數組進行冒泡排序
//a表示待排序數組
//n表示數組長度
//size表示數組元素大小(即每一個數組元素佔用的字節數)
//int (*compare)(void *,void *) 聲明瞭一個函數指針,在此做爲參數
//void *類型的指針表示指向未知類型的指針,編譯器並不會給void類型的指針分配空間,但咱們能夠把它進行強制類型轉換
void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *))
{
    int i,j,k;
    char *p,*q;
    char temp;//交換時暫存一個字節的數據
    for(i=0;i<n;i++)
        for(j=0;j<n-i-1;j++){
            //注意p,q都是字符類型的指針,加一都只移動一個字節
            p=(char*)a+j*size;
            q=(char*)a+(j+1)*size;
            if(compare(p,q)>0){
                //一個一個字節的交換,從而實現了一個數據類型數據的交換
                for(k=0;k<size;k++){
                    temp=*p;
                    *p=*q;
                    *q=temp;
                    p++;
                    q++;
                }
            }
        }
}

運行結果:

再看看C語言標準庫中的快速排序函數,它的實現原理及用法同上述冒泡排序

 

四、指針函數(返回指針的函數,確切的說是返回指針類型的函數)

#include<stdlib.h>
#include<stdio.h>

//建立長度爲n的動態數組
//這是一個指針函數
int* array(int n)
{
    int *a=(int*)malloc(sizeof(int)*n);
    return a;
}
void main()
{ 
    int i,n=3;
    int *a=array(n);
    for(i=0;i<n;i++)
        a[i]=i;
    free(a);//注意a不用時要free掉,不然內存泄露
}

五、參考資料

《C和指針》、《the c programming language》、《c語言程序設計》譚浩強版、c標準庫、《冒泡排序 && 快速排序 》  、其它網上資料

相關文章
相關標籤/搜索