ZetCode

C fread 函数

最后修改日期:2025 年 4 月 6 日

fread 函数是 C 语言中用于高效读取文件二进制数据的强大工具。它允许您一次操作读取数据块,非常适合处理结构化数据和二进制文件。本教程将深入探讨 fread,从基本用法到高级技术。您将学习如何读取不同数据类型、处理错误以及优化性能。掌握 fread 对于在 C 语言中使用二进制文件和大型数据集至关重要。

什么是 fread?

fread 函数将文件数据读取到缓冲区中。它接受四个参数:指向缓冲区的指针、每个元素的大小、要读取的元素数量以及一个 FILE 指针。fread 返回成功读取的元素数量,该数量可能少于请求的数量。它通常与以“rb”模式打开的二进制文件一起使用。请始终检查返回值以验证读取是否成功并妥善处理错误。

fread 基本示例

让我们从一个从二进制文件中读取单个整数的简单示例开始。

basic_read.c
#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 操作的基础。

读取数字数组

现在,让我们通过一次从文件中读取整个数组来读取多个值。

array_read.c
#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 在读取结构化数据方面特别有用。以下是如何从文件中读取自定义结构体。

struct_read.c
#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 读取数据直到文件末尾。

read_until_eof.c
#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,表示文件末尾。这种方法内存效率高,适用于任何大小的文件,包括那些太大而无法一次性载入内存的文件。

读取和验证数据

验证您是否读取了预期的数据量很重要。此示例展示了正确的错误处理。

verify_read.c
#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 可以读取混合数据类型。这是一个例子。

mixed_read.c
#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 无缝处理此问题,因为它处理的是原始内存。请注意,这假设文件的结构与程序的结构定义完全匹配,包括填充和对齐。

带定位的读取

freadfseek 结合使用,以从文件的特定位置读取。

positioned_read.c
#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 的最佳实践

来源

C fread 文档

本教程深入探讨了 fread 函数,从基本用法到高级技术。您已经了解了如何读取不同数据类型、处理错误以及优化文件读取操作。掌握了这些技能,您就可以在 C 程序中高效地处理二进制文件和结构化数据。

作者

我叫 Jan Bodnar,是一名敬业的程序员,对编码充满热情。自 2007 年以来,我通过 1,400 多篇文章和 8 本电子书分享了我的专业知识。凭借十多年的教学经验,我致力于让编程变得易于理解和引人入胜。

列表 C 标准库