内存函数的简单实用

本篇要分享的是常见的内存函数

前面分享的函数都是和字符串相关,但是当我们在操作数据的时候不仅仅要操作字符数据

接下来分享几个与内存相关的函数

目录

本篇要分享的是常见的内存函数

1.memcpy

2.memmove

自定函数模拟实现memmove函数

3.memcmp

4.memset


这些函数根本不会在乎需要拷贝什么类型的数据,交给这个函数就能帮你完成任务

1.memcpy

下面对函数作用的描述是把source指针指向的数据拷贝num个字节到destination空间里去,

那么直接上代码

#include<stdio.h>
#include<string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[8] = { 0 };
	memcpy(arr2, arr1, 20);
	return 0;
}

上面代码的意思就是将arr1中的20个字节的数据拷贝到arr2数组中去

那我们调试起来验证一下

这是还没进入函数时arr2数组的内容

 再往下走一步

我们就会看到arr2数组内的数据就变成了arr1中20个字节的内容 

那这就是memcpy函数的简单的使用

那么我们用这个函数来处理浮点数的数据可以吗?

完全可以

测试下面的代码

void test2()
{
	float arr1[] = { 1.0,2.0,3.0,4.0,5.0 };
	float arr2[8] = { 0 };
	memcpy(arr2,arr1,12);
}
int main()
{
	test2();
	return 0;
}

我们将整型类型的数据都换成了浮点类型的数据

调试起来看看

 可以看到arr2数组已经初始化完成;

再往下走一步;

 此时我们就会看到arr1中的12个字节的数据,也就是前三个数据已经拷贝到了arr2数组当中 

所以我们就直到memcpy函数不在乎拷贝什么数据类型,他只会拷贝多少个字节的数据。

那我们在回头观察一下函数参数和返回类型时目标空间的起始地址,参数是void*类型的指针,那么我们之前也了解过void*类型的指针其实就是通用类型,可以接受任何类型数据的地址,这也是memcpy函数能够拷贝任何类型数据的原因。

那接下来我们不妨自己自定义函数模拟一个memcpy的函数

void* my_memcpy(void* dest,const void* src,size_t num)
{


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

那这个主函数相信你大家不难看懂;

void*类型的指针dest指向arr2数组中的首元素的地址,第二个参数void*类型的指针src指向arr1数组中的首元素的地址,第三个参数是整型参数意为传递20个字节;

接下来我们就想到拷贝的内容时直接将他解应用就可以了如下操作

但是我们会看到编译器会报错

这里要说明的是void*类型的指针不能直接解应用,而是要将其强制转化成想要转化成的类型

但是这个函数在拷贝的时候是要考虑到各种类型的,而且如果在使用的时候你想拷贝int类型,但是却在函数的第三个参数中输入了7,每次处理int类型的数据时会处理4个字节,所以第二次处理就会处理八个字节的数据,超过了7个字节,所以我们不妨将其强制转化成char*类型的指针,让他一次只处理一个字节的数据,并对其解应用

代码变成了这个样子

但是这样写还是会出错;

因为这样强制类型的转化只是临时改变了其类型,等再次需要调用函数时可能还会变成void*类型的指针,会出现错误,所以我们对其进行优化

void* my_memcpy(void* dest,const void* src,size_t num)
{
        *(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
}

 那么这么写的好处是,我们可以将强制转化后的类型保存下来,让其赋值给dest,这样的写法就很完美啦;

那这样只能处理一次,我们要处理num个字节的数据,不妨再加入while循环,

void* my_memcpy(void* dest,const void* src,size_t num)
{
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}

}

最后还需要加上一个返回值,必须是void*类型,但是这时候dest已经随着强制类型的转化离起始位置很远,所以我们不妨再创建一个指针变量在函数体的开始将其起始位置保存下来

void* my_memcpy(void* dest,const void* src,size_t num)
{
	void* p = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return dest;
}

 让代码调试起来

 可以看到arr2数组中的前二十个字节的数据已经被被改变了

那以后想要拷贝哪些内容直接使用memcpy会是一个不错的选择;

不过当我们如果要拷贝字符串的时候最好还是最好使用strcpy,因为memcpy函数中有一个操作是类型的强转换,如果处理数据较多的话会浪费很多时间,所以说尽量使用最匹配的函数。

当我们在使用memcpy出现了类似以下这种情况时;

void test4()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr1+2,arr1,20);

}
int main()
{
	test4();
	return 0;  
}

