C语言内存函数:memcpy、memcat、memmove介绍和模拟实现(实用性高,建议三连收藏)

目录

1.memcpy函数 

1.1函数介绍

1.2函数示范使用 

1.3函数的模拟实现

 1.4补充

2.memmove函数

2.1函数介绍

2.2函数的使用示范 

2.3函数的模拟实现 

3.memcmp(内存比较函数) 

3.1函数介绍

 3.2函数的示范使用,有趣的例子

4.函数补充memset(内存设置函数) 

4.1函数介绍

4.2函数示范使用 

5.结语


 

1.memcpy函数 

引入:之前我们讲过字符串的拷贝函数,但是当我们要拷贝整型数据或者结构体数组等类型弟弟数据的时候,就需要一个包容性更好的函数了。 

1.1函数介绍

函数头文件:string.h

函数参数:

①目的地

指向要复制内容的目标数组的指针,类型转换为 void* 类型的指针。

②源

指向要复制的数据源的指针,类型转换为 const void* 类型的指针。

③数量

要复制的字节数。 size_t 是无符号整数类型。

返回值类型:void*

返回值:返回目的字符串的地址也就是考到到某个地方,这个某个地方的地址。

函数功能:

函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。


1.2函数示范使用 

struct {
	char name[40];
	int age;
} person, person_copy;
int main()
{
	char myname[] = "Pierre de Fermat";
	
	memcpy(person.name, myname, strlen(myname) + 1);
	person.age = 46;
	
	memcpy(&person_copy, &person, sizeof(person));
	printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
	return 0;
}

输出: 

1.3函数的模拟实现

函数模拟实现思想:我们参照库函数1来写,我们的想法是将源src的地址起的num个自己的内容拷贝到以目的地址dest为起点的空间中。那么由于我们的函数是要实现不同类型的内容都可以兼容拷贝,那么参数肯定是要设计为我们的void*类型来实现广泛接受,当我们实现的时候我们又想一个字节一个字节的遍历,那就应该将原先的void*的地址,转换为char*d的地址,以num为循环遍历拷贝条件,最后返回我们的目的空间的其实地址就行。

加下来我们看代码实现:

#include<stdio.h>
#include<assert.h>
#include <string.h>

void* my_memcpy(void* dst, const void* src, size_t count)
{
	void* ret = dst;//保存目的空间地址
	assert(dst);
	assert(src);//断言,因为后续有指针的解引用操作,防止指针为空指针
	while (count--)
   {
		*(char*)dst = *(char*)src;
		dst = (char*)dst + 1;
		src = (char*)src + 1;
	}
	return ret;
}

struct {
	char name[40];
	int age;
} person, person_copy;
int main()
{
	char myname[] = "woaini";
	
	my_memcpy(person.name, myname, strlen(myname) + 1);
	person.age = 46;
	
	my_memcpy(&person_copy, &person, sizeof(person));
	printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
	return 0;
}

我们来看一下实现效果:

 

 1.4补充

①关于 dst = (char*)dst + 1;指针的移动

首先我们知道void* l类型的指针是不支持指针运算的,所以要强制转换类型,然后因为char*类型的指针一步的长度刚好是一个字节,控制力度较好控制,所以转换为了字符型指针。

那么

可以这样写吗:(char*)dest++;

这种写法是错误的,因为强制类型转换是临时的,强制类型并不会改变这个指针本身弟弟类型,只是我用的时候临时转换的,后置++是先试用再++,等加加的时候,已经不能够在使用了。

那么:++(char*)dest 呢

这种写法可以但是有些编译器会报错,所以还是比较推荐第一种写法。

②我们想一下这个问题:

如果我们拷贝的空间是重复的呢,,比如我们有一个整型数组:

int arr1 = [1,2,3,4,5,6]

