当前位置: 首页 > news >正文

【c语言】深入理解指针3——回调函数

一、回调函数

回调函数:通过函数指针调用的函数.

当把一个函数的地址传递给另一个函数,通过该地址去调用其指向的函数,那么这个被调用的函数就是回调函数.

示例:
在【深入理解指针2】中结尾写了用函数指针实现计算器的功能,如下:

void menu()
{printf("1.add   2. sub \n");printf("3.mul   4. div \n");printf("0.exit \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 calc(int (*pf)(int, int))
{int a, b;printf("请输入两个参数:");scanf_s("%d %d", &a, &b);int ret = pf(a, b);printf("%d \n", ret);
}
int main()
{int a = 0;int b = 0;int input = 0;int ret = 0;do {menu();printf("请选择:");scanf_s("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);case 4:calc(Div);break;case 0:printf("退出\n");break;default:printf("请重新输入:");break;}} while (input);return 0;
}

这里加入想实现加法函数的功能,并没有直接在主函数中直接调用加法函数Add,而是通过另一个函数calc中函数指针变量来接受Add函数的地址,然后通过函数指针变量再去调用Add函数,因此,这里用函数指针去调用的那个函数就是回调函数,即Add、Sub、Mul、Div是回调函数.

二、qsort函数使用

官网对qsort函数的说明:

void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));      

qsort函数有四个参数:

  1. void* base : base是一个指针,指向待排序数组的首元素地址
  2. size_t num : num是指待排序数组中元素的个数
  3. size_t size :size是指待排序数组中每个元素的大小
  4. compar : 函数指针,指向两个元素的比较函数,此函数需要自己实现

在这里插入图片描述
假设两元素为p1,p2,若p1<p2,则返回小于0的值;若p1=p2,则返回等于0的值;若p1>p2,则返回大于0的值.

排序整型数据

#include <stdlib.h>
#include<stdio.h>
int cmp(void* e1, void* e2)//比较函数
{return *(int*)e1 - *(int*)e2;//void* 不能解引用,要强制类型转换//此方式默认升序排列,若想实现降序,可用e2-e1,将逻辑改反
}
void test()
{int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]),cmp );int i = 0;for (i = 0;i < sz;i++){printf("%d ", arr[i]);}}
int main()
{test();return 0;
}

排序结构体数据

#include <string.h>
struct stu
{char name[20];int age;
};
int cmp_name(const void* e1,const void* e2)
{return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void test()
{struct stu arr[3] = { {"zhangsan",18},{"lishi",25},{"wangwu",22} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_name);}
int main()
{test();return 0;
}

其中,strcmp函数比较时,比较的是对应字符的ASCII码值,并不比较字符串长度,比如:

  1. “abc"和"abd” : a和a的ASCII码值相等,比较b和b,也相等,再比较c和d,d的ASCII码值大,则"abc"小于"abd"
  2. “aef"和"abcde” : a的ASCII码值相同,e的ASCII码值大于b,则不再进行比较,认为"aef"大于"abcde".

在这里插入图片描述

可以看出 ’ l ’ < ’ w ’ < ’ z ',因此 " lishi " 排第一

三、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 - i - 1;j++)//交换次数{if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = arr[j];}}}
}

我们想排序的数据类型如果不是整型,是结构体类型、字符类型等,在bubble_sort函数参数部分就不可以用整型数组来接收,因此,参考qsort函数的定义:

void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2));
  1. 冒泡排序首先要知道要比较的数据的首元素地址,但是不知道具体是什么类型,因此用void* base来接收
  2. 需要知道数据的个数,类型为无符号的整型,用sz接收
  3. 在我们拿到首元素地址之后,向后找元素进行比较,需要知道每个元素的内存大小,不同类型元素的大小不同,但都是整型,因此用width接收,与qsort函数的size相似
  4. 最后需要传入对元素进行比较的函数的地址,类型为函数指针,因为并不知道要比较的元素类型,因此用void* 类型的指针进行接收,进行比较时根据不同的类型进行强制类型转换,然后用返回值来判断他们的大小关系,因此比较函数返回类型为 int