我写这段代码的目的是是想让memcpy函数从第三个数开始拷贝;让3,4,5,6,7变成1,2,3,4,5

 但是我们调试起来看到arr1数组中的元素并没有按我们想象中的变化

那么原理是怎么样的呢?

我们在拷贝内容的时候同时也会改变arr1中的内容,所以就等于一边改变arr1数组中的内容一边将其拷贝到arr1数组中,并且只拷贝20个字节的内容,所以改变后的arr1的内容是1,2,1,2,3,4,5,6,7,8,我们将前20个字节的内容拷贝到了arr+2后面的内容,所以就成为了如上图所示的数组内容

 所以我们发现在内存重叠的时候使用memcpy可能实现意想不到的后果;

建议在内存重叠的情况下使用memmove函数

2.memmove

从函数名字上可能会觉得和刚才的memcpy没有关系

但是我们从c++官网上观察一下

 他的返回类型和函数参数的类型van♂全一致;

 这时候使用memmove函数就会发现可以达到我们上述所说的目的

那我们不妨

自定函数模拟实现memmove函数

那么还是一样,我们一步一步来,

先将函数返回类型和返回参数写起来

void* my_memmove(void* dest, void* src, size_t num)
{

}

再次分析

我们是要将arr1数组中从3开始,数20个字节的元素,用原先的arr1中数组的元素将其覆盖,

那既然我们使用memcpy拷贝时会覆盖掉前面的一部分数据,那我们不妨先从后面拷贝其他的数据,再拷贝前面的数据。

但是我们还要分情况讨论

还是按照上面的数组举例,如果我们想要将arr1数组中的3,4,5,6,7拷贝到前五个元素的位置上呢?那就是要从前向后拷贝;

所以要分情况讨论

两种情况如何区分呢?给大家简单画个图

 第一种情况就是将蓝色框的数据放到红色框里去,这就需要从后往前拷贝数据

第二种情况是将蓝色框放到红色框里去,这种情况就需要从前向后拷贝数据

那第一种我们对比函数参数来看,数据来源于src指针,那目的地就是dest;

 

 第二种情况如下

 

 所以可以用if语句来判断

if(dest<src)
从前向后
else 
从后向前

那么集体要怎么实现呢

其实仔细观察你就会发现从前向后的方式和memcpy没有区别,完全可以直接剪下来

void* my_memmove(void* dest, void* src, size_t num)
{
    if(dest<src)
    {
            while (num--)
	    {
		    *(char*)dest = *(char*)src;
		    dest = (char*)dest + 1;
		    src = (char*)src + 1;
	    }
    }
    else 
    从后向前
}

从后向前循环依然是while(num--)

那我们既然是从后往前拷贝,那就要访问最后一个数的地址,那我们不妨将第一个元素的地址加上num;num开始是20进入到循环,num--变成19,dest是起始地址0,加上num就等于19,就是数组中第20个元素,也是最后一个元素,那么我们这样就访问到了最后一个元素的地址

*((char*)dest + num) = *((char*)src + num);

我们不妨将这个地址解应用并且将指向src的指针,也就是指向原数组的指针解应用赋值给目标空间,这样就完成了将源空间的值给了目标地址的操作,依然要加入while循环

当然函数返回值是一个指针类型,要指向数组首元素,所以依然要重新定义一个指针变量来存放首地址,在函数末尾将其返回。

最后代码如下

void* my_memmove(void* dest, void* src, size_t num)
{
    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);
		}
    return ret;

}

 那么最终的调试结果出来大家也可以看到成功的将1 2 3 4 5 放到了3 4 5 6 7的位置,

我们再将函数实参内容调换一下位置

 

监视的内容也得到了,我们将后面的内容拷贝到了前面

那memcpy和memmove的区别就很好理解了

memcpy拷贝的是不重叠的内存

memmove可以拷贝重叠的内存

当然也可以粗暴的理解为memmove使用的范围要大于memcpy

3.memcmp

那这个函数可能大家也都猜得到,是两块内存进行比较

 他的返回类型是int类型的整形,参数是两个任意类型的指针(因为不知道要比较的内存是什么类型),和一个size_t(无符号整形)类型的字节数;

比较的是从ptr1和ptr2指针开始的num个字节;

那和strcmp函数也是有相似之处的,就比如说下面的表格

ptr1指针指向的内存小于ptr2指针指向的内存中的数据,返回小于零的值;

反之则返回大于0的值;相等就返回零

 代码简单使用一下

 

  此时比较的是前16个字节的数据,也就是1 2 3 4 那上下都相等,返回0;

