当前位置:首页 » 《随便一记》 » 正文

【C语言】常用字符串函数有哪些?如何使用?如何模拟实现?从逻辑底层详细讲起,一文迅速搞懂_JasonTuring的博客

27 人参与  2022年01月11日 15:07  分类 : 《随便一记》  评论

点击全文阅读


文章目录

  • 一、字符数组与“字符串数组”?
  • 二、常见的字符串操作函数有哪些?
    • 1、strlen()
    • 2、strcpy()
    • 3、strcat()
    • 4、strcmp()
    • 5、strstr()

一、字符数组与“字符串数组”?

许多初学者面对字符串总是有一种莫名的恐惧感。C语言并没有纯字符串数组的说法,但其本质上不过就是“字符们”的集合,于是我们从字符数组的定义出发是可以定义“字符串”数组的。但需要注意的是,当不给定长度时,当以单个字符定义数组时,字符后没有\0作为终止符,而当以字符串定义数组时,在字符串末尾会以\0作为字符串的结束标志。
如:

char a[]={'a','b','c','d','e','f'};
//在不指定字符数组长度时,以单个字符进行定义时,在末尾无\0作为终止符
char b[]="abcdef";
//在不指定字符数组长度时,以字符串进行定义时,在末尾用\0作为终止符

不要小看这个\0,它接下来扮演着解开字符串神秘面纱的重要角色。
我知道这时候有的小伙伴会问,上面是不指定字符数组长度,那如果改成指定长度呢?
如:

char A[6]={'a','b','c','d','e','f'};
char B[6]="abcdef";
//当给定数组长度并且用指定元素内容占满时,
//无论以字符串定义或是字符定义的数组在末尾都不会直接存放\0,
//此时用字符串操作函数如strlen对长度进行检索,将发生难以预料的风险。
char A1[6]={'a','b','c','d','e'};
char B1[6]="abcde";
//此时在e的后面数组A1与B1都会存放\0来维持长度6
//所以当指定长度定义字符数组时,应当在满足长度元素之前的一个单位就结束元素存放

综上所述,\0在字符数组中占据一个元素单位,并且会作为字符的终止标志。
当然还有另一种定义字符串的方法:

char*str="abcdef";

这是一个用 char* 类型指针维护的字符串,char*str 指针指向字符串首元素a,但当对指针变量解引用操作时,将会访问整个字符串。这种方式与上述定义方式不同的是,这种定义方式的字符串不可修改,以指针形式维护的字符串将创建在内存中的静态区。(如果对内存分区不太理解的小伙伴可以参看我的函数栈帧那篇文章)
在实际应用中,我们更愿意用上述b数组的定义方式来定义字符串,在不给定长度的情况下,编译器会在字符串末尾自动填充\0作为字符串的终止符,并且该字符串可修改。

二、常见的字符串操作函数有哪些?

我们需要存储或处理一些字符类型数据,所以我们创建了字符串。但我们还不满足于此,我们还希望这些字符串之间可以进行某些操作来实现一些功能,比如说字符串的长度是多少?字符串之间是否相等?在一个字符串后面追加一个字符串等等…为了实现上述种种,字符串函数应运而生。下面我们将对常见字符串一一介绍,并从逻辑的角度出发来自定义模拟实现这些库函数。

1、strlen()

strlen函数是一个常见的C语言的求字符串长度的库函数,它在库中的定义如下:

size_t strlen(const char*string)

它的参数为一个const修饰的字符类型指针,它的返回类型为无符号整型size_t,当然我们更习惯另一种写法:unsigned int。这表明当用strlen计算字符串长度时,返回类型为一个无符号整型。
一种常见的错误如下:

int main()
{
    const char* str1="abcdef";
    const char* str2="abc";
    if(strlen(str2)-strlen(str1)>0)
    {
       printf("str2>str1\n");
    }
    else
    {
       printf("str1>str2\n");
    }
    return 0;
}

在VS2019编译环境下,会输出str2>str1。strlen函数会返回一个无符号整型。当strlen(str2)长度的3(unsigned)减去strlen(str1)的长度6(unsigned)时得到-3(补码),但是此补码会以无符号形式输出,那么将会是一个很大的正数。
**那么strlen是如何实现的呢?**在上述中我们提到,在字符串的最后一个元素后有\0作为终止符号。strlen函数就是依靠此机制进行工作的,我们模仿库函数中的长度检索机制,模拟三种实现方式:
(1)计数器形式

unsigned int mystrlen(const char* arr)// strlen模拟实现,计数器方式
{
	assert(arr != NULL);
	int count = 0;
	while (*arr != '\0')
	{
		count++;
		arr++;
	}
	return count;

}

(2)递归方式

unsigned int my_strlen(const char *arr)//strlen模拟实现 递归方式
{
    assert(arr);
	if (*arr != '\0')
	{
		return 1 + my_strlen(arr + 1);
	}
	else
		return 0;
}

(3)指针相减方式

unsigned int Mystrlen(const char*s)//strlen模拟实现 使用指针相减的方式
{
	assert(s);
	const char* p = NULL;
	p = s;
	while (*p != '\0')
	{
		p++;
	}
	int ret = p-s;
	return ret;
}

实际上,三种方式都将找到\0作为结束标志,不同的是,在某些场景下,函数递归方式可以不创建临时变量从而实现strlen函数功能。

2、strcpy()

字符串拷贝,或者叫复制粘贴,是编程实际中难以避免的字符运算。比如将学生信息进行拷贝,对备忘录进行拷贝操作等等
如:将char a []="Tom";拷贝到char a2[20]="name"中,即批量的将a2改变为Tom时,strcpy函数就派上用场。在C语言库中strcpy函数定义如下:

char *strcpy( char *strDestination, const char *strSource );

我们可以看到strcpy的参数分别为char*类型指针指向的目标地址(strDestination),以及const修饰的char*类型的指针指向源地址(strSource)。值得一提的时,在使用strcpy时有如下注意事项:

  • 源字符串必须有\0作为终止标志
  • 在进行拷贝时\0也会拷贝到目标空间
  • 目标空间必须足够大,否则将无法拷贝成功
  • 目标地址必须可变(即不能采用char*指针在静态区创建字符串)

模拟实现strcpy的核心思想就是以\0作为循环结束标志,并不断更新指针访问空间进行从源地址向目标地址元素的赋值
strcpy模拟实现

char* my_strcpy( char*dest, const char* str)
{
	assert(dest != NULL);
	assert(str != NULL);
	char* ret = dest;
	while (*dest++ = *str++)
	{
		;
	}
	return ret;
}
//strcpy模拟实现 切记 返回值类型为char*

strncpy是一种指定长度的字符串拷贝函数,与strcpy稍有不同,即在传递参数时需要传递n的值来指定拷贝长度,其他参数与strcpy一致
strncpy模拟实现:

char* my_strncpy(char*dest, const char* src, size_t count)
{
	assert(dest && src);
	char* ret = dest;

	while (count)
	{
		*dest++ = *src++;
		count--;
	}
	return ret;
}
//strncpy 限制字符长度的 字符串复制 函数的模拟实现

3、strcat()

试想这样一段编程实际场景,需要对一个字符串后进行补充一段字符串内容。strcat可以将源字符串全部追加到目标字符串处,在C库中它的定义如下:

char *strcat( char *strDestination, const char *strSource );

strcat与strcpy长得极像,无论是返回值类型还是参数类型。两者差异就在于strcpy是进行字符串之间的拷贝,strcat是将字符串追加到另一字符串之后。
其对字符串的要求与strcpy相同,同样是目标空间需要足够大以及字符串可修改\0作为终止符号
strcat模拟实现:

char* my_strcat( char*dest, const char* src)//strcat模拟实现
{
	assert(dest && src);
	char* ret = dest;
	while (*dest != '\0')
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

可以看到,与strcpy唯一的不同就在于,strcat需要先找到目标字符串的末尾位置,然后进行赋值(拷贝)操作。由于dest指针的不断修改,我们需要事先定义一个char* ret对dest进行存放,最终返回ret,即返回dest的首地址,在主函数中就可以对目标字符串进行打印。
strncpy类似,strncat也是一种可以指定长度追加的字符串追加函数
strncat模拟实现;

char* my_strncat(char*dest, const char* src, size_t count)
{
	assert(dest && src);
	char* ret = dest;
	while (*dest != '\0')
	{
		dest++;
	}
	while (count)
	{
		
		
		*dest++ = *src++;
		
		count--;
	}
	
	return ret;
}
//strncat 限制字符的字符串追加函数模拟实现

4、strcmp()

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

字符串比较函数strcmp,用来比较两个字符串的大小。这里的比较方法是:英文字母按照对应的ASCII码值进行逐一比较,直至发现不同的字符或者全部比较完毕。如果两个字符串相等那么就会返回0,如果string1大于string2将返回一个大于0的数,反之则会返回一个小于0的数。在实际编程中,对于密码的比对就可以使用strcmp函数来比较两个字符串。
strcmp模拟实现:

int my_strcmp(const char* s1,const char*s2)//字符串比较函数模拟实现
{
	assert(s1&&s2);
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
		{
			return 0;
		}
		s1++;
		s2++;
	}
	return *s1 - *s2;
}

除此之外还可以用strncmp来指定长度字符比较,这种方式较为安全。C语言库函数中有此函数,VS2019编译环境下推荐使用strncmp来代替strcmp,来取消字符串长度而带来的风险。
strncmp模拟实现如下

int my_strncmp(char* s1, char* s2,size_t count)
{
	assert(s1 && s2);
	size_t i = 0;
	for (i = 0; i < count; i++)
	{
		if ( ((*s1) == (*s2)) && i == count - 1)
		{
			return 0;
		}
		else if(*s1!=*s2)
		{
			return *s1 - *s2;
		}
		s1++;
		s2++;
	}
}
//strncat模拟实现 限制字符长度的 字符串比较函数

可以看到,在使用strncmp时,除了传递比较的字符串之外,还需要传递比较的长度n。

5、strstr()

strstr函数的主要功能是判断strCharSet字符串是否在string字符串中(即一个字符串是否为另一个字符串的子串)在C标准库中的定义如下:

char *strstr( const char *string, const char *strCharSet );
//注意这里说的是判断strCharSet是否为string的子串

strcatstrcpy不同,strstr要求参数需要均为const修饰的常量。返回值与strcatstrcpy一致均为char*类型的指针变量,返回string字符串中第一个与strCharSet相同的元素的地址
比如:

char a[]="abcdefbcd";
char b[]="bcd";
char* ret=strstr(a,b);
printf("%s\n",ret);
//此时将打印 bcdefbcd,即返回与bcd匹配的第一个b出现的位置

若简单将此功能模拟实现并不难,只需要按照strcpy或者strcat的逻辑进行检索并返回就可以,但是如果需要判别的字符串如下所示呢?

char string[]="abbbcde"
char strCharSet[]="bbc";

这里有人一定会讲:那就用KMP字符串匹配算法!
好主意,但是对于初学者来说有没有简单的逻辑实现方式呢?
完成匹配的关键其实就是:当strCharSet字符串出现c的时候字符串string中仍是b,那么之前的两次检索仿佛“前功尽弃”。其实不然,这里只需要在开始检索时设置一个cp指针,当发生检索失败时cp+1,再以cp为起点开始检索就可以。
strstr简单匹配算法逻辑如下:
strstr函数算法框图模拟实现

char* my_strstr(const char* str1, const char* str2)//strstr函数模拟实现,判断字符串是否在另一个字符串中,若在返回第一次出现位置的首地址
{
	assert(str1 && str2);
	char*s1;
	char*s2;
	char*cp;
	cp = str1;
	if (*str2 == '\0')
	{
		return str1;
	}
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
			
		}
		if (*s2 == '\0')
			return cp;
		cp++;
	}
	return NULL;
}

除此之外还有strtok,strerror等字符串操作函数分别可对字符串进行切分,以及打印报错号,这里不再进行一一介绍。刚兴趣的小伙伴可以自行学习查看。


点击全文阅读


本文链接:http://zhangshiyu.com/post/33031.html

字符串  函数  长度  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1