【str家族】如何使用处理字符和字符串的库函数

144 阅读13分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串 中或者 字符数组 中。

字符串常量 适用于那些对它不做修改的字符串函数.

但比较友好的是C语言有处理字符和字符串的一些库函数,方便我们对字符和字符串进行一些操作。

所以本节我会重点介绍处理字符和字符串的一些常用库函数,让大家学会使用,并且了解它们的实现原理和一些注意事项。 @TOC

strlen - 求字符串长度

1.函数介绍

 size_t strlen( const char *string );

strlen函数是一个用于求字符串长度的库函数。它的参数是被求长度的字符串的起始地址,返回值是一个无符号整型。 举例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "helloworld";
	size_t ret = strlen(arr);
	return 0;
}

我们定义一个size_t型的变量ret接收函数的返回值,即字符串长度,结果是10

==注意==

  • 1.参数指向的字符串要以’\0’结束。
  • 2.strlen返回的是在字符串中’\0’之前出现的字符个数(不包含’\0’)。

int main()
{
	//char arr[] = "abc";//'c'后面是'\0',所以字符串长度是3
	char arr[] = { 'a','b','c' };//'c'后面不一定是'\0',所以算出的字符串长度是随机值
	size_t len = strlen(arr);
	printf("%d\n", len);
	return 0;
}

运行结果: 在这里插入图片描述


  • 3.注意函数的返回值为size_t,是无符号的(易错)。

:下面运行结果应该是什么?

if (strlen("abc") - strlen("abcdef") > 0)
	{
		printf(">\n");
	}
	else
	{
		printf("<=\n");
	}
	
	return 0;

答案是大于 strlen接收的是无符号整形,两个无符号整数相减在算数层面还是无符号整数,原本的-3不算符号位,即补码当作原码,会变成一个很大的整数。 很显然,如果我们事先不知道strlen函数的返回值为size_t,很容易认为结果是<=

2.strlen模拟实现(三种方式)

1.计数器

我们定义一个变量为count,如果传入的指针指向的内容不是’\0’,那么count++,同时指针后移一位,循环往复,直到找到’\0’时返回count即可。

size_t my_strlen1(const char* str)
{
	size_t count = 0;//计数器
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}

2.递归

我们一进入函数体就判断传入指针指向的内容是否为’\0’,如果是就返回0,不是就返回1+my_strlen2(str+1),如此进行下去,直到递归到字符串结尾找到’\0’,这时再一步步将值返回回来即可。

size_t my_strlen2(const char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen2(str + 1);
}

3.指针

进入函数体时,我们事先定义一个指针变量将传入的指针保存下来,然后将传入的指针向后移,直到遇到’\0’时,我们返回当前指针与保存的起始位置指针的差值,即是字符串长度。(指针与指针的差的绝对值是两个指针之间的元素个数)

size_t my_strlen3(const char* str)
{
	const char* p = str;//保存起始位置
	while (*str != '\0')
		str++;
	return str - p;
}

strcpy - 字符串拷贝

1.函数介绍

char *strcpy( char *Destination, const char *Source );

strcpy函数是一个用于拷贝字符串的函数,即将一个字符串中的内容拷贝到另一个字符串中(会覆盖原字符串内容)。它的参数是两个指针,第一个指向的是拷贝字符串的目的地的起始位置,即要将字符串拷贝到什么地方;第二个指向的是要拷贝字符串的内容的起始位置,即需要拷贝的字符串。它的返回值是目标空间的起始位置。

==注意:==

  • 源字符串(需要被拷贝的字符串)必须以’\0’结束。
  • 会将源字符串中的’\0’一同拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。

举例:

