在c/c++中,字符串分割函数主要有两种:一是strtok函数,另一个就是strsep函数。下面我们对这两个函数作一个详细解释说明。
1、strtok
原形:
char* strtok(char *str, const char *delim);
功能:
分解字符串为一组字符串;str为要分割的字符串,delim为分隔符;
返回值:
从str开头开始的一个个子串,当没有分割的子串时返回NULL。
说明:
* strtok函数工作时,若在字符串中发现分隔符会将该字符改为’\0’;
* 第一次调用时,strtok函数必须给予参数str字符串,之后调用则将str参数设置为NULL;至于为什么之后调用将str参数设置为NULL,在下文介绍;
* strtok内部记录上次调用字符串的位置,所以不支持多线程,可重入版本为strtok_r。
示例
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello,,world!#c";//strtok函数会改变源字符串,所以不能写 char* str = "";
char* delim = ",#";
char *token = NULL;
token = strtok(str,delim);
while(token)
{
puts(token);
token = strtok(NULL,delim);
}
return 0;
}
运行结果:
源码
其源码有多种实现方式,下面只简单介绍其中的一种。
//字符串分割函数
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
char* strtok(char* str,const char* delim);
int main()
{
char str[] = "hello#world#c";
char* delim = "#";
char* ret = strtok(str,delim);
while(ret)
{
puts(ret);
ret = strtok(NULL,delim);
}
return 0;
}
char* strtok(char* str,const char* delim)
{
static char* last = NULL;
register int ch;
if(str == 0)
{
str = last;
}
do
{
if((ch = *str++) == '\0')
{
return 0;
}
}while(strchr(delim,ch));
--str;
last = str + strcspn(str,delim);
if(*last)
{
*last++ = 0;
}
return str;
}
说明:
* strcspn函数,其返回值为n,表示字符串的前n个字符均未出现分隔符delim;
看了源码实现,就能明白为什么第二次传参传的时NULL了?
因为若是传NULL,表示是第二次调用此函数,就将上次的last状态赋给str,然后跳过分隔符,通过strcspn截取需要的字符串,此时last指向下一个分隔符或字符串结尾’\0’处,通过判断last是否为空,若非空,表示last此时指向分隔符,然后将分隔符置为0,即截断字符串,last然后指向下一个字符。最后返回str,重复此过程,直到字符串末尾。
2、strsep
原形:
char* strtsep(char* * str, const char *delim);
功能:
分解字符串为一组字符串;str为要分割的字符串,delim为分隔符;
返回值:
被delim分开的左边的那个字符串,同时会导致str的值会指向分隔符号右边的字符串的起始位置
示例
#include<stdio.h>
#include<string.h>
int main()
{
char s[] = "hell##hhh#www";
char* delim = "#";
char* ret = NULL;
char* str = strdup(s);
ret = strsep(&str,delim);
while(ret)
{
puts(ret);
ret = strsep(&str,delim);
}
return 0;
}
运行结果
此时,有没有发现一个问题,为什么在strtok中没有多余换行,而在strsep中却有?
这是因为若被分割的字符串有连续的多个分割符出现,strtok会返回NULL,而strsep会返回空串,所以会将其当作分割下来的字符串而打印出来。
因此,我们若想用strsep分割字符串,必须进行返回值是否为空的检验。
改进代码:
#include<stdio.h>
#include<string.h>
int main()
{
char s[] = "hell##hhh#www";
char* delim = "#";
char* ret = NULL;
char* str = strdup(s);
ret = strsep(&str,delim);
while(ret)
{
if(*ret) //返回值检验
{
puts(ret);
}
ret = strsep(&str,delim);
}
return 0;
}
运行结果:
源码
#include<stdio.h>
#include<string.h>
char *my_strsep(char **str, const char *delim);
int main()
{
char str[] = "he#llo#wor##ld*asd*jji*9";
char* delim = "*#";
char* s = strdup(str);
char* ret = my_strsep(&s,delim);
while(ret)
{
if(*ret)//排除连续出现分隔符时多打印空格
{
puts(ret);
}
ret = my_strsep(&s,delim);
}
return 0;
}
char *my_strsep(char **str, const char *delim)
{
char *begin = NULL;
char *end = NULL;
begin = *str;
if(begin == NULL)
{
return NULL;
}
//delim分隔符是单个字符,调用strchr
if(delim[0] == '\0' || delim[1] == '\0')
{
char ch = delim[0];
if(ch == '\0')
{
end = NULL;
}
else
{
if(*begin == ch)
{
end = begin;
}
else if(*begin == '\0')
{
end = NULL;
}
else
{
end = strchr(begin + 1, ch);
}
}
}
else
{
end = strpbrk(begin, delim); //delim有两个字符以上,调用strpbrk
}
if(end)
{
*end++ = '\0';
*str = end;
}
else
{
*str = NULL;
}
return begin;
}
总结
1、strtok是不可重入的(strtok_r是strtok的可重入版本),strseq是可重入的。
2.strsep和strtok都对修改了src字符串。所以不能使用字符串常量作为分割字符串。
例如:
char* str = "he*#llo*wo";
char* delim = "*#"
char* ret = strtok(str,delim); #错误,str为常量,不可更改
3.strsep和strtok对字符串分割结果不一致。若被分割的字符串有连续的多个分割符出现,strtok会返回NULL,而strsep会返回空串;因此,我们若想用strsep分割字符串,必须进行返回值是否为空的检验。
4、最好使用strsep;尽量避免使用strtok。
下面的说明摘自于最新的Linux内核2.6.29,说明了strtok()已经不再使用,由速度更快的strsep()代替。
/** linux/lib/string.c** Copyright (C) 1991, 1992 Linus Torvalds*/
/** stupid library routines.. The optimized versions should generally be found
* as inline code in <asm-xx/string.h>
* These are buggy as well..
* * Fri Jun 25 1999, Ingo Oeser <[email protected]>
* - Added strsep() which will replace strtok() soon (because strsep() is
* reentrant and should be faster). Use only strsep() in new code, please.
** * Sat Feb 09 2002, Jason Thomas <[email protected]>,
* Matthew Hawkins <[email protected]>
* - Kissed strtok() goodbye
*/