当前位置: 首页 > news >正文

函数扩展之——内存函数

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

本篇文章,我们将讲解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。 

 

四.总结

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

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

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

我们下期再见啦!!!

相关文章:

函数扩展之——内存函数

前言&#xff1a;小伙伴们又见面啦。 本篇文章&#xff0c;我们将讲解C语言中比较重要且常用的内存函数&#xff0c;并尝试模拟实现它们的功能。 让我们一起来学习叭。 目录 一.什么是内存函数 二.内存函数有哪些 1.memcpy &#xff08;1&#xff09;库函数memcpy &…...

【在线机器学习】River对流数据进行机器学习

River是一个用于在线机器学习的Python库。它旨在成为对流数据进行机器学习的最用户友好的库。River是crme和scikit-multiflow合并的结果。 https://github.com/online-ml/river 举个简单示例&#xff0c;将训练逻辑回归来对网站网络钓鱼数据集进行分类。下面介绍了数据集中的…...

第 4 章 串(串的块链存储实现)

1. 背景说明 该实现和链表的实现极为相似&#xff0c;只是将链接的内存拆分为具体的大小的块。 2. 示例代码 1). status.h /* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H #define STATUS_H#define CHECK_NULL(pointer) if (!(pointer)) { \printf("FuncN…...

Element表格之表头合并、单元格合并

