【问题标题】:How to read a string with whitespaces and a int in the same line in c如何在c中的同一行中读取带有空格和int的字符串
【发布时间】:2018-06-15 11:03:53
【问题描述】:

我如何阅读这样的文本文件:

Acqua Naturale 200
Coca Cola 100
Bibite 300

并使用 sscanf() 将字符串 Acqua naturaleCoca Cola 及其 int 值存储在 int 变量中。

示例代码是这样的:

struct Test
{
 char name[16];
 int id;
};


 char * buffer = malloc(sizeof(struct Test));

 while(fgets(buffer, sizeof(struct Test), filep))
    {
      if(sscanf(buffer, "%s %d", p.name, &p.id) == 2)
      {
        //do something with data

      }
    }

【问题讨论】:

  • 呃,你分配的数据量太小了。您的结构的大小很可能是 20 字节,如果数字超过四位,这还不够。
  • 至于你的字符串问题,sscanf (and family)格式"%s"读取的是空格分隔字符串。
  • 是否应该将"7-up 100" 解析为"7-up" 和100? "abc4 123" 呢。 "xyz456" 解析成"xyz" 456 是否有效?
  • 这里有一些关于阅读不可预测的“语法”输入的非常好的(虽然更通用)的建议:sekrit.de/webdocs/c/beginners-guide-away-from-scanf.htmlstackoverflow.com/questions/35178520/…
  • 不建议用答案编辑您的问题。如果您想回答自己的问题,请将其放在答案部分,并将其标记为已回答。出于礼貌,事后改变问题的性质以适应您的回答是不好的形式。

标签: c text-files scanf


【解决方案1】:

将“Acqua Naturale 200”分成“Acqua Naturale”和200是一个在行尾查找整数的问题。

各种方法。

也许寻找最后一个空格分隔符,

OP 很好地读取了 line,然后尝试解析 - 这比 scanf() 更好。

请注意,OP 的缓冲区太小。考虑"abcdefghijklmno -2000000000\n",需要大小为 15 + 1 + 11 + 1 + 1 字节的有效输入。当然这比sizeof(struct Test) 更多,因为int 的文本可能需要比二进制编码的int 更多的空间(例如2、4 或8 个字节)。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

... 

  FILE *filep;
  struct Test p;
  //                        p.name    sp  int  \n  \0
  #define LINE_SIZE (sizeof p.name  + 1 + 11 + 1 + 1)
  char buffer[LINE_SIZE *2]; // No need to be stingy with temp buffer space, go for x2

  while(fgets(buffer, sizeof buffer, filep)) {
    char *last_space = strrchr(buffer, ' ');
    if (last_space == NULL || (last_space - buffer) >= sizeof p.name ||
        sscanf(last_space, "%d", &p.id) == 0) {
      fprintf(stderr, "Bad input '%s'\n", buffer);
      break;
    }
    memcpy(p.name, buffer, last_space - buffer);
    p.name[last_space - buffer] = '\0';

    // Do something with `p`
  }

更健壮的代码将使用strtoi 并在"xxx 122zzz" 中的数字后面查找额外的垃圾。过长的行也应该被检测出来。

