除了在读取数据时不要超出结构数组的末尾之外,您可能还需要在其他几个方面修改您对代码所采用的方法。
首先,除非有令人信服的理由将数据结构声明为全局变量,否则应将其范围限制为main(),并将结构数组作为参数传递给任何需要访问数据的函数.此外,在处理程序中的常量时(例如最大电话簿条目1000),最好定义一个常量(#define MAXE 1000)或最好使用enum 来定义常量,例如:
enum { MAXE = 1000 };
(匿名enum 可以。)
您还可以通过为您的结构创建typedef 来简化您的生活,这将使结构数组作为参数更容易传递。例如,您可以向您的结构(命名或匿名)声明typedef,如下所示:
typedef struct {
char Firstname[25];
char Lastname[25];
char street[20];
int street_no ;
char city[15];
int number;
char email[50];
} pb;
这将允许在main() 中进行简单声明,例如:
pb k[MAXE] = {{{0},{0},{0},0,{0},0,{0}}};
虽然不是必需的,但最好在声明所有变量(包括结构数组)时对其进行初始化。
虽然在这种情况下,使用fscanf 读取数据文件或使用面向行的 输入函数几乎没有区别,但您通常会发现使用@987654333 一次读取一行@ 或getline,然后使用sscanf 或简单的指针将行解析为组件,这将提供更灵活和健壮的输入例程。无论您是使用fscanf 阅读还是使用fgets 阅读并使用sscanf 解析总是检查fscanf 或sscanf 的返回以验证成功转换的数量。
使用面向行的输入函数从输入文件中读取每一行文本的好处是它可以消除fscanf的僵化@格式字符串从文件的实际读取中提取,并允许您在将行成功读入缓冲区后处理分离值。在您的情况下使用fgets 的示例可能是:
/* read addresses from input file up to a maximum of MAXE
* addresses. updates 'idx' pointer to hold the number of
* addreses read from file and returns number read
*/
size_t read_str (pb (*k)[], size_t *idx, FILE *fp)
{
char tmp[MAXL] = {0};
while (*idx < MAXE && fgets (tmp, MAXL, fp)) {
// printf ("read[%zu]\n", *idx);
if (sscanf (tmp, " %24[^,],%24[^,],%19[^,],%d,%14[^,],%d,%49[^\n]",
(*k)[*idx].Firstname, (*k)[*idx].Lastname,
(*k)[*idx].street, &(*k)[*idx].street_no,
(*k)[*idx].city, &(*k)[*idx].number, (*k)[*idx].email) != 7) {
fprintf (stderr, "read_str() error: parse of line[%zu] failed.\n",
*idx);
break;
}
(*idx)++;
}
return *idx;
}
注意还返回读取的地址条目数,让您可以衡量函数的成功/失败,并为您提供读取的条目数。读取的条目数 (idx) 也作为指向函数的指针传递,使调用函数中的条目数可用(此处为 main()),无论是否分配返回值。
除了这些初始问题之外,您还需要验证您所采取的每项操作,这些操作会对您的代码的持续运行产生影响。 (例如,所有文件打开、读取、写入等...)将这些部分放在一起并添加基本验证,并使用面向行的输入,您的任务的另一种方法可能如下所示:
#include <stdio.h>
/* constants for max input line and max entries */
enum { MAXL = 256, MAXE = 1000 };
typedef struct {
char Firstname[25];
char Lastname[25];
char street[20];
int street_no ;
char city[15];
int number;
char email[50];
} pb;
size_t read_str (pb (*k)[], size_t *idx, FILE *fp);
void print_str_fmt (pb *k, size_t idx);
int print_str (pb *k, size_t idx, FILE *fp);
int main (int argc, char **argv) {
if (argc < 3) { /* validate input/output filenames given as arguments */
fprintf (stderr, "error: insufficient input, usage: %s infile outfile\n",
argv[0]);
return 1;
}
pb k[MAXE] = {{{0},{0},{0},0,{0},0,{0}}}; /* initialize variables */
size_t index = 0;
FILE *ifp, *ofp;
if (!(ifp = fopen (argv[1], "r"))) { /* validate input file open */
fprintf (stderr, "error: file open failed '%s'\n", argv[1]);
return 1;
}
if (!(ofp = fopen (argv[2], "w"))) { /* validate output file open */
fprintf (stderr, "error: file open failed '%s'\n", argv[2]);
return 1;
}
if (!read_str (&k, &index, ifp)) { /* validate entries read */
fprintf (stderr, "error: read_str - no addresses read\n");
return 1;
}
fclose (ifp); /* close input file */
printf ("The addresses are:\n\n");
print_str_fmt (k, index);
if (print_str (k, index, ofp)) { /* validate entries written */
fprintf (stderr, "error: print_str - no addresses read\n");
return 1;
}
fclose (ofp); /* close output file */
return 0;
}
/* read addresses from input file up to a maximum of MAXE
* addresses. updates 'idx' pointer to hold the number of
* addreses read from file and returns number read
*/
size_t read_str (pb (*k)[], size_t *idx, FILE *fp)
{
char tmp[MAXL] = {0};
while (*idx < MAXE && fgets (tmp, MAXL, fp)) {
// printf ("read[%zu]\n", *idx);
if (sscanf (tmp, " %24[^,],%24[^,],%19[^,],%d,%14[^,],%d,%49[^\n]",
(*k)[*idx].Firstname, (*k)[*idx].Lastname,
(*k)[*idx].street, &(*k)[*idx].street_no,
(*k)[*idx].city, &(*k)[*idx].number, (*k)[*idx].email) != 7) {
fprintf (stderr, "read_str() error: parse of line[%zu] failed.\n",
*idx);
break;
}
(*idx)++;
}
return *idx;
}
/* formatted print of addressbook to stdout */
void print_str_fmt (pb *k, size_t idx)
{
size_t i;
for (i = 0; i < idx; i++)
printf (" %s %s\n %s No. %d\n %s, %d\n %s\n\n",
k[i].Firstname, k[i].Lastname, k[i].street, k[i].street_no,
k[i].city, k[i].number, k[i].email);
}
int print_str (pb *k, size_t idx, FILE *fp)
{
size_t i;
for (i = 0; i < idx; i++)
if (fprintf (fp, "%s,%s,%s,%d,%s,%d,%s\n",
k[i].Firstname, k[i].Lastname, k[i].street, k[i].street_no,
k[i].city, k[i].number, k[i].email) < 0)
return 1;
return 0;
}
编译
gcc -Wall -Wextra -O3 -o bin/readstructsscanf readstructsscanf.c
测试输入
$ cat ../dat/phonebook.txt
Ahmed,Mohamed,26 Elhoreya Street,15,Alexandria,4876321,ahmed@gmail.com
Sarah,Zaki,7 Smouha,36,Alexandria,3974542,sarah@hotmail.com
使用/输出
$ ./bin/readstructsscanf ../dat/phonebook.txt foo.txt
The addresses are:
Ahmed, Mohamed
26 Elhoreya Street No. 15
Alexandria, 4876321
ahmed@gmail.com
Sarah, Zaki
7 Smouha No. 36
Alexandria, 3974542
sarah@hotmail.com
确认输出文件
$ diff ../dat/phonebook.txt foo.txt
$
与 C 中的所有问题一样,通常有很多方法可以找到正确的解决方案。希望这会给您一些关于如何使您的代码更加灵活和健壮的想法。