strtok()问题:如果标记由分隔符分隔,为什么分隔符和空'\ 0'之间的最后一个标记?

时间:2013-05-15 17:07:16

标签: c token delimiter strtok

在以下程序中,strtok()在主要部分中按预期工作,但我无法理解一个发现背后的原因。我已经阅读了strtok()

  
    

要确定令牌的开头和结尾,该函数首先从起始位置扫描未包含在分隔符中的第一个字符(它成为令牌的开头)。然后从令牌的开头开始扫描分隔符中包含的第一个字符,这将成为令牌的结尾。

  
     

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

正如我们所知,strtok()在每个令牌的末尾放置了\0。但是在下面的程序中,最后一个分隔符是一个点(.),之后在该点和引号(")之间有 Toad 。现在点是我程序中的分隔符,但是在 Toad 之后没有分隔符,甚至没有空格(在我的程序中是分隔符)。请清除以下因此前提出的混淆:

为什么strtok() Toad 视为令牌,即使它不在2个分隔符之间?这是我在遇到NULL字符(strtok())时读到的\0

  
    

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

输出:

  

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

5 个答案:

答案 0 :(得分:9)

标准的strtok(7.24.5.8)规范非常清楚。特别是第4段(我强调)与问题直接相关,如果我理解正确的话:

  

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

     

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

致电

char *where = strtok(string_or_NULL, delimiters);

返回的令牌(指向的指针) - 如果有的话 - 从起始位置(包括)找到的第一个非定界符扩展到下一个定界符(不包括),如果存在,或者结束如果没有以后的分隔符,则为字符串。

链接描述没有明确提到延伸到字符串结尾的标记的情况,而不是标准,因此在这方面它是不完整的。

答案 1 :(得分:4)

转到POSIX中strtok()的说明,说明如下:

  

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

     

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

     

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

     

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

注意第三段的第二句:

  

如果找不到这样的字节,则当前标记扩展到s1指向的字符串的末尾,随后对标记的搜索将返回空指针。

这清楚地表明,在问题的例子中,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()是否适合您的目的。

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

您是否只是误读了描述?

  

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

鉴于'后续',我在读取strtok 后发现\0的{​​{1}} ,而不一定是当前的那个,我正在读这个。因此,定义与行为(以及您对strtok的期望)一致。