一、回調函數
回調函數:通過函數指針調用的函數.
當把一個函數的地址傳遞給另一個函數,通過該地址去調用其指向的函數,那么這個被調用的函數就是回調函數.
示例:
在【深入理解指針2】中結尾寫了用函數指針實現計算器的功能,如下:
void menu()
{printf("1.add 2. sub \n");printf("3.mul 4. div \n");printf("0.exit \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;
}
void calc(int (*pf)(int, int))
{int a, b;printf("請輸入兩個參數:");scanf_s("%d %d", &a, &b);int ret = pf(a, b);printf("%d \n", ret);
}
int main()
{int a = 0;int b = 0;int input = 0;int ret = 0;do {menu();printf("請選擇:");scanf_s("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);case 4:calc(Div);break;case 0:printf("退出\n");break;default:printf("請重新輸入:");break;}} while (input);return 0;
}
這里加入想實現加法函數的功能,并沒有直接在主函數中直接調用加法函數Add,而是通過另一個函數calc中函數指針變量來接受Add函數的地址,然后通過函數指針變量再去調用Add函數,因此,這里用函數指針去調用的那個函數就是回調函數,即Add、Sub、Mul、Div是回調函數.
二、qsort函數使用
官網對qsort函數的說明:
void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));
qsort函數有四個參數:
- void* base : base是一個指針,指向待排序數組的首元素地址
- size_t num : num是指待排序數組中元素的個數
- size_t size :size是指待排序數組中每個元素的大小
- compar : 函數指針,指向兩個元素的比較函數,此函數需要自己實現
假設兩元素為p1,p2,若p1<p2,則返回小于0的值;若p1=p2,則返回等于0的值;若p1>p2,則返回大于0的值.
排序整型數據
#include <stdlib.h>
#include<stdio.h>
int cmp(void* e1, void* e2)//比較函數
{return *(int*)e1 - *(int*)e2;//void* 不能解引用,要強制類型轉換//此方式默認升序排列,若想實現降序,可用e2-e1,將邏輯改反
}
void test()
{int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]),cmp );int i = 0;for (i = 0;i < sz;i++){printf("%d ", arr[i]);}}
int main()
{test();return 0;
}
排序結構體數據
#include <string.h>
struct stu
{char name[20];int age;
};
int cmp_name(const void* e1,const void* e2)
{return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void test()
{struct stu arr[3] = { {"zhangsan",18},{"lishi",25},{"wangwu",22} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_name);}
int main()
{test();return 0;
}
其中,strcmp函數比較時,比較的是對應字符的ASCII碼值,并不比較字符串長度,比如:
- “abc"和"abd” : a和a的ASCII碼值相等,比較b和b,也相等,再比較c和d,d的ASCII碼值大,則"abc"小于"abd"
- “aef"和"abcde” : a的ASCII碼值相同,e的ASCII碼值大于b,則不再進行比較,認為"aef"大于"abcde".
可以看出 ’ l ’ < ’ w ’ < ’ z ',因此 " lishi " 排第一
三、qsort函數模擬實現
用冒泡排序實現qsort函數
冒泡排序算法:
void bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0;i < sz-1;i++)//趟數{int j = 0;for (j = 0;j < sz - i - 1;j++)//交換次數{if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = arr[j];}}}
}
我們想排序的數據類型如果不是整型,是結構體類型、字符類型等,在bubble_sort函數參數部分就不可以用整型數組來接收,因此,參考qsort函數的定義:
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2));
- 冒泡排序首先要知道要比較的數據的首元素地址,但是不知道具體是什么類型,因此用void* base來接收
- 需要知道數據的個數,類型為無符號的整型,用sz接收
- 在我們拿到首元素地址之后,向后找元素進行比較,需要知道每個元素的內存大小,不同類型元素的大小不同,但都是整型,因此用width接收,與qsort函數的size相似
- 最后需要傳入對元素進行比較的函數的地址,類型為函數指針,因為并不知道要比較的元素類型,因此用void* 類型的指針進行接收,進行比較時根據不同的類型進行強制類型轉換,然后用返回值來判斷他們的大小關系,因此比較函數返回類型為 int
然后分析冒泡排序函數內部:其中排序的趟數和元素交換次數不需要變
- 在元素進行比較時,如果元素類型不是整型,如結構體類型、字符類型等就不可用大于小于這種方式來比較,因此應該用函數指針cmp去調用 用來比較的函數進行元素比大小
- 在元素交換部分,交換的元素類型不能確定,肯定不能使用 int 類型,因此將其封裝成函數,在其內部進行操作,具體思路如下:
拿到元素地址的目的是為了進行元素的交換. 首先,base是無符號指針,應該先對其進行強制類型轉換成char*類型,那么base+4就拿到下一個元素的地址,base+8拿到第三個元素的地址,以此類推…
每個元素的內存大小為width,那么第一個元素地址為(char*) base + j * width
, 第二個元素地址為(char*) base + (j+1) * width
,拿到兩個元素地址后進行交換,交換完成后 j++,這樣繼續取得第二個和第三個元素的地址,繼續進行操作
有人會問,為什么base要強轉成char*
類型,轉換成int*
不行嗎?
我們不知道要排序的元素類型是什么,可能是整型,可能是結構體類型,可能是字符類型等等,每個元素的大小可能是1、4、7、9等,那么在解引用,進行交換元素的時候,由于整型一次只能交換4個字節的內存,在交換7、9字節的內存時候就不行了,char*類型進行解引用是char類型,每次只交換一個字節,這樣無論元素是什么類型都可以交換完成
因此,由于排序元素的類型不確定性,選擇一個字節一個字節進行交換的方式完成排序,進而選擇(char*)base
具體實現代碼如下:
//比較函數由自己編寫,只有使用者知道要排序什么類型的元素
int cmp_int(const void* e1, const void* e2)
{return strcmp((char*)e1 , (char*)e2);
}
void Swap(char* buf1, char* buf2,size_t width)
{char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;
}
void bubble_sort(void* base, size_t sz, size_t 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 - i - 1;j++){if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}int main()
{char arr[] = "aksjdu";int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);int i = 0;for (i = 0;i < sz;i++){printf("%c ", arr[i]);}return 0;
}