2010-10-08 2 views
73

Пожалуйста, объясните мне, работает ли функция strtok(). В руководстве говорится, что она разбивает строку на токены. Я не могу понять из руководства, что на самом деле он делает.Как strtok() разбивает строку на токены в C?

Я добавил часы на str и *pch, чтобы проверить его работу, когда произошел первый цикл while, содержимое str было только «этим». Как результат, показанный ниже, был напечатан на экране?

/* 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 
+3

'strtok()' изменяет свою строку аргумента, завершая токены с помощью NUL перед возвратом. Если вы попытаетесь изучить весь буфер (str []), вы увидите, что он изменяется между последовательными вызовами на 'strtok()'. –

+0

Вместо просмотра 'str', watch' str [0] ',' str [1] ',' str [2] ', ... – pmg

+0

@pmg: Я смотрел str [0] и str [1] .str [1] должно быть «\ 0», но это было пространство. –

ответ

34

strtok() делит строку на лексемы. т. е. начиная с любого из разделителей до следующего, это будет ваш единственный токен. В вашем случае начальный токен будет от «-» и заканчивается следующим пробелом «». Затем следующий токен начнется с "" и заканчивается на ",". Здесь вы получаете «This» в качестве вывода. Точно так же остальная часть строки разбивается на токены из космоса в космос и, наконец, заканчивается последний токен на "."

+0

Конечное условие для одного токена становится стартовым токеном следующего токена? Также есть ли нулевой символ вместо конечного условия? –

+0

@ fahad- Да, все разделители, которые у вас есть, будут заменены символом NUL, как и другие люди. –

+0

Если все разделители заменены на Nul, то почему строка содержит «-this»? Он должен содержать «\ 0» –

6

В первый раз вы называете это, вы предоставляете строку разметить в strtok. И затем, чтобы получить следующие токены, вы просто даете NULL этой функции, если она возвращает указатель не NULL.

Функция strtok записывает строку, которую вы впервые предоставили, когда вы ее вызываете. (Что действительно опасно для многопоточных приложений)

4

strtok изменяет свою входную строку. Он помещает в него нулевые символы ('\ 0'), чтобы он возвращал биты исходной строки в качестве токенов. На самом деле strtok не выделяет память. Вы можете понять это лучше, если вы нарисуете строку как последовательность ящиков.

141

функции strtok выполнения работ, как этот

в первый раз, вы вызываете strtok вы предоставляете строку, которую вы хотите разметить

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

в строке выше пространстве кажется хорошим разделитель между словами так что позволяет использовать это:

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

, что происходит сейчас в том, что «s» ищется пока пробел не найден, первый маркер возвращается («это») дп указывает, что знак (строка)

для того, чтобы получить следующий токен и продолжить с той же строки NULL передается в качестве первого аргумента, так как strtok поддерживает статический указатель к предыдущей переданной строки:

p = strtok(NULL," "); 

р теперь указывает на «есть»

и так далее, пока не больше пространства не может быть найден, то последняя строка возвращается в качестве последнего маркера «строка».более

удобно вы могли бы написать это вместо того, чтобы печатать все маркеры:

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

EDIT:

Если вы хотите сохранить возвращаемые значения из strtok необходимо скопировать маркер в другой буфер, например strdup(p);, поскольку исходная строка (указана статическим указателем внутри strtok) изменена между итерациями, чтобы вернуть токен.

+0

Значит, на самом деле он не помещает нулевой символ между строкой? Почему мои часы показывают, что строка остается только с «ЭТО»? –

+3

он действительно заменяет '' он найден с '\ 0'. И он не восстанавливается позже, поэтому ваша строка разрушена навсегда. – Arkadiy

+0

Хорошее объяснение. – Nagaraju

5

strtok будет токенизировать строку, то есть преобразовать ее в ряд подстрок.

Это делается путем поиска разделителей, разделяющих эти жетоны (или подстроки). И вы указываете разделители. В вашем случае вы хотите «или», «или». или '-' - разделителем.

Модель программирования для извлечения этих жетонов заключается в том, что вы передаете strtok свою основную строку и набор разделителей. Затем вы вызываете его повторно, и каждый раз strtok возвращает следующий токен, который он находит. Пока он не достигнет конца основной строки, когда он вернет нуль. Другое правило состоит в том, что вы передаете строку только в первый раз, а NULL - для последующих времен. Это способ сказать strtok, если вы начинаете новый сеанс токенизации с помощью новой строки, или вы извлекаете токены из предыдущего сеанса токенизации. Обратите внимание, что strtok запоминает свое состояние для сеанса токенизации. И по этой причине он не реентерабелен или потокобезопасен (вместо этого вы должны использовать strtok_r). Еще одна вещь, которую нужно знать, это то, что она фактически изменяет исходную строку. Он пишет '\ 0' для разделителей, которые он находит.

Один из способов вызвать strtok, succintly, заключается в следующем:

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 
9

strtok не изменяет сам параметр (str). Он хранит этот указатель (в локальной статической переменной). Затем он может изменить то, что этот параметр указывает на в последующих вызовах без возврата параметра. (И это можно заранее, что указатель он сохранил, однако он должен выполнять свои операции.)

На странице POSIX strtok:

Эта функция использует статическую память для отслеживания текущей позиции строки между вызовами ,

Существует нитевидный вариант (strtok_r), который не делает этот тип магии.

+0

Какова была причина этого. – chris

+2

Ну, функции библиотеки C датируются с обратной стороны, когда потоки вообще не были в изображении (это только начало существовать в 2011 году по сравнению со стандартом C), поэтому повторное включение не было действительно важным (Я полагаю). Это статическое локальное делает функцию «простой в использовании» (для некоторого определения «легко»). Подобно 'ctime', возвращающему статическую строку - практично (никто не должен удивляться, кто должен ее освобождать), но не переадресовывает и не путешествует, если вы не очень хорошо это понимаете. – Mat

18

strtok поддерживает статическую внутреннюю ссылку, указывающую на следующий доступный токен в строке; если вы передадите ему указатель NULL, он будет работать из этой внутренней ссылки.

По этой причине strtok не является рецидивистом; как только вы передадите ему новый указатель, эта старая внутренняя ссылка будет сбита.

+0

Что вы имеете в виду под старым внутренним справочником «get clobbered». Вы имеете в виду «перезапись»? –

+0

@ ylun.ca: Да, вот что я имею в виду. –

2

Чтобы понять, как работает strtok(), сначала необходимо знать, что такое static variable. This link объясняет это довольно хорошо ....

Ключ к работе strtok() является сохранение местоположения последнего разделителя между seccessive вызовов (поэтому strtok() продолжает анализировать очень оригинальную строку, которая передается к нему, когда он вызывается с null pointer в последовательных вызовах). .

Посмотрите на мою собственную strtok() реализации, называется zStrtok(), который имеет sligtly различные функциональные возможности, чем тот, предоставляемой 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) 

Код от a string processing library I maintain on Github, называется zString. Посмотрите на код, или даже способствовать :) https://github.com/fnoyanisi/zString

1

Вот моя реализация, которая использует хэш-таблицу для разделителя, что означает его O (N) вместо O (N^2) (here is a link to the code):

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

Вот как я реализовал 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; 
} 
0

strtok() хранит указатель на статическую переменную, где вы в последний раз остановились, так и на 2-м вызове, когда мы передаем нуль, strtok() получает указатель из статической переменной.

Если вы указываете одно и то же имя строки, оно снова начинается с начала.

Кроме того strtok() является разрушительным, то есть он вносит изменения в строку orignal. поэтому убедитесь, что у вас всегда есть копия orignal.

Еще одна проблема использования strtok() заключается в том, что при сохранении адреса в статических переменных в многопоточном программировании вызов strtok() более одного раза приведет к ошибке. Для этого используйте strtok_r().

Смежные вопросы