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

C:内存函数

目录

前言:

一、memcpy 函数的使用及实现

1、memcpy函数的介绍

 1.1 memcpy函数参数解读

 2、memcpy函数的使用

3、memcpy函数的模拟实现

二、memmove函数的使用及模拟

1、memmove函数的使用

2、memmove函数的模拟实现

三、memset 函数的使用

1、memset函数的介绍(cplusplus)

2、memset函数的使用

2.1 memset函数对数组的应用

2.2 memset函数对字符串的应用

​编辑

 四、memcmp函数的使用

1、memcmp函数的介绍

2、memcmp函数的使用


前言:

上篇文章介绍了C语言字符串函数,我们学会了一些对字符串的操作,比如说将字符串从一个字符数组拷贝到另一个字符数组中,我们可以通过使用strcpy函数实现。但是,如果我们想要拷贝一个整型数组到另一个整型数组中时,strcpy函数就失效了,那我们应该怎么才能实现这个操作呢?不要着急,本篇文章将带大家搞定这个问题。

一、memcpy函数的使用及实现

1、memcpy函数的介绍

cplusplus上的介绍:

作用介绍:

参数介绍:

返回值介绍:

 1.1 memcpy函数参数解读

(1)void* destination

该参数的作用是目标空间,用来存放将要拷贝的内存,为什么返回值是 void* 呢?这是因为这个函数的作用是内存拷贝,既然是内存拷贝,内存中又可能存放的是整型数组,也有可能存放的是字符数组……,因此我们不关心存放数据的类型,因此使用void*指针来接收任意类型的数据的地址。

(2)const void* source

source是源头,也就是要拷贝的内存数据,这里也是void*指针是因为我们不知道我们未来要拷贝的数据是什么类型的,可能是整型,可能是字符,也可能是结构体,因此我们也使用void*。

用const修饰是因为我们不希望要拷贝的数据被修改,因此使用const修饰会使得整个工程更加稳定。

(3)size_t num

num的作用是限定拷贝的字节数,比如说source中有十个字节的数据,我们可以通过修改num的值来拷贝我们想要的个数,num为5,我们就拷贝五个字节的数据到destination中;num的类型是size_t的原因是我们拷贝的个数最低都是0个,不会出现负数的情况,因此使用size_t类型最为合适。

 2、memcpy函数的使用

前面学习了memcpy函数,接下来我们将使用memcpy函数来实现一些操作

比如说我们打算将整型数组arr1[ ] = {1,2,3,4,5,6,7,8,9,10}拷贝到整型数组 arr2[10] = { 0 };

#include <stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[10] = { 0 };memcpy(arr2, arr1, 5 * sizeof(int));return 0;
}

调试结果:

源头从 3 开始拷贝,比如说:

#include <stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[10] = { 0 };memcpy(arr2, arr1+2, 5 * sizeof(int));return 0;
}

 一些总结:

  • 函数memcpy从source的位置开始向后赋值num个字节的数据到destination指向的内容;
  • 这个函数在遇到\0的时候并不会停下来,与字符串函数不同;
  • 如果source和destination有任何的重叠,赋值的结果都是未定义的。

3、memcpy函数的模拟实现

接下来我们尝试自己写一个函数来实现memcpy的功能

void* my_memcpy(void* dest, const void* src, size_t num)
{}
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[10] = { 0 };my_memcpy(arr2, arr1, 5 * sizeof(int));return 0;
}

注意:在my_memcpy中,源头是void*指针类型的  

现在有一个问题,我们不知道我们要拷贝的是内容是什么类型的,我们只知道要拷贝的是20个字节,我们该怎么将这20个字节拿到arr2中呢?

在前面学习qsort函数的模拟实现中,我们用到了一个方法,我们可以一个字节一个字节的拷贝,那么就可以使用强制类型转换将void*指针转换为char*指针

void* my_memcpy(void* dest, const void* src, size_t num)
{while (num--)//num是字节总数,因此num每减1,就拷贝一个字节{*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}
}

调试结果: 

总代码:

#include <stdio.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{// 保存目标地址,以便最后返回void* ret = dest;// 循环 num 次进行逐个字节的复制while (num--){// 将源地址指向的内容复制到目标地址指向的位置,并转换为 char* 类型进行操作,确保每次只复制一个字节*(char*)dest = *(char*)src;// 目标地址向后移动一个字节dest = (char*)dest + 1;// 源地址向后移动一个字节src = (char*)src + 1;}// 返回复制后的目标地址return ret;
}
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[10] = { 0 };my_memcpy(arr2, arr1, 5 * sizeof(int));return 0;
}

