C-用fgets或scanf读取一个多行文件?

问题描述 投票:0回答:1

比如说我有一个文本文件

person1
25
500
male
person2
..
..
..
person3
..

有多少人,我想把文件中的4行读到一个结构中,让文件中的每个人都能读到。

我怎样才能做到这一点?我试过使用多个 fgets,但我不知道如何在一次读取四行的同时,循环到文件的最后。

谢谢你

c file-io
1个回答
0
投票

继续上面的评论,当你在一个数据文件中有一组固定的重复行,你需要将其读入一个结构中。struct这是您应该考虑的唯一例外之一。scanf()/fscanf() 超过建议的 fgets()/sscanf() 的每一行。

为什么呢?

scanf() 是一个 格式化输入功能 (与 fgets() 这是个 行向输入函数). 如果您的格式化输入跨越多行。scanf()/fscanf() 漠视 空白 (该 '\n'空白),并允许您将多行作为一个单一的输入来使用(使用一个正确制作的 格式化字符串)

当使用 scanf()/fscanf() 来读取数据到一个字符串(或数组)中,你必须使用 字段宽度 修饰符来限制读取到数组中的值的数量,以避免写到数组的末端,调用 未定义的行为 如果一个输入超出了你的数组范围。这适用于每当你使用 scanf()/fscanf()/sscanf() 全家)。使用无 字段宽度修改器 读取数组数据并不比使用 gets().

那么如何制作你的 格式化字符串? 让我们来看一个类似于问题中所示的4个成员结构的例子,例如

...
#define MAXG 8      /* if you need a constant, #define one (or more) */
#define MAXP 32
#define MAXN 128

typedef struct {    /* struct with typedef */
    char name[MAXN], gender[MAXG];
    int iq, weight;
} person;
...

如图所示的数据,并在声明中写明 name128 角色,而对于 gender8 角色,其余两名成员为 int 类型,你可以做类似下面的事情。

    int rtn;                                /* fscanf return */
    size_t n = 0;                           /* number of struct filled */
    person ppl[MAXP] = {{ .name = "" }};    /* array of person */
    ...
    while (n < MAXP &&  /* protect struct array bound, and each array bound below */
            (rtn = fscanf (fp, " %127[^\n]%d%d %7[^\n]", /* validate each read */
                    ppl[n].name, &ppl[n].iq, &ppl[n].weight, ppl[n].gender)) == 4)
        n++;            /* increment array index */

具体来看 格式化字符串,你有。

    " %127[^\n]%d%d %7[^\n]"

其中 " %127[^\n]"凭借着领先的 ' '消耗任何前导空格,然后最多读到 127 字符(不能用变量或宏来指定 字段宽度),字符是指该行中的任何字符,该字符不是 '\n' 字符(允许您读取空格作为名称的一部分,如 "Mickey Mouse").

请注意 "%[...] 是一个字符串转换,将读取字符列表中的任何字符。[...] 作为一个字符串。使用环形 '^' 作为列表的第一个字符 否定 导致 "%[^\n]" 读出所有字符,不包括 '\n' 变成一个字符串。

前面的空格 " %[^\n]" 是需要的,因为 "%[...]" 喜欢 "%c" 是唯一 转换规格 不消耗 前导空格所以你可以通过在格式字符串中的转换前加入一个空格来实现。的另外两个转换指定符 int例如 "%d" 会自行消耗前导空格导致总的转换。

    " %127[^\n]%d%d %7[^\n]"

总的来说,就是会:

  • 消耗任何前导空格(the '\n' 留在 stdin 前一读或 gender 为数组中的前一个结构)。)
  • 将最多127个字符的行读入数组中的 name 成员 %127[^\n];
  • 将包含第一个整数值的行读入。iq%d 消耗前导空格)。
  • 将包含第二个整数值的行读入到 weight%d (同上)。
  • ' ' 消耗 '\n' 从阅读中留下的 weight;最后
  • 读取一行最多7个字符的文字到 "我的 "中。gender 成员 %7[^\n] (根据需要调整,以保持你的最长性别字符串)

通过这种方法,你可以通过调用 fscanf(). 你应该检查 rtn 循环退出时,确保循环退出的时间是在 EOF 从文件中读取所有值后。 一个简单的检查就可以涵盖所需的最低限度的验证,如

    if (rtn != EOF) /* if loop exited on other than EOF, issue warning */
        fputs ("warning: error in file format or array full.\n", stderr);

(注意。 您还可以检查是否 n == MAXP 来看看循环退出的原因是不是因为数组分别满了)。)

放在一起,你可以这样做。

#include <stdio.h>

#define MAXG 8      /* if you need a constant, #define one (or more) */
#define MAXP 32
#define MAXN 128

typedef struct {    /* struct with typedef */
    char name[MAXN], gender[MAXG];
    int iq, weight;
} person;

int main (int argc, char **argv) {

    int rtn;                                /* fscanf return */
    size_t n = 0;                           /* number of struct filled */
    person ppl[MAXP] = {{ .name = "" }};    /* array of person */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (n < MAXP &&  /* protect struct array bound, and each array bound below */
            (rtn = fscanf (fp, " %127[^\n]%d%d %7[^\n]", /* validate each read */
                    ppl[n].name, &ppl[n].iq, &ppl[n].weight, ppl[n].gender)) == 4)
        n++;            /* increment array index */

    if (rtn != EOF) /* if loop exited on other than EOF, issue warning */
        fputs ("warning: error in file format or array full.\n", stderr);

    for (size_t i = 0; i < n; i++)  /* output results */
        printf ("\nname   : %s\niq     : %d\nweight : %d\ngender : %s\n",
                ppl[i].name, ppl[i].iq, ppl[i].weight, ppl[i].gender);

    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
}

(注意。 您也可以使用全局的 enum 来定义你的常量)

输入文件示例

$ cat dat/ppl.txt
person1
25
500
male
person2
128
128
female
Mickey Mouse
56
2
male
Minnie Mouse
96
1
female

例子 UseOutput

$ ./bin/readppl dat/ppl.txt

name   : person1
iq     : 25
weight : 500
gender : male

name   : person2
iq     : 128
weight : 128
gender : female

name   : Mickey Mouse
iq     : 56
weight : 2
gender : male

name   : Minnie Mouse
iq     : 96
weight : 1
gender : female

您也可以用 fgets() 使用行式计数器或多行读数方法,但这更多的是关于为工作选择合适的工具。使用 fgets() 然后多次调用 sscanf() 的整数值或两次调用 strtol() 用于转换,但对于大的输入文件,1-函数调用到 fscanf() 相比于4次单独调用 fgets() 再加上2次单独的呼叫 sscanf()strtol() 再加上处理行计数器或多缓冲区逻辑的额外逻辑,将开始增加。

如果你有更多的问题,请仔细阅读,并告诉我。


0
投票

一些示例行。 我将让你提供程序的其余部分。

#define MAX 1000

  ...

  FILE *f;
  char line1[MAX], line2[MAX], line3[MAX], line4[MAX];

  ...

  while(fgets(line1, MAX, f) != NULL)
    {
      if (fgets(line2, MAX, f) == NULL ||
          fgets(line3, MAX, f) == NULL ||
          fgets(line4, MAX, f) == NULL)
        {
         /* insert code here to handle end of file in unexpected place */
          break;
        }
      /* insert code here to do your sscanf and anything else you want */
    }

    ....
© www.soinside.com 2019 - 2024. All rights reserved.