如何正确获取一行并用 C 解析它

How to properly get a line and parse it with C

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

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

我想做的事情:

  1. 获取第一行 // 第一行是 space 分隔的字符串“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

预期输出:

this is prams[0][0]: 1
...
this is prams[4][4]: 5

实际输出:

this is prams[0][0]: 1
this is prams[0][1]: 1
this is prams[0][2]: 1
this is prams[0][3]: 1
this is prams[0][4]: 1
program ends

直接的主要问题是您一直告诉 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() 的使用不到六次,全部用于错误检查代码和循环控制中的 none。你真的赢了根本不需要经常使用它。)

主要问题是:

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

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[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

补充说明:

  • 检查打开成功
  • 初始化 prams 或记住第一行有多少列,并检查下一行的列数是否始终相同,当然还要记住多少行,否则你以后不知道数组中读取的数字在哪里
  • 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", &i, &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

如果您想解析以空格分隔的文本,那么 scanf 和 friends 是您最好的选择。但是,如果您想特殊处理换行符而不是空格,那么您需要 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; }

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