strtok()如何在C中将字符串拆分为令牌?


114

请向我解释strtok()功能的作用。该手册说它将字符串拆分为令牌。我无法从手册中了解其实际功能。

我添加了手表,str*pch在第一个while循环发生时检查其工作情况,其中的内容str仅为“ this”。屏幕上如何显示下面显示的输出?

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}

输出:

分割字符串“-这是示例字符串”。代币:
这个
一个
样品
串

5
strtok()通过在返回之前使用NUL终止标记来修改其参数字符串。如果您尝试检查整个缓冲区(str []),则会看到在连续调用之间对其进行了修改strtok()
Michael Foukarakis 2010年

而不是看的str,手表str[0]str[1]str[2],...
PMG

@pmg:我看着str [0]和str [1] .str [1]应该是'\ 0',但是那里是一个空格。
fuddin

3
老实说,我从来没有去检查过,但是我想它会存储传入的最后一个指针以及它停在的位置。然后,如果指针为NULL,它就可以继续;否则,清除位置并重新开始。
克里斯,2012年

7
@Firegun:静态变量
DCoder

Answers:


38

strtok()将字符串分成令牌。即从任何一个定界符开始到下一个定界符将是您的一个标记。在您的情况下,起始令牌将从“-”开始,并以下一个空格“”结束。然后下一个标记将从“”开始,以“,”结束。在这里,您将获得“ This”作为输出。同样,字符串的其余部分也从一个空间拆分为多个标记,最后在“”上结束最后一个标记。


一个令牌的结束条件成为下一个令牌的开始令牌?在结束条件的位置还放置一个nul字符吗?
fuddin

1
@ fahad-是的,正如其他人所建议的那样,您拥有的所有距离将被NUL字符替代。
Sachin Shanbhag

如果所有定界符都被Nul取代,那么为什么字符串包含“ -this”?它应该包含“ \ 0”
fuddin

2
@fahad-它仅将分隔符替换为NUL,而不是所有分隔符之间的字符。将字符串拆分为多个标记的一种。之所以会得到“ This”,是因为它位于两个指定的定界符之间,而不是“ -this”之间。
Sachin Shanbhag

1
@Fahad-是的,绝对是。据我所知,所有空格“,”和“-”均被NUL替换,因为您已将它们指定为定界符。
Sachin Shanbhag

212

strtok运行时函数的工作方式如下

第一次调用strtok时,会提供要标记化的字符串

char s[] = "this is a string";

在上面的字符串空间中,单词之间似乎是一个很好的分隔符,因此让我们使用它:

char* p = strtok(s, " ");

现在发生的是搜索“ s”直到找到空格字符,返回第一个标记(“ this”),p指向该标记(字符串)

为了获得下一个标记并继续使用相同的字符串,因为strtok维护指向先前传递的字符串的静态指针,所以将NULL作为第一个参数传递:

p = strtok(NULL," ");

p现在指向“是”

以此类推,直到找不到更多空间,然后最后一个字符串作为最后一个标记“字符串”返回。

更方便的是,您可以这样写,而不是打印所有令牌:

for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
  puts(p);
}

编辑:

如果要存储返回的值,则strtok需要将令牌复制到另一个缓冲区,例如,strdup(p);由于原始字符串(由内的静态指针指向strtok)在两次迭代之间被修改以返回令牌。


因此,它实际上并没有在字符串之间放置nul字符。为什么我的手表显示字符串仅留有“ THIS”?
fuddin

4
它确实确实将'找到的''替换为'\ 0'。而且,它不会在以后恢复'',因此您的字符串永久损坏了。

33
+1为静态缓冲区,这是我不了解的
IEatBagels

1
“返回第一个令牌并p指向该令牌”行中缺少的一个非常重要的细节是,strtok需要通过放置空字符代替定界符来使原始字符串发生变异(否则其他字符串函数将不知道在哪里)令牌结束)。并且它还使用静态变量来跟踪状态。
Groo

@Groo我想我已经在2017年所做的编辑中添加了该内容,但是您是对的。
AndersK

25

strtok维护一个静态的内部引用,该引用指向字符串中的下一个可用标记;如果您将其传递为NULL指针,则它将从该内部引用起作用。

这是strtok不重入的原因。一旦您将其传递给新指针,旧的内部引用就会被破坏。


您所说的旧内部参考“变得扑朔迷离”是什么意思。您是说“覆盖”吗?
ylun.ca 2015年

1
@ ylun.ca:是的,这就是我的意思。
约翰·博德

10

strtok不会更改参数本身(str)。它将该指针存储在本地静态变量中。然后,它可以更改该参数在后续调用中指向的对象,而无需将该参数传递回。(它可以使它保留的指针前进,但是需要执行其操作。)

从POSIX strtok页面:

此函数使用静态存储来跟踪两次调用之间的当前字符串位置。

有一个线程安全变体(strtok_r)不能执行这种魔术。


