C中的strtok和strsep有什么区别

时间:2011-08-28 02:11:24

标签: c strtok strsep

有人可以解释一下strtok()strsep()之间的差异吗? 它们的优点和缺点是什么? 为什么我会选择一个而不是另一个。

3 个答案:

答案 0 :(得分:39)

strtok()strsep()之间的一个主要区别是strtok()是标准化的(通过C标准,因此也是POSIX),但strsep()未标准化(通过C或POSIX;它在GNU C库中可用,并且起源于BSD)。因此,可移植代码比strtok()更有可能使用strsep()

另一个区别是对不同字符串的strsep()函数的调用可以是交错的,而使用strtok()则不能这样做(尽管可以使用strtok_r())。因此,在库中使用strsep()不会意外破坏其他代码,而在库函数中使用strtok()必须记录,因为同时使用strtok()的其他代码无法调用库功能

kernel.org strsep()的手册页说:

  

引入了strsep()函数作为strtok(3)的替代,因为后者无法处理空字段。

因此,另一个主要区别是George Gaál在答案中突出显示的区别; strtok()允许单个标记之间有多个分隔符,而strsep()期望标记之间有单个分隔符,并将相邻分隔符解释为空标记。

strsep()strtok()都修改了它们的输入字符串,并且都不允许您识别标记结束标记的分隔符(因为在结束后都在分隔符上写入NUL '\0'令牌)。

何时使用?

  • 当您需要空令牌时,可以使用strsep(),而不是在令牌之间允许多个分隔符,并且当您不介意可移植性时。
  • 如果您想在令牌之间允许多个分隔符并且不想要空标记(并且POSIX对您来说足够便携),则可以使用strtok_r()
  • 当有人威胁你的生命时,如果你不这样做,你只会使用strtok()。而你只能用它足够长的时间让你摆脱危及生命的境地;然后你会再次放弃使用它。它有毒;不要使用它。编写自己的strtok_r()strsep()比使用strtok()更好。

为什么strtok()有毒?

如果在库函数中使用,strtok()函数是有毒的。如果您的库函数使用strtok(),则必须清楚地记录它。

那是因为:

  1. 如果任何调用函数正在使用strtok()并调用也使用strtok()的函数,则会中断调用函数。
  2. 如果您的函数调用任何调用strtok()的函数,则会破坏函数对strtok()的使用。
  3. 如果您的程序是多线程的,那么在任何给定时间,一个线程最多可以使用strtok() - strtok()次调用。
  4. 此问题的根源是调用之间保存的状态,允许strtok()在停止的位置继续。除了“不要使用strtok()”之外,没有合理的方法可以解决问题。

    • 如果可用,您可以使用strsep()
    • 您可以使用POSIX的strtok_r()(如果可用)。
    • 如果可用,您可以使用Microsoft的strtok_s()
    • 名义上,您可以使用ISO / IEC 9899:2011附件K.3.7.3.1函数strtok_s(),但其界面不同于strtok_r()和Microsoft的strtok_s()

    BSD strsep()

    char *strsep(char **stringp, const char *delim);
    

    POSIX strtok_r()

    char *strtok_r(char *restrict s, const char *restrict sep, char **restrict state);
    

    Microsoft strtok_s()

    char *strtok_s(char *strToken, const char *strDelimit, char **context);
    

    附件K strtok_s()

    char *strtok_s(char * restrict s1, rsize_t * restrict s1max,
                   const char * restrict s2, char ** restrict ptr);
    

    请注意,这有4个参数,而不是strtok()上的其他两个变体中的3个参数。

答案 1 :(得分:7)

来自GNU C Library手册 - Finding Tokens in a String

  

strsepstrtok_r之间的一个区别是,如果输入字符串在一行strsep中包含来自分隔符的多个字符,则会为分隔符中的每对字符返回一个空字符串。这意味着程序通常应该在处理之前测试strsep返回一个空字符串。

答案 2 :(得分:2)

strtok()strep()的第一个区别是它们处理输入字符串中连续定界符的方式。

strtok()处理连续的定界符:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    const char* teststr = "aaa-bbb --ccc-ddd"; //Contiguous delimiters between bbb and ccc sub-string
    const char* delims = " -";  // delimiters - space and hyphen character
    char* token;
    char* ptr = strdup(teststr);

    if (ptr == NULL) {
        fprintf(stderr, "strdup failed");
        exit(EXIT_FAILURE);
    }

    printf ("Original String: %s\n", ptr);

    token = strtok (ptr, delims);
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok (NULL, delims);
    }

    printf ("Original String: %s\n", ptr);
    free (ptr);
    return 0;
}

输出:

# ./example1_strtok
Original String: aaa-bbb --ccc-ddd
aaa
bbb
ccc
ddd
Original String: aaa

在输出中,您可以看到令牌"bbb""ccc"一个接一个。 strtok() 不表示出现了连续的定界符。另外,strtok() 修改输入字符串

