127

请向我解释strtok()功能的工作。手册说它将字符串分解为标记。我无法从手册中了解它的实际作用。

当第一个while循环发生时,我添加了手表str*pch检查它的工作情况,内容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;
}

输出:

拆分字符串“- 这是一个示例字符串。” 进入令牌:
这个
一个
样本
细绳
4

16 回答 16

243

strtok 运行时函数是这样工作的

第一次调用 strtok 时,您提供了一个要标记的字符串

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

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

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

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

为了获得下一个令牌并继续使用相同的字符串,NULL 作为第一个参数传递,因为 strtok 维护一个指向您之前传递的字符串的静态指针:

p = strtok(NULL," ");

p 现在指向“是”

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

更方便的是,您可以这样编写它来打印出所有标记:

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

编辑:

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

于 2010-10-08T11:51:35.617 回答
44

strtok()将字符串划分为标记。即从任何一个分隔符到下一个分隔符将是您的一个标记。在您的情况下,起始标记将来自“-”并以下一个空格“”结束。然后下一个标记将从“”开始并以“,”结尾。在这里你得到“这个”作为输出。类似地,字符串的其余部分被分割成从一个空间到另一个空间的标记,最后以“。”结束最后一个标记。

于 2010-10-08T11:33:49.553 回答
32

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

这就是strtok不可重入的原因;一旦你向它传递一个新指针,旧的内部引用就会被破坏。

于 2012-05-17T18:22:26.370 回答
10

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

从 POSIXstrtok页面:

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

有一个线程安全的变体 ( strtok_r) 不会做这种魔法。

于 2012-05-17T18:22:01.067 回答
9

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

它通过搜索分隔这些标记(或子字符串)的分隔符来做到这一点。并且您指定分隔符。在你的情况下,你想要''或','或'。或“-”作为分隔符。

提取这些标记的编程模型是您手动 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
于 2010-10-08T13:35:04.660 回答
8

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

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

于 2010-10-08T11:32:03.193 回答
5

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

于 2010-10-08T11:32:29.800 回答
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;
}
于 2017-05-08T18:55:09.427 回答
3

对于那些仍然难以理解这个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.

于 2018-05-08T12:24:47.157 回答
3

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

操作的关键strtok()是在连续调用之间保留最后一个分隔符的位置(这就是为什么在连续strtok()调用中使用 a 调用它时继续解析传递给它的原始字符串的原因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

于 2016-02-18T01:01:22.713 回答
2

这是我的实现,它使用哈希表作为分隔符,这意味着它是 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;
}
于 2017-03-05T13:16:44.383 回答
1

strtok 用 NULL 替换第二个参数中的字符,并且 NULL 字符也是字符串的结尾。

http://www.cplusplus.com/reference/clibrary/cstring/strtok/

于 2010-10-08T11:32:49.430 回答
1

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

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

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

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

于 2018-01-27T12:05:54.627 回答
0

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

#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;
}
于 2019-12-06T21:07:51.330 回答
0

strtok'\0'正在用给定字符串中的 NULL 字符替换分隔符

代码

#include<iostream>
#include<cstring>

int main()
{
    char s[]="30/4/2021";     
    std::cout<<(void*)s<<"\n";    // 0x70fdf0
    
    char *p1=(char*)0x70fdf0;
    std::cout<<p1<<"\n";
    
    char *p2=strtok(s,"/");
    std::cout<<(void*)p2<<"\n";
    std::cout<<p2<<"\n";
    
    char *p3=(char*)0x70fdf0;
    std::cout<<p3<<"\n";
    
    for(int i=0;i<=9;i++)
    {
        std::cout<<*p1;
        p1++;
    }
    
}

输出

0x70fdf0       // 1. address of string s
30/4/2021      // 2. print string s through ptr p1 
0x70fdf0       // 3. this address is return by strtok to ptr p2
30             // 4. print string which pointed by p2
30             // 5. again assign address of string s to ptr p3 try to print string
30 4/2021      // 6. print characters of string s one by one using loop

在标记字符串之前

我将字符串 s 的地址分配给一些 ptr(p1) 并尝试通过该 ptr 打印字符串并打印整个字符串。

代币化后

strtok 将字符串 s 的地址返回给 ptr(p2) 但是当我尝试通过 ptr 打印字符串时它只打印“30”它没有打印整个字符串。所以可以肯定strtok is not just returning adress but it is placing '\0' character where delimiter is present

交叉检查

1.

我再次将字符串 s 的地址分配给某个 ptr (p3) 并尝试打印字符串,它打印“30”,因为在分隔符处使用 '\0' 更新字符串的标记。

2.

看到通过循环逐个字符打印字符串第一个分隔符被替换为'\ 0'所以它打印的是空格而不是''

于 2021-05-01T03:41:00.500 回答
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”中并进一步用于打印。

于 2020-02-29T08:07:39.613 回答