2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 函数指针的使用精髓 -- 回调函数+qsort的内部实现

函数指针的使用精髓 -- 回调函数+qsort的内部实现

时间:2022-01-28 12:21:15

相关推荐

函数指针的使用精髓 -- 回调函数+qsort的内部实现

前言:

我们有普通指针(存放普通变量的地址)、数组指针(存放数组的地址)... ... 那函数的地址可以存起来吗?可以的话该怎么用函数指针呢?这是本篇博客探讨的问题...

目录:​​​​​​​

一、函数指针的定义+简单使用

二、函数指针的使用场景 -- 回调函数的定义引入

1.函数指针的使用 + 回调函数引入

2.回调函数定义

三、回调函数qsort的使用

1.qsort的使用

2. 使用原理-- 了解qsort函数

2.1 qsort函数的参数

2.2 qsort对compare函数返回值的要求

3.测试题

四、qsort函数的内部实现

1. 分析

1.1 找差异化部分

1.2 调用差异化部分的函数

2. 源码

一、函数指针的定义+简单使用

函数指针 -- 指针,存放函数的地址。或者说,指针指向一个函数。

函数指针这样用可就太别扭啦~打开方式不对,重来🤪

我们接下来用一个例子,来引入回调函数的定义,并在其中探讨函数指针的用法!

二、函数指针的使用场景 -- 回调函数的定义引入

1.函数指针的使用 + 回调函数引入

为了方便理解并使用函数指针,我们先引入一个编程题:写一个计算器,能够根据用户输入case情况,完成加减乘除四种算法。(提示:可以用switch case语句)

代码示例:

#include<stdio.h>void menu(){printf("**********************************\n");printf("***** 1. add2. sub*****\n");printf("***** 3. mul4. div*****\n");printf("***** 0. exit*****\n");printf("**********************************\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;}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("输入2个操作数:>");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输入2个操作数:>");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输入2个操作数:>");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输入2个操作数:>");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;}

分析过程:

提问: 我们会发现:每次完成一个算法,我们就要有输入整数部分(图中红色部分所示),这使得代码非常冗余。有没有好的方式可以解决冗余问题?

思考:可否将重复代码放在一个新函数里面?但是加减乘除的算法函数并不相同啊,如果把代码红色部分放在一起,那俺们怎么进行不同的计算呢?💢

解决:首先我们将冗余部分包装成一个新的函数(叫他F吧),现在这个函数的返回值和参数待定。那既然计算函数不同,那我们就把不同的算法函数地址传给F函数,在F函数里,解引用函数指针就可以找到计算函数,继而完成不同计算... F函数没有输出值,所以返回类型是void;传入了函数指针,所以参数就是函数指针。void F( int (*p)(int x,int y) )

看!函数指针就派上用场了,我们来改装一下代码...

改装代码示例:

#include<stdio.h>void menu(){printf("**********************************\n");printf("***** 1. add2. sub*****\n");printf("***** 3. mul4. div*****\n");printf("***** 0. exit*****\n");printf("**********************************\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 F(int(*p)(int,int)){int x = 0;int y = 0;int ret = 0;printf("输入2个操作数:>");scanf("%d %d", &x, &y);ret = p(x, y);printf("ret = %d\n", ret);}int main(){int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:F(Add);break;case 2:F(Sub);break;case 3:F(Mul);break;case 4:F(Div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误\n");break;}} while (input);return 0;}

将Add函数的地址传给新函数F, F通过解引用地址来调用Add算法函数。F就叫做回调函数。

这样回调函数可以实现多个计算,帮助解决冗余代码~

2.回调函数定义

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

三、回调函数qsort的使用

1.qsort的使用

在使用之前,我们要先了解它。一个函数,参数是什么,返回类型是什么?完成的是什么操作?qsort是库函数,它是一个回调函数。Performs a quick sort. --是干嘛的?是完成快速排序的。

前面提到计算器中回调函数的使用和qsort的使用一样:qsort函数也是接收一个差异化函数并调用

我们先来试着,用qsort函数编写一个对整形数组排序的算法...代码如图所示:

2. 使用原理-- 了解qsort函数

2.1 qsort函数的参数

来看一看它的声明:

返回类型是void , 有四个参数。

我们先要弄明白,每个参数的含义。既然是排序,那应该是对数组里面的元素排序。

void*base -- 数组地址

size_tnum -- 数组长度大小

size_twidth--数组每个元素的字节大小

