C fread 函数
最后修改日期:2025 年 4 月 6 日
fread
函数是 C 语言中用于高效读取文件二进制数据的强大工具。它允许您一次操作读取数据块,非常适合处理结构化数据和二进制文件。本教程将深入探讨 fread
,从基本用法到高级技术。您将学习如何读取不同数据类型、处理错误以及优化性能。掌握 fread
对于在 C 语言中使用二进制文件和大型数据集至关重要。
什么是 fread?
fread
函数将文件数据读取到缓冲区中。它接受四个参数:指向缓冲区的指针、每个元素的大小、要读取的元素数量以及一个 FILE 指针。fread
返回成功读取的元素数量,该数量可能少于请求的数量。它通常与以“rb”模式打开的二进制文件一起使用。请始终检查返回值以验证读取是否成功并妥善处理错误。
fread 基本示例
让我们从一个从二进制文件中读取单个整数的简单示例开始。
#include <stdio.h> int main() { FILE *fp; int value; fp = fopen("data.bin", "rb"); // Open in binary read mode if (fp == NULL) { perror("Failed to open file"); return 1; } size_t result = fread(&value, sizeof(int), 1, fp); if (result != 1) { perror("Failed to read data"); fclose(fp); return 1; } printf("Read value: %d\n", value); fclose(fp); return 0; }
此示例演示了 fread
的基本用法。我们将文件以二进制读取模式打开,然后将一个整数(int
的大小)读取到我们的变量中。我们会检查 fread
的返回值,以确保我们读取了一个元素。最后,我们打印该值并关闭文件。这种模式是所有 fread
操作的基础。
读取数字数组
现在,让我们通过一次从文件中读取整个数组来读取多个值。
#include <stdio.h> #define SIZE 5 int main() { FILE *fp; int numbers[SIZE]; fp = fopen("array.bin", "rb"); if (fp == NULL) { perror("Failed to open file"); return 1; } size_t elements_read = fread(numbers, sizeof(int), SIZE, fp); if (elements_read != SIZE) { printf("Only read %zu of %d elements\n", elements_read, SIZE); } printf("Array contents:\n"); for (int i = 0; i < elements_read; i++) { printf("%d ", numbers[i]); } printf("\n"); fclose(fp); return 0; }
此示例一次性读取整个整数数组。我们指定每个元素的大小(sizeof(int)
)和元素数量(SIZE
)。返回值告诉我们实际读取了多少个元素,我们在循环中使用它。这种方法比逐个读取元素要高效得多,尤其是对于大型数组。
读取结构体
fread
在读取结构化数据方面特别有用。以下是如何从文件中读取自定义结构体。
#include <stdio.h> typedef struct { int id; char name[20]; float score; } Student; int main() { FILE *fp; Student s; fp = fopen("student.bin", "rb"); if (fp == NULL) { perror("Failed to open file"); return 1; } if (fread(&s, sizeof(Student), 1, fp) != 1) { perror("Failed to read student"); fclose(fp); return 1; } printf("Student ID: %d\n", s.id); printf("Name: %s\n", s.name); printf("Score: %.2f\n", s.score); fclose(fp); return 0; }
此示例演示了一次性读取整个结构体。sizeof(Student)
确保我们读取了确切的数据量。这项技术对于读取复杂数据类型非常强大,因为它保留了确切的内存布局。请注意,这假设文件是用相同的结构定义在相同的系统架构上写入的。
读取直到文件末尾
以下是如何使用 fread
读取数据直到文件末尾。
#include <stdio.h> #define BUFFER_SIZE 1024 int main() { FILE *fp; char buffer[BUFFER_SIZE]; size_t bytes_read; fp = fopen("largefile.bin", "rb"); if (fp == NULL) { perror("Failed to open file"); return 1; } while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) { printf("Read %zu bytes\n", bytes_read); // Process the buffer here } fclose(fp); return 0; }
此示例展示了分块读取大文件的常见模式。我们一次最多读取 BUFFER_SIZE
字节,并在每次读取后处理该块。循环将继续,直到 fread
返回 0,表示文件末尾。这种方法内存效率高,适用于任何大小的文件,包括那些太大而无法一次性载入内存的文件。
读取和验证数据
验证您是否读取了预期的数据量很重要。此示例展示了正确的错误处理。
#include <stdio.h> #include <stdlib.h> int main() { FILE *fp; double *data; size_t count = 1000; fp = fopen("dataset.bin", "rb"); if (fp == NULL) { perror("Failed to open file"); return 1; } data = (double *)malloc(count * sizeof(double)); if (data == NULL) { perror("Memory allocation failed"); fclose(fp); return 1; } size_t read = fread(data, sizeof(double), count, fp); if (read != count) { if (feof(fp)) { printf("Reached end of file after %zu elements\n", read); } else if (ferror(fp)) { perror("Error reading file"); } } // Use the data (first 5 elements for demonstration) printf("First 5 values:\n"); for (size_t i = 0; i < (read < 5 ? read : 5); i++) { printf("%.2f ", data[i]); } printf("\n"); free(data); fclose(fp); return 0; }
此示例演示了使用 fread
进行健壮的错误处理。我们动态分配内存用于我们的数据,然后尝试读取 1000 个双精度浮点数。如果我们读取的数量少于预期,我们会检查是到达了文件末尾(feof
)还是遇到了错误(ferror
)。对于数据完整性至关重要的生产代码,这种程度的验证是必不可少的。
读取不同的数据类型
当文件结构正确时,fread
可以读取混合数据类型。这是一个例子。
#include <stdio.h> typedef struct { char header[4]; int version; float values[3]; char footer[4]; } DataFile; int main() { FILE *fp; DataFile df; fp = fopen("mixed.bin", "rb"); if (fp == NULL) { perror("Failed to open file"); return 1; } if (fread(&df, sizeof(DataFile), 1, fp) != 1) { perror("Failed to read data file"); fclose(fp); return 1; } printf("Header: %.4s\n", df.header); printf("Version: %d\n", df.version); printf("Values: %.2f, %.2f, %.2f\n", df.values[0], df.values[1], df.values[2]); printf("Footer: %.4s\n", df.footer); fclose(fp); return 0; }
此示例一次性读取一个包含不同数据类型的复杂结构。该结构包括字符数组、整数和浮点数。fread
无缝处理此问题,因为它处理的是原始内存。请注意,这假设文件的结构与程序的结构定义完全匹配,包括填充和对齐。
带定位的读取
将 fread
与 fseek
结合使用,以从文件的特定位置读取。
#include <stdio.h> int main() { FILE *fp; int values[3]; fp = fopen("positioned.bin", "rb"); if (fp == NULL) { perror("Failed to open file"); return 1; } // Read first value if (fread(&values[0], sizeof(int), 1, fp) != 1) { perror("Failed to read first value"); fclose(fp); return 1; } // Skip ahead to read third value if (fseek(fp, sizeof(int), SEEK_CUR) != 0) { perror("Failed to seek"); fclose(fp); return 1; } if (fread(&values[2], sizeof(int), 1, fp) != 1) { perror("Failed to read third value"); fclose(fp); return 1; } // Go back to read second value if (fseek(fp, -2 * sizeof(int), SEEK_CUR) != 0) { perror("Failed to seek back"); fclose(fp); return 1; } if (fread(&values[1], sizeof(int), 1, fp) != 1) { perror("Failed to read second value"); fclose(fp); return 1; } printf("Values: %d, %d, %d\n", values[0], values[1], values[2]); fclose(fp); return 0; }
此示例演示了在读取时如何在文件中导航。我们首先读取第一个整数,然后跳过第二个读取第三个,然后返回读取第二个。fseek
允许我们移动文件位置指针,而 fread
从当前位置读取。此技术对于读取结构化二进制文件的特定部分很有用。
使用 fread 的最佳实践
- 始终检查返回值: 验证
fread
是否返回了预期的元素数量。 - 使用二进制模式: 使用“b”(例如,“rb”)打开文件以进行二进制数据读取,以避免文本模式转换。
- 匹配数据类型: 确保缓冲区类型和大小与文件中的内容匹配。
- 处理部分读取: 准备好处理
fread
读取的元素少于请求的情况。 - 结合定位: 使用
fseek
和ftell
在文件中进行随机访问。 - 考虑字节序: 在不同系统之间读取文件时,请注意字节顺序的差异。
来源
本教程深入探讨了 fread
函数,从基本用法到高级技术。您已经了解了如何读取不同数据类型、处理错误以及优化文件读取操作。掌握了这些技能,您就可以在 C 程序中高效地处理二进制文件和结构化数据。
作者
列表 C 标准库。