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

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇: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)
{
	//采用计数器来模拟实现strlen
	int 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:目标空间足够大,源字符串有 \0
	2:目标空间可以修改       const char*dst //err
	3:源字符串的\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++;  // err
		dst = (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;
		}
		else
			return *(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,相信到这个,各位老铁们的知识量已经大增了吧,这些函数在我们日常的模拟题中也会涉及到,若是到时候用上,那岂不是很香嘛。在此我也衷心希望各位铁子们能够收获满满。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[学习笔记] ONNX 基础知识

1. ONNX 简介 1.1 什么是 ONNX 开放神经网络交换 ONNX&#xff08;Open Neural Network Exchange&#xff09;是一套表示深度神经网络模型的开放格式&#xff0c;由微软和 Facebook 于 2017 推出&#xff0c;然后迅速得到了各大厂商和框架的支持。通过短短几年的发展&#xf…

【JavaEE进阶】 #{}和${}

文章目录 &#x1f343;前言&#x1f333;#{}和${}使⽤&#x1f6a9;Interger类型的参数&#xff08;基础数据类型&#xff09;&#x1f388;使用#{}&#x1f388;使用${} &#x1f6a9;String类型的参数使用&#x1f388;#{}使用&#x1f388;${} &#x1f38d;#{}和${}区别&a…

林浩然与极限的“无穷”约会

林浩然与极限的“无穷”约会 Lin Haoran’s Encounter with the Mathematical “Infinity” 在数学王国里&#xff0c;有一位名叫林浩然的大侠&#xff0c;他的江湖就是高等数学的殿堂。而他要挑战的终极Boss&#xff0c;便是那个既神秘又顽皮的“极限”。 In the kingdom of …

《golang设计模式》第三部分·行为型模式-10-模板方法(Template Method)

文章目录 1. 概述1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 模板方法&#xff08;Template Method&#xff09;用来定义算法的框架&#xff0c;将算法中的可变步骤定义为抽象方法&#xff0c;指定子类实现或重写。 1.1 角色 AbstractClass&#xff08;…

字符串相关函数【超详细】(strcpy,strstr等string.h中的函数)

文章目录 strlen库中函数定义函数作用函数大概“工作”流程函数使用注意&#xff08;要求&#xff09;函数使用例举 strcpy库中函数定义函数作用函数使用注意&#xff08;要求&#xff09;函数大概“工作”流程函数使用例举 strcat库中函数定义函数作用函数使用注意&#xff08…

Go 的命令行解析 flag 包如何扩展新类型呢?

上篇文章 说到&#xff0c;除布尔类型 Flag&#xff0c;flag 支持的还有整型&#xff08;int、int64、uint、uint64&#xff09;、浮点型&#xff08;float64&#xff09;、字符串&#xff08;string&#xff09;和时长&#xff08;duration&#xff09;。 flag 内置支持能满足…

transformer和vit学习笔记

以下记录自己对transformer的学习笔记&#xff0c;可能自己看得懂【久了自己也忘了看不懂】&#xff0c;别人看起来有点乱。以后再优化文档~ 小伙伴请直接去看学习资源&#xff1a; Transformer的理解T-1_哔哩哔哩_bilibili 首先&#xff0c;时序处理&#xff1a;一些模型的出…

Two-factor authentication (2FA) is required for your GitHub account解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【基于电商履约场景的 DDD 实战】DDD业务建模第二部分:履约的战术设计(梳理整个战术设计流程图)

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

Java笔记 --- 二、Stream流

二、Stream流 结合Lambda表达式&#xff0c;简化集合、数组的操作 获取Stream流对象 单列集合获取Stream流 双列集合获取Stream流 数组获取Stream流 一堆零散的数据获取Stream流 Stream流的静态方法of的形参是一个可变参数&#xff0c;可以传递零散数据&#xff0c;也可以传递…

【Python】02快速上手爬虫案例二:搞定验证码

文章目录 前言1、不要相信什么验证码的库2、以古诗文网为例&#xff0c;获取验证码1&#xff09;code_result.py2&#xff09;gsw.py 前言 提示&#xff1a;以古诗文网为例&#xff0c;获取验证码&#xff1a; 登录&#xff1a;https://so.gushiwen.cn/user/login.aspx 1、不…

【C++】类与对象(一)

前言 类与对象&#xff08;一&#xff09; 文章目录 一、面向对象和面向过程的对比二、类的引入2.1 C中的结构体2.2 类2.3 类定义方法2.4 修饰限定符2.5 封装2.6 类的实例化2.7 类对象的大小 三、this指针3.1 this 指针的使用 一、面向对象和面向过程的对比 面向过程编程是将程…

【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…

【排序4】探秘归并排序:提高程序效率的必备技巧

&#x1f60a;归并排序 &#x1f38a;1、基本思想&#x1f38a;2、代码示例&#x1f38a;3、非递归实现&#x1f38a;4、归并排序的性能分析&#x1f38a;5、归并排序的优缺点&#x1f38a;6、归并排序的应用场景&#x1f38a;7、总结 &#x1f38a;1、基本思想 归并排序&…

(笔记总结)C/C++语言的常用库函数(持续记录,积累量变)

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

【vue3源码】vue源码探索之旅:项目介绍

简言 记录下我眼中的vue源码项目。 gitHubvue3项目仓库 项目要求: vue版本 3.4.15nodeV18.12.0以上使用pnpm包管理器vitest测试框架Vue3 vue3是渐进式JavaScript框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 Vue 是一个框架,也是一个生态。其功能覆盖了大部分…

Spark运行架构以及容错机制

Spark运行架构以及容错机制 1. Spark的角色区分1.1 Driver1.2 Excuter 2. Spark-Cluster模式的任务提交流程2.1 Spark On Yarn的任务提交流程2.1.1 yarn相关概念2.1.2 任务提交流程 2.2 Spark On K8S的任务提交流程2.2.1 k8s相关概念2.2.2 任务提交流程 3. Spark-Cluster模式的…

HBase入门:运行机制

文章目录 HBase 系统架构客户端ZooKeeper 服务器Master 主服务器Region 服务器 Region 服务器工作原理用户读写数据的过程缓存的刷新StoreFile合并 Store 的工作原理HLog 的工作原理 HBase 系统架构 HBase 的系统架构包括客户端、ZooKeeper 服务器、Master 主服务器、Region服…

Linux文本三剑客---grep

grep&#xff08;从文本或字符串种过滤特定内容。&#xff09; 格式&#xff1a;Usage: grep [OPTION]... PATTERNS [FILE]... 常用选项&#xff1a; -E 等价于 egrep 扩展正则 -i 忽略大小写 -w 匹配单词 -o 仅显示匹配内容 -r 递归匹配 -c 统计匹配的行数 -v 取反 -n 行号 -A…

vue2 事件总线

原图下载&#xff1a;https://download.csdn.net/download/weixin_47401101/88788636