【问题标题】:sscanf variable length string parsingsscanf 变长字符串解析
【发布时间】:2013-03-21 11:21:01
【问题描述】:

我有一个可能相当普遍的问题,并且可能有一个我不知道的漂亮技巧。如果有人能启发我,我将不胜感激!

我正在使用 C 的 sscanf() 函数来解析输入,格式为 “%d %d %d %s %d %s %d ...” 其中前两个 %d 是字符串的随机 ID 整数(无关紧要),第三个是要遵循的 %d %s 个组合的计数。

例如,“12 34 2 3 yes 2 no”可以是一个字符串,其中 12 和 34 random 是 ID(对问题不重要),2 指定 '3 yes' 和 '2 no' 之后的两个组合.前面的 3 'yes' 指定后面的字符串的长度,对于前面带有 2 的 'no' 也是如此。后面我们可以有不同数量的这些组合,我们想用 sscanf 捕获它们。

有谁知道用 sscanf 做这件事的任何方法吗?

非常感谢!

【问题讨论】:

  • 如果 C++ 标签是准确的,您可能希望使用 stringstream 来代替。它会让这变得更容易。
  • 是的,我更正了。对不起。
  • @TimHaggard:这是 C 问题还是 C++ 问题?它们是不同的语言。
  • @nneonneo:scanf 函数是 C 标准的一部分,也是 C++ 标准的一部分。用一种或两种语言标记scanf() 问题是合适的。
  • @Ben 但是根据使用的语言,一个好的答案会大不相同,这需要两个不同的问题/答案。将两者结合起来并没有真正帮助 imo。

标签: c++ c string parsing


【解决方案1】:

只需在两个(或更多)通道中解析字符串。这使用%n 格式说明符来写入处理的字节数,因此我们知道在后续传递中从哪里获取:

int a, b, n, pos;
const char *buf = "12 34 2 3 yes 2 no";

assert(sscanf(buf, "%d %d %d %n", &a, &b, &n, &pos) == 3);
for(int i=0; i<n; i++) {
    int cur;

    int x;
    char y[20];

    assert(sscanf(buf+pos, "%d %19s %n", &x, y, &cur) == 2);
    printf("%d %s\n", x, y);
    pos += cur;
}

输出

3 yes
2 no

【讨论】:

  • 我将 sscanf 包裹在 assert 周围,以确保我知道第一个返回 3 以外的东西,第二个分别返回 2 以外的东西。除此之外,+1 因为这是我在 C 编程领域最喜欢的答案。
  • 显然%n 对返回值的影响有些不确定(ew)。所以我会为&gt;=3/&gt;=2添加断言。
  • 顺便说一句,这实际上可能是我唯一一次发现%n 有用(除了利用格式字符串漏洞将任意数据写入任意堆栈地址)。
  • 不,定义明确。 C 标准说“执行 %n 指令不会增加 fscanf 函数执行完成时返回的赋值计数。”如果前面的指令成功,%n 指令将始终执行,这就是我建议assert 的原因。理想情况下,人们会报告错误而不是引发断言失败。但是,在这样的示例中包含错误报告代码会使焦点偏离解决方案的方向。 assert 希望在发生错误确实时重新关注。
  • 当您需要整数中的小数位数时,%n 也很有用。许多人会使用 assert(scanf("%d", &amp;val) == 1); do { decimal_count++; val /= 10; } while (val &gt; 0); 之类的代码(错误地,对于负值),而所有必要的代码就是 assert(scanf("%d%n", &amp;val, &amp;decimal_count) == 1);
【解决方案2】:

初始“%d %d %d”标头后面的 %d %s 对的数量是否有最大值? 必须有,因为您将值放在某个地方,也许在 结构{int i;字符 s[j]; } 一个]; 具有适当的 j 和 n 大小。

(顺便说一句,当描述表明它应该是“%d %d %d %d %s %d %s”时,您的示例使用“%d %d %d %s %d %s %d ...” %d %s ...", 3 位小数,后跟小数/字符串对)

如果有最大值,只需创建一个最大化的模板并测试来自 sscanf 的返回码(应该指示成功匹配和分配的输入项的数量)是您的第三个值减去 3 个标题项。 如果返回代码与第三个 int 相比不正确,请报告格式错误的行。

我曾经把这样的东西做成了一个函数,所以 a[n] 是自动从堆栈中分离出来的,然后该函数从堆中为 sscanf-ed 的项目分配了一个链表,并返回一个指向第一个项目的指针。

【讨论】:

    【解决方案3】:

    首先,ssprintf() 用于生成字符串,而不是解析它。您应该使用 sscanf。我不知道如何在一个 sscanf() 中完成它。但是你可以在一个循环中这样做:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(){
        int choice_id, count;
        char choice[20],id_1[20],id_2[20], count_str[20], choice_id_str[20];
        int index;
        char *input = "12 34 2 3 yes 2 no";
    
        sscanf(input, "%s %s %s", id_1, id_2, count_str);
        input += strlen(id_1)+strlen(id_2)+strlen(count_str)+2;
        count = atoi(count_str);
        for(index = 0; index< count; ++index){
            sscanf(input, " %s %s", choice_id_str, choice );
            choice_id = atoi(choice_id_str);
            // Process or store the record
            printf("%d: %s\n",choice_id, choice);
            input += strlen(choice_id_str) + strlen(choice) + 2;
        }
        return 0;
    }
    

    使用 gcc (GCC) 4.1.2 编译,并在 Linux 上运行。输出是:

    -bash-3.2$ ./a.out
    3: yes
    2: no
    

    【讨论】:

    • 这里输入的是要解析的字符串,比如“12 34 2 3 yes 2 no”
    • 不清楚如何移动指针。 scanf 可以消耗任意数量的输入字符串。
    • 不正确...sscanf 中的空格可以匹配任意数量的空格(不仅仅是一个字符)。
    • 我没有注意到这一点。但我认为它仍然运作良好。我将代码更新为完整示例。
    【解决方案4】:

    仅使用sscanf 没有方便的方法来执行此操作。您需要动态生成格式字符串本身,将其传递给sscanf

    您可能需要考虑为此编写一个专门的解析例程,在循环中调用 sscanf,或者更优选(因为您指定 C++ 标记)在循环中使用 std::istringstream

    【讨论】:

    • 我的意思是 sscanf。很抱歉造成混乱!
    猜你喜欢
    • 1970-01-01
    • 2015-04-10
    • 2021-11-18
    • 1970-01-01
    • 1970-01-01
    • 2020-04-28
    • 1970-01-01
    • 1970-01-01
    • 2019-07-24
    相关资源
    最近更新 更多