我正在尝试为我的第二学期编程课做作业,我们必须从这样的文件中读取数据:
Fred 23 2.99
Lisa 31 6.99
Sue 27 4.45
Bobby 456 18.844
Ann 7 3.45
在恐惧中使用结构。我最终必须创建一个循环来读取所有数据,然后将其转换为二进制并将其写入文件,但这是我在遇到问题之前得到的:
struct data
{
char name[25];
int iNum;
float fNum;
};
int main(int argc, char *argv[])
{
struct data mov;
FILE *fp;
fp = fopen(argv[1], "r");
fread(&mov, sizeof(struct data), 1, fp);
printf(" name: %s\n int: %d\n float: %f\n", mov.name, mov.iNum, mov.fNum);
return 0;
}
我遇到的问题是,fread会将前25个字符读入数组而不是停留在第一个空格,因此它产生如下输出:
name: Fred 23 2.99
Lisa 31 6.99
Sue 27 4.4
int: 926031973
float: 0.000000
而不是期望的结果,这将更像是:
name: Fred
int: 23
float: 2.99000
从我读过的内容来看,我相信这就是fread的功能,我确信有更好的解决这个问题的方法,但是赋值需要我们在结构中使用fread和25个字符的数组。最好的方法是什么?
有没有办法阻止fread在空格中的结构中读取字符到数组?
答案:是的(但不能直接使用fread
,你需要一个缓冲区才能完成任务)
你使用fread
从输入文件解析格式化文本的要求当然是一个学术练习(一个很好的练习),但不是你通常会做的事情。为什么?通常,当您关注从文件中读取数据行时,可以使用面向行的输入函数,例如fgets()
或POSIX getline()
。
你也可以使用面向字符的输入函数fgetc()
,只需从文件中读取,缓冲输入,直到找到'\n'
,用缓冲区做你需要的并重复。你最后一个正常的选择(但由于它的脆弱而不鼓励)是使用像fscanf()
这样的格式化输入函数 - 但是它的误用占了这个网站上相当大比例的问题。
但是,如果对于学术挑战,你必须使用fread()
,然后如评论中所提到的,你将要将整个文件读入已分配的缓冲区,然后解析该缓冲区,就好像你正在读取它一行一样。来自实际文件的时间。如果用sscanf
读取,将使用fgets()
,它可以在这里用来读取填充fread()
的缓冲区。唯一的技巧是跟踪缓冲区中的位置以开始每次读取 - 并知道停止的位置。
有了这个大纲,你如何使用fread()
将整个文件读入缓冲区?首先需要获取文件长度以了解要分配多少空间。您可以通过调用stat
或fstat
并使用包含filesize的填充st_size
的struct stat
成员来执行此操作,或者使用fseek
移动到文件的末尾并使用ftell()
从头开始报告偏移量。
采用打开的FILE*
指针的简单函数,保存当前位置,将文件位置指示器移动到末尾,使用ftell()
获取文件大小,然后将文件位置指示器恢复到其原始位置可以是:
/* get the file size of file pointed to by fp */
long getfilesize (FILE *fp)
{
fpos_t currentpos;
long bytes;
if (fgetpos (fp, ¤tpos) == -1) { /* save current file position */
perror ("fgetpos");
return -1;
}
if (fseek (fp, 0, SEEK_END) == -1) { /* fseek end of file */
perror ("fseek-SEEK_END");
return -1;
}
if ((bytes = ftell (fp)) == -1) { /* get number of bytes */
perror ("ftell-bytes");
return -1;
}
if (fsetpos (fp, ¤tpos) == -1) { /* restore file positon */
perror ("fseek-SEEK_SET");
return -1;
}
return bytes; /* return number of bytes in file */
}
(注意:上面的每一步都经过验证,错误时会返回-1
,否则会在成功时返回文件大小。请确保验证程序中的每一步,并始终从函数中提供有意义的返回,表明成功/失败。 )
有了文件大小,在调用fread()
之前你需要做的就是分配一个足够大的内存块来保存文件的内容,并将该内存块的起始地址分配给一个可以与之一起使用的指针。 fread()
。例如:
long bytes; /* size of file in bytes */
char *filebuf, *p; /* buffer for file and pointer to it */
...
if ((bytes = getfilesize (fp)) == -1) /* get file size in bytes */
return 1;
if (!(filebuf = malloc (bytes + 1))) { /* allocate/validate storage */
perror ("malloc-filebuf");
return 1;
}
(我们稍后将讨论+ 1
)
现在,您的文件有足够的存储空间,并且存储器的地址被分配给指针filebuf
,您可以调用fread()
并将整个文件读入该内存块:
/* read entire file into allocated memory */
if (fread (filebuf, sizeof *filebuf, bytes, fp) != (size_t)bytes) {
perror ("fread-filebuf");
return 1;
}
现在您的整个文件存储在filebuf
指向的内存块中。如何将数据逐行解析到结构中(或实际上是一个struct数组,以便每个记录存储在一个单独的结构中)?这实际上非常简单。您只需从缓冲区中读取并跟踪用于读取的字符数,直到找到'\n'
,将该行中的信息解析为数组的struct元素,将偏移量添加到指针以准备下一次读取并增加struct数组的索引以考虑刚填充的结构。您实际上正在使用sscanf
就像使用fgets()
从文件中读取行一样,但是您手动跟踪缓冲区内的偏移量,以便下次调用sscanf
,例如
#define NDATA 16 /* if you need a constant, #define one (or more) */
#define MAXC 25
struct data { /* your struct with fixed array of 25-bytes for name */
char name[MAXC];
int iNum;
float fNum;
};
...
struct data arr[NDATA] = {{ .name = "" }}; /* array of struct data */
int used; /* no. chars used by sscanf */
size_t ndx = 0, offset = 0; /* array index, and pointer offset */
...
filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */
p = filebuf; /* set pointer to filebuf */
while (ndx < NDATA && /* while space in array */
sscanf (p + offset, "%24s %d %f%n", /* parse values into struct */
arr[ndx].name, &arr[ndx].iNum, &arr[ndx].fNum, &used) == 3) {
offset += used; /* update offset with used chars */
ndx++; /* increment array index */
}
这就是它。你可以使用free (filebuf);
完成它并且所有的值现在都存储在struct arr
的数组中。
上面有一个重要的代码行,我们还没有谈到 - 我告诉过你我们稍后会讨论它。它也是你通常不会做的事情,但它强制要求你使用sscanf
处理缓冲区作为文本,sscanf
是一个通常用于处理字符串的函数。你如何确保filebuf
知道停止阅读并且不继续阅读超出 filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */
的范围?
+ 1
这就是分配大小的sscanf
发挥作用的地方。您通常不会终止缓冲区 - 没有必要。但是,如果要使用通常用于处理字符串的函数来处理缓冲区的内容,那么就可以了。否则,'\n'
将继续读取缓冲区中的最终0
进入内存,您无法有效访问,直到它在堆中的某处找到随机的#include <stdio.h>
#include <stdlib.h>
#define NDATA 16 /* if you need a constant, #define one (or more) */
#define MAXC 25
struct data { /* your struct with fixed array of 25-bytes for name */
char name[MAXC];
int iNum;
float fNum;
};
long getfilesize (FILE *fp); /* function prototype for funciton below */
int main (int argc, char **argv) {
struct data arr[NDATA] = {{ .name = "" }}; /* array of struct data */
int used; /* no. chars used by sscanf */
long bytes; /* size of file in bytes */
char *filebuf, *p; /* buffer for file and pointer to it */
size_t ndx = 0, offset = 0; /* array index, and pointer offset */
FILE *fp; /* file pointer */
if (argc < 2) { /* validate at least 1-arg given for filename */
fprintf (stderr, "error: insufficient arguments\n"
"usage: %s <filename>\n", argv[0]);
return 1;
}
/* open file / validate file open for reading */
if (!(fp = fopen (argv[1], "rb"))) {
perror ("file open failed");
return 1;
}
if ((bytes = getfilesize (fp)) == -1) /* get file size in bytes */
return 1;
if (!(filebuf = malloc (bytes + 1))) { /* allocate/validate storage */
perror ("malloc-filebuf");
return 1;
}
/* read entire file into allocated memory */
if (fread (filebuf, sizeof *filebuf, bytes, fp) != (size_t)bytes) {
perror ("fread-filebuf");
return 1;
}
fclose (fp); /* close file, read done */
filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */
p = filebuf; /* set pointer to filebuf */
while (ndx < NDATA && /* while space in array */
sscanf (p + offset, "%24s %d %f%n", /* parse values into struct */
arr[ndx].name, &arr[ndx].iNum, &arr[ndx].fNum, &used) == 3) {
offset += used; /* update offset with used chars */
ndx++; /* increment array index */
}
free (filebuf); /* free allocated memory, values stored in array */
for (size_t i = 0; i < ndx; i++) /* output stored values */
printf ("%-24s %4d %7.3f\n", arr[i].name, arr[i].iNum, arr[i].fNum);
return 0;
}
/* get the file size of file pointed to by fp */
long getfilesize (FILE *fp)
{
fpos_t currentpos;
long bytes;
if (fgetpos (fp, ¤tpos) == -1) { /* save current file position */
perror ("fgetpos");
return -1;
}
if (fseek (fp, 0, SEEK_END) == -1) { /* fseek end of file */
perror ("fseek-SEEK_END");
return -1;
}
if ((bytes = ftell (fp)) == -1) { /* get number of bytes */
perror ("ftell-bytes");
return -1;
}
if (fsetpos (fp, ¤tpos) == -1) { /* restore file positon */
perror ("fseek-SEEK_SET");
return -1;
}
return bytes; /* return number of bytes in file */
}
。 (如果碰巧满足格式字符串,可能会用垃圾填充额外的附加结构)
完全放在一起,你可以这样做:
fread()
(注意:大约1/2行代码专门用于验证每一步。这是正常的,也是至关重要的,以确保在发生故障后盲目地继续在代码中继续前进,从而阻止您处理有效数据,从而不会调用未定义行为。)
示例使用/输出
有了这个,程序就完成了,你应该能够解析$ ./bin/freadinumfnum dat/inumfnum.txt
Fred 23 2.990
Lisa 31 6.990
Sue 27 4.450
Bobby 456 18.844
Ann 7 3.450
填充的缓冲区中的数据,该缓冲区在空格后的所有适当时间都已停止。
valgrind
内存使用/错误检查
在您编写的任何动态分配内存的代码中,您对分配的任何内存块都有2个职责:(1)始终保留指向内存块起始地址的指针,因此,(2)当它为no时可以释放它需要更久。
您必须使用内存错误检查程序,以确保您不会尝试访问内存或写入超出/超出已分配块的范围,尝试读取或基于未初始化值的条件跳转,最后,确认你释放了你分配的所有内存。
对于Linux,$ valgrind ./bin/freadinumfnum dat/inumfnum.txt
==5642== Memcheck, a memory error detector
==5642== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==5642== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==5642== Command: ./bin/freadinumfnum dat/inumfnum.txt
==5642==
Fred 23 2.990
Lisa 31 6.990
Sue 27 4.450
Bobby 456 18.844
Ann 7 3.450
==5642==
==5642== HEAP SUMMARY:
==5642== in use at exit: 0 bytes in 0 blocks
==5642== total heap usage: 2 allocs, 2 frees, 623 bytes allocated
==5642==
==5642== All heap blocks were freed -- no leaks are possible
==5642==
==5642== For counts of detected and suppressed errors, rerun with: -v
==5642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。
qazxswpoi
始终确认已释放已分配的所有内存并且没有内存错误。
仔细看看,如果您有其他问题,请告诉我。