c语言进阶 文件操作

1. 为什么要使用文件

  • 程序运行时,数据通常存储在内存中,当程序退出后,内存中的数据会被释放,无法长期保存。
  • 文件的核心作用是实现数据的持久化存储,将数据保存在外部存储设备(如硬盘)中,即使程序关闭,数据也不会丢失,下次运行程序时可重新读取使用。
  • 示例场景:
    • 通讯录程序:若不使用文件,每次运行程序后添加的联系人会随程序退出而消失;使用文件可将联系人数据保存,下次打开程序时读取文件恢复数据。
    • 游戏存档:游戏进度需通过文件保存,否则关闭游戏后进度全部丢失,无法继续上次游戏。

2. 什么是文件

  • 文件是存储在外部存储设备上的数据流,按用途可分为程序文件数据文件

2.1 程序文件

  • 定义:用于程序编译、链接和运行的文件,包含程序的代码和指令。
  • 类型及示例:
    • 源文件(.c):如main.c,存放C语言源代码。
    • 目标文件(.obj/.o):编译后生成的二进制文件,如main.obj
    • 可执行文件(.exe):链接后生成的可运行文件,如main.exe

2.2 数据文件

  • 定义:程序运行时读写的文件,用于存储程序处理的数据(如文本、数值等)。
  • 操作方向:
    • 写操作:程序(.c文件编译后的可执行文件)将数据输出到数据文件中。
    • 读操作:程序从数据文件中读取数据并处理。
  • 示例:
    • 程序将用户输入的账号密码写入user.txt(写操作)。
    • 程序从score.dat中读取学生成绩并计算平均分(读操作)。

2.3 什么是文件名

  • 文件名是唯一标识一个文件的字符串,用于在文件系统中定位文件,其组成格式为:路径 + 文件名主干 + 后缀
  • 各部分含义:
    • 路径:表示文件在存储设备中的位置(如D:\data\)。
    • 文件名主干:文件的核心名称(如test)。
    • 后缀:标识文件类型(如.txt表示文本文件,.dat表示数据文件)。
  • 示例:
    • C:\project\result.txt:绝对路径,完整标识了文件位置。
    • ./logs/error.log:相对路径(相对于当前程序运行目录)。
    • 路径中的转义字符:在C语言字符串中,\需用\\表示(转义),如"D:\\data\\info.dat"

3. 文件的打开和关闭

3.1 文件指针

  • 缓冲文件系统中,当打开一个文件时,系统会自动创建一个FILE类型的结构体变量(称为文件信息区),该结构体包含了与文件相关的所有信息:
    • 文件的名称、存储路径;
    • 文件的打开方式(读/写/追加);
    • 文件当前的读写位置;
    • 文件的状态(是否出错、是否已到末尾)等。
  • 这个FILE结构体由系统定义(不同编译器的实现细节可能不同,但核心成员一致),使用者无需关心其内部结构,只需通过指向该结构体的指针来操作文件。
  • 文件指针的定义:通过FILE*类型声明一个指针变量,用于指向上述FILE结构体。
    FILE* pf; // pf就是一个文件指针变量,未指向任何文件时为NULL
    
  • 文件指针的作用:当通过fopen打开文件时,函数会返回一个指向该文件信息区(FILE 结构体)的指针,将其赋值给文件指针后,后续对文件的所有操作(读 / 写 / 关闭)都通过该指针完成。
  • 示例:文件指针与文件的关联过程
// 1. 打开文件,fopen返回指向该文件信息区的指针
FILE* pf = fopen("test.txt", "r");

if (pf == NULL) { // 若打开失败,返回NULL
    perror("fopen failed");
    return 1;
}

// 2. 此时pf指向test.txt的文件信息区,通过pf操作文件
// 例如:读取文件内容、获取文件状态等

// 3. 操作完成后,通过pf关闭文件
fclose(pf);
pf = NULL; // 关闭后将指针置空,避免成为野指针
  • 关键说明

    • 一个文件指针只能关联一个文件,若要操作多个文件,需定义多个文件指针:
    FILE* pf1 = fopen("a.txt", "r"); // pf1关联a.txt
    FILE* pf2 = fopen("b.txt", "w"); // pf2关联b.txt
    
  • 文件指针本质是通过指向文件信息区,间接获取文件的所有状态和位置信息,从而实现对物理文件的操作,它是程序与文件之间的 “桥梁”。

