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

!!‧✧̣̥̇‧✦‧✧̣̥̇‧✦ ‧✧̣̥̇:Solitary-walk

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

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

 


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

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

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

字符串相关的函数和内存块相关函数_第1张图片


目录

一:strlen( )

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

        strcpy strcat strcmp

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

       strncpy strncat strncmp

四:字符串查找

       strstr strtok

五:错误信息报告

       strerror

        perror

六:字符分类函数

七:内存函数

memcpy

memmove

memcmp

memset

 一:strlen( )

函数原型:

字符串相关的函数和内存块相关函数_第2张图片

正确的应用中: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

函数原型:

字符串相关的函数和内存块相关函数_第3张图片

使用注意要点:

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

函数原型:

字符串相关的函数和内存块相关函数_第4张图片

 函数模拟实现

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

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

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

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

字符串相关的函数和内存块相关函数_第5张图片

答案:no no no

 分析如下:

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

指针s,dst初始位置

字符串相关的函数和内存块相关函数_第6张图片

经过依次拷贝之后

字符串相关的函数和内存块相关函数_第7张图片

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

strcmp

字符串相关的函数和内存块相关函数_第8张图片

 strcmp函数介绍:

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

2)函数的返回类型:int 

 strcmp模拟实现

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

字符串相关的函数和内存块相关函数_第9张图片

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

字符串相关的函数和内存块相关函数_第10张图片

注意:对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++指向 字符串相关的函数和内存块相关函数_第11张图片

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

字符串相关的函数和内存块相关函数_第12张图片

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

字符串相关的函数和内存块相关函数_第13张图片

这时候内嵌的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函数的使用

[email protected]#99 对这个字符串进行提取

int main()
{
	char a1[] = "[email protected]#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[] = "[email protected]#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语言的一个全局变量

 字符串相关的函数和内存块相关函数_第14张图片

 字符串相关的函数和内存块相关函数_第15张图片

      perror

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

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

 字符串相关的函数和内存块相关函数_第16张图片

六:字符分类函数

字符串相关的函数和内存块相关函数_第17张图片

七:内存函数
     memcpy

字符串相关的函数和内存块相关函数_第18张图片

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

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

  memcp的模拟实现分析:

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

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

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

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

#include
#include
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

字符串相关的函数和内存块相关函数_第19张图片

memmove模拟实现的分析

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

字符串相关的函数和内存块相关函数_第20张图片

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

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

字符串相关的函数和内存块相关函数_第21张图片

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

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

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

字符串相关的函数和内存块相关函数_第22张图片

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

不知道聪明的你,是否已经注意到一个问题就是到底从前往后还是从后往前拷贝其实是取决于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)从后往前拷贝的逻辑

字符串相关的函数和内存块相关函数_第23张图片

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

 字符串相关的函数和内存块相关函数_第24张图片

 注意: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代码:

字符串相关的函数和内存块相关函数_第25张图片

方法1代码:

 字符串相关的函数和内存块相关函数_第26张图片


结语:

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

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

你可能感兴趣的:(C语言进阶讲解,javascript,开发语言,ecmascript)