征服C语言字符串函数(超详细讲解,干货满满)

一.字符串函数

1.1.strlen

1.1.1.strlen剖析

strlen:求字符串长度函数
1.首先,我们先看一下strlen这个函数的返回值和参数类型

size_t strlen ( const char * str );

C语言中VS编译器下是这样定义size_t:
typedef unsigned __int64 size_t;
也就是说:size_t实际上是对unsigned int类型起的一个别名,
我们可以理解为size_t就是一种unsigned int

其中的const char* str:说明传入的参数需要是一个地址,
strlen函数通过所传入的一个地址来找到所对应的字符串,
然后从对应地址处开始读取计算字符个数,一直计算到'\0'为止.

!!!注意:
1.strlen函数是计算'\0'之前的字符个数,
也就是说'\0'本身并不会计算在strlen所计算的个数之内

2.sizeof操作符计算字节大小时也会计算上'\0'

3."abcd"这种类型的字符数组在末尾处会隐含'\0',{'a','b','c','d'}这种类型的字符数组在末尾处则不会隐含'\0'
也就是说除非在末尾处人工添加'\0',否则这种字符数组末尾是没有'\0'4.关于const的作用是:限制str所指向的内容不能被修改,
也就是*str不能被修改,即不能充当左值

例如:后面给了大家答案,大家可以参照一下
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = { 'a','b','c','d','e','f' };
	printf("arr1的strlen:%d\n", strlen(arr1));//6
	printf("arr1的sizeof:%d\n", sizeof(arr1));//7
	printf("arr2的strlen:%d\n", strlen(arr2));//x
	printf("arr2的sizeof:%d\n", sizeof(arr2));//6
	return 0;
}

1.1.2 模拟实现strlen函数

接下来让我们模拟实现一下strlen函数,
1.在这里我们使用int类型作为返回值,实现完后会跟大家说明原因
2.在这里我们使用assert宏来保证arr所指向的空间不是NULL,
便于函数调用者传入参数错误时能够精确定位到错误的位置和原因
3.assert宏会在后面给大家详细说明

法一:计数器方法

思想是这样的:
让指针从数组首元素开始一次右移读取字符,
记录下读取到的字符个数,即为’\0’之前的字符个数
从strlen函数的功能上面可以很好的理解

//法一:计数器
int my_strlen1(const char* arr)
{
	assert(arr != NULL);
	int count = 0;
	while (*arr++ != '\0')
	{
		count++;
	}
	//注意:这两种对arr++的操作完全相同,
	//大家可以思考一下为什么这两种方法得到的最终答案相同
	//while (*arr != '\0')
	//{
	//	count++;
	//	arr++;
	//}
	return count;
}

下面我们解释一下为什么这两种方法得到的最终答案相同
1.注意:两种while循环的循环次数是相等的
但是不同点在于
1.第一种while循环中arr在循环条件判断结束之后就进行了自增操作,
2.而第二种while循环中arr的自增操作是在循环体内部完成的

不过这个函数返回的是count,而count只与while循环的循环次数有关,与arr无关,所以最终答案相同

2.递归方法

与上面的具体实现方式相同,只不过算法实现方式并不相同
上面是计数器方式,这里是递归调用的方式

这里的递归思想是
把一个字符串(假设长度为len)分割为
1.第一个字符
2.后面那个长度为len-1的字符串

第一步:
我们判断第一部分是否为’\0’,
如果为’\0’,则终止计算,返回0
第二步:
如果不为’\0’,则返回1+计算第二部分的长度

//法二:递归
int my_strlen2(const char* arr)
{
	assert(arr != NULL);
	if (*arr == '\0')
	{
		return 0;
	}
	return 1 + my_strlen2(arr + 1);
}

3,指针-指针的方法:

补充:
指针-指针:得到的是两个指针之间的元素个数!!!
指针之间可以进行减法(前提:必须是指向同一结构的指针,例如;数组)
指针之间不可以进行加法

思路:
让end指针指向’\0’的位置,然后只需要返回end-arr即可