修改一下比较的对象

 此时比较的是17个字节的数据,那多出一个字节该怎么比较呢?

就要提到我们所学过的大端储存方式和小端储存方式了

在vs的环境下是小端储存方式(低位字节数据存放在高地址,高位字节数据存放在低地址),

我们将内容数组内容展示出来

 那第十七个比特位就是05和06进行比较,返回值就为小于零的数;

4.memset

memset我们在之前也有接触过

 了解一下这个函数的返回类型和参数;

不难看到返回参数是void*类型,第一个参数是void*类型的指针,第二个参数是int类型,第三个是无符号整形;

他的作用就是在第一个参数指向的数组中将num个字节的内容都改变成value,

简单使用代码如下

#include<stdio.h>
int main()
{
	char arr[] = "hello world";
	memset(arr, 'x', 5);
	printf("%s\n",arr);
	return 0;
}

 可以看到"hello world "变成了"xxxxx world"

那这个字符串的作用就是将arr数组中的前5个元素都改成x,通过代码简单的使用应该不难理解。

这个函数唯一要注意的是最后一个参数是以字节为单位来设置的。

那如果要改动数组,将数组 的所有元素改成1 呢?

 很遗憾不能将数组中的元素改为1,因为memset是以字节为操作单位的,所以编译器会将01 01 01  01看作一个整形,换做十进制我们再打印出来

 我们看到是是个非常大的数字;所以无法用memset来设置整型数组的内容

以上就是所要分享的内存函数的内容,希望对你有所帮助

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

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

相关文章

【算法经典题集】DP和枚举(持续更新~~~)

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;算法经典题集&#x1f50a;本专栏涉及到的知识点或者题目是算法专栏的补充与应用&#x1f4aa;种一棵树最好是十年前其次是现在DPDP就是动态规划&a…

Web前端 JS WebAPI

1、操作DOM 1.1、什么DOM&#xff1f; DOM&#xff08;Document Object Model——文档对象模型&#xff09;&#xff1a;DOM是浏览器提供的一套专门用来操作网页内容的功能 DOM作用&#xff1a;开发网页内容特效和实现用户交互 DOM树是什么&#xff1f; 将 HTML 文档以树状…

手把手教你使用vue创建第一个vis.js

先看一下实现效果吧 &#xff0c;如下图 &#xff1a; 为什么要写这篇文章呢&#xff1f;因为之前有浅浅的了解一下vis.js&#xff0c;后期开发中没有使用vis&#xff0c;所以太深奥的也不懂&#xff0c;但是当时是用js写的。这两天有人问我用vue怎么写&#xff0c;然后说看到…

减治法实现插入排序,减治法实现二叉查找树(二叉搜索数,二叉排序数)的创建、插入与查找(含解析与代码实现)

&#x1f38a;【数据结构与算法】专题正在持续更新中&#xff0c;各种数据结构的创建原理与运用✨&#xff0c;经典算法的解析✨都在这儿&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 -…

嵌入式软件开发之Linux下C编程

目录 前沿 Hello World&#xff01; 编写代码 编译代码 GCC编译器 gcc 命令 编译错误警告 编译流程 Makefile 基础 何为 Makefile Makefile 的引入 前沿 在 Windows 下我们可以使用各种各样的 IDE 进行编程&#xff0c;比如强大的 Visual Studio。但是在Ubuntu 下如何进…

【Java版oj】day10 井字棋、密码强度等级

目录 一、井字棋 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、密码强度等级 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、井字棋 &a…

CAT8网线测试仪使用中:线缆的抗干扰参数解读以及线缆工艺改进注意事项

FLUKE Agent platform -深圳维信&#xff0c;带你更深入的了解铜缆测试&#xff0c;详细为您讲解什么是TCL/ELTCL&#xff0c;他们对数据的传输到底有什么影响呢&#xff1f; 前情分析&#xff1a;为什么用双绞线传输信号&#xff1f;&#xff08;一图就懂&#xff09; TCL&a…

【深度解刨C语言】符号篇(全)

文章目录一.注释二.续行符与转义符1.续行符2.转义符三.回车与换行四.逻辑操作符五.位操作符和移位操作符六.前置与后置七.字符与字符串八./和%1.四种取整方式2.取模与取余的区别和联系3./两边异号的情况1.左正右负2.左负右正九.运算符的优先级一.注释 注释的两种符号&#xff…

Sentinel

