当前位置: 首页 > 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); /…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...