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

114

Пожалуйста, объясните мне, как работает 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;
}

Вывод:

Разделение строки "- Это образец строки". в токены:
это
а
образец
строка
Fuddin
источник
5
strtok()изменяет свою строку аргумента, завершая токены NUL перед возвратом. Если вы попытаетесь исследовать весь буфер (str []), вы увидите, что он изменяется между последовательными вызовами strtok().
Майкл Фукаракис
Вместо того , чтобы смотреть str, смотреть str[0], str[1], str[2], ...
PMG
@pmg: я смотрел str [0] и str [1]. str [1] должен быть '\ 0', но там был пробел.
fuddin
3
Честно говоря, я никогда не утруждал себя проверкой, но я полагаю, что он хранит последний переданный указатель вместе с позицией, на которой он остановился. Затем он может просто продолжить, если указатель равен NULL, или очистить позицию и начать заново, если нет.
Крис

Ответы:

38

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

Сачин Шанбхаг
источник
конечное условие для одного токена становится начальным токеном следующего токена? Также есть ли нулевой символ вместо конечного условия?
fuddin
1
@ fahad - Да, все имеющиеся у вас разделители будут заменены символом NUL, как предлагали другие люди.
Сачин Шанбхаг 08
Если все разделители заменены на Nul, тогда почему в строке содержится «-this»? Он должен содержать "\ 0"
fuddin
2
@fahad - заменяет только символы-разделители на NUL, а не все символы между разделителями. Это своего рода разбиение строки на несколько токенов. Вы получаете «Это», потому что оно находится между двумя указанными разделителями, а не «-это».
Сачин Шанбхаг
1
@Fahad - Да, конечно. Все пробелы, "," и "-" заменяются на NUL, потому что вы указали их как разделители, насколько я понимаю.
Сачин Шанбхаг 08
214

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

АндерсК
источник
Значит, на самом деле между строкой не помещается нулевой символ? Почему мои часы показывают, что в строке осталось только "ЭТО"?
fuddin 08
4
он действительно заменяет найденный "" на "\ 0". И он не восстанавливается позже, так что ваша струна испорчена навсегда.
33
+1 для статического буфера, вот чего я не понял
IEatBagels
1
Очень важная деталь, отсутствующая в строке «первый токен возвращается и pуказывает на этот токен» , заключается в том, что strtokнеобходимо изменить исходную строку, поместив нулевые символы вместо разделителя (иначе другие строковые функции не будут знать, где токен заканчивается). И он также отслеживает состояние с помощью статической переменной.
Groo
@Groo Я думаю, что уже добавил это в Edit, который я сделал в 2017 году, но вы правы.
AndersK
25

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

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

Джон Боде
источник
Что вы имеете в виду под старой внутренней ссылкой "затирать"? Вы имеете в виду «перезаписанный»?
ylun.ca 02
1
@ ylun.ca: да, я это имел в виду.
Джон Боде
10

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

Со strtokстраницы POSIX :

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

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

Мат
источник
2
Что ж, библиотечные функции C появились в далеком прошлом, когда потоковой передачи вообще не было (которая начала существовать только в 2011 году, что касается стандарта C), поэтому повторный вход не был действительно важен ( Я думаю). Этот статический локальный делает функцию «простой в использовании» (для некоторого определения «легкой»). Как и ctimeвозврат статической строки - практично (никому не нужно задаваться вопросом, кто должен ее освободить), но не повторным входом и сбивает вас с толку, если вы этого не очень понимаете.
Mat
Это неправильно: « strtokсам параметр не меняет ( str)». puts(str);печатает "- Это", так как strtokизменено str.
MarredCheese
1
@MarredCheese: прочтите еще раз. Он не изменяет указатель. Он изменяет данные, на которые указывает указатель (т. Е. Строковые данные)
Mat
О, ладно, я не понимал, что ты к чему. Согласовано.
MarredCheese
8

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

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

Tibur
источник
8

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
Ziffusion
источник
5

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

xpmatteo
источник
3

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

фниси
источник
3

Вот как я реализовал 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;
}
Дипак
источник
1

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

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

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

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

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

Vaibhav
источник
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;
}

Кредиты принадлежат Андерсу К.


источник
0

вы можете сканировать массив символов в поисках токена, если вы его нашли, просто напечатайте новую строку, иначе распечатайте символ.

#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;
}
Фахад Алотайби
источник
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' и используются в дальнейшем для печати.

tr_abhishek
источник
Я думаю, что объяснять на примере намного лучше, чем ссылаться на какой-то документ.
tr_abhishek