SentinelSentinel介绍什么是Sentinel?为什么需要流量控制&#xff1f;为什么需要熔断降级&#xff1f;一些普遍的使用场景本文介绍参考&#xff1a;Sentinel官网《Spring Cloud Alibaba 从入门到实战.pdf》Sentinel下载/安装项目演示构建项目控制台概览演示之前需先明确&#…

【webrtc】ICE 到VCMPacket的视频内存分配

ice的数据会在DataPacket 构造是进行内存分配和拷贝而后DataPacket 会传递给rtc模块处理rtc模块使用DataPacket 构造rtp包最终会给到OnReceivedPayloadData 进行rtp组帧。吊炸天的是DataPacket 竟然没有声明析构方法。RtpVideoStreamReceiver::OnReceivedPayloadData 的内存是外…

3.网络爬虫——Requests模块get请求与实战

Requests模块get请求与实战requests简介&#xff1a;检查数据请求数据保存数据前言&#xff1a; 前两章我们介绍了爬虫和HTML的组成&#xff0c;方便我们后续爬虫学习&#xff0c;今天就教大家怎么去爬取一个网站的源代码&#xff08;后面学习中就能从源码中找到我们想要的数据…

普通Java工程师 VS 优秀架构师

1 核心能力 1.1 要成为一名优秀的Java架构师 只懂技术还远远不够&#xff0c;懂技术/懂业务/懂管理的综合型人才&#xff0c;才是技术团队中的绝对核心。 不仅仅是架构师&#xff0c;所有的技术高端岗位&#xff0c;对人才的综合能力都有较高的标准。 架构路线的总设计师 规…

安卓渐变的背景框实现

安卓渐变的背景框实现1.背景实现方法1.利用PorterDuffXfermode进行图层的混合&#xff0c;这是最推荐的方法&#xff0c;也是最有效的。2.利用canvas裁剪实现&#xff0c;这个方法有个缺陷&#xff0c;就是圆角会出现毛边&#xff0c;也就是锯齿。3.利用layer绘制边框1.背景 万…

多线程案例——阻塞队列

目录 一、阻塞队列 1. 生产者消费者模型 &#xff08;1&#xff09;解耦合 &#xff08;2&#xff09;“削峰填谷” 2. 标准库中的阻塞队列 3. 自己实现一个阻塞队列&#xff08;代码&#xff09; 4. 自己实现生产者消费者模型&#xff08;代码&#xff09; 一、阻塞队列…

【Pytorch】 理解张量Tensor

本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 这是目录张量Tensor是什么&#xff1f;张量的创建为什么要用张量Tensor呢&#xff1f;总结张量Tensor是什么&#xff1f; 在深度学习中&#xff0c;我们经常会遇到一个概念&#xff…

更改Hive元数据发生的生产事故

今天同事想在hive里用中文做为分区字段。如果用中文做分区字段的话&#xff0c;就需要更改Hive元 数据库。结果发生了生产事故。导致无法删除表和删除分区。记一下。 修改hive元数据库的编码方式为utf后可以支持中文&#xff0c;执行以下语句&#xff1a; alter table PARTITI…

Vue初入,了解Vue的发展与优缺点

作者简介&#xff1a;一名计算机萌新、前来进行学习VUE,让我们一起进步吧。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;我叫于豆豆吖的主页 前言 从本章开始进行Vue前端的学习&#xff0c;了解Vue的发展&#xff0c;以及背后的故事。 一.vue介…

ASEMI代理瑞萨TW9992AT-NA1-GE汽车芯片

编辑-Z TW9992AT-NA1-GE是一款低功耗NTSC/PAL模拟视频解码器&#xff0c;专为汽车应用而设计。它支持单端、差分和伪差分复合视频输入。集成了对电池短路和对地短路检测&#xff0c;先进的图像增强功能&#xff0c;如可编程的自动对比度调整&#xff08;ACA&#xff09;和MIPI…

【Linux】网络编程套接字(下)

&#x1f387;Linux&#xff1a; 博客主页&#xff1a;一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 看似不起波澜的日复一日&#xff0c;一定会在某一天让你看见坚持…

ASEMI代理MIMXRT1064CVJ5B原装现货NXP车规级MIMXRT1064CVJ5B

编辑&#xff1a;ll ASEMI代理MIMXRT1064CVJ5B原装现货NXP车规级MIMXRT1064CVJ5B 型号&#xff1a;MIMXRT1064CVJ5B 品牌&#xff1a;NXP /恩智浦 封装&#xff1a;LFGBA-196 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;196 类型&#…
最新文章