我们这样写:memecpy(arr1+2,arr1,60

我们原本是想得到:
1,2,1,2,3,4,5,6

但是如果我们使用memcpy就会出现这样的情况

但是,当我们去编译器里实现这段代码我们来看一下结果:

 

我们发怎么和我们预计的不一样,这个函数好像对于重叠的内存空间也能正常拷贝呀

这就是最后要补充的点:

memcpy确实是不负责重叠拷贝的。这是因为最初设计memcpy函数的时候我们是要求只用实现不重叠拷贝就好(相当于60分),在Vs上函数实现了重叠拷贝(相当于100分),哪也可以,但是我们接下来要介绍的这个函数才是正宗的负责重叠拷贝的。我们接着next.

2.memmove函数

2.1函数介绍

 

函数头文件:<string.h>

函数参数:

void * destination:指向要复制内容的目标数组的指针,类型转换为 void* 类型的指针。
const void * source:指向要复制的数据源的指针,类型转换为 const void* 类型的指针。
 size_t num :要复制的字节数。 size_t 是无符号整数类型。

 返回值:返回目标数组的地址

函数功能:将 num 字节的值从指向的位置复制到目标指向的内存块。复制就像使用中间缓冲区一样进行,从而允许目标重叠。

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。

2.2函数的使用示范 

刚刚上面举例的数组就是一个示范,下面用一个字符数组来示范:

#include <stdio.h>
#include <string.h>
int main ()
{
  char str[] = "memmove can be very useful......";
  memmove (str+20,str+15,11);
  puts (str);
  return 0;
}

2.3函数的模拟实现 

对于函数的返回值由于不知道传递进入的目标首地址会是什么类型,我们就用Void*作为返回类型,对于函数参数的设计,目的数组和源数组的地址都不确定是什么类型所以设计为void*类型,由于源字符串我们不会去操作改变,所以我们用const修饰。

由于函数实现的同一片空间的内容拷贝所以请看图解实现思想:

由上图得出结论一:当我们的dst<= src,也就是说目标地址要小于源地址的时候应该先将源字符串首地址的内容拷贝到目的数组的地址中,循环拷贝num个字节。 

结论②:当src<=dest<=src+num时, 目标地址要大于等于源地址并且小于等于源地址+num字节的的时候应该先将源字符串尾部地址的内容拷贝到目的数组的尾部地址中,循环拷贝num个字节。

当dest大于src+num时,随便怎么拷贝循序都行1,为了方便书写条件,我们就可以将其归结到上述两种情况其中之一,最好是第二种情况。 

 第一种实现:

void* my_memmove(void* dest, const void* src, size_t num)
{
	void* ret = dest;//保存目的空间地址
	assert(dest && src);//断言判断
	if (dest < src)//虽然void*不可以解引用或者算术运算,但是可以比较大小,因为保存的是地址编号
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
	    }
		
	}
	else
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
			
		}
	}
	return ret;
}
int main()
{
	char str[] = "memmove can be very useful......";
	my_memmove(str + 20, str + 15, 11);
	puts(str);
	return 0;
}

第二种实现大家可以试一下:

void * memmove ( void * dst, const void * src, size_t count)
{
        void * ret = dst;
        if (dst <= src || (char *)dst >= ((char *)src + count))
             {
while (count--) {
                        *(char *)dst = *(char *)src;
                        dst = (char *)dst + 1;
                        src = (char *)src + 1;
               }
       }
        else {
 dst = (char *)dst + count - 1;
                src = (char *)src + count - 1;
                while (count--) {
                        *(char *)dst = *(char *)src;
                        dst = (char *)dst - 1;
                        src = (char *)src - 1;
               }
       }
        return(ret);
}

3.memcmp(内存比较函数) 

3.1函数介绍

头文件包含:string.h

函数参数: 

PTR1型

指向内存块的指针。

PTR2型

指向内存块的指针。

数量

要比较的字节数。

返回值:

返回值表明
<0在两个内存块中不匹配的第一个字节在 ptr1 中的值低于 ptr2 中的值(如果计算为 unsigned char 值)
0两个内存块的内容相等
>0在两个内存块中不匹配的第一个字节在 ptr1 中的值大于 ptr2 中的值(如果计算为 unsigned char 值)
函数:是根据指针指向的内容一个字节一个字节的往后面进行比较,直到比出大小或者达到要求比较的字节数num.

 3.2函数的示范使用,有趣的例子

