使用strtok()函数时,如何判断两个分隔符之间是否没有数据?

9

我正在尝试对字符串进行分词,但我需要知道在两个标记之间没有数据的确切时间。例如,在对以下字符串"a,b,c,,,d,e"进行分词时,我需要知道在 'd' 和 'e' 之间的两个空槽...但我无法仅使用strtok()找到这些信息。我的尝试如下:

char arr_fields[num_of_fields];
char delim[]=",\n";
char *tok;
tok=strtok(line,delim);//line contains the data

for(i=0;i<num_of_fields;i++,tok=strtok(NULL,delim))
{
    if(tok)
        sprintf(arr_fields[i], "%s", tok);
    else
        sprintf(arr_fields[i], "%s", "-");          
}

使用上述代码执行前面提到的示例,将字符a、b、c、d、e放入数组arr_fields的前五个元素中是不可取的。我需要每个字符的位置进入数组的特定索引中:即如果在两个字符之间缺少一个字符,则应按原样记录。


5
“@DhaivatPandya:如果没有附带原因,那不是很有用的建议。” - Oliver Charlesworth
你的意思是“在‘c’和‘d’之间”吗? - Eternal_Light
这是非常准确的建议。问题在于strtok()被设计成忽略标记分隔符的重复,并将其删除。因此,如果您需要了解相邻的标记分隔符,或者需要知道哪个分隔符标记了一个标记的结尾,您不能使用strtok()来完成这项工作。 - Jonathan Leffler
6个回答

18

7.21.5.8 strtok函数

标准对于strtok的用法有如下规定:

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

通过上述引用可以看出,如果你想让delims中的任意连续字符被视为一个单独的令牌,那么你不能使用strtok来解决你的问题。


我是否注定只能默默流泪,还是有人能帮我一把?

你可以轻松地实现自己想要的strtok的版本,见本帖末尾的代码片段。

strtok_single利用了strpbrk (char const* src, const char* delims)函数,该函数将返回一个指向在src中找到的delims中的任何字符的第一个出现位置的指针。

如果没有找到匹配的字符,该函数将返回NULL。


strtok_single

char *
strtok_single (char * str, char const * delims)
{
  static char  * src = NULL;
  char  *  p,  * ret = 0;

  if (str != NULL)
    src = str;

  if (src == NULL)
    return NULL;

  if ((p = strpbrk (src, delims)) != NULL) {
    *p  = 0;
    ret = src;
    src = ++p;

  } else if (*src) {
    ret = src;
    src = NULL;
  }

  return ret;
}

示例用法

  char delims[] = ",";
  char data  [] = "foo,bar,,baz,biz";

  char * p    = strtok_single (data, delims);

  while (p) {
    printf ("%s\n", *p ? p : "<empty>");

    p = strtok_single (NULL, delims);
  }

输出

foo
bar
<empty>
baz
biz

@FilipRoséen-refp 我有一个问题关于你的回答。你能看一下这个链接吗?http://stackoverflow.com/questions/30294129/i-need-a-mix-of-strtok-and-strtok-single - aVC
1
请注意,此版本的 strtok_single() 不会返回最后一个分隔符后的片段。在这个答案中有一个修复的版本,以及问题的演示代码。 - Jonathan Leffler
@ChristopheQuintard 发现得真好,我认为那个特定的修复在某些历史版本中丢失了(现在已经修复,请参见历史记录)。 - Filip Roséen - refp
@JonathanLeffler 我刚刚注意到你提供了一个修复由@ChristopheQuintard解决的错误的方法,虽然它是有效的,但我认为你会对我刚刚编辑的修复感兴趣。 - Filip Roséen - refp
这看起来大致相等,尽管细节上有所不同。结果看起来是一样的,这很好。我只能点赞一次 - 因为“如果标准工具无法完成任务,则自己编写”这个建议完全有效,尽管需要谨慎使用。在重新发明轮子之前,您需要确保没有其他可替代的标准工具可供使用。 - Jonathan Leffler
显示剩余2条评论

2

最近我遇到了同样的问题,并在这个帖子中找到了解决方案。

你可以使用strsep()函数。 从手册中可以看到:

strsep()函数被引入作为strtok(3)的替代, 因为后者无法处理空字段。


2
如果你想这样做,就不能使用strtok()。从手册中可以看到:
如果解析字符串中有两个或多个连续的分隔符字符序列,则将其视为单个分隔符。字符串开头或结尾处的分隔符字符会被忽略。换句话说:由strtok()返回的标记始终是非空字符串。
因此,在你的示例中,它只会从c跳到d
你需要手动解析字符串或者寻找一个CSV解析库来简化你的生活。

1

此答案所述,您需要自己实现类似于strtok的东西。我更喜欢使用strcspn(而不是strpbrk),因为它允许更少的if语句:

char arr_fields[num_of_fields];
char delim[]=",\n";
char *tok;

int current_token= 0;
int token_length;
for (i = 0; i < num_of_fields; i++, token_length = strcspn(line + current_token,delim))
{
    if(token_length)
        sprintf(arr_fields[i], "%.*s", token_length, line + current_token);
    else
        sprintf(arr_fields[i], "%s", "-");
    current_token += token_length;
}

0
  1. 解析(例如,strtok)
  2. 排序
  3. 插入
  4. 如有需要,请重复以上步骤 :)

0
你可以尝试使用 strchr 来查找 , 符号的位置。手动将字符串标记化到找到的标记(使用 memcpystrncpy),然后再次使用 strchr。这样,您就可以看到两个或更多逗号是否相邻(strchr 将返回它们之间的差为 1 的数字),并编写一个 if 语句来处理该情况。

由于分隔符可以是逗号或换行符,因此 strchr() 不是合适的工具。 - Jonathan Leffler
strchr()无法定位‘\n’值吗? - Eternal_Light
是的,strchr() 可以找到逗号和换行符,但要找到下一个“逗号或换行符”,您必须调用 strchr() 两次,一次查找逗号,一次查找换行符。 - Jonathan Leffler
你不能使用一个'case'语句吗?我认为它不是最合适的工具,但它可以解决问题。 - Eternal_Light
1
还有其他的字符串函数 - strspn()strcspn()strpbrk(),它们可以完成大部分需要的工作。是的,我确定你可以使用case语句来编写它,但这不是我首先想到的方法。 - Jonathan Leffler
+1 因为你是对的 :) 我总是倾向于使用最简单的工具并手动构建周围的内容,以尝试使其按照我的意愿工作。显然,这会给我带来很多麻烦... - Eternal_Light

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接