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
函数
如下图所示
分析: 这里博主主要是画了个图,分别把str1
和str2
数组以及里面的内存布局画出来了,以及如何去分析这个代码思路的,怎么去构思这个代码的?
图中都进行详细的介绍了,希望大家可以仔细看一下这幅图,说不定理解了这个图的内容,自己就能模拟实现这个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 nums
的strcpy
函数和strcat
以及strcmp
函数跟现在有函数参数size_t nums
的strncpy
函数和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
指针指向的元素相同,那我们分别地对px
和py
指针进行一次遍历判断,如果说py
指针遍历到子串'\0'
的位置。
也就是说该子串在主串是有出现过的,那此时我们要返回的是子串首字符在主串中第一次遇到的位置。
这个时候指针变量cp
起到了重要作用,因为它是记录子串首字符和主串第一次遇到字符Ascll
码值相同的位置。 所以我们直接把指针变量cp
强制转换为char*
的指针,将它返回去即可。
10.2.2 例子2
如图:
具体动图如下:
通过这个动图: 大家有没有发现,当这个指针变量px
和py
指向的首元素相同的话。也就是说该子串是有可能在主串中是有出现的,如果让我们设计算法的话,我们这里还需用个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
码值是相同的,然后指针变量py
和px
分别比较子串'\0'
之前的字符和对应主串字符的Ascll
码值是否相同。
但是这里当py
指针指向子串第三个元素'c'
,px
指针指向主串第四个元素'b'
,它俩的Ascll
码值是不相同的。
那我们这时应该更新指针变量px
和py
的所指向的元素地址,让它们指向主串和子串相同字符Ascll
码值的地方,但是这里没有指针记录子串首元素的地址,这显然是不合理的。
因为有可能指针变量px
从主串后面的元素开始遍历,有可能它指向的主串后面元素的Ascll
码值会和指针变量py
指向子串首元素的Ascll
码值相同,但是由于这个py
指针指向子串第三个元素的‘c’
,那如果说主串后面某个的字符是'c'
,指针变量py
往后偏移一个元素就为'\0'
,这就能就说明这个子串"bbc"
在主串出现过?
这显然是不符合逻辑的,因此我们要用一个指针变量来记录子串首元素的地址。
那该怎么记录子串首元素的地址呢?
我们先来看下图:
从图中: 可以看出strstr
函数参数是const char * str2
,也就是子串首元素的地址,那这就好办了,我们如果发现指针变量px
和py
指向主串和子串对应字符的Ascll
码值不相同的话,把指针变量str2
强转为char*
类型,再把它的地址赋给指针变量py
。这样就能有效地避免出现运行结果出错的情况了。
可能讲了太多理论,同学们也许会觉得有点抽象啥的,没事,博主这里再上一个动图,希望大家能够彻底理解这个例子~
如图:
通过动图: 我们发现当指针变量px
指向主串的第二个元素,指针变量py
指向子串的第一个元素,对应字符的Ascll
码值是相同的,那么指针变量px
和py
分别向后偏移元素,从而来比较主串和子串对应字符的Ascll
码值。
那cp
指针作用是记录px
指针最开始和子串首元素相同的位置,而str2
指针作用是用来记录子串首元素的地址,如果到时px
和py
指针变量所指向对应字符的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
来记录程序的当前错误码。只不过程序启动的时候errno
是0
,表示没有错误,当我们使用标准库中的函数的时候发生了某种错误,就会讲对应的错误码,存放在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
函数就更直接了,直接将错误信息打印出来。也就是说当打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。
好,讲到这里,希望同学们能够好好消化今天博主所讲的内容,今天将的内容偏多,需要大家课后多去敲代码实践一下,才能够将这些字符串函数理解透彻。
如果有讲的不怎么好的地方,可以私信一下博主,我们尽量给你们讲明白~
**当然如果大家觉得博主讲得不错的话,可以给博主一键三连吗 **
** 谢谢大家!!! **