Так стрток это деструктивно? - PullRequest
2 голосов
/ 22 марта 2020

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

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

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

Ответы [ 4 ]

3 голосов
/ 22 марта 2020

Стандарт C прямо заявляет" разрывает строку ... в ...":

Последовательность обращений к Функция strtok разбивает строку, указанную s1, на последовательность токенов

Явно разбивает строку, на которую указывает s1, больше, чем подразумевает, что исходная строка изменена.

Обратите внимание также на краткий обзор :

Синопсис

     #include <string.h>
     char *strtok(char * restrict s1,
          const char * restrict s2);

Это char * restrict s1, что явно не имеет никакого const.

Обратите внимание, что это "последовательность вызовов", которая "разрывает" строку. Восстановление строки после разбора каждого токена не будет соответствовать требованию «разбить строку» или что «строка будет разбита» после «последовательности вызовов».

POSIX вносит изменения явный (выделение жирным шрифтом):

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

3 голосов
/ 22 марта 2020

strtok разрушительно и описано в стандарте

Если такой символ найден, он перезаписывается нулевым символом, который завершает текущий токен.

enter image description here

7.21.5.8

1 голос
/ 23 марта 2020

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

Предлагаемая вами стратегия это интересно и не сложно реализовать, но оно несовместимо с текущей семантикой, определенной стандартом C. Описание очень точное относительно того, где нулевые байты установлены в исходной строке. Также обратите внимание, что ваше предложение по-прежнему имеет два недостатка:

  • оно использует скрытое состояние stati c.
  • оно изменяет строку аргумента, поэтому не подходит для строковых констант.
  • каждый последующий вызов изменяет токены 1015 *, возвращаемые предыдущими вызовами, поскольку нулевые терминаторы постепенно заменяются исходными байтами. Такое поведение будет даже более нелогичным, чем текущие побочные эффекты.

Для выполнения тех же самых задач синтаксического анализа, что и strtok() без изменений в исходной строке, я предлагаю использовать эти часто пропускаемые, но стандартные строковые функции из <string.h>:

// return the number of characters at the start of s1 not matching any characters from s2
size_t strcspn(const char *s1, const char *s2);

// return the number of characters at the start of s1 matching one of the characters in s2
size_t strspn(const char *s1, const char *s2);

Например, вот простая функция, которая разбивает свой строковый аргумент на слова, разделенные пробелом или пунктуацией:

void print_words(const char *str) {
    const char *separators = " \t\r\n,.:;'-";
    int len;
    for (;;) {
        /* skip separators */
        str += strspn(str, separators);
        if (*str == '\0')
            break;
        /* get the word length */
        len = strcspn(str, separators);
        /* output the word */
        printf("%.*s\n", len, str);
        /* skip the word */
        str += len;
    }
}
1 голос
/ 22 марта 2020

Так что strtok является деструктивным?

Да.

Поэтому я должен сначала скопировать исходную строку в другой буфер перед обработкой с помощью strtok, если я хочу исходная строка останется неизменной?

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

Альтернатива

Другой способ заключается в поиске начала и длины токена. , Тогда нет необходимости изменять исходную строку.

Возвращенный указатель - не строка токена, а только его начало.

#include <stdlib.h>
#include <string.h>

// Return beginning of token.
const char *my_strtok(const char *s, const char *delim, size_t *token_length) {
  // Skip over leading delimiters
  const char *token = s + strspn(s, delim);
  *token_length = strcspn(token, delim);
  if (*token_length == 0) {
    // No token was found
    return NULL;
  }
  return token;
}

#include <stdio.h>

int main() {
  const char *s = "Jenny:867-5309";  // https://en.wikipedia.org/wiki/867-5309/Jenny
  size_t length;
  while ((s = my_strtok(s, "-:", &length)) != NULL) {
    printf("<%.*s>\n", (int) length, s);  // print only to the length of the token.
    s += length;
  }
}

Вывод

<Jenny>
<867>
<5309>
...