C_深入理解指针(四) —— 回调函数、qsort使用举例、qsort函数的模拟实现
目录
一、回调函数
二、qsort 使用举例
1、使用qsort函数排序整型数据
2、使用qsort排序结构数据
三、qsort函数的模拟实现
一、回调函数
- 回调函数是⼀个通过函数指针调用的函数。
- 若把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。
- 回调函数不是由该函数的实现方式直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进⾏响应。
上一篇博客中写的计算机实现的代码中,红色框中的代码是重复出现的,其中虽然执行计算的逻辑是区别的,但是输⼊输出操作是冗余的,这就需要qsort函数。
因为红色框中的代码,只有调用函数的逻辑是有差异的,
回调函数的功能:
可把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数
。
使用回到函数改造前:
//使⽤回调函数改造前
#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("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n",ret);
break;
case 4:
printf("输⼊操作数:");
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;
}
使用回到函数改造后:
//使⽤回到函数改造后
#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;
}
void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 1;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
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是C语言中的一个库函数,这个函数是用来对数据进行排序,对任意类型的数据都能进行排序。
- qsort函数的头文件是#include<stdlib>
- qsort函数的底层逻辑是用了快速排序的思想,而且qsort排序默认是升序。
可以通过第一个形参base知道所指向的数据的地址位置,可以通过第三个形参size的大小去判断比较的类型:
void qsort (void* base, //指向待排序数组的第一个元素的指针
size_t num,//base指向数组中的元素个数
size_t size,//base指向的数组中一个元素的大小,单位是字节
int (*compar)(const void*,const void*)//函数指针——传递函数的地址
);
以下代码排序整型数据是没问题的,但是对于其他类型的数据,比如字符型、字符串、浮点数、结构体等类型的数据则不能排序。比如两个字符串比较大小不能使用>、>=、<、<=、==、!=,两者想要比较的话则需要用strcmp(比较对应位置上字符元素的ASCII码值的大小)
排序算法有很多种:冒泡排序、插入排序、选择排序、快速排序等等......
#include <stdio.h>
//冒泡排序
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-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[] = { 3,1,7,9,4,2,6,5,8,0 };
//排序——升序
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr, sz);
Print_arr(arr,sz);
return 0;
}
二、qsort 使用举例
1、使用qsort函数排序整型数据
#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
//void类型不能解引用
int int_cmp(const void * p1, const void * p2)
{
//所以首先要强制转化为整型类型再解引用
if (*( int *)p1 - *(int *) p2)
return 1;
else if (*( int *)p1 - *(int *) p2)
return -1;
else
return 0;
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
2、使用qsort排序结构数据
struct Stu //学⽣
{
char name[20];//名字
int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test2();
test3();
return 0;
}
重点扩展:
结构成员访问操作符有两种:
- . 结构体变量.成员名
- -> 结构体指针->成员名
#include <stdio.h>
struct Stu
{
char name[20];
int age;
}
//结构成员访问操作符有两种:
//. 结构体变量.成员名
//-> 结构体指针->成员名
int main()
{
struct Stu s = {"zhangsan",20};
printf("%s %d\n",s.name,s.age);
struct Stu* ps = &s;
printf("%s %d\n",(*ps).name,(*ps).age);
printf("%s %d\n",ps->name,ps->age);
return 0;
}
qsort排序默认是升序;若想要实现降序排序的话,则可以将p1和p2的为位置互换一下,这样会使满足的条件相反的交换,形成降序排序。
调用qsort函数这类函数的代码叫做泛型编程,没有指定的类型,只用指针来确定位置,类型可以随意变化使用。
三、qsort函数的模拟实现
使用回调函数,模拟实现qsort(采用冒泡的方式)。
注意:
这里第⼀次使用void* 的指针,讲解 void* 的作用。
#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
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;
}
}
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(arr[j]>arr[j+1])
if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
{
//交换两个元素
_swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
重点:一定要使用char,使用char的原因:一个一个字节地去交换,char类型大小是一个字节,以一个字节为单位的字节加减可以使精度更准确。