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

内存函数详解,包含部分字符串函数

目录

         一,memcpy内存函数的介绍

二memmove函数的介绍

三,memset的函数使用

四,memcmp的介绍

五,内存函数的模拟实现,以及一个字符串函数strstr的模拟实现

        5.1memcpy函数的实现

        5.2memmove的模拟实现

        5.3memcmp的模拟实现

        5.4 strstrg函数的模拟实现(暴力实现以及KMP算法实现)


内存操作函数

memcpy的使用以及模拟实现

memmove的使用以及模拟实现

memcmp的使用以及模拟实现

memset的使用

strstr的模拟实现

一,memcpy内存函数的介绍

void* memcpy(void* destination, const void* source, size_t num);
这里我们可以看到,他是有三个参数的,其中有俩个无符号指针,还有一个是无符号的num
这俩个指针,一个指向目标要改变的数据的地址,另一个是源地址,要拷贝到我们目标中
当然这里使用无符号类型的指针,是为了我们能拷贝任何类型的数据,就不仅限于拷贝字符

解释如下

函数 memcpy source 的位置开始向后复制 num 个字节的数据到 destination 的内存位置。
这个函数在遇到 '\0' 的时候并不会停下来,因为我们给了需要拷贝的字节大小,所以拷贝停止是由我们自己来控制的
如果 source destination 有任何的重叠,复制的结果都是未定义的。
这个意思是所不能在自己的后面拷贝自己,因为可能会导致越界的风险,当然在VS下是可以自己拷贝自己的内容的,但是在不同的编译器中,可能效果不同,有些编译器是不允许这样使用的

这便是在自己后面进行拷贝,然后出现越界的情况,例如在字符串中,他在拷贝的过程中,可以会将'\0'修改,导致字符串没有结尾标志。

可以看到,我们将arr2中的数据拷贝到了arr1中去,而arr1和arr2中存放的都是int类型的变量,这就是和字符串拷贝的区别,当然我们也可以拷贝其他类型的变量,例如结构体

memmove函数的介绍

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

这里我们可以看到,他是有三个参数的,其中有俩个无符号指针,还有一个是无符号的num
这俩个指针,一个指向目标要改变的数据的地址,另一个是源地址,要拷贝到我们目标中
当然这里使用无符号类型的指针,是为了我们能拷贝任何类型的数据,就不仅限于拷贝字符
memcpy 的差别就是 memmove 函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用 memmove 函数处理。
当然他也能在字符串后面去追加自己的部分
我们可以看到,他是能在他自己的后面追加内容的。

三,memset的函数使用

void *memset( void *dest, int c, size_t count );

memset函数,有三个参数,一个是无符号的指针,一个是你要设置的符号是什么,最后一个参数是无符号的整形,返回类型是无符号的指针类型,指向的是 *dest的首元素地址

memset函数的功能是,在dest里面设置count字节的符号C,并返回dest的地址

我们使用memset就能使我们想要的字节个数的内容变成我们需要的值。

四,memcmp的介绍

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

我们可以看到,他有三个参数,前俩个是我们要比较内容的地址,最后一个是我们要比较的个数,返回类型是int类型,如果ptr1 > ptr2 则返回 > 0 的数,如果ptr1 < ptr2 则返回 0  < 的数,如果 ptr1 == ptr2 则返回 0

五,内存函数的模拟实现,以及一个字符串函数strstr的模拟实现

5.1memcpy函数的实现

void* my_memcpy(void* dest, const void* cur, size_t num)
{//保留dest的地址void* ret = dest;while (num){*(char*)dest = *(char*)cur;dest = (char*)dest + 1;cur = (char*)cur + 1;num--;}//返回dest的地址return ret;
}
int main()
{char str1[] = "hello world .............";char str2[] = ".............";//将str2的前5个字节的内容拷贝到str1中my_memcpy(str1,str2,5sizeof(str2[0]));printf(str1);return 0;
}

5.2memmove的模拟实现

图,一

图,二

当然在拷贝自己时会出现上述俩种情况,如果我们一直只选一直拷贝方式,例如,从前向后拷贝因为cur和dest都是来自同一部分,那么会出现什么情况呢,我们在dest > cur时,在拷贝的时候就已经将存储在a ,b ,c 之后的数据d,e,f修改为 a,b,c 因为dest和cur指向的是同一块内容,那么cur里面的数据 d,e,f也被修改了,那么就达不到我们需要的拷贝效果。这时我们就要进行一定的判断,当dest > cur时,我们从后向前拷贝,当 cur > dest时我们从前向后拷贝。当然,如果不是相同地址块的内容,如何拷贝不会影响他们的结果,这个判断只对拷贝同一块地址内容进行区分。

