【C语言】回调函数 和 部分库函数的用法以及模拟实现

一、回调函数:

1、定义:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

2、qsort的模拟实现(回调函数的应用)

那么回调函数有什么实际性的作用呢?下面模拟库函数qsort来顺便了解回调函数的用法:(在这里使用冒泡排序来模拟)
首先要了解什么是qsort:
上面的意思是:对数组的元素进行排序,使用函数确定顺序所排序的类型。
依然从代码入手:
int my_cmp(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}void Swap(char* buf1, char* buf2, size_t width)
{for (size_t i = 0; i < width; i++){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}Bubble_Sort(void* base,size_t sz,size_t width,int (*cmp)(const void* e1,const void* e2))
{for (size_t i = 0; i < sz - 1; i++){for (size_t 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);}}}
}void test1()
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);Bobble_Sort(arr, sz, sizeof(arr[0]), my_cmp);for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
int main()
{test1();return 0;
}

如上所示:我们要想将arr数组排为升序,在模拟实现时,要给四个参数:

1、这个数组名

2、这个数组的元素个数

3、这个数组每个元素的大小(单位字节)

4、指向比较两个元素的函数的指针。(这个来控制是比较整型或者浮点型或者结构体等等)

接收的时候用void*接受是因为不知道我传过来的是什么类型的,在之后强制类型转即可。

上述代码中:
my_cmp为我要排序整型所需要的函数。
Swap为交换两个元素
Bubble_Sort为冒泡排序模拟库函数qsort
在其中:
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
    Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
这行代码是最核心的,
width为偏移量,这样可以通过 回调函数来调用my_cmp来知道我是什么类型的和大小。
将传过来的数组名用void* base接收,后来转换为(char*)这样可以+偏移量来找到每个元素,这也是要传数组每个元素的大小的意义。
在Swap函数中与传统的交换不同的是需要每一个元素的大小,这样可以将一个元素以字节为单位一个一个交换,毕竟如果直接交换的话,就会将代码写死(比如写了个int类型,就不能交换浮点数类型)

在上述代码中就在函数中调用了函数,所以就用了回调函数cmp

二、库函数的用法及部分模拟实现:

首先来了解一下有那些处理字符和字符串的库函数:

1、strlen:

这是用来计算字符串长度的。

//计数
int my_strlen1(const char* src)
{assert(&src);int count = 0;while (*src++)count++;return count;
}
//递归
int my_strlen2(const char* src)
{assert(src);if (*src != '\0')return 1 + my_strlen2(src + 1);elsereturn  0;
}
//指针-指针
int my_strlen3(const char* src)
{assert(src);const char* dest = src;while (*dest){dest++;}return dest - src;
}int main()
{char arr[] = "abcdefg";int ret = my_strlen3(arr);printf("%d\n", ret);return 0;
}

如上用了三种方法分别模拟了字符串长度的计算。

2、strcpy:

这是将一个字符串拷贝到另外一个字符串中的

char* my_strcpy(char* dest, char* src)
{char* ret = dest;assert(dest && src);while (*dest++ = *src++){;}return ret;
}int main()
{char arr1[] = "abcdefg";char arr2[20] = "";my_strcpy(arr2, arr1);printf("%s\n", arr1);printf("%s\n", arr2);return 0;
}

在模拟实现中将src一个个赋给dest,最后返回目标数组的地址

3、strcat:

这是将一个字符串追加在另外一个字符串后面。

char* my_strcat(char* dest, char* src)
{assert(dest && src);char* ret = dest;//1.找到要追加的地方while (*dest){dest++;}//2.追加while (*dest++ = *src++){;}return ret;
}int main()
{char arr1[20] = "hello ";char arr2[] = "ppr";my_strcat(arr1, arr2);printf("%s\n", arr1);return 0;
}

首先找到我要追加的地方,然后进行赋值即可,最后返回被追加的数组首元素。

4、strcmp:

这是比较两个字符串的大小。

如上,如果第一个字符串小于后一个,那么返回一个小于0 的数;

           如果第一个字符串等于后一个,那么返回0 ;

           如果第一个字符串大于后一个,那么返回一个大于0 的数。

