【问题标题】:How to parse an input line with sscanf?如何用 sscanf 解析输入行?
【发布时间】:2014-03-24 01:10:53
【问题描述】:

我有一个如下所示的输入 .txt 文件:

Robert Hill 53000 5

Amanda Trapp 89000 3

Jonathan Nguyen 93000 3

Mary Lou Gilley 17000 1 // Note that came contains of 3 parts!

Warren Rexroad 72000 7

我需要阅读这些行并将它们解析为三个不同的类别:名称(这是一个字符数组)、里程(int)和年份(int)。

 sscanf(line, "%[^] %d %d ", name, &mileage, &years);

这对我来说不是很好,有什么建议吗?

【问题讨论】:

    标签: c arrays parsing input scanf


    【解决方案1】:

    问题

    传递给sscanf 的当前说明符的问题在于它的格式不正确,而且即使修复它也无法满足您的要求。如果您将[^ ] 用作第一个转换说明符sscanf 会在点击空格之前尝试读取尽可能多的字符。

    如果我们假设名称不能包含指定[^0123456789] 的数字,则将读取正确的数据,但它还会在名称之后、第一个mileage 条目之前包含尾随空格。然而,这很容易通过将name 中的最后一个空格替换为空字节来解决。

    要获取读入name 的字符数,我们可以使用%n 说明符来表示我们将sscanf 存储读入匹配参数的字节数;我们稍后可以使用这个值来正确地“修剪”我们的缓冲区。

    我们还应该指定%[^0123456789] 读取的字符的最大宽度,这样它就不会导致缓冲区溢出,这是通过在我们之后直接指定缓冲区的大小来完成的。 %.


    示例实现

    #include <stdio.h>
    #include <string.h>
    
    int
    main (int argc, char *argv[])
    {
      char const * line = "Mary Lou Gilley 17000 1";
    
      char     name[255];
      int mileage, years, name_length;
    
      sscanf(line, "%254[^0123456789]%n %d %d ", name, &name_length, &mileage, &years);
    
      name[name_length-1] = '\0';
    
      printf ("data: '%s', %d, %d", name, mileage, years);
    
      return 0;
    }
    

    data: 'Mary Lou Gilley', 17000, 1
    

    【讨论】:

    • 应该检查sscanf() 结果。应该是"%254[..."“指定我们缓冲区的大小” - 1。(大小为宽度+1)建议“[^0123456789+-]”。
    • 谢谢,您的回答非常好。我只是不明白为什么我需要 name_length 变量。
    【解决方案2】:

    如果你有一个函数可以像这样找到第一个数字的位置:

    // This function returns the position of the 
    // space before the first digit (assuming that
    // the names dont contain digits)...
    char *digitPos(char *s){
        if isdigit(*(s+1)) return s;
        else return digitPos(s+1);
    }
    

    然后您可以通过在正确的位置插入'\0' 来分隔两个变量,如下所示:

    pos  = digitPos(line); // This is a pointer to the space
    *pos = '\0';
    strcpy(name, line);
    sscanf(pos + 1, "%d %d", &mileage, &years);
    

    【讨论】:

    • +1 这是朝着正确方向迈出的一步。当然,你应该更进一步,根本不要使用scanf
    • 另外,您的digitPos 需要检查字符串终止符。 C 不保证提供“正确的尾递归”,尽管我希望大多数现代编译器会为这个例子做正确的事情。
    【解决方案3】:

    这可能会帮助您入门。它缺乏 BLUEPIXY 解决方案的智能,该解决方案处理尾随空格的能力比我的要好一些(或者你可以自己砍掉它)。

    dan@rachel ~ $ echogcc -o t t.c
    dan@rachel ~ $ echo "Dan P F 3 21" | ./t
    Name:    Dan P F ,
    Mileage:         3,
    Years:   21.
    

    这是代码。

    #include <stdio.h>
    #include <string.h>
    
    int main(){
            char *buf;
            int mileage, years;
            while(!feof(stdin) ){
                    if( fscanf( stdin, "%m[^0-9] %d %d", &buf, &mileage, &years) == 3 ){
                            fprintf(stderr, "Name:\t %s,\nMileage:\t %d,\nYears:\t %d.\n", 
                                    buf, mileage, years
                            );
                    }
            }
    
    }
    

    【讨论】:

      【解决方案4】:

      您发现了永远不应使用 *scanf 的三个原因之一:几乎不可能编写处理非平凡输入语法的格式规范,尤其是当您不得不担心从 格式错误 中恢复时输入。但还有两个更重要的原因:

      • 许多输入规范,包括您的 %[...] 构造,都像臭名昭著的 gets 一样乐于溢出缓冲区。
      • 数字溢出会引发未定义的行为 - C 库被许可崩溃,只是因为有人键入了太多数字。

      解析此类行的正确方法是使用strcspn("0123456789", line)while (*p &amp;&amp; !isdigit(*p)) p++;扫描第一个数字,然后使用strtoul转换后面的数字。

      【讨论】:

        【解决方案5】:
        int pos;
        sscanf(line, "%*[^0-9]%n", &pos);
        line[--pos]=';';
        sscanf(line, "%[^;]; %d %d ", name, &mileage, &years);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-07-23
          • 1970-01-01
          • 2011-01-08
          • 1970-01-01
          • 2017-08-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多