C语言文件操作
1、为什么使用文件
2、什么是文件
3、二进制文件和文本文件
4、文件的打开和关闭
5、文件的顺序
6、文件的随机读写
7、文件读取结束的判定
8、文件缓冲区
int main()
{
int a = 0;
scanf("%d", &a); // 这个就是将输入的数字放入文件中
printf("%d\n", a);
return 0;
}
一、问什么使用文件
- 内存数据断电丢失,文件可永久保存数据;
- 方便数据重复读取、批量处理;
- 实现多程序数据共享。
二、什么是文件
磁盘(硬盘)上的文件是文件,但是在程序设计中,我们一般谈到的文件有两种:程序文件、数据文件
程序文件:
源程序文件(.c)
- 后缀:
.c目标文件(
.obj/.o) - 后缀:Windows 下
.obj,Linux 下.o可执行文件(
.exe) - 后缀:Windows 系统下
.exe
数据文件
专门用来存放程序运行所需数据的文件,不包含代码指令。作用:给程序提供输入数据、保存程序运行结果,实现数据长久存储。
本章讨论的是数据文件
在以前各章所处理数据的输入和输出都是以终端为对象的,即从终端的键盘输入数据并行结果显示在显示器上,其实有的时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读到内存中,使用这里处理的就是磁盘上的文件。
文件名:
一个文件要有一个唯一的文件标识,以便用户识别和引用。文件名包含三个部分,文件路径 + 文件名主干 + 文件后缀(扩展名);例如:E:\project\main.c为了方便起见,文件标识常被称为文件名。
二进制文件和文本文件
文本文件(ASCII 文件)
1. 存储规则
把每一个字符按它对应的 ASCII 码 存储,内容就是我们能看懂的文字、数字、符号。
2. 特点
- 可用记事本、写字板直接打开阅读、编辑;
- 以
\n、EOF(-1)作为结束标志; - 占用空间略大,读写速度稍慢;
- 适合存放文字、普通数据。
二进制文件
1. 存储规则
数据直接按照内存中的二进制原样保存,不转换成 ASCII 字符。
2. 特点
- 记事本打开显示乱码,只能由对应程序读取;
- 存储紧凑、占用空间小、读写速度快;
- 原样保存变量、结构体、数组,适合存大批量数据、自定义类型;
- 没有专门的换行分隔符。

