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

时间:2019-03-25 16:23:48

标签: c parsing io fgets strtok

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

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

我想做什么:

  1. 获取第一行// //第一行是由空格分隔的整数“ 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
  程序结束

3 个答案:

答案 0 :(得分: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();他们方面有一些正义,但可能不足以解决问题。

有关如何使用sscanf()解析行的信息,另请参见How to use sscanf() in loops?

使用:

while (fgets(lines, 30, file))

用于外循环控制; don't use feof()除外(也许)在循环终止之后(以区分EOF和I / O错误)。 (几年前,我检查了我的数百个C源文件,发现eof()的使用少于一半,全部用于错误检查代码,而没有用于循环控件。您实际上不需要经常使用它。)

答案 1 :(得分:1)

主要问题是:

  • 您永远不会将 s 重置为0,因此该列始终会增加而不是从0增加到4(如果每行5个数字),那么您就不会在数组中的预期条目上书写从第二行开始,您可能会以不确定的行为(例如分段错误)写出数组
  • 检查您没有读太多的行和列(在代码中为30),否则您可以用未定义的行为(例如分段错误)写出数组
  • 您错误地使用strtok_r,仅在第一次解析行时(编辑之前),第一个参数不能为null
  • 执行number = strtok_r(from, " ", &next) 下一步strtok_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 的时间)

其他说明:

  • 检查打开成功
  • 初始化 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来轻松检测数字是否只有数字,请注意,我添加的\n strtok

的分隔符

编译和执行:

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

答案 2 :(得分:0)

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

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form action="addServicesRequest.php" method="POST">
  <div class="container form-control">
    <h5>Ajoutez des services à votre réservation :</h5>

    <div class="text-center">
      <input type="checkbox" name="service" value="10">
      <label>service</label>
      <strong class="priceOfService">10 €</strong><br>
    </div>

    <div class="text-center">
      <input type="checkbox" name="service" value="20">
      <label>service</label>
      <strong class="priceOfService">20 €</strong><br>
    </div>

    <hr>
    <div>
      <p>Prix de la réservation : <strong>X€</strong>
       Prix des services : <strong id="priceServices">X€</strong></p>
      <h4>Total : <strong id="totalPrices">X€</strong></h4>
      <input type="button" value="Valider" class="btn btn-dark btn-sm container">
    </div>
  </div>

</form>

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