新手入门指南:认识 C 语言文件操作(下)

1. ⽂件的随机读写详细解释

在C语言中,文件操作是数据处理的核心部分。随机读写允许程序直接访问文件的任意位置,提高效率和灵活性。本部分将详细解释用户提供的函数和概念,包括fseekftellrewindfflush,以及文件缓冲区和更新文件行为。

1.1 fseek函数详解

fseek函数用于移动文件指针的位置,实现随机访问。其原型为:

c 复制代码
int fseek(FILE *stream, long int offset, int origin);
  • 功能:根据文件指针的当前位置和偏移量定位指针(即文件内容的"光标")。例如,可以从文件开头、当前位置或末尾移动。
  • 参数
    • stream:指向FILE结构体的指针,指定文件流。
    • offset:偏移量,单位为字节。正数向文件末尾移动,负数向文件开头移动,0表示不移动。
    • origin:基准位置,有三个取值:
      • SEEK_SET:文件开始位置。
      • SEEK_CUR:文件指针当前位置。
      • SEEK_END:文件末尾位置。
  • 返回值
    • 成功:返回0。
    • 失败:返回非零值(如-1),通常因无效偏移或文件流错误。
  • 注意事项
    • 二进制 vs 文本文件 :在二进制文件中,fseek行为直观,可精确移动到任何字节位置。在文本文件中,由于换行符转换(如Windows的"\r\n" vs Linux的"\n"),行为可能不确定。唯一安全用法是:
      • fseek(stream, 0, SEEK_SET):移动到文件开头。
      • 配合ftell使用:先获取位置,再恢复。
    • 常见错误:移动超出文件范围(如负偏移)会失败。

代码示例分析

c 复制代码
#include <stdio.h>
int main() {
    FILE* ps = fopen("test.txt", "r");
    if (ps == NULL) {
        perror("fopen");
        return 1;
    }
    int c = fgetc(ps); // 读取第一个字符
    fputc(c, stdout);  // 输出到屏幕,例如'a'
    
    // 定位文件指针:三种方式示例
    // fseek(ps, 5, SEEK_SET); // 从文件开头移动5字节
    // fseek(ps, 4, SEEK_CUR); // 从当前位置移动4字节
    fseek(ps, -9, SEEK_END);   // 从文件末尾移动-9字节(向开头方向)
    
    c = fgetc(ps); // 读取新位置的字符
    fputc(c, stdout); // 输出,例如如果文件内容为"abcdefghij",移动到-9位置可能读'b'
    
    fclose(ps);
    ps = NULL;
    return 0;
}
  • 逐步解释
    1. 打开文件test.txt用于读取。
    2. 读取并输出第一个字符(如'a')。
    3. 使用fseek移动指针:示例中从末尾移动-9字节,假设文件有10字节(如"abcdefghij"),则移动到第1字节(索引从0开始)。
    4. 读取并输出新位置的字符(如'b')。
  • 重点:移动后读取字符,演示随机访问。但需注意文本文件的不可靠性。

1.2 ftell函数详解

ftell函数用于获取当前文件指针位置。其原型为:

c 复制代码
long int ftell(FILE *stream);
  • 功能:返回文件指针相对于文件开头的偏移量(字节数)。
  • 参数stream,指向已打开文件的指针。
  • 返回值
    • 成功:返回偏移量,类型为long int
    • 失败:返回-1L(如文件流无效)。
  • 注意事项
    • 二进制 vs 文本文件 :在二进制文件中,返回值是精确字节数。在文本文件中,返回值可能不等于字符数(因换行符处理),但通常可安全与fseek配合使用。
    • 使用场景:记录位置以便恢复,如编辑文件时。

代码示例分析

c 复制代码
int main() {
    FILE* ps = fopen("test.txt", "r");
    if (ps == NULL) {
        perror("fopen");
        return 1;
    }
    int c = fgetc(ps); // 读取第一个字符,如'a'
    fputc(c, stdout);  // 输出'a'
    
    int pos = ftell(ps); // 记录当前位置,偏移量为1(假设文件内容为"abcdef")
    
    fseek(ps, 4, SEEK_CUR); // 从当前位置移动4字节,如从第1字节到第5字节
    c = fgetc(ps); // 读取新位置字符,如'f'
    fputc(c, stdout); // 输出'f'
    
    fseek(ps, pos, SEEK_SET); // 恢复到之前记录的位置(第1字节)
    c = fgetc(ps); // 读取字符,如'b'
    fputc(c, stdout); // 输出'b'
    
    fclose(ps);
    ps = NULL;
    return 0;
}
  • 逐步解释
    1. 打开文件并读取第一个字符(输出'a')。
    2. ftell记录当前位置(偏移量1)。
    3. fseek移动4字节后读取字符(输出'f')。
    4. 使用ftell的返回值恢复位置,读取原位置的下一个字符(输出'b')。
  • 重点ftellfseek配合实现位置标记和恢复,避免手动计算偏移。

1.3 rewind函数详解

rewind函数用于重置文件指针到开头。其原型为:

c 复制代码
void rewind(FILE *stream);
  • 功能:将文件指针移动到文件起始位置,并清除文件错误标志和结束标志。
  • 参数stream,指向文件流的指针。
  • 注意事项
    • fseek区别fseek(stream, 0, SEEK_SET)只移动指针,不清除错误标志;rewind移动指针并清除标志。
    • 使用场景:重新读取文件或重置状态。

代码示例分析

c 复制代码
int main() {
    FILE* ps = fopen("test.txt", "r");
    if (ps == NULL) {
        perror("fopen");
        return 1;
    }
    int c = fgetc(ps); // 读取第一个字符,如'a'
    fputc(c, stdout);  // 输出'a'
    
    int pos = ftell(ps); // 记录位置
    
    fseek(ps, 4, SEEK_CUR); // 移动4字节
    c = fgetc(ps); // 读取,如'f'
    fputc(c, stdout); // 输出'f'
    
    fseek(ps, pos, SEEK_SET); // 恢复位置
    c = fgetc(ps); // 读取,如'b'
    fputc(c, stdout); // 输出'b'
    
    rewind(ps); // 重置指针到开头
    c = fgetc(ps); // 重新读取第一个字符,如'a'
    fputc(c, stdout); // 输出'a'
    
    fclose(ps);
    ps = NULL;
    return 0;
}
  • 逐步解释
    1. 类似前例,读取和移动指针。
    2. rewind后,指针重置到开头,读取第一个字符(输出'a')。
  • 重点rewind确保文件状态重置,适合重复操作。

2. ⽂件缓冲区解释

ANSI C标准采用"缓冲文件系统":数据在内存缓冲区中暂存,减少磁盘访问次数。

  • 机制
    • 输出:数据先写入缓冲区,满后才写入磁盘。
    • 输入:从磁盘读取数据到缓冲区,再分批送到程序。
  • 缓冲区大小:由C编译器决定,通常为512字节或更大。
  • 重要性:提高效率,但可能导致数据延迟写入(如程序崩溃时丢失数据)。

2.1 fflush函数详解

fflush函数强制刷新缓冲区。其原型为:

c 复制代码
int fflush(FILE *stream);
  • 功能 :刷新指定流的缓冲区。
    • 对输出流:立即将未写入数据写入文件。
    • 对输入流:行为非标准(可能清空缓冲区)。
    • 参数为NULL:刷新所有输出流。
  • 参数stream,如stdout或文件指针。
  • 返回值
    • 成功:返回0。
    • 失败:返回EOF
  • 注意事项
    • 仅输出流有明确行为;输入流刷新不可移植。
    • 程序正常终止(如exitfclose)时自动刷新,但崩溃时数据可能丢失。

代码示例分析

c 复制代码
#include <windows.h>
int main() {
    FILE* ps = fopen("test.txt", "w");
    fputs("abcdef", ps); // 数据写入缓冲区,未立即写入文件
    
    printf("睡眠10秒 - 已经写好数据,打开test.txt文件,发现文件没有内容\n");
    Sleep(10000); // 暂停10秒,模拟延迟
    
    printf("刷新缓冲区\n");
    fflush(ps); // 强制刷新,数据写入文件
    // 注意:高版本Visual Studio中fflush可能受限
    
    printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
    Sleep(10000); // 再次暂停
    
    fclose(ps); // fclose也会自动刷新缓冲区
    ps = NULL;
    return 0;
}
  • 逐步解释
    1. 打开文件写入数据,但数据停留在缓冲区。
    2. Sleep暂停后,文件可能无内容。
    3. fflush刷新缓冲区,数据写入磁盘。
    4. fclose关闭文件时也刷新缓冲区。
  • 重点fflush确保数据及时写入,避免丢失。

