ZetCode

C feof 函数

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

C 语言中的文件处理需要精确地检测文件结束符(end-of-file)条件,以避免错误并确保正确的数据处理。`feof` 函数是实现此目的的关键工具,它指示文件流何时已到达文件末尾。本教程将深入探讨 `feof` 函数,解释其正确用法,并通过清晰的示例演示实际应用。理解 `feof` 有助于防止常见的文件读取错误,并确保健壮的文件操作。

feof 是什么?

`feof` 函数用于检查给定文件流的文件结束符指示器是否已设置。它接受一个 `FILE` 指针作为参数,如果已达到文件结束符,则返回非零值。与直接检查 `EOF` 常量不同,`feof` 会检查流的内部状态。重要的是,要**在读取操作之后**使用 `feof`,而不是之前,以获得准确的结果。正确使用 `feof` 可以防止无限循环,并确保完整的文件处理。

基本 feof 示例

这个简单的示例演示了 `feof` 的基本用法,它会读取文件直到到达文件末尾。

basic_feof.c
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "r");
    if (fp == NULL) {
        perror("Error opening file");
        return 1;
    }

    int ch;
    while (1) {
        ch = fgetc(fp);
        if (feof(fp)) {
            break;  // Exit loop when EOF is reached
        }
        putchar(ch);
    }

    fclose(fp);
    return 0;
}

此程序打开 "data.txt" 文件并逐个字符读取。`feof` 函数在每次读取后检查我们是否已到达文件末尾。当 `feof` 返回真时,循环终止。请注意,我们是在读取**之后**检查 EOF,而不是之前,以确保我们正确处理所有数据。完成后务必使用 `fclose` 关闭文件。

逐行读取直到 EOF

此示例展示了如何使用 `feof` 和 `fgets` 逐行读取整个文件。

read_lines.c
#include <stdio.h>
#include <string.h>

#define MAX_LEN 256

int main() {
    FILE *fp = fopen("lines.txt", "r");
    if (fp == NULL) {
        perror("File open error");
        return 1;
    }

    char buffer[MAX_LEN];
    while (!feof(fp)) {
        if (fgets(buffer, MAX_LEN, fp) != NULL) {
            // Remove newline if present
            buffer[strcspn(buffer, "\n")] = '\0';
            printf("Read: %s\n", buffer);
        }
    }

    fclose(fp);
    return 0;
}

在这里,我们将 `feof` 用作循环条件,以继续读取直到到达 EOF。`fgets` 函数将每一行读取到一个缓冲区中,然后我们对其进行处理。请注意检查 `fgets` 返回的 `NULL` 值——这很重要,因为 `feof` 仅在读取尝试失败后才返回真。这种组合确保我们正确处理所有行。

使用 feof 进行二进制文件处理

此示例演示了在二进制文件操作中使用 `feof`。

binary_feof.c
#include <stdio.h>

struct Record {
    int id;
    double value;
};

int main() {
    FILE *fp = fopen("data.bin", "rb");
    if (fp == NULL) {
        perror("Binary file open error");
        return 1;
    }

    struct Record rec;
    while (!feof(fp)) {
        size_t read = fread(&rec, sizeof(struct Record), 1, fp);
        if (read == 1) {
            printf("ID: %d, Value: %.2f\n", rec.id, rec.value);
        }
    }

    fclose(fp);
    return 0;
}

在处理二进制文件时,`feof` 有助于在读取结构化数据时检测文件末尾。我们读取 `Record` 结构直到 `feof` 指示我们已到达文件末尾。检查 `fread` 的返回值以确保我们仅处理完整的记录。这种方法适用于固定大小的二进制数据结构。

常见的 feof 陷阱

此示例说明了使用 `feof` 时一个常见的错误以及如何避免它。

feof_pitfall.c
#include <stdio.h>

// WRONG way to use feof
void wrong_approach(FILE *fp) {
    char ch;
    while (!feof(fp)) {  // Check before read - BAD!
        ch = fgetc(fp);
        putchar(ch);     // May process EOF as data
    }
}

// RIGHT way to use feof
void correct_approach(FILE *fp) {
    int ch;
    while ((ch = fgetc(fp)) != EOF) {  // Check read result
        putchar(ch);
    }
}

