點擊上方,選擇“置頂/星標公眾號”
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 *就是一個不能動的“地址”,在進行&、移動指針之前必須轉型為類型指針。

這里我們看一下我們之前了解的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
? ? {
? ? ? ? *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
? ? {
? ? ? ??printf("%d ",nums[i]);
? ? }
? ??printf("\n");
? ??return0;
}
在這個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 *的用途:在只知道內存,但是不知道是什么類型的時候。
我們可以在C中輕松地定義一個函數指針:
typedef?void?(*intFunc)(int?i);
這里我們定義了一個無返回值的,只有一個int類型參數的函數指針intFunc。
我們可以在main函數中使用這個函數指針來指向一個具體的函數(這個具體的函數定義需要和函數指針的定義一致):
void?test1(int?age){
? ??printf("test1:%d\n",age);
}
int?main(void){
? ??
? ??// 聲明一個intFunc類型的函數指針
? ? intFunc f1 = test1;
? ??// 執行f1函數指針所指向的代碼區
? ? f1(8);
? ??return?0;
}
最終運行結果如下圖所示,執行函數指針f1即執行了其所指向的具體的函數:

這里我們通過一個小案例來對函數指針做一個基本的使用介紹。相信大部分的C#或Java程序員都很熟悉foreach,那么我們就來模擬foreach對int數組中的值進行不同的處理。具體體現為for循環的代碼是復用的,但是怎么處理這些數據不確定,因此把處理數據的邏輯由函數指針指定。
void?foreachNums(int?*nums,int?len,intFunc func)
{
? ??int?i;
? ??for(i=0;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);
最終運行的結果如下圖所示:

通過函數指針,我們可以屏蔽各種具體處理方法的不同,也就是將不確定的因素都依賴于抽象,這也是面向抽象或面向接口編程的精髓。
(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
? ? {
? ? ? ??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;
}
typedefstruct?_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);
? ??return0;
}
最終運行結果如下圖所示:

qsort包含在頭文件中,此函數根據你給的比較條件進行快速排序,通過指針移動實現排序。排序之后的結果仍然放在原數組中。
使用qsort函數必須自己寫一個比較函數。我們可以看看qsort函數的原型:
?void?qsort?(?void?* base,?size_t?num,?size_t?size,?int?( * comparator ) (?const?void?*,?const?void?* ) );
? ??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);
? ? }
那么,快速排序后是否有結果呢?答案是肯定的,我們可以傳入各種比較方法,可以升序排序也可以降序排序。

??兄弟們!一個人單打獨斗確實能沖得挺快,但要想走得更遠、更穩,還得靠一群志同道合的伙伴??!
?? 麥鴿的知識星球現在已經聚集了一波人,大家都在這里互相學習、共同進步。
如果你也想找個靠譜的學習圈子
趕緊???戳鏈接 ???加入我們吧!
在這里,你能讀到星球專欄的干貨,優質教程,練手項目,隨時向麥鴿提問,還能幫你定制學習計劃。別猶豫了,兄弟,一起沖!??
往期推薦