快速上手字符串函数

news2024/11/18 15:39:08

文章目录

  • 前言
  • 一、求字符串的长度
    • strlen函数
      • strlen函数学习使用
      • strlen函数模拟实现
        • strlen函数模拟实现方法1:计数器法
        • strlen函数模拟实现方法2:指针减指针法
        • strlen函数模拟实现方法3:递归方法
  • 二、字符串的拷贝,拼接和比较
    • strcpy函数
      • strcpy函数学习使用
      • strcpy函数模拟实现
        • strcpy函数模拟实现方法1:
        • strcpy函数模拟实现方法2:对方法1的改进
    • strncpy函数
      • strncpy函数学习使用
      • strncpy函数模拟实现
    • strcat函数
      • strcat函数学习使用
      • strcat函数模拟实现
    • strncat函数
      • strncat函数学习使用
      • strncat函数模拟实现
    • strcmp函数
      • strcmp函数学习使用
      • strcmp函数模拟实现
    • strncmp函数
      • strncmp函数学习使用
      • strncmp函数模拟实现
  • 三、字符串的查找
    • strstr函数
      • strstr函数学习使用
      • strstr函数模拟实现
    • strtok函数
      • strtok函数学习使用
  • 四、错误信息报告
    • strerror函数
      • strerror函数学习使用
  • 五、内存操作的函数
    • memcpy函数
      • memcpy函数学习使用
      • memcpy函数模拟实现
    • memmove函数
      • memmove函数学习使用
      • memmove函数模拟实现
  • 六、比较实用的字符操作函数
  • 总结

前言

在C语言中,我们对字符串的操作大部分都是通过字符串函数来进行的,下面就让我们来深入的了解一下关于字符串操作的函数。

一、求字符串的长度

strlen函数

函数原型:

size_t strlen( const char *string );
//string:起始位置的地址

strlen是用来求字符串长度的函数,它求的长度不包含结尾的’\0’。
官方文档解释:
在这里插入图片描述
注意,返回字符串中的字符数中不包括终止的null字符!!!

strlen函数学习使用

int main()
{
	char str1[] = "abcdef";
	char str2[] = { 'a','b','c','d','e','f'};
	int test1 = strlen(str1);
	int test2 = strlen(str2);
	printf("%d\n%d\n", test1, test2);
	return 0;
}

在这里插入图片描述
下面我们根据内存来看一下原因:
在这里插入图片描述
我们看到str1和str2相比,结尾有一个’\0’,而strlen函数找到结束字符才停止,str2后面的元素是未知的,str2后’\0’的位置也是未知,所以得到值和我们str1不同。
如果我们传空指针或是空字符串会出现什么情况累?

int main()
{
	char* str3 = "";
	char* str4 = NULL;
	int test3 = strlen(str3);
	printf("%d", test3);
	int test4 = strlen(str4);
	printf("%d", test4);
	return 0;
}

在这里插入图片描述

我们发现程序会挂掉,但是原因是什么呢?
在这里插入图片描述

空指针不可以进行解引用操作!!!

strlen函数模拟实现

根据上面的案例,我们来模拟实现strlen函数的主要思路是找到结束字符,对传入的地址进行判断(防止为空)。,下面就让我们来模拟实现一下吧。
测试用例:

int main()
{
	char str1[] = "abcdef";
	char str2[] = { 'a','b','c','d','e','f'};
	char* str3 = "";
	int test1 = my_strlen(str1);
	int test2 = my_strlen(str2);
	int test3 = my_strlen(str3);
	printf("%d\n%d\n%d\n", test1, test2,test3);
	return 0;
}

strlen函数模拟实现方法1:计数器法

size_t my_strlen(const char* string)
{
	assert(string);//判断传入的地址是否有效
	int count = 0;//创建一个计数器,用来存储字符个数
	while (*string++)//string会先和++结合,但是是前置++,进行解引用时使用的是未++前的值
	//当解引用未结束字符时,即'\0',这时循环条件为假,结束循环
	{
		count++;
	}
	return count;
}

在这里插入图片描述

strlen函数模拟实现方法2:指针减指针法

size_t my_strlen(const char* string)
{
	assert(string);//判断传入的地址是否有效
	char *start = string;//记录起始位置
	char *end = string;//记录结束位置
	while (*end)//判断是否为结束字符
	{
		end++;//结束位置后移
	}
	return end - start;//指针减指针,得到的是两个指针之间的元素个数
}

在这里插入图片描述

strlen函数模拟实现方法3:递归方法