int main()
{
	int arr1[] = { 1,2,1,4,5,6 };
	int  arr2[] = { 1,2,257 };
	int ret1 = memcmp(arr1, arr2, 9);
	int ret2 = memcmp(arr1, arr2, 10);
	printf("%d\n%d\n", ret1, ret2);
	return 0;
}

两个数组前九个字节是一模一样的,这样说明我们的这个memcmp是一个字节一个字节的比较的

4.函数补充memset(内存设置函数) 

4.1函数介绍

 函数功能:将指定空间的num个字节设置为指定的value值,返回的是指定空间的地址。

4.2函数示范使用 

int main()
{
	char arr[] = { "hello word" };
	memset(arr + 1, 'x', 4);
	printf("%s\n", arr);
	return 0;
}

 

5.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,感谢大家的关注与喜欢。

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

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

相关文章

Visual Studio 最新版安装教程

Visual Studio简介 Visual Studio是一个集成开发环境&#xff08;IDE&#xff09;&#xff0c;广泛应用于.NET和C工作负载以及许多其他语言和框架的开发。它提供了一套完整的工具集&#xff0c;包括UML工具、代码管控工具、集成开发环境&#xff08;IDE&#xff09;等&#xff…

c++重载、隐藏和覆盖

重载 函数名字相同&#xff0c;但参数列表或者返回值不同一组函数要重载必须处在同一作用域中 class Base { public:Base(int data0):m_a(data){}void show(){cout<<"Base::show()"<<endl;}void show(int){cout<<"Base:show(int)"<&l…

Opencv(C++)学习 TBB与OPENMP的加速效果实验与ARM上的实践(二)

在上一篇文章中&#xff0c;我们成功验证了Intel Threading Building Blocks (TBB) 与 OpenMP 在多线程并行处理方面的加速潜力。为了更深入地理解这些技术在实际应用场景中的效能提升&#xff0c;接下来我们将目光转向目标开发板环境&#xff0c;进一步探究这两种框架在嵌入式…

快速排序

思想&#xff1a;分而治之&#xff1b; 确定好基准值然后在两侧递归调用,记好模版就好了 #include<bits/stdc.h> using namespace std; int n; const int N1e610; int q[N]; void quick_sort(int q[],int l,int r) {if(l>r)return ;int xq[l],il-1,jr1;while(i<j…

js数组和字符串之间的转换方式以及数组的一些方法

一、数组和字符串之间的转换方式 1&#xff09;将字符串切割成字符串数组—stringObject.split(separator, howmany) seperator-----字符串、正则表达式&#xff0c;必需 howmany------指定返回的数组的最大长度&#xff0c;可省略&#xff0c;省略后全量返回 源代码 var str&q…

自研人工智能小工具-小蜜蜂(国外ChatGpt的平替)

国内有非常多好用的人工智能工具&#xff0c;但均无法完全替代国外ChatGpt。 ChatGPT相较于其他国内工具的优势在于以下几点&#xff1a; 创新的语言生成能力&#xff1a;ChatGPT是由OpenAI开发的先进的自然语言生成模型&#xff0c;它采用了大规模的预训练和精细调整方法。因此…

揭秘远程控制APP的便捷之美!

在这个科技日新月异的时代&#xff0c;我们的生活被各种手机软件所包围。几乎每个人都有一个甚至多个手机&#xff0c;你是否也有遇到过需要远程操作自己某一台手机的场景呢&#xff1f;今天&#xff0c;我要向大家推荐一款神奇的手机远程操作神器&#xff0c;让你可以随时随地…

格式化日期注解@JsonFormat的使用和TimeZone时区问题

JsonFormat的使用 目的 为了便于date类型字段的序列化和反序列化&#xff0c;需要在数据结构的Date、Timestamp、DateTime类型的字段上用JsonFormat注解进行注解 使用 JsonFormat注解是一个时间格式化注解&#xff0c;比如我们存储在mysql中的数据是date类型的&#xff0c;当…

聊聊比特币----比特币地址

⽐特币地址是⼀个标识符&#xff08;帐号&#xff09;&#xff0c;包含27-34个字母数字拉丁字符&#xff08;0&#xff0c;O&#xff0c;I除外&#xff09;。地址可以以QR码形式表⽰&#xff0c;是匿名的&#xff0c;不包含关于所有者的信息。 地址⽰例&#xff1a;14qViLJfdG…

树状数组复习

基本原理 树状数组的原理简单来说就是利用二进制拆分区间 我们可以对一个数进行二进制分解&#xff0c;最多分解成log(x)个数&#xff0c;同样我们可以对[1,n]这个区间进行分解。也是最多log段&#xff0c;每次修改时我们维护受到影响的区间&#xff0c;然后查询时用这log个区…

ele-h5项目使用vue3+vite开发:第四节、业务组件-SearchView组件开发

需求分析 展示切换动画搜索框输入文字&#xff0c;自动发送请求搜索结果展示搜索状态维护历史搜索展示&#xff0c;点击历史搜索后发送请求历史搜索更多切换动画效果 <script setup lang"ts"> import OpSearch from /components/OpSearch.vue import { ref } f…

前端JavaScript篇之对JSON的理解

目录 对JSON的理解 对JSON的理解 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它以易读易写的文本形式表示结构化数据&#xff0c;比较适合用来在不同的应用程序或平台之间传递数据。 简单来说&#xff0c;JSON就像是一种…

LangChain 81 LangGraph 从入门到精通三

LangChain系列文章 LangChain 60 深入理解LangChain 表达式语言23 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 61 深入理解LangChain 表达式语言24 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 62 深入理解Lang…

Git使用命令大全

命令大全参考阮一峰的博客&#xff0c;根据自己的使用习惯作了调整。 Git常用命令 其他常用的命令 配置Git # 显示当前的Git配置 $ git config --list# 编辑Git配置文件 $ git config -e [--global]# 设置提交代码时的用户信息 $ git config [--global] user.name "[nam…

JAVA工厂方法模式详解

工厂方法模式 工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 在工厂模式中&#xff0c;我们在创建对象时不会对客户端暴露创建逻辑&#xff0c;并且是通过…

如何结合ChatGPT生成个人魔法咒语词库

3.6.1 ChatGPT辅助力AI绘画 3.6.1.1 给定主题让ChatGPT直接描述 上面给了一个简易主题演示一下&#xff0c;这是完全我没有细化的提问&#xff0c;然后把直接把这些关键词组合在一起。 关键词&#xff1a; 黄山的美景&#xff0c;生机勃勃&#xff0c;湛蓝天空&#xff0c;青…

python使用Netmiko库配置路由器

目录 一&#xff1a;介绍 二&#xff1a;查看路由器接口信息 三&#xff1a;配置ip地址 四&#xff1a;配置防火墙 五&#xff1a;备份配置信息 一&#xff1a;介绍 Netmiko 是一个 Python 库&#xff0c;用于自动化网络设备的交互。它使用 Paramiko 作为其底层库来执行 S…

VSCode 安装LLDB调试器(OS X)并启动调试

插件&#xff1a;&#xff08;LLDB插件安装&#xff09; 安装这个版本不好弄错了&#xff0c;CodeLLDB&#xff08;名字&#xff09; 配置&#xff1a;&#xff08;LLDB启动调试&#xff09; {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更…

[ChatGPT们】ChatGPT 如何辅助编程初探

主页&#xff1a;元存储的博客 全文 9000 字&#xff0c; 原创请勿转载。 我没有写过诗&#xff0c;但有人说我的代码像诗一样优雅 -- 雷军 图片来源&#xff1a;https://www.bilibili.com/video/BV1zL411X7oS/ 1. 引言 作为一个程序员&#xff0c;我们不仅要熟悉各种编程语…

vit细粒度图像分类(十)TransFG学习笔记

1.摘要 细粒度视觉分类(FGVC)是一项非常具有挑战性的任务&#xff0c;它旨在从子类别中识别对象&#xff0c;这是由于类间固有的微妙差异。现有的大部分工作主要是通过重用骨干网络提取检测到的判别区域的特征来解决这一问题。然而&#xff0c;这种策略不可避免地使管道变得复…