• 字符串与内存操作函数详解与模拟实现


    一.字符串数组与常量字符串的区别

    const char * str=“abcdef” 与 char buffer[]="abcdef"的区别
    先说结论:

    字符串常量是存放在字符常量区与全局变量一样生命周期是从程序运行到结束,而且字符串常量是不可被修改。

    单独一串 “abcdef” 就代表a字符的地址
    在这里插入图片描述

    char buffer[]=“abcdef” 字符串被保存在数组中, 而数组又是在栈上开辟(局部数组),函数运行完时数组被销毁字符串也被销毁,保存在数组中的字符串可以被修改。

    二.字符串操作函数

    1.strlen

    在这里插入图片描述
    1.字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
    2.参数指向的字符串必须要以 ‘\0’ 结束。
    3.注意函数的返回值为size_t,是无符号的

    1)字符串必须以’\0’结尾因为strlen只有找到’\0’才会停止计数

    在这里插入图片描述
    在这里插入图片描述
    因为arr1数组没有存储\0字符所以strlen函数必须找到\0才停止计数,而数组后面的存储的数据是随机值,所以随机找到\0

    2).注意函数的返回值为size_t,是无符号的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    如果对整形如果在内存中存储还不熟悉的请看——>C语言深度解剖之数据到底在内存中如何存储

    strlen函数的三种模拟实现方式

    • 1.计数器
    int my_strlen_1(const char* str)
    {
    	assert(str != NULL);
    	int count = 0;
    	while (*str++ != '\0')
    	{
    		count++;
    	}
    	return count;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    返回值设为int就是为了避免相减产生歧义

    • 2.递归
    
    int my_strlen_2(const char* str)
    {
    	assert(str != NULL);
    	if (*str != '\0')
    	{
    		return 1 + my_strlen_2(str+1);
    	}
    	else
    	{
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 3.指针减指针
    
    int my_strlen_3(const char* str)
    {
    	assert(str != NULL);
    	const char *start = str;
    	const char *end = str;
    	while (*++end != '\0');
    	return end - start;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.strcpy

    在这里插入图片描述
    在这里插入图片描述
    总结:
    1.源字符串必须以 ‘\0’ 结束。
    2.会将源字符串中的 ‘\0’ 拷贝到目标空间。
    3.目标空间必须足够大,以确保能存放源字符串。
    4.目标空间必须可变,不能是一个字符串常量

    1)目标空间必须足够大,以确保能存放源字符串。
    在这里插入图片描述

    2)目标空间必须可变,不能是一个字符串常量
    在这里插入图片描述
    strcpy函数模拟实现

    char *my_strcpy(char *dest,const char *src)
    {
    	assert(dest !=NULL);
    	assert(src   !=NULL);
    	char *ret = dest;
    	while (*dest++ = *src++)
    	{
    		;
    	}
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    源字符串中的 \0字符也要拷贝到目的字符串中

    3.strcat

    在这里插入图片描述
    在这里插入图片描述
    总结:
    1.源字符串必须以 ‘\0’ 结束。
    2.目标空间必须有足够的大,能容纳下源字符串的内容。
    3.目标空间必须可修改。
    4.目的字符串与源字符串不应重叠(不能自己给自己追加)
    5.目标字符串中的\0字符会被源字符串的第一个字符覆盖并且将源字符串中的\0字符拷贝到目标字符串

    1)目的字符串与源字符串不应重叠(不能自己给自己追加)
    在这里插入图片描述
    在这里插入图片描述

    strcat函数的模拟实现

    char *my_strcat(char *dest,const char *src)
    {
    	assert(dest != NULL);
    	assert(src   != NULL);
    	char *ret = dest;
    	//找到目的字符串的\0字符
    	while (*++dest != '\0')
    	{
    		;
    	}
    	//追加字符串包含\0字符
    	while (*dest++ = *src++)
    	{
    		;
    	}
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.strcmp

    在这里插入图片描述

    总结:
    第一个字符串大于第二个字符串,则返回大于0的数字
    第一个字符串等于第二个字符串,则返回0
    第一个字符串小于第二个字符串,则返回小于0的数字

    将两个字符串的字符一个个进行比较,如果有字符不同则比较这两个字符ASCLL码值,谁的ASCLL码值大则该字符串大,若比较到\0则两个字符串相等返回0.

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    strcmp函数的模拟实现

    int my_strcmp(const char * str1, const char *str2)
    {
    	assert(str1 != NULL);
    	assert(str2 != NULL);
    	while (*str1 == *str2)
    	{
    		if (*str1 == '\0' )
    		{
    			//比较到\0则两个字符串相等
    			return 0;
    		}
    		str1++;
    		str2++;
    	}
    	//有字符不相等,返回字符的ascll差值
    	return *str1 - *str2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.strncpy

    在这里插入图片描述
    1.拷贝num个字符从源字符串到目标空间。
    2.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。

    在这里插入图片描述
    strncpy函数的模拟实现

    char *my_strncpy(char *dest,const char* src, int count)
    {
    	assert(dest != NULL);
    	assert(src != NULL);
    	char * ret = dest;
    	while ( count)
    	{
    		count--;
    		if ((*dest++ = *src++) == '\0')
    		{
    			break;
    		}
    	}
    	//如果要拷贝的字符数大于源字符串的大小,则后面补\0
    	if (count)
    	{
    		while (count--)
    		{
    			*dest++ = '\0';
    		}
    	}
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    6.strncat

    在这里插入图片描述
    总结:
    1.目的字符串数组要足够大
    2.若num小于源字符串的大小,追加num个字符后自动在后面追加一个\0
    3.若num大于源字符串的大小,只会追加一个源字符串(包含\0)到目的字符串,后面不会继续追加\0.

    1)若num小于源字符串的大小,追加num个字符后自动在后面追加一个\0
    在这里插入图片描述
    strncat函数的模拟实现

    char *my_strncat(char *dest, const char *src, int count)
    {
    	assert(dest != NULL);
    	assert(src != NULL);
    	char *ret = dest;
    	while (*++dest)
    	{
    		;
    	}
    	while (count--)
    	{
    		if (!(*dest++ = *src++))
    			return ret;
    	}
    	//若num小于源字符串的大小,追加num个字符后自动在后面追加一个\0
    	 *dest = '\0';
    	 return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    7.strncmp

    在这里插入图片描述
    比较C字符串str1和C字符串str2的最多num个字符。
    这个函数开始比较每个字符串的第一个字符。 如果它们彼此相等,则继续执行以下两对,直到字符不相同,直到达到终止的空字符,或直到两个字符串中有num个字符匹配,以先发生的情况为准。

    strncmp函数的模拟实现

    int my_strncmp(const char * str1, const char *str2, int count)
    {
    	assert(str1 != NULL);
    	assert(str2 != NULL);
    	while (count--)
    	{
    		if (*str1 != *str2)
    		{
    			break;
    		}
    		str1++;
    		str2++;
    	}
    	return *str1 - *str2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    8.strstr

    在这里插入图片描述
    总结:
    1.返回str1中第一个出现srt2的指针,如果str2不是str1的一部分,则返回空指针

    strstr函数模拟实现

     char *my_strstr(const char*p1, const char* p2)
    {
    	assert(p1 !=NULL);
    	assert(p1 !=NULL);
    	const char *s1 = NULL;
    	const char *s2 = NULL;
    	const char *cur = p1;
    	if (*p2 == '\0')
    	{
    		return NULL;
    	}
    	while (*cur)
    	{
    		s1 = cur;
    		s2 = p2;
    		while ((*s1 == *s2) && (*s1 != '\0') && (*s2 != '\0'))
    		{
    			s1++;
    			s2++;
    		}
    		//匹配成功
    		if (*s2 == '\0')
    		{
    			return (char *)cur;
    		}
    		//匹配失败,提前结束
    		if (*s1 == '\0')
    		{
    			return NULL;
    		}
    		cur++;
    	}
    	return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    实现思路:
    创建两个临时指针来进行字符的比较,而不改变原来两个在字符串的首地址,因为一旦第一趟比较失败,第二个字符串要从首字符进行比较,而第一个需要从下一个字符进行比较,所以p1指针与p2指针最好不动。

    在这里插入图片描述
    在这里插入图片描述

    9.strtok

    在这里插入图片描述

    1.delimiters参数是个字符串,定义了用作分隔符的字符集合
    2.第一个参数指定一个字符串,它包含了0个或者多个由delimiters字符串中一个或者多个分隔符分割的标记。
    3.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
    4.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
    5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
    6.如果字符串中不存在更多的标记,则返回 NULL 指针。

    在这里插入图片描述
    在这里插入图片描述

    三.内存操作函数

    前面的字符串操作函数只能是针对字符串,而如果要拷贝,追加其他类型的元素,字符串操作函数就不适用啦,所以我们需要一组内存操作函数直接拷贝或追加、比较,内存中的数据,以数据类型无关。

    1.memcpy

    ==在这里插入图片描述
    总结:
    1.函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
    2.这个函数在遇到 ‘\0’ 的时候并不会停下来,就是复制num个字节数据
    3.如果source和destination有任何的重叠,复制的结果都是未定义的。

    在这里插入图片描述
    将arr1的内容拷贝到arr2,只需要知道arr1占内存多少个字节,然后拷贝多少个字符到arr2数组中

    memcpy的模拟实现

     void *my_memcpy(void *dest, const void *src, size_t num)
     {
    	 assert(dest != NULL);
    	 assert(src   != NULL);
    	 void *ret = dest;
    	 while (num--)
    	 {
    		 //一个一个字节进行拷贝
    		 *(char*)dest = *(char*)src;
    		 ++(char*)dest;
    		 ++(char*)src;
    	 }
    	 return ret;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    1)如果source和destination有任何的重叠,复制的结果都是未定义的
    在这里插入图片描述
    在这里插入图片描述
    如果source和destination有重叠,这个是实现的是从前向后拷贝所以可能会出问题

    在这里插入图片描述
    所以得不同的重叠拷贝需要不同的拷贝方式,而memmove函数正好实现了所有重叠拷贝的情况

    2.memmove

    在这里插入图片描述
    memove函数实现了memcpy的所有功能而且允许目标和源重叠
    如果源空间和目标空间出现重叠,就得使用memmove函数处理。

    模拟实现memmove函数

     void *my_memmove(void *dest, const void *src, size_t num)
     {
    	 assert(dest != NULL);
    	 assert(src   != NULL);
    	 void *ret = dest;
    	 if (src > dest)
    	 {
    		 //从前向后拷贝
    		 while (num--)
    		 {
    			 *(char*)dest = *(char*)src;
    			 ++(char*)dest;
    			 ++(char*)src;
    		 }
    	 }
    	 else
    	 {
    		 //从后向前拷贝
    		 while (num--)
    		 {
    			 *((char*)dest + num) = *((char*)src + num);
    		 }
    	 }
    	 return ret;
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    3.memcmp

    在这里插入图片描述
    memcmp模拟实现

    int my_memcmp(const void *str1, const void *str2, size_t num)
     {
    	 assert(str1 != NULL);
    	 assert(str2 != NULL);
    	 while (--num)
    	 {
    		 if (*(char*)str1 != *(char*)str2)
    		 {
    			 break;
    		 }
    		 ++(char*)str1;
    		 ++(char*)str2;
    	 }
    	return *(char*)str1 - *(char*)str2; 
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    与strncmp基本一致

  • 相关阅读:
    看得见风和日丽,看不见风起“云”涌
    Spring Boot(二)
    观察者模式 行为型设计模式之七
    融合一致性正则与流形正则的半监督深度学习算法
    如何运营好技术相关的自媒体?
    814. 二叉树剪枝 : 简单递归运用题
    7-143 降价提醒机器人
    can not remove .unionfs
    1-深度学习绪论讲解
    论文阅读:SuMa++: Efficient LiDAR-based Semantic SLAM
  • 原文地址:https://blog.csdn.net/k666499436/article/details/125339897