3.2 文件的打开和关闭

  • 文件操作流程:打开文件→读写文件→关闭文件。打开文件后必须手动关闭,否则可能导致数据丢失或资源泄漏。
3.2.1 打开文件:fopen函数
  • 函数原型:FILE* fopen(const char* filename, const char* mode);

  • 参数:

    • filename:文件名(含路径和后缀,注意\\转义)。
    • mode:打开方式(常用方式:r读,w写,a追加)。
  • 返回值:成功返回文件指针(指向文件信息区),失败返回NULL(需检查返回值)。

  • 打开方式示例:

    • r(只读):打开已存在的文件,用于读取;文件不存在则打开失败。
    • w(只写):创建新文件或清空已有文件,用于写入;若文件存在,原有内容会被删除。
    • a(追加):打开文件在末尾追加内容;文件不存在则创建,原有内容保留。
  • 路径示例:

    // 绝对路径:打开D盘data目录下的test.txt(注意\\转义)
    FILE* pf1 = fopen("D:\\data\\test.txt", "r");
    
    // 相对路径:打开当前程序目录下的log.txt
    FILE* pf2 = fopen("log.txt", "w");
    
    // 检查打开是否成功
    if (pf1 == NULL || pf2 == NULL) {
        perror("fopen failed"); // 打印错误信息
        return 1;
    }
    
3.2.2 关闭文件:fclose 函数
  • 函数原型:int fclose(FILE* stream);
  • 参数:stream为要关闭的文件指针。
  • 返回值:成功返回 0,失败返回非 0。
    示例
  // 关闭文件
if (fclose(pf1) != 0) {
    perror("fclose failed");
}
if (fclose(pf2) != 0) {
    perror("fclose failed");
}
pf1 = NULL; // 避免野指针
pf2 = NULL;
3.2.3 完整示例(写文件)
// 打开文件(w模式,若存在则清空内容)
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 写入数据
fputs("hello world", pf);

// 关闭文件
fclose(pf);
pf = NULL;
3.2.4 完整示例(追加文件)
// 打开文件(a模式,在末尾追加)​
FILE* pf = fopen("test.txt", "a");if (pf == NULL) {perror("fopen failed");return 1;}​
​
// 追加数据(原有内容不会被删除)​
fputs("\nappend content", pf);​
​
// 关闭文件​
fclose(pf);​
pf = NULL;

4. 文件的顺序读写

4.1 字符输入函数 fgetc

  • 函数原型int fgetc(FILE* stream);

  • 功能:从指定的流(stream)中读取一个字符(以int类型返回,包含EOF)。

  • 参数stream为文件指针(如pf)或标准输入流(stdin,对应键盘输入)。

  • 返回值:成功返回读取的字符(ASCII值),失败或读到文件末尾返回EOF(通常为-1)。

  • 什么是流:流是数据输入/输出的抽象概念,C语言中所有输入/输出操作都通过流完成。常见流包括:

    • 文件流:通过fopen打开的文件(如pf = fopen("test.txt", "r"))。
    • 标准流:程序启动时默认打开的3个流(stdinstdoutstderr)。
  • 示例1:从文件读取字符

FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

int ch;
// 循环读取文件,直到遇到EOF
while ((ch = fgetc(pf)) != EOF) {
    printf("%c", ch); // 打印读取的字符(输出到stdout)
}

fclose(pf);
pf = NULL;
  • 示例 2:从标准输入流(键盘)读取字符
int ch = fgetc(stdin); // 从键盘读取一个字符(如输入'a')
printf("你输入的字符是:%c\n", ch); // 输出'a'

4.2 字符输出函数 fputc

  • 函数原型int fputc(int character, FILE* stream);

  • 功能:将一个字符(character)写入指定的流(stream)。

  • 参数character为要写入的字符(int类型,实际取低8位);stream为文件指针或标准输出流(stdout,对应屏幕)。

  • 返回值:成功返回写入的字符,失败返回EOF

  • 示例1:向文件写入字符

FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 写入字符'A'、'B'、'C'
fputc('A', pf);
fputc('B', pf);
fputc('C', pf); // 文件内容为"ABC"

fclose(pf);
pf = NULL;
  • 示例 2:向标准输出流(屏幕)输出字符​
fputc('H', stdout);
fputc('i', stdout); // 屏幕输出"Hi"

4.3 文本行输出函数 fputs

  • 函数原型int fputs(const char* str, FILE* stream);

  • 功能:将字符串(str)写入指定的流(stream),不自动添加换行符(\n)。

  • 参数str为要写入的字符串(以\0结尾);stream为文件指针或标准输出流。

  • 返回值:成功返回非负值,失败返回EOF

  • 示例1:向文件写入字符串(手动换行)

FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 写入两行字符串(需手动添加\n)
fputs("Hello World", pf);
fputs("\nWelcome to C", pf); // 文件内容:Hello World\nWelcome to C

fclose(pf);
pf = NULL;
  • 示例 2:向屏幕输出字符串
fputs("This is a test", stdout); // 屏幕输出"This is a test"(无换行)
fputs("\nAnother line", stdout); // 换行后输出"Another line"

4.4 文本行输入函数 fgets

  • 函数原型char* fgets(char* str, int num, FILE* stream);

  • 功能:从流(stream)中读取最多num-1个字符(预留1个位置给\0),遇到换行符(\n)或文件末尾则停止。

  • 参数str为存储读取内容的缓冲区;num为最大读取字符数(含\0);stream为文件指针或标准输入流。

  • 返回值:成功返回str(缓冲区地址),失败或读到文件末尾返回NULL

  • 示例1:从文件读取字符串(限制长度)

FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

char buf[5]; // 最多读取4个字符(+1个\0)
fgets(buf, 5, pf); // 假设文件内容为"abcdef"
printf("%s\n", buf); // 输出"abcd"(仅读4个字符)

fclose(pf);
pf = NULL;
  • 示例 2:从键盘读取一行(含换行符)
char buf[100];
printf("请输入:");
fgets(buf, 100, stdin); // 读取键盘输入(如输入"hello"并回车)
printf("你输入的是:%s", buf); // 输出"hello\n"(包含换行符)

4.5 格式化输出函数 fprintf

  • 函数原型int fprintf(FILE* stream, const char* format, ...);

  • 功能:按指定格式(format)将数据写入流(stream),与printf的区别是printf固定输出到stdout(屏幕),而fprintf可输出到任意流(如文件)。

  • 参数stream为文件指针或标准流;format为格式控制字符串;后续参数为要输出的数据。

  • 返回值:成功返回写入的字符数,失败返回负数。

  • 示例:向文件写入格式化数据

FILE* pf = fopen("user.txt", "w");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

char name[] = "Alice";
int age = 25;
float score = 95.5;

// 按格式写入文件
fprintf(pf, "Name: %s, Age: %d, Score: %.1f\n", name, age, score);
// 文件内容:Name: Alice, Age: 25, Score: 95.5

fclose(pf);
pf = NULL;

对比 printf:printf("Name: %s\n", name)等价于fprintf(stdout, "Name: %s\n", name)

4.6 格式化输入函数 fscanf

  • 函数原型int fscanf(FILE* stream, const char* format, ...);

  • 功能:按指定格式(format)从流(stream)中读取数据,与scanf的区别是scanf固定从stdin(键盘)读取,而fscanf可从任意流(如文件)读取。

  • 参数stream为文件指针或标准流;format为格式控制字符串;后续参数为存储数据的地址。

  • 返回值:成功返回读取的项目数,失败或读到文件末尾返回EOF

  • 示例:从文件读取格式化数据

FILE* pf = fopen("user.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

char name[20];
int age;
float score;

// 按格式读取文件内容(假设文件为"Name: Alice, Age: 25, Score: 95.5")
fscanf(pf, "Name: %s, Age: %d, Score: %f", name, &age, &score);
printf("解析结果:%s, %d, %.1f\n", name, age, score); // 输出"Alice, 25, 95.5"

fclose(pf);
pf = NULL;

4.7 标准流的默认打开

  • C程序启动时会自动打开3个标准流,无需手动fopen

    1. stdin:标准输入流,对应输入设备(通常是键盘),可作为fgetcfscanf等函数的stream参数。
    2. stdout:标准输出流,对应输出设备(通常是屏幕),可作为fputcfprintf等函数的stream参数。
    3. stderr:标准错误流,对应输出设备(通常是屏幕),用于输出错误信息(如perror的输出)。
  • 示例:使用标准流替代文件指针

// 从键盘读取字符(stdin)
int ch = fgetc(stdin);
// 向屏幕输出字符(stdout)
fputc(ch, stdout);

// 格式化输出到屏幕(等价于printf)
fprintf(stdout, "Hello, %s\n", "World"); // 输出"Hello, World"

4.8 二进制输入函数 fread

  • 函数原型size_t fread(void* ptr, size_t size, size_t count, FILE* stream);

  • 功能:从流(stream)中读取二进制数据,存储到ptr指向的缓冲区。

  • 参数ptr为接收数据的缓冲区;size为每个数据项的字节数;count为数据项个数;stream为文件指针。

  • 返回值:成功返回实际读取的完整数据项个数(小于等于count),失败或读到文件末尾返回0。

  • 示例:读取二进制文件中的结构体数组

typedef struct {
    int id;
    char name[20];
} Student;

FILE* pf = fopen("students.dat", "rb"); // 二进制读模式
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

Student s[3];
// 读取3个Student类型数据(每个4+20=24字节)
size_t n = fread(s, sizeof(Student), 3, pf);
printf("成功读取%d个学生数据\n", n);

fclose(pf);
pf = NULL;

4.9 二进制输出函数 fwrite

  • 函数原型size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);

  • 功能:将ptr指向的二进制数据按指定大小和个数写入流(stream)。

  • 参数ptr为待写入数据的地址;size为每个数据项的字节数;count为数据项个数;stream为文件指针。

  • 返回值:成功返回实际写入的完整数据项个数(等于count,否则表示出错)。

  • 示例:向二进制文件写入结构体数组

