簡體   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