函数扩展之——内存函数

前言:小伙伴们又见面啦。

本篇文章,我们将讲解C语言中比较重要且常用的内存函数,并尝试模拟实现它们的功能

让我们一起来学习叭。


目录

一.什么是内存函数

二.内存函数有哪些

1.memcpy

(1)库函数memcpy

(2)模拟实现memcpy

2.memmove

(1)库函数memmove

(2)模拟实现memmove

3.memset

4.memcmp

四.总结


一.什么是内存函数

我们从这个名字不难看出,这将会是一个函数,还是一个对内存进行操作的函数。

而事实上,这其实也是一种对数据进行操作的函数

我们之前学习过:strcmp、strcpy、strlen等等,它们都是对字符进行操作的函数,统称为字符串函数。但是这些字符串函数却有着弊端,那就是它们仅仅只能操作字符串,而像整型这些其他类型的数据却无法操作。

因此我们引出了内存函数,帮助我们对各种各样类型的数据进行操作。


二.内存函数有哪些

  • memcpy
  • memmove             
  • memset
  • memcmp

和字符串操作函数类似,内存函数也是由内存的英文"memory",和后边的操作方式的英文拼接而成,同时使用它们都需要头文件#include<string.h>。

下面我们来逐个讲解这四个内存函数的具体用法。


1.memcpy

和strcpy类似,memcpy也是数据拷贝,将一个数组里的数据拷贝到另一个数组中去。

先来认识一下memcpy的函数头:

 这个函数有三个参数:

destination        代表着目的地

source        代表着源头

num        约束着我们要操作的数据数目

有没有小伙伴们知道,为什么数组指针参数的返回值以及函数的返回值都要是void*类型呢???

我们已经知道,memcpy是用来拷贝各种各样的数据类型的函数,那么它的参数就不能是单一的char*或者int*,而void*则充当一个中转站,他可以接收任意类型的数据也可以在函数内部通过强制类型转换成各种各样的数据类型

size_t是无符号整型,因为我们拷贝字符串不可能说拷贝负数个或者小数个,所以用它来接收。

而我们的源头source我们是不希望它有任何变化或者被修改的,所以要用一个const来修饰。

事实上我们这篇文章讲到的所有内存函数的参数都是如此。


(1)库函数memcpy

下面我们来看一下memory的具体用法:

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

来看这样一个简单的代码及其结果,我们用memcpy成功的实现了整型数据的拷贝。

这时候有小伙伴们会说:你这个代码不对吧,你memcpy里的数据数目为啥是20啊???

事实上,memcpy的数据数目代表的是字节数而5个整型的字节数刚好是20

那为什么是字节数而不是元素的个数呢???

下面我们就通过模拟实现一个memcpy函数来看看它的具体内部构造:

(2)模拟实现memcpy

自主实现memcpy,我们完全可以使用它本身的函数头,也就是:

void* My_memcpy(void* destination, const void* source, size_t num)

下面我们来分析怎么构造函数体:

我们知道,任何类型的数据都有它的大小,字节数基本都不相同,但是它们都有最小的单位,那就是一个字节的char类型

因此,我们将形参强制类型转换为char*类型一个字节一个字节的拷贝,是不是就能实现啦。

这时候我们就悟出来了为什么数据数目是字节数了。

下面来看具体函数体实现:

#include<stdio.h>
#include<string.h>
void* My_memcpy(void* destination, const void* source, size_t num)
{char* p = destination;while(num--){*(char*)destination = *(char*)source;destination = (char*)destination + 1;source = (char*)source + 1;}return p;
}
int main()
{int arr1[10] = { 0 };int arr2[5] = { 1,2,3,4,5 };My_memcpy(arr1, arr2, 20);for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

按我们前边分析的,将destination和source都转化为char*指针,再用while循环实现一个字节一个字节的拷贝,这样我们便能够实现对一组整型数据的拷贝了。来看结果:


这时候请小伙伴们思考一个问题,我们上边实现的是将一个数组的数据拷贝到另一组数组中

那我们能不能在一个数组的内部进行拷贝呢???

比如说我现在有一个数组:

    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

我现在要将1,2,3,4,5拷贝到3,4,5,6,7的位置去得到1,2,1,2,3,4,5,8,9,10能否实现???

事实上是不行的:

当我们第一步把1拷贝到3的位置时,3的值已经被替换成1整个数据变成了1,2,1,4,5,6,7,8,9,10

会导致我们得到1,2,1,2,1,2,1,8,9,10 这样一个结果。

这时候我们的memcpy就无法实现我们想要的结果了,于是我们引出了memmove函数

(这里要补充一点,不同的编译器memcpy的功能不完全相同,比如博主所使用的VS2019的memcpy就可以实现同一个数组元素的拷贝,有的却不行)


2.memmove

memmove的函数头和memcpy一模一样,只是函数体有些许不同。

 下面我们先来看作为库函数的memmove的使用

(1)库函数memmove

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

值得注意的一点是,我们知道数组名在一般情况下代表的是数组的首元素地址,因此我们向函数传递时,分别传递我们要进行操作的两个数列的首元素的地址便可。

例如我们上述就是将数组的1-5位拷贝到3-7位,所以就将第一位和第三位的地址作为参数传过去。

得到结果为:


 那到底怎么才能实现同一个数组内数据的相互拷贝呢???

 先来分析一下,既然我们从前往后会造成值被修改,那我们从后往前可不可以呢???

 例如我们还是将1,2,3,4,5拷贝到3,4,5,6,7上去,先将5拷贝到7,再将4拷贝到6,这样下去,确实能够完成。

 下面我们来模拟实现一下memmove的具体内部构造。


(2)模拟实现memmove

#include<stdio.h>
#include<string.h>
void* My_memmove(void* destination, const void* source, size_t num)
{char* p = destination;while (num--){*((char*)destination + num) = *((char*)source + num);}return p;
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };My_memmove(arr + 2, arr, 20);for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

要注意的是,既然是从后往前一个字节一个字节的拷贝,那么我的destination和source指针都需要指向末位,所以在解引用之前要先加上num

这样我们便实现了数组内部的拷贝。

但是这时候问题又来了如果我想将3,4,5,6,7拷贝到1,2,3,4,5上去,从后往前还管用吗???

显然,又出现被覆盖的情况,看来,我们上边的代码还有缺陷没有完全实现memmove的功能

那该怎么解决呢???

这时候我们思考一下,将前边的数据拷贝到后边要从后往前相对的将后边的数据拷贝到前边是不是要从前往后,那我们将这两者整合在一起不就好了。

只需要一个判断条件判断是将前边的数据拷贝到后边还是将后边的数据拷贝到前边不就行啦。

现在我们只需要解决一个问题,怎么判断呢???

其实这个很简单,如果是将前边的数据拷贝到后边,那么源头的地址就会比目的地小,反之,将后边的数据拷贝到前边,那么源头的地址就会比目的地小。

 这时候我们只需要比较一下源头和目的地的地址就好啦。

来看具体实现:

#include<stdio.h>
#include<string.h>
void* My_memmove(void* destination, const void* source, size_t num)
{char* p = destination;if (destination < source){while (num--){*(char*)destination = *(char*)source;destination = (char*)destination + 1;source = (char*)source + 1;}}else{while (num--){*((char*)destination + num) = *((char*)source + num);}}return p;
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };My_memmove(arr, arr + 2, 20);for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

这样我们便实现memmove的完整功能啦。 


看到这里,小伙伴们是不是都感觉非常的累,其实博主我讲到这里也是非常的累。

事实上对于上边的两个函数,我们更需要掌握他们的内部构造,而接下来要讲的剩下的两个函数就比较简单了,我们只需要知道怎么用它们就可以啦。


3.memset

这个函数叫做内存设置函数,其作用是修改内存中的若干个数据

 ptr        是我们要修改的数据起始位置

value        是我们要改为的数据

num        是我们要修改的数据数量

来看实操:

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

我们要将"hello"五个字符全改为"*",便将数组首地址,"*",5作为参数传入,得到结果如下:

 值得注意的是,这个函数也是只能一个字节一个字节的操作

#include<stdio.h>
#include<string.h>
int main()
{int arr[5] = {0};memset(arr, 1, 20);return 0;
}

 假如我要把这个整型数组的五个元素都改为1,一个字节一个字节的改要改20个,所以我们传入20,但是我们来看结果和内存:

这并不是我们想要的结果,一个字节8个bite位,所以我们实际上得到的是二进制序列

00000001 00000001 00000001 00000001,也就是十进制的16843009

所以这个函数可以说是只能跟char型的数据挂钩啦,不要轻易用在其他类型哦。


4.memcmp

既然是cmp结尾,肯定也是一个比较函数,是一个内存比较函数。

 但是这个比较函数有点特殊,它可以让你指定要比较的位置和数量,可以说是strcmp pro max。

这里值得注意的是,这个函数的返回值类型是int当前者 > 后者时,返回 1,反之返回 -1,相等则返回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,4,5 };int ret = memcmp(arr1 + 1, arr2, 20);printf("%d", ret);return 0;
}

 (arr1 + 1)便跑到了2的位置,2 > 1,自然会返回1。