//法三:指针-指针
int my_strlen3(const char* arr)
{
	assert(arr != NULL);
	char* end = arr;
	while (*end != '\0')
	{
		end++;
	}
	//下面的方法是错误的,得到的字符串长度为实际长度+1
	//大家可以思考一下为什么这两种方法得到的最终答案不同
	//while (*end++ != '\0')
	//{
	//	;
	//}
	return end - arr;
}

前面说明了这两种while循环的区别之处和相同之处
下面我们来看一下这个题为什么得到的答案就不一样了呢?
1.这题返回的是end-arr,又因为arr在该函数内部并不会变动
所以最终答案与end有直接联系
2.上面提到过end是在while()循环的条件判断出进行的,
而我们又知道:
在for循环和while循环中,循环体执行的次数是循环条件执行的次数-1,所以错误的方法中end多自增了一次,所以返回的答案是实际答案+1

下面我们说一下用int类型作为返回值的好处
大家可以做一下这道题
int main()
{
	if (strlen("abc") - strlen("abcdef") > 0)
	{
		printf(">\n");
	}
	else
	{
		printf("<=\n");
	}
	return 0;
}

最终的答案是   > ,而不是 <=

其实最终的答案是挺出人意料的,
因为strlen这个库函数的返回值为size_t(可以理解为unsigned int类型)
所以最终得到的"-3"size_t类型,而不是int类型,
所以"-3"在内存中被取出时是以无符号整型的视角去取出的,所以-3被取出时是一个非常大的正数,所以答案是>

所以使用size_t作为返回值的话无法用来这样直接判断两个字符串的长度,
不太方便,也正是因为这个原因我们使用了int类型作为返回值,
这样的话就可以很方便的直接判断两个字符串的长度了

那是不是说库函数实现的strlen函数用size_t函数作为返回值就不好呢?
当然不是,strlen这个函数设计的本意就是求字符串的长度

显然结果不可能为负数,所以我们使用了size_t作为返回值是很好的

这也就说明了不同的人在设计相同功能的函数时,出发的角度不同,所设计出的函数也会不同,也就是说,这两种设计方式各有各的好处,我们要灵活使用
下面我们说一下assert这个宏
void assert (int expression);
assert翻译过来就是断言的意思,
也就是说assert会执行expression中的语句,
如果这个语句的计算结果为0,那么断言失败,程序终止,
并且在屏幕上指出错误位置和具体原因
我们可以把assert中的语句和if中的语句联系起来看待,
例如:
int main()
{
	int a = 2;
	int b = 0;
	if (b = a)
	{
		printf("you can see me\n");//会执行该条语句
	}
	printf("%d", b);//2
}

举一个例子,没有什么意义,只是为了说明assert中的语句会执行
例如:
int main()
{
	int a = 2;
	assert(a=1);
	printf("%d", a);//结果为1,说明assert中的语句会执行
}

1.2 strcpy

strcpy:字符串拷贝函数

1.2.1 strcpy函数剖析

 strcpy(字符串拷贝)

 char* strcpy(char* destination,char* source)
 
英语好的老铁可以看一下:
Copies the C string pointed by source into the array pointed by destination, 
including the terminating null character(and stopping at that point).
翻译后:
源字符串必须以 '\0' 结束。
会将源字符串中的 '\0' 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。
目标空间必须可变。

1.2.2 strcpy函数模拟实现

char* my_strcpy(char* dest,const char* src)
{
	char* ret = dest;
	assert(dest != NULL);
	assert(src != NULL);
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[20] = "xxxxxxxxxxxxxxx";
	char arr2[] = "hello world";
	my_strcpy(arr1, arr2); //arr2中'\0''\0'之前的元素拷贝到arr1中
	printf("%s\n", arr1);
	return 0;
}

1.因为strcpy函数要返回目标空间首元素的地址,所以先用ret来保存目标字符串dest的首元素地址
2.采取逐个赋值,当*src==‘\0’时进行完对应的赋值操作后(*dest++=*src++)这个表达式整体的值为’\0’,对应的ASCII码值即为0,所以跳出while循环,字符串拷贝操作结束

1.3. strcat

strcat:字符串追加函数

1.3.1 strcat函数剖析

Appends a copy of the source string to the destination string. The terminating null character
in destination is overwritten by the first character of source, and a null-character is included
at the end of the new string formed by the concatenation of both in destination.
源字符串必须以 '\0' 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
字符串自己给自己追加,如何?
这个问题等到我们模拟实现完strcat函数后再进行解释

