Как strtok () разбивает строку на токены в C?


Пожалуйста, объясните мне работы

13 82

13 ответов:

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

функция выполнения strtok работает следующим образом

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

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

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

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

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

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

p = strtok(NULL," ");

p теперь указывает на 'is'

и так далее, пока не будет найдено больше пробелов, тогда последняя строка возвращается как последняя строка токена.

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

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

EDIT:

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

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

это причина strtok не является re-entrant; как только вы передаете ему новый указатель, эта старая внутренняя ссылка получает clobbered.

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

из POSIX strtok страницы:

эта функция использует статическое хранение для того чтобы держать след течения позиция строки между вызовами.

есть потокобезопасный вариант (strtok_r), что не делает этот тип магии.

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

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

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

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] = '';

    /* 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 заменяет символы во втором аргументе на NULL и нулевой символ также является концом строки.

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

вот моя реализация, которая использует хэш-таблицу для разделителя, что означает, что это 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 != '') {
        last++;
    }
    str = last;
    if(*last == '') {
        free(deli_dict);
        free(to_free);
        deli_dict = NULL;
        to_free = NULL;
        return NULL;
    }
    while (*last != '' && !deli_dict[*last]) {
        last++;
    }

    *last = '';
    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() сохраняет указатель в статической переменной , где вы в последний раз остановились , поэтому при его 2-м вызове , когда мы передаем null, strtok () получает указатель из статической переменной .

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

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

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

вот как я реализовал 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 != ''; j++) {
        for(int i=0 ; filter[i] != '' ; i++) {
            if(ptr[j] == '') {
                flag = 1;
                return ptrReturn;
            }
            if( ptr[j] == filter[i]) {
                ptr[j] = '';
                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;
}

кредиты идут в Андерс К.