typedef struct {
    int id;
    char name[20];
} Student;

Student s[2] = {
    {1, "Alice"},
    {2, "Bob"}
};

FILE* pf = fopen("students.dat", "wb"); // 二进制写模式
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 写入2个Student类型数据(每个24字节)
size_t n = fwrite(s, sizeof(Student), 2, pf);
if (n == 2) {
    printf("成功写入2个学生数据\n");
}

fclose(pf);
pf = NULL;

5.文件的随机读写

5.1 fseek函数

  • 函数原型int fseek(FILE* stream, long int offset, int origin);

  • 功能:根据指定的起始位置(origin)和偏移量(offset),移动文件指针到目标位置,实现随机读写(非顺序读写)。

  • 参数详解

    • stream:文件指针(需已打开的文件)。
    • offset:偏移量(可正可负),正数表示向文件末尾方向移动,负数表示向文件起始方向移动。
    • origin:起始位置,有三种取值:
      • SEEK_SET:以文件开头为起始点( offset 为相对于文件开头的偏移量)。
      • SEEK_CUR:以文件指针当前位置为起始点( offset 为相对于当前位置的偏移量)。
      • SEEK_END:以文件末尾为起始点( offset 为相对于文件末尾的偏移量)。
  • 返回值:成功返回0,失败返回非0。

  • 示例1:从文件指定位置读取内容

FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 假设文件内容为"abcdefghij"(共10个字符)
// 1. 从文件开头偏移3个字符(指向第4个字符'd')
fseek(pf, 3, SEEK_SET);
int ch1 = fgetc(pf);
printf("偏移后读取的字符:%c\n", ch1); // 输出'd'

// 2. 从当前位置('d'后)偏移2个字符(指向'f')
fseek(pf, 2, SEEK_CUR);
int ch2 = fgetc(pf);
printf("再次偏移后读取的字符:%c\n", ch2); // 输出'f'

// 3. 从文件末尾偏移-2个字符(指向'i')
fseek(pf, -2, SEEK_END);
int ch3 = fgetc(pf);
printf("从末尾偏移后读取的字符:%c\n", ch3); // 输出'i'

fclose(pf);
pf = NULL;
  • 示例 2:在文件指定位置写入内容
FILE* pf = fopen("test.txt", "r+"); // 读写模式(不覆盖原有内容)
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 原文件内容:"abcdef"
fseek(pf, 2, SEEK_SET); // 定位到第3个字符'c'的位置
fputc('X', pf); // 将'c'替换为'X',文件内容变为"abXdef"

fclose(pf);
pf = NULL;

5.2 ftell函数

  • 函数原型long int ftell(FILE* stream);

  • 功能:计算文件指针当前位置相对于文件起始位置的偏移量(以字节为单位),用于获取当前读写位置。

  • 参数stream为文件指针。

  • 返回值:成功返回当前偏移量(正数),失败返回-1L。

  • 示例:结合fseek使用,获取偏移量

FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 移动文件指针到第5个字符(偏移量为4,从0开始计数)
fseek(pf, 4, SEEK_SET);
// 获取当前偏移量
long int pos = ftell(pf);
printf("当前文件指针位置:%ld\n", pos); // 输出4(相对于文件开头偏移4字节)

