如果您的输入文件如图所示,那么一个可靠的方法是使用fgets() 将输入文件的每一行读入一个足够大的缓冲区(字符数组)(就像您处理所有输入一样),然后解析所需的使用sscanf() 从缓冲区获取信息。无论您是从文件中读取记录还是从命令行获取用户输入,这种方法都能很好地为您服务。在这种情况下,它还避免了必须将输入与正确的结构成员匹配,就像使用 strtok() 循环时需要做的那样。
(由于字段数量有限,您可以只写出对strtok() 的每次调用并一次处理一个字符串以匹配正确的结构成员。您可以使用相同的解析缓冲区一对指针或交替调用 strcspn() 和 strspn())
在查看读取每一行并使用sscanf() 拆分的方法之前,让我们避免在代码中使用MagicNumbers 来调整数组的大小,而是声明用于该目的的常量。这样,如果您需要调整大小,您可以在一个方便的位置进行更改,而不必在代码中选择每个声明和循环限制。您可以使用#define 语句或使用全局enum 定义一个或多个常量,例如
/* if you need constants, #define them, or use a global enum */
enum { C20 = 20, C30 = 30, C100 = 100, MAXC = 1024 };
(选择有意义的名称,此处附加了 'C' 以提供一种简单的方法来查看常量在数组声明中被替换的位置,同时仍然接近您的原始名称)
从文件读取时,您应该能够确定最大行长度。对于一般用途,通常使用 1K 缓冲区(1024 字节)或 2K 缓冲区。如果您正在为内存有限的嵌入式系统编写代码,请根据需要减小总体大小。这里使用了一个简单的 1K 缓冲区:
int main (int argc, char **argv) {
char buf[MAXC]; /* buffer to hold each line read from file */
int n = 0;
employee e[C100];
...
现在开始你的读取循环,确保你只在你的e[]数组中剩余可用空间时循环,例如
/* while array not full and line read from file */
while (n < C100 && fgets (buf, MAXC, fp)) {
...
buf 包含文件中的一整行,现在调用 sscanf() 以解析缓冲区中所需的值。注意:"day-mo-year" 被读取为单个字符串,然后拆分为 birthdate 的整数值,以简化为 sscanf() 格式字符串:
char dobtmp[C30] = ""; /* temp buffer to hold "day-mo-year" */
/* parse needed info fron buf, with field-width to protect array bounds
* VALIDATE return -- all conversions successful
*/
if (sscanf (buf, "%d, %19[^,], %19[^,],%f, %29[^,],"
" %99[^,], %19[^,], %29[^\n]",
&e[n].id, e[n].first_name, e[n].last_name, &e[n].salary,
dobtmp, e[n].address, e[n].phone_number,
e[n].email) == 8) {
...
(注意:您必须使用 field-width 修饰符来保护.first_name、.last_name 等的数组边界。您正在读取的任何位置防止缓冲区溢出的字符数组)
此外,当您查看格式字符串时,请确保您了解在 %[...] 转换说明符之前包含一个空格的目的以及数据文件中的哪些潜在变化可以防止...
您必须验证来自sscanf() 的返回,以确保您指定的每次转化——实际上都发生了。上面的sscanf() 位于if() 语句中,如果成功通过所有验证,代码只会增加员工人数。
在上面对sscanf() 的调用之后,dobtmp 保存了"day-mo-year" 字符串,该字符串需要进一步拆分以获得birthdate 的各个整数值。一个简单的函数可以提供帮助:
/* returns 1 if dob filled, 0 otherwise */
int splitdob (dob *d, const char *s)
{
return sscanf (s, "%d-%d-%d", &d->day, &d->month, &d->year) == 3;
}
所以剩下的就是调用splitdob() 并验证返回,如果它通过了,那么,只有这样,才增加员工计数器(为方便起见,上面重命名为n)。完整的读取循环和将值分离到您的结构中将是:
/* while array not full and line read from file */
while (n < C100 && fgets (buf, MAXC, fp)) {
char dobtmp[C30] = ""; /* temp buffer to hold "day-mo-year" */
/* parse needed info fron buf, with field-width to protect array bounds
* VALIDATE return -- all conversions successful
*/
if (sscanf (buf, "%d, %19[^,], %19[^,],%f, %29[^,],"
" %99[^,], %19[^,], %29[^\n]",
&e[n].id, e[n].first_name, e[n].last_name, &e[n].salary,
dobtmp, e[n].address, e[n].phone_number,
e[n].email) == 8) {
/* separate dobtmp into birthdate, store in employee */
if (splitdob (&e[n].birthdate, dobtmp)) {
n++; /* update count only after ALL validations passed */
}
}
}
在员工计数器递增之前通过所有验证,您可以确保每个员工中的所有数据都是有效的。 (您验证程序继续运行所依赖的代码中的每一步,并处理出现的任何错误)
处理您的示例日期的简短、完整的示例程序可能如下所示:
#include <stdio.h>
/* if you need constants, #define them, or use a global enum */
enum { C20 = 20, C30 = 30, C100 = 100, MAXC = 1024 };
typedef struct {
int day;
int month;
int year;
} dob;
typedef struct {
int id;
char first_name[C20]; /* size with constants, not MagicNumbers */
char last_name[C20];
float salary;
dob birthdate;
char address[C100];
char phone_number[C20];
char email[C30];
} employee;
/* returns 1 if dob filled, 0 otherwise */
int splitdob (dob *d, const char *s)
{
return sscanf (s, "%d-%d-%d", &d->day, &d->month, &d->year) == 3;
}
/* function to output single employee data */
void prnemployee (employee *e)
{
char nametmp[2 * C20], /* temp buffers for combined name and dob */
dobtmp[C20];
/* join first last names and birthdate in day/mo/year format */
sprintf (nametmp, "%s %s", e->first_name, e->last_name);
sprintf (dobtmp, "%02d/%02d/%04d", e->birthdate.day, e->birthdate.month,
e->birthdate.year);
/* output employee */
printf ("\n%s\n id : %d\n salary : %.2f\n dob : %s\n"
" addr : %s\n phone : %s\n email : %s\n",
nametmp, e->id, e->salary, dobtmp, e->address,
e->phone_number, e->email);
}
int main (int argc, char **argv) {
char buf[MAXC]; /* buffer to hold each line read from file */
int n = 0;
employee e[C100];
/* 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 array not full and line read from file */
while (n < C100 && fgets (buf, MAXC, fp)) {
char dobtmp[C30] = ""; /* temp buffer to hold "day-mo-year" */
/* parse needed info fron buf, with field-width to protect array bounds
* VALIDATE return -- all conversions successful
*/
if (sscanf (buf, "%d, %19[^,], %19[^,],%f, %29[^,],"
" %99[^,], %19[^,], %29[^\n]",
&e[n].id, e[n].first_name, e[n].last_name, &e[n].salary,
dobtmp, e[n].address, e[n].phone_number,
e[n].email) == 8) {
/* separate dobtmp into birthdate, store in employee */
if (splitdob (&e[n].birthdate, dobtmp)) {
n++; /* update count only after ALL validations passed */
}
}
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
printf ("\n%d employees:\n", n); /* output results */
for (int i = 0; i < n; i++) {
prnemployee (&e[i]);
}
}
使用/输出示例
使用文件dat/empfile.txt 中的示例数据,您将拥有:
$ ./bin/emp-nested-dob dat/empfile.txt
5 employees:
Steven1 Thomas
id : 1
salary : 2001.00
dob : 10/06/1995
addr : 1 Elhoreya Street
phone : 01234567891
email : 1sthomas@gmail.com
Steven2 Thomas
id : 2
salary : 2002.00
dob : 10/06/1995
addr : 2 Elhoreya Street
phone : 01234567892
email : 2sthomas@gmail.com
Steven3 Thomas
id : 3
salary : 2003.00
dob : 10/06/1995
addr : 3 Elhoreya Street
phone : 01234567893
email : 3sthomas@gmail.com
Steven4 Thomas
id : 4
salary : 2004.00
dob : 10/06/1995
addr : 4 Elhoreya Street
phone : 01234567894
email : 4sthomas@gmail.com
Steven5 Thomas
id : 5
salary : 2005.00
dob : 10/06/1995
addr : 5 Elhoreya Street
phone : 01234567895
email : 5sthomas@gmail.com
检查一下,如果您还有其他问题,请告诉我。