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

【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;
}

相关文章:

  • 深入理解 MySQL 查询分析工具 EXPLAIN 的使用
  • 【ARMv8/ARMv9 硬件加速系列 4 -- 加解密 Cryptographic Extension 介绍】
  • 通过摄像头检测步频
  • 【C语言】数组参数和指针参数详解
  • MOS参数详解
  • nginx ws长连接配置
  • web端即时通信技术
  • Python for循环中的引用传递和值传递
  • Redis 面试热点(二)
  • 每日一练:攻防世界:Ditf
  • Golang并发控制的三种方案
  • 一文理清GO语言日志库实现开发项目中的日志功能(rotatelogs/zap分析)
  • 基于多头注意力机制卷积神经网络结合双向门控单元CNN-BIGRU-Mutilhead-Attention实现柴油机故障诊断附matlab代码
  • MongoDB~高可用集群介绍:复制集群(副本集)、分片集群
  • SQL MAX() 函数深入解析
  • Angular数据绑定机制
  • angular组件开发
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • js ES6 求数组的交集,并集,还有差集
  • Meteor的表单提交:Form
  • Node项目之评分系统(二)- 数据库设计
  • SegmentFault 2015 Top Rank
  • Terraform入门 - 1. 安装Terraform
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 编写高质量JavaScript代码之并发
  • 反思总结然后整装待发
  • 开发基于以太坊智能合约的DApp
  • 什么软件可以剪辑音乐?
  • 使用putty远程连接linux
  • 在Unity中实现一个简单的消息管理器
  • MPAndroidChart 教程:Y轴 YAxis
  • #FPGA(基础知识)
  • #pragma 指令
  • #pragma预处理命令
  • #QT(QCharts绘制曲线)
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (2)(2.10) LTM telemetry
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (C++17) std算法之执行策略 execution
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (函数)颠倒字符串顺序(C语言)
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .net 验证控件和javaScript的冲突问题
  • .NET开发者必备的11款免费工具
  • .NET项目中存在多个web.config文件时的加载顺序
  • @SpringBootApplication 包含的三个注解及其含义
  • [12] 使用 CUDA 加速排序算法
  • [20170713] 无法访问SQL Server