对于我的任务,我需要使用fread/fwrite。我写的
#include <stdio.h>
#include <string.h>
struct rec{
int account;
char name[100];
double balance;
};
int main()
{
struct rec rec1;
int c;
FILE *fptr;
fptr = fopen("clients.txt", "r");
if (fptr == NULL)
printf("File could not be opened, exiting program.\n");
else
{
printf("%-10s%-13s%s\n", "Account", "Name", "Balance");
while (!feof(fptr))
{
//fscanf(fptr, "%d%s%lf", &rec.account, rec.name, &rec.balance);
fread(&rec1, sizeof(rec1),1, fptr);
printf("%d %s %f\n", rec1.account, rec1.name, rec1.balance);
}
fclose(fptr);
}
return 0;
}
clients.txt文件
100琼斯564.90
200里塔54.23
300理查德-45.00
输出
账户名称余额
540028977琼斯564.90
200里塔54.23
300 Richard -45.00╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
__9X°E-92559631349317831000000000000000000000000000000000000000000000000000000000.000000
按任意键继续。……
我可以使用fscanf(我已经注释掉了),但是我必须使用fread/fwrite。
为什么一开始乔恩的账户就有这么多?
为什么后面有垃圾?菲奥夫不应该停止吗?
使用这种方法有什么缺点吗?或者fscanf方法?
我怎样才能修好这些?
多谢提前
最佳答案
正如评论所说,fread
在没有任何解释的情况下读取文件中的字节。文件clients.txt
由50个字符组成,第一行16个字符,第二行14个字符,第三行18个字符,外加两个换行符。(clients.txt不包含第三行后的换行符,很快您就会看到。)在UNIX或Mac OS X计算机上,换行符是单字节\n
,但在Windows计算机上(可能)是两字节\r\n
,因此可以是50或51个字符。以下是十六进制的ascii字节序列:
3130 3020 4a6f 6e65 7320 3536 342e 3930 100 Jones 564.90
0a32 3030 2052 6974 6120 3534 2e32 330a \n200 Rita 54.23\n
3330 3020 5269 6368 6172 6420 2d34 352e 300 Richard -45.
3030 00
您的
fread
语句在没有任何解释的情况下直接将这些字节复制到您的rec1
数据结构中。这个结构以int account;
开头,它表示将前四个字节解释为int
。正如其中一条注释所指出的,您正在一个小的endian机器(很可能是intel机器)上运行程序,因此最低有效字节是第一个,最高有效字节是第四个。因此,您的fread
表示将四个ascii字符的序列"100 "
解释为四字节整数0x20303031
,它在十进制中等于540028977
。结构的下一个成员是char name[100];
,这意味着rec1
中的下100字节数据将是name
。但是fread
被告知读取sizeof(rec1)=112
字节(4字节帐户,100字节名称,8字节余额)。由于您的文件只有50(或52)个字符,fread
将只能填充rec1
的那么多字节。如果您没有丢弃fread
的返回值,它会告诉您读取的字节数少于您请求的字节数。因为您点击了eof,所以在第一次传递之后,feof
调用就脱离了循环,一口吞下了整个文件。您的所有输出都是由第一个也是唯一一个调用
fprintf
生成的。数字540028977和以下空格由"%d "
和rec1.account
参数产生。下一位只是部分确定的,您很幸运:"%s"
说明符和相应的rec1.name
参数将以ascii格式打印下一个字符,直到找到一个\0
字节。因此,输出将以文件的剩余字符(包括两个换行符)开始,并可能永远持续,因为文件(或任何文本文件)中没有字节,这意味着在打印文件的最后一个字符之后,你看到的是当你的程序启动时,自动变量50-4
中发生了什么垃圾。(这种无意的输出类似于openssl中著名的heartbleed bug。)幸运的是,垃圾只包含几十个字符后的52-4
字节。注意\0
无法知道rec1
被声明为只有100字节的数组--它只得到指向\0
开头的指针--您有责任保证printf
包含一个终止的rec1.name
字节,而您从未这样做过。我们可以多说一点。数字
name
(在rec1.name
格式中相当难看)是\0
的值。ieee 754机器(比如你的intel和所有现代计算机)上的-9.2559631349317831e61
值的8字节是十六进制的。与"%f"
对应的rec1.balance
输出中出现了64个特殊的double
符号,而100个字符中只剩下100-46=54个字符,因此您的0xcccccccccccccccc
输出已超出╠
的末尾,并将"%s"
包含在协议中,我们了解到您的终端程序将非ascii字符rec1.name
解释为"%s"
。有很多方法可以解释大于127(0x7f)的字节;例如,在拉丁语-1中,它应该是rec1.name
。图形字符rec1.balance
表示古代MS-DOS字符集中的0xcc(204)字节,Windows代码页437。你不仅在英特尔的机器上运行,它还是一台windows的机器(当然最有可能是从一开始)。这回答了你的前两个问题。我不太明白你的第三个问题。我希望“缺点”是显而易见的。
至于如何修复它,使用
0xcc
读取和解释文本文件并没有一种相当简单的方法。为此,您需要复制╠
Ì
函数中的大部分代码。唯一合理的方法是首先使用╠
创建一个二进制文件;然后fread
自然地将其读回。所以必须有两个程序——一个用来写二进制文件,另一个用来读回。当然,这并不能解决第一个程序的数据从何而来的问题。它可能来自于使用libc
读取fscanf
。或者它可以包含在fwrite
程序的源代码中,例如通过初始化fread
数组,如下所示:struct rec recs[] = {{100, "Jones", 564.90},
{200, "Rita", 54.23},
{300, "Richard", -45.00}};
或者它可能来自于读取mysql数据库,或者……它不太可能起源于一个二进制文件(很容易)用
clients.bin
读取。