【问题标题】:Determine if sscanf Read Anything with %*s确定 sscanf 是否使用 %*s 读取任何内容
【发布时间】:2015-02-06 03:26:52
【问题描述】:

假设我有字符串:

char* foo = "  blah  blee  bleck  ";

现在说我要阅读并扔掉前两个字:

int bar = 0;

sscanf(foo, "%*s%n", &bar);
foo += bar;

if(bar > 0) sscanf(foo, "%*s%n, &bar);

我的问题是如何判断第二个 sscanf 是否读到了什么?

我是否需要在每次读取之间将bar 设为 0 以确定是否实际读取了字符串,还是有更好的方法?

编辑:

检查 sscanf 返回值将不起作用,因为 %*s 和 %n 不会增加 sscanf 的返回值:

printf("%d ", sscanf(foo, "%*s%n", &bar));
printf("%d ", bar);
printf("%d ", sscanf(foo + bar, "%*s%n", &bar));
printf("%d\n", bar);

将输出:

0 6 0 6

【问题讨论】:

标签: c scanf dummy-data


【解决方案1】:

检查 sscanf 调用的返回值,因为文档离子状态将返回一个整数值。

int sscanf( const char          *buffer, const char          *format, ... ); (C99)

我在我的机器上尝试了一小段代码,类似于你的查询,确实 sscanf 为不成功的操作返回负值。

测试代码:

#include <stdio.h>

void main(void)
{
char* foo = "  blah  bleee  bleckk  ";
//char* foo = "  blah";
int bar = 0;
int ret = -10;

printf("#m1 foo:%s\n",foo);
printf("#m1 bar:%d\n",bar);
printf("#m1 ret:%d\n\n",ret);

ret = sscanf(foo, "%*s%n", &bar);
foo += bar;
printf("#m2 foo:%s\n",foo);
printf("#m2 bar:%d\n",bar);
printf("#m2 ret:%d\n\n",ret);

if(bar > 0)
{
    ret = -10;
    ret = sscanf(foo, "%*s%n", &bar);
    foo += bar;
}
printf("#m3 foo:%s\n",foo);
printf("#m3 bar:%d\n",bar);
printf("#m3 ret:%d\n",ret);
}

两种场景的测试输出:

./a.out
#m1 foo:  blah  bleee  bleckk
#m1 bar:0
#m1 ret:-10

#m2 foo:  bleee  bleckk
#m2 bar:6
#m2 ret:0

#m3 foo:  bleckk
#m3 bar:7
#m3 ret:0


./a.out
#m1 foo:  blah
#m1 bar:0
#m1 ret:-10

#m2 foo:
#m2 bar:6
#m2 ret:0

#m3 foo:oo:%s

#m3 bar:6
#m3 ret:-1

问题是 sscanf 调用失败时 bar 值未设置为零。

【讨论】:

  • 这篇文章的答案是在第二种情况下,foo 只是“废话”。请注意,ret 在最后一个输出中为 -1 (EOF)。有了这种理解,@hdante answer 就有意义了。
【解决方案2】:

如果您忽略空格,正确的方法是在 sscanf() 返回值中测试 EOF。 EOF 表示 %*s 没有读取任何内容。如果您不忽略空格,则解决方案很简单:如果 *foo != 0 在 sscanf 之前,则 sscanf 会读取一些内容。

【讨论】:

    【解决方案3】:

    ...阅读并丢弃前两个单词:

    const char* foo = "  blah  blee  bleck  ";
    int bar = 0;
    sscanf(foo, "%*s%*s% %n", &bar);
    if (bar) {
      printf("Success: string with first two words thrown away:'%s'\n", &foo[bar]);
    }
    

    上面在"%n"之前添加了一个空格来丢弃“blee”之后的空白。


    判断第二个 sscanf 是否读取了任何内容?我需要 0 吗?

    是的,重置bar = 0; 是一种简单的方法。除非前面的 "%*s" 扫描了一些非空白,否则 "%n" 不会被执行。

    char* foo = "  blah  blee  bleck  ";
    int bar = 0;
    sscanf(foo, "%*s%n", &bar);
    foo += bar;
    
    if(bar > 0) {
      puts("first sscanf read something.");
      bar = 0;
      sscanf(foo, "%*s%n", &bar);
      if (bar) puts("second sscanf read something.");
    }
    

    【讨论】:

    • 很好,我从这篇文章中了解到:“格式字符串中的单个空格可以验证从流中提取的任意数量的空格字符(包括无)。” cplusplus.com/reference/cstdio/scanf/#parameters
    • @Jonathan Mee 关于" ""\t""\n" 等的另一个重要说明是它们永远不会失败。扫描将始终进行到下一个指令。
    • 是的,我读到了。 man scanf 太强大了,我真希望我在大学时就知道所有这些技巧!
    【解决方案4】:

    更好的方法可能是使用一些适当的标记化代码,而不是基于sscanf()。我可能会写这样的东西:

    const char * skip_word(const char *s)
    {
      while(isspace(*s))
       ++s;
      while(*s && !isspace(*s))
       ++s;
      return s;
    }
    

    这可以用来跳过前导空格后跟一个单词,其中一个单词被非常松散地定义为“非空白字符”。然后,您可以通过两次调用上述方法来提取第三个单词,当然要检查中间返回值以获得敏感性。类似的东西。

    但是,当然,您必须清除 bar 才能获得新的解释。

    规范似乎有点混淆%n 是否包含在返回值中,仅此一点就告诉我这不是最好的方法。

    【讨论】:

    • “正确的标记化代码”是指我编写自己的函数一次读取一个字符,丢弃空格,并在下一个字符为空格或空字符时返回?
    • 虽然我确实认为标记化是一种有效的替代方案,但它似乎不必要地为应该是一个简单的解决方案引入了复杂性。 hdanteMuruganV-userm 的解决方案可以防止重新发明轮子。
    【解决方案5】:

    来自sscanf() 文档:

    返回值

    这些函数返回成功匹配的输入项的数量和 分配的,它可以比规定的要少,或者在发生以下情况时甚至为零 早期匹配失败。

    因此,如果 sscanf() 没有返回您期望的匹配数,它可能没有读取您想要的内容。

    【讨论】:

    • 不,星号抑制了这一点。
    • @GreenScape "%*s" 和 "%n" 都不会增加 sscanf 的返回值。 (我实际上是先尝试过这种方法。)
    • @JonathanMee 你能告诉我们为什么这种方法行不通吗?如果 sscanf 失败,它应该返回失败值
    • @MuruganV-userm 我已经编辑了这个问题来说明为什么这不起作用。
    • @JonathanMee 请检查我的答案,因为输出清楚地表明 -ve sscanf 失败的值
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多