size_t my_strlen(const char* string)
{
	if (*string)//如果字符串这个位置不为结束字符
	{
		return 1 + my_strlen(string + 1);
		//返回一个字符的数量,并且进行递归,传入这个元素的下一个位置,直到递归到结束字符为止
	}
	return 0;//如果是结束字符,则返回0,因为结束字符是不计算的
}

在这里插入图片描述

二、字符串的拷贝,拼接和比较

strcpy函数

函数原型:

char* strcpy(char* strDestination, const char* strSource);
//strDestination目标字符串的位置
//strSource 源头字符串的位置
//返回值,传入的目标字符串起始的位置

strcpy函数将源头字符串(包括终止的null字符)复制到目标字符串中。
官方文档解释:
在这里插入图片描述
注意:strcpy是不执行溢出检查的,所以我们使用时要注意数组的越界,防止拷贝时出现数组的越界访问!!!

strcpy函数学习使用

int main()
{
	char strDestination1[15] = "abcdefggggggg";
	char strDestination2[] = "abcde";
	char strSource[] = "hijklm";
	printf("拷贝前strDestination1:%s\n", strDestination1);
	strcpy(strDestination1, strSource);
	printf("拷贝后strDestination1:%s\n",strDestination1);
	printf("拷贝前strDestination2:%s\n", strDestination2);
	strcpy(strDestination2, strSource);
	printf("拷贝后strDestination2:%s\n", strDestination2);
	return 0;
}

在这里插入图片描述
我们发现程序崩溃,并且提示出来错误。观察发现是我们strDestination2拷贝时发生了越界,由原来的5个元素变成了6个,产生了越界访问。所以产生了越界访问错误。让我们通过内存再来详细的看一下吧。
在这里插入图片描述
上面是strDestination1中内存经过strcpy后的变化。在这里插入图片描述
打印结果可以看出strcpy确实对我们的strDestination1进行了拷贝,由于我们strDestination1的内存远大于我们源头的字符串,所以并未出现错误信息,并且程序可以正常运行。
接下来让我们看strDestination2的内存信息吧:
在这里插入图片描述
我们来看打印信息:
在这里插入图片描述
我们可以发现strDestination2内存已经不足了,但还是把源头的字符串全部放入到了strDestination2中。
在这里插入图片描述
通过错误信息来看是我们越界访问并且修改了strDestination2后面的元素造成的。

strcpy函数模拟实现

通过上面用例学习,我们已经了解了strcpy函数的使用方法和注意事项,那么我们在模拟实现就解决这些问题,并且改进一下吧。
测试用例:

int main()
{
	char strDestination1[15] = "abcdefggggggg";
	char strDestination2[] = "abcde";
	char strSource[] = "hijklm";
	printf("拷贝前strDestination1:%s\n", strDestination1);
	printf("拷贝后strDestination1:%s\n", my_strcpy(strDestination1, strSource));
	printf("拷贝前strDestination2:%s\n", strDestination2);
	printf("拷贝后strDestination2:%s\n", my_strcpy(strDestination2, strSource));
	return 0;
}

我们在刚才的测试中小小的改动了一下,为了观察函数的返回值,目的是为了实现链式访问。

strcpy函数模拟实现方法1:

char* my_strcpy(char* strDestination, const char* strSource)
{
	assert(strDestination && strSource);//判断strDestination和strSource地址是否合法
	char* str = strDestination;//用来记录strDestination的起始位置,方便对目标字符串的返回
	while ((*strDestination != '\0') && (*strSource != '\0'))//只有双方都不为‘\0’才进入循环
	{
		*strDestination = *strSource;//进行拷贝
		strDestination++;//对目标位置进行加1
		strSource++;//对源头置进行加1
	}
	if (*strSource == '\0')
	//如果源头为‘\0’,我们需要把源头的‘\0’拷贝到目标字符串中
	//如果是目标字符串中‘\0’,则证明我们在进行拷贝会产生越界,所以不需要在拷贝了
	{
		*strDestination = *strSource;
	}
	return str;//返回strDestination的起始位置
}

在这里插入图片描述
这种我们不仅实现拷贝’\0’,并且防止了数组越界的产生。

strcpy函数模拟实现方法2:对方法1的改进

char* my_strcpy(char* strDestination, const char* strSource)
{
	assert(strDestination && strSource);//判断strDestination和strSource地址是否合法
	char* str = strDestination;//用来记录strDestination的起始位置,方便对目标字符串的返回
	while ((*strDestination != '\0') && (*strDestination++ = *strSource++))
	//strDestination不为空
	//strDestination先和++结合,和*结合是++前的结果解引用等于strSource++前的结果
	//一个等号是进行赋值,当*strSource为‘\0’时,把这个结果赋给*strSource时,这个表达式的值也为0
	{
		;
	}
	return str;//返回strDestination的起始位置
}

在这里插入图片描述

strncpy函数

函数原型:

char *strncpy( char *strDest, const char *strSource, size_t count );
//strDest目标字符串
//strSource 源字符串
//count要复制的字符数
//返回值,传入的目标字符串起始的位置

strncpy函数拷贝count个字符从源字符串到目标空间。如果源字符串的长度小于count,则拷贝完源字符串之后,在目标的后边追加0,直到count个。
官方文档解释:
在这里插入图片描述
注意:目标重叠将产生未定义行为!!!

strncpy函数学习使用

int main()
{
	char strDest1[] = "abcdefggggggg";
	char strDest2[] = "abcdefggggggg";
	char strDest3[] = "abcde";
	char strSource[] = "hijklm";
	printf("拷贝前strDestination1:%s\n", strDest1);
	printf("拷贝后strDestination1:%s\n", strncpy(strDest1, strSource, 4));
	printf("拷贝前strDestination2:%s\n", strDest2);
	printf("拷贝后strDestination2:%s\n", strncpy(strDest2, strSource, 8));
	printf("拷贝前strDestination3:%s\n", strDest3);
	printf("拷贝后strDestination3:%s\n", strncpy(strDest3, strSource, 8));
	return 0;
}

在这里插入图片描述
我们发现strncpy函数也没有实现数组是否越界的检查,这需要我们模拟实现时需要思考解决的。
让我们看内存情况了解一下strncpy函数的工作情况吧:
在这里插入图片描述
当我们的源字符串大于我们要拷贝的数量时,只是对目标字符串的替换,并会加入‘\0’。
在这里插入图片描述
当我们的源字符串小于我们要拷贝的数量时,会补‘\0’,直到count个。
在这里插入图片描述
我们可以发现strncpy对超过限制的没有检查作用。

strncpy函数模拟实现

我们的测试用例用刚才的用例来测试我们自己的函数。 我们strncpy函数模拟实现要解决超过限制的没有检查的问题。

char* my_strncpy(char* strDest, const char* strSource, size_t count)
{
	assert(strDest && strSource);//判断strDest和strSource地址是否合法
	char* str = strDest;//用来记录strDest的起始位置,方便对目标字符串的返回
	while (count--)//对传入字符个数进行循环
	{
		if (*strDest == '\0')//当目标字符串到达结尾时,就返回我们记录的起始位置
		{
			return str;
		}
		else if (*strSource == '\0')//当我们源字符串到达结尾时
		{
			*strDest = *strSource;//我们把目标字符替换为'\0',并且只对目标字符串进行移动
			strDest++;
		}
		else//当我们都没有到达结尾时,对两个字符串进行移动
		{
			*strDest = *strSource;
			strDest++;
			strSource++;
		}
	}
	return str;
}

在这里插入图片描述
注意:我们模拟实现要考虑是否补’\0’和数组是否会产生越界的情况。当我们源字符串到达结尾时不可以在对我们的源字符串进行移动。

strcat函数

函数原型:

char *strcat( char *strDestination, const char *strSource );
//strDestination目标字符串的位置
//strSource 源头字符串的位置
//返回值,传入的目标字符串起始的位置

strcat函数追加一个字符串。
官方文档解释:
在这里插入图片描述
注意这里的未定义行为,还是数组的越界访问。

strcat函数学习使用

int main()
{
	char strDestination[15] = "abcdefg";
	char strSource[] = "hijklm";
	printf("追加前strDestination:%s\n", strDestination);
	printf("追加后strDestination:%s\n", strcat(strDestination, strSource));
	return 0;
}

在这里插入图片描述
在这里插入图片描述
我们需要把源字符串全部追加到目标字符串中。

strcat函数模拟实现

char* my_strcat(char* strDestination, const char* strSource)
{
	assert(strDestination && strSource);//判断strDest和strSource地址是否合法
	char* str = strDestination;//用来记录strDest的起始位置,方便对目标字符串的返回
	while (*strDestination)//把目标字符串移植末尾结束字符位置
	{
		strDestination++;
	}
	while (*strDestination++ = *strSource++)//进行字符串追加
	{
		;
	}
	return str;
}

在这里插入图片描述
但我们实现的函数由一个致命的缺陷,就是对自己的追加。问题的解决我们放在memmove函数中解决。

strncat函数

函数原型:

char *strncat( char *strDest, const char *strSource, size_t count );
//strDest目标字符串的位置
//strSource 源头字符串的位置
//count要追加的字符数
//返回值,传入的目标字符串起始的位置

strncat函数将源字符串的前count个字符追加到目标,再加上一个终止的null字符。如果源字符串的长度小于count,则只复制终止null字符之前的内容。
官方文档解释:
在这里插入图片描述
前面的返回值等我们已经很熟悉了,这里我们只看注意事项。

strncat函数学习使用

int main()
{
	char strDestination1[15] = "abcdefg";
	char strDestination2[15] = "abcdefg";
	char strSource[] = "hijklm";
	printf("追加前strDestination1:%s\n", strDestination1);
	printf("追加后strDestination1:%s\n", strncat(strDestination1, strSource,4));
	printf("追加前strDestination2:%s\n", strDestination2);
	printf("追加后strDestination2:%s\n", strncat(strDestination2, strSource,8));
	return 0;
}

在这里插入图片描述
在这里我们分别追加方式分别使用小于源字符串追加和大于源字符的追加。

strncat函数模拟实现

有了前面的基础,我们来实现一下我们自己的函数吧。

char* my_strncat(char* strDest, const char* strSource, size_t count)
{
	assert(strDest && strSource);//判断strDest和strSource地址是否合法
	char* str = strDest;//用来记录strDest的起始位置,方便对目标字符串的返回
	while (*strDest)//把目标字符串移植末尾结束字符位置
	{
		strDest++;
	}
	while (count--)//对传入字符个数进行循环
	{
		if (*strSource == '\0')//当我们源字符串到达结尾时,
		{
			*strDest = *strSource;//把结束字符追加后结束本次追加
			return str;
		}
		else//对两个字符串进行移动
		{
			*strDest = *strSource;
			strDest++;
			strSource++;
		}
	}
	*strDest = '\0';//当循环结束时,代表我们源字符串的不完全追加,需要我们额外的补结束字符
	return str;
}

在这里插入图片描述
我们自己的实现可以发现思路就是我们strncpy和我们strcat的结合。当让,我们这个依然没有解决追加自己的问题。

strcmp函数

函数原型:

int strcmp( const char *string1, const char *string2 );
//string1要比较的以Null结尾的字符串
//string2 要比较的以Null结尾的字符串
//返回值,这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。

strcpy函数用于两个字符串比较
官方文档解释:
在这里插入图片描述

strcmp函数学习使用

int main()
{
	char string1[] = "abcde";
	char string2[] = "abcde";
	char string3[] = "abcd";
	char string4[] = "abcdf";
	int cmp1 = strcmp(string1, string2);//相等情况
	int cmp2 = strcmp(string1, string3);//string1>string2
	int cmp3 = strcmp(string1, string4);//string1<string2
	printf("%d %d %d", cmp1, cmp2, cmp3);
	return 0;
}

在这里插入图片描述
这里我们直接用返回值来判断了。
注意:strcpy函数用于两个字符串比较是一个字符一个字符进行比较。

strcmp函数模拟实现

int my_strcmp(const char* string1, const char* string2)
{
	assert(string1 && string2);//判断string1和string2地址是否合法
	while (*string1 != '\0' && *string2 != '\0')//只有当两个都不为空字符是才进行比较
	{
		if (*string1 > *string2)
		{
			return 1;
		}
		else if (*string1 < *string2)
		{
			return -1;
		}
		else//当两个字符相等时比较下一个字符
		{
			string1++;
			string2++;
		}
	}
	//循环结束,要判断是单个结束还是两个一起结束
	if (*string1 != '\0')
	{
		return 1;
	}
	else if (*string2 != '\0')
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

在这里插入图片描述

strncmp函数

函数原型:

int strncmp( const char *string1, const char *string2, size_t count );
//string1要比较的以Null结尾的字符串
//string2 要比较的以Null结尾的字符串
//count,要比较的字符数
//返回值,这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。

strncpy函数用于两个字符串前count个字符的比较。
官方文档解释:
在这里插入图片描述

strncmp函数学习使用

int main()
{
	char string1[] = "abcde";
	char string2[] = "abcd";
	int cmp1 = strncmp(string1, string2,4);//相等情况
	int cmp2 = strncmp(string1, string2,5);//string1>string2
	printf("%d %d", cmp1, cmp2);
	return 0;
}

在这里插入图片描述
类比strcmp,我们很容易理解我们的strncmp函数。

strncmp函数模拟实现

int my_strncmp(const char* string1, const char* string2, size_t count)
{
	assert(string1 && string2);//判断string1和string2地址是否合法
	while (count--)//进行要比较的字符次数的循环
	{
		if (*string1 > *string2)
		{
			return 1;
		}
		else if (*string1 < *string2)
		{
			return -1;
		}
		else//当两个字符相等时比较下一个字符
		{
			string1++;
			string2++;
		}
	}
	//循环结束,证明两个相等
	return 0;
}

在这里插入图片描述
我们strncmp函数的思想就是循环count次,当count的元素都相同时,证明这两个字符串相等。

三、字符串的查找

strstr函数

函数原型:

char *strstr( const char *string, const char *strCharSet );
//string要搜索的以Null结尾的字符串
//strCharSet 要搜索的以Null结尾的子字符串
//返回值,返回一个指针,指向字符串中第一个出现的strCharSet,如果字符串中没有出现strCharSet则返回NULL

strstr函数在一个字符串中查找子串。
官方文档解释:
在这里插入图片描述
这个函数我们需要注意返回值为NULL还是返回第一次出现的位置,或者是被查找的字符串。

strstr函数学习使用

int main()
{
	char str[] = "abcccdefgh";
	char *str1 = strstr(str, "ccdef");
	char *str2 = strstr(str, "");
	char *str3 = strstr(str, "acbd");
	puts(str1);
	puts(str2);
	if (str3 == NULL)
	{
		printf("str3 is NULL\n");
	}
	return 0;
}

在这里插入图片描述
我们分别查询在字符串中,不在字符串中和空字符串的情况,充分的了解了各种情况下的返回值。

strstr函数模拟实现

char* my_strstr(const char* string, const char* strCharSet)
{
	assert(string && strCharSet);//判断string1和string2地址是否合法
	if (*strCharSet == '\0')//判断传入的要查找的子字符是否为空字符串。
	{
		return string;
	}
	char* retu = string;//一个节点用来存储要返回的位置
	char* hand = strCharSet;//用来记录子串的起始位置
	while (*retu)//当要返回的位置不为空时
	{
		strCharSet = hand;//把字串的位置置为起始位置
		string = retu;//把被查找的位置改为现在存储返回的位置
		while (*string == *strCharSet)//如果两个字符相等,则比较下一个字符
		{
			if (*strCharSet == '\0')//判断是否两个都到达结束位置
			{
				return retu;
			}
			string++;
			strCharSet++;
		}
		if (*strCharSet == '\0')//当strCharSet到达结束标志时,证明改返回位置是查找的起始位置
		{
			return retu;
		}
		retu++;
	}
	return NULL;
}

在这里插入图片描述
在这里插入图片描述
在这里retu指针的意义是为了防止连续出现多的重复和子串一样元素,但最后不同时,便于找到开始查找的位置。hand指针的意义是每次对比结果不同时,把字串的指针位置回到开始。

strtok函数

函数原型:

char *strtok( char *strToken, const char *strDelimit );
//strToken包含令牌的字符串
//strDelimit字符串,分隔符字符集

strtok函数用于查找字符串中的下一个标记。
官方文档解释:
在这里插入图片描述
注意:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。(strtok函数找到strToken中的下一个标记,并将其用 ‘\0’ 结尾,返回一个指向这个标记的指针。)
strtok函数函数返回值讨论:
1.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
2.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
3.如果字符串中不存在更多的标记,则返回 NULL 指针。

strtok函数学习使用

看了上面的解释,我们看的雨里雾里的。让我们通过实践来学习一下吧。

int main()
{
	char* strToken = "xiao/hei.123@com.xiaohei";
	char* strDelimit = "/.@";//strDelimit放置的分隔字符为'/','.','@'
	char arr[30] = { 0 };
	strcpy(arr, strToken);//将数据拷贝一份,处理arr数组的内容
	char* str = NULL;
	for (str = strtok(arr, strDelimit); str != NULL; str = strtok(NULL, strDelimit))
	//函数第一次需要把要分隔的字符串传入
	//后续改函数会记住上次分隔符的位置
	{
		printf("%s\n", str);
	}
	return 0;
}

在这里插入图片描述
这里注意对同一个字符串进行查找时第二次要传NULL,因为该函数会记录上次查找的位置。

四、错误信息报告

strerror函数

函数原型:

char *strerror( int errnum );
//errnum错误编号
//返回值,指向错误消息字符串的指针

strerror函数用于返回错误码所对应的错误信息。
官方文档解释:
在这里插入图片描述

strerror函数学习使用

下面我们演示一下用法吧

#include <errno.h>//必须包含的头文件
int main()
{
	FILE* pf;
	pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("Error: %s\n", strerror(errno));
	}
	int* p = (int*)malloc(sizeof(int) * 0xFFFFFFFFF);
	if (p == NULL)
	{
		printf("Error: %s\n", strerror(errno));
	}
	return 0;
}

在这里插入图片描述
注意:返回的错误信息如果不即时打印就会被下一个错误信息覆盖。
和这个函数相似的还有一个perror函数。

int main()
{
	FILE* pf;
	pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("Error: %s\n", strerror(errno));
		perror("Error");
	}
	int* p = (int*)malloc(sizeof(int) * 0xFFFFFFFFF);
	if (p == NULL)
	{
		printf("Error: %s\n", strerror(errno));
		perror("Error");
	}
	return 0;
}

在这里插入图片描述

五、内存操作的函数

我们对内存的操作都是针对所有类型的,所以我们的形参都是用void指针接收,返回值也是void类型的指针。

memcpy函数

函数原型:

void *memcpy( void *dest, const void *src, size_t count );
//dest目标字符串的位置
//src 源头字符串的位置
//返回值,传入的目标字符串起始的位置

memcpy函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。
官方文档解释:
在这里插入图片描述
memcpy函数在遇到 ‘\0’ 的时候并不会停下来。如果src和dest有任何的重叠,复制的结果都是未定义的。

memcpy函数学习使用

struct stu
{
	char name[10];
	int age;
};
int main()
{
	struct stu stu1 = { {"zhangsan"},20 };
	struct stu stu2;
	memcpy(&stu2, &stu1, sizeof(struct stu));
	printf("%s %d", stu2.name, stu2.age);
	return 0;
}

在这里插入图片描述

memcpy函数模拟实现

在实现前我们要先思考为什么库函数中用的void指针来接收的。好处是什么?
从上面的例子可以看出,我们这个实现的是可以拷贝所有的类型,所以类型我们把它限定为void,因为void指针可以接收任何指针。

void* my_memcpy(void* dest, const void* src, size_t count)
{
	assert(dest && src);
	void* begin = dest;
	while (count--)
	{
		*((char*)dest) = *((char*)src);//把dest和src转化为字符指针,对字符指针进行移动
		(char*)dest += 1;
		(char*)src += 1;
	}
	return dest;
}

在这里插入图片描述
这里面我们用字符指针进行移动,一个字符一个字符的进行拷贝,这样可以做到拷贝任何类型的数据。

memmove函数

函数原型:

void *memmove( void *dest, const void *src, size_t count );
//dest目标字符串的位置
//src 源头字符串的位置
//返回值,传入的目标字符串起始的位置

memmove函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。
memmove函数和memcpy函数的区别:
1.memmove函数处理的源内存块和目标内存块是可以重叠的(有的编译器memmove函数和memcpy功能已经一模一样了)。
2. 如果源空间和目标空间出现重叠,最好使用memmove函数处理,memmove函数就是为了拷贝相同区域而生的
官方文档解释:
在这里插入图片描述

memmove函数学习使用

int main()
{
	char arr1[15] = "abcdefghigkl";
	char arr2[15] = "abcdefghigkl";
	char arr3[15] = "abcdefghigkl";
	memmove(arr1 + 5, arr1, 5);
	memmove(arr2 + 5, arr2 + 3, 5);
	memmove(arr3 + 5, arr3 + 7, 5);
	printf("%s\n", arr1);
	printf("%s\n", arr2);
	printf("%s\n", arr3);
	return 0;
}

在这里插入图片描述
我们看到正对三种情况都可以正确的把字符串进行复制,接下来我们在函数模拟实现中着重的讨论一下如何复制重叠的区域吧,解决我们上面的strcpy等函数的缺陷吧。

memmove函数模拟实现

先让我们看看复制自己会有什么情况吧:
在这里插入图片描述

void* my_memmove(void* dest, const void* src, size_t count)
{
	assert(dest && src);
	void* begin = dest;
	if (*((char*)dest) - *((char*)src) > 0)
	{
		while (count--)
		{
			*((char*)dest + count) = *((char*)src + count);//把dest和src转化为字符指针,对字符指针进行移动
		}
	}
	else
	{
		while (count--)
		{
			*((char*)dest) = *((char*)src);//把dest和src转化为字符指针,对字符指针进行移动
			(char*)dest += 1;
			(char*)src += 1;
		}
	}
	return begin;
}

思路:
当源头右边重叠时,我们从源头的左边进行拷贝时会把我们的值进行覆盖,所以我们要从源头右边进行拷贝
情况如下图所示:
在这里插入图片描述
当源头左边重叠时,我们从源头的右边进行拷贝时会把我们的值进行覆盖,所以我们要从源头左边进行拷贝
情况如下图所示:
在这里插入图片描述我们把两个元素转化为字符指针,通过指针相减可以得到两个指针的前后关系。然后选择合适的循环来进行拷贝。当两个不交叉重叠时,我们使用哪种方法都相同。
在这里插入图片描述

六、比较实用的字符操作函数

下面是一些实用的字符操作函数:

函数如果参数符合下列条件就返回真
iscntr任何控制字符
isspace空白字符
isdigit十进制数字
isxdigit十六进制数字
islower小写字母
isupper大写字母
isalpha字母
isalnum字母或者数字
ispunct标点符号
isgraph任何图形字符
isprint任何可打印字符
函数字符转换
tolower大写字母转换为小写字母
toupper小写字母转换为大写字母

下面演示几个吧:

int main()
{
	char arr[] = "aB1cD0eFg2HiGk";
	int sz = strlen(arr);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		if (isupper(arr[i]))//如果是大写,就进行字符转化
		{
			arr[i] = tolower(arr[i]);
		}
		else if (islower(arr[i]))//如果是小写,就进行字符转化
		{
			arr[i] = toupper(arr[i]);
		}
		else if (isdigit(arr[i]))//如果是十进制数字,就打印
		{
			printf("%c ", arr[i]);//我们存的数字字符,如果用%d进行打印,需要让该元素减去一个字符0
		}
	}
	printf("\n%s\n", arr);
	return 0;
}

在这里插入图片描述

总结

相信你对字符串相关函数已经有了深刻的了解,在实战中多多使用吧。欢迎点赞留言呀。💕

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

某科技公司提前批测试岗

文章目录 题目 今天给大家带来一家提前批测试岗的真题&#xff0c;目前已经发offer 题目 1.自我介绍 2.登录页面测试用例设计 3.如何模拟多用户登录 可以使用Jmeter,loadRunner性能测试工具来模拟大量用户登录操作去观察一些参数变化 4.有使用过Jmeter,loadRunner做过性能压…

为什么 CSS 这么难学?

前言 CSS难其实就难在其内容的多变上&#xff0c;我觉得这些其实都可以通过大量的练习来解决&#xff0c;去记再多的东西不如写几个demo或者小项目来的收获大&#xff0c;当然练完项目所需要的总结是必需的&#xff01;下面我推荐整理了一些学习css相关的网站和项目&#xff0…

谁会拒绝一篇关于【python装饰器】的友情分享呢~

一、什么是装饰器 1. python装饰器是用于拓展原来函数功能的一种函数&#xff0c;目的是在不改变原函数的情况下&#xff0c;给函数增加功能。2. 装饰器是通过闭包实现&#xff0c;所以讲装饰器首先得知道什么是闭包。 二、什么是闭包 1、什么是闭包 1. 一个定义在函数内部的…

【Jmeter】配置不同业务请求比例,应对综合场景压测

目录 背景 Jmeter实现&#xff08;Random&#xff09; 在测试计划下&#xff0c;我们右键“添加”——配置原件——Random Variable 设置随机数的变量名称为num&#xff0c;设置取值范围0到100 右键线程组添加“逻辑控制器”——如果&#xff08;if&#xff09;控制器 当…

Win11系统优化

当你看到这篇文章&#xff0c;我相信你电脑卡的已经不行不行的了&#xff0c;执行完快抢救一下。 一、设置win11系统显示效果&#xff08;减轻系统运行压力&#xff09; &#xff08;1&#xff09;.点击桌面的&#x1f50d;按钮&#xff0c;然后在输入框内输入“查看高级系统设…

卷积相关点

从传统的神经网络到卷积神经网络 一.传统的神经网络&#xff08;全连接网络&#xff09; 参数太多&#xff0c;冗余度高&#xff0c;容易过拟合&#xff0c;难以训练 二.卷积神经网络 1.空间平移的不变性&#xff1a;当输入的图像在空间上发生平移时&#xff0c;CNN的相应不…

f12 CSS网页调试_css样式被划了黑线怎么办

我的问题是这样的 class加上去了,但是样式不生效,此时可能是样式被其他样式覆盖了, 解决方案就是 给颜色后边添加一个!important

Java—抽象类

目录 1.抽象类概念 2.抽象类语法 3.抽象类特性 4.抽象类作用 1.抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果 一个类中没有包含足够的信息来描绘一个具体的…

10年测试经验分享 —— 新手如何去做性能测试?

