Tokenisation strtok() strtok r() 和 strtok s()

函数 strtok 使用一组分隔符将字符串分解为较小的字符串或标记。

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

int main(void)
{
    int toknum = 0;
    char src[] = "Hello,, world!";
    const char delimiters[] = ", !";
    char *token = strtok(src, delimiters);
    while (token != NULL)
    {
        printf("%d: [%s]\n", ++toknum, token);
        token = strtok(NULL, delimiters);
    }
    /* source is now "Hello\0, world\0\0" */
}

输出:

1: [Hello]
2: [world]

分隔符字符串可以包含一个或多个分隔符,并且每次调用 strtok 时可以使用不同的分隔符字符串。

调用 strtok 继续标记相同的源字符串不应再次传递源字符串,而是将 NULL 作为第一个参数传递。如果传递相同的源字符串*,*则第一个令牌将被重新标记化。也就是说,给定相同的分隔符,strtok 将再次返回第一个令牌。

请注意,由于 strtok 不为标记分配新内存,因此会修改源字符串。也就是说,在上面的例子中,字符串 src 将被操作以产生由调用 strtok 返回的指针引用的标记。这意味着源字符串不能是 const(因此它不能是字符串文字)。它还意味着分隔字节的标识丢失(即在示例中,“,”和“!”被有效地从源字符串中删除,你无法分辨哪个分隔符匹配)。

另请注意,源字符串中的多个连续分隔符被视为一个; 在示例中,第二个逗号被忽略。

strtok 既不是线程安全也不是重入,因为它在解析时使用静态缓冲区。这意味着如果一个函数调用 strtok,它在使用 strtok 时没有调用的函数也可以使用 strtok,并且任何本身使用 strtok 的函数都无法调用它。

举例说明 strtok 不可重入的问题如下:

char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ","); 

do 
{
    char *part;
    /* Nested calls to strtok do not work as desired */
    printf("[%s]\n", first);
    part = strtok(first, ".");
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok(NULL, ".");
    }
} while ((first = strtok(NULL, ",")) != NULL);

输出:

[1.2]
 [1]
 [2]

预期的操作是外部 do while 循环应该创建由每个十进制数字字符串(1.23.54.2)组成的三个标记,对于每个标记,strtok 调用内部循环应该将它分成单独的数字字符串(123542)。

但是,因为 strtok 不是可重入的,所以不会发生这种情况。相反,第一个 strtok 正确地创建了“1.2 \ 0”标记,并且内部循环正确地创建了标记 12。但是外循环中的 strtok 位于内循环使用的字符串的末尾,并立即返回 NULL。src 阵列的第二个和第三个子字符串根本没有被分析。

Version < C11

标准 C 库不包含线程安全或可重入版本,但其他一些版本不包含,例如 POSIX’strtok_r 。请注意,在 MSVC 上,strtok 等效,strtok_s 是线程安全的。

Version >= C11

C11 有一个可选部分,附件 K,提供了一个名为 strtok_s 的线程安全和可重入版本。你可以使用 __STDC_LIB_EXT1__ 测试该功能。此可选部分未得到广泛支持。

strtok_s 函数与 POSIX strtok_r 函数的不同之处在于防止在被标记化的字符串之外存储,并通过检查运行时约束。但是,在正确编写的程序中,strtok_sstrtok_r 的行为相同。

现在使用 strtok_s 与示例产生正确的响应,如下所示:

/* you have to announce that you want to use Annex K */ 
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif

char src[] = "1.2,3.5,4.2";  
char *next = NULL;
char *first = strtok_s(src, ",", &next);

do 
{
    char *part;
    char *posn;

    printf("[%s]\n", first);
    part = strtok_s(first, ".", &posn);
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok_s(NULL, ".", &posn);
    }
} 
while ((first = strtok_s(NULL, ",", &next)) != NULL);

输出将是:

[1.2]
 [1]
 [2]
[3.5]
 [3]
 [5]
[4.2]
 [4]
 [2]