使用这个函数值得注意的一点是,它只会比较第一组遇见的不相同的数据而不会比较整体的数据和的大小

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

例如上述代码,1 + 2 + 3 + 4 + 5 明显大于1 + 2 + 4 + 1 + 1但是只因为3 < 4就返回了 - 1。 

 

四.总结

终于终于,内存函数的讲解到这里就结束啦!!!

感谢各位小伙伴能够耐心的看到最后,希望博主的讲解能够帮助到你们。

本篇创作实属不易,不要忘记支持努力的博主呀,记得一键三连哦!!!

我们下期再见啦!!!

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

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

相关文章

高云FPGA系列教程(9):cmd-parser串口命令解析器移植

文章目录 [toc]cmd-parser库简介cmd-parser库源码获取GW1NSR-4C移植cmd-parser实际测试cmd-parse命令解析器优化 本文是高云FPGA系列教程的第9篇文章。 上一篇文章介绍片上ARM Cortex-M3硬核处理器串口外设的使用&#xff0c;演示轮询方式和中断方式接收串口数据&#xff0c;并…

GLTF编辑器如何快速重置模型原点

1、什么是模型原点&#xff1f; 模型原点是三维建模中的概念&#xff0c;它是指在一个虚拟三维空间中确定的参考点。模型原点通常位于模型的几何中心或基本组件的中心位置。如图所示&#xff1a; 可以看到模型的原点在模型的几何中心 2、模型原点的作用 知道了什么是模型原点&…

可转债实战与案例分析——成功的和失败的可转债投资案例、教训与经验分享

实战与案例分析——投资案例研究 股票量化程序化自动交易接口 一、成功的可转债投资案例 成功的可转债投资案例提供了有价值的经验教训&#xff0c;以下是一个典型的成功案例&#xff1a; 案例&#xff1a;投资者B的成功可转债投资 投资者B是一位懂得风险管理的投资者&#…

idea如何关闭项目文件显示的浏览器图标

这里写自定义目录标题 1.idea经常项目文件右上角弹出图标2.setting中Tools 取消勾选浏览器 1.idea经常项目文件右上角弹出图标 2.setting中Tools 取消勾选浏览器

Zabbix

Zabbix简介 ●zabbix 是一个基于 Web 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。 ●zabbix 能监视各种网络参数&#xff0c;保证服务器系统的安全运营&#xff1b;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 ●zabbix 由 2 部…

创造您梦寐以求的家居设计——Live Home 3D Pro for Mac

您是否曾经想象过在舒适的家中展现自己独特的风格&#xff1f;现在&#xff0c;您可以通过Live Home 3D Pro for Mac来实现您的家居设计梦想&#xff01;这款强大的3D家居设计软件将带给您无限的创作可能性。 Live Home 3D Pro for Mac是一款专业级的家居设计软件&#xff0c;…

Visual Studio 更新:远程文件管理器

Visual Studio 中的远程文件管理器可以用来访问远程机器上的文件和文件夹&#xff0c;通过 Visual Studio 自带的连接管理器&#xff0c;可以实现不离开开发环境直接访问远程系统&#xff0c;这确实十分方便。 自从此功能发布以来&#xff0c;VS 开发团队努力工作&#xff0c;…

基于STC15单片机电子时钟液晶1602串口显示-proteus仿真-源程序

一、系统方案 1、本设计采用STC15单片机作为主控器。 2、液晶1602显示电子时钟。 3、串口显示电子时钟。 4、按键控制开启暂停清零。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 uint count0; uint8 strPhoto[8]; uint wendu0;P3M0 0x…