fclose(pf);
pf = NULL;
  • 应用场景:在使用 fseek 后,可通过 ftell 验证指针是否移动到预期位置;也可用于计算文件长度(fseek 到文件末尾,再用 ftell 获取偏移量)。

5.3 rewind函数

  • 函数原型void rewind(FILE* stream);

  • 功能:将文件指针重新定位到文件的起始位置,相当于fseek(stream, 0, SEEK_SET);,但无返回值。

  • 参数stream为文件指针。

  • 示例:重置文件指针位置

FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

// 读取文件前5个字符
char buf[6];
fread(buf, 1, 5, pf);
buf[5] = '\0';
printf("前5个字符:%s\n", buf); // 输出前5个字符

// 将文件指针重置到起始位置
rewind(pf);

// 重新读取文件前3个字符
fread(buf, 1, 3, pf);
buf[3] = '\0';
printf("重新读取前3个字符:%s\n", buf); // 输出前3个字符

fclose(pf);
pf = NULL;
  • 优势: 比fseek更简洁,专门用于将指针复位到文件开头,无需指定偏移量和起始位置。

6. 文本文件和二进制文件

  • 数据文件按数据的组织形式可分为文本文件二进制文件,核心区别在于数据在外存(如磁盘)中的存储形式是否经过ASCII转换。
6.1 文本文件
  • 定义:数据在外存中以ASCII码形式存储,数值型数据会先转换为对应的ASCII字符序列再存储。
  • 特点
    • 可读性强(可用文本编辑器直接查看内容)。
    • 存储数值型数据时占用空间较大(每个数字字符占1字节)。
  • 示例:整数10000的文本存储
    • 10000由字符’1’、‘0’、‘0’、‘0’、'0’组成,每个字符对应ASCII码0x31、0x30、0x30、0x30、0x30。
    • 存储占用5字节,用文本编辑器打开可直接看到"10000"。
6.2 二进制文件
  • 定义:数据在外存中以内存中的二进制形式直接存储,不经过ASCII转换(与数据在内存中的存储形式一致)。
  • 特点
    • 可读性差(用文本编辑器打开显示乱码)。
    • 存储高效,占用空间小(数值型数据按其实际字节数存储)。
  • 示例:整数10000的二进制存储
    • 在32位系统中,int类型的10000在内存中存储为二进制0010011100010000(十六进制0x2710),占用4字节。
    • fwrite写入文件后,文件中直接存储这4字节,用文本编辑器打开无法识别(显示乱码)。
6.3 代码验证:文本与二进制存储的差异
#include <stdio.h>
int main() {
    int a = 10000;
    
    // 二进制存储(直接写入内存中的二进制形式)
    FILE* pf_bin = fopen("binary.dat", "wb");
    if (pf_bin != NULL) {
        fwrite(&a, sizeof(int), 1, pf_bin); // 写入4字节
        fclose(pf_bin);
        pf_bin = NULL;
    }
    
    // 文本存储(先转换为ASCII码)
    FILE* pf_txt = fopen("text.txt", "w");
    if (pf_txt != NULL) {
        fprintf(pf_txt, "%d", a); // 写入"10000",5字节
        fclose(pf_txt);
        pf_txt = NULL;
    }
    
    return 0;
}
  • 结果对比:​
    binary.dat大小为 4 字节(二进制存储),text.txt大小为5字节(文本存储)。​
    用文本编辑器打开text.txt可看到 "10000",打开binary.dat显示乱码。

7. 文件读取结束的判定

7.1 正确理解与使用feof函数

  • 函数原型int feof(FILE* stream);
  • 功能:用于文件读取结束后,判断结束原因:是正常读到文件末尾(返回非0),还是因读取错误(如磁盘错误)而结束(返回0)。
  • 核心误区feof不能直接用于判断文件是否结束(如循环条件中用!feof(pf)是错误的),它仅用于结束后的原因判断。

7.2 文本文件读取结束的判定

  • 判定依据:通过读取函数的返回值判断,而非feof

    • fgetc:读取结束时返回EOF(可直接作为判定条件)。
    • fgets:读取结束时返回NULL(可直接作为判定条件)。
  • 示例:用fgetc读取文本文件并判断结束

FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

int ch;
// 循环读取,直到fgetc返回EOF(正常结束)
while ((ch = fgetc(pf)) != EOF) {
    putchar(ch); // 输出读取的字符
}

// 读取结束后,用feof判断原因
if (feof(pf)) {
    printf("\n正常结束:已读到文件末尾\n");
} else {
    printf("\n异常结束:读取过程中发生错误\n");
}

