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

C语言笔记第10篇:内存函数

上一篇的字符串函数只是针对字符串的函数,而内存函数是针对内存块的,不在乎内存中存储的数据!这就是字符串函数和内存函数的区别。

准备好爆米花,正片开始

1、memcpy的使用和模拟实现

memcpy库函数的功能:任意类型数组的拷贝

memcpy的函数声明:

void* memcpy(void* destination, const void* source, size_t num);

destination是目标空间,source是源,size_t num是拷贝字节的个数。

为什么还有输入拷贝字节个数呢?

因为memcpy可以拷贝任意类型的数组,可以是字符,可以是int,也可以是struct自定义类型的,但是前提是要输入要拷贝的字节个数,因为传过去的地址被void类型的指针接收,所以不能得知元素大小。

memcpy函数的调用:

#include <stdlib.h>
#include <stdio.h>
int main()
{int arr1[10] = { 0 };int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };memcpy(arr1, arr2, 20);//向拷贝20个字节也就是5个int类型大小的arr2元素到arr1数组中int i = 0;for (i = 0; i < 5; i++){printf("%d ", arr1[i]);}return 0;
}

memcpy函数的模拟实现:

#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* source, size_t num)
{assert(dest && source);void* ret = dest;while (num--){*(char*)dest = *(char*)source;dest = (char*)dest + 1;source = (char*)source + 1;}return ret;
}
int main()
{int arr1[10] = { 0 };int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };my_memcpy(arr1, arr2, 20);//向拷贝20个字节也就是5个int类型大小的arr2元素到arr1数组中int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

但是这个函数有一个缺点,就是不能重叠内存拷贝,什么意思呢?就是不能在同一个数组中拷贝,会导致打印信息不正确,例如:

#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* source, size_t num)
{assert(dest && source);void* ret = dest;while (num--){*(char*)dest = *(char*)source;dest = (char*)dest + 1;source = (char*)source + 1;}return ret;
}
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };my_memcpy(arr+2, arr, 20);更改位置int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

我想将arr+2也就是第3个元素的位置开始,拷贝1-5,我们想象的答案是1 2 1 2 3 4 5 8 9 10,但是实际上的答案是1 2 1 2 1 2 1 8 9 10,因为是从前往后开始拷贝,拷贝信息和拷贝的位置重叠了,导致拷贝时更改了拷贝信息,打印出的结果有所差异。

那怎么办?其实还有memmove函数,他和memcpy的拷贝一样,任意类型都可以拷贝,不同的是memmove可以处理重叠内存拷贝。

2、memmove的使用和模拟实现

memmove库函数功能:拷贝任意类型的数组,也可以处理重叠内存拷贝问题

memmove函数的声明:

void* memmove(void* destination, const void* source, size_t num);

可以看到memmove和memcpy的返回类型和参数一模一样,唯一不同的只是memmove函数的实现细节

memmove函数的调用:

#include <stdlib.h>
#include <stdio.h>
int main()
{int arr1[10] = { 0 };int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };memmove(arr1, arr2, 20);//向拷贝20个字节也就是5个int类型大小的arr2元素到arr1数组中int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}

memmove究竟是如何处理拷贝重叠的的呢?请继续往下看

memmove函数的模拟实现:

#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* source, size_t num)
{assert(dest && source);void* ret = dest;if (dest < source)//如果拷贝的地址小于拷贝信息的地址就可以从前向后拷贝{while (num--){*(char*)dest = *(char*)source;//从前向后拷贝dest = (char*)dest + 1;source = (char*)source + 1;}}else//如果拷贝的地址大于或等于拷贝信息的地址就从后向前拷贝{while (num--){*((char*)dest + num) = *((char*)source + num);//从后向前拷贝}}return ret;
}
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };my_memmove(arr + 2, arr, 20);int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

memmove模拟实现逻辑:

我们可以使用地址于地址之间的关系运算,简单概述就是两个地址之间比较大小。因为该函数是排序数组的,数组又是连续存放的,所以可以比较两个地址。如果目标空间地址比源地址大,就从后往前拷贝。如果目标空间地址比源地址小,就可以从前往后拷贝。

3、memset的使用和模拟实现

memory - 记忆(内存),set - 设置。memset就是内存设置的意思。

memset库函数功能:将参数ptr的前num个字节设置成指定的value值。

memset的函数声明:

void* memset(void* ptr, int value, size_t num);

比如我有一个字符数组,字符串是 " hello world " ,我想把它改成 " hello xxxxx",那我们就可以使用memset函数。

memset函数的调用:

#include <stdio.h>
#include <stdlib.h>
int main()
{char str[] = "hello world";memset(str + 6, 'x', 5);//参数1:字符数组下标6的位置 参数2:需替换的的源值 参数3:字节为单位,向后拷贝的字节大小printf("%s\n", str);//打印 "hello xxxxx"return 0;
}