一、合并表头 el-table配置 :header-cell-style"headFirst"headFirst({ row, colunm, rowIndex, columnIndex }) {let base { background-color: rgba(67, 137, 249, 0.3), color: #333, text-align: center };//这里为了是将第一列的表头隐藏&#xff0c;就形成了合…...

go学习-JS的encodeURIComponent转go

背景 encodeURIComponent() 函数通过将特定字符的每个实例替换成代表字符的 UTF-8 编码的一个、两个、三个或四个转义序列来编码 URI&#xff08;只有由两个“代理”字符组成的字符会被编码为四个转义序列&#xff09;。 与 encodeURI() 相比&#xff0c;此函数会编码更多的字…...

MySQL索引、事务与存储引擎

索引 事务 存储引擎 一、索引1.1 索引的概念1.2 索引的实现原理1.2 索引的作用1.3 创建索引的依据1.4 索引的分类和创建1.4.1 普通索引 index1.4.2 唯一索引 unique1.4.3 主键索引 primary key1.4.4 组合索引&#xff08;单列索引与多列索引&#xff09;1.4.5 全文索引 fulltex…...

【Spring面试】八、事务相关

文章目录 Q1、事务的四大特性是什么&#xff1f;Q2、Spring支持的事务管理类型有哪些&#xff1f;Spring事务实现方式有哪些&#xff1f;Q3、说一下Spring的事务传播行为Q4、说一下Spring的事务隔离Q5、Spring事务的实现原理Q6、Spring事务传播行为的实现原理是什么&#xff1f…...

Windows平台Qt6中UTF8与GBK文本编码互相转换、理解文本编码本质

快速答案 UTF8转GBK QString utf8_str"中UTF文"; std::string gbk_str(utf8_str.toLocal8Bit().data());GBK转UTF8 std::string gbk_str_given_by_somewhere"中GBK文"; QString utf8_strQString::fromLocal8Bit(gbk_str_given_by_somewhere.data());正文…...

【探索Linux】—— 强大的命令行工具 P.9(进程地址空间)

阅读导航 前言一、内存空间分布二、什么是进程地址空间1. 概念2. 进程地址空间的组成 三、进程地址空间的设计原理1. 基本原理2. 虚拟地址空间 概念 大小和范围 作用 虚拟地址空间的优点 3. 页表 四、为什么要有地址空间五、总结温馨提示 前言 前面我们讲了C语言的基础知识&am…...

ESP32主板-MoonESP32

产品简介 Moon-ESP32主板&#xff0c;一款以双核芯片ESP32-E为主芯片的主控板&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;低功耗&#xff0c;板载LED指示灯&#xff0c;引出所有IO端口&#xff0c;并提供多个I2C端口、SPI端口、串行端口&#xff0c;方便连接&#xff0c;…...

Python 图片处理笔记

import numpy as np import cv2 import os import matplotlib.pyplot as plt# 去除黑边框 def remove_the_blackborder(image):image cv2.imread(image) #读取图片img cv2.medianBlur(image, 5) #中值滤波&#xff0c;去除黑色边际中可能含有的噪声干扰#medianBlur( Inp…...

SpringCloud Ribbon--负载均衡 原理及应用实例

&#x1f600;前言 本篇博文是关于SpringCloud Ribbon的基本介绍&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力…...

Redis的介绍以及简单使用

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据存储系统&#xff0c;它以键值对的形式将数据存在内存中&#xff0c;并提供灵活、高性能的数据访问方式。Redis具有高速读写能力和丰富的数据结构支持&#xff0c;可以广泛应用于缓存、消息队列、实…...

ad18学习笔记十二:如何把同属性的元器件全部高亮?

1、先选择需要修改的器件的其中一个。 2、右键find similar objects&#xff0c;然后在弹出的对话框中&#xff0c;将要修改的属性后的any改为same 3、像这样勾选的话&#xff0c;能把同属性的元器件选中&#xff0c;其他器件颜色不变 注意了&#xff0c;如果这个时候&#xff…...

SpringSecurity 核心过滤器——SecurityContextPersistenceFilter

文章目录 前言过滤器介绍用户信息的存储获取用户信息存储用户信息获取用户信息 处理逻辑总结 前言 SecurityContextHolder&#xff0c;这个是一个非常基础的对象&#xff0c;存储了当前应用的上下文SecurityContext&#xff0c;而在SecurityContext可以获取Authentication对象…...

反转单链表

思路图1&#xff1a; 代码&#xff1a; struct ListNode* reverseList(struct ListNode* head){if(headNULL)//当head是空链表时 {return head; }struct ListNode* n1NULL;struct ListNode* n2head;struct ListNode* n3head->next;if(head->nextNULL)//当链表只有一个节…...

加速新药问世,药企如何利用云+网的优势?

随着计算能力的不断提高和人工智能技术的迅速发展&#xff0c;药物研发领域正迎来一场革命。云端强大的智能算法正成为药物研发企业的得力助手&#xff0c;推动着药物的精确设计和固相筛选。这使得药物设计、固相筛选以及药物制剂开发的时间大幅缩短&#xff0c;有望加速新药物…...

C++中string对象之间比较、char*之间比较

#include <cstring> //char* 使用strcmp #include <string> //string 使用compare #include <iostream> using namespace std; int main() {string stringStr1 "42";string stringStr2 "42";string stringStr3 "213";cout …...

MVVM模式理解

链接&#xff1a; MVVM框架理解及其原理实现 - 知乎 (zhihu.com) 重点&#xff1a; 1.将展示的界面窗口和创建的构件是数据进行分离 2.利用一个中间商进行数据的处理&#xff0c;所有的数据通过中间商进行处理...

js常用的数组处理方法

some 方法 用于检查数组中是否至少有一个元素满足指定条件。如果有满足条件的元素&#xff0c;返回值为 true&#xff0c;否则返回 false。 const numbers [1, 2, 3, 4, 5];const hasEvenNumber numbers.some((number) > number % 2 0); console.log(hasEvenNumber); /…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

Unity中的transform.up

2025年6月8日&#xff0c;周日下午 在Unity中&#xff0c;transform.up是Transform组件的一个属性&#xff0c;表示游戏对象在世界空间中的“上”方向&#xff08;Y轴正方向&#xff09;&#xff0c;且会随对象旋转动态变化。以下是关键点解析&#xff1a; 基本定义 transfor…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...