C语言常用的内存函数

在上一篇博客中我为大家分享了一些常用的字符串函数,以及它们的用法和模拟实现。通过字符串函数中的strcpy,我们能够做到将一个字符串中的内容拷贝到另一个字符串上,可如果有一天我们想把一个整型数组中的内容拷贝到另一个整型数组中呢?这样看来好像strcpy就不适用了,因为它只能对字符串进行拷贝。那么今天所提到的内存函数就能够实现整型数组,甚至其他类型的拷贝,让我们开始今天的学习吧~!

一、memcpy函数

我们可以看到,memcpy函数的返回类型以及参数类型大多都是void类型,这也就使得它能够接收不同类型的数据,并且能够对不同类型的数据加以处理~

①memcpy函数的使用

memcpy函数的作用是:将num字节的值从源指向的位置直接复制到目标指向的内存块。由于参数为void*类型,所以源指针和目标指针指向的对象的底层类型与此函数无关。(注:这个函数在遇到 '\0' 的时候并不会停下来。)

memcpy函数的第一个参数void* destination指向目标起始位置,代表想要改变的初始位置第二个参数const void* source指向想要复制的数据的起始位置,它的作用是作为模板为目标复制字节第三个参数size_t表示要复制的字节数。那么了解了memcpy函数的组成,让我们举个例子,尝试一下使用memcpy函数拷贝整型数组吧~

int main()
{int arr0[20] = { 0 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };memcpy(arr0, arr1, 10 * sizeof(int));for (int i = 0; i < 10; i++){printf("%d ", arr0[i]);}return 0;
}

甚至我们还能做到使用memcpy拷贝结构体

struct stu
{char name[20];int age;long id;
};
int main()
{struct stu s0 = { "xiaowang",20,10666 };struct stu s1 = { "xiaosong",40,10333 };memcpy(&s0, &s1, 8 * sizeof(char));memcpy(&s0, &s1, 7 * sizeof(int));printf("%s\n", s0.name);printf("%d\n", s0.age);printf("%ld\n", s0.id);return 0;
}

(我这里只是举一个例子,对于结构体的拷贝还需要学会结构体内存对齐,如果有机会的话下次我会单独用一篇博客来分享一下关于结构体内存对齐的知识)

②memcpy函数的模拟实现

不知道小伙伴们还记不记得我们之前学习过的qsort函数,让我们顺便回顾一下吧,qsort函数可以排序任意类型的数据,函数底层使用的是快速排序的方法。那么qsort函数是怎么做到能够排序任意类型数据的呢?没错,就是在不知道数据类型的情况下直接交换每一个字节

而我们的memcpy函数也差不多是这个意思,我们最后传递的size_t类型参数就代表了需要替换的字节数,所以我们可以传递一个size_t类的num代表字节数,使用一个while循环,每循环一次,传递的num就减一,直到num为0时循环结束,此时正好交换了num个字节。

void* my_memcpy(void* arr0, const void* arr1, size_t num)
{assert(arr0 && arr1);while (num--){*(char*)arr0 = *(char*)arr1;arr0 = (char*)arr0 + 1;arr1 = (char*)arr1 + 1;}return (void*)arr1;
}
int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前:\n");for (int i = 0; i < 5; i++){printf("%d ", arr0[i]);}my_memcpy(arr0, arr1, 5 * sizeof(int));printf("\n替换后:\n");for (int i = 0; i < 5; i++){printf("%d ", arr0[i]);}return 0;
}

那我们能不能尝试自己拷贝自己,做到将arr1里的4,5,6,7,8拷贝成它的前五位元素1,2,3,4,5呢?让我们尝试一下:

void* my_memcpy(void* arr0, const void* arr1, size_t num)
{assert(arr0 && arr1);while (num--){*(char*)arr0 = *(char*)arr1;arr0 = (char*)arr0 + 1;arr1 = (char*)arr1 + 1;}return (void*)arr1;
}
int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}my_memcpy(arr1+3, arr1, 5 * sizeof(int));printf("\n替换后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

可以看到在我们想用arr1前方的元素拷贝给后方时,却打印出了错误的答案,这是为什么呢?因为在对arr1进行拷贝的时候改变的是arr1本身,当第一次拷贝时arr1中的4变成了1,第二次拷贝时5变成了2,第三次拷贝时6变成了3,第四次拷贝时,我们需要将7变成4,可是此时arr1中的4已经被替换成了1,所以此时7变成了1,同理8也变成了2。这就说明了:(memcpy函数j仅仅是简单的从前往后进行拷贝,并没有考虑内存有重叠的情况,如果内存有重叠,其行为是不确定的。)

那么此时,下一个要介绍的函数就该登场了~

二、memmove函数

可以看到memmove的参数与memcpy函数是一致的,但与memcpy函数相较,memmove函数能够完美的处理重叠的情况。

①memmove函数的使用

• 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

• 如果源空间和目标空间出现重叠,就可以使用memmove函数处理。

那么我们使用memmove来证实上面的想法,就用刚刚memcpy打印出错的代码:

int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}memmove(arr1+3, arr1, 5 * sizeof(int));printf("\n替换后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

看来这次使用memmove确实解决了重叠的情况。

②memmove函数的模拟实现

想要实现memmove函数的模拟实现,就需要我们解决上面memcpy函数的重叠问题。那么memcpy函数的重叠问题是如何造成的呢?刚刚的思路我们可以用这张图来表示,而我们按照memcpy的思路来进行拷贝就是从前往后拷贝,用图片来表示出来应该是这样的:这就是出现了重叠的情况,我们想调用arr[3]时,其指向的元素已经从4变成了1,想要避免这种情况我们只需要在拷贝元素时保证需要拷贝的内容不被替换就好了。就比如此时,我们转换一下思路,我们现在想将1,2,3,4,5拷贝成4,5,6,7,8。这样的话能不能做到呢?是可以的!因为我们在将1拷贝成4后,拷贝的位置和需要拷贝的内容的位置都向后挪一位,此中重叠的元素为4和5,但4和5在最开始就为1和2拷贝成功啦,所以并不会发生错误的打印。这种情况就是拷贝内容初始位置拷贝初始位置,此时需要使用顺序拷贝

而对于上面将4,5,6,7,8变成1,2,3,4,5,我们可以尝试用逆序拷贝。通过这个图可以知道,拷贝内容初始位置拷贝初始位置,此时需要使用逆序拷贝。通过逆序拷贝的方式,就能够避免在拷贝前,拷贝内容被替换掉的情况。

想要模拟实现memmove函数,我们只需要将两种情况结合在一起,先判断两个初始位置谁在前谁在后,然后再对应的编写出解决方案就可以啦~(拷贝内容初始位置在拷贝初始位置之后时,直接使用memcpy的模拟函数就可以,拷贝内容初始位置在拷贝初始位置之前时,就逆序拷贝,从需要拷贝的最后一个字节往前拷贝就能够解决了)

void* my_memmove(void* arr0, const void* arr1, size_t num)
{assert(arr0 && arr1);if (arr0 < arr1){while (num--){*(char*)arr0 = *(char*)arr1;arr0 = (char*)arr0 + 1;arr1 = (char*)arr1 + 1;}}else{while (num--)*((char*)arr0 + num) = *((char*)arr1 + num);}return (void*)arr1;
}
int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}my_memmove(arr1 + 3, arr1, 5 * sizeof(int));printf("\n替换后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

这样就成功的模拟实现了memmove函数啦~

三、memset函数

memset的作用是:复制字符 value (注意,不是字符串,是一个字符)到参数 ptr 所指向的字符串的前 num 个字符。

memset和前两个函数最大的区别就是,前两个函数可以传输的是可以每一个的不同的,可以改变的,多样的。而memset能做到的只是将一个固定的字符拷贝到一段空间中。那么它的具体用法是什么呢?我们来尝试一下使用memset将一段数组进行清零。

int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("清零前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}memset(arr1, 0, 10 * sizeof(int));printf("\n清零后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

能够看到这边也是成功清零了,那可能有人会问,那不是也可以把值全部变成1,全部变成2之类的吗?那让我们来尝试一下这个思路:欸?怎么出现了这么一堆又奇怪又大的数字,这是怎么一回事呢?请仔细看刚刚的介绍,我们传输的并不是直接的一个值,而是一个将参数转化为二进制后填入的一个字节比如此时我们想填入的是1,那么1转换为二进制形式就是0000 0001,但是arr中存放的int整形变量有四个字节,所以传进四个字节后其实这个对应的值会变成0000 0001 0000 0001 0000 0001 0000 0001。也就是一个非常大的数字。由于之前传递的0对应的就是00 00 00 00所以会直接是0,并不是说明传递的是什么数字,就会是什么数字,想要理解memset函数,就要清楚这一点。而传递其他的值大部分比较麻烦,所以memset函数的主要作用还是使结构体或数组进行清零(注意并不是说就没有其他的作用了哦)

四、memcmp函数

memcmp的作用是比较从ptr1和ptr2指针指向的位置开始,向后的num个字节

memcmp的用法与strncmp的用法很像,都是用于指定长度的比较大小,并且都是大于返回>0的数,小于返回<0的数,等于返回0。唯一不同的是memcmp函数是内存函数,通过它能够比较所有的类型,因为不论存储的内容是字符串,整数还是其它类型的数据,该函数都会逐个字节进行比较

int main()
{int arr0[] = { 1,2,3,4,5,6 };int arr1[] = { 1,2,3,4,5,7 };if (memcmp(arr0, arr1, 6 * sizeof(int)) > 0){printf("arr0>arr1");}else if (memcmp(arr0, arr1, 6 * sizeof(int)) < 0){printf("arr0<arr1");}else{printf("arr0=arr1");}return 0;
}

由此就可以进行两个数组的大小的比较。同样字符串也是可以的:

int main()
{char arr0[] = "abcdef";char arr1[] = "abcdez";if (memcmp(arr0, arr1, 6 * sizeof(char)) > 0){printf("arr0>arr1");}else if (memcmp(arr0, arr1, 6 * sizeof(char)) < 0){printf("arr0<arr1");}else{printf("arr0=arr1");}return 0;
}

那么关于比较常用的内存函数的知识,就给大家分享到这里啦,如果有讲解的有问题或者不清楚的地方,还希望各位在评论区多多指出,我也会吸取教训,多多学习的,那么我们下期再见啦~

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

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

相关文章

KV存储之ETCD

ETCD 是一种分布式键值存储系统&#xff0c;主要用于分布式系统中的配置管理、服务发现和分布式协调。它由 CoreOS 团队开发&#xff0c;现在是 CNCF&#xff08;云原生计算基金会&#xff09;托管的一个开源项目。ETCD 在设计时非常注重一致性、可用性和性能&#xff0c;通常被…

Eclipse的使用配置教程:必要设置、创建工程及可能遇到的问题(很详细,很全面,能解决90%的问题)

Eclipse的使用配置&#xff1a; Ⅰ、Eclipse 的必要配置&#xff1a;1、Eclipse 的安装&#xff1a;其一、将 Eclipse 解压或安装到没有中文且没有空格的路径下。其二、拿到 eclipse.exe 文件&#xff0c;傻瓜式安装即可; 2、设置工作空间(workspace)&#xff1a;其一、首次启动…

C程序设计——基本变量类型(指针杂谈)

瞎聊 本文后面的内容&#xff0c;可以暂时看不懂&#xff0c;以后如果从事这一行&#xff0c;慢慢会理解&#xff0c;但是这句话要记住&#xff1a;如果 piInt 是一个指向整型的指针变量&#xff0c;那么 *piInt 就是一个整型变量&#xff1b;类似的&#xff0c;如果pcChar是…

原生微信小程序笔记完整总结4.0

&#x1f939;‍♀️潜意识起点&#xff1a;个人主页 &#x1f399;座右铭&#xff1a;得之坦然&#xff0c;失之淡然。 &#x1f48e;擅长领域&#xff1a;大前端 是的&#xff0c;我需要您的&#xff1a; &#x1f9e1;点赞❤️关注&#x1f499;收藏&#x1f49b; 是我…

【MySQL】事务管理

【MySQL】事务管理 什么是事务为什么要有事务事务的版本支持事务的提交方式事务的常见操作事务的隔离级别如何理解隔离性隔离级别隔离级别的设置与查看读未提交【Read Uncommitted】读提交【Read Committed】可重复读【Repeatable Read】串行化【serializable】一致性(Consiste…

代码随想录算法训练营43期 | Day 14——226.翻转二叉树、101. 对称二叉树、104.二叉树的最大深度、二叉树最小深度

代码随想录算法训练营 226.翻转二叉树101. 对称二叉树递归法 104.二叉树的最大深度二叉树最小深度 226.翻转二叉树 leetcode链接 思路&#xff1a; 递归三部曲&#xff1a; 确定递归函数的参数和返回值确定终止条件确定单层递归的逻辑 递归法 TreeNode* invertTreeNode(Tree…

并发系统的 CSP+PAT 形式化建模与验证方法(以Kafka系统为例)

消息队列中间件是分布式系统的重要组成部分。它允许应用程序仅关注数据本身&#xff0c;而无需关心数据传输的具体细节。这一特性有效解决了消息异步传输、应用程序解耦以及流量削峰等问题。Kafka是一个开源的分布式消息系统&#xff0c;它基于发布-订阅模型构建。Kafka具有低延…

Unity使用代码生成ScriptableObject数据并赋值之后,重启数据就没有啦!

2024年8月14日早&#xff0c;因数据持续化存储&#xff0c;重启电脑后数据会丢失&#xff0c;而我找不到原因被领导质疑了&#xff0c;故写一片博客记录这个错误。 省流 使用在编辑器的play模式中为ScriptableObject赋值之后&#xff0c;需要使用 #if UNITY_EDITORUnityEdit…

Codeforces Round 495 (Div. 2) F. Sonya and Bitwise OR(线段树)

原题链接&#xff1a;F. Sonya and Bitwise OR 题目大意&#xff1a; 给出一个长度为 n n n 的数组 a a a&#xff0c;并给出 m m m 次询问以及一个数字 x x x。 每个询问形式如下给出&#xff1a; 1 1 1 i i i y y y &#xff1a;将 a i a_{i} ai​ 位置的值更改为 y…

数据库分库分表的介绍

为什么要分库分表 把存于一个库的数据分散到多个库中&#xff0c;把存于一个表的数据分散到多个表中。如果说读写分离是为了分散数据库读写操作压力&#xff0c;分库分表就是为了分散存储压力&#xff0c;一般情况下&#xff0c;单表数据量到达千万级别&#xff0c;就可以考虑…

基于飞腾平台的Hbase的安装配置

【写在前面】 飞腾开发者平台是基于飞腾自身强大的技术基础和开放能力&#xff0c;聚合行业内优秀资源而打造的。该平台覆盖了操作系统、算法、数据库、安全、平台工具、虚拟化、存储、网络、固件等多个前沿技术领域&#xff0c;包含了应用使能套件、软件仓库、软件支持、软件适…

支持S/MIME证书的邮件客户端有哪些?

S/MIME证书&#xff0c;也叫做邮件安全证书&#xff0c;支持安全/多用途互联网邮件扩展协议&#xff08;S/MIME协议&#xff09;&#xff0c;是通过加密和数字签名来确保电子邮件的安全性、保密性和完整性的数字证书。GDPR、HIPAA、FDA等多个行业都要求邮件发送方在发送邮件时对…

基于R语言遥感随机森林建模与空间预测;遥感数据处理与特征提取;数据分析与可视化

目录 第一章 理论基础与数据准备【夯实基础】 第二章 随机森林建模与预测【讲解实践】 第三章 实践案例与项目 更多应用 随机森林作为一种集成学习方法&#xff0c;在处理复杂数据分析任务中特别是遥感数据分析中表现出色。通过构建大量的决策树并引入随机性&#xff0c;随…

C++ 特殊类设计以及单例模式

目录 1 不能被拷贝 2 只能在堆上创建对象 3 只能在栈上创建对象 4 禁止在堆上创建对象 5 不能被继承的类 6 单例类 特殊类就是一些有特殊需求的类。 1 不能被拷贝 要设计一个防拷贝的类&#xff0c;C98之前我们只需要将拷贝构造以及拷贝赋值设为私有&#xff0c;同时只声明…

RTX 4070 GDDR6显存曝光:性能与成本的平衡之选

近期&#xff0c;关于NVIDIA RTX 4070新显卡的信息曝光&#xff0c;这款显卡将配备较为缓慢的GDDR6显存&#xff0c;而非更高性能的GDDR6X。这一配置的选择引发了业内的广泛关注&#xff0c;特别是在性能与成本的平衡问题上。 新版RTX 4070 OC 2X的核心特点 **1.显存类型与带…

8B 端侧小模型 | 能力全面对标GPT-4V!单图、多图、视频理解端侧三冠王,这个国产AI开源项目火爆全网

这两天&#xff0c; Github上一个 国产开源AI 项目杀疯了&#xff01;一开源就登上了 Github Trending 榜前列&#xff0c;一天就获得将近600 star。 这个项目就是国内大模型四小龙之一面壁智能最新大打造的面壁「小钢炮」 MiniCPM-V 2.6 。它再次刷新端侧多模态天花板&#xf…

微分方程求解的三种解析方法:经典时域法(齐次解+特解,零状态+零输入),冲激响应卷积法、传递函数法

经典时域分析方法 以例题的形式对经典时域解法&#xff08;齐次解特解&#xff09;进行说明&#xff0c;最后进行总结。考虑如下形式微分方程&#xff1a; y ′ ′ ( t ) 5 y ′ ( t ) 6 y ( t ) 2 f ′ ( t ) 6 f ( t ) y\left( t \right) 5y\left( t \right) 6y\left(…

pyinstaller使用

pyinstaller 入门 Pyat5 的安装程序开发PyQt6 的安装程序开发 编写好的程序编译成可执行文件资源文件:用 zip 打包&#xff0c;基本可以压缩到 1/3 大小;然后再用 pyqt 写一个 setup 安装程序&#xff0c;安装到指定目录(安装的过程实际就是把文件解压、拷贝到指定目录、注册到…

[000-01-030].第2节 :Zookeeper本地安装

1.Zookeeper下载地址 1.Zookeeper官网地址 2.会显示Zookeeper的一些版本 2.Zookeeper本地模式安装&#xff1a; 2.1.Zookeeper安装前准备 1.在Centos7虚拟机中安装jdk8 2.2.Zookeeper安装过程&#xff1a; 1.下载zookeeper压缩版本&#xff0c;解压放在opt/moduel目录下…

虚拟人实时主持创意互动方案:赋能峰会论坛会议等活动科技互动感

随着增强现实、虚拟现实等技术的不断发展&#xff0c;“虚拟人实时主持”创意互动模式逐渐代替传统单一真人主持模式&#xff0c;虚拟主持人可以随时随地出现在不同活动现场&#xff0c;也可以同一时间在不同分会场中担任主持工作&#xff0c;在峰会、论坛、会议、晚会、发布会…