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


一、不能動的“地址”—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 *就是一個不能動的“地址”,在進行&、移動指針之前必須轉型為類型指針。

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吧:

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類型是一個具體類型,所以可以使用++或者--進行指針的移動。

  對於結構體類型,也可以使用我們的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.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.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語言也能干大事(第三版)》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM