字符串相关的函数和内存块相关函数

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary-walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

👑💎💎👑💎💎👑 
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑    希望在看完我的此篇博客后可以对你有帮助哟

👑👑💎💎💎👑👑   此外,希望各位大佬们在看完后,可以互赞互关一下,看到必回
👑👑👑💎👑👑👑

 字符串相关的函数和内存块相关函数思维导图:


目录

一:strlen( )

二:长度不受限制的字符串函数

        strcpy strcat strcmp

三:长度受限制的字符串函数

       strncpy strncat strncmp

四:字符串查找

       strstr strtok

五:错误信息报告

       strerror

        perror

六:字符分类函数


七:内存函数

memcpy

memmove

memcmp

memset


 一:strlen( )

函数原型:

正确的应用中:strlen()的参数部分必须要有 \0

注意:

1)strlen只统计\0 之前的字符个数

2)strlen的函数返回类型是size_t (对应的占位符是  %u)

 strlen模拟实现

方法1:借助计数的方式

size_t my_strlen(const char* start)
{//采用计数器来模拟实现strlenint count = 0;while (*start ){count++;start++;}return  count;
}
int main()
{printf("%u\n",my_strlen("abc"));}

方法2:借助指针 - 指针 

size_t my_strlen( char* str)
{//采用指针 - 指针实现strlen 模拟char* p = str;//遍历字符串while (*p){p++;}return p - str;
}
int main()
{printf("%u\n",my_strlen("abcd"));
}
二: 长度不受限制的字符串函数
strcpy

函数原型:

使用注意要点:

1)目标空间足够大并且可以修改

2)源字符串必须有 \0

3)  在拷贝的时候 源字符串的\0也拷贝

 strcpy模拟实现

有了以上的注意点,相信对strcpy的模拟实现咱也是易如反掌

思路分析:

1:定义2个指向目标空间和源字符串的指针

 char* dst   //指向目标空间

char*   s     //指向源字符串

2:一个字符一个字符的拷贝

3:注意最后源字符串的\0也要拷贝过去

char* my_strcpy(char* dst, const char* s)
{char* ret = dst;//拷贝完之后返回目标空间起始地址,所以需要先保存一下assert(dst);assert(s);while (*s){*dst++ = *s++;//对源字符串非\0之前的拷贝}//别忘了还有\0没有拷贝*dst = *s;return ret;//返回目标空间起始地址}
int main()
{/*strcpy(char* dst,const char* sor);使用注意事项1:目标空间足够大,源字符串有 \02:目标空间可以修改       const char*dst //err3:源字符串的\0也会拷贝过去*/char a1[20] = {0};char a2[11] = {"hello bit"};my_strcpy(a1, a2);printf("%s\n", a1);return 0;
}

 对于上面这个核心代码还可以进行再次优化

简化版之后的
    while (*dst++ = *s++)  
    {
        ;
         
    }

代码分析:

先把*s赋给 *dst并进行后置加加 注意此时是对加加之前的条件进行判断真假

直到 *s == \0 赋给*dst 结束拷贝同时源字符串的\0也已经拷贝了

strcat

函数原型:

 函数模拟实现

1)首先找到目标空间的\0

2)其次是对源字符串的拷贝 (和strcpy模拟实现一样的)

char* my_strcat(char* dst, const char* s)
{/*先找到目标空间的开始追加的地址(一般是\0)开始拷贝*/char* ret = dst;while (*dst++)  //找 \0{;}while (*dst++ = *s++)//数据拷贝{;}return ret;
}

 是否可以对字符串自己进行追加?

答案:no no no

 分析如下:

假设对字符串 "hello"来进行追加

指针s,dst初始位置

经过依次拷贝之后

通过画图我们知道,此时\0已经被覆盖,这就会导致指针s一直找不到从而造成死循环

strcmp

 strcmp函数介绍:

1)比较对应位置上字符的ASCII码值(a-z的ASCII码值依次增加)

2)函数的返回类型:int 

 strcmp模拟实现