2
好吧,C库函数的创建可以追溯到那时,根本没有使用线程技术(就C标准而言,它只是在2011年才开始存在),因此重新进入并不是很重要(我猜)。该静态局部变量使函数“易于使用”(对于“ easy”的某种定义)。就像ctime返回静态字符串一样-实用(没有人会想知道谁应该释放它),但是如果您不太了解它,就不要重新进入并绊倒您。
Mat

这是错误的:“ strtok不会更改参数本身(str)。” puts(str);strtok修改以来打印“-This” str
MarredCheese

1
@MarredCheese:再读一遍。它不会修改指针。它修改了指针指向的数据(即字符串数据)
Mat

哦,好吧,我不知道那是你的意思。同意
MarredCheese

8

首次调用时,您提供了要标记为的字符串strtok。然后,要获得以下标记,只要给NULL该函数,只要它返回非NULL指针即可。

strtok函数记录您在调用时首先提供的字符串。(这对于多线程应用程序确实很危险)


8

strtok将标记字符串,即将其转换为一系列子字符串。

它通过搜索分隔这些标记(或子字符串)的定界符来实现。然后指定定界符。在您的情况下,您需要“”或“,”或“。” 或以“-”作为分隔符。

提取这些标记的编程模型是您手工绘制主字符串和定界符集。然后您反复调用它,每次strtok都会返回它找到的下一个标记。当它返回null时,直到到达主字符串的末尾。另一个规则是,您只能在第一次传递字符串,而在随后的几次传递NULL。这是一种告诉strtok的方式,是您是要使用新字符串启动新的令牌化会话,还是要从先前的令牌化会话中检索令牌。请注意,strtok会记住令牌化会话的状态。因此,它不是可重入的或线程安全的(您应该使用strtok_r代替)。要知道的另一件事是,它实际上修改了原始字符串。它为找到的定界符写“ \ 0”。

简洁地调用strtok的一种方法如下:

char str[] = "this, is the string - I want to parse";
char delim[] = " ,-";
char* token;

for (token = strtok(str, delim); token; token = strtok(NULL, delim))
{
    printf("token=%s\n", token);
}

结果:

this
is
the
string
I
want
to
parse

5

strtok修改其输入字符串。它在其中放置空字符('\ 0'),以便它将原始字符串的位作为令牌返回。实际上,strtok不会分配内存。如果将字符串绘制为一系列框,则可能会更好地理解它。


3

要了解其strtok()工作原理,首先需要知道什么是静态变量该链接很好地解释了...。

操作的关键strtok()是保留两次相继调用之间的最后一个分隔符的位置(这就是为什么strtok()继续解析null pointer在连续调用中被调用时传递给它的非常原始的字符串的原因)。

看一下我自己的strtok()实现,zStrtok()该实现与所提供的功能截然不同。strtok()

char *zStrtok(char *str, const char *delim) {
    static char *static_str=0;      /* var to store last address */
    int index=0, strlength=0;           /* integers for indexes */
    int found = 0;                  /* check if delim is found */

    /* delimiter cannot be NULL
    * if no more char left, return NULL as well
    */
    if (delim==0 || (str == 0 && static_str == 0))
        return 0;

    if (str == 0)
        str = static_str;

    /* get length of string */
    while(str[strlength])
        strlength++;

    /* find the first occurance of delim */
    for (index=0;index<strlength;index++)
        if (str[index]==delim[0]) {
            found=1;
            break;
        }

    /* if delim is not contained in str, return str */
    if (!found) {
        static_str = 0;
        return str;
    }

    /* check for consecutive delimiters
    *if first char is delim, return delim
    */
    if (str[0]==delim[0]) {
        static_str = (str + 1);
        return (char *)delim;
    }

    /* terminate the string
    * this assignmetn requires char[], so str has to
    * be char[] rather than *char
    */
    str[index] = '\0';

    /* save the rest of the string */
    if ((str + index + 1)!=0)
        static_str = (str + index + 1);
    else
        static_str = 0;

        return str;
}

这是一个示例用法

  Example Usage
      char str[] = "A,B,,,C";
      printf("1 %s\n",zStrtok(s,","));
      printf("2 %s\n",zStrtok(NULL,","));
      printf("3 %s\n",zStrtok(NULL,","));
      printf("4 %s\n",zStrtok(NULL,","));
      printf("5 %s\n",zStrtok(NULL,","));
      printf("6 %s\n",zStrtok(NULL,","));

  Example Output
      1 A
      2 B
      3 ,
      4 ,
      5 C
      6 (null)

该代码来自我在Github上维护的一个名为zString 的字符串处理库。看一下代码,甚至贡献一下:) https://github.com/fnoyanisi/zString


3

这就是我实现strtok的方式,虽然不是很好,但是经过2个小时的工作终于实现了。它确实支持多个定界符。

#include "stdafx.h"
#include <iostream>
using namespace std;

