继续上面的评论,当您在数据文件中有一组固定的重复行需要读入struct 时,这是您应该考虑scanf()/fscanf() 而不是推荐的唯一例外之一fgets()/sscanf() 每行。
为什么?
scanf() 是一个格式化输入函数(相比之下fgets() 是一个面向行的输入函数)。如果您有跨多行的格式化输入,scanf()/fscanf() 忽略 whitespace('\n' 字符是 whitespace)并允许您将多行作为单个输入使用(使用精心制作的格式字符串)
当使用scanf()/fscanf()将数据读入字符串(或数组)时,你必须使用field-width修饰符来限制读入数组的值的数量,以避免写入超出结尾如果输入超出您的数组边界,您的数组会调用 Undefined Behavior。这适用于您使用scanf()/fscanf()/sscanf()(整个家庭)的任何时候。不使用 field-width 修饰符 来读取数组数据并不比使用 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]"
总而言之,将:
- 使用任何前导空格(
'\n' 来自先前读取的 stdin 或 gender 用于数组中的前一个结构);
- 使用
%127[^\n] 将最多127 个字符的行读入name 成员;
- 将包含第一个整数值的行用
%d 读入iq(使用前导空格);
- 用
%d(同上)将包含第二个整数值的行读入weight;
-
' ' 消耗weight 读取后留下的'\n';最后
- 使用
%7[^\n] 将最多 7 个字符的行读入 gender 成员(根据需要调整以保留最长的性别字符串)
使用这种方法,您只需调用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 : %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
使用/输出示例
$ ./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() 的单独调用加上对sscanf() 或strtol() 的2 次单独调用以及用于处理行计数器或多缓冲区逻辑的额外逻辑将开始累加。
查看一下,如果您还有其他问题,请告诉我。