1.3.2 strcat模拟实现

char* my_strcat(char* dest, const char* src)
{
	assert(dest != NULL);
	assert(src != NULL);
	char* ret = dest;
	while (*dest != '\0')
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[30] = "hello ";
	char arr2[] = "world";
	my_strcat(arr1, arr2);
	printf("%s\n", arr1);//hello world
	return 0;
}

方法:
1.先找到dest即目标字符串的末尾位置(即’\0’的位置)
2.将src即源头字符串中的数据拷贝到目标字符串dest中
注意:该函数的返回值为目标字符串的首元素地址,所以需要用ret来保存dest字符串的首元素地址

1.3.3. 问题回答

字符串自己给自己追加的话:

假设字符串char arr[20]="abcd"给自己追加
strcat(arr,arr);
我们先把隐藏的'\0'写出来
"abcd\0"
第一步找到目标字符串的'\0'
第二步进行逐步赋值,直到源头字符串src找到'\0'为止
"abcda"
注意到仅仅只进行了一次赋值,'\0'就被'a'给覆盖了,也就是说源头字符串src将永远无法找到'\0',所以
:
如果字符串自己给自己追加的话,'\0'会被覆盖,所以循环会变为死循环

所以一定不能自己给自己追加,否则程序会崩溃

1.4. strcmp

strcmp:字符串比较函数

1.4.1 strcmp函数剖析

C语言标准规定:
int strcmp ( const char * str1, const char * str2 );
字符串比较函数strcmp:
依次比较两个字符串str1与str2对应位置的ascii码值

1.如果相同,则str1++,str2++,进行下一个位置元素的比较
2.如果最终两者相同,则返回0
3.如果str1 > str2:返回大于0的数字
4.如果str1 < str2:返回小于0的数字

假设str1 = "abcd"
str2 = "abde"
str3 = "abc"
则str1与str2的'a''b'均相同,跳过,到达'c''d'之后,因为'c'的ASCII码值小于'd',所以返回<0的数字
str1与str3的'a''b''c'均相同,到达'd''\0'之后,因为'd'的ASCII码值大于'\0',所以返回>0的数字
注意:'\0'的ASCII码值是最小的,为0

在VS编译器上:
2.如果最终两者相同,则返回0
3.如果str1 > str2:返回1
4.如果str1 < str2:返回-1

英语好的老铁可以看一下
This function starts comparing the first character of each string. 
If they are equal to each other, it continues with the following pairs until the characters differ or until a terminatingnull-character is reached.

1.4.2 strcmp函数模拟实现

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
	while (*str1 == *str2)
	{
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[] = "acdf";
	char arr2[] = "an";
	int ret = my_strcmp(arr1, arr2);
	printf("%d", ret);//返回<0的数字,因为'c'<'n'
	return 0;
}

1.5. strstr

strstr:字符串查找函数

1.5.1 strstr函数剖析

char * strstr ( const char *str1, const char * str2);

Returns a pointer to the first occurrence of str2 in str1,
or a null pointer if str2 is not part of str1.

返回str2在str1里出现的对应的str1中"首"元素的地址
假设:str1="abbbcde",str2="bbcde"
也就是说返回str1中第二个'b'的位置

1.5.2 strstr函数模拟实现

这里采用BF暴力算法:

char* my_strstr(const char* str1, const char* str2)
{
	char* cp = str1;
	char* s1 = cp;
	char* s2 = str2;
	while (*cp)
	{
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return cp;
		else if (*s1 == '\0')
			return NULL;
		else
		{
			cp++;
			s1 = cp;
			s2 = str2;
		}
	}
	return NULL;
}
具体思想是:
1.先用cp,str2作为标志位,
进行匹配时:
1.如果s1与s2对应位置的字符相同,则s1与s2均后移
直到*s1为'\0',或*s2为'\0',或s2与s1的对应位置的字符不同退出while循环,
当退出while循环后如果*s2为'\0',则表示匹配成功,返回cp
2.如果s1与s2对应位置的字符不同,则cp后移,s1退到cp,s2退到str2,再进行匹配,如果cp指向了'\0'时还没有匹配成功
也就意味着已经彻底匹配失败了,返回NULL
3.如果退出时是因为*s1为'\0',则表示彻底匹配失败,返回NULL

下面请大家看一下具体实现流程

在这里插入图片描述

1.6 strtok

字符串切割函数

1.6.1 strtok函数剖析

char * strtok ( char * str, const char * sep );
sep参数是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
记。

1.strtok函数找到str中的下一个标记,并将其用 '\0' 结尾,返回一个指向这个标记的指针。(注:
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)
2.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
中的位置。
3.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
记。
4.如果字符串中不存在更多的标记,则返回 NULL 指针。
从中可以看出strtok具有记忆性

演示:

int main()
{
	char arr[] = "helloworld@yeah.net";
	char copy[30];
	strcpy(copy, arr);
	char sep[] = "@.";
	char* ret = strtok(copy, sep);
	printf("%s\n", ret);//helloworld
	ret = strtok(NULL, sep);
	printf("%s\n", ret);//yeah
	ret = strtok(NULL, sep);
	printf("%s\n", ret);//net
	return 0;
}
这里还可以用for循环来简化代码
int main()
{
	char arr[] = "helloworld@yeah.net";
	char copy[30];
	strcpy(copy, arr);
	char sep[] = "@.";
	char* ret = NULL;
	因为ret只需设置为strtok(copy,sep)一次,所以可以考虑作为for循环的初始条件
	其余所有调用都只需要传NULL,sep即可,所以考虑作为for循环的更新条件
	当ret为NULL时,切割成功完成,所以可以作为for循环的终止条件
	for (ret = strtok(copy, sep); ret != NULL; ret = strtok(NULL, sep))
	{
		printf("%s\n", ret);
	}
	return 0;
}

二.有长度限制的字符串函数

上面的strcpy,strcat,strcmp函数都是需要找到’\0’的字符串,也就是如果找不着’\0’会继续向下找,所以不安全(可能会出现越界访问的情况)
所以接下来我们介绍一下三个有长度限制的函数:
strncpy
strncat
strncmp

2.1. strncpy

char * strncpy ( char * destination, const char * source, size_t num );
Copies the first num characters of source to destination. 
If the end of the source C string(which is signaled by a null-character) 
is found before num characters have been copied,
destination is padded with zeros until a total of num characters have been written to it.
1.拷贝num个字符从源字符串到目标空间。
2.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加'\0',直到num个。
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "xxx";
	strncpy(arr1, arr2, 2);
	printf("%s\n", arr1);//xxcdef
	return 0;
}

2.2. strncat

char * strncat ( char * destination, const char * source, size_t num );
Appends the first num characters of source to destination, plus a terminating null-character.
If the length of the C string in source is less than num,
only the content up to the terminating null-character is copied.

将源代码的第一个num字符附加到目标代码中,再加上一个终止的’\0’
如果源代码中的C字符串的长度小于num,则只复制直到终止的空字符的内容。

int main()
{
	char arr1[30] = "abcd";
	char arr2[] = "efgh";
	//strncat(arr1, arr2, 4);
	//printf("%s\n", arr1);//abcdefgh
	//strncat(arr1, arr2, 2);
	//printf("%s\n", arr1);//abcdef
	strncat(arr1, arr1, 4);
	printf("%s\n", arr1);//abcdabcd
	由此可见,我们可以通过strncat函数来让arr1:"abcd"变为"abcdabcd"
	实现"翻倍"
}

2.3. strncmp

int strncmp ( const char * str1, const char * str2, size_t num );
只比较num个字符来返回比较结果
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abdcdef";
	int ret = strncmp(arr1, arr2, 2);
	printf("比较前两个字符:%d\n", ret);//0
	ret = strncmp(arr1, arr2, 3);
	printf("比较前三个字符:%d\n", ret);//-1
	int len1 = strlen(arr1);
	int len2 = strlen(arr2);
	ret = strncmp(arr1 + len1 - 3, arr2 + len2 - 3, 3);
	printf("比较后三个字符:%d\n", ret);//0
}

以上就是字符串函数专题的讲解,希望能给大家带来帮助,谢谢大家.

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

program-learner

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值