C字符串和内存函数介绍(三)——其他的字符串函数

在#include<string.h>的这个头文件里面,除了前面给大家介绍的两大类——长度固定的字符串函数长度不固定的字符串函数。还有一些函数以其独特的用途占据一席之地。

今天要给大家介绍的是下面这三个字符串函数:strstr,strtok,strerror

学习指南:对于strstr函数有能力的小伙伴可以掌握一下它的模拟实现,但是strtok函数和strerror函数只需要知道它们的底层原理和基本使用即可。

———————————————————————————————————————————

附(难度评析表):

学习难度:

strstr:  ⭐⭐⭐⭐⭐

strtok:     ⭐⭐⭐

strerror:   ⭐

理解难度:

strstr:    ⭐ ⭐ ⭐(不考虑Kmp的部分,加上Kmp它完全可以达到五颗星的理解难度)

strtok: ⭐ ⭐ ⭐ ⭐

strerror: ⭐

目录

一、strstr函数:

1. 介绍:

2. 模拟实现:

二、strtok函数:

1. 介绍:

2. strtok函数的应用场景与工作原理:

应用场景:

工作原理:

(第一种情况)——str参数不为NULL指针:

(第二种情况)——str参数为NULL指针:

三、strerror函数:

1. 介绍:

2. 关于错误码:

3. perror函数的使用:


 

一、strstr函数:

1. 介绍:

函数原型:

 

const char * strstr ( const char * str1, const char * str2 );
函数名功能描述头文件
strstr在str1字符串查找str2字符串#include<string.h>

返回值说明:返回一个指针,该指针指向了str2在str1中首次出现的位置。如果str2不在str1里面,则返回一个NULL指针。

2. 模拟实现:

思路解析:

养成编程的好习惯:我们让str1和str2始终指向各自的字符串的首元素地址不动它们,这样一来可以避免在之后的查找遍历过程中,因为找不到str1和str2首元素的地址而无法回溯的问题。然后分别定义两个指针s1,s2遍历str1数组和str2数组。

如果对应位置两个字符串的值相等(即:s1 == s2),我们就让s1和s2同时往下走(s1++,s2++)。反之让s1回到最开始查找的位置的下一个位置(该位置我们可以用一个cp指针进行标记)。然后s2回到str2的位置(即子串的第一个元素的位置)。

然后重复上面的步骤,大概思路就是这样的。另外的话考虑到如果str1字符串剩余待检索字符不足str2的实际长度,那也就没必要继续检索了,所以在这之前我们可以先求一下str2数组的长度(这个过程可以用strlen函数,也可以自己写代码)。详情请见代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
//模拟实现strstr函数的功能:(暴力求解)

const char* my_strstr(const char* str1, const char* str2)
{
	const char* cp = str1;//记录开始遍历时的位置
	const char* s1 = str1;//遍历str1的指针
	const char* s2 = str2;//遍历str2的指针
	int cnt = 0;
	while (*s2++)
	{
		cnt++;
	}
	while (*(cp+cnt-1))
	{
		s1 = cp;
		s2 = str2;
		while (*s2 && *s1 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 != '\0')
		{
			cp++;
		}
		else
		{
			return cp;
		}
	}
	return NULL;
}

int main()
{
	const char* str1 = "abbbcef";
	const char* str2 = "bc";
	const char* ret = my_strstr(str1, str2);
	if (ret)
	{
		printf("%s", ret);
	}
	else
	{
		printf("str1里面找不着str2");
	}
	return 0;
}

上面的代码实际上一种暴力求解的思路,当然也有地方把这也叫作BF算法,这个是解决这种问题的通法。但是不是最高效的算法,这个在一个字符里面找另一个字符普遍认为最高效是KMP算法。这个算法这里博主也不过多介绍了。但是直接给代码很多小伙伴是很难理解的,而且也不是靠言语一两句话就可以讲明白的。

所以感兴趣的小伙伴可以自己下去了解一下,这里推介一个宝藏UP主的KMP算法课程,UP主很有耐心给大家介绍了该算法,虽然时间有点长,但是我觉得很适合小白来学习这个算法。

———————————————————————————————————————————

附(高质量KMP算法讲解):

【完整版】终于有人讲清楚了KMP算法,Java语言C语言实现_哔哩哔哩_bilibili

 

二、strtok函数:

1. 介绍:

函数原型:

 

char * strtok ( char * str, const char * sep );
函数名功能描述头文件
strtok按照指定分隔符对字符串进行切割#include<string.h>

参数说明:

(一)第一个参数str是待分割字符数组的首元素的地址,另外值得一提的是strtok函数会修改str的内容,所以要求str的内容必须是可以被修改的。如果str作为字符指针本身其被const关键字所修饰,亦或者是保存常量字符串首元素地址的指针,那这种传参将不被允许,是一种错误的传参方式。如图所示:

但是当你的源文件以.c作为后缀名的时候,这个传参将会被允许,但也只是表面上的允许,实际运行时会出错。这是由于编译器对于.c为后缀的源文件编译检查不够严格导致的。

(二)第二个参数sep是分隔符的集合,strtok函数允许分隔符有多个。另外sep可以是一个const char*类型的字符指针,也可以是一个字符数组的首元素的地址。

注:怎么理解上面的传参关系:我觉得可以用下面这一句话来总结概括:你可以让有大能力的人做小事,但是你不能让有小能力的或者几乎没有能力的人去做大事。

2. strtok函数的应用场景与工作原理:

应用场景:

strtok函数是用来进行字符串切割的,通过这个函数我们可以对诸如电子邮箱的信息进行一个切割提取,就比如对于一个电子邮箱:zhangsan@year.net,如果我希望能够提取到zhangsan,year,net这样的有用信息的话就可以用这样的函数。

再还有就是在计算机网路里面,对于诸如主机ID——192.168.101.32(底层使用点分十进制方式对无符号整数进行处理的结果)这样的进行一个信息提取我们也能用strtok函数。

再其次就是在密码学里面,对于很长很长的密码报文,我们可以定义一些特别的分割符来对密码报文进行一个切片的处理,以达到化繁为简的目的……

工作原理:

(第一种情况)——str参数不为NULL指针:

strtok函数处理的对象主要是str,也就是第一个参数,如果第一个参数str不是NULL指针,它会根据str的内容,同时对照sep分割符集合里面的分隔符的种类。在发现首个分隔符之后,将其置为'\0',并且记下该分隔符在数组中的位置,做一个标记,为下一轮的分割字符串做准备(后面会证明这一点)。是的,你没听错,这个函数具有记忆功能!!!

之后该函数会返回该标记开始时的位置。注意这里说的是标记开始时的位置,而并非标记本身位置。eg:对于字符串:zhangsan@year.net,如果我们的分隔符sep有“@”和“.”,那对于分隔符“@”而言,它开始时的位置应该是字母z所在的位置。而对于分隔符“.”而言,它开始时的位置应该是字母y所在的位置。

我们这里就以上面的字符串zhannsan@year.net为例,来使用strtok函数对它进行第一次分割,如图所示:

但是也有可能str里面本身就没有sep分隔符里面的任何一个分隔符,那么这个函数将返回str本身。同时标记数组末尾,以表示分割已完成。后面继续分割将返回空指针。

至于如何进行下一轮的分割我们在下面会讲,这里先给大家演示一下str数组里面不存在sep分隔符的话会出现的情况。如图所示:

注:这里的sep数组里面保存的只有空格和"\0"。

(第二种情况)——str参数为NULL指针:

如果第一个参数str用户传过去的是空指针的话,那strtok函数将会从上一次标记的位置开始,去寻找新的分割符。将其置为'\0'并标记该位置。通过这种方式来实现对同一个字符串的第二轮分割。

