C语言——详解字符函数和字符串函数(二)

Hi,铁子们好呀!之前博主给大家简单地介绍了部分字符和字符串函数,那么这次,博主将会把这些字符串函数给大家依次讲完!
今天讲的具体内容如下:

文章目录

  • 6.strcmp函数的使用及模拟实现
    • 6.1 `strcmp`函数介绍和基本使用
      • 6.1.1 `strcmp`函数介绍
      • 6.1.2 `strcmp`函数基本使用
    • 6.2 模拟实现strcmp函数
  • 7.`strncpy`函数的使用
    • 7.1 `strncpy`函数介绍和基本使用
      • 7.1.1 `strncpy`函数介绍
      • 7.1.2 `strncpy`函数的基本使用
    • 7.2 模拟实现`strncpy`函数
  • 8.`strncat`函数的使用
    • 8.1 `strncpy`函数介绍和基本使用
      • 8.1.1 `strncat`函数介绍
      • 8.1.2 `strncat`函数的基本使用
    • 8.2 模拟实现 `strncpy`函数
  • 9.strncmp函数的使用
    • 9.1 strncmp函数介绍以及基本使用
      • 9.1.1 strncmp函数介绍
      • 9.1.2 strncmp函数的基本使用
    • 9.2 模拟实现strncmp函数
  • 10.strstr函数使用和模拟实现
    • 10.1 strstr函数介绍以及基本使用
      • 10.1.1 strstr函数介绍
      • 10.1.2 strstr函数的基本使用
    • 10.2 模拟实现strstr函数
      • 10.2.1 例子1
      • 10.2.2 例子2
      • 10.2.3 例子3
      • 10.2.4 特殊情况处理
        • 10.2.4.1 情况1
        • 10.2.4.2 情况2
      • 10.2.5 算法实现
        • 10.2.5 VS运行效果
  • 11.strtok函数的使用
    • 11.1 strtok函数介绍:
    • 11.1 strtok函数案例详解:
    • 11.1 strtok函数算法实现:
  • 12.strerror 函数的使用
    • 12.1 strerror函数的基本介绍
    • 12.2 strerror 函数的使用
      • 12.2.1举例1
      • 12.2.2举例2
      • 12.2.3 举例3
  • 13.总结

6.strcmp函数的使用及模拟实现

6.1 strcmp函数介绍和基本使用

6.1.1 strcmp函数介绍

它的函数原型如下:

int strcmp ( const char * str1, const char * str2 );

具体的函数介绍如下图所示:

从图中我们得知:
如果第一个字符串PTR1大于PTR2的值,返回的是一个大于0的数。
如果第一个字符串PTR1等于PTR2的值,返回的是一个等于0的数。
如果第一个字符串PTR1小于PTR2的值,返回的是一个小于0的数。

6.1.2 strcmp函数基本使用

这里我们主要演示一下用strcmp函数比较两个字符串,且两个字符串存的字符的Ascll码值都是相同的,大家可以参考一下那个写法。
代码如下:

#include <stdio.h>
#include <string.h>
int main() {


	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	int ret = strcmp(arr1, arr2);//这里是用strcmp判断arr1和arr2两个数组的字符是否想等,然后这两个字符数组都想等,
	printf("%d\n", ret);

	return 0;
}

VS运行结果:

6.2 模拟实现strcmp函数

好,这里大家可以根据博主前面使用strcmp函数,可以自行尝试模拟实现一个strcmp函数。
当然如果有同学实在想不到如何模拟实现strcmp函数,也可以参考一下博主的思路。
如图:

相信同学们看了博主的思路,自己也会大彻大悟的。

代码实现:

#include <stdio.h>
int my_strcmp(const char* str1, const char* str2) {
	while (*str1==*str2)//判断str1和str2字符串是否想等,如果想等,则表达式为真,将会进入while循环
	{
		if (*str2 == '\0')//只有当遍历完str1字符串和str2的内容,还是符合while循环的表达式,那就返回0;
		{
			return 0;
		}
		str1++;//str1指针往后偏移1个元素
		str2++;//str2指针往后偏移1个元素
	}
	return *str1 - *str2;//如果str1字符串和str2字符串中所对应的字符不想等,那就返回str1字符串对应的字符的Ascll码值-str2字符串对应字符的Ascll码值
}
int main() {


	char arr1[] = "abcdef";
	char arr2[] = "abcde";
	int ret = my_strcmp(arr1, arr2);//这里面我们调用my_strcmp函数,用ret变量接收my_strcmp返回的值
	printf("%d\n", ret);

	return 0;
}

这里相信同学们看了博主的代码以及注释,应该是能够理解这个代码逻辑的~
好,那这个函数我们就讲到这里~

7.strncpy函数的使用

7.1 strncpy函数介绍和基本使用

7.1.1 strncpy函数介绍

它的函数原型如下:

char * strncpy ( char * destination, const char * source, size_t num );

具体的函数介绍如下图所示:

相信同学们看了这个官网对strncpy函数的介绍,自己是能够理解的。
那接下来博主教一下你如何使用strncpy这个函数对字符进行拷贝把~

7.1.2 strncpy函数的基本使用


代码如下:

#include <stdio.h>
#include <string.h>
int main() {

	char dest[20] = { 0 };//dest是目标字符串,这里我们先将dest数组初始化,方便后续用strncpy函数进行拷贝操作
	char src[] = "hello.";//src是源字符串
	strncpy(dest,src, 3);//移动3个元素个数,将src数组中的前三个字符拷贝到dest数组中
	printf("%s\n", dest);//这里本质上dest的首元素的地址,往后打印字符串,直到遇到'\0'才停止,因为dest数组我们一开始初始化为0,因此只要把前三个字符拷贝完,那就会停止拷贝

	return 0;
}

vs运行效果:

7.2 模拟实现strncpy函数

这里可能有同学对于如何模拟实现strncpy函数有点懵,不知从何下手。
没事,这里博主会提供思路,很快你们就理解了~

如图:

分析: 这里我们假设要将src数组中的前五个字符拷贝到dest数组中,我们除了一个一个对它进行遍历,还要在dest数组的末尾加上个'\0'
这是因为如果我们拷贝的字符个数如果小于src源字符串的个数,那它肯定没有把'\0字符拷到dest数组中,这会导致到时打印输出dest会出现乱码的情况。
为了避免出现这种情况,我们在遍历拷贝完src数组里的字符到dest数组中,顺带加一个'\0'dest的字符中。

好,讲到这里,相信同学们已经理解了这个模拟实现strncpy函数的思路,接下来博主直接上代码,好让大家理解。

代码如下:

#include <stdio.h>
#include <string.h>

char* my_strncpy(char* dest, const char* src, size_t nums) {

	char* tmp = dest;//创建临时指针变量tmp来接收目标字符串dest首字符的地址
	int j = 0;
	for (; j < nums && src[j]; j++)//这里是逐一遍历拷贝字符,但是拷贝的字符要少于等于nums个,要另外加上j<nums的循环条件,另外在右侧加上str[j]的条件,如果str[j]指向的是'\0',为假,会跳出for循环
	{
		dest[j] = src[j];
	}
	dest[j] = '\0';//在循环的外侧,我们还要在dest最后一个字符的后面加上 '\0'的符号,不然后面打印的时候可能会出现乱码。
	return tmp;//这里是将dest首字符的地址返回去
}

int main() {

	char dest[20];//dest是目标字符串,这里我们先将dest数组初始化,方便后续用strncpy函数进行拷贝操作
	char src[] = "hello.";//src是源字符串
	char*ret=my_strncpy(dest,src, 5);//将src数组中的前三个字符拷贝到dest数组中
	printf("%s\n", ret);//这里本质上dest的首元素的地址,往后打印字符串,直到遇到'\0'才停止。

	return 0;
}

这里相信同学们看来博主的代码以及注释,自己是能够理解这个代码逻辑的。

VS运行效果如下:

好,讲到这里,相信同学们可以理解如何模拟实现strncpy函数的具体方法,大家可以课后下去实践一下~

8.strncat函数的使用

8.1 strncpy函数介绍和基本使用

8.1.1 strncat函数介绍

它的函数原型如下:

char * strncat ( char * destination, const char * source, size_t num );

具体的函数介绍如下图所示:

相信同学们看了这个函数官网介绍以及那个例子,自己是应该能够看懂这个函数的用法~

8.1.2 strncat函数的基本使用

好,我们这里就简单演示一下strncat函数是怎么使用的,大家可以参考一下。
代码如下:

#include <stdio.h>
#include <string.h>
int main() {

	char str1[20] = "hello ";
	char str2[] = "world!!!";
	strncat(str1, str2, 5);//将str2前5个字符拼接到str1中,也就是把'!'前面的5个字符全部拼接到str1中
	printf("%s\n", str1);//这里是从str1的首元素地址逐一往后打印字符串,直到遇到'\0'为止
	return 0;
}

代码分析: 这里我们主要是把str2数组中的前5个字符拼接到str1数组中' '字符的后面。

下面我们来看一下vs运行效果。
VS运行效果如下:

好,如果说我们要模拟实现一个strncat函数,我们该怎么写呢?接下来听博主给你细细道来~

8.2 模拟实现 strncpy函数

如下图所示

分析: 这里博主主要是画了个图,分别把str1str2数组以及里面的内存布局画出来了,以及如何去分析这个代码思路的,怎么去构思这个代码的?
图中都进行详细的介绍了,希望大家可以仔细看一下这幅图,说不定理解了这个图的内容,自己就能模拟实现这个strncat函数了嘿嘿!

如果大家看了这幅图,依然是无法构思这个代码的,没事,博主这里直接上代码和注释,希望大家能够理解。

代码实现:

#include <stdio.h>
#include <assert.h>
#include <string.h>

char* my_strncat(char* str1, const char* str2, size_t nums)//这里的nums意思是拼接的字符个数有多少
{

	assert(str1 && str2);//这里的assert断言主要是判断两个字符串是否为空(NULL)
	char* tmp = str1;//这里我们用临时指针变量tmp来接收str1的首元素的地址
	while (*str1)str1++;//这里主要是遍历str1字符串,直到遍历到'\0',则会退出while循环
	int j = 0;//创建变量j,并初始化为0
	while ((j++ < nums) && (*str1++ = *str2++));//这里的while循环第一个条件是确保拼接的字符个数要小于等于nums的个数,第二个条件是把str2的字符逐一拼接到str1中空格字符的后面,直到遇到'\0',则整个表达式为假,就会跳出循环
	return tmp;//因为这个my_strncat函数类型是char*类型,因此我们这里就把指针变量tmp返回去
}


int main() {

	char str1[20] = "hello ";
	char str2[] = "world!!!";
	char *ret=my_strncat(str1, str2, 5);//这里面主要是把str1和str2首元素的地址传过去,把要拼接的个数传过去,结果用一个指针变量ret来接收
	printf("%s\n", ret);	//printf("%s\n", ret);//这里是从指针变量ret的首元素地址逐一往后打印字符串,直到遇到'\0'为止

	return 0;
}

好,接下来我们用VS编辑器执行一下这个程序,看看是否符合我们的预期吧。

VS运行效果如下:

从运行结果: 我们发现,这个代码运行结果是正确的。也就是说博主写的模拟实现strncat函数是正确的,大家可以参考一下博主的写法。

好,这个strncat函数我们就讲到这里~

9.strncmp函数的使用

9.1 strncmp函数介绍以及基本使用

9.1.1 strncmp函数介绍

该函数的原型如下:

int strncmp ( const char * str1, const char * str2, size_t num );

具体函数介绍如下所示:

从这个官网介绍中: 可以看出这个函数其实跟我之前讲的strcmp差不多的,本质上呢?这个函数比这个原先那个strcmp函数多了一个参数size_t num。这个参数就是比较两个字符串中前num个字符所对应的Ascll码值。
需要注意的是: 这里只是最多比较两个字符串的前num个字符,如果提前发现这个两个字符的Ascll码值不一样的话,就提前结束,无需继续往后比较字符的Ascll码值。
希望同学们能够理解这个strncmp函数各个参数的意义。

9.1.2 strncmp函数的基本使用

这里我们就演示一下如果在这两个数组中,它们所对应的Ascll值不相同,那它们返回的结果会怎么样呢?

代码如下:

#include <stdio.h>
#include <string.h>
int main() {

	char arr1[] = "abcdef";
	char arr2[] = "aqcdep";
	int ret=strncmp(arr1, arr2, 5);//这里主要是比较arr1和arr2数组中的前5个字符的Ascll码值,若对应的字符中的Ascll码值不相等,则提前结束。
	printf("%d\n", ret);
	return 0;
}

相信同学们看了博主的代码以及这个注释,是能够理解这个代码逻辑的。
好,接下来我们用VS编译器来测试这个代码,看看结果是怎么样?

VS运行效果如下:

从运行结果来看: 虽说这个strncmp是比较这两个数组中前5个字符的Ascll码值,但是在两个数组中的第二个字符,它们所对应的字符不同,arr1数组中的第二个字符是'b',而arr2数组中的第二个字符是'q',显然这两个字符的Ascll码值是不相同的,因此就不需要往后比较这两个数组中的第三个字符。
至于说为什么最终打印的值为-1呢?
因为之前我们就讲过如果strncmp函数中的第二个参数大于第一个参数,返回的是一个小于0的数。那讲到这里,相信同学们应该能够理解。

9.2 模拟实现strncmp函数

首先,我们讲一下: 这里模拟实现strncmp函数其实跟之前我们讲的模拟实现strcmp函数写法是差不多的。只不过我们需要在这里定义一个变量t,然后需要在while循环加上个j++<nums的条件,要确保比较的字符个数小于等于nums个字符。
当然啦: 如果说我们在这两个数组中前num-1或者nums-2的字符中,发现两个数组中对应字符的Ascll码值不一样的话,那就会提前退出while循环。无需往后进行比较字符的Ascll码值。

好,根据前面我们讲的strcmp函数模拟实现以及上面对strncmp模拟实现进行简单的介绍后,相信同学们应该能把这个代码写出来吧~

代码实现:

#include <stdio.h>
#include <string.h>


int my_strncmp(const char* str1, const char* str2, size_t nums) {

	int t = 0;//定义一个变量t,	确定要比较的字符个数
	while ((*str1 == *str2) && (t++ < nums))//这里的while循环第一个条件要确保两个字符的Ascll码值相同,然后第二个条件是t的值要小于等于nums的值,然后这里的t++相当于每循环一次,t的值都会进行自增。
	{
		if (!*str1)//这里就是说当str1指针指向的是'\0'字符地址,那对其解引用,就是'\0',然后!'\0'就是把它的结果取反,那这个结果就为真,就返回0。
		{
			return 0;//返回0就是代表前nums个字符的Ascll码值都是相同的
		}
		str1++;//这里就是str1和str2指针各自向后偏移一个元素
		str2++;
	}
	return *str1 - *str2;//这里返回的str1数组和str2数组中对应字符相减的Ascll码值。
}


int main() {

	char arr1[] = "abcdef";
	char arr2[] = "aqcdep";
	int ret=my_strncmp(arr1, arr2, 5);//这里相当于用ret来接收my_strncmp返回的值
	printf("%d\n", ret);
	return 0;
}

如果同学们自行实现了strncmp函数的话,大家可以参考一下博主的代码以及注释,可以让自己理解地更加透彻。

VS运行效果如下:

细心的同学可以发现: 这里打印的结果是-15
那这是为什么呢?
这是因为在这两个数组中的第二个字符的Ascll码值是不一样的,就无需往后进行比较了,直接跳出 while循环,执行return *str1 - *str2那句话,因此返回的是字符'b''q'Ascll码值。

好,这里总结一下之前讲的无函数参数size_t numsstrcpy函数和strcat以及strcmp函数现在有函数参数size_t numsstrncpy函数和strncat以及strncmp函数的区别吧~

可能有些同学不知道这两种类型的函数最本质的区别是什么,接下来博主来详细介绍一下。
如下图所示:


我们把没有函数参数size_t部分的归为长度不受限制的字符串函数一类,有函数参数size_t部分的归为长度受限制的字符串函数另一类。
这里有同学可能有疑问?为什么没有一类函数是不安全的,一类函数是相对安全的?
这里博主举个代码示例,很快里面就知道了原因了~

代码如下:

#include <stdio.h>
#include <string.h>
int main() {

	char arr1[10] = "hello";//创建字符数组arr1,长度为10,也就是说这里最多只能存10个字符
	char arr2[] = "world!!!!!!!";//创建字符数组arr2,长度为12,加上'\0'字符就是13个字符,也就是说这里最多只能存13个字符
	strcpy(arr1, arr2);//将arr2的全部字符都拷贝到arr1数组中,由于arr2数组长度比arr1要大,如果全部拷过去的话,可能会造成数组越界,因此是不安全的
	printf("%s\n", arr1);

	return 0;
}

代码分析: 由于我们知道arr1数组初始化的长度只有10,arr2数组长度是大于10的,但是strcpy函数可不管你的数组初始化的长度是否能够容纳源字符串的长度,这个函数是相对比较粗暴的,直接把arr2全部的字符拷到arr1数组中。因此可能会导致数组越界访问。

我们来看一下VS运行效果,看看是否会弹出报错警告吧~
如下图所示:

果不其然,VS弹出报错警告,说明这个arr1数组初始化的长度我们开小了,要开大一点才行。(至少开得要大于等于arr2数组里面的字符长度)这样才不会出现报错情况。

如图:

另外,需要注意的是: 有函数参数size_t部分,就有了多一层思考,要拷贝几个,追加几个,这能不能放得下,这是不是就增加了安全的处理呢。
但这并不是绝对的安全,因为一个程序员如果要写bug的话,谁都拦不住他。

总结: 如果同学们觉得给了函数参数size_t num更方便的话,可以使用这种类型的函数,这样可以尽可能地避免数组越界的风险。

10.strstr函数使用和模拟实现

10.1 strstr函数介绍以及基本使用

10.1.1 strstr函数介绍

它的函数原型如下:

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

具体的函数介绍如下:

分析: 从官网给的strstr函数介绍,我们大概也能知道它的基本用法。
它本质的意思还是: 指向源字符串str1首次出现的指针,如果存在要匹配字符序列的C字符串。则到时打印的时候直接从这个C字符串的首元素地址以%s的形式打印出来即可。
但如果说找不到匹配字符序列的C字符串, 则返回null字符。

10.1.2 strstr函数的基本使用

下面我们来给大家演示一下 strstr函数的基本使用,大家可以看一下,说不定就学会嘿嘿!
代码如下:

#include <stdio.h>
#include<string.h>
int main() {

	char s1[] = "This is a simple things";
	char s2[] = "a";//s2存的是字符a的地址
	char* pch = strstr(s1,s2);//返回str2字符串在str1中第一次出现的位置,比如这个字符a在s1字符数组出现过,所以指针变量pch拿到的是s1数组中a的地址
	printf("%s\n", pch);//这里以%s打印本质上就是从说s1数组中字符a的地址开始往后打印字符串,直到遇到'\0'为止

	return 0;
}

VS运行效果:

从图中: 我们发现这里以%s打印字符串是从s1数组中的a字符开始往后打印的,直到遇到'\0'为止。

需要注意的是: 这里在s1主串中找s2子串中的字符,也是有可能找不到的,那VS执行的结果又是多少呢?

我们来看一下VS运行效果:

当我们的s2子串中放的是an字符串,但是这个字符串在s2主串中中是没有出现过的,因此VS最终打印的是(null)字符串。

好,讲到这里,相信同学们已经理解了该程序的逻辑了,那接下来我们来讲一下如何模拟实现一个strstr函数。

10.2 模拟实现strstr函数

这里我们讲一下模拟实现strstr函数的思路:
如图:

我们这里举三个例子,主串是源字符串,而子串的字符串是我们要查找的。
那我们这里主要是模拟实现在主串中查找子串的算法~

10.2.1 例子1

如图:

具体动图如下:

分析1: 这里的例子1测试的用例结果比较简单,第一次查找就找到了。这里面本质是用cp指针遍历整个数组。当cp指针指向'\0'的元素,也就是说子串在主串是没出现过的,那就直接返回一个空指针(NULL)回去。
另外:px指针指向的元素跟py指针指向的元素相同,那我们分别地对pxpy指针进行一次遍历判断,如果说py指针遍历到子串'\0'的位置。
也就是说该子串在主串是有出现过的,那此时我们要返回的是子串首字符在主串中第一次遇到的位置。
这个时候指针变量cp起到了重要作用,因为它是记录子串首字符和主串第一次遇到字符Ascll码值相同的位置。 所以我们直接把指针变量cp强制转换为char*的指针,将它返回去即可。

10.2.2 例子2

如图:

具体动图如下:

通过这个动图: 大家有没有发现,当这个指针变量pxpy指向的首元素相同的话。也就是说该子串是有可能在主串中是有出现的,如果让我们设计算法的话,我们这里还需用个while循环语句,分别遍历子串和主串的每个字符,看看它们对应字符的Ascll码值是否相等。
仔细的同学可以发现: 当遍历到主串的第4个字符'b'和子串第3个字符c,这两个字符的Ascll码值是不相同。那难道我们就不继续判断,直接返回空字符串NULL吗?
这是不行的。这是为什么呢?
这是因为即使在主串中的第二个字符'b'和子串的首元素字符'b'向后偏移元素时,发现主串和子串对应字符的Ascll码值不相等,那在这个过程中,其实cp指针也会向后偏移一个元素的。那我们可以把cp指针的地址赋给px指针。
然后px指针就指向主串的第三个元素的地址,看看它的Ascll码值是否跟子串的首元素的Ascll码值相同。
1.如果不相同的话,指针变量cp也会往后偏移一个元素,指向下一个元素的地址,那我们就把cp指针变量的地址赋值给px指针。通过这样更新指针变量px的地址,直到指针变量cp指向'\0'的元素,那也就是说明子串在主串没找到,那才返回NULL空字符串。
2.如果相同的话,且指针变量py指向子串'\0'的元素,那也就是说明该子串在主串是出现过的。那就返回子串中首元素在主串第一次出现的位置。
但是: 大家有没有发现动图有个缺陷哈,就是我们看到主串的第二个字符'b'和子串第一个字符'b'Ascll码值是相同的,然后指针变量pypx分别比较子串'\0'之前的字符和对应主串字符的Ascll码值是否相同。
但是这里当py指针指向子串第三个元素'c'px指针指向主串第四个元素'b',它俩的Ascll码值是不相同的。
那我们这时应该更新指针变量pxpy的所指向的元素地址,让它们指向主串和子串相同字符Ascll码值的地方,但是这里没有指针记录子串首元素的地址,这显然是不合理的。
因为有可能指针变量px从主串后面的元素开始遍历,有可能它指向的主串后面元素的Ascll码值会和指针变量py指向子串首元素的Ascll码值相同,但是由于这个py指针指向子串第三个元素的‘c’,那如果说主串后面某个的字符是'c',指针变量py往后偏移一个元素就为'\0',这就能就说明这个子串"bbc"在主串出现过?
这显然是不符合逻辑的,因此我们要用一个指针变量来记录子串首元素的地址

那该怎么记录子串首元素的地址呢?
我们先来看下图:

从图中: 可以看出strstr函数参数是const char * str2,也就是子串首元素的地址,那这就好办了,我们如果发现指针变量pxpy指向主串和子串对应字符的Ascll码值不相同的话,把指针变量str2强转为char*类型,再把它的地址赋给指针变量py。这样就能有效地避免出现运行结果出错的情况了。
可能讲了太多理论,同学们也许会觉得有点抽象啥的,没事,博主这里再上一个动图,希望大家能够彻底理解这个例子~
如图:

通过动图: 我们发现当指针变量px指向主串的第二个元素,指针变量py指向子串的第一个元素,对应字符的Ascll码值是相同的,那么指针变量pxpy分别向后偏移元素,从而来比较主串和子串对应字符的Ascll码值。
cp指针作用是记录px指针最开始和子串首元素相同的位置,而str2指针作用是用来记录子串首元素的地址,如果到时pxpy指针变量所指向对应字符的Ascll码值不相同的话,则把str2指针地址赋值给py指针。
细心的同学可以发现: 这里的主串第三个字符'b'Ascll码值恰好也和子串首元素'b'Ascll码值相同。
那两个指针同时往后进行比较,这里的py指针是指向子串的'\0'字符,也就是说明该子串是在主串是出现过的。那应该返回子串中首元素字符在主串第一次出现的地址。也就是说cp指向那个元素的地址。我们就直接把指针变量cp强转为char*,返回去即可。

10.2.3 例子3

如图:

分析: 这个例子也是相对比较容易,因为子串中的字符'q'在主串中'\0'字符之前是完全没有出现过的。
因此我们这里直接返回空字符串NULL就行。

10.2.4 特殊情况处理

10.2.4.1 情况1

如图:

这里的情况1主要是如果主串长度比子串长度还要短,该如何处理?
比方说子串的第四个字符为'd',主串的第四个字符已经指向'\0'了。那这样就无需往后进行比较了。

10.2.4.2 情况2

如图:

这里的情况2主要是子串为空字符串时,该怎么处理?

解决方案:
如下两图所示:


我们可以用Everything这个工具搜strstr.c这个文件,看看VS真实的库里面的strstr函数是怎么处理当子串为空字符串的情况吧~
通过观察,可以发现这里面当str2为空字符串时,直接返回str1主串中首元素的地址,将其强转为char*的指针返回去就行。

10.2.5 算法实现

好,相信同学们看了上面的动图以及细致的讲解,自己应该是能够理解这个strstr函数的用法以及逻辑啥的。
接下来博主给大家上代码以及注释,希望大家能够理解地更加透彻。

代码如下:

#include <stdio.h>
char* my_strstr(const char* str1, const char* str2)//str1指的是主串首元素的地址,str2指的是子串首元素的地址
{
	char* cp = (char*)str1;//这里将s1数组中的首元素地址赋给指针变量cp,由于my_strstr参数用const修饰了,这里要将它强转为char*的指针才行。
	char* px, * py;//创建指针变量px和py,
	if (!*str2) {
		return (char*)str1;//当s2是空字符串,返回的是s1首元素的地址
	}
	while (*cp)//每循环一次,指针变量cp都会向后偏移一个元素,直到指针变量cp指向'\0',为假,则跳出循环 
	{
		px = cp;//将cp指针所指向字符的地址赋给px指针所指向字符的地址,这个是用来记录指针px和py分别指向主串中某个字符和子串首元素相同Ascll码值的位置
		py = ((char*)str2);//这个是记录子串首元素的地址,如果指针变量px和指针变量py在遍历比较字符Ascll码值时不相同的话,把str2指针强转为char*,赋值给py指针
		while (*px && *py && (*px == *py))//这里是我们要先确保主串和子串都不为空字符串,才在里面进行比较,然后px指针变量指向的字符元素要和px指针变量指向的字符元素要相同才行。
		{
			px++, py++;//每遍历一次,px和py指针变量同时向后偏移一个元素
		}
		if (!*py)//当py指针指向的是'\0'字符,然后其Ascll码值为0,(!0)为真,则会执行if里面那条语句
		{
			return (char*)cp;//这里面我们是返回指针变量cp的地址回去的,这是因为指针变量cp恰好是记录着指针px指向的元素和子串首元素相同的位置。
		}
		cp++;//每循环一次,指针变量cp都会向后偏移一个元素
	}
	return (NULL);//如果遍历主串的每个字符后,还是找不到子串中每个字符,那就直接返回空字符串NULL。
}



int main() {

	char str1[] = "abbbcdef";
	char str2[] = "abcdef";
	char *ret=my_strstr(str1, str2);//my_strstr函数的作用是将主串str1、子串str2地址传过去。然后返回的地址用char*的指针来进行接收
	printf("%s\n", ret);//这里的指针变量ret拿的是那个字符在主串str1第一次出现该字符的位置,然后从这个字符地址开始往后进行打印,直到遇到'\0'字符位置

	return 0;
}

希望同学们看了这个代码注释后,能够彻底理解strstr函数模拟实现的方法。
下来可以多去尝试动手写一下这个代码,提升一下自己的代码能力啥的。

10.2.5 VS运行效果

我们这里就放三张运行效果图,看看VS运行结果是否符合我们预期吧:
如图:



分析:VS运行结果来看,我们发现这三张图的运行结果是符合我们预期的,跟我们分析的是差不多的。
只要是子串的某个字符在主串中是毫无出现过的,那就返回空字符串NULL,如果是出现过的,则找到子串首字符在主串中第一次出现的地址,将它返回去即可。

好,strstr函数我们就讲到这里,这个函数相对比较复杂,大家课后要多看多写才行~

11.strtok函数的使用

11.1 strtok函数介绍:

它的函数原型如下:

char *strtok(char *str,const char *sep)

详细的函数介绍如下:

总结:

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

11.1 strtok函数案例详解:

在上面的strstr函数讲解中,相信同学们仅仅只是听了strtok函数的基本规则,但是还不知道怎么使用,接下来博主将会以一个例子来进行讲解。
如图:

通过上图: 相信同学们能够知道理解什么叫源字符串以及分隔符字符串的。
好,那我们现在的任务是把分隔符旁边的字符串给打印出来,打印的预期效果如下:

那我们该如何设计这个算法呢?
我们可以这么设计~

从图中: 可以看出;我们先创建一个指针变量ret,将其初始化为NULL,然后首次调用strtok函数的话,第一个参数应该是填源字符串中首元素的地址str,第二个参数应该是填分隔符字符串的指针。
那么strtok函数会先分割字符串"Keven-Zhou",将分隔符-替换为\0,然后返回字符串"Keven"的指针,并把它赋值给ret
那有同学可能要问:那第二次调用strtok函数还是这么写吗?
答案不是的。
具体写法如下:

为什么要这么写呢: 因为我们这里是传递NULL作为第一个参数,表示继续在上次处理的字符串中查找下一个分隔符。如果找到了分割符,则返回下一个分割后的子字符串的指针;如果没有找到分隔符,则返回NULL
那我们来看: 我们之前定义一个这个字符串指针sep,用于存储分隔符,即“-@.”。这里无论sep字符串指针顺序如何,strtok函数都会按照sep中的每个字符进行分割。
通俗点来讲: 也就是说分割后的子字符串的指针,如果没有找到三个分隔符,就会返回NULL

11.1 strtok函数算法实现:

好,当我们讲了上面的案例,相信同学们是可以理解这个strtok函数的,下面来讲一下这个函数的代码实现。
代码如下:

#include<stdio.h>
#include <string.h>
int main() {

	char arr[] = "Keven-Zhou@qq.com";
	const char* sep = ".-@";//sep是字符串指针,用来存放sep分隔符字符串的内容
	char* ret = NULL;//这里先将ret初始化为NULL
	ret = strtok(arr, sep); printf("%s\n", ret);
	ret = strtok(NULL, sep); printf("%s\n", ret);
	ret = strtok(NULL, sep); printf("%s\n", ret);
	ret = strtok(NULL, sep); printf("%s\n", ret);
	ret = strtok(NULL, sep); printf("%s\n", ret);

	return 0;
}

VS运行效果如下:

大家有没有发现第五次再次调用strtok函数,它这里以%s打印就是(null),这是因为当我们把子字符串"com"打印完之后,后面已经再也找不到分隔符,则返回NULL

同时,需要注意的是: 这个代码这么写是比较挫的,这里是我们知道源字符串的分割符的数量以及源字符串的长度是多少,才能把它里面的子字符串给打印出来,如果说到时我们不知道那个字符串长度是多少,以及里面有多少个分隔符,也是不能够把相应的子字符串给打印出来,因此我们这里要对这个代码进行改进

代码改进: 仔细观察,我们可以发现刚刚写的代码实际上可以很多步骤都是重复的,那也就是说我们可以用一个for循环就能改进原先的代码,把4子字符串给打印出来。
代码如下:

#include<stdio.h>
#include<string.h>
int main() {

	char arr[] = "Keven-Zhou@qq.com";
	const char* sep = ".-@";//sep是字符串指针,用来存放sep分隔符字符串的内容
	char* ret = NULL;//这里先将ret初始化为NULL
	for (ret = strtok(arr, sep); ret != NULL; ret = strtok(NULL, sep))//只要strtok函数没有找到分隔符,则返回NULL到指针变量ret中,所以for循环条件表达式为假,则跳出循环。
    {
		printf("%s\n", ret);
	}
	return 0;
}

VS运行效果:

从图中: 我们看到VS编译器确实是把这个源字符串分割成4个子字符串给打印出来,直到指针变量ret的值为NULL,循环条件为假,才跳出for循环。

好,这个strtok函数的使用我们就讲到这里。相信同学们应该知道怎么使用了~
对了,关于这个strtok函数的模拟实现可能比较复杂,由于博主的实力不够,可能讲不了,非常抱歉哈!如果同学们有兴趣的话可以下来研究一下这个源码
如下:

#include<stdio.h>
#include <string.h>

char* my_strtok(char* str, const char* delim)
{
    static char* next_start = NULL;  //保存到静态存储区,函数返回后不会被销毁

    if (str == NULL && (str = next_start) == NULL)
    {
        return NULL;
    }

    char* s = str;
    const char* t = NULL;

    while (*s)
    {
        t = delim;

        while (*t)
        {
            if (*t == *s)
            {
                next_start = s + 1;

                if (s == str)    //第一个字符就是分隔符
                {
                    str = next_start;
                    break;
                }
                else
                {
                    *s = '\0';
                    return str;
                }
            }
            else
            {
                t++;
            }
        }

        s++;
    }
    printf("%s\n", str);//由于'\0'是字符串结束的标志,所以第四个子字符串的字符'm'后面是'\0'字符,因此当my_strtok函数把字符c前面的分隔符'.'改为‘\0’,也会把这个后面的子串com给打印出来,如果不加这句话,会直接返回NULL,那就只能打印三条子字符串的语句了。
    return NULL;
}
int main() {

	char arr[] = "Keven-Zhou@qq.com";
	const char* sep = ".-@";//sep是字符串指针,用来存放sep分隔符字符串的内容
	char* ret = NULL;//这里先将ret初始化为NULL
    for (ret=my_strtok(arr, sep); ret!=NULL; ret=my_strtok(NULL, sep)) {
        printf("%s\n", ret);
    }
	return 0;
}

效果如下:

12.strerror 函数的使用

12.1 strerror函数的基本介绍

它的函数原型如下:

char * strerror ( int errnum );

具体的函数介绍下所示:

总结

  • strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。
  • 在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在errno.h这个头文件中说明的。
  • C语言程序启动的时候就会使用一个全面的变量erron来记录程序的当前错误码。只不过程序启动的时候errno0,表示没有错误,当我们使用标准库中的函数的时候发生了某种错误,就会讲对应的错误码,存放在errno中中,而一个错误码的数字是很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror函数就可以将对应的错误信息字符串的地址返回。

12.2 strerror 函数的使用

相信同学们看了上面函数介绍未免会有点懵,没事,博主这里直接上代码,希望大家能够理解。

12.2.1举例1

#include <stdio.h>
#include <string.h>
#include<errno.h>
//我们打印一下0~10这些错误码对应的信息
int main() {

	int j = 0;
	for (j = 0; j <= 10; j++) {
		printf("%-3d%s\n", j+1,strerror(j));//根据错误码打印错误信息
	}

	return 0;
}

注意,这里的运行结果可能跟操作系统和VS的版本有关系。
这里博主是在Window11+VS2022环境下输出的结果。
如下:

12.2.2举例2

需要注意的是: 这里的例子2的代码可能涉及一些我们没讲过的知识,就是C语言文件指针的知识。不过没事,之后博主会详细讲解这个知识点,现在大家只需了解一下即可~
代码如下:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
    FILE* pFile; // 声明一个文件指针变量pFile
    pFile = fopen("unexist.ent", "r"); //打开名为unexist.ent的文件,并将文件指针赋值给pFile。文件名unexist.ent是一个不存在的文件,使用 "r" 模式以只读方式打开。

    if (pFile == NULL) // 判断文件指针是否为NULL,即文件是否打开失败
        printf("Error opening file unexist.ent: %s\n", strerror(errno)); // 打印错误信息,包括文件名和具体错误信息

    return 0;
}

VS运行效果:

这里大家有没有发现:这里打印的错误信息恰好是errno错误码为2的时候。

12.2.3 举例3

当然呢,大家也看一下了解一下perror函数,perror函数相当于将上述代码中的第582行完成了,直接将错误信息打印出来。
perror函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。

代码如下:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
    FILE* pFile; // 声明一个文件指针变量pFile
    pFile = fopen("unexist.ent", "r"); //打开名为unexist.ent的文件,并将文件指针赋值给pFile。文件名unexist.ent是一个不存在的文件,使用 "r" 模式以只读方式打开。
    if (pFile == NULL) // 判断文件指针是否为NULL,即文件是否打开失败
        perror("Error opening file unexist.ent"); // 打印错误信息,包括文件名和具体的错误信息

    return 0;
}

VS运行效果:

好,这个strerror函数和perror函数我们就讲到这里。

13.总结

我们来回顾本次博客主要讲了什么吧~
1.讲了strcmp函数,它的功能主要是比较两个字符串对应字符的Ascll码值的大小,以及讲了strcmp函数模拟实现。
2.讲了strncpy函数和strncat函数和strncat函数介绍及模拟实现,并且讲了这三种类型的函数和
strcpy函数和strcat函数和strcat函数的本质区别:
也就是strcpy函数和strcat函数和strcat函数是不安全的strncpy函数和strncat函数和strncat函数是相对安全的,但不是绝对安全的。一个程序员要想写bug,谁都拦不住他~
3.strstr函数的功能是返回str2子串在str1主串第一次出现的位置,如果没有的话,则返回空字符串NULL。以及讲了strstr函数的模拟实现。
4.讲了strtok函数,它的功能是用于将一个字符串分割成多个子字符串,根据给定的分隔符进行分割。
5.分别讲了strerror函数和perror函数:
一.strerror函数主要功能是把参数部分错误码对应的错误信息的字符串的地址返回来。
二.perror函数就更直接了,直接将错误信息打印出来。也就是说当打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。

好,讲到这里,希望同学们能够好好消化今天博主所讲的内容,今天将的内容偏多,需要大家课后多去敲代码实践一下,才能够将这些字符串函数理解透彻。
如果有讲的不怎么好的地方,可以私信一下博主,我们尽量给你们讲明白~

**当然如果大家觉得博主讲得不错的话,可以给博主一键三连吗 **

** 谢谢大家!!! **

版权声明:本文为博主作者:Keven-zhou原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/m0_63564767/article/details/137024213

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2024年4月16日
下一篇 2024年4月16日

相关推荐