代码如下

void* my_memmove(void* dest, const void* cur, size_t num)
{void* ret = dest;//为了防止自己拷贝自己导致的内容覆盖//如果dest < curif (dest < cur){while (num){*(char*)dest = *(char*)cur;dest = (char*)dest + 1;cur = (char*)cur + 1;num--;}}else{dest = (char*)dest + num;cur = (char*)cur + num;while (num){*(char*)dest = *(char*)cur;dest = (char*)dest - 1;cur = (char*)cur - 1;num--;}}
}
int main()
{char str1[] = "hello world .............";char str2[] = ".............";my_memmove(str1,str1+5,6);printf(str1);return 0;
}

5.3memcmp的模拟实现

int my_memcmp(void* pstr1, char* pstr2, size_t num)
{while (num){//如果某个字节的值不相等,则中断循环if (*(char*)pstr1 != *(char*)pstr2){break;}//相等这继续向下走pstr1 = (char*)pstr1 + 1;pstr2 = (char*)pstr2 + 1;num--;}//直接返回pstr1 和 pstr2对应的差值即可return (*(char*)pstr1 - *(char*)pstr2);
}int main()
{char str1[] = "hello world .............";char str2[] = "hello word";int ret = my_memcmp(str1,str2,5);printf("ret = %d",ret);return 0;
}

可以看到,直接返回了不相等部分的差值,大于0 说明前num个字节 str1大于str2

可以看到,直接返回了相等部分的差值,等于0 说明前num个字节 str1等于str2

5.4 strstrg函数的模拟实现(暴力实现以及KMP算法实现)

暴力实现:

char* my_strstr(const char* ps1, const char* ps2)
{char* p1 = ps1;char* p2 = ps2;char* p3 = ps1;while (*p3 != '\0'){//每次循环一次,将p3指向的下一个内容给p1,p2回到子集的起点p1 = p3;p2 = ps2;while (*p2 != '\0'){if (*p2 == *p1){//相等就p1和p2向后走,一直比较到 p2指向'\0'为止p2++;p1++;}else{//如果在p2到'\0'之前不相等,这直接跳出循环break;}}//如果*p2 == '\0' 那么说明以及在ps1中的子集里找到了与ps2相等的部分,那么就可以返回p3所指向的内容,if (*p2 == '\0'){return p3;}//如果不相等,p3向后走一位p3++;}//如果一直到最后都没有找到那么将返回空return NULL;
}int main()
{char str1[] = "abcdeffghjkln";char str2[] = "ffg";char* ret = my_strstr(str1,str2);printf(ret);return 0;
}

KMP算法:
next数组:
由上图next数组我们可以知道
如果我们只知道i之前的next数组下标,那么他之后的应该按照 next[i+1] = k+1,去计算
而图中我们要求的是 i 位置的next数据,那么如果字符串第 i - 1 位置的字符与 k 位置的字符相等,那么next[i] 位置的值就等于 next [i] = next[i-1] + 1,当然,如果 i 的前一个位置的字符串不等于 k 位置的字符串,那么next数组回退到 k 指向的位置,如果一直不相等,直到 k 会退到 -1位置处,在出现进行比较
void GetNext(char* ps2,int* next,int len2)
{next[0] = -1;next[1] = 0;int i = 2;int k = 0;//一直插入数据到next的最后一个位置while(i < len2){// 如果 k 等于 -1 那么说明已经回退到原点,或者 第 i 的前一项与k位置的字符相等 if (k == -1 ||ps2[i - 1] == ps2[k]){next[i] = k + 1;i++;k++;}//不相等这 k 回退到next[k]对应的值else{k = next[k];}}
}
char* KMP( char* ps1,  char* ps2,int pos)
{int len1 = strlen(ps1);int len2 = strlen(ps2);//我们要找的子串,和需要的主串不能为空if (len1 == 0 || len2 == 0){return NULL;}//我们希望开始比较的位置不能大于主串的长度,也不能小于零if (pos<0 || pos > len1){return NULL;}//数组这里我们选择动态开辟在堆上建next数组int* next = (int*)malloc(sizeof(int) * len2);//如果malloc失败这直接返回if (next == NULL){return;}//函数得到我们的next数组GetNext(ps2, next,len2);int i = pos;//遍历主串int j = 0;//遍历子串//在主串中寻找子串while (i < len1 && j < len2){//如果 j = -1;这说明需要比较下一个,相等这比较下一个if (j == -1||ps1[i] == ps2[j]){i++;j++;}//不相等将 j 更新为next[k] 对应的值在重新去比较else{j = next[j];}}//释放我们在堆上申请的空间free(next);// 如果j >= len2的长度则说明已经找到主串中的子串,返回对应位置的地址if (j >= len2){return &ps1[i - j];}//没有找到返回空return NULL;
}

KMP整体代码如下

#include<stdio.h>
#include<stdlib.h>
void GetNext(char* ps2,int* next,int len2)
{next[0] = -1;next[1] = 0;int i = 2;int k = 0;while(i < len2){if (k == -1 ||ps2[i - 1] == ps2[k]){next[i] = k + 1;i++;k++;}else{k = next[k];}}
}
char* KMP( char* ps1,  char* ps2,int pos)
{int len1 = strlen(ps1);int len2 = strlen(ps2);if (len1 == 0 || len2 == 0){return NULL;}if (pos<0 || pos > len1){return NULL;}int* next = (int*)malloc(sizeof(int) * len2);if (next == NULL){return;}GetNext(ps2, next,len2);int i = pos;//遍历主串int j = 0;//遍历子串while (i < len1 && j < len2){if (j == -1||ps1[i] == ps2[j]){i++;j++;}else{j = next[j];}}if (j >= len2){free(next);return &ps1[i - j];}free(next);return NULL;
}int main()
{char str1[] = "abcdeffghjkln";char str2[] = "ffghjk";char* ret = KMP(str1,str2,0);printf(ret);return 0;
}

相关文章:

  • 深入理解python列表与字典:数据结构的选择与性能差异
  • 07、SpringBoot 源码分析 - SpringApplication启动流程七
  • 入门五(项目介绍及登录和发布需求)
  • k8s 创建 LoadBalancer 类型的 svc
  • python数据分析-CO2排放分析
  • 安卓开发--安卓使用Echatrs绘制折线图
  • 二.对配置文件中数据库密码进行加密
  • Java 为什么使用泛型
  • Linux程序开发(十二):线程与多线程同步互斥实现抢票系统
  • 【单片机】STM32F070F6P6 开发指南(一)STM32建立HAL工程
  • VUE2 tab切换导航 展示页面内容(父级子级独立)
  • c语言:模拟strlen(三种方法)最全版本
  • Day03—flask与react实现交互(解决跨域问题)
  • 从容应对亿级QPS访问,Redis还缺少什么?no.29
  • c语言----函数
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • es6--symbol
  • extjs4学习之配置
  • leetcode98. Validate Binary Search Tree
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • overflow: hidden IE7无效
  • pdf文件如何在线转换为jpg图片
  • scrapy学习之路4(itemloder的使用)
  • spring boot下thymeleaf全局静态变量配置
  • underscore源码剖析之整体架构
  • 读懂package.json -- 依赖管理
  • 对象引论
  • 记录:CentOS7.2配置LNMP环境记录
  • 简单实现一个textarea自适应高度
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 网络应用优化——时延与带宽
  •  一套莫尔斯电报听写、翻译系统
  • 用简单代码看卷积组块发展
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • ​Linux·i2c驱动架构​
  • #QT(QCharts绘制曲线)
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • $LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .NET HttpWebRequest、WebClient、HttpClient
  • .Net IOC框架入门之一 Unity
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .NET编程C#线程之旅:十种开启线程的方式以及各自使用场景和优缺点
  • .Net各种迷惑命名解释
  • @RestControllerAdvice异常统一处理类失效原因
  • [ C++ ] STL---stack与queue
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce
  • [ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限
  • [2544]最短路 (两种算法)(HDU)
  • [Algorithm][综合训练][体育课测验(二)][合唱队形][宵暗的妖怪]详细讲解
  • [Android] Android ActivityManager
  • [C++][opencv]基于opencv实现photoshop算法图像剪切
  • [CISCN2019 华北赛区 Day1 Web2]ikun