int main() {
    FILE *fp = fopen("sample.txt", "r");
    if (fp == NULL) {
        perror("File error");
        return 1;
    }

    printf("Incorrect output:\n");
    wrong_approach(fp);
    
    rewind(fp);  // Reset to file start
    
    printf("\nCorrect output:\n");
    correct_approach(fp);

    fclose(fp);
    return 0;
}

错误的方法是在读取之前检查 `feof`,这可能导致处理一个额外的“幻象”字符。正确的方法是直接将读取操作的结果与 `EOF` 进行比较。此示例清楚地表明了为什么您永远不应仅将 `feof` 作为循环条件而不检查读取操作的成功。正确的方法更可靠,并避免了常见错误。

使用 feof 计算文件大小

此示例使用 `feof` 来计算文件中的字节数。

file_size.c
#include <stdio.h>

int main() {
    FILE *fp = fopen("largefile.bin", "rb");
    if (fp == NULL) {
        perror("File open failed");
        return 1;
    }

    long count = 0;
    while (!feof(fp)) {
        if (fgetc(fp) != EOF) {  // Read and check
            count++;
        }
    }

    printf("File contains %ld bytes\n", count);
    fclose(fp);
    return 0;
}

此程序在到达 EOF 之前计算二进制文件中的每个字节。我们在循环条件中使用 `feof`,但仍然检查每个 `fgetc` 的结果以确保我们仅计算成功的读取。对于非常大的文件,可以考虑改用 `fseek` 和 `ftell`,但此方法非常适合通过字节级处理来演示 `feof`。计数完成后务必关闭文件。

处理 CSV 数据时使用 feof

此示例展示了在正确处理 EOF 条件的同时处理 CSV 数据。

csv_reader.c
#include <stdio.h>
#include <string.h>

#define MAX_LINE 1024

int main() {
    FILE *fp = fopen("data.csv", "r");
    if (fp == NULL) {
        perror("CSV open error");
        return 1;
    }

    char line[MAX_LINE];
    int row_count = 0;
    
    while (!feof(fp)) {
        if (fgets(line, MAX_LINE, fp) != NULL) {
            // Process CSV line
            char *token = strtok(line, ",");
            printf("Row %d: ", ++row_count);
            
            while (token != NULL) {
                printf("[%s] ", token);
                token = strtok(NULL, ",");
            }
            printf("\n");
        }
    }

    printf("Processed %d rows\n", row_count);
    fclose(fp);
    return 0;
}

此 CSV 处理器使用 `feof` 控制外部循环,同时检查 `fgets` 是否成功读取行。每一行都被分割成逗号分隔的令牌进行处理。`feof` 和 `NULL` 检查的结合确保我们处理所有数据,而不会遗漏行或处理空行。这种模式不仅适用于 CSV,还适用于许多文本文件格式。

自定义文件读取函数

此示例演示了一个健壮的文件读取函数,该函数正确处理 EOF 条件。

file_reader.c
#include <stdio.h>
#include <stdbool.h>

bool read_next_chunk(FILE *fp, char *buffer, size_t size) {
    if (feof(fp)) {
        return false;  // Already at EOF
    }

    size_t read = fread(buffer, 1, size, fp);
    if (read == 0) {
        return false;  // Read failed or EOF
    }
    
    buffer[read] = '\0';  // Null-terminate
    return true;
}

int main() {
    FILE *fp = fopen("document.txt", "r");
    if (fp == NULL) {
        perror("File open failed");
        return 1;
    }

    char chunk[256];
    while (read_next_chunk(fp, chunk, sizeof(chunk) - 1)) {
        printf("Chunk: %s\n", chunk);
    }

    if (feof(fp)) {
        printf("Reached end of file\n");
    } else {
        printf("Error before EOF\n");
    }

    fclose(fp);
    return 0;
}

此示例展示了在自定义文件读取函数中 `feof` 的更高级用法。该函数在尝试读取之前检查 EOF,然后验证读取操作是否成功。主循环使用函数的返回值来控制处理。循环结束后,我们再次检查 `feof` 以确定我们是正常到达文件末尾还是遇到了错误。这种模式可以创建健壮、可重用的文件处理代码。

使用 feof 的最佳实践

来源

C feof 文档

本教程深入探讨了 `feof` 函数,并通过实际示例展示了其正确用法。掌握 `feof` 对于 C 语言中的健壮文件处理至关重要,可以防止常见错误并确保完整的数据处理。请记住,始终将 `feof` 检查与读取操作验证相结合,以获得最可靠的结果。

作者

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

列表 C 标准库