【问题标题】:strtok() issue: If tokens are delimited by delimiters,why is last token between a delimiter and the null '\0'?strtok() 问题:如果标记由分隔符分隔,为什么最后一个标记在分隔符和空 '\0' 之间?
【发布时间】:2013-05-10 09:06:54
【问题描述】:

在下面的程序中,strtok() 在主要部分按预期工作,但我无法理解一个发现背后的原因。我读过关于strtok() 的内容:

为了确定记号的开始和结束,该函数首先从起始位置扫描不包含在分隔符中的第一个字符(它成为记号的开始)。然后从标记的这个开头开始扫描分隔符中包含的第一个字符,这成为标记的结尾。

来源:http://www.cplusplus.com/reference/cstring/strtok/

众所周知,strtok() 在每个标记的末尾放置一个\0。但是在下面的程序中,最后一个定界符是一个点(.),在该点和引号(")之间有Toad。现在点是我程序中的分隔符,但 Toad 之后没有分隔符,甚至没有空格(这是我程序中的分隔符)。请清除由此前提引起的以下混淆:

为什么strtok()Toad 视为标记,即使它不在两个分隔符之间?这是我在遇到 NULL 字符 (\0) 时读到的关于 strtok() 的内容:

一旦在对 strtok 的调用中找到 str 的终止空字符,所有后续以空指针作为第一个参数的函数调用都返回空指针。

来源:http://www.cplusplus.com/reference/cstring/strtok/

没有地方说一旦遇到空字符,就会返回指向标记开头的指针(我们甚至没有标记,因为我们没有得到标记的结尾,因为没有扫描后发现的分隔符从令牌的开头开始(即从 Toad 的“T”开始),我们只找到了一个空字符,不是分隔符)。那么为什么参数字符串的最后一个分隔符和引号之间的部分strtok()视为标记?请解释一下。

代码:

#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] =" Falcon,eagle-hawk..;buzzard,gull..pigeon sparrow,hen;owl.Toad";
  char * pch=strtok(str," ;,.-");

    while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ;,.-");
  }

  return 0;
}

输出:

猎鹰
老鹰

秃鹰
海鸥
鸽子
麻雀
母鸡
猫头鹰
蟾蜍

【问题讨论】:

  • 不确定我是否理解您的问题;你期望什么输出? Toad 不会被打印出来?如果您删除输入字符串中的前导空格,则按照该逻辑,Falcon 也不应该被打印。我会说这会导致一些不直观的行为。
  • 如果您删除了 Falcon 之前的空白,strtok() 仍会将“Falcon”视为第一个标记。
  • @JonathanLeffler 我是故意这样做的。就像我说的,一切都和strtok() 的预期一样,除了最后一个标记,它显然不在两个分隔符之间。
  • @JonathanLeffler 我很遗憾在发布这个问题后我不得不马上出去。
  • @Praetorian 为什么我不应该期望打印Falcon?我已经从源中提到the function first scans from the starting location for the first character not contained in delimiters..ie,对于令牌的开头,我们不需要定界符(空格是我程序中的定界符),但是要标记令牌的结尾,我们显然需要一个定界符,并且字符串末尾的NULL不在定界符列表中。

标签: c token delimiter strtok


【解决方案1】:

转到strtok() 的 POSIX 中的描述,描述说:

char *strtok(char *restrict s1, const char *restrict s2);

strtok() 的一系列调用将s1 指向的字符串分解为一系列标记,每个标记由s2 指向的字符串中的一个字节分隔。序列中的第一个调用将s1 作为其第一个参数,然后是使用空指针作为其第一个参数的调用。 s2 指向的分隔符字符串可能因调用而异。

序列中的第一个调用在s1 指向的字符串中搜索不包含在s2 指向的当前分隔符字符串中的第一个字节。如果没有找到这样的字节,则s1 指向的字符串中没有标记,strtok() 将返回一个空指针。如果找到这样的字节,它就是第一个令牌的开始。

strtok() 函数然后从那里搜索包含在当前分隔符字符串中的字节。如果没有找到这样的字节,则当前标记将扩展到s1 指向的字符串的末尾,随后对标记的搜索将返回一个空指针。如果找到这样的字节,它会被 NUL 字符覆盖,这会终止当前令牌。 strtok() 函数保存一个指向下一个字节的指针,下一个令牌搜索将从该字节开始。

注意第三段第二句:

如果没有找到这样的字节,当前的token会延伸到s1指向的字符串的末尾,后续搜索token应该返回一个空指针。

这清楚地表明,在问题的示例中,Toad 确实是一个令牌。一种思考方式是分隔符列表始终在分隔符字符串的末尾包含 NUL '\0'


诊断后,请注意strtok() 不是一个好用的函数——它不是线程安全的或可重入的。在 Windows 上,您可以改用 strtok_s();在 Unix 上,您通常可以使用 strtok_r()。这些是更好的函数,因为它们不会在内部存储要恢复搜索的指针。