int(__cdecl*compare)(constvoid*elem1,constvoid*elem2 -- 函数指针,指向一个比较函数。比较函数的的返回类型是int,参数是参与比较的数组元素地址

参数的大概意思我们已经知道了,但是对于void*base以及int(__cdecl*compare)(constvoid*elem1,constvoid*elem2有需要注意的地方!一个个参数来分析:

提问1:void*base为什么指针类型是void* ?

思考思考:比较两个数的大小时,变量可能是整型、浮点型、甚至是结构体。为什么对于的参数不是int*,float*... ?我们在引入部分也提到了,回调函数实现多个计算。既然需要进行不同的计算,如果传入的是指定的类型,比如是int *,那就不能用回调函数排序浮点型和其他的数据类型了。void* 可以接收不同类型的参数。(第四个参数也是一样的原理)

提问2:为什么有const修饰?

因为void*eum1是数组元素的指针,这个值是不能被随意改变的,否则比较的就不是我们想要的两个元素的值了。用const修饰可以很好的避免被修改

2.2 qsort对compare函数返回值的要求

所以在比较函数中我们用的是return>0或者<0或者=0的数字

提示:

因为前面提到,void*是不能直接被解引用的,也是不能+-整数的。所以比较两个元素时,需要将void*类型的指针转化为元素原本的类型,才能够实现数据的比较。例如:比较整型数组的元素,数组元素的类型本来是int整型的,所以指针类型也应该转成整型。然后再解引用。

3.测试题

学会了上面对整型数组排序之后,再来做一做结构体数组的排序吧~

案例:用qsort函数,编程实现对结构体数组元素的排序

代码示例:

#include<stdio.h>#include<stdlib.h>//创建结构体类型struct Stu{char name[20];int age;double score;};//比较函数 -- 比较字符串int cmp_by_name(const void* num1, const void* num2){return( strcmp ( ((struct Stu*)num1)->name , ((struct Stu*)num2)->name ) );}//比较函数 -- 比较ageint cmp_by_age (const void*num1, const void* num2){return ( ((struct Stu*)num1)->age - ((struct Stu*)num2)->age );}int cmp_by_score(const void* num1, const void* num2){if (((struct Stu*)num1)->score > ((struct Stu*)num2)->score){return 1;}else if (((struct Stu*)num1)->score < ((struct Stu*)num2)->score){return -1;}else{return 0;}}//打印排序后结构体的值void print_(struct Stu arr[],int sz){for (int i = 0; i < sz; i++){printf("%9s %5d %9.2f", arr[i].name, arr[i].age, arr[i].score);printf("\n");}printf("\n");}int main(){struct Stu arr[] = { {"Dongxin",19,97.63},{"Lihua",18,89.77},{"Xiaohong",20,100} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_by_name);//排序nameprint_(arr, sz);qsort(arr, sz, sizeof(arr[0]), cmp_by_age);//排序ageprint_(arr, sz);qsort(arr, sz, sizeof(arr[0]), cmp_by_score);//排序scoreprint_(arr, sz);return 0;}

代码讲解:

注意事项:

-- 比较name(字符串)

name是字符串,字符串比较要用不能直接相减!需要用库函数strcmp,记得引头文件

-- 比较age(int整型)

首先要明确,我们最终要比较的是结构体的元素里面的age

(struct Stu*)就是是将进行比较的void指针强制转换成结构体类型指针

(struct Stu*)num1结构体数组中的第num1个结构体元素,但我们要比较的是结构体里面的 age。

(struct Stu*)num1)->age是对结构体的解引用,这样就可以拿到里面的age进行比较

-- 比较浮点数 (浮点型)

比较两个浮点数,由于浮点数在内存中的存储比较特殊,不能直接相减返回。

四、qsort函数的内部实现

既然会用了,那再走远一点...qsort函数内部是怎么实现的呢?

编程:模仿qsort的功能实现一个通用的冒泡排序

1. 分析

1.1 找差异化部分

前面计算器的案例提到过:我们需要找差异化的部分

先来看看冒泡排序差异化部分在哪里...

#include<stdio.h>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_(int arr[],int sz){for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");}int main(){int arr[10] = {1,4,5,3,2,7,6,8,0,10 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr,sz);print_(arr, sz);return 0;}

排序过程中对传入的数据大小比较是不同的,如红色框框部分。如果是字符串进行比较,要用strcmp;如果是浮点型、结构体,也要根据相应的类型做不同的计算。

1.2 调用差异化部分的函数

⬇⬇⬇

重点来了:怎么在qsort里面用cmp函数?

函数名作为函数地址的时候可以直接调用。所以直接cmp_int()

⬇⬇⬇

cmp函数接受的是地址,所以调用cmp-int,()传的是地址。要想保证一个函数实现多个计算,不能强制转化成int,double等特定的类型。由于qsort接收了width参数,我们可以把元素指针转换成char*类型。由(char*)ch+width决定走一步的步长,即确定指针类型。

i,j++,步长需要变化。(char*)th + j * width可以解决问题。

⬇⬇⬇

接下来就是交换值,char*指针每次只能访问一个字节。但是目标元素的单位可不止一个字节,所以需要一个字节一个字节去改变值。

2. 源码

#include<stdio.h>int cmp_int(const void* e1, const void* e2){return *(int*)e1 - *(int*)e2;}void swap(char* element1, char* element2, int width){for (int i = 0; i < width; i++){char tmp = *element1;*element1 = *element2;*element2 = tmp;element1++; element2++;}}void my_qsort(int arr1[], int sz, int 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_int((char*)arr1 + j * width, (char*)arr1 + (j + 1) * width) > 0){swap((char*)arr1 + j * width, (char*)arr1 + (j + 1) * width, width);}}}}void print_(int arr2[],int sz){for (int i = 0; i < sz; i++){printf("%d ", arr2[i]);}printf("\n");}int main(){int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);my_qsort(arr,sz,sizeof(arr[0]),cmp_int);print_(arr,sz);return 0;}

学自己不会的知识才能慢慢成长~

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。