(在VS编译器中分别返回1,0,-1)。

int my_strcmp( char* str1, char* str2)
{assert(str1 && str2);while (*str1 == *str2){if (*str1 == '\0')return 0;str1++;str2++;}if (*str1 > *str2)return 1;elsereturn -1;//return *str1 - *str2;
}int main()
{char arr1[20] = "abqf";char arr2[20] = "abqf";int ret = my_strcmp(arr1, arr2);printf("%d\n", ret);return 0;
}

5、strncpy:

这些长度受限制的函数引入是因为strcpy函数不安全,

比如在strcpy函数中,如果拷贝过去字符串,此时接收这个字符串的数组不够这么大,就会失败,同样,如果c语言中最开始不声明“#define _CRT_SECURE_NO_WARNINGS 1”就会不可以使用,所以就衍生出了strncpy,这个作用和strcpy几乎是一模一样的,就是多了1个无符号整型参数,作用是我要拷贝过来几个字符。

但是strncpy只是相对于strcpy安全罢了,如果你输入的数字等于这个数组的最大元素,那么就会使‘\0’拷贝不过来,也会出现错误:

所以strncpy只是相对于strcpy安全。

6、strncat:

这个多出来的无符号整型的参数就是我需要追加几个字符数。

7、strncmp:

这个多出来的无符号整型的参数就是我需要比较几个字符数。

8、strstr:

这个函数的作用是在一个字符串中找另外一个字符串。

char* my_strstr(const char* src1, const char* src2)
{assert(src1 && src2);if (*src2 == '\0')return (char*)src1;const char* s1 = src1;const char* s2 = src2;const char* p= src1;while (*p){s1 = p;s2 = src2;while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2){s1++;s2++;}if (*s2 == '\0')return (char*)p;p++;}return NULL;
}int main()
{char arr1[] = "abbbbcdef";char arr2[] = "bbc";char* arr3 = my_strstr(arr1, arr2);if (arr3 == NULL){printf("找不到\n");}else {printf("%s\n", arr3);}return 0;
}

思路:

在src1中找src2,第一个while循环将s1,s2分别指向src1和src2的第一位(这样可以使得每次找完可能相等的位置后,如果不相等就会返回此时记录的位置),之后来找可能相等的位置,在第二个while循环中来看看是否相等。

9、strtok:

这是属于一个字符串的分割,

第一个参数为我所需要分割的起始位置,

在后续的调用中在第一个参数位置只需传空指针即可。

第二个参数中传我所分割的标志集合。

10、strerror:

这是一个打印错误信息的函数,C语言的库函数在运行的时候,如果发生错误,就会将错误码存在一个变量中,这个变量是:errno(在errno.h这个头文件里面)

错误码是一些数字:1 2 3 4 5

我们需要将错误码翻译成错误信息

int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){printf("%s\n", strerror(errno));//perror("fopen");return 1;}//读文件//关闭文件fclose(pf);pf = NULL;return 0;
}

在上面代码中,在当前路径下读一个叫做test.txt的文件,如果没有就会将错误代码打印出来

拓展:有另外一个函数perror这个用起来更加顺手些,将可以函数错误信息直接打印出来。

可以理解为:printf + strerror

11、memcpy:

这个函数也是拷贝,但是不只局限于字符串的拷贝,属于内存的拷贝,

第一个参数是目标拷贝函数,第二个参数是待拷贝的函数,第三个参数是拷贝多少个字节

int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[20] = { 0 };memcpy(arr2, arr1, 24);return 0;
}

模拟实现:

void* my_memcpy(void* dest, void* src, size_t num)
{ void* ret = dest;while (num--){*(char*)dest = *(char*)src;++(char*)dest;++(char*)src;}return ret;
}int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[20] = { 0 };my_memcpy(arr2, arr1, 24);return 0;
}

我们这个自己模拟的局限性:不能够自我拷贝(但是库函数里的memcpy可以),那么接下来进行优化---------引入了memmove函数的模拟实现

12、memmove:

解决方法:如下图,若src在dest后面,就将src从前往后拷贝,若src在dest前面就从后往前拷贝。

