文章目录
前言
在前面小编讲述了许多有关于c语言指针的内容,今天小编要讲述的是有关于回调函数和空指针相关的传参函数qsort。
提示:以下是本篇文章正文内容,下面案例可供参考
一、回调函数
1.1、什么是回调函数?
回调函数是指不直接调用,而用函数指针进行调用的函数叫做回调函数
如果你把函数的地址(指针)作为参数传递给另一个函数,在另一个函数中通过函数指针进行调用该函数,这个被调用的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,而是在特定的事件或条件发⽣时由另外的一方调用的,用于对该事件或条件进⾏响应。
int add(int x, int y)
{
return x + y;
}
void test(int(*p)(int, int))
{
int ret = p(20, 30);
printf("%d",ret);
}
int main()
{
test(add);
return 0;
}
分析
在上面代码中,我们在主函数中传递add函数指针给test函数,main主函数中不调用add函数,而在test函数中,通过传递过来的函数地址去找到函数,并调用函数,那么此时add函数被称为回调函数。
1.2、回调函数的优点
简化代码,避免代码重复书写导致代码过于冗长
写一份代码完成计算机的加减乘除的操作。
在没有学习函数指针,正常情况书写下,在switch语句中,会出现以下这种状况:
这样代码重复部分就会很多,导致代码过于冗长,或者之后我们需要实现的不止两个数的加减乘除,还会计算一些乘方,取根这种运算或者计算3个数4个数的情况的话,那么修改代码就会很长,也不会方便修改。在这里合理运用函数指针,可以有效的减少代码量和修改,并可以随时根据使用的情况随意切换所要调用的函数,使代码更加高效。
使用函数指针传参时候
我们可以实现:
- 把重复的代码实现成函数
- 但是这个函数又能完成不同的任务
//使⽤回调函数改造后
#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函数的使用
2.0、冒泡排序
冒泡函数学习可以参考小编所写c语言题目之利用冒泡排序算法解决十个乱序数字排成升序数字进行学习冒泡排序。
冒泡排序的基本原理就是相邻的两个数之间进行比较大小,但是我们可以看到它只能对单独的整型数据进行排序大小,那有没有能排序所有类型数据的,肯定有的,这就是小编所要讲的qsort函数,qsort函数能够排序任意类型的数据。
2.1、qsort函数
qsort具体函数功能参考链接:
cpluscplus官网
qsort是一个库函数,直接用来排序数据的。底层使用的是快速排序的方式
分析
点开小编上面的官网,与小编所绘图,我们可以知道以下几点
- qsort函数原型为void qsort (void* base, size_t num, size_t size,
int (compar)(const void,const void*));函数的参数有四个。 参数1
:void* base指向要排序的数组的第一个元素的地址。为什么是void*类型呢?因为我们也不知道我们这里要排序的数组到底是字符数组还是整型数组,或者其他类型的数组,所以这里我们用无具体类型的指针进行接收。参数2
:size_t num,要排序的数组的元素个数,因为要排序的数组的元素个数肯定是大于0的,所以这里的类型接收是无符号整型的。参数3
:size_t size,这里是要排序的数组的每个元素的大小,为什么要这个参数,是因为,我们用的是无符号类型的指针进行接收,我们不知道一个元素到底占多大的大小,指针+1-1到底跳过多少个字节。参数4
:函数指针 int (compar)(const void p1,const void* p2),这个函数指针是需要自己进行定义的,这个是比较函数,将两个要比较的元素的地址传进去,返回值有三种情况,当p1大于p2返回大于0的数,当p1小于p2返回小于0的数,当p1等于p2返回0。
2.2、qsort函数的使用 - 排序整型数据
int cmp_int(const void* p1, const void* p2)
{
return *((int*)p1) - *((int*)p2);
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = { 1,5,6,4,7,3,8,9,2,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print(arr,sz);
return 0;
}
代码分析
在上述代码中,小编首先先创建了一个整型类型的数组,数组中存放打乱的1-10的数字。在求出数组元素的个数;之后调用qsort函数,qsort函数有四个参数,第一个参数是数组首元素的地址,第二个参数是数组元素的个数,第三个参数是每个元素的大小,第四个参数则是我们自己需要定义的函数,定义的函数返回值是int类型,两个参数接收需要用const void*类型的指针进行进行接收。
2.3、qsort函数的使用 - 排序结构体数据
struct stu
{
char name[20];
int age;
};
int cmp_by_name(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name,((struct stu*)p2)->name);
}
int main()
{
struct stu arr[3] = { {"zhangsan",28},{"lisi",35},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ",arr[i].name);
}
return 0;
}
分析
跟整型数据的不同的是,我们这里定义了一个结构体类型的数据,并且需要排序的结构体数据中的字符串数据,前面内容定义变量啥的小编不多赘述,如果需要了解结构体相关内容的话,请看小编所写c语言结构体学习,因为比较的是字符串,所以qsort函数的最后的参数比较的是俩个字符串的比较,两个字符串比较则需要strcmp函数进行比较。
2.4、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 - 1 - i; j++)
{
if (arr[j] < arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
需要修正的地方
首先,参数部分肯定要修改,因为模拟实现qsort函数进行比较的不是单一的整型类型数据,参数部分可以参考qsort函数的部分,也就是上面小编所写的四个参数。
第二,两个元素之间进行比较肯定也要进行修改,因为不确定传进来的参数是整型数据还是其他数据,假如是字符串类型的数据,那么肯定不能这样比较,这就是qsort第四个参数的缘由。
最后,两个元素之间进行交换也是需要修改的,两个元素交换涉及到交换多少个字节,所以也是需要修改的。
两两元素之间的比较修改
函数参数:
修改后的函数比较代码部分:
compar((char*)arr+j*width,(char*)arr+(j+1)*width)
为什么要修改成这样呢?
我们传进来的是不知道是什么类型的数据的首元素的地址arr,我们假如和上方原代码一样:arr[j] < arr[j + 1],arr[ j ] = *(arr + j),arr是数组首元素的地址,地址加1我们不知道这是什么类型的数据,也就不知道跳过多少个字节。所以这里比较函数是必须要修改的。但是怎么修改呢,我们可以看到下面这张图:
这里arr代表首元素的地址,数据是什么类型的我们不可知,我们只知道传进来一个元素的大小,那我们首元素的地址强制转换成char*类型的地址然后再去加上一个元素的大小不就可以得到下一个元素的地址。
两个元素之间的交换
交换函数,为了代码的可读性,我们将交换函数封装起来。
void swap(const char* arr1, const char* arr2, size_t width)
{
size_t i = 0;
char temp = 0;
for (i = 0; i < width; i++)
{
temp = *((char *)arr1+i);
*((char*)arr1 + i) = *((char*)arr2 + i);
*((char*)arr2 + i) = temp;
}
}
这里存在三个参数是为什么呢?前面两个是要交换元素的地址,最后一个元素则是元素大小,这还是和之前一样的原因:两个元素需要进行交换值,但是他只给定了两个地址
但是我们并不知道他要交换多少的数据,s2后面是长长的空间,不能全部都交换给s1,这样会发生错误,正确的就是把他们的元素大小传进去,每一次交换则交换一个元素的大小。