打印结果确实是 " hello xxxxx ",但是我们也可以用它来改变整型数组:

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

我们想象的是改变前20个字节也就是前5个整型元素,打印为:1,1,1,1,1,6,7,8,9,10,但实际上却却是以每个字节更改为01,并不是我们想象的改为五个1,如下图:

所以你想让它的每个字节都是1是可以做的到的,但是你想让它每个整型都是1这个是做不到的,memset本身就是以字节为单位进行设置的。前面的memcpy和memmove虽然也是以字节为单位来拷贝的,但是它们两边都是在变化着拷贝的,所以能够拷贝正确答案。而这个需拷贝的源始终都是一个值,这个值是不会变化的,每次拷贝一个字节都从这里面的一个字节拷贝到另一个空间。

memset的模拟实现:

#include <stdio.h>
#include <stdlib.h>
void my_memset(void* str, int value, size_t num)
{assert(str != NULL);void* ret = str;while (num--){*(char*)str = (char)value;str = (char*)str + 1;}return ret;
}
int main()
{char str[] = "hello world";int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);my_memset(str+6, 'x', 5);my_memset(arr, 1, 20);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");printf("%s\n", str);return 0;
}

4、memcmp的使用和模拟实现

memcmp库函数的功能:和strncmp的功能一样,strncmp是比较两个字符串的,memcmp是比较两个数组内存的

memcmp函数的声明:

int memcmp(void* ptr1, void* ptr2, size_t num);

memcmp返回值:如果ptr1比ptr2大就返回大于0的数字,如果ptr1比ptr2小就返回小于0的数字,如果相等就返回0

memcmp函数的调用:

#include <stdio.h>
#include <stdlib.h>
int main()
{int arr1[] = { 1,4,3,4,5 };int arr2[] = { 1,3,5,7,9 };int ret = memcmp(arr1, arr2, 5);printf("%d\n", ret);return 0;
}

memcmp函数的模拟实现:

#include <stdio.h>
#include <stdlib.h>
int my_memcmp(void* ptr1, void* ptr2, size_t num)
{assert(ptr1 && ptr2);while (num--){if (*(char*)ptr1 != *(char*)ptr2){return *(char*)ptr1 - *(char*)ptr2;}ptr1 = (char*)ptr1 + 1;ptr2 = (char*)ptr2 + 1;}return 0;
}
int main()
{int arr1[] = { 1,3,3,4,5 };int arr2[] = { 1,4,5,7,9 };int ret = my_memcmp(arr1, arr2, 5);printf("%d\n", ret);return 0;
}

                                                              end

相关文章:

  • 【动手学深度学习】卷积神经网络(AlexNet)的研究详情
  • Java——Stream流(2/2):Stream流的中间方法、终结方法(方法、案例演示)
  • 掌握Django文件处理:一步步构建上传功能
  • 安全生产新篇章:可燃气体报警器检验周期的国家标准解读
  • 正则表达式 0.1v
  • Spring AI 第二讲 之 Chat Model API 第七节Mistral AI Chat
  • Docker:定义未来的软件部署
  • JVM之【字节码/Class文件/ClassFile 内容解析】
  • 【C语言之排序】-------六大排序
  • LabVIEW软件开发人员如何在软件开发中捕捉需求?
  • 前端 Web 与原生应用端 WebView 通信交互 - HarmonyOS Next
  • “Kubectl 如何工作案例:编写自定义 Kubectl 命令
  • 【机器学习】基于OpenCV和TensorFlow的MobileNetV2模型的物种识别与个体相似度分析
  • Spring Boot项目中,如何在yml配置文件中读取maven pom.xml文件中的properties标签下的属性值
  • 27、matlab傅里叶变换:fft()函数
  • axios 和 cookie 的那些事
  • conda常用的命令
  • eclipse的离线汉化
  • gf框架之分页模块(五) - 自定义分页
  • JS数组方法汇总
  • Linux各目录及每个目录的详细介绍
  • Node 版本管理
  • vue:响应原理
  • 初识 webpack
  • 从零开始学习部署
  • 浮现式设计
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 我看到的前端
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 译自由幺半群
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • 树莓派用上kodexplorer也能玩成私有网盘
  • ​​​​​​​​​​​​​​Γ函数
  • !!Dom4j 学习笔记
  • #Linux(帮助手册)
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (3)(3.5) 遥测无线电区域条例
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (二)WCF的Binding模型
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (汇总)os模块以及shutil模块对文件的操作
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (三)终结任务
  • (十六)一篇文章学会Java的常用API
  • (已解决)什么是vue导航守卫
  • (转)项目管理杂谈-我所期望的新人
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .net framework4与其client profile版本的区别
  • .Net FrameWork总结
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .Net MVC4 上传大文件,并保存表单
  • .net 反编译_.net反编译的相关问题
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)