【C语言】内存函数的使用和模拟实现

在这里插入图片描述

文章目录

  • 一、memcpy的使用和模拟实现
  • 二、memmove的使用和模拟实现
  • 三、memset的使用
  • 四、memcmp的使用

一、memcpy的使用和模拟实现

   在之前我们学习了使用和模拟实现strncpy函数,它是一个字符串函数,用来按照给定的字节个数来拷贝字符串,那么问题来了我们想拷贝的不是字符串,而是整型、浮点型的数据,该怎么办呢?
   这时候就要使用我们的内存函数memcpy,mem是memory的缩写,它原本是记忆的意思,在这里是内存的意思,它的作用范围就宽泛多了,因为它是对内存块的内容进行拷贝,不管内存中存放的是什么数据类型,都可以通过拷贝内存块来实现拷贝
   但是使用函数memcpy需要包含的头文件还是<string.h>,接下来我们来看看这个函数的原型:

void * memcpy ( void * destination, const void * source, size_t num );

   它有三个参数,可以看到,它的前两个参数不再是char * 指针,而是void*的指针了,因为我们不再知道要拷贝的内容具体是什么数据类型,所以可以使用void * 的指针,而它的第三个参数是一个无符号整型,代表了要拷贝的内存的字节数
   它的返回类型是void * ,也是由于不知道需要操作的数据类型是什么,所以使用void * ,那它具体返回的地址是什么呢?我们可以参照字符串函数strcpy,它返回的就是目标空间的首地址,memcpy函数也是这样,返回目标空间的首地址,也就是这里的destination
  接着我们可以总结出memcpy函数的特点:

  • 函数memcpy从source的位置,也就是源空间的首地址开始,向后复制num个字节的数据到destination指向的内存位置
  • 这个函数在遇到 ‘\0’ 的时候并不会停下来,它拷贝多少数据完全看第三个参数
  • 如果source和destination有任何的重叠,复制的结果都是未定义的

   接下来我们来简单使用一下这个函数,用它来拷贝一个整型数组,如下:

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

我们来看看运行结果:
在这里插入图片描述
   可以看到它已经帮我们把数据完全拷贝过来了,它是怎么做到的呢?我们来试着模拟实现一下这个函数,就会发现其实并不难,它会结合我们学过的qsort实现和strcpy实现的知识,现在我们赶紧来实现一下吧!

  1. 函数命名:my_memcpy
  2. 函数参数:照抄memcpy的参数,简化一些长的名字:
void* my_memcpy ( void* dest, const void* src, size_t num )
  1. 函数实现:
    (1)老规矩,首先进行一次断言,确保这两个指针不是空指针
    (2)由于要返回目标空间的首地址,所以要创建一个void*的指针变量start来存储,用于最后的返回
    (3)这里由于不知道是什么类型的数据,所以我们不能妄自定义一个数据类型,这里我们可以采用qsort里面的思想,将它们转为字符指针,一个字节一个字节的拷贝,这样就可以确保能够完美拷贝所有数据
    (4)所以我们创建一个while循环,每进行一次循环就让num–,每一次循环我们就进行一个字节的拷贝,并且拷贝完后让dest和src往后走一个字节
    (5)进行一个字节的拷贝就很简单了,只需要将dest和src强制类型转换为字符指针就可以了,主要是让它们往后面走一个字节不能使用(char * )dest++,所以我们这里可以采用++最原始的操作,就是给它+1,然后赋值给dest,如下:
dest = (char*)dest + 1;

(6)最后一步就是返回之前存下的变量start

  1. 函数代码:
#include <assert.h>void* my_memcpy(void* dest, const void* src, size_t num)
{assert(dest && src);void* start = dest;while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}return start;
}
  1. 函数测试:
    在这里插入图片描述

   最后我们来探讨一个问题,memcpy能否自己对自己进行拷贝呢?比如有一个数组arr,存放的是1到10的数字,能不能将从1开始的4个整型数据,拷贝到从5开始的4个整型数据,如下图:
在这里插入图片描述

   能否将绿色圆圈内的数据作为源数据,拷贝到蓝色圆圈的空间中,使得数组中的数据变成1 2 3 4 1 2 3 4 9 10,问题就在于怎么找到从5开始的地址
   arr是首元素地址,所以很容易想到,可以使用arr+4来作为目标空间地址,而arr作为源地址,我们来测试一下,如图:
在这里插入图片描述

   可以看到,它成功实现了,接着我们继续思考,如果在拷贝时源空间和目标空间有重叠呢?如图:
在这里插入图片描述   这个时候我们想要经过拷贝后,数据变成1 2 3 4 3 4 5 6 9 10,memcpy能否帮我们实现拷贝呢?如图:
在这里插入图片描述
   可以看到居然失败了,这是为什么呢?我们可以画图求解:
在这里插入图片描述

   到经过第三个整型的拷贝时我们发现了问题,原本该被拷贝的5现在已经变成了3,所以在7,的位置放的是3,在8的位置放的是4,所以最后整个数组变成了这个样子:
在这里插入图片描述
   跟我们打印出来的样子一模一样,那么怎么才能实现怎么内存重叠的拷贝呢?这个就要用到我们马上要学习的memmove函数了
   但是在学习memmove函数之前,我们先插个题外话,刚刚我们一直使用的是自己实现的memcpy,无法处理内存重叠的情况,那库里面的那个memcpy函数本尊呢?能否实现,我们来看看:
在这里插入图片描述
   可以看到,神奇的事情发生了,库里面的memcpy居然可以处理这种内存重叠的情况,那是不是我们写的memcpy太挫了呢?
   很明显不是,是因为C语言规定了memcpy只处理没有内存重叠的情况,有内存重叠的情况交给memmove函数解决,这里的memcpy函数又为什么能够解决这个问题呢?
   这个就涉及到编译器的问题了,比如C语言规定memcpy只处理没有内存重叠的情况,而VS的memcpy在处理了没有内存重叠的基础上,还实现了有内存重叠的情况,相当于老师只要求你考60分就能及格,就能到达要求,而你考了100分
   所以不用担心是不是我们的momcpy函数实现的有问题,我们实现的momcpy已经满足C语言的规定了,已经合格了,没有问题

二、memmove的使用和模拟实现

   memmove函数相当于时memcpy函数的进阶版,它不仅可以实现C语言规定的memcpy函数的功能,处理没有内存重叠的情况,还能处理存在内存重叠的情况,使用它也需要包含头文件string.h
   我们来看看memmove的原型:

 void * memmove ( void * destination, const void * source, size_t num );

   可以看到和memcpy的原型长得一模一样,它们参数的含义和返回值都相同,这样就不再赘述
   我们可以总结一下它们的不同:

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

   接着我们首先来测试一下memmove能否实现memcpy的功能,如下图:
在这里插入图片描述
   很明显我们看到memmove成功实现了,接着我们继续用memmove测试,让它替我们处理内存重叠的情况,如下:
在这里插入图片描述
   可以看到memmove完美替我们解决了问题,我们接下来就来学习它的模拟实现:

  1. 函数命名:my_memmove
  2. 函数参数:
void* my_memmove ( void* dest, const void* src, size_t num )
  1. 函数实现:
    (1)老规矩,对dest和src断言,确保它们不是空指针
    (2)然后创建一个变量start用来存储dest的值,用于最后的返回
    (3)我们来想想怎么解决内存重叠的情况,根据之前的尝试,我们知道,根据memcpy那样实现肯定不行,而memcpy实现时我们采用的是从前往后拷贝,我们可以来尝试一下从后往前拷贝
    (4)我们可以画一个图试试,如下:
    在这里插入图片描述
    很明显看到居然成功了,说明这种情况从后往前进行拷贝是可以的,那么是不是所有情况都可以这样呢?我们接着继续讨论
    (5)我们将上面的源空间和目标空间交换试一试:
    在这里插入图片描述
    这里就发现问题了,再想从后往前拷贝一个整型时,我们发现5和6都已经被覆盖了,无法得到了正确结果,所以光从后往前拷贝是行不通的
    (6)经过简单的思考,我们可以发现在上图的情况下,从前往后进行拷贝居然又可以了,问题就在于我们如何判断什么时候从前往后拷贝,什么时候从后往前拷贝
    (7)我们可以根据dest和src的位置判断,当目标空间首地址dest在源空间首地址src前时,就是分析的第(5)点中,我们从前往后拷贝,当目标空间首地址dest在源空间首地址src后面时,也就是分析第(4)点中,我们从后往前拷贝
    (8)我们之前说过,数组的空间是连续的,并且随着下标的增大,地址也是逐渐增大的,所以我们可以发现当dest>src,就正常从前往后拷贝,当dest<src时,就从后往前拷贝
    (9)从前往后拷贝我们之前在memcpy讲过,就不再赘述了,如下:
	if (dest < src){while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}

