函数扩展之——内存函数

news2024/9/27 19:27:14

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

本篇文章,我们将讲解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.coloradmin.cn/o/1026284.html

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

相关文章

交换机端口镜像详解

交换机端口镜像是一种网络监控技术&#xff0c;它允许将一个或多个交换机端口的网络流量复制并重定向到另一个端口上&#xff0c;以便进行流量监测、分析和记录。通过端口镜像&#xff0c;管理员可以实时查看特定端口上的流量&#xff0c;以进行网络故障排查、安全审计和性能优…

已解决 Microservice Error: Circuit Breaker: Service is temporarily unavailable

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

【操作系统】聊聊磁盘IO是如何工作的

磁盘 机械磁盘 主要是由盘片和读写磁头组成。数据存储在盘片的的环状磁道上&#xff0c;读写数据前需要移动磁头&#xff0c;先找到对应的磁道&#xff0c;然后才可以访问数据。 如果数据都在同一磁道上&#xff0c;不需要在进行切换磁道&#xff0c;这就是连续IO&#xff0c;可…

离散数学之 一阶逻辑等值演算与推理

一阶逻辑等值式与置换规则 基本等值式 这里用到了量词辖域的收缩 未完待续

电工三级证(高级)实战项目:PLC控制步进电机正反转

实训目的 了解使用PLC代替传统继电器控制回路的方法及编程技巧&#xff0c;理解并掌握步进电动机的运行方式及其实现方法。通过实验进一步加深理解步进电机控制的特点以及在实际中的应用。 控制要求 PLC设备:Siemens S7-200 要求:打开开关K0(I0.0)得电&#xff0c;启动PLC程…

【xshell和xftp连接Ubuntu教程】

一、下载xshell和xftp 下载地址 https://www.xshell.com/zh/free-for-home-school/ 二、连接xshell 输入ip&#xff0c;端口号 输入用户名&#xff0c;密码 出现这个使用就行了 三、连接xftp 同上&#xff0c;输入ip&#xff0c;端口&#xff0c;用户名&#xff0c;密码 连接成…

拓扑关系如何管理?

在设备对接涂鸦的云端过程中&#xff0c;一部分设备由于自身资源或硬件配置&#xff0c;无法直接连接云端。而是需要通过网关进行中转&#xff0c;由网关代理实现和云端进行数据交互&#xff0c;间接实现设备接入云端。这样的设备也称为子设备。 要想实现网关代理子设备接入云…

C++跳坑记:位移超出范围的处理

在C编程中&#xff0c;数据类型的选择不仅影响内存占用和性能&#xff0c;还可以对某些操作的结果产生意想不到的影响。今天&#xff0c;我将分享一个关于C在不同变量类型下位移操作结果的发现。 位移操作是C中常见的对整数的高效操作之一。然而&#xff0c;我们可能会忽视一个…

单播与多播mac地址

MAC 地址&#xff08;Media Access Control Address&#xff09;是一个用于识别网络设备的唯一标识符。每个网络设备都有一个独特的 MAC 地址&#xff0c;用于在局域网中进行通信。 单播MAC地址&#xff1a;单播MAC地址用于单播通信&#xff0c;即一对一的通信模式。当设备发送…

day4_QT

day4_QT qt绘制钟表 qt绘制钟表 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->resize(1000,1000);this->setStyleSheet("background-color:…

Word中对象方法(Methods)的理解及示例(下)

【分享成果&#xff0c;随喜正能量】当你的见识多了&#xff0c;眼界宽了&#xff0c;格局大了&#xff0c;所有的磨难都将不再是磨难&#xff0c;而是助你成长的阶梯。 。 《VBA之Word应用》&#xff08;10178982&#xff09;&#xff0c;是我推出第八套教程&#xff0c;教程…

pnpm入门教程

一、概述 1、更小 使用 npm 时&#xff0c;依赖每次被不同的项目使用&#xff0c;都会重复安装一次。 而在使用 pnpm 时&#xff0c;依赖会被存储在内容可寻址的存储中。 2、更快 依赖解析。 仓库中没有的依赖都被识别并获取到仓库。目录结构计算。 node_modules 目录结构是…

编程(47)----------Spring AOP

AOP是Spring中, 个人认为较为抽象的一个思想. 一般来说, 学习一个新东西, 第一件事是先看看这个知识点的定义是什么. 同时要注意, 同一事物的定义可以有很多, 毕竟定义没有绝对的对与错, 只有准确与否. 而初次接触AOP的定义, 第一感觉可能就是抽象, 或者说看不懂, 这里面也有…

刷题日记——将x减到0的最小操作数

将x减到0的最小操作数 题目链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/ 题目解读 题目要求移除元素总和等于参数x&#xff0c;这道题给我的第一感觉就是从数组的两边入手&#xff0c;对数据进行加和删除&#xff0c;但是这里有一…

SVN状态图标不显示

问题可能点1&#xff1a;图标覆盖 1、右键找到设置 2、找到图标覆盖 3、重启TortoiseSVN 问题可能点2&#xff1a;注册表图标顺序太靠下&#xff0c;被占用 1、windowsr, 输入regedit进入注册表 2、找到一下目录 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Cu…

servlet中doGet方法无法读取body中的数据

servlet中doGet方法不支持读取body中的数据。

警惕!多本SCI/SSCI被剔除,9月SCI/SSCI期刊目录已更新~(附下载)

【SciencePub学术】 2023年9月20日&#xff0c;科睿唯安更新了Web of Science核心期刊目录。 继上次SCI期刊目录和SSCI期刊目录更新之后&#xff0c;本次9月更新共有9本期刊发生变动&#xff1a; • SCIE&#xff1a;有3本期刊不再被SCIE期刊目录收录(Editorial De-listing/Pr…

Python 之 shadow 爆破密码脚本编写

文章目录 Linux shadow 爆破脚本Linux shadow 爆破初探Linux shadow 爆破进阶 Linux shadow 爆破脚本 Linux shadow 爆破初探 目的是为了明白其shadow爆破原理 # Linux shadow爆破初探 1import crypt#shadow文件中的一条用户数据 shadow_line "ghui:$y$j9T$DQ2d2fD138…

(JavaEE)(多线程案例)线程池 (简单介绍了工厂模式)(含经典面试题ThreadPoolExector构造方法)

线程诞生的意义&#xff0c;是因为进程的创建/销毁&#xff0c;太重了&#xff08;比较慢&#xff09;&#xff0c;虽然和进程比&#xff0c;线程更快了&#xff0c;但是如果进一步提高线程创建销毁的频率&#xff0c;线程的开销就不能忽视了。 这时候我们就要找一些其他的办法…

基于微信小程序的个人健康管理系统的设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…