流和标准流
流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入、输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念。我们可以把流想象成流淌着的字符的河流。c 程序针对文件、画面、键盘等的数据输入,输出的操作都是通过流操作的,一般情况下我们想要向流里写数据,或者从流中读取数据,都要打开流,然后操作。
标准流
那为什么我们从键盘输入数据,向屏幕上输出数据并没有打开流?那是因为 c 语言程序在启动的时候默认打开了三个流:
stdin标准输入流 → 绑定键盘,scanf函数就是从标准输入流中读取数据的stdout标准输出流 → 绑定显示器,printf函数就是将信息输出到标准输出流中stderr标准错误流 → 绑定显示器,大多数环境中输出到显示器界面
这默认打开了这三个流,我们使用 scanf、printf 的函数就可以直接进行输入,输出操作的。stdin、stdout、stder三个流是类型是:FILE*,通常称为文件指针。 C 语言通过 FILE 文件指针 管理流。
文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。在编写程序的时候,在打开文件的同时都会返回一个 FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
fclose
语法:int fclose ( FILE * stream );
- 头文件:
#include <stdio.h> - 形参:文件指针(必须是已经通过
fopen成功打开的指针) - 返回值:
- 关闭成功:返回 0
- 关闭失败:返回 非 0 值
功能关闭形参 stream 所关联的文件,解除文件指针和文件之间的关联。
- 解除内部缓冲区与流的绑定;
- 刷新输出缓冲区:缓冲区里还没写到磁盘的数据,全部写入文件(防止数据丢失);
- 清空输入缓冲区:缓冲区中还未读取的数据,直接丢弃。
- 参数:
FILE *类型的流 / 文件指针。 - 返回值:成功返回
0,失败返回非 0 值。
fopen 打开文件
1. 函数原型
FILE *fopen(const char *filename, const char *mode);
- 返回值:文件指针
FILE*- 打开成功:返回有效文件地址
- 打开失败:返回
NULL(空指针)
- 参数 1
filename:文件路径 + 文件名(同目录直接写文件名) - 参数 2
mode:打开模式
标准使用模板
固定四步:打开 → 判断是否失败 → 读写 → 关闭
#include <stdio.h>
int main()
{
FILE *fp;
// 1. 打开文件
fp = fopen("test.txt", "r");
// 2. 必须判断打开是否失败(高频考点)
if (fp == NULL)
{
printf("文件打开失败!\n");
return 1; // 异常退出
}
// 3. 此处写 读/写 文件代码
// 4. 关闭文件
fclose(fp);
fp = NULL; // 好习惯,置空指针
return 0;
}
w 模式 写入文本文件
#include <stdio.h>
int main()
{
FILE *fp;
// 只写模式,不存在则创建,存在则清空
fp = fopen("info.txt", "w");
if (fp == NULL)
{
printf("打开失败\n");
return 1;
}
// 向文件写入内容
fprintf(fp, "Hello C File\n");
fprintf(fp, "文件读写练习");
fclose(fp); // 关闭
return 0;
}
运行后同级目录生成 info.txt,内容被写入。
r 模式 读取文本文件
#include <stdio.h>
int main()
{
FILE *fp;
char buf[100];
fp = fopen("info.txt", "r"); // 只读
if (fp == NULL)
{
printf("文件不存在或打开失败\n");
return 1;
}
// 读取一行
fgets(buf, 100, fp);
printf("%s", buf);
fclose(fp);
return 0;
}
示例 3:a 模式 追加内容
#include <stdio.h>
int main()
{
FILE *fp;
fp = fopen("info.txt", "a"); // 追加
if (fp == NULL)
{
return 1;
}
fprintf(fp, "\n追加一行新内容");
fclose(fp);
return 0;
}
原有内容保留,新内容写在文件最后。
fwrite 向文件写入数据
参数写法口诀:地址 + 单个大小 + 个数 + 文件指针
写入模板
fwrite( &变量, sizeof(变量类型), 个数, 文件指针 );
fread 从文件读取数据
读取模板
fread( &变量, sizeof(变量类型), 个数, 文件指针 );
基础示例 1:读写普通变量
#include <stdio.h>
int main()
{
FILE *fp;
int num = 100;
int res;
// 1. 二进制只写模式打开
fp = fopen("data.dat", "wb");
if (fp == NULL)
{
printf("打开失败\n");
return 1;
}
// 写入1个int数据
fwrite(&num, sizeof(int), 1, fp);
fclose(fp);
// 2. 二进制只读模式打开
fp = fopen("data.dat", "rb");
if (fp == NULL)
{
printf("打开失败\n");
return 1;
}
// 读取1个int数据
fread(&res, sizeof(int), 1, fp);
printf("读取到:%d\n", res);
fclose(fp);
return 0;
}
高频考点示例 2:结构体读写(必考)
这是考试最常考场景,搭配 wb/rb + 结构体。
#include <stdio.h>
// 定义结构体
struct Student
{
char name[20];
int age;
};
int main()
{
FILE *fp;
struct Student s1 = {"张三", 18};
struct Student s2;
// 写入:wb 二进制只写
fp = fopen("stu.dat", "wb");
if (fp == NULL) return 1;
fwrite(&s1, sizeof(struct Student), 1, fp);
fclose(fp);
// 读取:rb 二进制只读
fp = fopen("stu.dat", "rb");
if (fp == NULL) return 1;
fread(&s2, sizeof(struct Student), 1, fp);
fclose(fp);
// 输出读取内容
printf("姓名:%s 年龄:%d\n", s2.name, s2.age);
return 0;
}
数组批量读写
#include <stdio.h>
int main()
{
FILE *fp;
int arr[3] = {10, 20, 30};
int brr[3];
// 写入整个数组:3 个 int 数据
fp = fopen("arr.dat", "wb");
fwrite(arr, sizeof(int), 3, fp);
fclose(fp);
// 读取整个数组
fp = fopen("arr.dat", "rb");
fread(brr, sizeof(int), 3, fp);
fclose(fp);
for (int i = 0; i < 3; i++)
{
printf("%d ", brr[i]);
}
return 0;
}
文件的顺序读写:
表格
| 函数名 | 函数原型 | 核心功能 | 关键参数 | 返回值 | 高频考点 |
|---|---|---|---|---|---|
fopen |
|
打开文件,建立文件指针与文件的关联 |
|
成功:返回文件 失败:返回 |
必须判断打开是否成功;路径转义符\\;打开方式的选用 |
fclose |
|
关闭文件,解除指针与文件的关联,刷新缓冲区 | stream:已打开的文件指针 |
成功:返回 失败:返回非 0 值 |
必须配合fopen使用;防止数据丢失;释放系统资源 |
fprintf |
|
文本文件顺序写入,按格式将内存数据写入文件 | stream:文件指针format:格式控制串 |
成功:返回写入的字符数 失败:返回负数 |
文本文件写专用;用法和printf几乎一致,仅多文件指针参数 |
fscanf |
|
文本文件顺序读取,从文件读取数据存入内存变量 | stream:文件指针format:格式控制串 |
成功:返回成功读取的变量数失败 / 到文件尾:返回EOF |
文本文件读专用;变量必须加&;和scanf用法一致 |
fwrite |
|
二进制文件顺序写入,按字节块写入数据 | ptr:数据首地址size:单个数据字节数count:数据个数 |
成功:返回实际写入的完整数据块数 | 二进制文件写专用;适合数组 / 结构体 / 大批量数据 |
fread |
|
二进制文件顺序读取,按字节块读取数据 | ptr:接收数据的内存地址其余参数同fwrite |
成功:返回实际读取的完整数据块数 | 二进制文件读专用;和fwrite配对使用 |
表格
| 流名称 | 系统预设指针 | 对应设备 | 核心作用 | 常用函数 |
|---|---|---|---|---|
| 标准输入流 | stdin |
键盘 | 从键盘读取数据,输入到程序中 | scanf、getchar、fscanf(stdin,...) |
| 标准输出流 | stdout |
显示器 | 把程序数据输出到屏幕上 | printf、putchar、fprintf(stdout,...) |
| 标准错误流 | stderr |
显示器 | 专门输出程序的错误、警告信息 | 报错提示专用 |
表格
| 打开方式 | 含义 | 核心规则 | 适用场景 |
|---|---|---|---|
r |
只读(文本文件) | 文件必须存在,否则打开失败;只能读,不能写 | 读取已有的文本文件数据 |
w |
只写(文本文件) | 文件不存在则新建;文件存在则清空原有全部内容,再写入 | 新建文本文件、覆盖原有文件内容 |
a |
追加(文本文件) | 文件不存在则新建;文件存在则在末尾续写内容,不覆盖原有数据 | 在已有文本文件末尾添加新内容 |
rb |
只读(二进制文件) | 同r,仅针对二进制文件 |
读取已有的二进制文件数据 |
wb |
只写(二进制文件) | 同w,仅针对二进制文件 |
新建 / 覆盖二进制文件 |
ab |
追加(二进制文件) | 同a,仅针对二进制文件 |
在二进制文件末尾续写内容 |
feof和ferror
feof + ferror 组合使用(综合判断)
同时判断:是读到末尾,还是读写出错
#include <stdio.h>
int main()
{
FILE *fp;
char buf[100];
fp = fopen("test.txt", "r");
if (fp == NULL) return 1;
while (1)
{
fgets(buf, 100, fp);
if (ferror(fp)) // 先判断是否出错
{
printf("文件读写异常\n");
break;
}
if (feof(fp)) // 再判断是否到末尾
{
printf("读取完毕,到达文件尾\n");
break;
}
printf("%s", buf);
}
fclose(fp);
return 0;
}
表格
| 函数 | 原型 | 功能 | 返回值 | 使用场景 |
|---|---|---|---|---|
| feof | int feof(FILE *fp); |
判断是否到达文件末尾 |
到文件尾:返回非 0 未到尾:返回0 |
循环读文件,判断是否读完 |
| ferror | int ferror(FILE *fp); |
判断文件读写是否出错 |
出错:返回非 0 正常:返回0 |
检测读写过程 |
fputs和fgets
组合读写(写 → 读 完整流程)
#include <stdio.h>
int main()
{
FILE *fp;
char str[100];
// 1. 写入
fp = fopen("msg.txt", "w");
fputs("Hello C\n", fp);
fputs("fgets fputs 练习", fp);
fclose(fp);
// 2. 读取
fp = fopen("msg.txt", "r");
while (fgets(str, 100, fp) != NULL)
{
printf("%s", str);
}
fclose(fp);
return 0;
}
表格
| 函数 | 功能 | 原型 | 适用文件 | 核心特点 |
|---|---|---|---|---|
| fputs | 向文件写入字符串 | int fputs(char *str, FILE *fp); |
文本文件 | 不自动加换行、\0 不写入文件 |
| fgets | 从文件读取字符串 | char *fgets(char *str, int n, FILE *fp); |
文本文件 | 最多读 n-1 个字符;读到 \n 会一并存入;遇文件尾返回NULL |
规则总览
- 正常读到 换行符
\n:- 只要还没读满
n-1位,会把\n一并存入数组; - 然后自动追加
\0作为字符串结束。
- 只要还没读满
- 未遇到
\n,但字符数达到n-1:- 停止读取,不接收后续字符,也不收
\n; - 末尾直接补
\0。
- 停止读取,不接收后续字符,也不收
- 读到文件末尾:返回
NULL。
和同类函数对比 & 易错点
1. fgets 与 gets
gets:不安全,无长度限制,容易越界,不推荐使用fgets:带长度限制,安全,标准用法
2. fputs 与 puts
puts(字符串):输出到屏幕,自动加换行fputs(字符串, fp):输出到文件,不会自动加换行,需手动写\n
对比一组函数
一、输入类函数对比(scanf /fscanf/sscanf)
表格
| 函数名称 | 核心功能 | 标准函数原型 | 核心输入源 | 适用场景 | 核心特点 | 返回值规则 | 高频考点 / 易错点 |
|---|---|---|---|---|---|---|---|
| scanf | 从标准输入流读取格式化数据 |
|
键盘(终端) | 日常程序的终端手动输入、控制台数据读取 |
1. 系统自动打开标准输入流,无需手动开关 2. 输入数据以空格 / 回车 / 制表符分隔 3. 是最基础、最常用的输入函数 |
成功:返回成功读取的变量个数 失败 / 输入结束:返回 |
1. 读取变量必须加取地址符 2. 格式符必须与变量类型严格匹配 3. 无法读取带空格的完整字符串 |
| fscanf | 从文件流读取格式化数据 |
|
磁盘文本文件 | 从已打开的文本文件中读取数据、文件内容解析 |
1. 必须先通过 2. 用法与 |
成功:返回成功读取的变量个数失败 / 到文件末尾:返回 EOF(-1) |
1. 必须先判断文件是否打开成功( 2. 仅适用于文本文件,二进制文件不推荐使用 3. 读取结束后必须用 |
| sscanf | 从内存字符串中读取格式化数据 |
|
内存中的字符数组 / 字符串常量 | 字符串解析、从已有字符串中提取指定格式数据、字符串拆分 |
1. 输入源是内存里的字符串,不涉及键盘 / 文件操作 2. 不会修改原字符串,仅从其中读取数据3. 可实现复杂的字符串格式提取,是字符串处理高频函数 |
成功:返回成功读取的变量个数失败 / 字符串结束:返回 EOF(-1) |
1. 第一个参数是字符串首地址,不是文件指针 2. 原字符串必须以 3. 常与 |
二、输出类函数对比(printf /fprintf/sprintf)
fprintf 写入文件:fprintf(fp, "格式", 变量);
fscanf 读取文件:fscanf(fp, "格式", &变量);
表格
| 函数名称 | 核心功能 | 标准函数原型 | 核心输出目标 | 适用场景 | 核心特点 | 返回值规则 | 高频考点 / 易错点 |
|---|---|---|---|---|---|---|---|
| printf | 向标准输出流输出格式化数据 | int printf(const char *format, 输出列表); |
显示器(终端屏幕) | 日常程序的控制台结果输出、调试信息打印 |
1. 系统自动打开标准输出流,无需手动开关 2. 是最基础、最常用的输出函数 3. 输出内容直接显示在终端屏幕上 |
成功:返回成功输出的字符总数失败:返回负数 | 1. 格式符必须与输出变量类型严格匹配2. 输出字符串时,数组名 / 指针无需加&3. 可通过\n实现换行、\t实现制表对齐 |
| fprintf | 向文件流输出格式化数据 | int fprintf(FILE *fp, const char *format, 输出列表); |
磁盘文本文件 | 向已打开的文本文件写入数据、生成带格式的文本文件 |
1. 必须先通过 2. 用法与 3. 按顺序写入文件,文件指针自动后移 |
成功:返回成功写入的字符总数失败:返回负数 |
1. 必须先判断文件是否打开成功( 2. 仅适用于文本文件,二进制文件不推荐使用 3. 写入结束后必须用 |
| sprintf | 向内存字符串输出格式化数据 |
|
内存中的字符数组 | 字符串拼接、格式化生成新字符串、数字转字符串、字符串组装 |
1. 输出目标是内存里的字符数组,不涉及屏幕 / 文件操作 2. 自动在生成的字符串末尾添加结束符 3. 可实现复杂的字符串格式化拼接,是字符串处理高频函数 |
成功:返回成功写入的字符总数(不含末尾的\0)失败:返回负数 |
1. 第一个参数是目标字符数组首地址,不是文件指针 2. 目标数组必须足够大,否则会造成缓冲区溢出(核心易错点) 3. 常与 |
文件的随机读写
基本概念
- 顺序读写:从文件开头往后依次读写,文件指针只能向后移动,不能跳转。
- 随机读写:可人为移动文件指针,直接跳到文件任意位置读写,不用从头开始读。
- 核心:靠 文件位置指针定位
- 适用:二进制文件、结构体、数组(考试重点)
注意:随机读写一般搭配 二进制文件(
rb/wb),文本文件很少用。
表格
| 函数 | 功能 | 原型 |
|---|---|---|
fseek |
移动文件位置指针(定位) | int fseek(FILE *fp, long offset, int origin); |
ftell |
获取当前指针距离文件开头的字节数 | long ftell(FILE *fp); |
rewind |
把指针重置到文件开头 | void rewind(FILE *fp); |
1. rewind () 重置文件指针到开头
1)功能
将文件位置指针直接跳回文件起始位置。
2)语法
rewind(FILE *fp);
- 无返回值,无额外参数。
3)使用场景
读完一遍文件后,想从头再读一次,不用重新 fopen。
示例
rewind(fp); // 指针回到文件开头
2. ftell () 获取当前指针位置
1)功能
计算当前文件指针 距离文件首部的字节数,返回字节偏移量。
2)语法
long ftell(FILE *fp);
- 返回值:long 类型,当前偏移字节数。
3)典型用途
求文件总大小
// 1.指针移到文件末尾
fseek(fp, 0, SEEK_END);
// 2.获取总字节数
long size = ftell(fp);
printf("文件大小:%ld 字节\n", size);
3. fseek () 重点(随机读写核心)
1)函数原型
int fseek(FILE *fp, long offset, int origin);
三个参数逐个拆解(必背)
参数 1:FILE *fp
已打开的文件指针(一般二进制文件 rb/wb)
参数 2:long offset 偏移量
- 正数:向文件尾部 向后移动
- 负数:向文件头部 向前移动
- 单位:字节
参数 3:int origin 参考起点(三种固定宏,必须记)
表格
| 宏名 | 数值 | 参考位置 |
|---|---|---|
SEEK_SET |
0 | 文件开头(最常用) |
SEEK_CUR |
1 | 当前指针位置 |
SEEK_END |
2 | 文件末尾 |
2)组合写法(高频定位)
- 从文件开头向后移 10 字节
fseek(fp, 10, SEEK_SET);
- 从当前位置向后移 5 字节
fseek(fp, 5, SEEK_CUR);
- 从文件末尾向前移 8 字节(读最后一段数据)
fseek(fp, -8, SEEK_END);
- 等价于
rewind(回到开头)
fseek(fp, 0, SEEK_SET);
3)返回值
- 定位成功:返回 0
- 定位失败:返回 非 0 值
文件缓冲区

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)