还是以str = "zhangsan@year.net",sep = "@."为例,我们在进行了第一轮分割的基础之上,进行第二次分割和第三次分割应该这样去传参和使用。如图所示:

之前我们有提到说:该函数具有记忆功能,在这里就得到了很好的一个体现。第一次分割结束之后就记下了@在数组中的位置,第二次分割就从该位置开始往下找新标记,找到.,然后记下.在数组中位置。依次类推,最后会标记在数组末尾,以表示分割已完成。

再继续进行分割则会返回空指针,那是不是这样的呢,我们也可以来测试一下:

大概就是这么一回事,那有小伙伴们可能又要问了,那如果在此之前strtok函数没有标记位置呢。换句话说,strtok函数没有进行哪怕任何一次的有效分割,一上来str参数就传个NULL指针过去会怎么样。这里直接说结论:这个时候会导致程序的崩溃!!!所以在实际使用过程中应当避免这样的行为。

 

三、strerror函数:

1. 介绍:

函数原型:

char * strerror ( int errnum );
函数名功能描述头文件
strerror打印错误码所对应的错误信息#include<string.h>

返回值说明:返回错误信息字符串的起始地址。

参数说明:一个整数,表示错误码。

2. 关于错误码:

首先错误码是一个整数,不同的错误码标识程序在运行过程中不同的错误信息。错误码有很多,大概100来条左右。我们可以通过下面这个代码来看一下前十个错误码(0~9)所代表的含义。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>

int main()
{
	for (int i = 0; i < 10; i++)
	{
		printf("%d.%s\n", i, strerror(i));
	}
	return 0;
}

运行截图:

在实际开发过程中,如果仅仅是用strerror函数来查看错误码所对应的错误信息,这是没有什么实际意义的。实际上每一个程序都有一个全局变量——errno。这个全局变量是保存在头文件#include<string.h>里面(更准确一点来说是在头文件errno.h里面)。保存了当前程序错误码的信息。

当程序状态异常时,编译器会根据当前状态自动更新errno的值,这个全局变量联合strerrno函数来使用可以帮助开发人员更快更准确地确定程序的问题所在。

下面是一个使用示范:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>

int main()
{
	//打开文件:
	FILE* fin = fopen("Unexist.txt", "r");
	if (fin == NULL)
	{
		printf("发生错误:%s", strerror(errno));
		return errno;
	}

	//读文件:
	//......

	//关闭文件:
	fclose(fin);
	fin = NULL;
	return 0;
}

运行截图:

注:上面的代码阅读需要一定的C语言文件操作的基本知识。C语言打开文件的函数是fopen,打开文件的方式则有r,w,a三种方式,分别表示只读,只写和追加。如果用户以只写和追加的方式来打开文件,如果当前文件不存在系统会自动创建文件。而当用户用只读的方式打开文件时,如果文件不存在则会出现错误。

3. perror函数的使用:

提到程序错误信息的打印,就不得不提及一下perror函数了。perror函数是stdio.h标准输入输出流头文件里面的一个标准库函数。

函数原型如下所示:

void perror ( const char * str )

该函数的功能是向屏幕打印错误信息,该函数的使用不需要用户对errno这个全局变量有所了解。他会自己检索底层的错误信息后并告诉我们的用户。

除此之外,函数的参数是用户输入的字符常量,这是用户希望打印的自定义信息。之后perror函数向显示器设备打印信息将按照如下格式进行:自定义信息:错误信息。这里值得一提的是打印格式中的“冒号”是编译器自己会带上的,不需要用户主动添加!

我们下面使用perror函数来改造上面的代码,以实现和使用strerror函数一样的效果:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	//打开文件:
	FILE* fin = fopen("date.txt", "r");
	if (fin == NULL)
	{
		perror("发现错误");
		return errno;
	}

	//读文件:
	//......

	//关闭文件:
	fclose(fin);
	fin = NULL;
	return 0;
}

运行截图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值