int main()
{
	char arr[20] = { 0 };
	//arr = "hello";//err 
	char arr1[10] = "abcaaa";
	char arr2[] = "def";
	strcpy(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

运行结果是def

在这里插入图片描述 深入来看,我们通过监视看到: 完成拷贝后,arr1只被覆盖了"abca"前四个字符,即拷贝了arr2中的"def"和'\0',但是输出时默认遇到'\0'结束,所以打印结果只显示"def"三个字符

2.strcpy模拟实现

进入函数体时先定义一个指针变量保存目标空间的起始位置,便于之后返回。然后将源字符串中的字符一一赋值给目标空间,直到遇到源字符串中的’\0’,将’\0’也赋值给目标空间后结束赋值,最后返回目标空间的起始位置。

char* my_strcpy(char* dest, char* src)
{
	char* ret = dest;//保存目标空间的起始位置
	assert(dest != NULL);//断言,dest为空指针时报错
	assert(src != NULL);//断言,src为空指针时报错
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

strcat - 字符串追加

1.函数介绍

char *strcat( char *Destination, const char *Source );

strcat函数是一个用于追加字符串的函数,即将一个字符串中的内容追加到另一个字符串后面(不会覆盖原字符串内容)。它的参数是两个指针,第一个指向的是追加字符串的目的地的起始位置,即要将字符串追加到哪个字符串后面;第二个指向的是要追加字符串的内容的起始位置,即需要追加的字符串。它的返回值是目标空间的起始位置。

==注意:==

  • 源字符串必须以’\0’结束。
  • 目标空间必须足够大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • 字符串不能给自己追加(’\0’被覆盖,无终止条件)。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world!";
	strcat(arr1, arr2);
	return 0;
}

追加结束后arr1数组中的内容变成"hello world!"。

2.strcat模拟实现

进入函数体依然先定义一个指针变量用于存放目标空间的起始位置,便于之后返回。然后用循环先找到目标空间的’\0’,之后从’\0’的位置开始追加源字符串的内容,直到追加到源字符串中的’\0’为止。最后返回目标空间的起始位置。

char* my_strcat(char* dest, const char* src)
{
	assert(dest != NULL);//断言,dest为空指针时报错
	assert(src != NULL);//断言,src为空指针时报错
	char* ret = dest;
	//找到目标空间的'\0'
	while (*dest)
	{
		dest++;
	}
	//追加
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

strcmp - 字符串比较

1.函数介绍

int strcmp( const char *string1, const char *string2 );

strcmp函数是一个用于比较两个字符串内容的函数。它的参数是两个指针,指针分别指向两个待比较字符串的起始位置。它的返回值是一个整型数字。当string1大于string2的时候返回一个大于0的数;当string1等于string2的时候返回0;当string1小于string2的时候返回一个小于0的数。

==注意:==

  • 字符串比较的不是字符串长度的大小,而是两个字符串中对应位置字符的ASCII值。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "hello world!";
	char arr2[20] = "hello bitt!";
	int ret = strcmp(arr1, arr2);
	return 0;
}

比较字符串的时候发现前面字符的ASCII值都相同,直到比较到字符’w’和字符’b’时,发现字符’w’的ASCII值大于字符’b’的ASCII值,于是返回一个大于0的数。

2. strcmp模拟实现

进入函数体直接比较起始位置的字符的大小。如果相同并且不为’\0’那么继续比较下一对字符的大小;如果相同并且为’\0’那么说明字符串比较完毕,那么直接返回0;如果不同则直接返回str1与str2中对应字符的ASCII值的差值(当str1中对应字符大于str2中的对应字符时返回正值,当str1中对应字符小于str2中的对应字符时返回负值)。

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 != NULL);//断言,str1为空指针时报错
	assert(str2 != NULL);//断言,str2为空指针时报错
	while (*str1 == *str2)
	{
		if (*str1 == '\0')//字符串全部比较完毕
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

strncpy、strncat、strncmp - 限制操作长度

我们发现strcpy是将一个字符串全部拷贝到另一个字符串,strcat是将一个字符串全部追加到另一个字符串后面,strcmp也是比较两个字符串的全部内容,这类操作函数称为长度不受限制的字符串操作函数。

那么我们如果操作字符串时并不想操作整个字符串,而只想操作字符串的一部分怎么办呢?

库函数中的strncpy、strncat、strncmp便解决了这个问题。

1.strncpy

char *strncpy( char *Dest, const char *Source, size_t count );

strncpy的参数与strcpy相比较多出了一个参数,而这个参数就是需要被操作的字符个数。

==注意:==

  • 当操作数小于等于源字符串中的字符个数时,操作数的大小决定被拷贝的字符个数。
  • 操作数大于源字符串中字符的个数时,strncpy函数将源字符串中的字符拷贝到目标空间后不够的将用’\0’填充。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[10] = "##########";
	char arr2[] = "abcd";
	strncpy(arr1, arr2, 3);
	strncpy(arr1, arr2, 6);
	return 0;
}

在这里插入图片描述 当操作数为3时,拷贝结束后arr1数组中存放的是"abc#######" 在这里插入图片描述 当操作数为6时,拷贝结束后arr1数组中存放的是"abcd\0\0####"

2.strncat

char *strncat( char *Dest, const char *Source, size_t count );

strncat的参数与strcat相比较也多出了一个参数,而这个参数也就是需要被操作的字符个数。

==注意:==

  • 当操作数小于源字符串中的字符个数时,操作数的大小决定被追加的字符个数,并在追加完后再追加一个’\0’。
  • 当操作数大于等于源字符串中的字符个数时,将源字符串内容全部追加到目标空间便结束追加,并在追加完后再追加一个’\0’表示结束。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[10] = "abc\0#####";
	char arr2[] = "def";
	strncat(arr1, arr2, 2);
	strncat(arr1, arr2, 5);
	return 0;
}

