那么strtok是破坏性的吗?

So strtok is destructive?

我是否正确理解 strtok 使源字符串充满了空字符?

我可以理解每个新的迭代调用首先用原始字符替换它放置在那里的空字符然后继续,最后一次调用 returns null 因为没有更多的游行,然后用它的原始字符替换它最后放在那里的最后一个空字符。结果,源字符串最终将保持不变。 (当然,如果您在最后一次调用之前停止,源字符串将保持修改状态。)

但是没有文档提到这样的策略。因此,如果我希望源字符串保持不变,我必须先将源字符串复制到另一个缓冲区,然后再使用 strtok 进行处理?

strtok是破坏性的,在标准

中有描述

If such a character is found, it is overwritten by a null character,which terminates the current token.

7.21.5.8

C standard explicitly states将字符串...打断成...”:

A sequence of calls to the strtok function breaks the string pointed to by s1 into a sequence of tokens

明确打破“s1 指向的字符串”不仅仅意味着修改了原始字符串。

Note also the synopsis:

剧情简介

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

它是 char * restrict s1,明显缺少任何 const

请注意,“中断”字符串的是“调用序列”。在解析每个标记后恢复字符串不符合“破坏字符串”的要求,或者在“调用序列”之后字符串被“破坏”的要求。

POSIX makes the modification explicit(加粗我的):

The strtok() function then searches from there for a byte that is contained in the current separator string. If no such byte is found, the current token extends to the end of the string pointed to by s, and subsequent searches for a token shall return a null pointer. If such a byte is found, it is overwritten by a NUL character, which terminates the current token. The strtok() function saves a pointer to the following byte, from which the next search for a token shall start.

So strtok is destructive?

是的。

So I must first copy the source string to another buffer before processing with strtok if I want the source string to remain unmodified?

复制是一种方式。

备选

另一种是求token的起始和长度。那就不用修改源字符串了。

返回的指针不是令牌的字符串,只是它的开头。

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

你是对的,strtok() 可以修改它的第一个字符串参数,我喜欢你的描述:strtok 使源字符串充满空字符 .

您提出的策略很有趣且不难实现,但它与 C 标准定义的当前语义不兼容。关于在源字符串中设置空字节的位置,描述非常精确。另请注意,您的提案仍有两个缺点:

  • 它使用隐藏静态。
  • 它修改参数字符串,因此不适用于字符串常量。
  • 每个后续调用都会修改先前调用返回的 tokens,因为空终止符逐渐被原始字节替换。这种行为比当前的副作用更违反直觉。

要在不修改源字符串的情况下执行与 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 == '[=11=]')
            break;
        /* get the word length */
        len = strcspn(str, separators);
        /* output the word */
        printf("%.*s\n", len, str);
        /* skip the word */
        str += len;
    }
}