如何选择适合爬虫的动态住宅套餐

在当今互联网时代&#xff0c;爬虫已经成为了一项非常重要的技术。爬虫技术可以帮助企业和个人获取大量的数据&#xff0c;从而进行数据分析和决策。但是&#xff0c;要想让爬虫技术发挥最大的作用&#xff0c;就需要选择一款动态住宅套餐。那么&#xff0c;在选择动态住宅套餐…

1.3python基础语法——PyCharm

1&#xff09;PyCharm的作用 python的集成开发环境&#xff0c;功能如下&#xff1a; Project管理 智能提示 语法高亮 代码跳转 调试代码 解释代码(解释器) 框架和库 2&#xff09;下载与安装 下载地址&#xff1a;http://www.jetbrains.com/pycharm/download/#sectionwind…

进阶指针(一)

✨博客主页&#xff1a;小钱编程成长记 &#x1f388;博客专栏&#xff1a;进阶C语言 进阶指针&#xff08;一&#xff09; 0.回顾初阶指针1.字符指针1.1 相关面试题 2.数组指针3.指针数组3.1 数组指针的定义3.2 &数组名VS数组名3.3 数组指针的使用 4.数组传参和指针传参4.…

C# Onnx Yolov8 Cls 分类

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System…

Jenkins学习笔记1

CI 服务器&#xff1a; 认识Jenkins&#xff1a; Jenkins是一个可扩展的持续集成&#xff08;CI&#xff09;引擎&#xff0c;是一个开源项目&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使得软件持续集成变成可能。Jenkins非常易于安装和配置&#xff0c;简单易…

什么是SVG(可缩放矢量图形)?它与普通图像格式有何不同?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是SVG&#xff1f;⭐ 与普通图像格式的不同⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚…

Tomcat多实例+Nginx动静分离、负载均衡

这里写目录标题 Tomcat多实例动静分离、负载均衡一、Tomcat多实例部署1、安装JDK2、安装启动tomcat 二、NginxTomcat负载均衡、动静分离1、Nginx负载均衡实现原理1.1 原理1.2 Nginx配置反向代理的主要参数 2、Nginx动静分离实现原理2.1 原理2.2 Nginx静态处理优势 3、动静分离配…

jvm-sandbox-repeater源码解析-配置管理

一、配置初见 源码里提供的控制台截图如下&#xff1a;&#xff08;怎么搭建自己去百度&#xff09; 从中取出对应的配置如下&#xff1a; { "degrade": false, //阻断能力 "exceptionThreshold": 1000, //异常采样率 "httpEntrancePatterns&qu…

工业检测 ocr

采用OpenCV和深度学习的钢印识别_菲斯奇的博客-CSDN博客采用OpenCV和深度学习的钢印识别[这个帖子标题党了很久&#xff0c;大概9月初立贴&#xff0c;本来以为比较好做&#xff0c;后来有事情耽搁了&#xff0c;直到现在才有了一些拿得出手的东西。肯定不会太监的。好&#xf…

2023.9.19 关于 数据链路层 和 DNS 协议 基本知识

目录 数据链路层 MTU DNS 协议 补充 DHCP协议 数据链路层 基本概念&#xff1a; 考虑相邻两个节点之间的传输&#xff08;通过 网线 / 光纤 / 无线 直接相连的两个设备&#xff09;以太网协议 规定了 数据链路层 和 物理层 的内容 IP地址 与 mac地址 的相互配合 IP地址 描…

vue的模板语法(下篇)

目录 一.事件处理 二.表单的综合案例 三.组件通信⭐⭐ 3.1 自定义组件 3.2 组件通信之父传子 3.3组件通信之子传父 一.事件处理 Vue通过由点(.)表示的指令后缀来调用修饰符&#xff0c; .stop .prevent .capture .self .once 如下&#xff1a; 阻止单击事件冒泡 <a v-on…

踩坑:Invalid character found in method name. HTTP method names must be tokens

一、原因 在进行本地小程序与服务端请求时,由于加了签名认证,访问接口时报错 Spring boot端 小程序端 二、解决方案 2.1 更改访问路径 将https:更换成http: 示例:https://localhost:8080 改为 http://localhost:8080 2.2其他原因 ssl证书到期了Tomcat的header缓冲区大小不…