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

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

Я добавил часы на str и *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

Ответ 1

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

Ответ 2

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

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

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

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

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

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

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

p = strtok(NULL," ");

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

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

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

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

EDIT:

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

Ответ 3

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

Чтобы понять, как работает 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

Ответ 10

Вот моя реализация, которая использует хеш-таблицу для разделителя, что означает, что 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;
}

Ответ 11

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

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

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

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

Ответ 12

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

Ответ 13

Для тех, кто все еще не понимает эту функцию strtok(), взгляните на этот #include 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; }&mode=edit&origin=opt-frontend.js&py=c&rawInputLstJSON=[] rel="nofollow noreferrer">пример 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;
}

Кредиты отправляются Андерсу К.