strep()处理连续的定界符:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    const char* teststr = "aaa-bbb --ccc-ddd"; //Contiguous delimiters between bbb and ccc sub-string
    const char* delims = " -";  // delimiters - space and hyphen character
    char* token;
    char* ptr1;
    char* ptr = strdup(teststr);

    if (ptr == NULL) {
        fprintf(stderr, "strdup failed");
        exit(EXIT_FAILURE);
    }

    ptr1 = ptr;

    printf ("Original String: %s\n", ptr);
    while ((token = strsep(&ptr1, delims)) != NULL) {
        if (*token == '\0') {
            token = "<empty>";
        }
        printf("%s\n", token);
    }

    if (ptr1 == NULL) // This is just to show that the strep() modifies the pointer passed to it
        printf ("ptr1 is NULL\n");
    printf ("Original String: %s\n", ptr);
    free (ptr);
    return 0;
}

输出:

# ./example1_strsep
Original String: aaa-bbb --ccc-ddd
aaa
bbb
<empty>             <==============
<empty>             <==============
ccc
ddd
ptr1 is NULL
Original String: aaa

在输出中,您可以看到<empty>bbb之间的两个空字符串(通过ccc表示)。这两个空字符串用于"--""bbb"之间的"ccc"。当strep()' '之后找到分隔符"bbb"时,它将分隔符替换为'\0'字符并返回"bbb"。此后,strep()找到了另一个定界符'-'。然后它将分隔符替换为'\0'字符并返回空字符串。下一个定界符也是如此。

strsep()返回指向空字符的指针(即,值为'\0'的字符)时指示连续的定界符。

strsep() 修改输入字符串以及指针,其地址作为第一个参数传递给strsep()

第二个区别是strtok()依靠静态变量来跟踪字符串中当前的解析位置。此实现要求在开始第二个字符串之前完全解析一个字符串。但是strsep()并非如此。

在另一个strtok()未完成时呼叫strtok()

#include <stdio.h>
#include <string.h>

void another_function_callng_strtok(void)
{
    char str[] ="ttt -vvvv";
    char* delims = " -";
    char* token;

    printf ("Original String: %s\n", str);
    token = strtok (str, delims);
    while (token != NULL) {
        printf ("%s\n", token);
        token = strtok (NULL, delims);
    }
    printf ("another_function_callng_strtok: I am done.\n");
}

void function_callng_strtok ()
{
    char str[] ="aaa --bbb-ccc";
    char* delims = " -";
    char* token;

    printf ("Original String: %s\n", str);
    token = strtok (str, delims);
    while (token != NULL)
    {
        printf ("%s\n",token);
        another_function_callng_strtok();
        token = strtok (NULL, delims);
    }
}

int main(void) {
    function_callng_strtok();
    return 0;
}

输出:

# ./example2_strtok
Original String: aaa --bbb-ccc
aaa
Original String: ttt -vvvv
ttt
vvvv
another_function_callng_strtok: I am done.

函数function_callng_strtok()仅打印令牌"aaa",而不会打印输入字符串的其余令牌,因为它调用another_function_callng_strtok(),而后者依次调用strtok()并进行设置完成提取所有标记后,strtok()NULL的静态指针。控件返回到function_callng_strtok() while循环,由于指向strtok()的静态指针导致循环条件{{1},NULL返回NULL }并退出循环。

在另一个false未完成时呼叫strsep()

strsep()

输出:

#include <stdio.h>
#include <string.h>

void another_function_callng_strsep(void)
{
    char str[] ="ttt -vvvv";
    const char* delims = " -";
    char* token;
    char* ptr = str;

    printf ("Original String: %s\n", str);
    while ((token = strsep(&ptr, delims)) != NULL) {
        if (*token == '\0') {
            token = "<empty>";
        }
        printf("%s\n", token);
    }
    printf ("another_function_callng_strsep: I am done.\n");
}

void function_callng_strsep ()
{
    char str[] ="aaa --bbb-ccc";
    const char* delims = " -";
    char* token;
    char* ptr = str;

    printf ("Original String: %s\n", str);
    while ((token = strsep(&ptr, delims)) != NULL) {
        if (*token == '\0') {
            token = "<empty>";
        }
        printf("%s\n", token);
        another_function_callng_strsep();
    }
}

int main(void) {
    function_callng_strsep();
    return 0;
}

在这里您可以看到,在完全解析一个字符串之前调用# ./example2_strsep Original String: aaa --bbb-ccc aaa Original String: ttt -vvvv ttt <empty> vvvv another_function_callng_strsep: I am done. <empty> Original String: ttt -vvvv ttt <empty> vvvv another_function_callng_strsep: I am done. <empty> Original String: ttt -vvvv ttt <empty> vvvv another_function_callng_strsep: I am done. bbb Original String: ttt -vvvv ttt <empty> vvvv another_function_callng_strsep: I am done. ccc Original String: ttt -vvvv ttt <empty> vvvv another_function_callng_strsep: I am done. 并没有什么区别。

因此,strsep()strtok()的缺点是都修改了输入字符串,但是strsep()strsep()更具优势,如上所述。

来自strsep

  

strsep()函数旨在替代strtok()函数。虽然出于可移植性的原因应该首选strtok()函数(它符合ISO / IEC 9899:1990(``ISO C90'')),但是它无法处理空字段,即检测由两个相邻的分隔符分隔的字段,或一次使用多个字符串。 strsep()函数首先出现在4.4BSD中。


供参考: