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

C语言第十章内存函数

一. memcpy函数的使用和模拟实现

1.函数的使用

        该函数和上一章节学到的strcpy比较相似,本章节是通过内存来实现数据的复制,而上章节的strcpy函数主要是通过数据本身实现复制操作。

        根据上述图片可以得到,该函数的函数参数为void *类型。实现了泛型编程。可以接收不同类型的数据。函数返回值为void*类型,强制类型转换之后就可以得到目标类型的函数返回值了。

        函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。这个函数在遇到 '\0' 的时候并不会停下来,因为该函数本质上使用的是内存进行数据的复制,而不是读取内容从而实现数据的复制。如果source和destination有任何的重叠,就不可以达到预期效果,复制的结果都是未定义的(具体详见模拟实现的说模拟说明)。

        下面举一个该函数使用的例子:

#include <stdio.h>
#include <string.h>    //该函数需要的头文件
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };    //创建两个整型数组,用于接下来的内存复制int arr2[10] = { 0 };memcpy(arr2, arr1, 20);    //调用函数,将从arr1开始,复制20个字节的数据给arr2int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr2[i]);    //通过循环进行数组的打印,查看复制效果}return 0;
}

        上述是函数的使用代码,首先创建了两个整型数组,一个是接收复制内容的目标空间;另一个是提供复制内容的源头数组。调用函数时,第三个参数表示的是复制内容的大小(单位是字节)。所以上述代码可以实现:将arr1的前20个字节的内容复制到arr2数组中去。

2.函数的模拟实现

void * memcpy ( void * dst, const void * src, size_t count)
{void * ret = dst;        //保存首地址,方便后续处理返回值assert(dst);            //防止接收的函数参数指针为空指针,避免影响解引用操作assert(src);while (count--) {*(char *)dst = *(char *)src;    //将地址先进行强制类型转换后解引用,最后进行数据的复制dst = (char *)dst + 1;    
//将指针后移,因为强制类型转换操作是暂时性的,当强转之后进行后置++,此时强制类型转换过了时效性,就会产生对 void*指针解引用的错误操作。所以采用简单的+1计算操作src = (char *)src + 1;}return(ret);
}

        根据上方模拟实现函数的代码和图片解释可得,函数需要以字节为单位复制数据,这时候选择强制类型转化为char*,原因是:char*的大小为1个字节,刚好为函数参数的最小单位。这样无论碰到什么类型的数据,都会逐一字节进行复制,实现了泛型编程。函数在实现过程中,通过控制while循环的循环条件,从而控制复制内容字节数的大小。

        指针后移不用简便的后置++,原因是:C语言的强制类型转化操作具有暂时性,当进行到自增运算时,强制类型转换就会过了时效性,导致代码解引用void*类型的指针,这种行为是具有危险性的,所以这里的模拟实现采用了简单的+1操作。

二.memmove函数使用和模拟实现

1.函数的使用

        讲到该函数,貌似这个函数就是为了弥补memcpy函数的不足的(不能复制重叠的数据)。该函数可以将一段内存数据从源地址复制到目标地址处。

         通过上述图片可得,函数的返回类型和每个参数和memcpy函数极为相似,实际上目的也是相同的。都实现了泛型编程。

         memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用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, 20);    //调用函数,将arr1的前20字节数据复制到arr1+2空间处int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr1[i]);        //利用循环打印该数组,查看函数调用的效果}return 0;
}

        输出:1 2 1 2 3 4 5 8 9 10

        上述代码的理解:上述代码通过自己复制数据给自己,来实现重叠数据复制时,函数效果的验证。根据输出结果可以得出,该函数确实具有复制重叠数据内容的能力。

2.函数的模拟实现

void * memmove ( void * dst, const void * src, size_t count)
{void * ret = dst;    //保存首地址,用于最后的函数返回if (dst <= src || (char *)dst >= ((char *)src + count))//当已拷贝数据不会对未拷贝数据进行覆盖时{while (count--) //进行正向复制{*(char *)dst = *(char *)src;dst = (char *)dst + 1;src = (char *)src + 1;}}else     //否则进行反向复制{dst = (char *)dst + count - 1;src = (char *)src + count - 1;while (count--) {*(char *)dst = *(char *)src;dst = (char *)dst - 1;src = (char *)src - 1;}}return(ret);
}

        该函数的实现比较复杂,需要分情况讨论。因为函数需要解决重叠空间复制数据的问题,所以根据上方图片可以看出思路(可以根据目标空间和源头数据的位置关系来决定复制的方向)。

        当(dst <= src || (char *)dst >= ((char *)src + count)) 时,说明已拷贝数据不会对未拷贝数据进行覆盖,所以使用正向复制的顺序;当dst位于src和src+count中间时,说明已拷贝数据会对未拷贝数据进行覆盖,所以进行反向复制。

