Как работает функция strtok в C?

Я нашел эту примерную программу, которая объясняет функцию strtok:

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

Однако я не вижу, как это можно работать.

Как возможно, что pch = strtok (NULL, " ,.-"); возвращает новый токен. Я имею в виду, мы вызываем strtok с NULL. Это не имеет большого значения для меня.

Ответы

Ответ 1

Две вещи, которые нужно знать о strtok. Как уже упоминалось, он "поддерживает внутреннее состояние". Кроме того, он испортил строку, которую вы ее кормите. По существу, он напишет '\0', где он найдет маркер, который вы предоставили, и вернет указатель на начало строки. Внутренне он поддерживает расположение последнего токена; и в следующий раз, когда вы его назовете, он начнется оттуда.

Важным следствием является то, что вы не можете использовать strtok для строки типа const char* "hello world";, так как вы получите нарушение доступа при изменении содержимого строки const char*.

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

Пример. Если у вас есть "это, есть, строка", последовательные вызовы strtok будут генерировать указатели следующим образом (значение ^ - это возвращаемое значение). Обратите внимание, что добавляется '\0', где найдены токены; это означает, что исходная строка изменена:

t  h  i  s  ,  i  s  ,  a  ,  s  t  r  i  n  g \0         this,is,a,string

t  h  i  s  \0 i  s  ,  a  ,  s  t  r  i  n  g \0         this
^
t  h  i  s  \0 i  s  \0 a  ,  s  t  r  i  n  g \0         is
               ^
t  h  i  s  \0 i  s  \0 a  \0 s  t  r  i  n  g \0         a
                        ^
t  h  i  s  \0 i  s  \0 a  \0 s  t  r  i  n  g \0         string
                              ^

Надеюсь, что это имеет смысл.

Ответ 2

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

Из http://www.cplusplus.com/reference/cstring/strtok/:

Точка, в которой был найден последний токен, хранится внутри с помощью функции, которая будет использоваться при следующем вызове (для предотвращения сбоев данных не требуется реализация конкретной библиотеки).

Ответ 3

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

В связи с тем, как работает strtok, вам необходимо убедиться, что вы связываетесь с многопоточной версией среды выполнения C, если вы пишете многопоточное приложение. Это гарантирует, что каждый поток получит собственное внутреннее состояние для strtok.

Ответ 4

Функция strtok хранит данные во внутренней статической переменной, которая распределяется между всеми потоками.

Для обеспечения безопасности потоков вы должны использовать strtok_r

От http://www.opensource.apple.com/source/Libc/Libc-167/string.subproj/strtok.c

Взгляните на static char *last;

char *
strtok(s, delim)
    register char *s;
    register const char *delim;
{
    register char *spanp;
    register int c, sc;
    char *tok;
    static char *last;


    if (s == NULL && (s = last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}