前言
之前我们用两篇文章介绍了strlen、strcpy、stract、strcmp、strncpy、strncat、strncmp这些函数
第二篇文章strcmp、strncpy、strncat、strncmp
今天我们就来学习:
话不多说,我们直接开始
strstr
返回值
如果s2是s1的子串,就返回子串的首元素地址,
如果没找到,就返回空指针
补充说明
当s1中存在多个s2时,其返回的地址,是s2在s1中第一次出现的地址
一般情况
int main() { char* p1 = "abcdefabcdef"; char* p2 = "def"; char * ret = strstr(p1, p2); if (ret == NULL) { printf("0\n"); } else { printf("%s\n", ret); } return 0; }
模拟实现
基本思路
两种情况:
*s1 == *s2
*s1 != *s2
*s1 == *s2
s1向后移动,s2也向后移动,看是否继续相等
while (*s1 == *s2) { s1++; s2++; }
*s1 != *s2
s1不动,s2向后移动一位,看二者是否相等
while (*s1 == *s2) { s1++; s2++; } s1++;
问题1
当s2是’\0‘时,说明要查找的字符串已经结束了,这时就结束查找
问题2
当s1是’\0‘时,说明被查找的字符串已经结束了,这时就结束查找
问题3
当我们真的找到这个子串的时候,想要返回首元素地址,却发现s2指向的是字串的最后一个元素。
问题4
当字符串1为“abbbc”,字符串2为“bbc”时,
是得不到正确结果的,因为s2指向的已经是第二个b了,s1指向的也是第二个b
解决方案
想解决问题三和四,就单独创建两个变量p1、p2来存储s1和s2就行了
并且,当s1和s2指向的元素相等时,要创建一个变量cur来记录这个位置,方便s1和s2回到这个位置继续查找。
最终代码
char* my_strstr(const char* s1, const char* s2) { assert(s1 && s2); if (*s2 == 0)//当s2是空字符串时,传进来的s2是‘\0’ { return (char*)s1;//此时直接返回s1即可 } char* p1 = s1; char* p2 = s2; char* cur = s1; while (*cur) { p1 = cur; p2 = s2; while ((*p1 == *p2) && (*p1 != '\0') && (*p2 != '\0')) { p1++; p2++; } if (*p2 == '\0') { return cur; } cur++; } return NULL; } int main() { char* p1 = "abcdef"; char* p2 = "def"; char* ret = strstr(p1, p2); if (ret == NULL) { printf("0\n"); } else { printf("%s\n", ret); } return 0; }
写的还是比较清晰的,有疑问可以在文章下面留言~
strstr函数在编译器中的实现
我没在库里找到这个文件,所以在网上复制了一份源码
#include <stdio.h> char * __cdecl strstr(const char *str1, const char *str2) { char *cp = (char *)str1; char *s1, *s2; if (!*str2) return((char *)str1); while (*cp) { s1 = cp; s2 = (char *)str2; while (*s2 && !(*s1 - *s2)) s1++, s2++; if (!*s2) return(cp); cp++; } return(NULL); } int main(int argc, char *argv[], char *envp[]) { char str[] = "asfasfas"; char *p = "asas"; char ret = '0'; ret = strstr(str, p); if (ret != NULL) { printf("%c", ret); } else { printf("NULL"); } return 0; }
补充说明,在源代码中,我们可以看到参数都用const保护起来了,所以在返回值那里需要进行强制类型转换(char*)
//虽然不转换代码也能运行,但还是严谨一点好
拓展
KMP算法和上面的实现过程结果是一样的,但更高级,感兴趣的可以去了解一下
strtok(会用即可)
介绍
- sep参数是个字符串,定义了用作分隔符的字符合集
2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
记。
3.strtok函数找到str中的下一个标记,并将其用 \0结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
4.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
6.如果字符串中不存在更多的标记,则返回NULL 指针。
详细解释
下面我们看一段代码,来实际应用一下
int main() { char arr[] = "hello@world.nicetomeetyou"; char* p = "@."; char buff[10000] = { 0 }; strcpy(buff, arr); char* ret = strtok(arr, p); printf("%s\n", ret); return 0; }
1.p就是sep,里面的字符就是分隔符
2.arr就是str
3.strtok函数会从str开始,逐个向后查找,当找到sep中的分隔符时,strtok就将它改成’\0’,并返回分割出的这个字符串的首元素地址。
下一次查找就从\0后面开始寻找(等于是创建了一个静态变量存储’\0’后面的元素的地址)
4.当第二个参数不为NULL时,从sep中的的第一个分隔符开始查找
5.当第二个参数为NULL时,那就进入sep中的下一个分隔符开始查找
意思就是:查找第二个分隔符
6.当字符串str中遇到\0时,就结束查找,并返回NULL
注意:
strtok函数会破坏被查找的字符串,所以我们需要将原字符串拷贝一份,再进行查找
实际应用
像上文那样使用的话,有几个字符串,就得写几次
但实际上,我们并不会像上面那段代码那样使用strtok函数,
在上文“详细解释”中,第六点提到:当str中遇到\0时,就返回NULL
所以我们可以先创建一个变量ret定义为NULL
使用for循环来打印出分割的多个字符串
for (ret = strtok(arr, p); ret != NULL; ret = strtok(NULL, p)) { printf("%s\n", ret); }
(很神奇的一种使用方式)
strerror
介绍
char * strerror ( int errnum )
返回错误码,所对应的错误信息
使用方法
传入一个整数,输出一个地址
int main() { char* str = strerror(0); printf("%s\n", str); return 0; }
运行结果:
此处传入的整数,被称为错误码
每个错误码,都对应一个错误信息
如:
0:No error
1:Operation not permitted
实际使用
但在实际应用中,错误码不会由我们传入,而是输入errno,
errno是一个全局的错误码变量
当C语言的库函数在执行过程中,发生了错误,就会把对应的错误码,赋值给errno
需要包含头文件<errno.h>
举例
C语言中我们使用fopen函数来打开一个文件
int main() { FILE* pf = fopen("test.txt", "r"); //要打开文件的名称是test.txt,打开方式是"r",读取这个文件 //这个函数会返回一个FILE*的指针 if (pf == NULL) //当返回为空指针,说明读取失败 //但读取失败有很多原因,可能是文件不存在,可能是访问权限不够等等 { printf("%s\n", strerror(errno)); } else { printf("open file success\n"); } return 0; }
这里我们就可以通过strerror函数,来找到确切的原因
运行结果
没有这样的文件或目录
errno
想知道更多可以直接转到定义查看不同的整数对应的错误信息
结语
字符串函数就介绍到这里了,下一篇文章我们会学习字符分类函数
我们明天见~