欢迎来CILMY23的博客喔,本期系列为【C语言】指针的收尾篇,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】,图文讲解qsort函数,带大家更深刻理解指针,感谢观看,支持的可以给个赞哇。
前言
在上一篇指针的进阶篇博客中,我们了解了其他类型的指针变量,并且彻底了解了指针数组和数组指针的区别,以及函数指针和函数指针数组的出现,让我们了解了简化代码的另一种方式,本期博客将用两个有趣的代码来开篇,并学习回调函数和qsort函数的相关内容。
目录
一、有趣的代码
二、回调函数
三、qsort函数
四、qsort模拟实现
(*(void (*)())0)();
void (*signal(int , void(*)(int)))(int);
首先第一段代码我们看里面void (*)(),这是一个函数指针类型,指针所指向的函数类型是void,参数是(), 在函数指针的外围我们看到一个0,左边有一对括号,这个(),是代表强制类型转换的意思,我们把0这个数值,强制类型转换成函数指针类型,就表示0地址放了一个void (*)()的函数,然后对其解引用,进行调用。
调用0地址处的函数,调用的函数参数是无参,返回类型是void
第二段代码:
首先在第二段代码里,我们看到有个signal函数,它的两个参数类型一个是整型,一个是函数指针类型,这个函数指针类型的返回类型是void,形参中有int整型。所以这一段代码说的是signal的函数声明,它的返回类型是函数指针类型。
signal是一个函数的函数名
它有两个参数,第一个参数是int类型,第二个参数是函数指针类型,该函数指针指向的函数参数是int类型,返回类型是void。
signal函数的返回类型也是函数指针类型,该函数指针指向的函数参数是int类型,返回类型是void。
回调函数就是⼀个通过函数指针调用的函数。 在进阶篇我们使用了转移表来实现普通的四则计算器,现在我们对转移表进行改装一下,
#include
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;
int ret = 0;
printf("请输入2个操作数:>");
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;
}
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数 时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,⽤于对该事件或条件进行响应。
就像上述代码中,函数我们把它作为参数传递给cal函数,这个指针被用来调用四则运算的函数,那么四则运算的函数就是回调函数。
qsort函数可以在cplusplus网站查询:cplusplus.com - The C++ Resources Network
底下一部分是参数解释:
qsort函数是一个库函数,可以对数据进行排序,它可以排序任意类型的数据!
四个参数解析如下:
首先解释一个指针类型:void*是什么?
#include
int main()
{
int a = 5;
int* pa = &a;
char* pc = &a;
void* p = &a;
return 0;
}
我们先来运行以下代码,我们发现,编译器会给我们一个警告:
而void * 不报错。这是因为void* 是一种指针类型,这种类型是通用的。可以用来接收任意数据类型的地址。 针对void * 类型,它只是用来存放地址的,不能拿来解引用。
其次是中间三个参数,一个是待排序,一个是元素个数,一个是大小。
最后我们看第四个参数 ,
它的使用是,比较前面一个参数和后面一个参数的大小,如果前面小于后面,就返回一个比0小的值,如果前面一个参数和后面一个参数的大小相等,就返回0,如果前面一个参数比后面一个参数大的话,就返回一个比0大的值。
qsort的使用如下:
#include
//两个元素比较
int int_com(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//打印数组
void print_arr(int* p, size_t sz)
{
size_t i = 0;
for (i = 0; i
在上述代码中,我们在test1函数里测试qsort排序一个整型数组。 我们自己写了一个函数,int
_com来完成第四个参数。所以这个int_com就是我们前面所提到的回调函数。
接下来是qsort测试排序结构体:
#include
#include
struct Stu
{
char name[20];
int age;
};
//结构体年龄数据比较
int Stu_age_com(const void* e1,const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//结构体名字数据比较
int Stu_name_com(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}
//打印结构体
void print_Stu(struct Stu* p,int sz)
{
for (size_t i = 0; i < sz; i++)
{
printf("%s %d\n", (p + i)->name, (p + i)->age);
}
}
//测试qsort排序结构体数据
void test2()
{
struct Stu s[] = { {"zhangsan",30},{"lisi",20},{"wangwu",35} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), Stu_age_com);
print_Stu(s, sz);
qsort(s, sz, sizeof(s[0]), Stu_name_com);
print_Stu(s, sz);
}
int main()
{
test2();
return 0;
}
结果如下:
在上述代码中,我们要比较两个结构体并不是简单用关系运算来比较,而是根据结构体成员的不同来具体比较,比如我们按照年龄来,姓名来等等……
qsort的排序是用的快速排序算法,排序算法有很多,希尔排序,快速排序,冒泡排序等等……
我们先来写出先前讲述的冒泡排序,它存在的一些缺点是什么?
#include
void Bubble_sort(int* arr, int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
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]);
}
}
int main()
{
int arr[] = { 1,5,2,3,7,4,6,0,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubble_sort(arr, sz);
print_arr(arr, sz);
return 0;
}
它一个是只能用来排序整型数据,第二个我们是其实把它写死,比较上也只能有简单的关系比较,那如果我们想扩展一下排序任意数据呢?那我们就需要用到void * 这个类型来接收任意数据类型的地址。
改造的三个点:
一个是比较方法上要更改,能够比较任意数据
一个是数据类型要更改,也就是改造参数
一个是交换的代码不在是只有整型数据了
首先第一个在排序算法上,我们要对原先的bubble_sort进行参数增加,首先是起始位置的地址,我们要能接收任意数据类型的地址,就要采用void * 的数据类型,其次是长度我们用sz来接收,大小我们写成width,然后是比较的函数,写成函数指针类型。
其次是交换的地址
为了获取地址,我们需要将地址转换成char*一个字节,为什么用char*,因为内存中的内存单元就是一个字节大小,char*刚好是一个字节,这样获得最小单位,然后用width*我所对应的第几个元素,就能得到对应的数据元素地址。
然后我们把地址传给我们需要比较的函数,最后是交换的内容。因为数据是二进制存入内存,一个内存单元存一个字节的数据,我们只需要把每个数据的地址传进Swap,然后根据width决定我一次要交换几个内存单元,最后就是完成交换的内容,这样一个Bubble_sort的模拟qsort实现就结束了。
#include
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++;
}
}
int int_com(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void Bubble_sort(void* base,size_t sz,
size_t width ,int(*cmp)(const void*e1,const void*e2))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 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);
}
}
}
}
测试环节
利用新写的Bubble_sort来排序整型数据
void test1()
{
int arr[] = { 1,5,2,3,7,4,6,0,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubble_sort(arr, sz,sizeof(arr[0]),int_com);
printf_arr(arr, sz);
}
int main()
{
test1();
return 0;
}
结果如下:
利用Bubble_sort测试结构体数据,我们把先前写的代码给它扣下来,拿来测试
struct Stu
{
char name[20];
int age;
};
//结构体年龄数据比较
int Stu_age_com(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//结构体名字数据比较
int Stu_name_com(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//打印结构体
void print_Stu(struct Stu* p, int sz)
{
for (size_t i = 0; i < sz; i++)
{
printf("%s %d\n", (p + i)->name, (p + i)->age);
}
}
//测试Bubble_sort排序结构体数据
void test2()
{
struct Stu s[] = { {"zhangsan",30},{"lisi",20},{"wangwu",35} };
int sz = sizeof(s) / sizeof(s[0]);
Bubble_sort(s, sz, sizeof(s[0]), Stu_age_com);
print_Stu(s, sz);
Bubble_sort(s, sz, sizeof(s[0]), Stu_name_com);
print_Stu(s, sz);
}
结果如下:
源码如下:
#include
#include
//交换函数
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++;
}
}
//整型数据的比较函数
int int_com(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//冒泡排序
void Bubble_sort(void* base,size_t sz,size_t width ,
int(*cmp)(const void*e1,const void*e2))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 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 print_arr(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//定义结构体
struct Stu
{
char name[20];
int age;
};
//结构体年龄数据比较
int Stu_age_com(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//结构体名字数据比较
int Stu_name_com(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//打印结构体
void print_Stu(struct Stu* p, int sz)
{
for (size_t i = 0; i < sz; i++)
{
printf("%s %d\n", (p + i)->name, (p + i)->age);
}
}
//测试Bubble_sort排序结构体数据
void test2()
{
struct Stu s[] = { {"zhangsan",30},{"lisi",20},{"wangwu",35} };
int sz = sizeof(s) / sizeof(s[0]);
Bubble_sort(s, sz, sizeof(s[0]), Stu_age_com);
print_Stu(s, sz);
Bubble_sort(s, sz, sizeof(s[0]), Stu_name_com);
print_Stu(s, sz);
}
//Bubble_sort测试整型数组
void test1()
{
int arr[] = { 1,5,2,3,7,4,6,0,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubble_sort(arr, sz,sizeof(arr[0]),int_com);
print_arr(arr, sz);
}
int main()
{
test1();
test2();
return 0;
}
感谢各位同伴的支持,本期指针收尾篇就讲解到这啦,接下来指针还会有一个番外练习,如果你觉得写的不错的话,可以给个赞,若有不足,欢迎各位在评论区讨论。