然后分析冒泡排序函数内部:其中排序的趟数和元素交换次数不需要变
在这里插入图片描述

  1. 在元素进行比较时,如果元素类型不是整型,如结构体类型、字符类型等就不可用大于小于这种方式来比较,因此应该用函数指针cmp去调用 用来比较的函数进行元素比大小
  2. 在元素交换部分,交换的元素类型不能确定,肯定不能使用 int 类型,因此将其封装成函数,在其内部进行操作,具体思路如下:

在这里插入图片描述

在这里插入图片描述

拿到元素地址的目的是为了进行元素的交换. 首先,base是无符号指针,应该先对其进行强制类型转换成char*类型,那么base+4就拿到下一个元素的地址,base+8拿到第三个元素的地址,以此类推…
每个元素的内存大小为width,那么第一个元素地址为 (char*) base + j * width , 第二个元素地址为 (char*) base + (j+1) * width ,拿到两个元素地址后进行交换,交换完成后 j++,这样继续取得第二个和第三个元素的地址,继续进行操作

有人会问,为什么base要强转成char*类型,转换成int*不行吗?

我们不知道要排序的元素类型是什么,可能是整型,可能是结构体类型,可能是字符类型等等,每个元素的大小可能是1、4、7、9等,那么在解引用,进行交换元素的时候,由于整型一次只能交换4个字节的内存,在交换7、9字节的内存时候就不行了,char*类型进行解引用是char类型,每次只交换一个字节,这样无论元素是什么类型都可以交换完成

因此,由于排序元素的类型不确定性,选择一个字节一个字节进行交换的方式完成排序,进而选择(char*)base
具体实现代码如下:

//比较函数由自己编写,只有使用者知道要排序什么类型的元素
int cmp_int(const void* e1, const void* e2)
{return strcmp((char*)e1 , (char*)e2);
}
void Swap(char* buf1, char* buf2,size_t width)
{char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;
}
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{int i = 0;for (i = 0;i < sz - 1;i++){int j = 0;for (j = 0;j < sz - i - 1;j++){if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}int main()
{char arr[] = "aksjdu";int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);int i = 0;for (i = 0;i < sz;i++){printf("%c ", arr[i]);}return 0;
}

相关文章:

  • 2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(三级)真题
  • 非比较排序——计数排序
  • GitHub创建远程仓库
  • 【Win】 cmd 执行curl命令时,输出 ‘命令管道位置 1 的 cmdlet Invoke-WebRequest 请为以下参数提供值: Uri: ’ ?
  • 力扣刷题Day 20:柱状图中最大的矩形(84)
  • 万物对接大模型:【爆火】MCP原理与使用指南
  • 广东水利水电安全员 B 证考试精选题
  • AutoSAR从概念到实践系列之MCAL篇(一)——MCAL架构及其模块详解
  • http请求和websocket区别和使用场景
  • o3和o4-mini的升级有哪些亮点?
  • 纯CSS实现自动滚动到底部
  • C++ 二叉搜索树
  • 安装多个DevEco Studio版本,如何才能保证各个版本不冲突?
  • 「仓颉编程语言」Demo
  • 网络互连与互联网3
  • 从零到精通:用 GoFrame 和 go-resty 优雅调用第三方 HTTP API
  • 消息队列生产者投递的高可靠性与一致性保障方案
  • 【Linux】深入理解Linux文件系统:从C接口到内核设计哲学
  • MCP 协议:技术架构与核心机制深度解析——为智能时代打造统一“接口”
  • Linux | I.MX6ULL Uboot 编译(12)
  • 商务部24日下午将举行发布会,介绍近期商务领域重点工作情况
  • 澎湃思想周报|哈佛与特朗普政府之争;学习适应“混乱世”
  • 习近平致电祝贺诺沃亚当选连任厄瓜多尔总统
  • 重大虚开发票偷税骗补案被查处:价税2.26亿,涉700余名主播
  • 陈杨梅:为爸爸寻我19年没有放弃而感动,回家既紧张又期待
  • 日本多地发生无差别杀人事件,中使馆提醒中国公民加强安全防范