欢迎光临
我们一直在努力

C语言文件操作完全指南

C语言文件操作完全指南

写程序的时候,数据怎么存是个大问题。你肯定遇到过这种情况:程序跑得好好的,结果一关掉,刚才输入的数据全没了。下次再运行,又得重新来一遍。

这就是文件发挥作用的地方——它能让数据持久化保存,程序退出后数据还在,下次运行还能读出来继续用。

今天就把C语言文件操作从头到尾梳理一遍,包括文件的打开关闭、读写方式、随机定位、结束判定这些核心知识点。


一、为什么需要文件?

程序运行时,数据都存在内存里。内存有个特点:程序一退出,内存回收,数据就没了。

如果你想让数据保存下来,比如:

  • 游戏存档
  • 配置信息
  • 日志记录
  • 用户数据

那就得把数据写到文件里,存到硬盘上。硬盘上的数据不会因为程序退出而丢失,这就是文件的意义——数据持久化。


二、什么是文件?

从程序员的角度看,文件分两类:

程序文件

跟程序本身相关的文件:

  • 源程序文件(.c)
  • 目标文件(.obj)
  • 可执行文件(.exe)

数据文件

程序运行时读写的数据,比如:

  • 从文件读取输入数据
  • 把结果输出到文件

本章讨论的是数据文件。

文件名

一个完整的文件名包含三部分:

文件路径 + 文件名主干 + 文件后缀

比如 c:\\code\\test.txt,平时我们说的"文件名"通常就指这个完整路径。


三、二进制文件和文本文件

根据数据的存储形式,数据文件分为文本文件和二进制文件。

存储方式的区别

  • 文本文件:数据以ASCII码形式存储,每个字符占一个字节
  • 二进制文件:数据以二进制形式直接存储,不加转换

举个例子,整数 10000:

存储方式占用空间存储内容
文本文件 5字节 ‘1’ ‘0’ ‘0’ ‘0’ ‘0’ 五个字符的ASCII码
二进制文件 4字节 10000的二进制表示(int类型)

文本文件存的是字符,二进制文件存的是原始数据。

写个测试代码

#include <stdio.h>

int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb"); // 以二进制写入模式打开
fwrite(&a, 4, 1, pf); // 把a的二进制形式写入文件
fclose(pf);
pf = NULL;
return 0;
}

用VS打开这个文件,选择"二进制编辑器",就能看到 10000 的二进制表示(小端存储):10 27 00 00。


四、文件的打开和关闭

流(Stream)的概念

C程序要跟外部设备(键盘、显示器、硬盘等)打交道,不同设备的操作方式不一样。为了统一处理,C语言抽象出了流的概念——你可以把它想象成一条流淌着字符的河。

一般操作流程是:打开流 → 读写数据 → 关闭流。

三个标准流

C程序启动时,默认打开了三个流:

流类型说明
stdin 标准输入流 通常对应键盘,scanf 就从这里读
stdout 标准输出流 通常对应显示器,printf 往这里写
stderr 标准错误流 通常对应显示器,用于输出错误信息

这三个流的类型都是 FILE*,叫文件指针。我们用 scanf、printf 时没感觉在"打开流",就是因为它们已经默认打开了。

文件指针 FILE*

每个被使用的文件,系统都会在内存里开辟一个文件信息区,存放文件的各种信息(文件名、状态、当前位置等)。这些信息保存在一个 FILE 结构体里。

// VS2013 中 FILE 的定义(不同编译器可能略有差异)
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};

typedef struct _iobuf FILE;

我们通过 FILE* 指针来操作文件:

FILE* pf; // 定义一个文件指针变量

pf 指向某个文件的文件信息区,通过它就能间接访问那个文件。

fopen 和 fclose

文件读写前要先打开,用完后要关闭。

// 打开文件
FILE* fopen(const char* filename, const char* mode);

// 关闭文件
int fclose(FILE* stream);

filename 是文件名,mode 是打开模式。

文件打开模式

模式含义文件不存在时
"r" 只读(文本文件) 出错
"w" 只写(文本文件) 创建新文件
"a" 追加(文本文件) 创建新文件
"rb" 只读(二进制文件) 出错
"wb" 只写(二进制文件) 创建新文件
"ab" 追加(二进制文件) 创建新文件
"r+" 读写(文本文件) 出错
"w+" 读写(文本文件) 创建新文件
"a+" 读写(文本文件,从末尾开始) 创建新文件

注意:"w" 模式会清空原文件内容!如果文件已存在,用 "w" 打开后文件就变成空的了。想保留原内容并在后面追加,用 "a" 模式。

基本使用示例

#include <stdio.h>

int main()
{
FILE* pFile;
pFile = fopen("myfile.txt", "w"); // 以写入模式打开文件

if (pFile != NULL) // 检查是否打开成功
{
fputs("fopen example", pFile); // 写入字符串
fclose(pFile); // 关闭文件
}
return 0;
}

养成好习惯:打开文件后一定要检查返回值是否为 NULL,防止文件打开失败导致后续操作出错。


五、文件的顺序读写

顺序读写就是从头到尾依次读写,文件指针自动往后移动。

常用读写函数

函数功能适用于
fgetc 读取一个字符 所有输入流
fputc 写入一个字符 所有输出流
fgets 读取一行 所有输入流
fputs 写入一行 所有输出流
fscanf 格式化读取 所有输入流
fprintf 格式化写入 所有输出流
fread 二进制读取 文件输入流
fwrite 二进制写入 文件输出流

"所有输入流"包括 stdin 和文件输入流,"所有输出流"包括 stdout 和文件输出流。

fgetc / fputc —— 字符读写

int fgetc(FILE* stream); // 从流中读取一个字符
int fputc(int c, FILE* stream); // 向流中写入一个字符

示例:把文件内容逐字符读出来打印

#include <stdio.h>

int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("打开文件失败");
return 1;
}

int ch;
while ((ch = fgetc(fp)) != EOF) // 读到文件末尾返回EOF
{
putchar(ch);
}

fclose(fp);
return 0;
}

fgets / fputs —— 行读写

char* fgets(char* str, int n, FILE* stream); // 读取一行(最多n-1个字符)
int fputs(const char* str, FILE* stream); // 写入一行

fgets 会读到换行符或文件末尾,最多读 n-1 个字符,末尾自动加 \\0。

fscanf / fprintf —— 格式化读写

这两个函数跟 scanf、printf 用法几乎一样,只是多了一个文件指针参数:

int fscanf(FILE* stream, const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);

示例:写入格式化数据,再读出来

#include <stdio.h>

int main()
{
FILE* fp = fopen("data.txt", "w");
if (fp == NULL) return 1;

// 写入格式化数据
fprintf(fp, "%d %f %s\\n", 100, 3.14, "hello");
fclose(fp);

// 重新打开读取
fp = fopen("data.txt", "r");
int a;
float b;
char s[20];
fscanf(fp, "%d %f %s", &a, &b, s);
printf("读出: %d, %.2f, %s\\n", a, b, s); // 读出: 100, 3.14, hello

fclose(fp);
return 0;
}

fread / fwrite —— 二进制读写

这两个是二进制读写的核心函数,适合读写结构体、数组等:

size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);

  • ptr:数据存放地址
  • size:每个元素的大小
  • count:元素个数
  • 返回值:实际读写的元素个数

示例:写入并读取一个数组

#include <stdio.h>

int main()
{
double a[5] = {1.0, 2.0, 3.0, 4.0, 5.0};

// 写入二进制文件
FILE* fp = fopen("data.bin", "wb");
fwrite(a, sizeof(double), 5, fp); // 写入5个double
fclose(fp);

// 读取二进制文件
double b[5];
fp = fopen("data.bin", "rb");
size_t ret = fread(b, sizeof(double), 5, fp);

if (ret == 5)
printf("读取成功\\n");
else
printf("读取不完整,只读了 %zu 个\\n", ret);

fclose(fp);
return 0;
}

