字符串分割函数--strtok与strsep

  在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

*/

猜你喜欢

转载自blog.csdn.net/z_ryan/article/details/79252381