C-使用fgets或sscanf读取多行文件



例如,我有一个文本文件

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

不管有多少人,我想把文件的4行读入文件中每个人的结构中。

我该怎么做?我试过只使用多个fget,但我不知道如何在每次读取四行的同时循环到文件末尾

感谢

继续上面的注释,当数据文件中有一组固定的重复行需要读取到struct中时,这是唯一一个例外,您应该将scanf()/fscanf()考虑到每行的推荐fgets()/sscanf()之上。

为什么

scanf()是一个格式的输入函数(与fgets()相比,CCD_5是一个面向行的输入函数)。如果您格式化了跨多行的输入,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;
...

使用如图所示的数据,name的声明为128字符,gender的声明为8字符,其余两个成员为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]"

总之,这将:

  • 使用任何前导空格(上一次读取后留在stdin中的'n'或数组中上一个结构的gender)
  • 用CCD_ 35向CCD_ 34成员读取最多127个字符的行
  • 将包含第一整数值的行读取到具有%diq中(其消耗前导空白)
  • %d将包含第二整数值的行读取到weight中(同上)
  • CCD_ 40消耗CCD_;最后
  • %7[^n]gender成员中读取最多7个字符的行(根据需要进行调整以保持最长的性别字符串)

使用这种方法,只需对fscanf()进行一次调用,就可以在数组中的每个结构中使用4行输入。您应该在循环退出时检查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   : %sniq     : %dnweight : %dngender : %sn",
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

示例使用/输出

$ ./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()来进行转换,这并没有错,但对于大的输入文件,对fscanf()的1函数调用(与对fgets()的4个单独调用相比)加上对sscanf()strtol()的2个单独调用(加上用于处理行计数器或多缓冲区逻辑的附加逻辑)将开始相加。

仔细看看,如果你还有问题,请告诉我。

一些示例行。剩下的节目我交给你。

#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 */
}
....

最新更新