如果我们向将int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };中(1,2,3,4,5)拷贝放到(3,4,5,6,7)的位置上,这样可以实现吗?

	my_memcpy(arr1+2, arr1, 5 * sizeof(int));

调试结果:

为什么这里会是(1,2,1,2,1)呢?其实也很好理解

 因此最终结果是(1,2,1,2,1,2,1,8,9,10) 

关于重叠问题,我们一般使用后面的这个函数memmove函数

而memcpy函数一般用来处理不重叠情况。

在vs2022中,memcpy的能力是比较强的,也是可以用来处理重叠问题,但是对于memcpy函数,本来的作用是不包括处理重叠的问题的,这就像是老师说让你考到60分就行,但是你能考100分。但是不能保证在所有的编译器上memcpy都可以考到100分

也就是说无法确定其它编程环境是否可以实现,因此,如果要处理重叠问题,最好还是交给memmove函数.

二、memmove函数的使用及模拟

1、memmove函数的使用

memove函数的使用与memcpy函数是一样的,也是用来实现内存中数据的拷贝的,因此就不详细介绍了。不过前面也说了memmove函数可以实现重叠拷贝,来测试一下

#include <stdio.h>
#include <string.h>
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };memmove(arr1+2, arr1, 5 * sizeof(int));return 0;
}

调试结果:
 可以看到的确将(1,2,3,4,5)的内容拷贝到(3,4,5,6,7)的位置上了

那么memmove函数究竟是怎么实现这个操作的呢?我们来模拟了解一下

2、memmove函数的模拟实现

前面我们知道,如果拷贝1,会把3给覆盖,拷贝2,会把4给覆盖。

该怎么拷贝才能实现不被覆盖呢?

可以从后向前拷贝,先拷贝5,覆盖7,在拷贝4覆盖6,这时候在拷贝3覆盖5,拷贝2覆盖4,拷贝1覆盖3,由于3,4,5已经拷贝完成,不会出现还没有拷贝就被覆盖的情况。

那是不是从后向前拷贝就一定正确呢?

我们在换一种情况试试:

这时候如果还是从后向前拷贝的话会出现什么问题呢?

8拷贝到6,7拷贝到5,这时候向拷贝6的时候已经变成了8,因此从后向前失效了。

这时候我们在从前向后拷贝,3拷贝到1,4拷贝到2……恰好可以全部拷贝。

不知道大家有没有发现一个规律:

如果dest在src的后面,则从后向前拷贝;

如果dest在src的前面,则从前向后拷贝;

如果没有重叠,则随意。

如果是 后->前,该怎么拷贝呢?

比如说先拷贝5,我们只需要在起始位置跳过num个字节即可

比如说:*((char*)src + num)

代码实现:

#include <stdio.h>
#include <string.h>
void* my_memmove(void* dest, const void* src, size_t num)
{void* ret = dest;if (dest < src){//前->后while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{while (num--){//后->前//num进来减1,变为19,src加上19后跳到最后一位上,也就是5,dest加上19跳到8的位置,然后将5赋值到8的位置*((char*)dest + num) = *((char*)src + num);}}return ret;
}
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };my_memmove(arr+2, arr, 5 * sizeof(int));return 0;
}

三、memset函数的使用

1、memset函数的介绍(cplusplus)

参数介绍:
注意:

memset是以字节为单位来设置内存的 ,而不是以一个元素为单位设置的。

 作用介绍:

返回值介绍:

2、memset函数的使用

2.1 memset函数对数组的应用

那么memset函数究竟有什么作用呢?

比如说:

#include <stdio.h>
int main()
{int arr[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){arr[i] = i + 1;}return 0;
}

我们想将arr数组全部初始化为0,我们该怎么做呢?

你可能会说这不简单?直接使用循环不就可以了吗?

for (i = 0; i < 10; i++)
{arr[i] = 0;
}

这样的确可以,不过我们也可以使用库函数memset函数来实现这个操作。

我们要设置的这个空间整型数组arr[10]的地址交给ptr,而数组的地址就是数组名arr,我们需要将该数组的元素都变为0,也就是要设置的值value为0,由于是整型数组,有十个元素,所以num就等于40字节。

代码展示:

#include <stdio.h>
#include <string.h>
int main()
{int arr[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){arr[i] = i + 1;}/*for (i = 0; i < 10; i++){arr[i] = 0;}*/memset(arr, 0, 10 * sizeof(int));for (i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

调试过程:

结果展示:

 如果memset(arr, 1, 10 * sizeof(int));这是否是将每一个元素都改为1了呢?

调试监控窗口

为什么没有达到想要的结果呢?

我们在来看一下内存窗口:

破案了,memset函数将每一个字节都设置为1,而不是把一个元素设置为1。

前面强调了memset是以字节为单位来设置内存的 ,而不是以一个元素为单位设置的。

2.2 memset函数对字符串的应用

代码:

#include <stdio.h>
#include <string.h>
int main()
{char arr[] = "hello world";//如何将helo改为五个xmemset(arr, 'x', 5);return 0;
}

调试结果:

如果我们想修改world呢?

memset(arr+6, 'x', 5);

从前向后数hello五个字符,还有一个空格,共6个字符。

注意:这个函数比较常见,因此需要熟练掌握!!! 

 四、memcmp函数的使用

memcmp函数与之前学习的strcmp函数的功能是比较相似的,不过strcmp函数只能用来做字符串的比较,而memcmp函数是用来做内存块的比较,不论是什么类型。

1、memcmp函数的介绍

参数介绍:
 

返回值介绍:

2、memcmp函数的使用

直接上例题,比较arr1与arr2中前3个元素

#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, 12);printf("%d\n", ret);return 0;
}

结果:

如果我们比较前4个元素呢?

int ret = memcmp(arr1, arr2, 16);、

这里返回的就是1了。


结语:本篇文章到这里就结束啦!期待下次的相遇!!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • JVM-类加载器的双亲委派模型详解
  • 在C#中使用NPOI将表格中的数据导入excel中
  • 信息安全数学基础(15)欧拉定理
  • 第二十九章 添加数字签名 - 指定 KeyInfo 的规范化方法
  • 【Kubernetes】常见面试题汇总(二十七)
  • 【原创 架构设计】多级缓存的应用、常见问题与解决方式
  • 【无人机设计与控制】基于蜣螂优化算法求解多无人机集群路径规划问题
  • 【波束管理】
  • leetcode第80题:删除有序数组的重复项(||)
  • MatrixOne助力一道创新打造高性能智能制造AIOT系统
  • java进程与线程
  • 基于单片机无线智能报警系统的设计
  • 华为昇腾智算中心-智算中心测试方案与标准
  • Java Integer 缓存机制:小镇的居民与大城市的拥堵
  • python内置模块pathlib.Path类操作目录和文件
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • conda常用的命令
  • CSS 提示工具(Tooltip)
  • es6要点
  • Hibernate【inverse和cascade属性】知识要点
  • HTTP中的ETag在移动客户端的应用
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • leetcode-27. Remove Element
  • mysql常用命令汇总
  • nfs客户端进程变D,延伸linux的lock
  • Promise面试题2实现异步串行执行
  • Python打包系统简单入门
  • quasar-framework cnodejs社区
  • Redash本地开发环境搭建
  • Ruby 2.x 源代码分析:扩展 概述
  • Twitter赢在开放,三年创造奇迹
  • 阿里云前端周刊 - 第 26 期
  • 百度地图API标注+时间轴组件
  • 飞驰在Mesos的涡轮引擎上
  • 应用生命周期终极 DevOps 工具包
  • 由插件封装引出的一丢丢思考
  • 原生 js 实现移动端 Touch 滑动反弹
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​MySQL主从复制一致性检测
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • # 利刃出鞘_Tomcat 核心原理解析(七)
  • #if和#ifdef区别
  • #ubuntu# #git# repository git config --global --add safe.directory
  • $.ajax()方法详解
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第6节 (嵌套的Finally代码块)
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (HAL库版)freeRTOS移植STMF103
  • (笔试题)分解质因式
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (三)mysql_MYSQL(三)
  • (十七)Flink 容错机制
  • (四)linux文件内容查看
  • (图)IntelliTrace Tools 跟踪云端程序
  • (五)c52学习之旅-静态数码管