char* mystrtok(char str[],char filter[]) 
{
    if(filter == NULL) {
        return str;
    }
    static char *ptr = str;
    static int flag = 0;
    if(flag == 1) {
        return NULL;
    }
    char* ptrReturn = ptr;
    for(int j = 0; ptr != '\0'; j++) {
        for(int i=0 ; filter[i] != '\0' ; i++) {
            if(ptr[j] == '\0') {
                flag = 1;
                return ptrReturn;
            }
            if( ptr[j] == filter[i]) {
                ptr[j] = '\0';
                ptr+=j+1;
                return ptrReturn;
            }
        }
    }
    return NULL;
}

int _tmain(int argc, _TCHAR* argv[])
{
    char str[200] = "This,is my,string.test";
    char *ppt = mystrtok(str,", .");
    while(ppt != NULL ) {
        cout<< ppt << endl;
        ppt = mystrtok(NULL,", ."); 
    }
    return 0;
}


1

这是我的实现,它使用哈希表作为定界符,这意味着它是O(n)而不是O(n ^ 2)(这是代码的链接)

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

#define DICT_LEN 256

int *create_delim_dict(char *delim)
{
    int *d = (int*)malloc(sizeof(int)*DICT_LEN);
    memset((void*)d, 0, sizeof(int)*DICT_LEN);

    int i;
    for(i=0; i< strlen(delim); i++) {
        d[delim[i]] = 1;
    }
    return d;
}



char *my_strtok(char *str, char *delim)
{

    static char *last, *to_free;
    int *deli_dict = create_delim_dict(delim);

    if(!deli_dict) {
        /*this check if we allocate and fail the second time with entering this function */
        if(to_free) {
            free(to_free);
        }
        return NULL;
    }

    if(str) {
        last = (char*)malloc(strlen(str)+1);
        if(!last) {
            free(deli_dict);
            return NULL;
        }
        to_free = last;
        strcpy(last, str);
    }

    while(deli_dict[*last] && *last != '\0') {
        last++;
    }
    str = last;
    if(*last == '\0') {
        free(deli_dict);
        free(to_free);
        deli_dict = NULL;
        to_free = NULL;
        return NULL;
    }
    while (*last != '\0' && !deli_dict[*last]) {
        last++;
    }

    *last = '\0';
    last++;

    free(deli_dict);
    return str;
}

int main()
{
    char * str = "- This, a sample string.";
    char *del = " ,.-";
    char *s = my_strtok(str, del);
    while(s) {
        printf("%s\n", s);
        s = my_strtok(NULL, del);
    }
    return 0;
}

1

strtok()将指针存储在您上次中断的静态变量中,因此在第二次调用时,当我们传递null时,strtok()从静态变量获取指针。

如果您提供相同的字符串名称,则它将再次从头开始。

而且strtok()具有破坏性,即它会更改原始字符串。因此,请确保您始终拥有一份原始的副本。

使用strtok()的另一个问题是,由于它将地址存储在静态变量中,因此在多线程编程中多次调用strtok()会导致错误。为此,请使用strtok_r()。


0

对于仍然难以理解此strtok()功能的人,请看一下此pythontutor示例,它是可视化C(或C ++,Python ...)代码的好工具。

如果链接断开,请粘贴:

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

int main()
{
    char s[] = "Hello, my name is? Matthew! Hey.";
    char* p;
    for (char *p = strtok(s," ,?!."); p != NULL; p = strtok(NULL, " ,?!.")) {
      puts(p);
    }
    return 0;
}

积分归Anders K所有。


0

您可以扫描char数组以查找令牌,如果发现它只是打印新行,否则打印char。

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

int main()
{
    char *s;
    s = malloc(1024 * sizeof(char));
    scanf("%[^\n]", s);
    s = realloc(s, strlen(s) + 1);
    int len = strlen(s);
    char delim =' ';
    for(int i = 0; i < len; i++) {
        if(s[i] == delim) {
            printf("\n");
        }
        else {
            printf("%c", s[i]);
        }
    }
    free(s);
    return 0;
}

0

因此,这是一个代码片段,可帮助您更好地理解该主题。

打印令牌

任务:给定一个句子s,在新行中打印该句子的每个单词。

char *s;
s = malloc(1024 * sizeof(char));
scanf("%[^\n]", s);
s = realloc(s, strlen(s) + 1);
//logic to print the tokens of the sentence.
for (char *p = strtok(s," "); p != NULL; p = strtok(NULL, " "))
{
    printf("%s\n",p);
}

输入: How is that

结果:

How
is
that

说明:因此,这里使用“ strtok()”函数,并使用for循环对其进行迭代,以在单独的行中打印令牌。

该函数将参数作为“字符串”和“断点”,并在这些断点处断开字符串并形成标记。现在,这些令牌存储在“ p”中,并进一步用于打印。


我认为通过示例进行解释比参考某些文档要好得多。
tr_abhishek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.