分析:假设比较字符串str1 ,str2

int my_strcmp(const char* s1,const char* s2)
{while (*s1 && *s2){if (*s1 != *s2)break;s1++, s2++;//继续比较下一对字符}return *s1 - *s2;}
三:长度受限制的字符串函数(就是在原来功能的基础上加上了字数的限制)
strncpy

注意当指定拷贝的个数大于源字符串的内容时,会自动补加\0

strncat

 strncat可不像strncpyhan函数一样当指定追加的字符个数大于源字符串的内容时,会自动补加\0

strncmp

注意:对num的传参决定了这个函数返回值

str1: abcd

str2 : abd

当num == 3;返回小于0

当num == 2;返回等于0

当num == 4;返回小于0

四:字符串查找
       strstr

strstr  功能:在一个字符串中查找一个子字符串并返回这个子字符串第一次出现的位置

当 *cp = a时,显然是不匹配的,所以cp指向下一个字符 b,

此时是有一个字符可以匹配上,此时s2++,若继续让cp++,当匹配成功时,cp的初始位置是无法找到的,所以设置一个变量s1,s1++,来到下一个字符b的位置 s1,s2指向的字符相等,此时s1++,s2++二者指向的字符不相等,所以此时cp++指向 

同时s1也指向这里,s1 == s2 ,继续 s1++,s2 ++,

s1 == s2,继续 s1++,s2 ++,

这时候内嵌的while循环结束,执行

    if (*s2 == '\0') 
            return cp;

char* my_strstr( char* str1,  char* str2)
{char* cp = str1;while (*cp){char* s1 = cp;char* s2 = str2;while (*s2 && *s1 && *s1 == *s2)  // 处理对应位置相等{s1++;s2++;}if (*s2 == '\0') return cp;cp++;}//来到这:没有找到return NULL;}
 strtok

char * strtok ( char * str, const char * sep );

1)sep参数是个字符串,定义了用作分隔符的字符集合

  第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。

2)strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)

3)strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置(会把这个标记换成\0)

4)strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。 

5)如果字符串中不存在更多的标记,则返回 NULL 指针。 

 strtok函数的使用

solitary@walk.at.123#99 对这个字符串进行提取

int main()
{char a1[] = "solitary@walk.at.123#999";char copy[50];//对a1临时拷贝strcpy(copy, a1);char sep[] = "#@.";//分隔符集合char* ret = strtok(copy, sep);printf("%s\n", ret);ret = strtok(NULL, sep);printf("%s\n", ret);ret = strtok(NULL, sep);printf("%s\n", ret);ret = strtok(NULL, sep);printf("%s\n", ret);ret = strtok(NULL, sep);printf("%s\n", ret);return 0;
}

 不知道各位有没有这样的问题:对于这个字符串的具体内容我是知道的,我直接进行这种暴力的打印就可以,那要是这个字符串很长很长,你是否又知道需要具体打印多少次呢?

这里我们借助循环就可以实现,只不过需要我们对strtok这个函数非常的了解

可能有的老铁就会说:我们不是一般借助循环来进行次数的判断嘛。这就说明了我们见的少了吧

当字符串不在存在标记时,这个函数返回NULL  :依据这个点我们作为循环的判断条件

int main()
{char a1[] = "solitary@walk.at.123#999";char copy[50];//对a1临时拷贝strcpy(copy, a1);char sep[] = "#@.";//分隔符集合char* ret = strtok(copy, sep);//借助循环来实现字段的打印for (ret; ret != NULL; ret = strtok(NULL, sep)){printf("%s\n", ret);}return 0;
}
五:错误信息报告
       strerror

strerror函数通常用于处理发生在系统调用库函数(记住不是自己编写 的函数)出现的错误,该函数会返回一个指向错误消息字符串的指针

当库函数调用失败的时候,strerror函数会把错误码记录到errno这个变量中

errno是C语言的一个全局变量

 

 

      perror

perror这个函数功能和strerror函数功能是一样的:都是打印错误码对应的错误信息

perror这个错误码也是从errno这个全局变量里面拿到的

 

六:字符分类函数

七:内存函数
     memcpy

memcpy的前2个参数都是 vod*类型:因为内存块里存放的数据你不知道是什么类型

第三个参数:拷贝源数据总的字节数

  memcp的模拟实现分析:

1)对数据进行一个字节一个字节的拷贝

2)注意:对void* 类型指针不能直接解引用或者是加1操作

3)对指针强转具有临时性

4)对于内存块重叠的拷贝是不可以的(严格意义来说),一般涉及内存块重叠交给memmove函数

#include<stdio.h>
#include<string.h>
void* my_memcpy(void* dst, const void* sor, size_t num)
{char* ret = dst;//对目标空间起始地址保留while (num--){//一个字节的拷贝*(char*) dst = *(char*)sor; //dst++, sor++;  // err 强转具有临时性//(char*)dst++;  // errdst = (char*)dst + 1;sor = (char*)sor + 1;}return ret;
}
int main()
{char a1[20] = "xxxxxxxxxxxxxxxx";char a2[] = "yyyyy";my_memcpy(a1, a2, 4);printf("%s\n",a1);return 0;
}
          memmove

memmove模拟实现的分析

1)拷贝:从前往后拷贝还是从后往前拷贝

   若是从后往前拷贝  7拷贝到5的位置, 6拷贝到4的位置  ,原来的5已经被覆盖掉

   从前往后拷贝:5拷贝到3 的位置, 6拷贝到4的位置,7拷贝到5 的位置,刚好解决了数据覆盖的问题

注意看,此时  dst 的起始位置 要 > sor的起始位置 ,若果要是从前往后拷贝会不会出现问题

从前往后拷贝:3拷贝到5 的位置,4拷贝到6 的位置,原来5 的已经被覆盖掉了

从后往前拷贝:5拷贝到7 的位置,4拷贝到6 的位置,3拷贝到5的位置刚刚好解决了数据覆盖的问题

此时无论从前往后还是从后往前拷贝都会出现数据覆盖问题

不知道聪明的你,是否已经注意到一个问题就是到底从前往后还是从后往前拷贝其实是取决于dst 与sor的起始相对位置

对于这个函数的模拟其实是有2种方法实现

方法1:

dst < sor  从前往后拷贝

dst >= sor && dst <= sor+num  从后往前拷贝

dst > sor+num  从前往后拷贝

方法2:

dst < sor  从前往后拷贝

dst >= sor   从后往前拷贝

2) 从前往后拷贝的逻辑(这个就和memcpy模拟实现一样的)

    while (num--) 
        {
            //从前往后拷贝  ,和memcpy模拟实现一样
            //一个字节一个字节拷贝
            *(char*)dst = *(char*)sor;
            dst = (char*)dst + 1;
            sor = (char*)sor + 1;
        }

3)从后往前拷贝的逻辑

这里我们需要考虑如何拿到5这个数据对应在最后一个字节

 

 注意:num代表要拷贝字节的总数目

我知道sor+num 是从sor起始位置 跳过num个字节,对于上面的图而言就是指向6 的起始位置,sor+num-1不就是指向原内存块的最后一个字节嘛

代码逻辑: 
//先找到源内存块的最后一个字节  跳过num-1个字节指向最后一个字节while (num -- )  //先用后减减 (num不为0 ; 进入下面的同时-1){*((char*)dst +num)= *((char*) sor+num); }

这里借助方法2来实现模拟


void* my_memmove(void* dst, const void* sor, size_t num)
{char* ret = dst;assert(dst && sor);// 从前往后还是从后往前取余 dst 与 sor 起始位置谁比较在前if (dst > sor) {//从后往前拷贝,避免数据覆盖//先找到源内存块的最后一个字节  跳过num-1个字节指向最后一个字节while (num -- )  //先用后减减 (num不为0 ; 进入下面的同时-1){*((char*)dst +num)= *((char*) sor+num); }}else{while (num--) {//从前往后拷贝  ,和memcpy模拟实现一样//一个字节一个字节拷贝*(char*)dst = *(char*)sor;dst = (char*)dst + 1;sor = (char*)sor + 1;}}return ret;
}
 科普一下:对于内存块数据重叠拷贝按理说memmove就可以实现,但是当我们在VS的编译器来测试的时候,也能实现对 内存块数据重叠拷贝  

注意并不是所有 的编译器在调用标准库的memcpy都能实现内存块数据重叠拷贝,具体情况还是取决于编译器

memcmp
memmcmp函数原型:
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

参数介绍:

1)ptr1 和ptr2  分别指向各自对应内存块的起始地址

2) num : ptr1 和ptr2 指向内存块 的前个num字节进行比较

注意啦,友友们:这里的参数num(要比较的字节数) 和函数strncmp参数的num含义可不同,后者代表:2个字符串要比较的字符个数

还是惯例,接下来我们进行memcmp的模拟实现

 memcmp函数模拟实现
int my_memcmp(const void* str1, const void* str2, size_t num)
{assert(str1 && str2);//注意memcmp比较的过程是:;一个字节一个字节的比较(是对内存里的数据)while (num--){/*if (*(char*)str1 > *(char*)str2)return 1;else if (*(char*)str1 < *(char*)str2)return -1;*/if ((*(char*)str1 == *(char*)str2)){str1 = (char*)str1 + 1;str2 = (char*)str2 + 1;}elsereturn *(char*)str1 - *(char*)str2;}return *(char*)str1 - *(char*)str2;
}
int main()
{/*int a1[] = { 1,8,3,4,5,6,7,8,9 };int a2[] = { 1,2,12 };*/char a1[] = "abcd";char a2[] = "abmnj";int ret = memcmp(a1, a2, 2); //注意这里memcmp的第三个参数num表示要比较的前num个字节数而不是个数printf("%d", ret);return 0;
}
memset

    memset函数原型:  void * memset ( void * ptr, int value, size_t num );

    ptr:起始地址

    value:要设置的值  

    num:要设置的字节数

1)这个函数对内存块的设置,所以说value我们多数传参为字符(在C语言里,字符的本质也是数值)

2)对memset 模拟实现的关键也是在于第二参数

 我们需要考虑如何将int 类型数据转换成char 类型数据???

对于这个问题我暂时有2 个解决方案(目前理论以及实践上都可以说的过去)

方法1:对第二个参数进行改变:char val

方法2:第二个参数依然是int 类型

把int 类型数据转换成char 类型数据:对val % 256

        *((char*)p) = val % 256;

 对应完整代码

 方法2代码:

方法1代码:

 


结语:

以上就是关于字符串以及内存块相关函数的share,相信到这个,各位老铁们的知识量已经大增了吧,这些函数在我们日常的模拟题中也会涉及到,若是到时候用上,那岂不是很香嘛。在此我也衷心希望各位铁子们能够收获满满。

那话不多,你懂的,咱接下来走起!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/434567.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手势识别MATLAB代码

手势识别是智能设备常用的需求, 下面我们用MATLAB来识别手部的形态: 主程序main.m clc;clear all;close all;%清除命令行和窗口 imimread(DSC05815.JPG); [skin,bwycbcr,w,h] hand_segmentation(im); im1bwycbcr; % se strel(ball,[1 1 1;1 1 1;1 1 1]); im1 imdilate(im…

Go的基准测试

基准测试&#xff08;Benchmark&#xff09;是一项用于测量和评估软件性能指标的方法&#xff0c;主要用于评估你写的代码的性能。 基准测试的代码文件必须以_test.go结尾基准测试的函数必须以Benchmark开头&#xff0c;必须是可导出的基准测试函数必须接受一个指向Benchmark类…

【Docker】nacos集群搭建Nginx负载均衡

目录 一、mysql安装与基操 1.1 数据准备 1.2 创建mysql与数据表 二、Nacos集群部署 2.1 创建nacos及配置 2.2 创建Nginx容器 一、mysql安装与基操 1.1 数据准备 拉取mysql docker pull mysql:5.7(版本) 定义挂载目录 mkdir -p /mysql/{conf,data,script} 配置my.c…

37、WEB攻防——通用漏洞XSS跨站权限维持捆绑钓鱼浏览器漏洞

文章目录 XSS——后台植入Cookie&表单劫持&#xff08;获取明文密码&#xff09;XSS——Flash钓鱼配合MSF捆绑上线XSS——浏览器网马配合MSF访问上线 要想获取有效的cookie&#xff0c;需要&#xff1a;1、网站本身采用cookie进行验证&#xff1b;2、网站未做http-only等的…

[AG32VF407]国产MCU+FPGA 使用I2C测试陀螺仪MPU6050

视频讲解 [AG32VF407]国产MCUFPGA 使用I2C测试陀螺仪MPU6050 实验过程 查看原理图中定义的I2C的管脚&#xff0c;PB0和PB1 在board.ve中定义的引脚功能 I2C0_SDA PIN_36 I2C0_SCL PIN_35新建工程 测试代码 #include "board.h"#define MIN_IRQ_PRIORITY 1 #define …

一行命令在 wsl-ubuntu 中使用 Docker 启动 Windows

在 wsl-ubuntu 中使用 Docker 启动 Windows 0. 背景1. 验证我的系统是否支持 KVM&#xff1f;2. 使用 Docker 启动 Windows3. 访问 Docker 启动的 Windows4. Docker Hub 地址5. Github 地址 0. 背景 我们可以在 Windows 系统使用安装 wsl-ubuntu&#xff0c;今天玩玩在 wsl-ub…

Ubuntu系统中部署C++环境与Visual Studio Code软件

本文介绍在Linux Ubuntu操作系统下,配置Visual Studio Code软件与C++代码开发环境的方法。 在文章VMware虚拟机部署Linux Ubuntu系统的方法中,我们介绍了Linux Ubuntu操作系统的下载、安装方法;本文则基于前述基础,继续介绍在Linux Ubuntu操作系统中配置Visual Studio Code…

【深度学习:t-SNE 】T 分布随机邻域嵌入

【深度学习&#xff1a;t-SNE 】T 分布随机邻域嵌入 降低数据维度的目标什么是PCA和t-SNE&#xff0c;两者有什么区别或相似之处&#xff1f;主成分分析&#xff08;PCA&#xff09;t-分布式随机邻域嵌入&#xff08;t-SNE&#xff09; 在 MNIST 数据集上实现 PCA 和 t-SNE结论…

企业培训革新:在线教育系统源码的全面解析

如今&#xff0c;在线教育系统的兴起为企业提供了全新的解决方案&#xff0c;使得培训不再受到时间和地域的限制。 一、在线教育系统的关键组成 在线教育系统的源码包含众多关键组成部分&#xff0c;其中包括&#xff1a; 1.1用户管理模块 用户管理模块负责管理学员和教员的…

单片机学习笔记---独立按键控制LED亮灭

直接进入正题&#xff01; 今天开始我们要学习一个新的模块&#xff1a;独立按键&#xff01; 先说独立按键的内部结构&#xff1a; 它相当于一种电子开关&#xff0c;按下时开关接通&#xff0c;松开时开关断开&#xff0c;实现原理是通过轻触按键内部的金属弹片受力弹动来实…

Leetcode—114. 二叉树展开为链表【中等】

2023每日刷题&#xff08;九十八&#xff09; Leetcode—114. 二叉树展开为链表 Morris-like算法思想 可以发现展开的顺序其实就是二叉树的先序遍历。算法和 94 题中序遍历的 Morris 算法有些神似&#xff0c;我们需要两步完成这道题。 将左子树插入到右子树的地方将原来的右…

JVM系列-7内存调优

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理&#x1f525;如果感觉博主的文…