【问题标题】:How to properly get a line and parse it with C如何正确获取一行并用 C 解析它
【发布时间】:2019-08-15 23:00:31
【问题描述】:

我正在编写一个 C 程序,它将打开一个文件,写入文件,然后读取写入的内容。我可以打开、写入和关闭文件,但我无法读取这些行并正确解析它们。

我阅读了许多其他博客和网站,但没有一个能够完全解决我想要做的事情。我试过调整他们的一般解决方案,但我从来没有得到我想要的行为。我已经用 fgets()、gets()、strtok()、scanf() 和 fscanf() 运行了这段代码。我使用了 strtok_r() ,因为它被推荐为最佳实践。我使用 gets() 和 scanf() 作为实验来查看它们的输出,而不是 fgets() 和 fscanf()。

我想做什么:

  1. get first line // 第一行是一串空格分隔的 ints "1 2 3 4 5"
  2. 解析这一行,将每个字符数转换为整数
  3. 将其存储到一个数组中。
  4. 获取下一行并重复直到 EOF

谁能告诉我我缺少什么以及哪些功能被认为是最佳实践?

谢谢

我的代码:

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

int main(){
  FILE * file;

  // read data from customer.txt
  char lines[30];
  file = fopen("data.txt", "r"); 
  // data.txt currently holds five lines
  // 1 1 1 1 1 
  // 2 2 2 2 2
  // 3 3 3 3 3
  // 4 4 4 4 4 
  // 5 5 5 5 5

  char *number;
  char *next = lines;


  int s = 0;
  int t = 0;
  int num;
  int prams[30][30];

  while(fgets(lines, 30, file)){
        char *from = next;

    while((number = strtok_r(from, " ", &next)) != NULL){
        int i = atoi(number);
        prams[t][s] = i;
        printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);

        s++;
        from = NULL;               
    }

    t++;
  }

  fclose(file);
}// main

预期输出:

这是婴儿车[0][0]:1
...
这是婴儿车[4][4]: 5

实际输出:

这是婴儿车[0][0]:1
这是婴儿车[0][1]: 1
这是婴儿车[0][2]: 1
这是婴儿车[0][3]: 1
这是婴儿车[0][4]: 1
节目结束

【问题讨论】:

  • @EugeneSh。这只是标准评论还是在这种情况下实际上是对问题的解释?
  • 请参阅How to use sscanf() in loops,了解如何解析带有sscanf() 的行。外环控制使用while (fgets(lines, 30, file));不要使用feof(),除非(可能)在循环终止后区分 EOF 和 I/O 错误。
  • @Yunnosch 这个问题的答案应该可以帮助这个问题的作者
  • @Yunnosch 一种标准。但是一旦我看到一个问题,我就会产生一种“由于以前的错误而停止阅读”的状态:)

标签: c parsing io fgets strtok


【解决方案1】:

直接的主要问题是你一直告诉strtok_r() 从字符串的开头开始,所以它一直返回相同的值。您需要将第一个参数设置为 strtok_r() 为 NULL,以便它从中断处继续:

char *from = next;
while ((number = strtok_r(from, " ", &next)) != NULL)
{
    int i = atoi(number);
    prams[t][s] = i;
    printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
    s++;
    from = NULL;               
}

有些人会支持strtol() 而不是atoi();他们有一些正义,但可能还不够重要。

另请参阅How to use sscanf() in loops?,了解如何使用 sscanf() 解析行。

用途:

while (fgets(lines, 30, file))

用于外循环控制; don't use feof() 除了(可能)在循环终止以区分 EOF 和 I/O 错误之后。 (几年前,我检查了我的数百个 C 源文件,发现 eof() 的使用不到六次,全部在错误检查代码中,在循环控制中没有。你真的不需要使用它非常频繁。)

【讨论】:

  • 我更新了我的代码和我发布的代码以反映这些更改,但是行为是相同的,并且程序在一个外部循环迭代后结束。有什么想法吗?
  • 感谢您对 from = NULL 和 feof() 的见解。
【解决方案2】:

主要问题是:

  • 您永远不会将 s 重置为 0,因此该列总是增加而不是从 0 增加到 4(如果每行 5 个数字),因此您不会在数组中的预期条目上写入从第二行开始,您就有可能以未定义的行为(如分段错误)写出数组
  • 检查您没有读取过多的列和行(代码中的 30 行),否则您可能会以未定义的行为(如分段错误)从数组中写入数据
  • 你用错了strtok_r,第一个参数不能为空,只有在你第一次解析一行时(在你编辑之前)
  • doing number = strtok_r(from, " ", &amp;next) nextstrtok_r 修改,同时用于为下一行初始化from,所以第二行不会被正确读取,你的执行只是:

这是婴儿车[0][0]:11
这是婴儿车[0][1]:12
这是婴儿车[0][2]:13
这是婴儿车[0][3]:14
这是婴儿车[0][4]:15
这是婴儿车[3][5]: 0

data.txt 包含:

11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
51 52 53 54 55

(还请查看索引[3][5],因为您错过了重置s

补充说明:

  • 检查fopen成功
  • 初始化婴儿车或记住第一行有多少列,并检查下一行的列数是否始终相同,当然也要记住多少行,否则你不以后不知道数组中读取的数字在哪里
  • atoi 不表示你是否读过一个数字

考虑这些备注的建议是(我用 0 初始化数组,而不假设每行的数字数量):

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

#define LINELENGTH 30
#define SIZE 30

int main(){
  // read data from customer.txt
  char lines[LINELENGTH];
  FILE * file = fopen("data.txt", "r"); 

  if (file == NULL) {
    fprintf(stderr, "cannot read data.txt");
    return -1;
  }

  // data.txt currently holds five lines
  // 1 1 1 1 1 
  // 2 2 2 2 2
  // 3 3 3 3 3
  // 4 4 4 4 4 
  // 5 5 5 5 5

  int t = 0;
  int prams[SIZE][SIZE] = { 0 };

  while (fgets(lines, LINELENGTH, file)) {
    char * number;
    char * str = lines;
    int s = 0;

    while ((number = strtok(str, " \n")) != NULL) {
      char c;
      int i;

      if (sscanf(number, "%d%c", &i, &c) != 1) {
        fprintf(stderr, "invalid number '%s'\n", number);
        return -1;
      }
      prams[t][s] = i;
      printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
      str = NULL;
      if (++s == SIZE)
        break;
    }

    if (++t == SIZE)
      break;
  }

  fclose(file);
}// main

我使用sscanf(number, "%d%c", &amp;i, &amp;c) != 1 来轻松检测是否读取了一个数字并且只读取了一个数字,注意我添加了\nstrtok

的分隔符

编译和执行:

pi@raspberrypi:/tmp $ !g
gcc -pedantic -Wall -Wextra l.c
pi@raspberrypi:/tmp $ cat data.txt 
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45 
51 52 53 54 55
pi@raspberrypi:/tmp $ ./a.out
this is prams[0][0]: 11
this is prams[0][1]: 12
this is prams[0][2]: 13
this is prams[0][3]: 14
this is prams[0][4]: 15
this is prams[1][0]: 21
this is prams[1][1]: 22
this is prams[1][2]: 23
this is prams[1][3]: 24
this is prams[1][4]: 25
this is prams[2][0]: 31
this is prams[2][1]: 32
this is prams[2][2]: 33
this is prams[2][3]: 34
this is prams[2][4]: 35
this is prams[3][0]: 41
this is prams[3][1]: 42
this is prams[3][2]: 43
this is prams[3][3]: 44
this is prams[3][4]: 45
this is prams[4][0]: 51
this is prams[4][1]: 52
this is prams[4][2]: 53
this is prams[4][3]: 54
this is prams[4][4]: 55

【讨论】:

  • 你说得对,非常感谢!我调整了我的代码以反映您所做的更改并且它的行为正确。这两个问题是:我没有正确重置“s”并且第一次迭代后 strtok() 的格式不正确。
  • @RiceMan 警告还必须考虑其他评论;-)
【解决方案3】:

如果你想解析空格分隔的文本,那么 scanf 和朋友是你最好的选择。但是,如果您想特别处理换行符而不是空格,那么您需要 fgets+sscanf 循环:

#define ROWS 30
#define COLS 30
#define MAXLINE 512
int prams[ROWS][COLS];
int row, col, len;
char buffer[MAXLINE], *p;

row = 0;
while (row < ROWS && fgets(buffer, MAXLINE, stdin)) {
    col = 0;
    p = buffer;
    while (col < COLS && sscanf(p, "%d %n", &prams[row][col], &len) > 0) {
        p += len;
        ++col; }
    if (*p) {
        /* extra stuff on the end of the line -- error? */ }
    ++row; }

注意还要检查边界以确保不超过固定大小的数组边界。

【讨论】:

  • 克里斯,谢谢你的例子。我无法让它与我使用文本文件编写的当前代码一起工作。但是,您的代码风格非常简洁和优雅,我计划将其合并。
猜你喜欢
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 1970-01-01
相关资源
最近更新 更多