因为strtok() 不是可重入的,所以在使用strtok() 时,不能从本身使用strtok() 的函数内部调用使用strtok() 的函数。此外,任何使用strtok() 的库函数都必须明确标识为这样做,因为它不能从使用strtok() 的函数中调用。所以,使用strtok() 会让生活变得艰难。

strtok() 系列函数(以及相关的strsep())的另一个问题是它们覆盖了分隔符;在标记器对字符串进行标记后,您无法找出分隔符是什么。这在某些应用程序中可能很重要(例如解析 shell 命令行;分隔符是管道、分号还是 & 符号(或...)很重要。所以 shell 解析器通常不使用 strtok(),尽管数量关于解析器确实使用 strtok() 的 shell 的问题。

一般来说,您应该避开普通的strtok(),由您决定strtok_r()strtok_s() 是否适合您的目的。

【讨论】:

  • Daniel Fischer 以几秒的优势击败你!!
  • 是的——我不应该去吃午饭的......我看到他的答案在我写我的时候就到了,但只是在我点击提交之后。
【解决方案2】:

strtok (7.24.5.8) 的标准规范非常明确。特别是第 4 段(我添加的重点)与问题直接相关,如果我理解正确的话:

3 序列中的第一个调用在s1 指向的字符串中搜索不包含在s2 指向的当前分隔符字符串中的第一个字符。如果没有找到这样的字符,则s1 指向的字符串中没有标记,strtok 函数返回一个空指针。如果找到这样的字符,它就是第一个标记的开始。

4 strtok 函数然后从那里搜索包含在当前分隔符字符串中的字符。 如果没有找到这样的字符,则当前标记会延伸到s1指向的字符串的末尾,随后搜索标记将返回一个空指针。如果找到这样的字符,它会被一个空字符覆盖,这会终止当前标记。 strtok 函数保存一个指向下一个字符的指针,下一次搜索令牌将从该指针开始。

通话中

char *where = strtok(string_or_NULL, delimiters);

返回的标记(指向它的指针) - 如果有的话 - 从从起始位置(包括)找到的第一个非分隔符延伸到下一个分隔符(不包括),如果存在,或者结束字符串,如果不存在后面的分隔符。

链接的描述没有明确提到令牌延伸到字符串末尾的情况,与标准相反,因此在这方面它是不完整的。

【讨论】:

  • If no such character is found, the current token extends to the end of the string pointed to by s1, and subsequent searches for a token will return a null pointer---谢谢你,钉它,直接取自标准。这正是我想知道的。
  • 牛眼命中目标!!
【解决方案3】:

因为 cplusplus.com 并没有告诉你整个故事。 Cppreference.com 有更好的描述。

Cplusplus.com 也没有提到 strtok 不是线程安全的,只记录了 C++ 编程语言的 strtok 函数,而 cppreference.com 确实提到了线程安全问题并记录了 strtok CC++ 编程语言的功能。

【讨论】:

    【解决方案4】:

    strtok 将字符串分解为一系列标记,由给定的分隔符分隔。 分隔符仅分隔标记,不必在两侧终止它们。

    【讨论】:

      【解决方案5】:

      您可能只是误读了描述吗?

      一旦在调用中找到 str 的终止空字符 strtok,所有后续使用空指针调用此函数 作为第一个参数返回一个空指针。

      鉴于“后续”,我将在每次调用strtok 时阅读此内容。发现\0 的那个之后,不一定是当前那个本身。因此,该定义与行为一致(以及您对 strtok 的期望)。

      【讨论】:

      • 从来源的描述中,很明显它说没有分隔符就不可能结束标记。后续调用或当前调用在这种情况下不重要。这是什么它表示令牌的结尾--And then scans starting from this beginning of the token for the first character contained in delimiters, which becomes the end of the token.
      • @Rüppell'sVulture 我同意在初始字符串为“.Toad”的情况下,该描述不能很好地描述。然而,在这一点上似乎很清楚,这里的问题只是源方面的糟糕文档,strtok 本身没有错。
      • 我不会说strtok 是错的,即使是口误!!无论如何,你已经接近我打算问的内容了......看,在倒数第二个标记的末尾,指针指向ToadT,但是要标记token的结尾,它需要一个分隔符。但是后面没有分隔符并且遇到空字符,此时它停止了。那么如何Toad 是令牌吗?
      • @Rüppell'sVulture :) 我不确定你来自哪里,但在美国我们说“叔叔!”在这一点上——是的,你是对的! cplusplus.com 的文档不足。但是,尽管很受欢迎,但我不知道它在何种意义上是规范的或以任何官方方式代表 C 语言。所以也许给他们发一封电子邮件......
      • 在 C 语言中搜索任何库函数,cplusplus.com 在 google 上是第一位的。这让我觉得它像圣经一样神圣。但现在我有第二个想法。我被警告了很多次在过去的几天里,关于那个景象。现在在这个世界上该相信谁?
      猜你喜欢
      • 1970-01-01
      • 2013-10-02
      • 2011-02-27
      • 1970-01-01
      • 2012-12-21
      • 1970-01-01
      • 1970-01-01
      • 2016-10-05
      • 1970-01-01
      相关资源
      最近更新 更多