【讨论】:

    【解决方案2】:

    在尝试编写代码来读取此文件之前,您应该多考虑一下文件是如何定义的——确切地说它是如何定义的。

    非正式地,文件的定义是“第一列是可能包含空格的字符串,第二列是整数”。但是是什么分隔了这些列?

    如果列由空格分隔,并且第一列可以包含空格,那么第一列并不是真正的第一列,它可能是多个列。也就是行

    Coca Cola 100
    

    确实包含三列。

    因此,如果我们想走这条路,我们必须尝试区分为整数的第二列和看起来不像整数的第一列(尽管它可能包含空格)。

    但如果我们沿着这条路走,我们会遇到两个非常严重的问题:

    1. 很难编码。单独使用scanfsscanf 可能无法令人满意地编写代码。

    2. 它仍然模棱两可。如果可口可乐推出新产品“Coca Cola 2020”会怎样?然后我们会有一行像

      Coca Cola 2020 50

    所以我的底线是,如果是我,我什至不会尝试编写代码来解析这种文件格式。我可能会想出一种更清晰、更简洁的文件格式

    Coca Cola, 100
    

    "Coca Cola",100
    

    Coca Cola|100
    

    然后编写一些简洁的代码来解析它。 (不过,我可能仍然不会使用scanf;我可能会使用更像strtok 的东西。另请参阅我的C 编程笔记中的this chapter。)


    附录:可能下降的另一条路是从右侧边缘计算列。在这种情况下,您可以编写代码,实际上说产品名称在第 1 到 N-1 列中,并且计数是第 N 列。只要最多有一个包含空格的“列”就可以工作。

    【讨论】:

      【解决方案3】:

      两个快速观察,

      • 考虑到这一特殊任务,strtok() 优于 sscanf() 是更好的选择。

      • 除非输入文件中只有一条记录(数据行),否则需要一个 struct 数组(而不是单个实例)来包含数据。

      理性:
      源文件的语法定义和可预测性越高,解析的复杂性就越低。如上所述,您的文件具有可预测的内容。由于语法的可变性有限,标记记录,使用 strtok() 函数是一个不错的选择。

      对于您正在执行的操作,文件内容的唯一可变性是行数,以及末尾数字字符串之前的字母字符串数。其余的假设每行中都有空格分隔的子字符串,only 最后一个有数字内容。因此,一种适应这种类型文件的方法可能会根据要处理的行数为struct 的数组创建运行时内存,并使用strtok() 函数来读取元素,并根据它是字符串的类型(字母或数字)。

      示例方法:

      文件:x.txt 包含以下内容:

      天然水 200
      可口可乐 100
      Bibite 300
      内斯比特黄金 400
      芬达冰橙 500
      可口可乐樱桃奶油600

      char filename[] = {".\\x.txt"};
      
      typedef struct {
          char name[200]; // add plenty of space
          int id;
      }TEST;
      
      void PopulateTest(TEST *t, char *file);//populate struct with content of file.
      int GetLines(char *name);//get line count
      
      int main(int argc, char *argv[])
      {
          int lineCount = GetLines(filename);//get lines in file
          int i;
      
          TEST *test;//to create a variable number of instances of TEST
      
          test = calloc(lineCount, sizeof(TEST));
          if(test)
          {
              PopulateTest(test, filename);
          }
          for(i=0;i<lineCount;i++)
          {
              ;//do something with results    
          }
          free(test);
      
          return 0;
      }
      
      void PopulateTest(TEST *t, char *file)
      {
          int num = 0;
          int i = 0;
          char *tok = NULL;
          char line[200] = {0};
          char accum[200] = {0};
          FILE *fp = fopen(filename, "r");
          if(fp)
          {
              while(fgets(line, sizeof(line), fp))
              {
                  tok = strtok(line, " ");
                  while(tok)// this loop accommodates a variable number of fields within each line 
                  {
                      if(isdigit(tok[0]))//test for sub-string content
                      {
                          num = atoi(tok);
                      }
                      else               //read string segments and reconstruct string,
                      {
                          strcat(accum, tok);
                          strcat(accum, " ");
                      }
                      tok = strtok(NULL, " ");
                  }
                  strcpy(t[i].name, accum);//populate struct element members with parsed data.
                  t[i].id = num;
                  i++;
              }
              fclose(fp);
          }
          return; 
      }
      
      int GetLines(char *name)
      {
          int count = 0;
          char line[200] = {0};
          FILE *fp = fopen(name, "r");
          if(fp)
          {
              while(fgets(line, sizeof(line), fp))
              {
                  count++;
              }
              fclose(fp);
          }
          return count;
      }
      

      【讨论】:

        【解决方案4】:

        这里有一些误解。

        • 输入文件似乎是一个文本文件。如果是这样,您无法从中读取 sizeof 结构,因为这将采用二进制格式,而不是更长的文本。而不是 malloc,只需将buffer 分配为“足够大”,例如char buffer[200];
        • 即使是二进制文件,也不应该直接从文件读/写结构体。这是因为结构可以包含填充字节来修复对齐,并且这些字节可以以不可移植的方式位于任何地方。因此,如果一个程序写入文件而另一个程序读取它,它将中断。读取/写入结构的常用方法是通过一次读取/写入每个单独的成员来“序列化”和“反序列化”。
        • 所以改为将整行作为文本读入buffer,然后解析它。 sscanf 是一个相当生硬的工具,除非您确定缓冲区格式。相反,您可以搜索字符串中的最后一个空格 ' ' 并将其前面的任何内容作为名称(确保它少于 15 个字符 + 1 个空终止符),以及传递给 strtol 的空格之后的所有内容。发给int

        【讨论】:

          【解决方案5】:

          我使用strtok()strcpy()strcat()atoi()isdigit() 找到了这个解决方案。我正在使用链表来存储数据,所以我认为这是一个特定的解决方案。忽略函数Load()和函数CreateNewNodeOfList()的参数。

          void Load(HeadNode *pp) // ignore parameter
          {
            FILE *f;
            struct Test p;
            char * buffer;
            char * token;
            char name[32] = "";
            if(!(f = fopen(PATH, "r")))
            {
              perror("Errore");
              exit(-1);
            }
          
            buffer = malloc(sizeof(struct Test));
          
            while(fgets(buffer, sizeof(struct Test), f))
              {
              for(token = strtok(buffer, " "); token != NULL; token = strtok(NULL, " "))
                {
                    if(isdigit(token[0]))
                      {
                        p.id = atoi(token);
                      }
                      else
                      {
                        strcat(p.name, token);
                        strcat(p.name, " ");
                      }
                }
                  CreateNewNodeOfList(p, pp); //ignore this function 
                  strcpy(p.name, "");
              }
          
          free(buffer);
          fclose(f);
          
          }
          

          【讨论】:

            猜你喜欢
            • 2011-04-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-11-05
            • 1970-01-01
            • 1970-01-01
            • 2020-03-18
            • 1970-01-01
            相关资源
            最近更新 更多