目錄
1、函數(shù)指針數(shù)組
1.1、函數(shù)指針數(shù)組是什么?
?1.2、函數(shù)指針數(shù)組的用途:轉(zhuǎn)移表
2、擴(kuò)展:指向函數(shù)指針的數(shù)組的指針
3、回調(diào)函數(shù)
3.1、回調(diào)函數(shù)介紹
?3.2、回調(diào)函數(shù)的案例:qsort函數(shù)
3.2.1、回顧冒泡排序
?3.2.1、什么是qsort函數(shù)?
1、函數(shù)指針數(shù)組
1.1、函數(shù)指針數(shù)組是什么?
函數(shù)指針數(shù)組是什么?首先主語是數(shù)組,數(shù)組是一個(gè)存放相同類型數(shù)據(jù)的存儲(chǔ)空間。那我們已經(jīng)學(xué)習(xí)了指針數(shù)組,比如:
char* arr[5]? ———— 字符指針數(shù)組,它是一個(gè)數(shù)組,存放的是字符指針。
int* arr[5]? ? ?———— 整型指針數(shù)組,它是一個(gè)數(shù)組,存放的是整型指針。
假設(shè)有這么一個(gè)使用場(chǎng)景,我需要將幾個(gè)函數(shù)的地址存放到一個(gè)數(shù)組中,那應(yīng)該怎么存?下面給大家介紹一下:函數(shù)指針數(shù)組
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pf1)(int, int) = &Add; //pf1和pf2是函數(shù)指針
int (*pf2)(int, int) = ⋐
//數(shù)組中存放類型相同的多個(gè)數(shù)組
int (*pfArr[4])(int, int) = { &Add,&Sub }; //pfArr就是函數(shù)指針數(shù)組
return 0;
}
函數(shù)指針數(shù)組的寫法與函數(shù)指針非常相似,只需要在名字后加個(gè)方括號(hào)[ ]就可以了。
注意:因?yàn)閿?shù)組是一個(gè)存放相同類型數(shù)據(jù)的存儲(chǔ)空間,所以函數(shù)指針數(shù)組只能夠存放返回類型和參數(shù)類型都一致的函數(shù)的函數(shù)地址。
?1.2、函數(shù)指針數(shù)組的用途:轉(zhuǎn)移表
用C語言實(shí)現(xiàn)一個(gè)計(jì)算器功能(加減乘除):
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void menu()
{
printf("*******************************\n");
printf("****** 1.add 2.sub *****\n");
printf("****** 3.mul 4.div *****\n");
printf("****** 0.exit *****\n");
printf("*******************************\n");
}
int main()
{
int input = 0;
int a = 0;
int b = 0;
int ret = 0;
do
{
menu();
printf("請(qǐng)選擇:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("請(qǐng)輸入2個(gè)操作數(shù):");
scanf("%d %d", &a, &b);
ret = add(a, b);
printf("%d\n", ret);
break;
case 2:
printf("請(qǐng)輸入2個(gè)操作數(shù):");
scanf("%d %d", &a, &b);
ret = sub(a, b);
printf("%d\n", ret);
break;
case 3:
printf("請(qǐng)輸入2個(gè)操作數(shù):");
scanf("%d %d", &a, &b);
ret = mul(a, b);
printf("%d\n", ret);
break;
case 4:
printf("請(qǐng)輸入2個(gè)操作數(shù):");
scanf("%d %d", &a, &b);
ret = div(a, b);
printf("%d\n", ret);
break;
case 0:
printf("退出計(jì)算器\n");
break;
default:
printf("選擇錯(cuò)誤,重新選擇\n");
break;
}
} while (input);
return 0;
}
? ? 上面的代碼雖然能實(shí)現(xiàn)一個(gè)計(jì)算器功能,但是可以發(fā)現(xiàn),這個(gè)代碼特別地冗余,重復(fù)的部分非常多,并且如果需要添加多一個(gè)功能是,又需要再添加多一個(gè)case,導(dǎo)致代碼越來越長(zhǎng),重復(fù)部分也越來越多,這是非常不好的代碼習(xí)慣,那有什么辦法能夠解決呢?
? ? 其實(shí)我們通過觀察可以發(fā)現(xiàn),這些函數(shù)有一些特點(diǎn),就是除了函數(shù)名不同之外,返回類型以及參數(shù)類型都是一致的。
? ? 既然除了函數(shù)名不同之外其余都相同,那么是否就可以使用函數(shù)指針數(shù)組來改造一下代碼?
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void menu()
{
printf("*******************************\n");
printf("****** 1.add 2.sub *****\n");
printf("****** 3.mul 4.div *****\n");
printf("****** 0.exit *****\n");
printf("*******************************\n");
}
int main()
{
int input = 0;
int a = 0;
int b = 0;
int ret = 0;
do
{
menu();
printf("請(qǐng)選擇:>");
scanf("%d", &input);
//創(chuàng)建一個(gè)函數(shù)指針數(shù)組
int (*pfArr[])(int, int) = { NULL,add,sub,mul,div };
//為了使數(shù)組下標(biāo)與菜單序號(hào)對(duì)應(yīng)起來,在0下標(biāo)處放置一個(gè)NULL
if (input == 0)
{
printf("退出計(jì)算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("請(qǐng)輸入2個(gè)操作數(shù):");
scanf("%d %d", &a, &b);
ret = pfArr[input](a, b); //下標(biāo)訪問數(shù)組中的函數(shù)并調(diào)用
printf("ret = %d\n", ret);
}
else
{
printf("選擇錯(cuò)誤,重新選擇\n");
}
} while (input);
return 0;
}
可以看到,使用函數(shù)指針數(shù)組一樣可以完成。未來如果還需要添加其他功能時(shí),只需要在菜單發(fā)生變化,然后寫出實(shí)現(xiàn)功能的函數(shù),再將函數(shù)放入函數(shù)指針數(shù)組當(dāng)中就可以了。而我們把這種場(chǎng)景下使用的函數(shù)指針數(shù)組就叫做轉(zhuǎn)移表。
這也就是說:使用函數(shù)指針數(shù)組不僅大大提高了代碼的質(zhì)量,而且大大降低了維護(hù)成本。
2、擴(kuò)展:指向函數(shù)指針的數(shù)組的指針
指向函數(shù)指針數(shù)組的指針是一個(gè)指針,指針指向一個(gè)數(shù)組 ,數(shù)組的元素都是函數(shù)指針?。
如何定義?
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函數(shù)指針pfun
void (*pfun)(const char*) = test;
//函數(shù)指針的數(shù)組pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函數(shù)指針數(shù)組pfunArr的指針ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
void (*(*ppfunArr)[5])(const char*)
void (*(*ppfunArr)[5])(const char*),ppfunArr首先與*結(jié)合,所以它是一個(gè)指針。
void (*(*ppfunArr)[5])(const char*),再和[5]結(jié)合,表示指針指向一個(gè)大小為5的數(shù)組,每個(gè)數(shù)組存放的類型是函數(shù)指針void (*)(const char*)。
當(dāng)然這里講到的函數(shù)指針數(shù)組指針已經(jīng)是很深入的內(nèi)容了,使用場(chǎng)景非常少,只作為擴(kuò)展了解即可,看不懂也不需要太過于擔(dān)心。
?
3、回調(diào)函數(shù)
3.1、回調(diào)函數(shù)介紹
回調(diào)函數(shù)在C語言中的地位非常高,非常重要?;卣{(diào)函數(shù)是依賴函數(shù)指針的,有了函數(shù)指針才能實(shí)現(xiàn)回調(diào)函數(shù)。在前面計(jì)算器功能使用函數(shù)指針數(shù)組調(diào)用加減乘除函數(shù)的時(shí)候,加減乘除函數(shù)就被成為回調(diào)函數(shù)。
回調(diào)函數(shù)就是一個(gè)通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用來調(diào)用其所指向的函數(shù)時(shí),我們就說這是回調(diào)函數(shù)。回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對(duì)該事件或條件進(jìn)行響應(yīng)。
?再次用計(jì)算器功能作為例子講解:
觀察代碼可以發(fā)現(xiàn) ,只有調(diào)用函數(shù)部分不一樣,那么能不能定義一個(gè)cacl()函數(shù),通過將加減乘除函數(shù)作為參數(shù)傳入到calc函數(shù)中,達(dá)到在calc函數(shù)中調(diào)用加減乘除函數(shù)?
按照這個(gè)思路修改后的代碼:
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void menu()
{
printf("*******************************\n");
printf("****** 1.add 2.sub *****\n");
printf("****** 3.mul 4.div *****\n");
printf("****** 0.exit *****\n");
printf("*******************************\n");
}
void calc(int(*pf)(int, int))
{
int a = 0;
int b = 0;
int ret = 0;
printf("請(qǐng)輸入2個(gè)操作數(shù):");
scanf("%d %d", &a, &b);
ret = pf(a, b);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("請(qǐng)選擇:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出計(jì)算器\n");
break;
default:
printf("選擇錯(cuò)誤,重新選擇\n");
break;
}
} while (input);
return 0;
}
【圖解】
可以把calc函數(shù)理解為中轉(zhuǎn)站,給我參數(shù)傳遞什么函數(shù)地址,我就調(diào)用什么函數(shù)。
提示:在往期博客中我有得出過一個(gè)結(jié)論:函數(shù)指針在調(diào)用所指向函數(shù)時(shí),可以不寫*直接和函數(shù)名一樣調(diào)用函數(shù),而*號(hào)在這里其實(shí)就只是一個(gè)擺設(shè),同樣是為了照顧初學(xué)者的使用習(xí)慣,所以才會(huì)導(dǎo)致當(dāng)加了很多*號(hào)去解引用時(shí)得出來的結(jié)果依然是正確的結(jié)果。
即(*pf)(a,b)等價(jià)于pf(a,b)。
如果想要了解更透徹,可以前往我的往期博客閱讀函數(shù)指針部分。(鏈接:點(diǎn)擊前往)
?3.2、回調(diào)函數(shù)的案例:qsort函數(shù)
3.2.1、回顧冒泡排序
?為了方便對(duì)比,我們先復(fù)習(xí)一下冒泡排序:
//冒泡排序算法
//給一組整型數(shù)據(jù),然后使用冒泡排序?qū)?shù)據(jù)進(jìn)行升序排序。
void bubble_sort(int arr[], int sz)
{
//趟數(shù)
int i = 0;
for ( i = 0; i < sz - 1; i++)
{
//每一趟冒泡排序的過程
int j = 0;
for ( j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for ( i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
上面就是冒泡排序的實(shí)現(xiàn),但是可以看到,這個(gè)冒泡排序其實(shí)是有缺陷的,它的參數(shù)是int類型,限制了它只能夠排序整型數(shù)據(jù)!而這里即將講到的qsort函數(shù)就是一個(gè)可以用來排序任意類型數(shù)據(jù)的函數(shù)。
?3.2.1、什么是qsort函數(shù)?
qsort是一個(gè)庫函數(shù),底層使用的是快速排序的方式對(duì)數(shù)據(jù)進(jìn)行排序。頭文件:<stdlib.h>
這個(gè)函數(shù)可以直接使用用來排序任意類型的數(shù)據(jù)。
當(dāng)然除了快速排序,還有很多排序,例如:冒泡排序、選擇排序,希爾排序,歸并排序等等
qsort函數(shù)定義原型:
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
- void* base:待排序數(shù)組的第一個(gè)元素的地址
- size_t num:待排序數(shù)組的元素個(gè)數(shù)
- size_t size:以字節(jié)為單位,待排序數(shù)組中一個(gè)元素的大小。
- int (*compar)(const void*,const void*):函數(shù)指針,指向一個(gè)函數(shù),用來比較兩個(gè)元素,由用戶自行創(chuàng)建并封裝。
?比較函數(shù)的形參中為什么用的是void*:
void* 是無具體類型的指針,不能進(jìn)行解引用操作符,也不能進(jìn)行+-整數(shù)的操作,它是用來存放任意類型數(shù)據(jù)的地址(可以理解為垃圾桶,什么都能裝,當(dāng)需要用時(shí)再強(qiáng)制類型轉(zhuǎn)換為需要的類型)。只有void*被允許存放任意類型數(shù)據(jù)的地址,如果是其他類型的指針編譯器會(huì)報(bào)錯(cuò)。正是因?yàn)槎xqsort函數(shù)時(shí)用的是void*,qsort函數(shù)才可以排序任意類型的數(shù)據(jù)。
使用qsort函數(shù)最重要的就是最后一個(gè)參數(shù),這個(gè)參數(shù)決定了qsort函數(shù)比較兩個(gè)元素的規(guī)則。這里先寫一個(gè)用于排序整型數(shù)據(jù)的比較函數(shù)cmp_int:
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
?比較函數(shù)的要求:
- 當(dāng)p1指向的元素大于p2指向的元素時(shí),返回大于0的數(shù)
- 當(dāng)p1指向的元素等于p2指向的元素時(shí),返回0
- 當(dāng)p1指向的元素小于p2指向的元素時(shí),返回小于0的數(shù)
?【完整代碼】
?使用qsort函數(shù)排序整型數(shù)據(jù)。
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for ( i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
同理,qsort函數(shù)排序結(jié)構(gòu)體類型數(shù)據(jù)(下面例子以結(jié)構(gòu)體中的年齡來排序):
struct Stu
{
char name[20];
int age;
};
int cmp_struct(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
struct Stu arr[] = { {"zhangsan",20},{"lisi",21},{"wangwu",22} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_struct);
return 0;
}
?【運(yùn)行結(jié)果】
可以發(fā)現(xiàn)確實(shí)是完成了按年齡排序。
如果覺得作者寫的不錯(cuò),求給博主一個(gè)大大的點(diǎn)贊支持一下,你們的支持是我更新的最大動(dòng)力!文章來源地址http://www.zghlxwxcb.cn/news/detail-727084.html
如果覺得作者寫的不錯(cuò),求給博主一個(gè)大大的點(diǎn)贊支持一下,你們的支持是我更新的最大動(dòng)力!文章來源:http://www.zghlxwxcb.cn/news/detail-727084.html
如果覺得作者寫的不錯(cuò),求給博主一個(gè)大大的點(diǎn)贊支持一下,你們的支持是我更新的最大動(dòng)力!
到了這里,關(guān)于【C語言】指針的進(jìn)階(二)—— 回調(diào)函數(shù)的講解以及qsort函數(shù)的使用方式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!