體現(xiàn)網(wǎng)站特色全球熱門網(wǎng)站排名
🌈write in front :
🔍個人主頁 : @啊森要自信的主頁
??真正相信奇跡的家伙,本身和奇跡一樣了不起啊!
歡迎大家關注🔍點贊👍收藏??留言📝>希望看完我的文章對你有小小的幫助,如有錯誤,可以指出,讓我們一起探討學習交流,一起加油鴨。
文章目錄
- 前言
- 一、轉(zhuǎn)移表
- 二、回調(diào)函數(shù)是什么?
- 三、qsort函數(shù)細解
- 3.1 類比冒泡排序?
- 3.2 qosrt函數(shù)超詳解
- 3.2.1qsort函數(shù)排序整型數(shù)據(jù)
- 3.2.2 使?qsort排序結構數(shù)據(jù)
- 四、 qsort函數(shù)的模擬實現(xiàn)
- 4.1 模擬qsort整形數(shù)據(jù)
- 4.2 模擬`qsort`排序結構數(shù)據(jù)
- 總結
前言
本小節(jié),我們將繼續(xù)學習C語言轉(zhuǎn)移表,什么是回調(diào)函數(shù),回調(diào)函數(shù)又是什么?qsort函數(shù)怎么使用,怎么理解處理,要注意的細節(jié),當然qsort使用舉例,最后我們進行qsort函數(shù)的模擬實現(xiàn)!文章干貨滿滿,走起!
一、轉(zhuǎn)移表
C語言轉(zhuǎn)移表是指根據(jù)一定條件,實現(xiàn)程序執(zhí)行流程的跳轉(zhuǎn)或轉(zhuǎn)移的機制。
具體來說,C語言中實現(xiàn)轉(zhuǎn)移表的主要方式有:
goto
語句:
goto
語句可以實現(xiàn)無條件跳轉(zhuǎn),直接跳轉(zhuǎn)到指定標簽所在的代碼塊
goto 標簽名;
例如:
goto label;
switch
語句:
switch
語句根據(jù)表達式的值,選擇性地執(zhí)行一個代碼塊。它實現(xiàn)了有條件跳轉(zhuǎn)。
switch(表達式)
{case 常數(shù)表達式1://語句break;case 常數(shù)表達式2: //語句break;//其他casedefault://語句
}
- continue語句:
continue
用于跳過循環(huán)體剩余部分,直接跳轉(zhuǎn)到循環(huán)條件判斷語句。
例如:
for(i=0;i<10;i++)
{if(i==5)continue;printf("%d",i);
}
break
語句:
break
用于跳出整個循環(huán)或switch
語句。
例如:
for(i=0;i<10;i++)
{if(i==5)break;printf("%d",i);
}
return
語句:
return
用于從函數(shù)中返回。
例如:
int func()
{return 0;
}
- 拓展:
longjmp()/setjmp()
:
setjmp()
和longjmp()
是C語言中的兩個非常重要的函數(shù),它們可以實現(xiàn)非局部跳轉(zhuǎn)的功能。
setjmp()
函數(shù)聲明如下:
int setjmp(jmp_buf env);
-
jmp_buf
是可以保存環(huán)境信息的結構體。 -
setjmp()
會將當前函數(shù)的執(zhí)行環(huán)境信息保存到env
中,并返回0。 -
然后程序可以正常執(zhí)行。
-
當需要跳轉(zhuǎn)時,調(diào)用
longjmp(env, val)
;longjmp()
函數(shù)聲明如下:
void longjmp(jmp_buf env, int val);
-
longjmp()
第一個參數(shù)就是setjmp()
保存的env
。 -
它會將程序跳轉(zhuǎn)回
setjmp()
后面要執(zhí)行的代碼。 -
但此時
setjmp()
會返回longjmp()
第二個參數(shù)val
,而不是0
。
jmp_buf env是setjmp和longjmp函數(shù)用來保存環(huán)境信息的結構體變量。
jmp_buf
是一個預定義的數(shù)據(jù)類型,它用來描述一個環(huán)境的狀態(tài)。
env
是一個jmp_buf
類型的變量。- 當調(diào)用
setjmp(env)
時,setjmp
函數(shù)會將當前函數(shù)調(diào)用棧(包括函數(shù)參數(shù)、局部變量等環(huán)境信息)保存到env
這個結構體變量中。- 之后程序可以正常執(zhí)行。
- 當需要非局部跳轉(zhuǎn)時,調(diào)用
longjmp(env, val)
。longjmp
函數(shù)第一個參數(shù)就是這個env
。longjmp
通過env
這個結構體,可以恢復到setjmp
函數(shù)保存環(huán)境時的狀態(tài)。實現(xiàn)一個“跳回”的效果。
小總結:
jmp_buf
是一個結構體類型,它可以保存一個函數(shù)環(huán)境的狀態(tài)信息。env
是一個此類型的變量,用于在setjmp
和longjmp
之間傳遞環(huán)境信息。setjmp
函數(shù)把當前環(huán)境信息保存到env
中。longjmp
函數(shù)通過env
這個結構體,實現(xiàn)恢復到setjmp
時的環(huán)境狀態(tài),從而實現(xiàn)非局部跳轉(zhuǎn)。
哎!當然你可以把env
可以看作是一個“傳送令牌”,只要通過longjmp
把令牌改了,他就重新傳送到setjmp
,然后繼續(xù)執(zhí)行,它連接setjmp和longjmp,使得longjmp能找到正確的環(huán)境信息進行跳轉(zhuǎn)。
所以通過setjmp()/longjmp()
就實現(xiàn)了一個非局部跳轉(zhuǎn):程序似乎"跳回"到setjmp()后面要執(zhí)行的代碼,但實際上環(huán)境已經(jīng)發(fā)生了變化。這在異常處理等場景中很有用。
工作原理是:
-
setjmp()
函數(shù)會保存當前函數(shù)調(diào)用棧(包括函數(shù)參數(shù)和局部變量等信息)的環(huán)境,并返回0。 -
之后程序可以正常執(zhí)行。
-
當需要非局部跳轉(zhuǎn)時,調(diào)用
longjmp()
,并將在setjmp()
保存的環(huán)境作為參數(shù)傳入。 -
這個時候程序就會跳轉(zhuǎn)回
setjmp()
保存的環(huán)境,仿佛從setjmp()后面繼續(xù)執(zhí)行。但此時setjmp()會返回非0值。
舉個例子
# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <setjmp.h>jmp_buf env; //jmp_buf是一個預定義的數(shù)據(jù)類型,它用來描述一個環(huán)境的狀態(tài)。//env是一個jmp_buf類型的變量。void func()
{//設置跳轉(zhuǎn)點int ret = setjmp(env);if (0 == ret){//正常流程printf("In func()\n");//觸發(fā)跳轉(zhuǎn)longjmp(env, 1);}else{//跳轉(zhuǎn)后流程printf("Jumped back to func()\n");}
}int main()
{func();return 0;
}
程序執(zhí)行流程:
- 主函數(shù)調(diào)用
func()
函數(shù)。func()
內(nèi)首先調(diào)用setjmp()
設置跳轉(zhuǎn)點env
。由于setjmp()
第一次調(diào)用會返回0
,所以進入if
塊。- 打印"
In func()
"信息。- 調(diào)用
longjmp(),
觸發(fā)非局部跳轉(zhuǎn)。- 程序跳轉(zhuǎn)回
setjmp()
設置的環(huán)境env
,此時setjmp()
返回1
。- 執(zhí)行else塊內(nèi)的代碼,打印"Jumped back to func()"。
- func()返回,主函數(shù)結束。
通過在函數(shù)內(nèi)使用setjmp()/longjmp()
,實現(xiàn)了從函數(shù)內(nèi)非局部跳回函數(shù)外的功能。這與goto
不同,可以實現(xiàn)跨函數(shù)的非順序跳轉(zhuǎn)。它常用于異常和錯誤處理等場景。
- C語言函數(shù)指針數(shù)組可以用來實現(xiàn)轉(zhuǎn)移表。
具體來說:
-
定義一個函數(shù)指針數(shù)組,元素類型為函數(shù)指針。
-
每個數(shù)組元素都指向一個具體的函數(shù)。
-
根據(jù)條件調(diào)用數(shù)組對應元素所指向的函數(shù)。
這與傳統(tǒng)的switch語句實現(xiàn)轉(zhuǎn)移的效果是一致的。
一個簡單的示例:
// 函數(shù)定義
void func1()
{printf("func1\n");
}void func2()
{printf("func2\n");
}// 主函數(shù)
int main()
{// 函數(shù)指針數(shù)組void (*funcs[])(void) = {func1, func2};int id = 1; // 條件值// 根據(jù)條件調(diào)用數(shù)組元素函數(shù)funcs[id]();return 0;
}
這樣就實現(xiàn)了根據(jù)條件值動態(tài)調(diào)用不同函數(shù)的功能,相當于一個簡單的轉(zhuǎn)移表。
函數(shù)指針數(shù)組用于轉(zhuǎn)移表的優(yōu)點是:
- 更靈活,可以在運行時動態(tài)添加/刪除函數(shù)
- 擴展性好,支持條件復雜情況下的多路徑轉(zhuǎn)移
- 與傳統(tǒng)switch語句相比代碼更簡潔清晰
所以總的來說,函數(shù)指針數(shù)組正是C語言實現(xiàn)轉(zhuǎn)移表的一個很好的選擇。它可以很好地替代switch
語句實現(xiàn)更復雜的多路轉(zhuǎn)移邏輯。
通過這個你可能不太能看出哪里能很好的替代switch
語句,讓我們來看一個典型的例子,實現(xiàn)一個計算器!!!
switch實現(xiàn)計算器:
主要實現(xiàn)計算器程序思路:
-
定義了四個運算函數(shù)add、sub、mul、div實現(xiàn)四則運算。
-
main函數(shù)中:
-
使用do while循環(huán)控制程序循環(huán)執(zhí)行。
-
打印菜單讓用戶選擇運算類型。
-
根據(jù)用戶選擇用switch case調(diào)用對應的運算函數(shù)。
-
每次運算前輸入兩個操作數(shù),運算后打印結果。
- 選擇0退出循環(huán),退出程序。
#include <stdio.h>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;
}int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf("**** 1:add 2:sub ***\n");printf("**** 3:mul 4:div ***\n");printf("**** 0:exit .... ***\n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);switch (input)//選擇{case 1:printf("輸入兩個操作數(shù):");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸入兩個操作數(shù):");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸入兩個操作數(shù):");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸入兩個操作數(shù):");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤,請重新選擇\n");break;}} while (input);return 0;
}
實現(xiàn)是實現(xiàn)了,但是
case
里面的每個代碼塊除了ret = (?)(x,y);
有點不同,其他都很相似,這么多代碼重復寫,會造成代碼的冗余,如果我們又繼續(xù)給用戶增加功能,比如&,^,>>等等,然后一個功能我們就加一個case,case
多了,代碼重復的也多咋改變呢?不著急,我們不是學習了函數(shù)指針數(shù)組嗎?
我們可以把函數(shù)的地址存儲在數(shù)組里面,然后通過指針訪問數(shù)組下標
(0,1,2,3,4,5...)
,然后解引用找到我們要找到我們要實現(xiàn)函數(shù)的地址
然后給他傳參,再接收他的計算的返回值不就搞定了。
哈哈哈哈!!掌聲應該送給自己,說做就做!讓我們繼續(xù)往下走。
函數(shù)指針數(shù)組實現(xiàn)計算器:
思路:
-
定義了4個函數(shù)Add、Sub、Mul、Div,用于四則運算。
-
menu()
函數(shù)打印菜單界面。 -
定義了一個函數(shù)指針數(shù)組pfArr,元素類型為int (*)(int, int),可以存儲這4個二元運算函數(shù)的地址。
-
在主函數(shù)中使用
do-while
循環(huán)不斷運行:-
調(diào)用
menu()
打印菜單 -
scanf
輸入選擇 -
根據(jù)選擇從
pfArr
數(shù)組中獲取對應函數(shù)的地址 -
調(diào)用該函數(shù)進行運算
-
打印結果
-
void menu()//封裝菜單
{printf("******************************\n");printf("**** 1. add 2. sub ****\n");printf("**** 3. mul 4. div ****\n");printf("**** 0. exit ****\n");printf("******************************\n");
}int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();//函數(shù)指針數(shù)組的方式解決一下//這里的函數(shù)指針數(shù)組,我們稱為轉(zhuǎn)移表// 為什么這里要加NULL,直接{add,sub,mul,div}不行嗎?如果真是可以的話,那我們觀察他的下標 0 1 2 3此時此刻,如果我們選擇1.add,那么(*p[1])取出的地址是sub,這不對呀,如果我們在前面加一個NULL,{ NULL,add,sub,mul,div }下標為 0 1 2 3 4地址:*p[ 0 ] == 0, *p[ 1 ] ==addint (*pfArr[])(int, int) = { NULL, Add, Sub, Mul, Div };// 0 1 2 3 4printf("請選擇:");scanf("%d", &input);if (input == 0){printf("退出計算器\n");}else if (input >= 1 && input <= 4){ printf("請輸入兩個操作數(shù):");scanf("%d %d", &x, &y);ret = pfArr[input](x, y); //(*p[input])==add/sub/mul/div函數(shù)名,也就是函數(shù)的地址//(p[input])也可以,*號有無,都相當于函數(shù)名,也是函數(shù)地址// 也就是ret=(p[input])(x,y);printf("%d\n", ret);}else{printf("選擇錯誤,重新選擇\n");}} while (input);return 0;
}
解釋:
當input
輸入1, pfArr[1]
取得Add
的地址,然后通過Add
函數(shù)的地址,執(zhí)行指令。(當然同理input輸入2,3,4也是同樣的步驟)。
如果要增加功能,那么可以int (*pfArr[])(int, int) = { NULL, Add, Sub, Mul, Div };
增加相應的功能,然后增加相應功能的代碼塊!
比如,你想要增加位運算(&, |, ^)的功能:
- 增加位運算函數(shù):
int And(int x, int y) {return x & y;
}int Or(int x, int y) {return x | y;
}int Xor(int x, int y) {return x ^ y;
}
- 修改菜單顯示:
void menu() {printf("******************************\n");printf("**** 1. add 2. sub ****\n"); printf("**** 3. mul 4. div ****\n");printf("**** 5. & 6. | ****\n");printf("**** 7. ^ ****\n"); printf("**** 0. exit ****\n");printf("******************************\n");}
- 增加函數(shù)指針:
int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div, And, Or, Xor};
- 判斷函數(shù)選擇范圍:
if(input >= 1 && input <= 7) {ret = pfArr[input](x, y);}
這樣就增加了位運算的功能選擇了。
如果還需要其他運算,可以繼續(xù)增加對應的函數(shù)和菜單顯示即可。
但是,思考———>
int (*p[5])(int x, int y) = { NULL,add,sub,mul,div };
那函數(shù)的add,sub,mul,div這些地址是不是也和一維數(shù)組一樣,存儲在函數(shù)指針數(shù)組里面,他們的地址是否是連續(xù)的呢?
解釋:
函數(shù)地址在函數(shù)指針數(shù)組中的存儲方式與一維數(shù)組類似,但有一點不同:
-
函數(shù)指針數(shù)組
pfArr
中,add、sub等函數(shù)地址的存儲是連續(xù)的,就像一維數(shù)組元素一樣,如下標0,1,2,3,4這樣連續(xù)存儲后就可以訪問了。 -
但是,函數(shù)本身的代碼可能不一定存儲在連續(xù)內(nèi)存地址中。
更準確地說:
-
在函數(shù)指針數(shù)組
pfArr
中,add、sub等函數(shù)地址是以連續(xù)方式存儲的。 -
而函數(shù)本身的代碼可能分散在不同代碼段(code section)中,地址不一定連續(xù)。
舉個例子:
假設add函數(shù)代碼在地址0x00001000
,sub函數(shù)代碼在0x00002000
,mul在0x00003000
。
那么在函數(shù)指針數(shù)組pfArr中:
pfArr[1] 指向 add函數(shù)地址 0x00001000
pfArr[2] 指向 sub函數(shù)地址 0x00002000
pfArr[3] 指向 mul函數(shù)地址 0x00003000
我們可以看到,pfArr[1]、pfArr[2]、pfArr[3]中的函數(shù)地址是以連續(xù)的方式存儲在數(shù)組中的。
但是函數(shù)本身的代碼地址0x00001000、0x00002000、0x00003000并不連續(xù)。
所以總結來說:
- 函數(shù)指針數(shù)組pfArr中函數(shù)地址是連續(xù)存儲的
- 但函數(shù)代碼本身不一定連續(xù)存儲在內(nèi)存中
二、回調(diào)函數(shù)是什么?
C語言中的回調(diào)函數(shù)是指在函數(shù)調(diào)用的過程中,被另外一個函數(shù)作為參數(shù)傳遞并調(diào)用的函數(shù)。
回調(diào)函數(shù)的主要特征如下:
-
回調(diào)函數(shù)必須事先定義。
-
回調(diào)函數(shù)的地址作為參數(shù)傳遞給另一個函數(shù),這個函數(shù)稱為主函數(shù)。
-
主函數(shù)在適當?shù)臅r候,通過調(diào)用回調(diào)函數(shù)的地址來調(diào)用回調(diào)函數(shù)。
一個典型的回調(diào)函數(shù)使用場景例子:
// 回調(diào)函數(shù)定義
void callback_func(int value)
{printf("value is %d\n", value);
}// 主函數(shù)定義
void main_func(void (*callback)(int))
{int num = 10;// 調(diào)用回調(diào)函數(shù)callback(num);
}int main()
{// 注冊回調(diào)函數(shù)main_func(callback_func);return 0;
}
注:回調(diào)函數(shù)的特點是函數(shù)的調(diào)用關系由使用者在運行時決定,而不是在編譯時就確定,這提供了更大的靈活性。
那可不可以使用回調(diào)函數(shù)實現(xiàn)計算器呢?
- 定義一個通用的計算函數(shù)
calc
,它接收一個函數(shù)指針作為參數(shù)。- 在
main
函數(shù)中,根據(jù)用戶選擇直接調(diào)用calc
函數(shù),并傳入相應的運算函數(shù)。- 回調(diào)函數(shù)是
Add、Sub、Mul、Div
void menu()
{printf("******************************\n");printf("**** 1. add 2. sub ****\n");printf("**** 3. mul 4. div ****\n");printf("**** 0. exit ****\n");printf("******************************\n");
}int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}//calc功能強大了void calc(int (*pf)(int,int))
{int x = 0;int y = 0;int ret = 0;printf("請輸入兩個操作數(shù):");scanf("%d %d", &x, &y);ret = pf(x, y);printf("%d\n", ret);
}int main()
{int input = 0;do{menu();printf("請選擇:");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("退出計算器\n");break;default:printf("選擇錯誤,重新選擇\n");break;}} while (input);return 0;
}
三、qsort函數(shù)細解
3.1 類比冒泡排序?
通過前面我們學過冒泡排序,qsort
函數(shù)的排序讓我們類比一下:
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;}}}
}
void print_arr(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}int main()
{//將一組整數(shù)排序為升序int arr[10] = { 3,1,5,2,4,6,8,7,0,9 };int sz = sizeof(arr) / sizeof(arr[0]);print_arr(arr, sz);bubble_sort(arr, sz);print_arr(arr, sz);return 0;
}
3.2 qosrt函數(shù)超詳解
庫函數(shù)的學習和查看?具很多,?如:
C/C++官?的鏈接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
qsort
函數(shù)是C標準庫中用于對數(shù)組進行快速排序的函數(shù)。(注:qsort
函數(shù)底層使用的排序算法就是快速排序。)
- 函數(shù)原型:
void qsort(void* base,//base 指向了要排序的數(shù)組的第一個元素size_t num, //base指向的數(shù)組中的元素個數(shù)(待排序的數(shù)組的元素的個數(shù))size_t size,//base指向的數(shù)組中元素的大小(單位是字節(jié))int (*compar)(const void*p1, const void*p2)//函數(shù)指針 - 指針指向的函數(shù)是用來比較數(shù)組中的2個元素的);
.- [ ] 分析定義:
-
base
指向要排序的數(shù)組首地址。 -
num
表示數(shù)組元素個數(shù)。 -
size
表示每個元素的大小,以字節(jié)為單位。 -
compar
是比較函數(shù),它接收兩個void
指針,返回值小于0表示第一個參數(shù)小于第二個參數(shù),等于0表示相等,大于0表示第一個參數(shù)大于第二個參數(shù)。
.- [ ] 特點:
-
qsort
使用快速排序算法,時間復雜度為O(nlogn)
。 -
調(diào)用qsort時,需要提供一個比較函數(shù)
compar
來判斷兩個元素的大小關系。 -
比較函數(shù)通過
void
指針間接訪問元素,避免與數(shù)據(jù)類型綁定,實現(xiàn)了最大程度的通用性。 -
qsort會在內(nèi)部調(diào)用比較函數(shù)多次對數(shù)組進行排序,這就是回調(diào)機制的實現(xiàn)。
-
qsort
是inplace
排序,不需要額外的空間。
3.2.1qsort函數(shù)排序整型數(shù)據(jù)
#include <stdio.h>
#include <stdlib.h>void print_arr(int arr[], int sz)
{//打印函數(shù)int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}int cmp_int(const void* p1, const void* p2)
{//好比冒泡排序return *(int*)p1 - *(int*)p2;
}//測試qsort排序整型數(shù)據(jù)的
int main()
{int arr[10] = { 4,2,5,3,1,6,7,8,0,9 };int sz = sizeof(arr) / sizeof(arr[0]);print_arr(arr, sz);//打印前qsort(arr, sz, sizeof(arr[0]), cmp_int);//arr->數(shù)組首元素地址//sz->數(shù)組元素個數(shù)也可以這樣寫sz==sizeof(arr)/sizeof(arr[0])//sizeof(arr[0])->數(shù)組元素大小,這里字節(jié)大小為4//cmp_int比較函數(shù)print_arr(arr, sz);//打印后
}
3.2.2 使?qsort排序結構數(shù)據(jù)
- 定義結構體類型
struct Stu
{char name[20];//名字int age;
};
- 定義比較函數(shù)
- 怎么比較2個結構體數(shù)據(jù)? - 不能直接使用 > < ==比較
- 可以按照名字比較
- 可以按照年齡比較
//按照年齡比較
int cmp_stu_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age;
}
- 聲明結構體數(shù)組和元素個數(shù)
void test2()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 38}, {"wangwu", 18} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
代碼實現(xiàn):
# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>測試qsort函數(shù)排序結構體數(shù)據(jù)
struct Stu
{char name[20];//名字int age;
};int cmp_stu_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age;
}
void print(struct Stu arr[], int sz)
{for (int i = 0; i < sz; i++){printf("%s %d\n", arr[i].name, arr[i].age);}
}void test2()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 38}, {"wangwu", 18} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);print(arr, sz);
}// //兩個字符串不能使用> < ==
// //而是使用庫函數(shù)strcmp - string compareint cmp_stu_by_name(const void* p1, const void* p2){return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);}void test3(){struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 38}, {"wangwu", 18} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);print(arr, sz);}int main()
{test3();//姓名排序//test2();//年齡排序return 0;
}
運行
test2()//年齡排序
四、 qsort函數(shù)的模擬實現(xiàn)
4.1 模擬qsort整形數(shù)據(jù)
-
主函數(shù):
- 定義
int
測試數(shù)據(jù) - 調(diào)用
bubble
排序 - 打印結果驗證
- 定義
#include <stdio.h>
int main()
{int arr[] = { 3, 1, 5, 8, 4, 2, 9, 6, 7, 0 };int i = 0; //元素個數(shù) //元素大小bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);//數(shù)組首元素地址arr //比較函數(shù)for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
-
bubble函數(shù):
- 接收基地址、元素個數(shù)、元素大小、比較函數(shù)作為參數(shù)
- 實現(xiàn)冒泡排序核心算法
- 通過傳遞的比較函數(shù)
int_cmp
來決定排序順序 - 使用
_swap
函數(shù)交換元素
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{int i = 0;int j = 0;for (i = 0; i < count - 1; i++){for (j = 0; j < count - i - 1; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0){_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}
-
int_cmp
函數(shù):- 定義一個
int
類型的數(shù)據(jù)比較函數(shù) - 通過減法實現(xiàn)兩個int*指針所指向的數(shù)據(jù)比較
- 返回值
大于0
表示p1大于p2(升序),小于0
表示p1小于p2(降序) - 實現(xiàn)了冒泡排序中的比較規(guī)則
- 定義一個
int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
-
_swap
函數(shù):- 定義泛型數(shù)據(jù)交換函數(shù)
- 通過循環(huán)交換每個字節(jié)實現(xiàn)數(shù)據(jù)交換
- 使用
char*
來交換,實現(xiàn)數(shù)據(jù)類型無關
void _swap(void* p1, void* p2, int size)
{int i = 0;for (i = 0; i < size; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}
總結:
每個代碼塊實現(xiàn)的功能:
- 主函數(shù): 測試驅(qū)動開發(fā)
bubble
: 實現(xiàn)冒泡排序算法int_cmp
: 提供比較規(guī)則_swap
: 實現(xiàn)泛型數(shù)據(jù)交換
4.2 模擬qsort
排序結構數(shù)據(jù)
各個代碼塊分析如下:
struct Stu
定義結構體類型,包含姓名和年齡字段。
# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>struct Stu
{char name[20];int age;
};
- Swap函數(shù)實現(xiàn)泛型數(shù)據(jù)交換,通過循環(huán)交換每個字節(jié)實現(xiàn)數(shù)據(jù)交換。
void Swap(char* buf1, char*buf2, size_t width)
{int i = 0;for (i = 0; i < width; i++) {char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}
- bubble_sort2函數(shù)實現(xiàn)冒泡排序算法,和普通冒泡排序區(qū)別在于使用void*作為參數(shù),通過width實現(xiàn)對結構體進行排序。
void bubble_sort2(void* base, int sz, int width, int (*cmp)(const void* p1, const void* p2))
{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])if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){/*int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;*///交換Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}
cmp_stu_by_age
函數(shù)實現(xiàn)結構體比較規(guī)則,根據(jù)年齡字段進行比較。
int cmp_stu_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
- test4函數(shù)定義測試數(shù)據(jù),調(diào)用
bubble_sort2
進行排序,打印結果驗證。
void test4()
{struct Stu arr[] = { {"zhangsan", 18},{"lisi", 35},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age);//打印arr數(shù)組的內(nèi)容int i = 0;for (i = 0; i < sz; i++){printf("%s %d\n", arr[i].name, arr[i].age);}
}
main
函數(shù)調(diào)用test4
函數(shù)進行測試。
int main()
{//模擬結構體按年齡排序test3();return 0;
}
小總結:
struct Stu
定義了需要排序的數(shù)據(jù)類型Swap函數(shù)
實現(xiàn)數(shù)據(jù)交換bubble_sort2
實現(xiàn)泛型冒泡排序cmp_stu_by_age
定義了結構體比較規(guī)則test4
函數(shù)測試驅(qū)動開發(fā)
總結
一、轉(zhuǎn)移表
利用函數(shù)指針數(shù)組實現(xiàn)轉(zhuǎn)移表是動態(tài)規(guī)劃算法解決最優(yōu)子結構問題時使用的一種技術。它記錄了子問題的解,避免重復計算。
二、回調(diào)函數(shù)是什么?
回調(diào)函數(shù)是指在函數(shù)調(diào)用后,被當作參數(shù)傳遞給另一個函數(shù)的函數(shù)。調(diào)用方在需要時,會調(diào)用被調(diào)用方內(nèi)部的這個函數(shù)。
三、qsort函數(shù)細解
3.1 類比冒泡排序?
qsort
函數(shù)實現(xiàn)的也是冒泡排序算法。不同之處在于:
-
qsort
是通用排序函數(shù),可以對任意數(shù)據(jù)類型進行排序,而冒泡排序只能對數(shù)組進行排序; -
qsort
通過回調(diào)函數(shù)來指定元素的比較方式,而冒泡排序直接比較元素值; -
qsort
內(nèi)部實現(xiàn)采用快速排序思想,而不是純粹的冒泡排序。
3.2 qsort
函數(shù)超詳解
qsort
函數(shù)原型:
void qsort(void *base, size_t num, size_t size, int (*compar)(const void*, const void*));
base
:待排序數(shù)組首地址num
:數(shù)組元素個數(shù)size
:每個元素大小compar
:比較函數(shù)回調(diào),返回值小于0時交換元素
3.2.1 qsort
排序整型數(shù)據(jù)
直接傳入整型比較函數(shù)如int cmp(const void*, const void*)
3.2.2 使用qsort
排序結構數(shù)據(jù)
定義結構體比較函數(shù),通過強制類型轉(zhuǎn)換比較結構體字段
四、qsort
函數(shù)的模擬實現(xiàn)
4.1 模擬qsort
整形排序
實現(xiàn)了一個簡單快速排序函數(shù),可以對整型數(shù)組排序。
4.2 模擬qsort結構體排序
同樣實現(xiàn)快速排序,但使用結構體比較函數(shù)作為回調(diào)。
感謝你的收看,如果文章有錯誤,可以指出,我不勝感激,讓我們一起學習交流,如果文章可以給你一個幫助,可以給博主點一個小小的贊😘