c - 如何读取外部文件中的前 n 个结构变量 - C
问题描述
我正在尝试运行一个程序,其中用户输入要阅读的员工数量,程序显示前 n 个员工。
#include <stdio.h>
struct employee {
int id;
char name[50];
float salary;
}
struct employee e;
adddata(){
FILE *fp;
fp=fopen("record.txt","a");
printf(" Employee Name : ");
scanf("%s",e.name);
printf(" ID :");
scanf("%d",&e.id);
printf(" Salary :");
scanf("%f",e.salary);
fseek(fp,0,SEEK_END);
fwrite(&e,sizeof(e),1,fp);
fclose(fp);
}
get(){
int n;
printf("amount to display: ");
scanf("%d", &n);
FILE *fp;
fp = fopen("record.txt", "r");
//code to get and display first n employees
//e.g. n = 3; displays employee 1, 2, 3
}
int main(){
int opt;
printf("1 - add data \n 2 - get data");
scanf("%d", &opt);
switch(opt){
case 1 : adddata();
break;
case 2 : get();
}
}
外部文本的内容:
id name salary
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
您如何阅读它(不是逐行而是按结构变量)?
解决方案
鉴于您添加的内容表明您正在以二进制模式将结构写入文件,这会极大地改变原始问题的特征。它不是从原始问题中显示的文本文件中读取和解析值之一,而是从二进制文件中简单地读取给定数量的结构,所有结构的大小都是固定的。
由于最初的问题表明阅读和解析文本,这将提供一个机会,在回答您的问题的同时完成这两项工作。因此,让我们读取并解析您提供的原始文本,然后编写一个包含结构的二进制文件,然后从该文件中读取并显示所请求员工数量的输出。(将您喜欢的任何标题信息添加到输出中留给您)
C 程序员的第 6 条诫命 - “你要被警告......”
首先,您的adddata()
函数非常脆弱,并且是在无辜的错误击键上调用未定义行为的秘诀。如果不检查 return,您将无法正确使用任何输入函数(与此相关的任何函数)。以免你们违反Henry Spencer 的 C 程序员 10 条诫命中的第 6 条诫命。
避免幻数和硬编码文件名
此外,不要在代码中使用幻数或硬编码文件名。如果您需要一个常量,#define
一个(或多个)并将要读取的文件名作为参数传递给main()
或将其作为输入。例如:
#define MAXC 512 /* max characters for read buffer */
#define MAXN 50 /* max characters in name */
struct employee {
int id;
char name[MAXN];
float salary;
};
和
int main (int argc, char **argv) {
int n, nemp; /* n for user input, nemp for number read from file */
char ofn[MAXC], /* buffer to hold output file name for writing bin file */
*p = ofn; /* pointer to output file name buffer to swap extensions */
FILE *fp; /* file pointer */
struct employee e[MAXN] = {{ .id = 0 }}; /* array of MAXN employee */
if (argc < 2 ) { /* validate 1 argument given for filename */
fprintf (stderr, "error: insufficient input,\n"
"usage: %s filename\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
原始问题文本文件读取
继续处理您的数据。对于以文本形式发布的原始数据,如果您面临读取数据并将其解析为. 的问题struct employee
,您只需忽略所有不以isdigit()
.
然后要提取信息,您基本上会从头到尾工作,读取id
并保存所占用的字符数。然后,您将从行尾开始备份,直到您的第一个空白字符(使用strrchr()
),然后将其转换salary
为float. All that is left is scanning forward from the end of
iduntil the first non-whitespace character is found, and then backing up from the space before
工资until the first non-whitespace character is found. What is left between is the
名称which you can copy with
memcpy()`(不要忘记 *nul-terminate)。
将它们放在一个readtext()
函数中,该函数接受一个打开的FILE*
流指针,一个指向struct employee
要填充的数组的指针(假定它足够大以保存从文件中读取的数据——您应该通过将最大大小作为另一个传递来添加额外的验证参数),最后是要读取的员工人数。你可以做类似的事情:
/** read text file as shown in question into array of struct */
int readtext (FILE *fp, struct employee *emp, int n)
{
int nemp = 0; /* number of employees read */
char buf[MAXC]; /* buffer to hold each line (don't skimp on size) */
struct employee tmp = { .id = 0 }; /* temp struct to fill */
while (nemp < n && fgets (buf, MAXC, fp)) { /* read each line */
char *p, *ep; /* pointer & end-pointer to use in parsing */
int offset; /* offset (no. of chars for id) */
if (!isdigit (*buf)) /* if 1st char not digit, get next line */
continue;
/* if no successful convertion to int for id, get next line */
if (sscanf (buf, "%d%n", &tmp.id, &offset) != 1)
continue;
/* if space before salary or no conversion to float, get next line */
if (!(ep = strrchr (buf, ' ')) || sscanf (ep, "%f", &tmp.salary) != 1)
continue;
p = buf + offset; /* skip whitespace until start of name */
while (isspace (*p))
p++;
do /* backup until last char in name found */
ep--;
while (ep > p && isspace(*ep));
ep++; /* advance 1 past last char in name */
memcpy (tmp.name, p, ep - p); /* copy name to tmp.name */
tmp.name[ep - p] = 0; /* nul-terminate tmp.name */
emp[nemp++] = tmp; /* assign tmp to array, increment nemp */
}
return nemp; /* return number of employees read into array */
}
你会从 main 调用它来填充你的结构数组e
:
...
fputs ("amount to display: ", stdout); /* prompt */
fflush (stdout); /* optional (but recommended) */
if (scanf ("%d", &n) != 1) { /* validate integer input */
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
if ((nemp = readtext(fp, e, n)) != n) /* if less than n read, warn */
fprintf (stderr, "\nwarning: less than %d employees found.\n\n", n);
fclose (fp); /* close file */
...
您可以通过简单地输出数据来检查您是否拥有所需的信息:
putchar ('\n'); /* add newline before output */
for (int i = 0; i < nemp; i++) /* output employees read from file */
printf ("%3d %-20s %.2f\n", e[i].id, e[i].name, e[i].salary);
其余的处理将数据写入二进制文件,然后可以读取,因为更新的问题表明这是真正的问题。从二进制文件写入和读取都是对fwrite
和的简单调用fread
。你可以为每个函数创建函数,但这留给你。
编写二进制文件以与更新的问题一起使用
由于我们是根据从文本文件中读取的信息写入二进制数据,因此需要做的就是以新的文件名写出信息。".txt"
简单地将文件扩展名从to交换".bin"
是区分文件的简单方法。如果输入文件没有扩展名,则只需将".bin"
扩展名添加到末尾。(您还应该添加额外的验证文件名是否适合 - 提供的空间ofn
- 这也留给您)
对于此示例,我们只需找到最后一个'.'
并将右侧的所有内容视为文件扩展名。这可以根据需要进行扩展。一个简单的实现是:
/* form output filename by changing extension to ".bin" */
if ((p = strrchr (argv[1], '.')) == NULL) /* if no last '.' in argv[1] */
strcpy (ofn, argv[1]); /* copy all to ofn */
else { /* otherwise */
memcpy (ofn, argv[1], p - argv[1]); /* copy up to last '.' to ofn */
ofn[p - argv[1]] = 0; /* nul-terminate */
}
strcat (ofn, ".bin"); /* concatenate .bin extension */
现在形成了输出文件名,只需将结构写出,然后关闭文件流(注意: 始终验证 close-after-write以捕获刷新文件流时不会被您的验证捕获的任何错误fwrite
)
/* open/validate output file open for writing */
if (!(fp = fopen (ofn, "wb"))) {
perror ("fopen-idnamesal.bin");
return 1;
}
/* write array to output file in binary */
if (fwrite (e, sizeof *e, nemp, fp) != (size_t)nemp) {
fputs ("error: short write to binary.\n", stderr);
return 1;
}
if (fclose (fp) == EOF) { /* always validate close-after-write */
perror ("fclose-after-write");
return 1;
}
struct employee
从二进制文件中读取
现在我们有一个包含您的结构的二进制文件,我们可以使用它来回答您更新的问题。虽然您可以使用结构数组来读取数据,但由于您不知道将读取多少数据struct employee
,因此传统上您会在其中分配足够大小的内存块来保存struct employee
用户输入的数量使用malloc()
. 这使您能够在运行时将存储大小调整为保存数据所需的确切数量。做起来很简单,但要遵循第 6 条诫命,例如
/* allocate block of storage to hold data read from binary file */
struct employee *empfrombin = malloc (nemp * sizeof *empfrombin);
if (!empfrombin) {
perror ("malloc-empfrombin");
return 1;
}
使用一块足够大的内存来保存struct employee
用户输入的数量,只需将二进制文件中的结构数量读取到新块中,只需一次调用fread
并关闭文件(无需验证关闭后-读)。然后,您可以以与上述相同的方式输出信息:
/* read/validate struct data from binary file into newly allocated blockm */
if (fread (empfrombin, sizeof *empfrombin, nemp, fp) != (size_t)nemp) {
fprintf (stderr, "error: file read failed - '%s'.\n", ofn);
return 1;
}
fclose (fp); /* close file */
/* output employees from newly allocated/filled block of memory */
fprintf (stdout, "\nemployees read from binary file '%s'\n\n", ofn);
for (int i = 0; i < nemp; i++)
printf ("%3d %-20s %.2f\n",
empfrombin[i].id, empfrombin[i].name, empfrombin[i].salary);
最后,在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有两个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在它被释放时被释放不再需要。所以只需free()
你的内存块,你就完成了:
free (empfrombin); /* don't forget to free what you have allocated */
}
总而言之,涵盖您的原始和更新问题的整个示例将是:
#include <stdio.h>
#include <stdlib.h> /* for malloc/free */
#include <string.h> /* for strrchr, memcpy */
#include <ctype.h> /* for isspace, isdigit */
#define MAXC 512 /* max characters for read buffer */
#define MAXN 50 /* max characters in name */
struct employee {
int id;
char name[MAXN];
float salary;
};
/** read text file as shown in question into array of struct */
int readtext (FILE *fp, struct employee *emp, int n)
{
int nemp = 0; /* number of employees read */
char buf[MAXC]; /* buffer to hold each line (don't skimp on size) */
struct employee tmp = { .id = 0 }; /* temp struct to fill */
while (nemp < n && fgets (buf, MAXC, fp)) { /* read each line */
char *p, *ep; /* pointer & end-pointer to use in parsing */
int offset; /* offset (no. of chars for id) */
if (!isdigit (*buf)) /* if 1st char not digit, get next line */
continue;
/* if no successful convertion to int for id, get next line */
if (sscanf (buf, "%d%n", &tmp.id, &offset) != 1)
continue;
/* if space before salary or no conversion to float, get next line */
if (!(ep = strrchr (buf, ' ')) || sscanf (ep, "%f", &tmp.salary) != 1)
continue;
p = buf + offset; /* skip whitespace until start of name */
while (isspace (*p))
p++;
do /* backup until last char in name found */
ep--;
while (ep > p && isspace(*ep));
ep++; /* advance 1 past last char in name */
memcpy (tmp.name, p, ep - p); /* copy name to tmp.name */
tmp.name[ep - p] = 0; /* nul-terminate tmp.name */
emp[nemp++] = tmp; /* assign tmp to array, increment nemp */
}
return nemp; /* return number of employees read into array */
}
int main (int argc, char **argv) {
int n, nemp; /* n for user input, nemp for number read from file */
char ofn[MAXC], /* buffer to hold output file name for writing bin file */
*p = ofn; /* pointer to output file name buffer to swap extensions */
FILE *fp; /* file pointer */
struct employee e[MAXN] = {{ .id = 0 }}; /* array of MAXN employee */
if (argc < 2 ) { /* validate 1 argument given for filename */
fprintf (stderr, "error: insufficient input,\n"
"usage: %s filename\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "r"))) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
fputs ("amount to display: ", stdout); /* prompt */
fflush (stdout); /* optional (but recommended) */
if (scanf ("%d", &n) != 1) { /* validate integer input */
fputs ("error: invalid integer input.\n", stderr);
return 1;
}
if ((nemp = readtext(fp, e, n)) != n) /* if less than n read, warn */
fprintf (stderr, "\nwarning: less than %d employees found.\n\n", n);
fclose (fp); /* close file */
putchar ('\n'); /* add newline before output */
for (int i = 0; i < nemp; i++) /* output employees read from file */
printf ("%3d %-20s %.2f\n", e[i].id, e[i].name, e[i].salary);
/* form output filename by changing extension to ".bin" */
if ((p = strrchr (argv[1], '.')) == NULL) /* if no last '.' in argv[1] */
strcpy (ofn, argv[1]); /* copy all to ofn */
else { /* otherwise */
memcpy (ofn, argv[1], p - argv[1]); /* copy up to last '.' to ofn */
ofn[p - argv[1]] = 0; /* nul-terminate */
}
strcat (ofn, ".bin"); /* concatenate .bin extension */
/* open/validate output file open for writing */
if (!(fp = fopen (ofn, "wb"))) {
perror ("fopen-idnamesal.bin");
return 1;
}
/* write array to output file in binary */
if (fwrite (e, sizeof *e, nemp, fp) != (size_t)nemp) {
fputs ("error: short write to binary.\n", stderr);
return 1;
}
if (fclose (fp) == EOF) { /* always validate close-after-write */
perror ("fclose-after-write");
return 1;
}
/* allocate block of storage to hold data read from binary file */
struct employee *empfrombin = malloc (nemp * sizeof *empfrombin);
if (!empfrombin) {
perror ("malloc-empfrombin");
return 1;
}
if (!(fp = fopen (ofn, "rb"))) { /* open/validate binary file */
perror ("fopen-ofn-rb");
return 1;
}
/* read/validate struct data from binary file into newly allocated blockm */
if (fread (empfrombin, sizeof *empfrombin, nemp, fp) != (size_t)nemp) {
fprintf (stderr, "error: file read failed - '%s'.\n", ofn);
return 1;
}
fclose (fp); /* close file */
/* output employees from newly allocated/filled block of memory */
fprintf (stdout, "\nemployees read from binary file '%s'\n\n", ofn);
for (int i = 0; i < nemp; i++)
printf ("%3d %-20s %.2f\n",
empfrombin[i].id, empfrombin[i].name, empfrombin[i].salary);
free (empfrombin); /* don't forget to free what you have allocated */
}
(注意:尝试添加上面段落中提到的其他验证,以确保您从文本文件中读取的数据不会超过可用数组存储的数据)
使用的原始文本输入文件
$ cat dat/idnamesal.txt
id name salary
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
生成的二进制文件
$ hexdump -C dat/idnamesal.bin
00000000 01 00 00 00 4a 6f 68 6e 20 44 6f 65 00 00 00 00 |....John Doe....|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000030 00 00 00 00 00 00 00 00 00 40 1c 46 02 00 00 00 |.........@.F....|
00000040 4b 79 6c 65 20 53 6d 69 74 68 00 00 00 00 00 00 |Kyle Smith......|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000070 00 00 00 00 00 60 6a 46 03 00 00 00 52 6f 6e 20 |.....`jF....Ron |
00000080 41 64 61 6d 73 00 00 00 00 00 00 00 00 00 00 00 |Adams...........|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000b0 00 40 9c 46 04 00 00 00 41 6c 69 63 65 20 57 69 |.@.F....Alice Wi|
000000c0 6c 64 65 00 00 00 00 00 00 00 00 00 00 00 00 00 |lde.............|
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 10 a4 46 |...............F|
000000f0 05 00 00 00 5a 6f 65 20 4a 6f 72 64 61 6e 00 00 |....Zoe Jordan..|
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000120 00 00 00 00 00 00 00 00 00 a0 8c 46 |...........F|
0000012c
示例使用/输出
从显示的文本格式读取,写入二进制文件struct employee
并从二进制文件读取并显示所有5
员工。
$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 5
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
employees read from binary file 'dat/idnamesal.bin'
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
阅读少于全部:
$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 1
1 John Doe 10000.00
employees read from binary file 'dat/idnamesal.bin'
1 John Doe 10000.00
尝试读取比文件中更多的员工:
$ ./bin/readidnamesal dat/idnamesal.txt
amount to display: 100
warning: less than 100 employees found.
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
employees read from binary file 'dat/idnamesal.bin'
1 John Doe 10000.00
2 Kyle Smith 15000.00
3 Ron Adams 20000.00
4 Alice Wilde 21000.00
5 Zoe Jordan 18000.00
如果您还有其他问题,请仔细查看并告诉我。虽然您的问题有所改变,但来自原始问题的文本操作和来自更新问题的二进制操作都是“面包和黄油”操作,您将一次又一次地执行,因此您不妨与两者交朋友。
推荐阅读
- c# - C# WPF 实时图表 - 通用地创建图表
- groovy - Groovy 2.5 是否支持 Java 13?
- c# - 防止用户在表单中输入 URL
- docker - Docker Content Trust 中有两个根密钥吗?
- javascript - 在更改滑块时将滑块的值从网页返回到程序
- oracle - 在 Oracle 12c 中修改后恢复原始过程
- angular - 在 fullcalendar 中为每个 EventInput 设置 Id
- arduino - 如何在 Arduino-Ide 中使用 Keypad_MC17 库
- apache-kafka - Kafka regex-topics / 听多个主题的实际限制是什么
- computer-vision - Pytorch 加载保存的重量但不工作