fclose(pf);
pf = NULL;
  • 示例:用 fgets 读取文本文件并判断结束
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

char buf[1024];
// 循环读取,直到fgets返回NULL(正常结束)
while (fgets(buf, sizeof(buf), pf) != NULL) {
    fputs(buf, stdout); // 输出读取的行
}

// 读取结束后,用feof判断原因
if (feof(pf)) {
    printf("\n正常结束:已读到文件末尾\n");
} else {
    printf("\n异常结束:读取过程中发生错误\n");
}

fclose(pf);
pf = NULL;

7.3 二进制文件读取结束的判定

  • 判定依据:通过fread的返回值判断,若返回值小于实际请求读取的个数,说明读取结束。

  • fread返回值:成功返回实际读取的完整数据项个数,失败或读到文件末尾返回0(或小于请求个数)。

  • 示例:用fread读取二进制文件并判断结束

typedef struct {
    int id;
    char name[20];
} Student;

FILE* pf = fopen("students.dat", "rb");
if (pf == NULL) {
    perror("fopen failed");
    return 1;
}

Student s;
size_t req = 1; // 每次请求读取1个Student
size_t n;

// 循环读取,直到fread返回值小于请求个数
while ((n = fread(&s, sizeof(Student), req, pf)) == req) {
    // 处理读取到的学生数据
    printf("ID: %d, Name: %s\n", s.id, s.name);
}

// 读取结束后,用feof判断原因
if (feof(pf)) {
    printf("正常结束:已读完所有学生数据\n");
} else if (ferror(pf)) { // 配合ferror判断是否为错误结束
    perror("读取错误");
}

fclose(pf);
pf = NULL;

说明:ferror(pf)用于判断是否因错误导致读取结束(如磁盘损坏),返回非 0 表示有错误。

8. 文件缓冲区

  • 定义:缓冲文件系统中,系统会为每个正在使用的文件在内存中自动开辟一块文件缓冲区,用于临时存储读写数据。
  • 工作机制
    • 写操作:数据先写入缓冲区,当缓冲区满、调用fflushfclose时,才将缓冲区数据一次性写入磁盘。
    • 读操作:数据先从磁盘读到缓冲区,当缓冲区数据被程序读取完后,再从磁盘读取新数据填满缓冲区。
  • 缓冲区大小:由C编译系统决定(通常为512字节或4096字节)。

8.1 缓冲区的影响与刷新

  • fflush函数int fflush(FILE* stream);,强制将缓冲区中的数据写入磁盘(写操作)或清空缓冲区(读操作),无缓冲时无效果。
  • 示例:验证缓冲区存在
#include <stdio.h>
#include <unistd.h> // 包含sleep函数(Linux系统)

int main() {
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL) {
        perror("fopen failed");
        return 1;
    }

    fputs("Hello", pf); // 数据写入缓冲区(未立即写入磁盘)
    printf("等待5秒...\n");
    sleep(5); // 此时查看test.txt,内容为空(数据在缓冲区)

    fflush(pf); // 刷新缓冲区,数据写入磁盘
    printf("刷新缓冲区后,等待5秒...\n");
    sleep(5); // 此时查看test.txt,内容为"Hello"

    fclose(pf); // 关闭文件时也会刷新缓冲区
    pf = NULL;
    return 0;
}

说明:若不调用fflush或fclose,程序异常退出时,缓冲区数据可能丢失(未写入磁盘)。

8.2 缓冲区相关注意事项

  1. 必须刷新或关闭文件:因缓冲区存在,写文件后需调用fflush刷新或fclose关闭文件,否则可能导致数据未写入磁盘。

    FILE* pf = fopen("test.txt", "w");
    fputs("Hello", pf);
    // 若此时程序崩溃,"Hello"可能未写入磁盘(仍在缓冲区)
    fclose(pf); // 关闭时刷新,确保数据写入
    
  2. fclosefflush的区别:

  • fflush:仅刷新缓冲区,不关闭文件,可继续操作文件。​
  • fclose:先刷新缓冲区,再关闭文件,之后不可再操作文件。​
  1. 标准输出流的特殊性stdout(屏幕输出)通常是行缓冲,遇到\n时自动刷新,而文件流是全缓冲(满缓冲才刷新)。
printf("Hello"); // 可能在缓冲区(未显示)
printf("World\n"); // 遇到\n,刷新缓冲区,显示"HelloWorld"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值