在这里插入图片描述 当操作数为2时,拷贝结束后arr1数组中存放的是"abcde\0###"

注意:从监视我们可以看出,这里所谓的追加其实是在要追加的目标字符串遇到'\0'的位置开始进行覆盖,除去覆盖位置arr1原本存在的字符并不会显示,只是因为输出时一般默认遇到'\0'结束,所以才达到所谓字符串追加的效果。

在这里插入图片描述 当操作数为5时,拷贝结束后arr1数组中存放的是"abcdef\0##"

3.strncmp

int strncmp( const char *string1, const char *string2, size_t count );

strncmp的参数与strcmp相比较也多出了一个参数,而这个参数也就是需要比较的字符个数。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcde";
	char arr2[] = "abcdf";
	int ret1 = strncmp(arr1, arr2, 4);
	int ret2 = strncmp(arr1, arr2, 5);
	return 0;
}
  • 当操作数为4时,我们只比较了arr1和arr2的前4个字符,而它们前4个字符都相同,所以返回的是0;
  • 而当操作数为5的时候,我们比较了arr1和arr2的前5个字符,因为字符’e’的ASCII值小于字符’f’的ASCII值,所以返回一个负值。

strstr - 字符串查找

1.函数介绍

char *strstr( const char *string, const char *strCharSet );

strstr函数可以在一个字符串(字符串1)中查找另一个字符串(字符串2),如果字符串2存在于该字符串1中,那么就返回字符串2在字符串1中第一次出现的起始位置,如果在字符串1中找不到字符串2,那么就返回空指针(NULL)。它的第一个参数是字符串1的起始位置,第二个参数是字符串2的起始位置。

==注意:==

  • 若字符串2为空字符串,则返回字符串1的起始位置。
  • strstr函数返回的是第一次出现的位置的起始位置,而不是出现几次就返回几个起始位置。

举例:在字符串"abcdefbcdef"中查找字符串"cde"

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcdefbcdef";
	char arr2[] = "cde";
	char* ret = strstr(arr1, arr2);//在arr1中查找arr2字符串第一次出现的位置
	if (ret != NULL)
		printf("%s\n", ret);
	else
		printf("找不到\n");
	return 0;
}

在这里插入图片描述

2.strstr模拟实现

我们需要设置3个指针变量来辅助实现函数功能。

  • cp指针: 记录每次开始匹配时的起始位置,当从该位置开始匹配时就找到了目标字符串,便于返回目标字符串出现的起始位置;当从该位置开始没有匹配成功时,则从cp++处开始下一次的匹配。
  • p1和p2指针: 通过判断p1和p2指针解引用后是否相等来判断每个字符是否匹配成功,若成功,则指针后移比较下一对字符;若失败,p1指针返回cp指针处,p2指针返回待查找字符串的起始位置。

实例:在字符串"abbbcdef"中查找字符串"bbc":