三.memset函数的使用

        该函数的作用是:改变一段内存区域的值,将其变为我们想改变的值。

        通过上述图片可以得知,该函数的函数参数为void*类型,原因同上,均为达到泛型编程的目的。第二个函数参数为改变后的值。第三个参数为要填充的字节数。最后函数返回类型为void*类型返回的是数据的首地址。

        这里需要注意的是,该函数填充内容时,是以字节为单位的,填充过程中,会将每个字节都填充为所需值。系统存数据是以二进制的补码进行存储的,如果填充的是大于1个字节的类型,那么一般不会达到预期的效果。

        比如:将一个空间的数据(整型值2,补码为:00000000 00000000 00000000 00000010),全部变为整型值1,这时候用此函数就会将该地址存储的内容,每个字节都变为1,数据就会变成00000001 00000001 00000001 00000001,可以明显看出得到的数据并不是整型值1,原因是整型值1的存储并不是每个字节都是1。

        下面举一个该函数的使用例子:

#include <stdio.h>
#include <string.h>    //该函数需要的头文件
int main ()
{char str[] = "hello world";    //定义字符数组,方便后续的函数验证memset (str,'x',6);    //调用函数,将str数组的前6个字节全部变为xprintf(str);    //打印字符串str,查看函数的具体效果return 0;
}

输出:xxxxxx world

四.memcmp函数的使用

        该函数的作用是内存比较,用于比较两个内存区域的前num字节内容大小。

        根据上方图片可以看出:前两个函数参数为void *指针(待比较的两个内存区域),旨在接收任意类型的数据,增强函数的健壮性,实现泛型编程。第三个函数参数为num,用于控制比较内容的多少(单位是字节)。

        该函数和strcmp函数比较相似,但是还是有些许区别。该函数不会以 \0为终止条件,不会检查内容的结束符。该函数的返回值和strcmp相同:第一个函数参数大时,函数返回大于0 的数字;第二个函数参数大时,函数返回小于0的数字;两个函数参数一样大时,返回数字0。

        下面是该函数的使用举例:

#include <stdio.h>
#include <string.h>    //该函数需要的头文件
int main()
{char buffer1[] = "DWgaOtP12df0";        //创建两个字符数组,用于接下来的函数验证char buffer2[] = "DWGAOTP12DF0";int n;n = memcmp(buffer1, buffer2, sizeof(buffer1)); //调用函数比较两字符串的前sizeof(buffer1)字节内容if (n > 0)         //根据判断结果打印相应的内容printf("'%s' is greater than '%s'.\n", buffer1, buffer2);else if (n < 0) printf("'%s' is less than '%s'.\n", buffer1, buffer2);else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);return 0;
}

        上述代码,通过调用该函数判断,来判断两内存区域数据内容的大小,这和strcmp函数类似,均是逐个字节进行ASCII码值大小的比较,比较的并不是内容的长短。

http://www.dtcms.com/a/341520.html

相关文章:

  • 《SQLAlchemy 2 In Practice》读后感
  • win与ubuntu双系统安装笔记
  • 小波函数多尺度变换的 Curvelet 变换
  • vue3项目,使用vue2方式来写,可以吗
  • 【嵌入式】CAN通信
  • 基于XGBoost算法的数据回归预测 极限梯度提升算法 XGBoost
  • 虚拟机部署HDFS集群
  • JDK 工具
  • IDEA(十四) IntelliJ Idea 常用快捷键(Mac)
  • 会计人员职业发展框架:核心能力构建与进阶路径
  • ROADS落地的架构蓝图
  • Java 通过 m3u8 链接下载所有 ts 视频切片并合并转换为 mp4 格式
  • Odoo 18 通用图片导入工具:从零到一的企业级开发实战
  • 记录一次ubuntu系统下ovito无法调用显卡驱动报错
  • keepalived的配置
  • Java内置注解
  • 区块链技术:重塑未来互联网的伟大动力
  • 中金所股指期货交易规则
  • c++之指针和引用
  • 第三十三天(信号量)
  • 大模型—— DeepSeek V3.1 Base / Instruct 发布
  • Mqtt — 使用详解EMQX,MQTTX
  • Annexin V应用指南--多领域应用与实验陷阱规避
  • MySQL之分区功能
  • 《算法导论》第 33 章 - 计算几何学
  • 分布式事务之Seata与RocketMQ
  • 【Java SE】初识Java:从语言特性到实战入门
  • 整体设计 之定稿 “凝聚式中心点”原型 --整除:智能合约和DBMS的在表层挂接 能/所 依据的深层套接
  • 盲盒商城h5源码搭建可二开幸运盲盒回收转增定制开发教程
  • Python的collections引入的类型介绍(Python中的map, unordered_map, struct, 计数器, chainmap)