繁体   English   中英

strtok() 如何将字符串拆分为 C 中的标记?

[英]How does strtok() split the string into tokens in C?

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

我在str*pch上添加了*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;
}

输出:

Splitting string "- This, a sample string." into tokens:
This
a
sample
string

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的静态指针指向)在迭代之间被修改以返回令牌。

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

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

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

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

从 POSIX strtok页面:

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

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

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

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

提取这些标记的编程模型是您手动 strtok 主字符串和分隔符集。 然后你反复调用它,每次 strtok 都会返回它找到的下一个标记。 直到它到达主字符串的末尾,当它返回空值时。 另一个规则是您只在第一次传入字符串,而在随后的时间则传入 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

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

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

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

要了解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

这就是我实现 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;
}

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

这是我的实现,它使用哈希表作为分隔符,这意味着它是 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;
}

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

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

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

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

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

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

下面是strtok如何在C中工作的链接,下面是Dipak在C ++中解释的相同程序

https://stackoverflow.com/a/58674383/7802396

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

#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;
}

所以,这是一个有助于更好地理解这个主题的代码片段。

打印令牌

任务:给定一个句子 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”中并进一步用于打印。

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' 替换,因此它打印的是空格而不是 ''

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM