你必須知道的指針基礎-7.void指針與函數指針

1、不能動的「地址」—void指針

1.1 void指針初探

  void *表示一個「不知道類型」的指針,也就不知道從這個指針地址開始多少字節爲一個數據。和用int表示指針殊途同歸,只是更明確是「指針」。編程

  所以void*只能表示一個地址,不能用來&取值,也不能++--移動指針,所以不知道多少字節是一個數據單位。數組

    int nums[] = {3,5,6,7,9};
    void* ptr1 = nums;
    //int i = *ptr1; // 對於void指針無法直接取值
    int* ptr2 = (int*)nums;
    printf("%d,%d\n",ptr1,ptr2);
    int i = *ptr2;
    printf("%d\n",i);

  從輸出結果能夠看出,不管是無類型的void指針仍是int類型指針,指向的地址都是同樣的:函數

PS:void *就是一個不能動的「地址」,在進行&、移動指針以前必須轉型爲類型指針。spa

1.2 void指針的用途

  這裏咱們看一下咱們以前瞭解的memset函數,其第一個參數就是一個void指針,它能夠幫咱們屏蔽各類不一樣類型指針的差別。以下面代碼所示,咱們既能夠傳入一個int類型數組的指針,也能夠傳入一個char類型數組的指針:指針

    int nums[20];
    memset(nums,0,sizeof(nums));
    char chs[2];
    memset(chs,0,sizeof(chs));

  那麼,咱們也能夠試着本身動手模擬一下這個memset函數,暫且命名爲mymemset吧:code

void mymemset(void *data,int num,int byteSize)
{
    // char就是一個字節,而計算機中是以字節爲單位存儲的
    char *ptr = (char*)data;
    int i;
    for(i=0;i<byteSize;i++)
    {
        *ptr=num;
        ptr++;
    }
}

int main(int argc, char *argv[])
{
    int nums[20];
    mymemset(nums,0,sizeof(nums));
    int i,len=sizeof(nums)/sizeof(int);
    for(i=0;i<len;i++)
    {
        printf("%d ",nums[i]);
    }
    printf("\n");

    return 0;
}

  在這個mymemset函數中,咱們利用void指針接收不一樣類型的指針,利用char類型(一個字節)逐個字節讀取內存中的每個字節,最後依次填充指定的數字。因爲char類型是一個具體類型,因此可使用++或者--進行指針的移動。blog

  對於結構體類型,也可使用咱們的mymemset函數:排序

typedef struct _Person
{
    char *name;
    int age;
} Person;

Person p1;
mymemset(&p1,0,sizeof(Person));
printf("p1.Age:%d\n",p1.age);

  最終的運行結果以下圖所示:接口

void *的用途:在只知道內存,可是不知道是什麼類型的時候。內存

2、函數指針

2.1 指向函數的指針—.NET中委託的原型

  我想用過.NET中的委託的童鞋,對於函數指針應該不會陌生,它是委託的原型。函數指針是一個指向函數的指針,咱們能夠在C中輕鬆地定義一個函數指針:

typedef void (*intFunc)(int i);

  這裏咱們定義了一個無返回值的,只有一個int類型參數的函數指針intFunc。咱們能夠在main函數中使用這個函數指針來指向一個具體的函數(這個具體的函數定義須要和函數指針的定義一致):

    // 聲明一個intFunc類型的函數指針
    intFunc f1 = test1;
    // 執行f1函數指針所指向的代碼區
    f1(8);

  這裏test1函數的定義以下:

void test1(int age)
{
    printf("test1:%d\n",age);
}

  最終運行結果以下圖所示,執行函數指針f1即執行了其所指向的具體的函數:

2.2 函數指針的基本使用

  這裏咱們經過一個小案例來對函數指針作一個基本的使用介紹。相信大部分的C#或Java碼農都很熟悉foreach,那麼咱們就來模擬foreach對int數組中的值進行不一樣的處理。具體體現爲for循環的代碼是複用的,可是怎麼處理這些數據不肯定,所以把處理數據的邏輯由函數指針指定。

void foreachNums(int *nums,int len,intFunc func)
{
    int i;
    for(i=0;i<len;i++)
    {
        int num = nums[i];
        func(num);
    }
}

void printNum(int num)
{
    printf("value=%d\n",num);
}

  在foreachNums函數中,咱們定義了一個intFunc函數指針,printNum函數是知足intFunc定義的一個具體的函數。下面咱們在main函數中將printNum函數做爲函數指針傳遞給foreachNums函數。

    int nums[] = { 1,5,666,23423,223 };
    foreachNums(nums,sizeof(nums)/sizeof(int),printNum);

  最終運行的結果以下圖所示:

  經過函數指針,咱們能夠屏蔽各類具體處理方法的不一樣,也就是將不肯定的因素都依賴於抽象,這也是面向抽象或面向接口編程的精髓。

3、函數指針應用案例

3.1 計算任意類型的最大值-getMax

  (1)定義函數指針及getMax主體:

typedef int (*compareFunc)(void *data1,void *data2);
// getMax 函數參數說明:
// data 待比較數據數組的首地址,uniteSize單元字節個數
// length:數據的長度。{1,3,5,6}:length=4
// 比較data1和data2指向的數據作比較,
// 若是data1>data2,則返回正數
void *getMax(void *data,int unitSize,int length,compareFunc func)
{
    int i;
    char *ptr = (char*)data;
    char *max = ptr;
    
    for(i=1;i<length;i++)
    {
        char *item = ptr+i*unitSize;
        //到底取幾個字節進行比較是func內部的事情
        if(func(item,max)>0)
        {
            max = item;
        }
    }

    return max;
}

  這裏能夠看到,在getMax中到底取幾個字節去比較都是由compareFunc所指向的函數去作,getMax根本不用關心。

  (2)定義符合函數指針定義的不一樣類型的函數:

int intDataCompare(void *data1,void *data2)
{
    int *ptr1 = (int*)data1;
    int *ptr2 = (int*)data2;

    int i1=*ptr1;
    int i2=*ptr2;

    return i1-i2;
}

typedef struct _Dog
{
    char *name;
    int age;
} Dog;

int dogDataCompare(void *data1,void *data2)
{
    Dog *dog1 = (Dog*)data1;
    Dog *dog2 = (Dog*)data2;

    return (dog1->age)-(dog2->age);
}

  (3)在main函數中針對int類型和結構體類型進行調用:

int main(int argc, char *argv[])
{
    // test1:int類型求最大值
    int nums[] = { 3,5,8,7,6 };
    int *pMax = (int *)getMax(nums,sizeof(int),sizeof(nums)/sizeof(int),
        intDataCompare);
    int max = *pMax;
    printf("%d\n",max);
    // test2:結構體類型求最大值
    Dog dogs[] ={{"沙皮",3},{"臘腸",10},{"哈士奇",5},
        {"京巴",8},{"大狗",2}};
    Dog *pDog = (Dog *)getMax(dogs,sizeof(Dog),
        sizeof(dogs)/sizeof(Dog),dogDataCompare);
    printf("%s=%d",pDog->name,pDog->age);

    return 0;
}

  最終運行結果以下圖所示:

3.2 C中自帶的qsort函數—自定義排序

  qsort包含在<stdlib.h>頭文件中,此函數根據你給的比較條件進行快速排序,經過指針移動實現排序。排序以後的結果仍然放在原數組中。使用qsort函數必須本身寫一個比較函數。咱們能夠看看qsort函數的原型:

 void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void * ) );

  能夠看出,qsort的第四個參數就是一個函數指針!其所指向的函數應該是一個返回值爲int類型的,參數爲兩個void指針。那麼,咱們可使用上面咱們已經寫好的兩個compare方法做爲參數傳入qsort來對上面的int數組和結構體數組進行快速排序。

    int nums[] = { 3,5,8,7,6 };
    qsort(nums,sizeof(nums)/sizeof(int),sizeof(int),intDataCompare);
    int i;
    for(i=0;i<sizeof(nums)/sizeof(int);i++)
    {
        printf("%d ",nums[i]);
    }
    printf("\n");

    Dog dogs[] ={{"沙皮",3},{"臘腸",10},{"哈士奇",5},
        {"京巴",8},{"大狗",2}};
    qsort(dogs,sizeof(dogs)/sizeof(Dog),sizeof(Dog),dogDataCompare);
    for(i=0;i<sizeof(dogs)/sizeof(Dog);i++)
    {
        printf("%s %d ",dogs[i].name,dogs[i].age);
    }

  那麼,快速排序後是否有結果呢?答案是確定的,咱們能夠傳入各類比較方法,能夠升序排序也能夠降序排序。

參考資料

  如鵬網,《C語言也能幹大事(第三版)》

 

相關文章
相關標籤/搜索