fread() c 中的一个结构

fread() a struct in c

对于我的作业,我需要使用 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 Jones 564.90
200 Rita 54.23
300 Richard -45.00

输出

Account   Name         Balance
540028977 Jones 564.90
200 Rita 54.23
300 Richard -45.00╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
╠╠ü☻§9x°é -92559631349317831000000000000000000000000000000000000000000000.000000

Press any key to continue . . .

我可以使用 fscanf(我已将其注释掉)执行此操作,但我需要使用 fread/fwrite。

  1. 为什么 Jone 的帐户以大量数字开头?
  2. 为什么后面有垃圾? feof 不应该阻止这个吗?
  3. 使用这种方法有什么缺点吗?或 fscanf 方法?

我该如何解决这些问题? 非常感谢

正如评论所说,fread 读取文件中的字节而不进行任何解释。文件 clients.txt 由 50 个字符组成,第一行 16 个,第二行 14 个,第三行 18 个,外加两个换行符。 (您的 clients.txt 在第三行之后不包含换行符,您很快就会看到。)换行符在 UNIX 上是单个字节 \n 或 Mac OS X机器,但(可能)在 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。正如评论中指出的那样,您 运行 在小端机器(很可能是 Intel 机器)上运行您的程序,因此最低有效字节是第一个字节,最高有效字节是第四个字节。因此,您的 fread 表示将四个 ASCII 字符 "100 " 的序列解释为四字节整数 0x20303031,十进制等于 540028977。您的结构的下一个成员是 char name[100];,这意味着 rec1 中接下来的 100 个字节的数据将是 name。但是 fread 被告知读取 sizeof(rec1)=112 字节(4 字节帐户,100 字节名称,8 字节余额)。由于您的文件只有 50(或 52)个字符,因此 fread 只能填充 rec1 的那么多字节。如果您没有丢弃 fread 的 return 值,它会告诉您读取未达到您请求的字节数就停止了。由于您点击了 EOF,feof 调用在第一次通过后跳出循环,在一个 gulp.

中消耗了整个文件

您的所有输出都是由第一次也是唯一一次调用 fprintf 产生的。数字 540028977 和后面的 space 是由 "%d "rec1.account 参数产生的。下一位只是部分确定的,你很幸运:"%s" 说明符和相应的 rec1.name 参数将以 ASCII 格式打印下一个字符,直到找到 [=38=] 字节。因此,输出将从文件的 50-4(或 52-4)个剩余字符开始——包括两个换行符——并可能永远持续下去,因为没有 [=38=] 个字节您的文件(或任何文本文件),这意味着在打印文件的最后一个字符后,您看到的是程序启动时自动变量 rec1 中碰巧出现的任何垃圾。 (这种无意的输出类似于 OpenSSL 中著名的 heartbleed 错误。)幸运的是,垃圾仅在几十个字符后包含一个 [=38=] 字节。请注意,printf 无法知道 rec1.name 被声明为仅 100 字节的数组——它只得到指向 name 开头的指针——这是你的责任保证 rec1.name 包含一个终止 [=38=] 字节,而你从来没有那样做。

我们可以说得更多一点。数字 -9.2559631349317831e61(在 "%f" 格式中非常难看)是 rec1.balance 的值。在 IEEE 754 机器(如您的 Intel 和所有现代计算机)上,double 值的 8 个字节是十六进制 0xcccccccccccccccc。在 "%s" 输出中出现了 64 个奇特的 符号对应于 rec1.name,而 100 只剩下 100-46 = 54 个字符,所以你的 "%s" 输出有运行 关闭 rec1.name 的末尾,并将 rec1.balance 纳入交易,我们了解到您的终端程序将非 ASCII 字符 0xcc 解释为 .有很多方法可以解释大于 127 (0x7f) 的字节;例如,在 latin-1 中它应该是 &Igrave;。图形字符是0xcc(204)字节在古MS-DOS字符集中的表示,Windows代码页437。不仅是你运行在 Intel 机器上,它是一台 Windows 机器(当然最有可能开始的可能性)。

这回答了您的前两个问题。我不确定我是否理解你的第三个问题。 "drawbacks" 我希望是显而易见的。

至于如何修复它,没有相当简单的方法来使用 fread 读取和解释文本文件。为此,您需要复制 libc fscanf 函数中的大部分代码。唯一明智的方法是先使用 fwrite 创建一个二进制文件;然后 fread 将自然地读取它。因此必须有两个程序——一个用于写入二进制 clients.bin 文件,第二个用于读回它。当然,这并不能解决第一个程序的数据从何而来的问题。它可能来自阅读 clients.txt 使用 fscanf。或者它可以包含在 fwrite 程序的源代码中,例如通过初始化 struct rec 的数组,如下所示:

struct rec recs[] = {{100, "Jones", 564.90},
                     {200, "Rita", 54.23},
                     {300, "Richard", -45.00}};

或者它可能来自读取 MySQL 数据库,或者......它不太可能起源的一个地方是二进制文件(很容易)可读 fread.