(10)主要是要解决从后往后拷贝的问题,关键就在于找到dest和src空间的末尾地址,方法也很巧妙,我们可以根据while(num–),当第一次进入循环num就已经-1了,这时我们让dest和src加上num,就可以得到dest和src空间的末尾地址,这时就把src指向的内容赋值给dest指向的内容,然后随着下一次num–,dest和src加上num就跟着改变了,依次类推就可以实现从后往前拷贝,如下:

	else{while (num--){*((char*)dest + num) = *((char*)src + num);}
  1. 函数代码:
#include <assert.h>void* my_memmove(void* dest, const void* src, size_t num)
{assert(dest && src);void* start = 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 start;
}
  1. 函数测试:
    在这里插入图片描述   可以看到确实实现memmove的功能了

三、memset的使用

   顾名思义,memset就是设置内存,它的作用就是一次性将num个字节全部置为某个值,使用它需要包含头文件string.h,我们来看看它的原型:

void * memset ( void * ptr, int value, size_t num );

   它的第一个参数就是要设置的数组的首元素地址,第二个参数是要设置的值,第三个参数就是要设置多少个字节
   我们要注意的是第二个参数是int类型的,所以一般不会用在浮点型当中,可以用在整型和字符型,因为字符型本质上存储的是ascll码值,也相当于整型
   所以当数组是整型数组和字符数组时,可以通过这个函数来设置它们的值
   我们首先测试一下整型数组,将所有数组的元素设置为0,如图:
在这里插入图片描述
   使用起来是不是特别方便呢?一般会用在竞赛或者项目中,需要多组输入之类的,使用完一个数组,需要把它的元素都置为0
   接下来我们想想,能不能使用这个函数将数组中的所有元素更改为1,如图:
在这里插入图片描述
   可以看到失败了,这是为什么呢?这是因为memset设置的单位是字节,而整型有4个字节,每一个字节都设置为1,这个数就很大了,我们来看看内存窗口,如图:
在这里插入图片描述
   接下来我们再来测试将字符数组全部弄成字符’x’,如图:
在这里插入图片描述
   可以看到,memset连带着\0和空格都改成了字符x,当然,如果不想\0被改掉,在写最后一个参数时可以-1
   到这里我们就讲完了memset,至于它的模拟实现,可以自行去实现,因为比较简单,只需要一个字节一个字节将对应的内容改成给出的数据即可,这里就不再赘述

四、memcmp的使用

   它跟我们学习过的strncmp有点像,strncmp可以根据给出的字节数来比较字符串的大小,而memcmp是根据给出的字节数来比较各种类型的数据的大小,使用它需要包含头文件string.h,接下来我们来看看它的原型:

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

   可以看到它和strncmp的参数一模一样,第一个参数是要比较的内容的首地址,第二个也是如此,第三个参数用来指定要比较的字节的个数,而返回值也和strncmp的规则一样,前一个大就返回大于0的数,后一个大返回小于0的数,相等则返回0
   接下来我们就用它来比较一下整型数组,如下:

#include <stdio.h>
#include <string.h>int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[] = { 1,2,3,3 };int ret = memcmp(arr1, arr2, 4 * sizeof(int));printf("经过4个整型的比较:");if (ret > 0)printf("arr1更大\n");else if (ret < 0)printf("arr2更大\n");elseprintf("arr1和arr2相等\n");return 0;
}

运行结果:
在这里插入图片描述
   在比较时,还需要注意一点,就是设置的要比较的字节数要有意义,比如arr2现在只有4个整型数据,如果要比较5个整型数据,也就是20个字节,就会读取到无效数据,所以最好保证字节数有意义
   那么它能否比较字符串呢?我们可以来测试一下:
在这里插入图片描述
   可以看到memcmp也可以比较字符串,至于memcmp的模拟实现可以自行完成,也是一个一个字节去比较,这里就不再赘述了
   我们的内存函数讲解就到这里结束了,如果有什么不懂的,欢迎在评论区提问

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

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

相关文章

【本地免费】SimpleTex 图像识别latex公式

文章目录 相关教程相关文献安装教程 由于mathpix开始收费了&#xff0c;于是本文将介绍一款目前本地免费的SimpleTex工具 相关教程 【超详细安装教程】LaTeX-OCR 图像识别latex公式&#xff08;开源免费&#xff09;_latex图片识别-CSDN博客 相关文献 SimpleTex主页——致力…

数据结构双向链表和循环链表

目录 一、循环链表二、双向链表三、循环双向链表 一、循环链表 循环链表就是首尾相接的的链表&#xff0c;就是尾节点的指针域指向头节点使整个链表形成一个循环&#xff0c;这就弥补了以前单链表无法在后面某个节点找到前面的节点&#xff0c;可以从任意一个节点找到目标节点…

5.3 克拉默法则、逆矩阵和体积

本节是使用代数而不是消元法来求解 A x b A\boldsymbol x\boldsymbol b Axb 和 A − 1 A^{-1} A−1。所有的公式都会除以 det ⁡ A \det A detA&#xff0c; A − 1 A^{-1} A−1 和 A − 1 b A^{-1}\boldsymbol b A−1b 中的每个元素都是一个行列式除以 A A A 的行列式。…

C(十一)scanf、getchar(第三弹)

问题引入&#xff1a;如何实现输入一串密码&#xff0c;如&#xff1a;“123 xxxx” &#xff0c;然后读取并确认&#xff0c;是 -- Y&#xff1b;否 -- N。 自然的&#xff0c;我们想到用scanf&#xff0c;但是在使用过程中你是否遇到跟我一样的困惑呢&#xff1f;如下&…

如何高效删除 MySQL 日志表中的历史数据?实战指南

在处理高并发的物联网平台或者其他日志密集型应用时&#xff0c;数据库中的日志表往往会迅速增长&#xff0c;数据量庞大到数百GB甚至更高&#xff0c;严重影响数据库性能。如何有效管理这些庞大的日志数据&#xff0c;特别是在不影响在线业务的情况下&#xff0c;成为了一项技…

【LeetCode HOT 100】详细题解之二叉树篇

【LeetCode HOT 100】详细题解之二叉树篇 94 二叉树的中序遍历方法一&#xff1a;递归方法二&#xff1a;迭代 104 二叉树的最大深度方法一&#xff1a;递归方法二&#xff1a;迭代 226 翻转二叉树方法一&#xff1a;递归方法二&#xff1a;迭代 101 对称二叉树方法一&#xff…

小程序-使用npm包

目录 Vant Weapp 安装 Vant 组件库 使用 Vant 组件 定制全局主题样式 API Promise化 1. 基于回调函数的异步 API 的缺点 2. 什么是 API Promise 化 3. 实现 API Promise 化 4.调用 Promise 化之后的异步 API 小程序对 npm 的支持与限制 目前&#xff0c;小程序中已经…

Java 之深入理解 String、StringBuilder、StringBuffer

前言 由于发现 String、StringBuilder、StringBuffer 面试的时候会经常问到&#xff0c;这里就顺便总结一下&#xff1a;本文重点会以这三个字符串类的性能、线程安全、存储结构这三个方面进行分析 ✨上期回顾&#xff1a;Java 哈希表 ✨目录 前言 String 介绍 String 的不可变…

全局安装cnpm并设置其使用淘宝镜像的仓库地址(地址最新版)

npm、cnpm和pnpm基本概念 首先介绍一下npm和cnpm是什么&#xff0c;顺便说一下pnpm。 npm npm&#xff08;Node Package Manager&#xff09;是Node.js的默认包管理器&#xff0c;用于安装、管理和分享JavaScript代码包。它是全球最大的开源库生态系统之一&#xff0c;提供了数…

如何使用ssm实现基于HTML的中国传统面食介绍网站的搭建+vue

TOC ssm758基于HTML的中国传统面食介绍网站的搭建vue 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔…