大家好我是彭于晏&#xff1a;一名北漂10年的测试&#xff0c;今天给大家带来关于性能测试的一些个人经验和测试新手如何快速上手性能测试。 1、性能测试是什么&#xff1f; 一句话概括&#xff1a;不断的通过不同场景的系统表现去探究系统设计与资源消耗之间的平衡。 具体一…

uC-OS2 V2.93 STM32L476 移植:系统启动篇

前言 前两篇已经 通过 STM32CubeMX 搭建了 NUCLEO-L476RG 的 STM32L476RG 的 裸机工程&#xff0c;下载了 uC-OS2 V2.93 的源码&#xff0c;并把 uC-OS2 的源文件加入 Keil MDK5 工程 本篇适配 uC-OS2 的 系统定时器&#xff08;Systick&#xff09;与 PendSV_Handler&#xf…

jwt+shiro认证

文章目录 jwt shiro 认证jwt 使用引入依赖编写 jwt 工具类测试 shiro jwt 认证项目文件路径&#xff1a;引入依赖shiro.ini重写 Realm认证服务接口认证服务接口实现类自定义 token测试 代码来自于小傅哥 《API网关》 项目 jwt shiro 认证 jwt 使用 引入依赖 <dependen…

一个SpringBoot 项目能处理多少请求?

这篇文章带大家盘一个读者遇到的面试题哈。 根据读者转述&#xff0c;面试官的原问题就是&#xff1a;一个 SpringBoot 项目能同时处理多少请求&#xff1f; 不知道你听到这个问题之后的第一反应是什么。 我大概知道他要问的是哪个方向&#xff0c;但是对于这种只有一句话的…

【C语言督学训练营 第二十一天】汇编语言零基础入门

文章目录 前言1.C语言源文件转汇编2.汇编指令格式3.汇编常用指令3.1 相关寄存器3.2 常用指令3.3 数据传送指令3.4 算术/逻辑运算指令3.5 控制流指令3.6 条件码 4.如何定义汇编中的变量5.选择循环汇编实战6.函数调用汇编实战7.C语言源文件转机器指令 前言 汇编语言是一种功能很强…

WebDAV之π-Disk派盘 + CX文件管理器

CX文件管理器是一款好用的文件管理工具。它的功能非常的丰富,它能满足用户对文件管理的需求,而且功能也是一目了然,可以帮助用户快速的对文件进行操作。这款软件还支持SFTP、WebDAV、FTP等下载访问方式。手机和电脑在同一局域网下,可以使用FTP或派盘从你的电脑直接访问手机…

selenium页面切换操作

selenuim页面切换 webdriver只能在一个页面里对元素进行识别和定位。如果有多个页面操作的时候&#xff0c;要先进行页面切换。 切换分两种 1 iframe内嵌页面切换&#xff08;框架集切换&#xff09; # 1 获取iframe标签 iframe driver.find_element_by_tag_name(iframe) …

echarts绘制甘特图

说在前面 项目上有需求&#xff0c;需要在大屏上展示进度甘特图&#xff0c;调研了DHTMLX和普加甘特图&#xff0c;效果都不是特别符合需求现状&#xff0c;查询了一些博客&#xff0c;决定使用echarts来绘制甘特图。 实现效果展示 实现思路分析 1、应该采用柱状图&#xff…

LeetCode404. 左叶子之和

404. 左叶子之和 文章目录 [404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/)一、题目二、题解方法一&#xff1a;递归方法二&#xff1a;迭代 一、题目 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9…

个人信息保护合规审计如何做?

8月3日&#xff0c;为指导、规范个人信息保护合规审计活动&#xff0c;根据《中华人民共和国个人信息保护法》等法律法规&#xff0c;国家互联网信息办公室就《个人信息保护合规审计管理办法&#xff08;征求意见稿&#xff09;》&#xff08;简称《办法》&#xff09;及配套的…

【Apifox】Apifox设置全局Token:

文章目录 一、获取登录Token和设置全局变量&#xff1a;二、设置全局参数&#xff1a;三、效果&#xff1a; 一、获取登录Token和设置全局变量&#xff1a; 二、设置全局参数&#xff1a; 三、效果&#xff1a;

盘点7月Sui生态发展,了解Sui的近期成长历程!

自5月Sui主网上线三个月以来&#xff0c;7月是Sui网络进行最多次重要更新的一个月&#xff0c;Sui网络和生态正呈指数形式不断向上发展。为吸引更多的项目或开发者加入生态构建以及活跃用户参与生态&#xff0c;Sui基金会推出了Builder House、黑客松、Bullshark Quests、NFT再…