一、回顾上节所学知识
#include<stdio.h> int my_strlen(const char* str) { return 0; } int main() { //指针数组 char* arr[10]; //数组指针 int arr2[5] = { 0 }; int(*p)[5] = &arr;//p是一个指向数组的指针变量 //函数指针 int (*pf)(const char*) = &my_strlen;//pf是一个指向函数的函数指针变量 (*pf)("abcdef"); pf("abcdef"); //函数指针数组 - 存放函数指针的数组 int (*pfArr[10])(const char*); return 0; }
二、指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针
#include<stdio.h> int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int main() { int (*pf)(int, int) = Add; int(*pfArr[10])(int, int) = { Add,Sub };//pfArr是数组名 int(*(*ppfArr)[10])(int, int) =&pfArr;//取出数组的地址应该放在数组的指针中 //ppfArr首先和*结合是一个指针,指向的一个数组(*ppfArr)[10];int(*)(int, int)剩下的这是一个函数指针类型这意味着指向的数组有10个元素每个元素都是函数指针类型 //ppfArr是一个指向函数指针数组的指针变量 return 0; }
三、回调函数
回调函数就是一个通过函数指针调用的函数如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
#include<stdio.h> 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 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 x = 0; int y = 0; printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); int 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; }
假设当有人选择1时,把Add这个函数地址传给Calc,通过pf这个指针调用它所指向的指针,来响应选1的这个动作,所以当通过pf调用加法的时候,加法函数就是它的回调函数,同理当通过pf调用减法的时候,减法函数就是它的回调函数,当通过pf调用乘法的时候,乘法函数就是它的回调函数,当通过pf调用除法的时候,除法函数就是它的回调函数,我们并没有直接调用Add,Sub,Mul,Div而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
四、qsort库函数
之前我们学过冒泡排序那你现在还会写吗?冒泡排序又有什么缺陷呢?我们有什么好办法去改善这个缺陷吗?
1.你还会写冒泡排序吗?
void bubble_sort(int* str, int sz) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++)//两两相邻的元素进行比较 { if (*(str+j) > *(str + 1+j)) { int tmp = *(str+j); *(str+j) = *(str + j+1); *(str + 1+j) = tmp; } } } } #include<stdio.h> int main() { int arr[10] = { 3,2,5,7,8,1,9,4,6,0}; 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; }
2.冒泡排序和qsort库函数的区别
qsort是可以排序任意类型的数据但是冒泡排序是不可以排序任意类型的数据的
3.qsort库函数介绍
void qsort(void* base,指向了待排序数组的第一个元素
size_t num,待排序的元素个数
size_t size,每个元素的大小,单位是字节
int ( * cmp)(const void * ,const void * )指向一个函数,这个函数可以比较2个元素的大小
指向一个函数,这个函数可以比较2个元素的大小时,当p1==p2时返回0,当p1<p2返回<0;当p1>p2返回>0;
注意:
int ( * cmp)(const void * ,const void * )这个函数指针参数类型是void * ;
#include<stdio.h> int main() { int a = 10; int* p = &a; //char* p = &a; void* p = &a;//*void - 无具体类型的指针,所以它可以接受任何类型的地址 *p;//void*的指针不能解引用操作符 p++;//也不能进行++ --操作 *(int*)p;//强制类型转换 return 0; }
a是一个int类型的放在int * 的指针中,所以放在char * 指针中会发生报错。但是放在void * 类型中却不会报错,但是void*的指针不能解引用操作符;也不能进行++ --操作,应该进行强制类型转换
qsort是可以排序任意类型的数据:
1.比较2个整数的大小,> < ==
2.比较2个字符串,strcmp进行比较
3.比较2个结构体数据(学生:张三、李四)指定比较的标准,拿什么比较?
4.qsort库函数完成冒泡排序
qsort默认是排成升序的
#include<stdio.h> #include<stdlib.h> int cmp_int(const void* p1, const void* p2) { return *(int*)p1 - *(int*)p2; } test1() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; 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]); } } int main() { test1(); return 0; }
5.qsort库函数完成结构体排序
比较2个结构体数据(张三、李四)指定比较的标准,拿什么比较?
我们可以根据年龄姓名等来比较:
(1).根据年龄来排序
#include<stdio.h> #include<stdlib.h> struct stu { char name[20]; int age; }; int cmp_stu_by_age(const void* p1, const void* p2) { return ((struct stu*)p1)->age - ((struct stu*)p2)->age ; } test2() { struct stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); } int main() { test2(); return 0; }
通过调试可以发现根据年龄改变了顺序
(2).根据名字来排序
名字是字符串,比较2个字符串,strcmp进行比较
#include<stdio.h> #include<stdlib.h> #include<string.h> struct stu { char name[20]; int age; }; int cmp_stu_by_name(const void* p1, const void* p2) { return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name); } test2() { struct stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); } int main() { test2(); return 0; }
strcmp比较时同样是当p1==p2时返回0,当p1<p2返回<0;当p1>p2返回>0;
通过调试可以发现根据名字改变了顺序是根据所在字母的ASCII码值如果第一对首字母都相同就比较下一对依次往后
五、使用冒泡排序的思想来实现一个类似于qsort这个功能的冒泡排序函数bubble_sort()
(1).冒泡排序
#include<stdio.h> #include<stdlib.h> int cmp_int(const void* p1, const void* p2) { return *(int*)p1 - *(int*)p2; } //希望这个bubble_sort函数可以排序任意类型的函数 //交换buf1和buf2这两个元素,每个元素假设6个字节,我们不能用两个变量进行交换因为我们不知道这两个元素是什么类型的所以不知道创建什么类型的临时变量所以我们一个字节一个字节的交换 //也就是buf1的第一个字节和buf2的第一个字节交换依次开始直到每个字节都交换了这样我们这两个元素就交换了 void Swap(char* buf1,char* buf2,int width) { int i = 0; for (i = 0; i < width; i++) { //这是处理了一对字节 char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; //然后地址++ buf1++; buf2++; } } void bubble_sort(void* base, size_t num, size_t width,int (*cmp)(const void* p1,const void* p2)) { //排序的是整形把数组名传过去是int* base,如果把void*改成char*会报警告,写成void*不论什么数据都能很好的接收 //因为排序的数字不可能是负数个所以size_int更贴合一点它是无符号整形 //确定冒泡排序的趟数 size_t i = 0; for (i = 0; i < num - 1; i++) { //一趟冒泡排序的过程 size_t j = 0; for (j = 0; j < num - 1 - i; j++) { //两个相邻元素的比较 //arr[j] arr[j+1] //把两个需要比较的函数放在cmp中 //通过代码我们可以知道首元素地址是base,那我们写成base+j 与base+j+1对吗?//base是一个void*的指针不能直接加一个值的 //我们不知道base所指向的类型是什么类型,如果我们强制类型转换成int*指针,加j就跳过j个整形,那如果我们排序的是字符串或者结构体呢,整形指针就不合适了 //width是宽度,一个元素是width加j就跳过j个元素,相当于跳过j*width这么多字节,char*的指针跳过一个一个字节,让它加j*width相当于跳过了j个元素字节的个数 //如果升序那就写成cmp()>0,也就是p1>p2然后进行交换 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0) { //当返回值>0时我们就交换 //Swap函数已经强制类型转换成char* //把两个数的地址传过去和两个字节的宽度这样我们就知道从buf1和buf2开始交换width这么多的字节 Swap((char*)base + j * width, (char*)base + (j + 1) * width,width); } } } } void test3() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); //qsort默认是排成升序的 bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int main() { test3(); return 0; }
步骤:
(1).
首先准备一个数组,然后求一下它的个数是10,然后我们把首元素地址,个数,一个元素的大小,cmp_int的函数的地址传进去
然后调用冒泡函数,base指向了数组首元素地址也就是9的地址,num是个数也就是10,宽度是4个字节,把cmp_int(也就是函数名)传给了cmp这个指针,然后指针指向了int cmp_int(const void p1, const void* p2)
(2).
假设第一对也就是比较9和8用cmp来比较,把9和8的地址传过去然后base强制类型转换成字符指针
第一次进去时j=0;0width=0,所以base+0还是指向首元素9的地址;j=0;j+1=1;1width(宽度是4)=4,强制类型转换成char*跳过4个字节指向8的地址
(3).
然后int cmp_int(const void* p1, const void* p2)中p1指向的是9;p2指向的是8,p1强制类型转换成整形指针解引用拿到的是9;同理p2拿到的是8;9-8=1
调用返回后1>0,所以9>8不满足升序,我们进行交换,把两个数的地址传过去,buf1指向的就是9,buf2指向的就是8;宽度是4
我们知道9和8在内存中存储时(小端存储);09 00 00 00 ;08 00 00 00
(4).
我们的buf1指向的就是09,buf2指向的就是08,我们循环4次,09和08交换,同理后面的00和00依次交换,直到全部交换完成后就变成了8,9
(5).
同理我们交换第一对后j++就变成了1,1width=4,所以base+1强制类型转换成char跳过4个字节指向元素9(交换后的)的地址;j=1;j+1=2;2width(宽度是4)=8,强制类型转换成char跳过8个字节指向7的地址,所以比较的是9和7
(6).
cmp这个函数是在外面提供的,所以通过函数指针去调用这个函数时,它就被成为回调函数
(7).
排成升序,假设在int cmp_int(const void* p1, const void* p2)中p1是1,p2是3,1-3<0;所以返回时就不需要交换了
如果降序就if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)<0)改成<0;
(2).根据名字来排序
#include<stdio.h> #include<stdlib.h> #include<string.h> struct stu { char name[20]; int age; }; int cmp_stu_by_name(const void* p1, const void* p2) { return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name); } void Swap(char* buf1, char* buf2, int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, size_t num, size_t width, int (*cmp)(const void* p1, const void* p2)) { size_t i = 0; for (i = 0; i < num - 1; i++) { size_t j = 0; for (j = 0; j < num - 1 - i; j++) { if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } void test3() { struct stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name); } int main() { test3(); return 0; }
可以看出经过调试根据名字排序是可以实现的
根据上面的代码那你知道怎么根据结构体年龄来排序吗
总结
以上是对C-指针进阶的简单了解和qsort库函数的简单了解。