【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);
	else
		return  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;
	else
		return -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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PHP基础语法【上】

文章目录 一、环境安装二、代码应该写在哪里&#xff1f;三、什么是错误&#xff1f;四、变量无需声明变量变量命名规则变量的作用域可变变量 五、常量常量的定义常量的特性 六、数据类型NULL 空值Boolean 布尔类型Integer 整型Float 浮点型String 字符串Array 数组Object 对象…

有效招聘营销策略的六个组成部分

任何想吸引更多人购买其产品的公司都必须投资于市场营销。然而&#xff0c;当涉及到让更多的人了解公司的工作时&#xff0c;许多有效的营销活动可能不是招聘团队的首要考虑因素。为了超越招聘委员会上的“发布祈祷”策略&#xff0c;有必要包括有效招聘营销策略的所有组成部分…

Redis 6.0新特性详解

Redis 6.0新特性主要有3个&#xff1a;多线程、Client Side Cache、Acls。下面详细说明一下。 1.多线程 redis 6.0 提供了多线程的支持&#xff0c;redis 6 以前的版本&#xff0c;严格来说也是多线程&#xff0c;只不过执行用户命令的请求时单线程模型&#xff0c;还有一些线…

【Axure高保真原型】动态统计中继器表格项目数

今天和大家分享动态统计中继器表格项目数的原型模板&#xff0c;具体包括以下功能&#xff1a; 表格下方可以自动根据表格内容统计表格的总项目数、启用和禁用数、选中和未选中数 我们可以点击开发切换启用和禁用 点击多选按钮&#xff0c;选中或取消选中对应行内容 选中后可…

跨境电商源码支持,多国语言与货币切换功能全解析

一、背景介绍 跨境电商的兴起&#xff0c;使得供货商和代理商们面临着一个全新的挑战&#xff1a;如何管理跨国交易和多语言 的销售平台。为了解决这一问题&#xff0c;跨境电商源码应运而生。本文将全面解析供货商和代理商后 台所具备的跨境电商源码支持功能&…

环境搭建---nginx

nginx离线安装 下载地址&#xff1a;https://nginx.org/download/ 一、安装编译工具及库文件 [rootVM-20-14-centos ~]# yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel二、安装 PCRE [rootVM-20-14-centos ~]# tar -zxvf pcre-8.35.tar.gz [r…

从热潮到理性,大模型迎来产业「拐点」

前言 无人不谈大模型&#xff0c;是今年上半年科技界的真实写照。 从市场热闹程度来看&#xff0c;大模型已经成为各家科技厂商争先涌入的赛道&#xff0c;无论是互联网巨头&#xff0c;还是科技公司&#xff0c;甚至是研究机构&#xff0c;均已加入这场大模型混战&#xff0…

k8s快速上手实操

前言 Kubernetes&#xff08;简称K8s&#xff09;是由Google开源的一个用于自动化部署、扩展和管理容器化应用程序的系统。自2014年发布以来&#xff0c;Kubernetes已经迅速成长为容器编排领域的标准&#xff0c;并在全球范围内得到了广泛的采用和认可。 Kubernetes作为现代容…

彩色图像批处理实例

在数字图像处理过程中&#xff0c;经常需要对一批图像进行处理&#xff0c;下面以自然场景下花背景分割为例&#xff0c;给出其主要处理过程和处理结果。 处理的主要步骤&#xff1a;1. 六张彩色图像存放在flower文件夹中&#xff0c;图像文件名为1.jpg 2.jpg 3.jpg 4.jpg 5.j…

同三维T80006EHL-4K30CN 单路4K30 HDMI编码器(全国产化)

同三维T80006EHL-4K30CN 单路4K30 HDMI编码器 带1路HDMI环出和1路3.5音频输入&#xff0c;支持4K30&#xff0c;所有元器件全国产 一、 产品简介&#xff1a; T80006EHL-4K30CN 4K编码器&#xff08;采集盒&#xff09;是一款全国产化的专业4K HDMI音视频编码产品&#xff0c;…

开始报名啦!智能可观测运维技术 MeetUp 议题硬核来袭

「龙蜥社区“走进系列”MeetUp」是由龙蜥社区与生态合作伙伴联合主办的系列月度活动&#xff0c;每期走进一家企业&#xff0c;聚焦龙蜥社区和合作伙伴的技术、产品和创新动态&#xff0c;展示硬核技术&#xff0c;共建繁荣生态。 龙蜥社区“走进系列”第 11 期走进中兴通讯-智…

文字转语音在线怎么转?总结了三种快速转换

文字转语音在线怎么转&#xff1f;在数字化信息爆炸的时代&#xff0c;文字转语音的操作也越来越多。无论是为了制作视频配音、播客节目&#xff0c;还是为了方便视力障碍者阅读&#xff0c;文字转语音技术都提供了极大的便利。因此&#xff0c;本文将总结三种文字转语音的在线…

Python热涨落流体力学求解算法和英伟达人工智能核评估模型

&#x1f3af;要点 &#x1f3af;平流扩散简单离散微分算子 | &#x1f3af;相场模拟&#xff1a;简单旋节线分解、枝晶凝固的 | &#x1f3af;求解二维波动方程&#xff0c;离散化时间导数 &#x1f3af;英伟达 A100 人工智能核性能评估模型 | &#x1f3af;热涨落流体动力学…

时隔一年,SSD大涨价?

同样产品&#xff0c;2T&#xff0c;去年400多到手&#xff0c;今年700。 去年 今年

win10重装系统如何操作,附上详细系统重装图文教程(2024年新)

win10重装系统如何操作呢&#xff1f;电脑使用时间长了&#xff0c;会出现各种各样的问题&#xff0c;如重要的系统文件被删除导致电脑无法正常运行&#xff0c;电脑运行内存空间不足&#xff0c;电脑卡顿等。Win10重装系统很简单&#xff0c;这里分享超详细的重装系统方法&…

红日靶场实战一 - 学习笔记

最近在学习红蓝对抗&#xff0c;如果有兴趣的可以多关注。 目录 环境搭建 靶场链接 配置网络 攻击机kali网络 配置win7 web服务器网络 配置winserver 2008网络&#xff08;DC域控&#xff09; 配置win2003/win2k3网路(域成员) IP配置情况 外网突破 信息收集 phpmya…

等待 chrome.storage.local.get() 完成

chrome.storage.local.get() 获取存储处理并计数&#xff0c;内部计数正常&#xff0c;外部使用始终为0&#xff0c;百思不得其解。 如何在继续执行之前等待异步chrome.storage.local.get()完成-腾讯云开发者社区-腾讯云 (tencent.com) 原来我忽略了异步问题&#xff0c;最简…

第二十二篇——香农第二定律(一):为什么你的网页总是打不开?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 看似在将知识&#xff0c;实际是在讲生活和所有&#xff1b;突破边界偶尔…

lotus snapshot 快照列表

快照列表 https://forest-archive.chainsafe.dev/list/mainnet/latest

阿赵UE引擎C++编程学习笔记——C++自定义蓝图函数

大家好&#xff0c;我是阿赵。   使用UE引擎&#xff0c;大部分功能都可以使用蓝图的自带节点去完成。但有时候我们也需要扩展一些蓝图没有的功能。这一篇主要学习一下怎样用C给蓝图新增自定义的函数节点。 一、 新建蓝图函数库 在添加C类的时候&#xff0c;选择蓝图函数库&…