3. 更新⽂件行为详解

更新模式允许读写结合,如"r+""w+""a+"。关键要点:

  • 读写切换规则
    • 写后读:需先fflush刷新缓冲区,再用rewindfseek重置指针。
    • 读后写:需用fseekrewind重置指针。
  • 原因:缓冲区未刷新或指针位置错误会导致数据不一致。

以下表格比较三种模式:

模式 文件不存在时 文件存在时 初始指针位置 写入是否覆盖原有数据 典型用途
"r+" 打开失败 保留内容 文件开头 是(可定位覆盖) 修改文件部分内容
"w+" 创建新文件 清空内容 文件开头 是(从头写入) 创建新文件或完全重写
"a+" 创建新文件 保留内容 文件末尾 否(默认追加) 追加数据,如日志记录

关键要点

  • "r+":适合就地修改,如覆盖部分数据。
  • "w+":清空文件后从头读写。
  • "a+":只追加,不覆盖。

综合代码示例分析

c 复制代码
int main() {
    FILE* ps = fopen("test.txt", "w+"); // 以w+模式打开
    if (ps == NULL) {
        perror("fopen for w+");
        return 1;
    }
    
    fputs("abcdefghi", ps); // 写入字符串到缓冲区
    
    fflush(ps); // 刷新缓冲区,确保数据写入文件
    
    fseek(ps, 1, SEEK_SET); // 移动指针到第1字节(如指向'b')
    int ch = fgetc(ps); // 读取字符,如'b'
    printf("%c\n", ch); // 输出'b'
    
    fseek(ps, -1, SEEK_CUR); // 从当前位置后退1字节(因读取后指针在'c')
    fputs("hello", ps); // 从当前位置写入"hello",覆盖部分数据
    
    // 结果:原"abcdefghi"变为"ahelloghi"
    fclose(ps);
    ps = NULL;
    return 0;
}
  • 逐步解释
    1. w+模式打开文件:如果存在,清空内容;写入"abcdefghi"
    2. fflush确保数据写入磁盘。
    3. fseek移动指针到位置1(索引0为'a',1为'b')。
    4. 读取并输出'b'
    5. fseek后退1字节(从'c'回到'b'),写入"hello",覆盖从'b'开始的数据。
    6. 最终文件内容变为"ahelloghi"
  • 重点:演示读写切换:写后刷新和移动指针,避免错误。

总结

文件随机读写是C语言文件操作的核心,通过fseekftellrewind实现指针定位,fflush管理缓冲区。关键点:

  • 区分二进制和文本文件行为。
  • 读写切换时,务必刷新缓冲区并重置指针。
  • 选择合适文件模式(如"r+"用于修改)。
  • 代码示例展示了实际用法,强调错误处理和效率。
相关推荐
少司府1 小时前
C++进阶:多态
c语言·开发语言·c++·多态·抽象类·虚函数·虚表指针
愿天垂怜1 小时前
【C++脚手架】etcd 的介绍与使用
java·linux·服务器·c语言·c++·中间件·etcd
飞翔中文网1 小时前
Java学习笔记之泛型
java·笔记·学习
a83331961 小时前
c语言课程设计小游戏,c语言小游戏设计案例
c语言·开发语言
程序猿编码1 小时前
如何把远程文件变化“骗“成本地inotify事件:一个LD_PRELOAD钩子
c语言·开发语言·网络·tcp/ip·安全
AOwhisky1 小时前
Ceph系列第四期:Ceph块存储(RBD)精讲
linux·运维·笔记·ceph·云计算·rbd
longxiangam10 小时前
esp-idf 中 mipi dsi 使用的笔记
笔记
EntyIU11 小时前
JVM内存与GC笔记
java·jvm·笔记
星恒随风12 小时前
C语言数据结构排序算法详解(下):冒泡排序、快速排序、归并排序和计数排序
c语言·数据结构·笔记·学习·排序算法