scanf/fscanf/sscanf 对比

函数来源
scanf 从标准输入(stdin)读取
fscanf 从指定文件流读取
sscanf 从字符串读取

同理,printf、fprintf、sprintf 是输出版本,sprintf 可以把格式化数据写到字符串里。


六、文件的随机读写

顺序读写只能从头到尾,但有时候我们需要跳来跳去,比如直接定位到文件中间某个位置。这就需要随机读写函数。

fseek —— 定位文件指针

int fseek(FILE* stream, long int offset, int origin);

  • offset:偏移量
  • origin:起始位置,有三个选项:
    • SEEK_SET:文件开头
    • SEEK_CUR:当前位置
    • SEEK_END:文件末尾

示例:修改文件中间的内容

#include <stdio.h>

int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile); // 写入字符串

fseek(pFile, 9, SEEK_SET); // 定位到第9个字节('a'的位置)
fputs(" sam", pFile); // 把 "an a" 替换成 " sam"

fclose(pFile);
return 0;
}

执行后文件内容变成:This is a sample.

ftell —— 获取当前位置

long int ftell(FILE* stream);

返回文件指针相对于开头的偏移量。常用技巧:配合 fseek 获取文件大小。

#include <stdio.h>

int main()
{
FILE* pFile;
long size;

pFile = fopen("myfile.txt", "rb");
if (pFile == NULL)
perror("打开文件失败");
else
{
fseek(pFile, 0, SEEK_END); // 定位到文件末尾
size = ftell(pFile); // 此时偏移量就是文件大小
fclose(pFile);
printf("文件大小: %ld 字节\\n", size);
}
return 0;
}

rewind —— 回到文件开头

void rewind(FILE* stream);

把文件指针重置到文件开头,相当于 fseek(fp, 0, SEEK_SET)。

示例:先写入26个字母,再从头读取

#include <stdio.h>

int main()
{
int n;
FILE* pFile;
char buffer[27];

pFile = fopen("myfile.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, pFile); // 写入 A-Z

rewind(pFile); // 回到开头

fread(buffer, 1, 26, pFile); // 读取刚才写入的内容
fclose(pFile);

buffer[26] = '\\0';
printf("%s\\n", buffer); // 输出: ABCDEFGHIJKLMNOPQRSTUVWXYZ
return 0;
}


七、文件读取结束的判定

feof 的正确用法

大坑警告:很多人以为 feof 返回真就表示读到文件末尾了,这是错误的理解。

feof 的真正作用是:在文件读取结束后,判断结束的原因是不是"遇到文件尾"。

正确的判断方式:

文本文件
  • fgetc:判断返回值是否为 EOF
  • fgets:判断返回值是否为 NULL

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
int c; // 注意用int,不是char,因为要处理EOF
FILE* fp = fopen("test.txt", "r");

if (!fp) {
perror("打开文件失败");
return EXIT_FAILURE;
}

// fgetc 读取失败或遇到文件末尾都会返回EOF
while ((c = fgetc(fp)) != EOF)
{
putchar(c);
}

// 判断结束原因
if (ferror(fp))
puts("读取时发生I/O错误");
else if (feof(fp))
puts("成功读到文件末尾");

fclose(fp);
return 0;
}

二进制文件

fread 返回实际读取的元素个数,判断是否小于预期个数:

#include <stdio.h>

enum { SIZE = 5 };

int main(void)
{
double a[SIZE] = {1., 2., 3., 4., 5.};
FILE* fp = fopen("test.bin", "wb");
fwrite(a, sizeof *a, SIZE, fp); // 先写入
fclose(fp);

double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof *b, SIZE, fp);

if (ret_code == SIZE) {
puts("读取成功,内容:");
for (int n = 0; n < SIZE; ++n)
printf("%f ", b[n]);
putchar('\\n');
}
else {
if (feof(fp))
printf("错误:文件意外结束\\n");
else if (ferror(fp))
perror("读取错误");
}

fclose(fp);
return 0;
}

总结一下判断逻辑:

  • 先读数据
  • 检查返回值(EOF、NULL、或实际读取个数)
  • 再用 feof/ferror 判断结束原因

  • 八、文件缓冲区

    缓冲机制

    C语言采用缓冲文件系统:系统自动为每个正在使用的文件在内存中开辟一块缓冲区。

    • 写文件时:数据先送到内存缓冲区,缓冲区装满后才一起写到磁盘
    • 读文件时:从磁盘读入数据填充缓冲区,再从缓冲区逐个送给程序

    这样做的目的是减少磁盘I/O次数,提高效率。

    缓冲区的影响

    看个实验:

    #include <stdio.h>
    #include <windows.h>

    int main()
    {
    FILE* pf = fopen("test.txt", "w");
    fputs("abcdef", pf); // 数据先进入输出缓冲区

    printf("睡眠10秒——此时打开test.txt,发现文件是空的\\n");
    Sleep(10000);

    printf("刷新缓冲区\\n");
    fflush(pf); // 强制把缓冲区内容写入磁盘

    printf("再睡眠10秒——此时打开test.txt,文件有内容了\\n");
    Sleep(10000);

    fclose(pf); // fclose 也会刷新缓冲区
    pf = NULL;
    return 0;
    }

    运行这个程序,你会发现:

    • 写入 fputs 后立即打开文件,内容还没进去
    • 调用 fflush 后,内容才真正写入磁盘
    • fclose 关闭文件时也会自动刷新缓冲区

    结论

    重要:因为有缓冲区的存在,操作文件时要注意:

    • 写完数据后及时 fflush 或 fclose
    • 否则程序异常退出时,缓冲区数据可能丢失

    九、总结与速查表

    文件操作流程

    打开文件 (fopen) → 检查是否成功 → 读写数据 → 关闭文件 (fclose)

    核心函数速查

    操作函数说明
    打开文件 fopen 返回 FILE*,失败返回 NULL
    关闭文件 fclose 关闭并刷新缓冲区
    字符读写 fgetc / fputc 一次一个字符
    行读写 fgets / fputs 一次一行
    格式化读写 fscanf / fprintf 类似 scanf/printf
    二进制读写 fread / fwrite 读写任意类型数据
    定位 fseek 移动文件指针
    获取位置 ftell 返回当前位置
    回到开头 rewind 重置到文件开头
    判断结束原因 feof 读完后判断是否因EOF结束
    判断错误 ferror 判断是否发生I/O错误
    刷新缓冲区 fflush 强制写入磁盘

    常见易错点

  • "w" 模式会清空原文件:想追加用 "a"
  • 打开文件后要检查返回值:防止 NULL 指针操作
  • feof 不能用来判断是否读完:要先读,再判断结束原因
  • 二进制文件要用 "rb" / "wb":不要用文本模式
  • 记得关闭文件:不关闭可能导致数据丢失(缓冲区未刷新)
  • fgetc 返回 int 不是 char:要能表示 EOF
  • 把这些知识点搞清楚,C语言文件操作就基本拿下了。多写几个小练习——比如实现一个简单的配置文件读写、日志记录功能,手感很快就上来了。

    C语言文件操作完全指南

    写程序的时候,数据怎么存是个大问题。你肯定遇到过这种情况:程序跑得好好的,结果一关掉,刚才输入的数据全没了。下次再运行,又得重新来一遍。

    这就是文件发挥作用的地方——它能让数据持久化保存,程序退出后数据还在,下次运行还能读出来继续用。

    今天就把C语言文件操作从头到尾梳理一遍,包括文件的打开关闭、读写方式、随机定位、结束判定这些核心知识点。


    一、为什么需要文件?

    程序运行时,数据都存在内存里。内存有个特点:程序一退出,内存回收,数据就没了。

    如果你想让数据保存下来,比如:

    • 游戏存档
    • 配置信息
    • 日志记录
    • 用户数据

    那就得把数据写到文件里,存到硬盘上。硬盘上的数据不会因为程序退出而丢失,这就是文件的意义——数据持久化。


    二、什么是文件?

    从程序员的角度看,文件分两类:

    程序文件

    跟程序本身相关的文件:

    • 源程序文件(.c)
    • 目标文件(.obj)
    • 可执行文件(.exe)

    数据文件

    程序运行时读写的数据,比如:

    • 从文件读取输入数据
    • 把结果输出到文件

    本章讨论的是数据文件。

    文件名

    一个完整的文件名包含三部分:

    文件路径 + 文件名主干 + 文件后缀

    比如 c:\\code\\test.txt,平时我们说的"文件名"通常就指这个完整路径。


    三、二进制文件和文本文件

    根据数据的存储形式,数据文件分为文本文件和二进制文件。

    存储方式的区别

    • 文本文件:数据以ASCII码形式存储,每个字符占一个字节
    • 二进制文件:数据以二进制形式直接存储,不加转换

    举个例子,整数 10000:

    存储方式占用空间存储内容
    文本文件 5字节 ‘1’ ‘0’ ‘0’ ‘0’ ‘0’ 五个字符的ASCII码
    二进制文件 4字节 10000的二进制表示(int类型)

    文本文件存的是字符,二进制文件存的是原始数据。

    写个测试代码

    #include <stdio.h>

    int main()
    {
    int a = 10000;
    FILE* pf = fopen("test.txt", "wb"); // 以二进制写入模式打开
    fwrite(&a, 4, 1, pf); // 把a的二进制形式写入文件
    fclose(pf);
    pf = NULL;
    return 0;
    }

    用VS打开这个文件,选择"二进制编辑器",就能看到 10000 的二进制表示(小端存储):10 27 00 00。


    四、文件的打开和关闭

    流(Stream)的概念

    C程序要跟外部设备(键盘、显示器、硬盘等)打交道,不同设备的操作方式不一样。为了统一处理,C语言抽象出了流的概念——你可以把它想象成一条流淌着字符的河。

    一般操作流程是:打开流 → 读写数据 → 关闭流。

    三个标准流

    C程序启动时,默认打开了三个流:

    流类型说明
    stdin 标准输入流 通常对应键盘,scanf 就从这里读
    stdout 标准输出流 通常对应显示器,printf 往这里写
    stderr 标准错误流 通常对应显示器,用于输出错误信息

    这三个流的类型都是 FILE*,叫文件指针。我们用 scanf、printf 时没感觉在"打开流",就是因为它们已经默认打开了。

    文件指针 FILE*

    每个被使用的文件,系统都会在内存里开辟一个文件信息区,存放文件的各种信息(文件名、状态、当前位置等)。这些信息保存在一个 FILE 结构体里。

    // VS2013 中 FILE 的定义(不同编译器可能略有差异)
    struct _iobuf {
    char *_ptr;
    int _cnt;
    char *_base;
    int _flag;
    int _file;
    int _charbuf;
    int _bufsiz;
    char *_tmpfname;
    };

    typedef struct _iobuf FILE;

    我们通过 FILE* 指针来操作文件:

    FILE* pf; // 定义一个文件指针变量

    pf 指向某个文件的文件信息区,通过它就能间接访问那个文件。

    fopen 和 fclose

    文件读写前要先打开,用完后要关闭。

    // 打开文件
    FILE* fopen(const char* filename, const char* mode);

    // 关闭文件
    int fclose(FILE* stream);

    filename 是文件名,mode 是打开模式。

    文件打开模式

    模式含义文件不存在时
    "r" 只读(文本文件) 出错
    "w" 只写(文本文件) 创建新文件
    "a" 追加(文本文件) 创建新文件
    "rb" 只读(二进制文件) 出错
    "wb" 只写(二进制文件) 创建新文件
    "ab" 追加(二进制文件) 创建新文件
    "r+" 读写(文本文件) 出错
    "w+" 读写(文本文件) 创建新文件
    "a+" 读写(文本文件,从末尾开始) 创建新文件

    注意:"w" 模式会清空原文件内容!如果文件已存在,用 "w" 打开后文件就变成空的了。想保留原内容并在后面追加,用 "a" 模式。

    基本使用示例

    #include <stdio.h>

    int main()
    {
    FILE* pFile;
    pFile = fopen("myfile.txt", "w"); // 以写入模式打开文件

    if (pFile != NULL) // 检查是否打开成功
    {
    fputs("fopen example", pFile); // 写入字符串
    fclose(pFile); // 关闭文件
    }
    return 0;
    }

    养成好习惯:打开文件后一定要检查返回值是否为 NULL,防止文件打开失败导致后续操作出错。


    五、文件的顺序读写

    顺序读写就是从头到尾依次读写,文件指针自动往后移动。

    常用读写函数

    函数功能适用于
    fgetc 读取一个字符 所有输入流
    fputc 写入一个字符 所有输出流
    fgets 读取一行 所有输入流
    fputs 写入一行 所有输出流
    fscanf 格式化读取 所有输入流
    fprintf 格式化写入 所有输出流
    fread 二进制读取 文件输入流
    fwrite 二进制写入 文件输出流

    "所有输入流"包括 stdin 和文件输入流,"所有输出流"包括 stdout 和文件输出流。

    fgetc / fputc —— 字符读写

    int fgetc(FILE* stream); // 从流中读取一个字符
    int fputc(int c, FILE* stream); // 向流中写入一个字符

    示例:把文件内容逐字符读出来打印

    #include <stdio.h>

    int main()
    {
    FILE* fp = fopen("test.txt", "r");
    if (fp == NULL)
    {
    perror("打开文件失败");
    return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) // 读到文件末尾返回EOF
    {
    putchar(ch);
    }

    fclose(fp);
    return 0;
    }

    fgets / fputs —— 行读写

    char* fgets(char* str, int n, FILE* stream); // 读取一行(最多n-1个字符)
    int fputs(const char* str, FILE* stream); // 写入一行

    fgets 会读到换行符或文件末尾,最多读 n-1 个字符,末尾自动加 \\0。

    fscanf / fprintf —— 格式化读写

    这两个函数跟 scanf、printf 用法几乎一样,只是多了一个文件指针参数:

    int fscanf(FILE* stream, const char* format, ...);
    int fprintf(FILE* stream, const char* format, ...);

    示例:写入格式化数据,再读出来

    #include <stdio.h>

    int main()
    {
    FILE* fp = fopen("data.txt", "w");
    if (fp == NULL) return 1;

    // 写入格式化数据
    fprintf(fp, "%d %f %s\\n", 100, 3.14, "hello");
    fclose(fp);

    // 重新打开读取
    fp = fopen("data.txt", "r");
    int a;
    float b;
    char s[20];
    fscanf(fp, "%d %f %s", &a, &b, s);
    printf("读出: %d, %.2f, %s\\n", a, b, s); // 读出: 100, 3.14, hello

    fclose(fp);
    return 0;
    }

    fread / fwrite —— 二进制读写

    这两个是二进制读写的核心函数,适合读写结构体、数组等:

    size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
    size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);

    • ptr:数据存放地址
    • size:每个元素的大小
    • count:元素个数
    • 返回值:实际读写的元素个数

    示例:写入并读取一个数组

    #include <stdio.h>

    int main()
    {
    double a[5] = {1.0, 2.0, 3.0, 4.0, 5.0};

    // 写入二进制文件
    FILE* fp = fopen("data.bin", "wb");
    fwrite(a, sizeof(double), 5, fp); // 写入5个double
    fclose(fp);

    // 读取二进制文件
    double b[5];
    fp = fopen("data.bin", "rb");
    size_t ret = fread(b, sizeof(double), 5, fp);

    if (ret == 5)
    printf("读取成功\\n");
    else
    printf("读取不完整,只读了 %zu 个\\n", ret);

    fclose(fp);
    return 0;
    }

    scanf/fscanf/sscanf 对比

    函数来源
    scanf 从标准输入(stdin)读取
    fscanf 从指定文件流读取
    sscanf 从字符串读取

    同理,printf、fprintf、sprintf 是输出版本,sprintf 可以把格式化数据写到字符串里。


    六、文件的随机读写

    顺序读写只能从头到尾,但有时候我们需要跳来跳去,比如直接定位到文件中间某个位置。这就需要随机读写函数。

    fseek —— 定位文件指针

    int fseek(FILE* stream, long int offset, int origin);

    • offset:偏移量
    • origin:起始位置,有三个选项:
      • SEEK_SET:文件开头
      • SEEK_CUR:当前位置
      • SEEK_END:文件末尾

    示例:修改文件中间的内容

    #include <stdio.h>

    int main()
    {
    FILE* pFile;
    pFile = fopen("example.txt", "wb");
    fputs("This is an apple.", pFile); // 写入字符串

    fseek(pFile, 9, SEEK_SET); // 定位到第9个字节('a'的位置)
    fputs(" sam", pFile); // 把 "an a" 替换成 " sam"

    fclose(pFile);
    return 0;
    }

    执行后文件内容变成:This is a sample.

    ftell —— 获取当前位置

    long int ftell(FILE* stream);

    返回文件指针相对于开头的偏移量。常用技巧:配合 fseek 获取文件大小。

    #include <stdio.h>

    int main()
    {
    FILE* pFile;
    long size;

    pFile = fopen("myfile.txt", "rb");
    if (pFile == NULL)
    perror("打开文件失败");
    else
    {
    fseek(pFile, 0, SEEK_END); // 定位到文件末尾
    size = ftell(pFile); // 此时偏移量就是文件大小
    fclose(pFile);
    printf("文件大小: %ld 字节\\n", size);
    }
    return 0;
    }

    rewind —— 回到文件开头

    void rewind(FILE* stream);

    把文件指针重置到文件开头,相当于 fseek(fp, 0, SEEK_SET)。

    示例:先写入26个字母,再从头读取

    #include <stdio.h>

    int main()
    {
    int n;
    FILE* pFile;
    char buffer[27];

    pFile = fopen("myfile.txt", "w+");
    for (n = 'A'; n <= 'Z'; n++)
    fputc(n, pFile); // 写入 A-Z

    rewind(pFile); // 回到开头

    fread(buffer, 1, 26, pFile); // 读取刚才写入的内容
    fclose(pFile);

    buffer[26] = '\\0';
    printf("%s\\n", buffer); // 输出: ABCDEFGHIJKLMNOPQRSTUVWXYZ
    return 0;
    }


    七、文件读取结束的判定

    feof 的正确用法

    大坑警告:很多人以为 feof 返回真就表示读到文件末尾了,这是错误的理解。

    feof 的真正作用是:在文件读取结束后,判断结束的原因是不是"遇到文件尾"。

    正确的判断方式:

    文本文件
    • fgetc:判断返回值是否为 EOF
    • fgets:判断返回值是否为 NULL

    #include <stdio.h>
    #include <stdlib.h>

    int main(void)
    {
    int c; // 注意用int,不是char,因为要处理EOF
    FILE* fp = fopen("test.txt", "r");

    if (!fp) {
    perror("打开文件失败");
    return EXIT_FAILURE;
    }

    // fgetc 读取失败或遇到文件末尾都会返回EOF
    while ((c = fgetc(fp)) != EOF)
    {
    putchar(c);
    }

    // 判断结束原因
    if (ferror(fp))
    puts("读取时发生I/O错误");
    else if (feof(fp))
    puts("成功读到文件末尾");

    fclose(fp);
    return 0;
    }

    二进制文件

    fread 返回实际读取的元素个数,判断是否小于预期个数:

    #include <stdio.h>

    enum { SIZE = 5 };

    int main(void)
    {
    double a[SIZE] = {1., 2., 3., 4., 5.};
    FILE* fp = fopen("test.bin", "wb");
    fwrite(a, sizeof *a, SIZE, fp); // 先写入
    fclose(fp);

    double b[SIZE];
    fp = fopen("test.bin", "rb");
    size_t ret_code = fread(b, sizeof *b, SIZE, fp);

    if (ret_code == SIZE) {
    puts("读取成功,内容:");
    for (int n = 0; n < SIZE; ++n)
    printf("%f ", b[n]);
    putchar('\\n');
    }
    else {
    if (feof(fp))
    printf("错误:文件意外结束\\n");
    else if (ferror(fp))
    perror("读取错误");
    }

    fclose(fp);
    return 0;
    }

    总结一下判断逻辑:

  • 先读数据
  • 检查返回值(EOF、NULL、或实际读取个数)
  • 再用 feof/ferror 判断结束原因

  • 八、文件缓冲区

    缓冲机制

    C语言采用缓冲文件系统:系统自动为每个正在使用的文件在内存中开辟一块缓冲区。

    • 写文件时:数据先送到内存缓冲区,缓冲区装满后才一起写到磁盘
    • 读文件时:从磁盘读入数据填充缓冲区,再从缓冲区逐个送给程序

    这样做的目的是减少磁盘I/O次数,提高效率。

    缓冲区的影响

    看个实验:

    #include <stdio.h>
    #include <windows.h>

    int main()
    {
    FILE* pf = fopen("test.txt", "w");
    fputs("abcdef", pf); // 数据先进入输出缓冲区

    printf("睡眠10秒——此时打开test.txt,发现文件是空的\\n");
    Sleep(10000);

    printf("刷新缓冲区\\n");
    fflush(pf); // 强制把缓冲区内容写入磁盘

    printf("再睡眠10秒——此时打开test.txt,文件有内容了\\n");
    Sleep(10000);

    fclose(pf); // fclose 也会刷新缓冲区
    pf = NULL;
    return 0;
    }

    运行这个程序,你会发现:

    • 写入 fputs 后立即打开文件,内容还没进去
    • 调用 fflush 后,内容才真正写入磁盘
    • fclose 关闭文件时也会自动刷新缓冲区

    结论

    重要:因为有缓冲区的存在,操作文件时要注意:

    • 写完数据后及时 fflush 或 fclose
    • 否则程序异常退出时,缓冲区数据可能丢失

    九、总结与速查表

    文件操作流程

    打开文件 (fopen) → 检查是否成功 → 读写数据 → 关闭文件 (fclose)

    核心函数速查

    操作函数说明
    打开文件 fopen 返回 FILE*,失败返回 NULL
    关闭文件 fclose 关闭并刷新缓冲区
    字符读写 fgetc / fputc 一次一个字符
    行读写 fgets / fputs 一次一行
    格式化读写 fscanf / fprintf 类似 scanf/printf
    二进制读写 fread / fwrite 读写任意类型数据
    定位 fseek 移动文件指针
    获取位置 ftell 返回当前位置
    回到开头 rewind 重置到文件开头
    判断结束原因 feof 读完后判断是否因EOF结束
    判断错误 ferror 判断是否发生I/O错误
    刷新缓冲区 fflush 强制写入磁盘

    常见易错点

  • "w" 模式会清空原文件:想追加用 "a"
  • 打开文件后要检查返回值:防止 NULL 指针操作
  • feof 不能用来判断是否读完:要先读,再判断结束原因
  • 二进制文件要用 "rb" / "wb":不要用文本模式
  • 记得关闭文件:不关闭可能导致数据丢失(缓冲区未刷新)
  • fgetc 返回 int 不是 char:要能表示 EOF
  • 把这些知识点搞清楚,C语言文件操作就基本拿下了。多写几个小练习——比如实现一个简单的配置文件读写、日志记录功能,手感很快就上来了。

    赞(0)
    未经允许不得转载:171主机测评 » C语言文件操作完全指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址