【算法】【C语言进阶】C语言字符串操作宝藏级别汇总 strtok函数 strstr函数该怎么用?【超详细的使用解释和模拟实现】
【算法】【C语言进阶】C语言字符串操作宝藏级别汇总【超详细的使用解释和模拟实现】
作者: @小小Programmer
这是我的主页:@小小Programmer
在食用这篇博客之前,博主在这里介绍一下其它高质量的编程学习栏目:
数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏:算法 这里可以说是博主的刷题历程,里面总结了一些经典的力扣上的题目,和算法实现的总结,对考试和竞赛都是很有帮助的!
先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要的动力。看完之后别忘记关注我哦!️️️
本篇为不收藏必后悔系列篇~
除了字符串操作函数,博主之前还输出过一篇有关内存操作函数的详解博客和一篇动态内存管理的宝藏级别总结,跟今天这篇的内容也算是息息相关的博客,同样也是干货满满哦!需要的伙伴可以通过传送门食用~
【内存操作】C语言内存函数介绍以及部分模拟实现【初学者保姆级福利】超详细的解释和注释
【动态内存】C语言动态内存管理及使用总结篇【初学者保姆级福利】
本篇博客所介绍的函数在使用时需包含#include<string.h>
的头文件。
当然,如果我们需要用assert()
来断言指针的话,还要包含#include<assert.h>
的头文件。
文章目录
strlen函数
函数原型:
size_t strlen ( const char * str );
作用:求字符串长度(以'\0'
为字符串结束标志)。
使用举例:
int main() {
char arr[] = "abcdef";
//char arr[] = { 'a','b','c','d','e','f' };
//如果这样初始化,没有'\0'求出来的结果就是随机值
int len = strlen(arr);
printf("%d\n", len);
return 0;
}
要注意的点:strlen
函数的返回值时size_t
类型,如果不注意的话容易写出bug。 比如:
int main() {
if (strlen("abc") - strlen("qwertyu") > 0) {
printf(">\n");
}
else {
printf("<\n");
}
return 0;
}
此时会打印>
,因为两个size_t
做减法小于0时,得到的是一个很大的正整数,所以会打印>
,这里涉及到整型在内存中的存储问题,我们使用的时候稍微注意一下就行了。
strlen函数的模拟实现
- strlen的实现方法有很多,有计数器法,有递归法,有指针减指针的方法,这里博主给出的时计数器法。这几种方法的思路都比较简单,这里就不一一展示。
size_t my_strlen(const char* str) {
assert(str);
int count = 0;
while (*str != '\0') {//如果没遇到'\0'说明字符串还没到结尾
count++;
str++;//指针++,找尾,没找到->count++
}
return count;//最后返回计数器结果就可以了,思路很简单
}
int main() {
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
strcpy函数
函数原型:
char * strcpy ( char * destination, const char * source );
作用:字符串拷贝,将source指向的字符串内容拷贝到destination所指向的字符串中。
使用举例:
int main() {
char arr1[20] = { 0 };
char arr2[] = "abcdef";
strcpy(arr1, arr2);
printf("%s\n", arr1);//这里打印的就是abcdef
return 0;
}
要注意的点:
- 注意,原字符串必须以
'\0'
结尾 - 拷贝的时候会把原字符串的
'\0'
也拷贝过去 - 目标空间一定要足够大
- 目标空间必须可变(不能传常量字符串的指针过去)
strcpy函数的模拟实现
//如果只是拷贝的话是不需要返回值的,但是如果返回一个dest的起始位置,就可以实现函数的链式访问了。
//src的内容不需要改,所以可以加const,使函数更安全
char* my_strcpy(char* dest, const char* src) {
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++) {//这种拷贝方式是会把'\0'也拷贝过去的,
//这样就和库里面一样了
;
}
return ret;
}
int main() {
char arr1[20] ="xxxxxxxxxxxxxxxxx";
char arr2[] = "abcdef";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
strcat函数
函数原型:
char * strcat ( char * destination, const char * source );
作用:字符串追加,将source指向字符串的内容追加到destination所指向的字符串的内容的后面。
使用举例:
int main() {
char arr1[20] = "hello ";
char arr2[] = "bit";
strcat(arr1, arr2);
printf("%s\n", arr1);//这里打印的就是hello bit了
return 0;
}
要注意的点:
- 原字符串要有
'\0'
,目标空间也要'\0'
,否则找不到开始追加的标志
否则它也会继续往后越界访问,知道找到'\0'
为止
strcat函数的模拟实现
追加分为两步:1.找到dest
的结尾 2.追加过去(这个过程就和strcpy一样了) 思路都很简单
char* my_strcat(char* dest, const char* src) {
assert(dest && src);
//1.找原字符串中的\0
char* start = dest;
while (*start) {
start++;
}
//开始拷贝
while (*start++ = *src++) {
;
}
return dest;
}
int main() {
char arr1[20] = "hello ";
char arr2[] = "bit";
//my_strcat(arr1, arr2);
printf("%s\n", my_strcat(arr1, arr2));//hello bit
return 0;
}
strcmp函数
函数原型:
int strcmp ( const char * str1, const char * str2 );
作用:比较两个字符串(注意:这里比较的不是长度,而是相对应位置的所对应的值的大小)。
返回值: 如果str1
大于str2
,返回一个大于0的数字,如果相等返回0,如果小于返回一个小于0的数。
使用举例:
int main() {
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);//ret是一个小于0的数字
return 0;
}
strcmp函数的模拟实现
思路:通过两个指针,一个指向str1,一个指向str2,一步一步向后比较,发现不一样的就停止比较,如果都同时到达‘\0’,说明两个字符串相等。
int my_strcmp(const char* str1, const char* str2) {
assert(str1 && str2);
while (*str1 == *str2) {//一样的话才进循环,不一样的话停止比较
if (*str1 == '\0')return 0;//比完了
str1++;
str2++;
}
return *str1 - *str2;
}
int main() {
char arr1[] = "abcdef";
char arr2[] = "abq";
//int ret = strcmp(arr1, arr2);
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
strncpy,strncmp和strncat函数
以上介绍的都是长度不受限制的字符串操作函数,下面要介绍的这三个,都是长度受限的字符串操作函数,使用方法几乎和上面那几个完全一样,只是多了一个参数,仅此而已。
- 通过按f10或者f11调试,我们就可以很好的了解函数具体是如何工作的,这里不赘述给大家了。
第三个参数: 要拷贝的长度,要比较的长度,要追加的长度。
使用举例:
strncpy:
int main() {
char arr1[] = "abcdef";
char arr2[] = "qwertyuiop";
strncpy(arr1, arr2, 3);
printf("%s\n", arr1);//qwedef
return 0;
}
strncat:
int main() {
//char arr1[20] = "abcdef";
char arr1[20] = "abcdef\0xxxxxxxx";
char arr2[] = "qwertyuiop";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);//abcdefqwert
return 0;
}
//追加结束之后,还会主动放一个\0进去
strncmp:
int main() {
char arr1[] = "abcdef";
char arr2[] = "abcdefqwert";
int ret = strncmp(arr1, arr2, 4);
printf("%d\n", ret);//0
return 0;
}
strstr函数
函数原型:
char * strstr ( const char *, const char * );
作用:判断一个字符串是不是另一个字符串的字串
找到了-返回字串在里面出现的地址
找不到-返回空
使用举例:
int main() {
char arr1[] = "abcdefg";
char arr2[] = "cdef";
char* ret = strstr(arr1, arr2);
if (ret == NULL) { printf("找不到子串\n"); }
else {
printf("%s\n", ret);//cdefg
}
return 0;
}
strstr函数的模拟实现
- 其实,这个函数的模拟实现就是一个经典的字符串找字串问题,这道题所蕴含的思想远不止解决这一个问题。在这里博主提供的是双指针算法的解法,当然,效率更高的还有KMP算法等方法,但是这些算法对于初学的我们来说算比较困难的,有兴趣的伙伴可以自己学习一下KMP算法。
双指针解法: 一个指针跟随,记录开始匹配的位置,另一个指针用于一个一个匹配。
一共要的定义三个指针。
char* my_strstr(const char* str1, const char* str2) {
assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* cur = str1;
while (*cur) {
s1 = cur;
s2 = str2;//这次匹配不成功,回到原位重新开始匹配
while (*s1 && *s2 && *s1 == *s2) {
s1++;
s2++;
}
if (*s2 == '\0') {//这次匹配成功了
return (char*)cur;
}
cur++;//匹配失败,说明从cur开始匹配不行
}
return NULL;
}
//测试案例
int main() {
char arr1[] = "abcdefg";
char arr2[] = "cdef";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL) { printf("找不到子串\n"); }
else {
printf("%s\n", ret);
}
return 0;
}
strtok函数
函数原型:
char * strtok ( char * str, const char * sep);
作用:切割一个字符串,str
是待操作的字符串,sep
是切割符号的集合。
-
sep
参数是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或多个由sep
字符串中一个或多个分隔符分割的标记 -
strtok
函数找到str中的下一个标记,并将其用'\0'
结尾,返回一个指向这个标记的指针
(注:strtok函数会改变被操作的字符串,所以在使用的时候一般都是临时拷贝的内容) -
strtok函数的第一个参数不为
NULL
时,函数将找到str
中的第一个标记,strtok
函数将保存它在字符串中的位置 -
strtok函数的第一个参数为
NULL
时,函数将在同一个字符串中被保存的位置开始,查找下一个标记 -
如果字符串中不存在更多的标记,则返回
NULL
看起来比较复杂,我们看一个例子就可以很好的理解了:
int main() {
char arr[] = "xiaoxiaoprogrammer@yeah.net";
char buf[30] = { 0 };
strcpy(buf, arr);
const char* sep = "@.";
//"@."--sep
printf("%s\n",strtok(buf, sep));//只找到第一个标记
printf("%s\n",strtok(NULL, sep));//从保存好的位置继续往后找
printf("%s\n", strtok(NULL, sep));//从保存好的位置继续往后找
return 0;
}
当然,这样写的代码就非常刻板,因为我们提前知道了要分割成3份,所以我们还可以优化。
- 我们发现传buf只需要传一次,所以巧妙利用for循环,for循环初始化只执行一次
int main() {
char arr[] = "xiaoxiaoprogrammer@yeah.net";
char buf[30] = { 0 };
strcpy(buf, arr);
const char* sep = "@.";
//"@."--sep
//我们发现传buf只需要传一次,所以巧妙利用for循环,for循环初始化只执行一次
char* str = NULL;
//巧妙利用for循环
for (str = strtok(buf, sep); str != NULL; str = strtok(NULL, sep)) {//处理完要赋值
printf("%s\n", str);
}
return 0;
}
至于这个函数该如何实现,我们就先不关心了,有兴趣的伙伴可以自己尝试一下,至于为什么每次用完strtok,不用传str和切割位置过去,它还记得上次切割完的位置在哪里?我们不需要深入了解,但是,我们知道的是,它里面肯定包含了一个静态的指针变量,在strtok调用完之后,还记录着上一次切割的位置的地址。
尾声
以上就是今天这篇博客的全部内容了。如果还对内存函数,动态内存处理,算法或者数据结构的伙伴,可以到文章开头的传送门食用。如果你感觉这篇博客对你有帮助的话,不要忘了一键三连,点赞收藏关注再离开噢!