void* my_memmove(void* dest, void* src, size_t num)
{assert(dest && src);void* ret = dest;if (dest < src){//前->后while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{//后->前while (num--){*((char*)dest + num) = *((char*)src + num);}}}int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };my_memmove(arr1+2, arr1, 20);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/29564.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎样打印微信文档文件?

在日常生活和工作中&#xff0c;我们经常需要打印微信中的文档文件&#xff0c;无论是工作资料、学习笔记还是其他重要信息。随着科技的发展&#xff0c;我们不再需要前往打印店进行繁琐的操作&#xff0c;而是可以通过一些便捷的在线打印平台轻松实现。今天&#xff0c;我们就…

微信小程序自定义组件(写一个点击查看大图有关闭按钮,复制即可使用)

今天碰到一个需求&#xff0c;本来使用wx.previewImage用的好好的&#xff0c;非要加一个关闭按钮&#xff0c;这可把不想写代码的我难住了&#xff0c;查看官方文档没有加这个按钮的属性&#xff0c;那就自己写了吧&#xff0c;自己写的途中发现胶囊隐藏不了&#xff0c;兴高采…

echarts学习:通过图例事件实现选中后控制多条折线的显隐

1.问题描述 我在工作中遇到了这样一个需求&#xff1a;我们都知道点击echarts折线图的图例&#xff0c;是可以控制折线的显隐的。我现在希望点击某一个图例可以改变多条折线的显隐。 例如在下面这张图中&#xff0c;我将“xxx水位”和“yyy水位”分为一组&#xff1b;将“xxx…

SGPT论文阅读笔记

这是篇想要用GPT来提取sentence embedding的工作&#xff0c;提出了两个框架&#xff0c;一个是SGPT-BE&#xff0c;一个是SGPT-CE&#xff0c;分别代表了Bi-Encoder setting和Cross-Encoder setting。CE的意思是在做阅读理解任务时&#xff0c;document和query是一起送进去&am…

IPv6改造是什么意思?网站IPv6改造怎么做?

随着互联网技术的飞速发展&#xff0c;IPv4地址资源短缺的问题日益凸显&#xff0c;已经难以满足物联网、人工智能等新基建的激增需求。为了解决这一困境&#xff0c;IPv6协议应运而生&#xff0c;其海量的地址资源和内置的安全机制为互联网的持续发展提供了广阔空间。目前各行…

kbadminv1版后台快速开发框架

探索高效开发的新境界&#xff01;kbadminv1 版后台快速开发框架震撼登场&#xff01; 基于强大的 thinkphp6 框架&#xff0c;kbadminv1 为开发者们带来了前所未有的便捷与高效。 它犹如一把智慧的钥匙&#xff0c;轻松开启后台开发的大门。简洁而直观的设计&#xff0c;让复…

2024-06-18 blue-VH-driver-订阅组态相关分析

摘要: 2024-06-18 blue-VH-driver-订阅组态相关分析 订阅组态相关: 1.1 创建订阅分组成功, 但是激活订阅分组失败 调用VH接口删除已经创建的订阅分组 无论否删除成功,都返回不做重试删除已经创建的订阅分组,避免无法结束调用1.2 向订阅分组添加的位号的数据量超过单个订阅分…

高并发系统中面临的问题 及 解决方案

在互联网软件系统中,高并发读写场景会带来一系列复杂的问题。以下是详细的分析和解决方案: 1. 性能瓶颈 问题: 数据库性能瓶颈:高并发请求会导致数据库负载过重,响应时间增加,甚至可能导致数据库崩溃。服务器性能瓶颈:服务器的CPU、内存、网络带宽可能无法承受高并发请…

Maven 配置学习:存在两个本地私服如何配置

Maven 配置学习&#xff1a;存在两个本地私服如何配置 目录 Maven 配置学习&#xff1a;存在两个本地私服如何配置解释&#xff1a;1.本地仓库位置&#xff1a;2.Profiles 定义&#xff1a;3.Repositories 定义顺序&#xff1a;4.Active Profiles&#xff1a; 操作步骤&#xf…

在Pycharm使用Github Copilot

文章目录 1.GitHub Copilot 是什么2.注册GitHub Copilot3.官方使用文档4.安装 GitHub Copilot插件5.在Pycharm中使用6.相关功能键7.启用或禁用 GitHub Copilot 1.GitHub Copilot 是什么 GitHub Copilot 是一款 AI 编码助手&#xff0c;可帮助你更快、更省力地编写代码&#xff…

【MySQL进阶之路 | 高级篇】SQL执行过程

1. 客户端与服务器的连接 运行中的服务器程序与客户端程序本质上都是计算机的一个进程&#xff0c;所以客户端进程向服务器端进程发送请求并得到相应过程的本质就是一个进程间通信的过程. 我们可以使用TCP/IP网络通信协议&#xff0c;命名管道和共享内存等方式&#xff0c;实…

2024/6/18(RBAC,查询用户权限,细粒度授权,选课,支付宝生成二维码支付,支付结果查询需要内网穿透)

黑马程序员【学成在线项目】,P141 测试沙箱支付宝_黑马学成在线支付宝沙箱-CSDN博客 需要内网穿透

PyCharm 快捷键积累

1、快速格式化&#xff1a;Ctrl Alt L Ctrl Alt L 快捷键在 PyCharm 中是用于格式化代码的&#xff0c;它不仅仅适用于 HTML 代码&#xff0c;而是适用于多种编程和标记语言。

31.加载配置文件中的游戏到辅助列表

上一个内容&#xff1a;30.保存游戏配置到文件 以 30.保存游戏配置到文件 它的代码为基础进行修改 效果图&#xff1a; 加载配置文件到列表的函数&#xff1a; void CWndINJ::LoadGame() {int count GetPrivateProfileInt(L"main", L"count", 0, GameI…

通勤必备!重回儿时回忆,完全免费!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 今天给大家安利三个网站&#xff0c;可以完全免费在线玩的小霸王游戏机&#xff0c;重回童年游戏时刻&#xff01; 最后一款还可以网页在线和朋友联机CS、吃鸡、暗黑破坏神、拳皇、恐龙…

【数学】负数

Hello!大家好&#xff0c;我是学霸小羊&#xff0c;今天讲讲负数。 目录 1.负数的概念 2.绝对值 附&#xff1a;c求绝对值方法 3.负数的大小比较 1.负数的概念 比0小的数&#xff0c;叫做负数。 负数全称负实数&#xff0c;负数与正数表示意义相反的量。负数用负号(Min…

[NISACTF 2022]babyserialize(pop链详细分析、构造)

目录 [NISACTF 2022]babyserialize &#xff08;一&#xff09;理清pop链&#xff08;链尾 链头&#xff09;&#xff0c;标注步骤 1. 先找eval、flag这些危险函数和关键字样&#xff08;这是链尾&#xff09; 2.往eval()上面看 3.往$bb()上面看 4.往strtolower()上面看 …

alibaba EasyExcel 导入 Excel 分享

前言&#xff1a; Excel 导入是一个非常常见的功能&#xff0c;项目开发中随处可见&#xff0c;市面上也有各种处理 Excel 的 API&#xff0c;本文简单分享一下 alibaba.excel 的导入功能。 引入依赖&#xff1a; <dependency><groupId>com.alibaba</groupId…

【GD32F303红枫派使用手册】第十六节 USART-DMA串口收发实验

16.1 实验内容 通过本实验主要学习以下内容&#xff1a; 串口DMA工作原理 使用DMA进行串口收发 16.2 实验原理 16.2.1 串口DMA工作原理 在前面ADC章节中&#xff0c;我们介绍了DMA的工作原理&#xff0c;这里就不多做介绍。从GD32F303用户手册中可以查到&#xff0c;各串…

Roboflow对YOLO数据集、标注、训练、下载

一、Roboflow 介绍 一个功能丰富的在线数据标注和深度学习平台&#xff0c;它提供了从数据标注 到模型训练的一站式服务&#xff0c;yolov5到v8官方推荐的数据集查找及下载网站。 主要功能 1、在线标注数据&#xff1a;Roboflow支持在线标注图片和视频数据&#xff0c;提供多…