在这里插入图片描述 直到当p2指向的内容为\0时,便说明待查找字符串中的字符已经被找完,也说明了从当前cp位置开始匹配能够找到目标字符串,所以此时返回指针cp即可。

代码实现:

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 != NULL);//断言,当str1为空指针报错
	assert(str2 != NULL);//断言,当str2为空指针报错
	const char* cp = str1;//记录开始匹配时的起始位置
	if (*str2 == '\0')//要查找的字符串为空字符串
		return (char*)str1;
	while (*cp)
	{
		const char* p1 = cp;
		const char* p2 = str2;
		while ((*p1!='\0') && (*p2!='\0') && (*p1 == *p2))
		{
			p1++;
			p2++;
		}
		if (*p2 == '\0')//目标字符串已被查找完
			return (char*)cp;
		cp++;
	}
	return NULL;//找不到目标字符串
}

strtok - 字符串分割

函数原型:

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

参数:

str,待分割的字符串;delimiters,分割符字符串。

该函数用来将字符串分割成一个个片段。参数str指向欲分割的字符串,参数delimiters则为分割字符串中包含的所有字符。当strtok()在参数str的字符串中发现参数delimiters中包涵的分割字符时,则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数str字符串,往后的调用则将参数s设置成NULL。每次调用成功则返回指向被分割出片段的指针。

需要注意的是,使用该函数进行字符串分割时,会破坏被分解字符串的完整,调用前和调用后的s已经不一样了。所以我们可以将待分割的字符串拷贝一份使用,防止原数据被修改。

==注意:==

  • strtok函数找到str中的一个标记时,会将其用 \0结尾(即用'\0'覆盖)并返回这个标记的首地址。
  • strtok函数会改变str函数,所以在使用strtok函数切分字符串时应该临时拷贝一份,并且内容可修改。
  • strtok函数的第一个参数不为NULL时,函数将找到str中的第一个标记,并保存它在字符串中的位置。
  • strtok函数的第一个参数为NULL时,函数将从同一个字符串中被保存的位置开始查找它的下一个标记。
  • 若字符串中不存在更多的标记,则返回NULL指针。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "275207042@qq.com";//待分割字符串
	char arr2[] = "@.";//分隔符的字符集合
	char arr3[20] = { 0 };
	strcpy(arr3, arr1);//将数据拷贝一份使用,防止原数据被修改
	char* token = strtok(arr3, arr2);//第一次传参需传入待分割字符串首地址
	while (token != NULL)//说明还未分割完
	{
		printf("%s\n", token);
		token = strtok(NULL, arr2);//第二次及以后的第一个参数设为NULL
	}
	return 0;
}

运行结果: 在这里插入图片描述

通过 '@' 和 '.' 将 "275207042@qq.com" 分为三部分。

strerror、perror - 错误报告函数

1.strerror

char *strerror( int errnum );

strerror函数可以把错误码转换为对应的错误信息,返回错误信息对应字符串的起始地址。

#include <errno.h>//必须包含的头文件 我们需要知道,库函数在使用的时候如果发生错误,都会有对应的错误码,而这些错误码都会被存放在errno这个全局变量中,如果要使用这个全局变量,我们需要引其对应的头文件:#include<errno.h>

举例: 在这里插入图片描述

strerror: 只负责将错误码转换为对应的错误信息,不打印

和strerror有相似功能的还有perror函数

2.perror

void perror( const char *string );

相比于strerror函数,perror函数会首先把错误码转化为错误信息,然后打印错误信息(包含了自定义的信息),更方便实用。

举例:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	//打开文件失败的时候,会返回空
	FILE* pf = fopen("test.txt", "r");//打开test.txt文件阅读
	if (pf == NULL)
	{
		//两者比较
		printf("%s\n", strerror(errno));
		perror();

	return 0;
}

在这里插入图片描述 很显然,同样的效果,perror使用起来更简洁方便。


– the End –

好了,今天关于“str家族”的分享就到这里了。 期待下次再见~~~

本文收录于CSDN专栏C语言进阶

关注作者,持续阅读作者的文章,学习更多知识! blog.csdn.net/weixin_5330…