微服务SpringGateway解析部署使用全流程

官网地址&#xff1a; Spring Cloud Gateway 目录 1、SpringGateway简介 1、什么是网关 2、为什么用网关【为了转发】 2、应用&#xff1a; 1.启动nacos 2.创建网关项目 3.网关配置1 4.网关配置2【了解】 5.过滤器配置【了解】 1、SpringGateway简介 核心功能有三个&…

Webpack模式-Resolve-本地服务器

目录 ResolveMode配置搭本地服务器区分环境配置 Resolve 前面学习时使用了各种各样的模块依赖&#xff0c;这些模块可能来自于自己编写的代码&#xff0c;也可能来自第三方库&#xff0c;在 Webpack 中&#xff0c;resolve 是用于解析模块依赖的配置项&#xff0c;它决定了 We…

每日OJ题_牛客_DP13[NOIP2002 普及组]过河卒_路径dp_C++_Java

目录 牛客_DP13[NOIP2002 普及组]过河卒_路径dp 题目解析 C代码1 C代码2 Java代码 牛客_DP13[NOIP2002 普及组]过河卒_路径dp [NOIP2002 普及组] 过河卒_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a; 棋盘上 A点有一个过河卒&#xff0c;需要走到目标 B点。卒行走的…

业务封装与映射 -- 业务映射路径

为什么需要封装映射 OTN网络客户业务种类繁多&#xff08;例如SDH、以太网、视频&#xff09;&#xff0c;且业务大小不一&#xff08;例如STM-1、STM-4、STM-16&#xff09;&#xff0c;为了便于传输、管理客户业务&#xff0c;保证设备间互联互通&#xff0c;ITU-T定义了OTN接…

《深度学习》OpenCV 角点检测、特征提取SIFT 原理及案例解析

目录 一、角点检测 1、什么是角点检测 2、检测流程 1&#xff09;输入图像 2&#xff09;图像预处理 3&#xff09;特征提取 4&#xff09;角点检测 5&#xff09;角点定位和标记 6&#xff09;角点筛选或后处理&#xff08;可选&#xff09; 7&#xff09;输出结果 3、邻域…

物联网智能项目全面解析

目录 引言 一、物联网概述 1.1 什么是物联网 1.2 物联网的历史与发展 二、物联网智能项目分类 三、关键组件与技术 3.1 传感器和执行器 3.2 连接技术 3.3 数据处理与分析 3.4 用户界面 四、物联网智能项目案例分析 4.1 智能家居 4.2 智慧城市 4.3 工业物联网 4.4…

前端编程艺术(2)----CSS

目录 1.CSS 2.CSS引入 3.选择器 1.标签选择器 2.类选择器 3.id选择器 4.属性选择器 5.后代选择器 5.直接子元素选择器 6.伪类选择器 链接相关 动态伪类 结构化伪类 否定伪类 其他伪类 UI元素状态伪类 4.字体 1.font-family 2.font-size 3.font-style 4.fo…

C++之多线程

前言 多线程和多进程是并发编程的两个核心概念,它们在现代计算中都非常重要,尤其是在需要处理大量数据、提高程序性能和响应能力的场景中。 多线程的重要性: 资源利用率:多线程可以在单个进程中同时执行多个任务,这可以更有效地利用CPU资源,特别是在多核处理器上。 性…

奔驰EQS450suv升级增强AR抬头显示HUD案例分享

以下是奔驰 EQS450 SUV 升级增强版 AR 抬头显示的一般改装案例步骤及相关信息&#xff1a; 配件&#xff1a;通常包括显示屏、仪表模块、饰板等。 安装步骤&#xff1a; 1. 拆下中控的仪表。 2. 在仪表上预留位置切割出合适的孔位&#xff0c;用于安装显示器。 3. 将显示器…

(IDEA)spring项目导入本地jar包方法和项目打包时找不到引入本地jar包的问题解决方案

系列文章目录 文章目录 系列文章目录一、&#xff08;IDEA&#xff09;spring项目导入本地jar包方法和项目打包时找不到引入本地jar包的问题解决方案1.资料 一、&#xff08;IDEA&#xff09;spring项目导入本地jar包方法和项目打包时找不到引入本地jar包的问题解决方案 1.资料…