标准 IO:刷新、定位

标准 IO:刷新、定位

1. 文件缓冲区刷新 (fflush)

1.1 问题背景

  • 在使用标准 IO 进行格式化输出到文件时,有时会出现以下情况:
    • 调用写函数(如 fprintf)显示成功。
    • 立即使用 cat 命令查看文件,发现内容为空。
  • 原因:标准 IO 是全缓存(Full Buffered)的。
    • 在没有调用 fclose 或缓冲区未满时,数据写入的是缓冲区(Buffer),而非直接写入磁盘文件。
    • 只有当缓冲区刷新或文件关闭时,数据才会真正写入文件。

1.2 解决方案:fflush

  • 功能:在不关闭文件的情况下,强制将缓冲区的内容刷新写入到文件中。
  • 优势:刷新后仍可继续对该文件流进行操作,无需重新 fopen
  • 函数原型
    int fflush(FILE *stream);
    
  • 参数
    • stream:指定要刷新的文件流指针(例如 fp)。
  • 用法示例
    fprintf(fp, "Hello");
    fflush(fp);  // 立即将 "Hello" 写入文件
    // 后续可继续操作 fp
    

2. 文件流定位 (fseek, ftell, rewind)

2.1 定位的必要性

  • 文件流内部维护了一个读写指针(File Position Indicator)。
  • 写操作后,指针会移动到文件末尾。若此时直接读取,无法读到刚才写入的内容。
  • 需要重新定位指针到文件开头或特定位置才能继续读取。
  • 对比
    • 文件 IO (File Descriptor) 使用 lseek
    • 标准 IO (File Stream) 使用 fseek

2.2 fseek 函数详解

  • 功能:移动文件流的读写指针位置。
  • 函数原型
    int fseek(FILE *stream, long offset, int whence);
    
  • 参数说明
    1. stream:文件流指针。
    2. offset偏移量(Offset)。
      • 正数:向后偏移。
      • 负数:向前偏移。
      • 零:位置不变。
    3. whence基准位置(宏定义)。
      • SEEK_SET:文件开头。
      • SEEK_CUR:当前位置。
      • SEEK_END:文件末尾。
  • 注意事项
    • 若文件以追加模式aa+)打开,fseek 对写操作无效(写入始终强制追加到末尾)。
    • 标准 IO 缓冲区大小有限(通常为 4096 字节),适用于一般文本文件操作。

2.3 ftell 函数

  • 功能:获取当前文件流指针相对于文件开头的偏移量。
  • 函数原型
    long ftell(FILE *stream);
    
  • 返回值long 类型,表示当前的字节位置。
  • 用途
    • 获取当前读写位置。
    • 配合 fseek 获取文件大小。

2.4 rewind 函数

  • 功能:将文件流指针直接重置到文件开头。
  • 等价操作
    rewind(fp); 
    // 等价于 fseek(fp, 0, SEEK_SET);
    
  • 特点:用法简单,专门用于回到文件开头。

3. 代码演示与指针移动逻辑

3.1 实验步骤

  1. 打开文件:使用 w+ 模式(读写模式),不可使用 a+(追加模式会影响定位)。

    FILE *fp = fopen("test.txt", "w+");
    
  2. 初始位置检查

    • 调用 ftell(fp),返回值为 0(文件开头)。
  3. 写入数据

    • 使用 fwrite 写入 10 个字符(例如 "HelloWorld")。
    char buf[] = "HelloWorld";
    fwrite(buf, sizeof(char), 10, fp);
    
  4. 写入后位置检查

    • 调用 ftell(fp),返回值变为 10(指针移至第 10 个字节后)。
  5. 重置指针

    • 使用 fseek(fp, 0, SEEK_SET)rewind(fp) 回到开头。
    • 再次 ftell 确认位置为 0
  6. 读取数据

    • 使用 fread 读取 5 个字符。
    • 读取后指针移动 5 个字节,ftell 返回 5
  7. 相对偏移演示

    • 从当前位置向前回退 5 个字节:
    fseek(fp, -5, SEEK_CUR);
    
    • 此时 ftell 返回 0
    • 若回退 8 个字节(假设当前在 10),ftell 返回 2

4. 实战练习:获取文件大小

4.1 实现思路

利用 fseekftell 配合,无需遍历读取文件即可获取大小。

  1. 打开文件。
  2. 使用 fseek 将指针定位到文件末尾 (SEEK_END)。
  3. 使用 ftell 获取当前指针位置,该值即为文件总字节数。
  4. 关闭文件。

4.2 代码示例

#include <stdio.h>

void get_file_size(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("Open file failed\n");
        return;
    }

    // 1. 定位到文件末尾,偏移量为 0
    fseek(fp, 0, SEEK_END);

    // 2. 获取当前位置(即文件大小)
    long file_size = ftell(fp);

    // 3. 输出结果
    printf("File size: %ld bytes\n", file_size);

    fclose(fp);
}

4.3 关键点总结

  • 返回值类型ftell 返回 long 类型,定义变量时需使用 long
  • 宏定义头文件SEEK_SET, SEEK_CUR, SEEK_END 通常包含在 <stdio.h> 中。
  • 效率:该方法比使用 fread 循环读取统计字节数更高效。
  • 验证:对于 1800 字节的文件,该方法返回值应为 1800。

5. 常见问题与注意事项

  • 模式选择:进行读写定位测试时,务必使用 w+r+,避免使用 a+,因为追加模式会忽略写操作的定位。
  • 指针状态:每次读写操作后,文件流指针都会自动移动,需清楚当前指针位置以便后续操作。
  • 缓冲区影响:若写入后未刷新或未关闭,直接查看文件可能看不到内容,调试时注意调用 fflush
  • 二进制与文本:在 Windows 环境下处理二进制文件时,建议打开模式加上 b(如 rb, wb),防止换行符转换影响字节计算。
标准IO:对象、格式化读写 2026-02-28
标准IO:目录操作 2026-03-02

评论区