✍️ 作者:BackCatK Chen | 厦门市电子工程中级工程师
💻 专注C语言底层原理、嵌入式实战开发 | 持续更新保姆级入门教程
在C语言中,"与文件进行通信"本质就是 "程序与存储介质(硬盘、U盘等)之间的数据交换" ------程序把数据写入文件(持久化存储),或从文件读取数据(供程序处理)。这是跨越"临时程序"到"实用程序"的关键一步,比如做通讯录要存联系人、做计算器要存历史记录、做数据分析要读Excel数据,都离不开文件通信!
很多新手刚接触时会被"文件本质""模式差异""I/O级别"这些概念绕晕,甚至分不清"r"和"rb"、getc()和fgets()的区别。这篇文章会从最基础的"文件是什么"开始,一步步拆解文本/二进制模式、4种I/O级别、3个标准文件,每个知识点都配 "生活比喻+实战代码+避坑指南",新手看完直接通透,轻松打通文件通信的"任督二脉"!
一、文件是什么?------ 程序的"永久数据容器" 📦
在学习具体操作前,我们先搞懂最核心的问题:文件到底是什么?
1.1 从生活场景理解文件本质
生活中,我们会用"笔记本"记笔记、"U盘"存照片、"硬盘"装电影------这些都是"存储数据的容器"。而计算机中的文件(File),就是存储在磁盘、U盘等非易失性存储介质上的"数据容器",它的核心作用是:
- 📥 存储数据:程序运行产生的临时数据(如计算结果、用户输入),通过文件永久保存,关闭程序也不会丢失;
- 📤 交换数据:不同程序、不同设备之间,通过文件传递数据(如A程序生成的
.txt报表,B程序可以读取分析); - 📊 处理大数据:内存容量有限(比如8GB),无法容纳GB级数据,文件可以轻松存储和处理海量信息(如日志文件、数据库文件)。
简单说:文件 = 存储在硬盘上的"数据盒子",程序通过I/O函数与这个盒子"收发数据"。
1.2 计算机中的文件分类(按存储格式)
不管是文本、图片、视频,本质上都是二进制数据,但根据存储格式,文件主要分为两类(前一篇重点提过,这里再深化):
| 文件类型 | 存储格式 | 核心特点 | 常见后缀 | 人类可读性 |
|---|---|---|---|---|
| 文本文件 | 字符的ASCII/UTF-8编码(如'A'存为0x41,'1'存为0x31) |
存储时可能有格式转换(如换行符) | .txt、.c、.h、.ini |
✅ 可用记事本直接打开阅读 |
| 二进制文件 | 数据的原始二进制形式(如整数100存为0x64,结构体直接存内存字节) |
无格式转换,存储效率高 | .bin、.dat、.jpg、.exe、.mp4 |
❌ 记事本打开是乱码,需专用程序解析 |
生活类比:
- 文本文件 = 用"中文"写的日记,任何人打开都能看懂;
- 二进制文件 = 用"密码"写的日记,只有知道密码(解析规则)的人才能看懂。
1.3 C语言中如何"识别"文件?------ FILE*文件指针 📌
我们知道,C语言操作变量需要"变量名",操作数组需要"数组名",而操作文件则需要 *"文件指针(FILE)"** ------它是程序与文件之间的"桥梁"。
核心知识点:FILE结构体与文件指针
头文件中定义了一个FILE`结构体(无需关心内部细节),它存储了文件的关键信息:文件路径、打开模式、当前读写位置、缓冲区状态等;FILE*是指向这个结构体的指针(相当于"文件的唯一ID"),程序通过这个指针,才能找到对应的文件并进行读写操作;- 任何文件操作的第一步:用
fopen()函数打开文件,获取FILE*指针("拿到文件的钥匙"); - 任何文件操作的最后一步:用
fclose()函数关闭文件,释放FILE*指针("归还钥匙")。
实战演示:获取文件指针(打开+关闭文件)
c
#include .h>
#include .h> // exit()函数必备
int main(void) {
// 1. 打开文件:路径为"test.txt",模式为"w"(文本只写)
FILE *fp = fopen("test.txt", "w");
// 2. 关键!判断文件是否打开成功(指针是否为NULL)
if (fp == NULL) {
printf("❌ 文件打开失败!可能是路径错误或权限不足。\n");
exit(1); // 异常退出,避免后续操作崩溃
}
printf("✅ 文件打开成功!已获取文件指针:%p\n", fp); // %p打印指针地址
// 3. 中间步骤:读写文件(后续章节详细讲)
// 4. 关闭文件:释放资源,必须做!
int close_result = fclose(fp);
if (close_result == 0) {
printf("✅ 文件关闭成功!\n");
} else {
printf("❌ 文件关闭失败!\n");
}
fp = NULL; // 指针置空,避免野指针
system("pause");
return 0;
}
运行结果:
✅ 文件打开成功!已获取文件指针:0000000000407020
✅ 文件关闭成功!
运行后,当前目录会生成空的test.txt文件,说明文件指针获取成功。
1.4 避坑指南(新手必看)
- ❌ 忘记包含
:FILE、fopen()、fclose()都定义在<stdio.h>`中,不包含会编译报错; - ❌ 打开文件后不判断
fp == NULL:文件不存在、路径错误、权限不足都会导致fp为NULL,直接操作会崩溃; - ❌ 操作完文件不调用
fclose():会导致数据丢失(缓冲区未刷新)、资源泄漏(系统文件描述符耗尽); - ❌ 关闭文件后继续使用
fp:fclose()后fp变成野指针,再用它读写会触发非法访问。
二、文本模式和二进制模式------ 文件操作的"两种打开方式" 🔢
打开文件时,必须指定"操作模式",而文本模式(Text Mode) 和二进制模式(Binary Mode) 是最核心的两种,选错模式会导致数据读写错误(比如文本乱码、二进制文件损坏)。
2.1 核心差异:是否进行"格式转换"
两者的本质区别的是:程序读写文件时,是否对数据进行"自动格式转换"。
关键转换:换行符\n的处理(跨平台重点)
不同操作系统的"换行符"定义不同:
- Windows系统:换行符是
\r\n(回车+换行,两个字节); - Linux/Mac系统:换行符是
\n(仅换行,一个字节)。
当程序以文本模式读写文件时,C语言会自动进行换行符转换:
- 写入时:程序中的
\n会自动转换成当前系统的换行符(Windows→\r\n,Linux→\n); - 读取时:文件中的系统换行符会自动转换成程序中的
\n(Windows→\r\n→\n,Linux→\n→\n)。
而二进制模式 下,C语言不会进行任何格式转换,数据"原样读写"(\n就是0x0A,\r\n就是0x0D 0x0A)。
生活类比:
- 文本模式 = 寄快递时选择"自动包装"(快递员帮你整理包装);
- 二进制模式 = 寄快递时选择"原封不动"(直接按你给的样子寄出)。
2.2 打开模式的语法区别
在fopen()的第二个参数(模式字符串)中,加'b'表示二进制模式,不加则默认是文本模式:
| 操作类型 | 文本模式 | 二进制模式 | 核心用途 |
|---|---|---|---|
| 只读 | "r" |
"rb" |
读文本文件/读二进制文件(如图片、视频) |
| 只写 | "w" |
"wb" |
写文本文件/写二进制文件(如结构体、数据块) |
| 追加写 | "a" |
"ab" |
向文本文件末尾追加/向二进制文件末尾追加 |
| 读写 | "r+" |
"rb+" |
读+写文本文件/读+写二进制文件 |
2.3 实战对比:文本模式vs二进制模式的换行符转换
我们用代码验证两种模式下,写入\n后的文件大小差异(Windows系统下):
c
#include .h>
#include .h>
int main(void) {
// 1. 文本模式写入:\n会转换成\r\n(2字节)
FILE *fp_text = fopen("text_mode.txt", "w");
if (fp_text == NULL) { exit(1); }
fputc('\n', fp_text); // 写入一个\n
fclose(fp_text);
fp_text = NULL;
// 2. 二进制模式写入:\n原样存储(1字节)
FILE *fp_bin = fopen("bin_mode.dat", "wb");
if (fp_bin == NULL) { exit(1); }
fputc('\n', fp_bin); // 写入一个\n
fclose(fp_bin);
fp_bin = NULL;
printf("✅ 两种模式写入完成!请查看文件大小:\n");
printf("📄 文本模式文件(text_mode.txt):2字节(\\n→\\r\\n)\n");
printf("📦 二进制模式文件(bin_mode.dat):1字节(\\n原样存储)\n");
system("pause");
return 0;
}
运行结果(Windows系统):
- 打开
text_mode.txt,文件大小为2字节(\r\n); - 打开
bin_mode.dat,文件大小为1字节(\n)。
如果在Linux系统下运行,两种文件大小都是1字节(因为Linux换行符本身就是\n,文本模式无需转换)------这就是跨平台开发中必须区分模式的原因!
2.4 两种模式的适用场景(新手必记)
| 模式类型 | 适用文件 | 核心优势 | 注意事项 |
|---|---|---|---|
| 文本模式 | .txt、.c、.h、配置文件等文本格式 |
跨平台兼容性好,人类可直接阅读 | 不要用于二进制文件(会破坏数据) |
| 二进制模式 | .jpg、.exe、.dat、结构体数据等二进制格式 |
读写效率高,数据无损耗 | 不要用于需要跨平台的文本文件(换行符不转换) |
典型错误案例:
❌ 用文本模式打开图片文件("r")并读取:图片是二进制数据,文本模式会把0x0A(\n)当成换行符转换,导致图片数据损坏,打开后显示异常;
✅ 必须用二进制模式("rb")打开图片、视频等二进制文件,确保数据原样读写。
2.5 避坑指南
- ❌ 混淆模式后缀:文本模式不加
'b',二进制模式必须加'b'(如"wb"不是"w"); - ❌ 用文本模式处理二进制文件:会导致数据格式转换,文件损坏;
- ❌ 跨平台开发不指定模式:Windows和Linux换行符差异,文本模式可自动适配,二进制模式需手动处理换行符;
- ❌ 认为"文本文件只能用文本模式":文本文件也可以用二进制模式打开(不转换换行符),但不推荐(可读性差)。
三、I/O的级别------ 与文件"对话"的4种方式 🗣️
"与文件通信"的核心是"读写数据",C语言提供了4种不同"级别"的I/O操作(从基础到高级),对应不同的使用场景。我们可以根据"数据粒度"和"效率需求"选择合适的方式。
3.1 I/O级别的分类与核心对比
| I/O级别 | 数据粒度 | 核心函数 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 字符级I/O | 单个字符(1字节) | getc()、putc()、ungetc() |
小型文本文件、字符级处理(如统计字符数) | 灵活度最高,可处理任意字符 | 效率低,需循环读写 |
| 行级I/O | 一整行字符串 | fgets()、fputs() |
文本文件、配置文件、日志文件 | 效率中等,适合按行处理数据 | 仅适用于文本文件,无法处理二进制 |
| 格式化I/O | 结构化数据(int/float/struct等) | fprintf()、fscanf() |
结构化文本文件(如学生成绩表) | 可直接读写任意数据类型,格式规范 | 效率一般,对文件格式要求严格 |
| 二进制I/O | 内存块(任意数据) | fread()、fwrite() |
二进制文件、结构体、大数据块 | 效率最高,数据无损耗 | 人类不可读,需按固定格式读写 |
生活类比:
- 字符级I/O = 用"单个字"和文件对话(逐字读写);
- 行级I/O = 用"完整句子"和文件对话(逐句读写);
- 格式化I/O = 用"填表格"的方式和文件对话(按格式读写);
- 二进制I/O = 用"打包数据"的方式和文件对话(按块读写)。
3.2 1. 字符级I/O:逐字符读写(最基础)
核心函数:
getc(FILE *stream):从文件指针stream指向的文件中,读取1个字符(返回int类型,避免漏判EOF);putc(int c, FILE *stream):向文件指针stream指向的文件中,写入1个字符(c是字符的ASCII码);ungetc(int c, FILE *stream):将已读取的字符c"回退"到文件输入流,下次可再次读取。
实战1:用putc()逐字符写入字符串到文件
c
#include #include int main(void) {
FILE *fp = fopen("char_write.txt", "w"); // 文本只写模式
if (fp == NULL) { exit(1); }
char *str = "Hello, 文件通信!\n字符级I/O演示~";
int i = 0;
// 逐字符写入,直到字符串结束符'\0'
while (str[i] != '\0') {
int result = putc(str[i], fp);
if (result == EOF) { // EOF表示写入失败
printf("❌ 字符写入失败!\n");
fclose(fp);
exit(1);
}
i++;
}
printf("✅ 字符串逐字符写入成功!\n");
fclose(fp);
fp = NULL;
system("pause");
return 0;
}
实战2:用getc()逐字符读取文件并打印
c
#include .h>
#include .h>
int main(void) {
FILE *fp = fopen("char_write.txt", "r"); // 文本只读模式
if (fp == NULL) { exit(1); }
printf("✅ 逐字符读取文件内容:\n");
int ch; // 必须用int接收,因为EOF是-1(char无法存储负数)
// 循环读取,直到文件末尾(getc()返回EOF)
while ((ch = getc(fp)) != EOF) {
putchar(ch); // 打印到控制台
}
printf("\n✅ 文件读取完毕!\n");
fclose(fp);
fp = NULL;
system("pause");
return 0;
}
运行结果:
控制台会完整打印char_write.txt中的字符串,和写入内容完全一致。
实战3:ungetc()字符回退演示
c
#include >
#include >
int main(void) {
FILE *fp = fopen("char_write.txt", "r");
if (fp == NULL) { exit(1); }
// 第一次读取第一个字符
int ch1 = getc(fp);
printf("第一次读取:%c\n", ch1); // 输出'H'
// 回退字符'H'到输入流
ungetc(ch1, fp);
printf("字符'H'已回退!\n");
// 第二次读取(会再次读到'H')
int ch2 = getc(fp);
printf("第二次读取:%c\n", ch2); // 输出'H'
fclose(fp);
fp = NULL;
system("pause");
return 0;
}
字符级I/O避坑点:
- ❌ 用
char接收getc()返回值:EOF是-1,char(无符号)会存为255,导致无法判断文件末尾; - ❌ 回退
EOF到输入流:ungetc()的参数c不能是EOF,否则操作无效; - ❌ 字符级I/O处理二进制文件:效率极低,且可能误将
0x1A(Windows文本模式的EOF)当成文件末尾。
3.3 2. 行级I/O:逐行读写(文本文件首选)
核心函数:
fgets(char *s, int size, FILE *stream):从文件中读取1行字符串,存入字符数组s;- 停止条件:读到
\n(会存入s)、读到size-1个字符(预留\0)、文件末尾;
- 停止条件:读到
fputs(const char *s, FILE *stream):将字符串s写入文件,不自动添加\n(需手动加)。
实战1:用fputs()逐行写入文本文件
c
#include #include int main(void) {
FILE *fp = fopen("line_write.txt", "w");
if (fp == NULL) { exit(1); }
// 逐行写入,手动加\n实现换行
fputs("第一行:行级I/O演示\n", fp);
fputs("第二行:fputs()不自动加换行\n", fp);
fputs("第三行:适合处理配置文件、日志文件", fp); // 无\n,不会换行
printf("✅ 多行文本写入成功!\n");
fclose(fp);
fp = NULL;
system("pause");
return 0;
}
实战2:用fgets()逐行读取文本文件
c
#include .h>
#include .h>
#include .h> // strlen()函数必备
int main(void) {
FILE *fp = fopen("line_write.txt", "r");
if (fp == NULL) { exit(1); }
char buffer[100]; // 缓冲区,存储读取的一行
printf("✅ 逐行读取文件内容:\n");
// 循环读取,直到文件末尾(fgets()返回NULL)
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
// 可选:去除缓冲区末尾的\n(如果需要)
// buffer[strcspn(buffer, "\n")] = '\0';
printf("读取到:%s", buffer); // buffer已包含\n,无需额外换行
}
printf("\n✅ 文件读取完毕!\n");
fclose(fp);
fp = NULL;
system("pause");
return 0;
}
行级I/O避坑点:
- ❌ 缓冲区大小设置过小:如果一行文本长度超过
size-1,fgets()会分多次读取,导致数据不完整; - ❌ 忘记手动添加
\n:fputs()不会自动换行,需在字符串末尾加\n才能实现换行; - ❌ 用
fgets()读取二进制文件:二进制文件中可能包含\0(字符串结束符),导致fgets()提前停止读取。
3.4 3. 格式化I/O:结构化数据读写(如学生成绩)
核心函数:
fprintf(FILE *stream, const char *format, ...):按格式将数据写入文件(printf()的文件版本);fscanf(FILE *stream, const char *format, ...):按格式从文件读取数据(scanf()的文件版本)。
实战:格式化读写学生信息(结构化数据)
c
#include
#include
// 定义学生结构体(结构化数据)
typedef struct {
char name[20];
int age;
float score;
} Student;
int main(void) {
// 第一步:格式化写入学生信息到文件
FILE *fp_write = fopen("format_stu.txt", "w");
if (fp_write == NULL) { exit(1); }
Student stu1 = {"张三", 18, 95.5};
Student stu2 = {"李四", 19, 88.0};
// 写入表头和数据(格式:姓名\t年龄\t成绩\n)
fprintf(fp_write, "姓名\t年龄\t成绩\n");
fprintf(fp_write, "%s\t%d\t%.1f\n", stu1.name, stu1.age, stu1.score);
fprintf(fp_write, "%s\t%d\t%.1f\n", stu2.name, stu2.age, stu2.score);
fclose(fp_write);
fp_write = NULL;
printf("✅ 学生信息格式化写入成功!\n");
// 第二步:格式化读取学生信息
FILE *fp_read = fopen("format_stu.txt", "r");
if (fp_read == NULL) { exit(1); }
Student stu;
char header[20]; // 存储表头,跳过不处理
// 跳过表头
fscanf(fp_read, "%s\t%s\t%s", header, header, header);
printf("\n✅ 格式化读取学生信息:\n");
printf("姓名\t年龄\t成绩\n");
printf("------------------------\n");
// 循环读取数据,直到文件末尾
while (fscanf(fp_read, "%s\t%d\t%f", stu.name, &stu.age, &stu.score) != EOF) {
printf("%s\t%d\t%.1f\n", stu.name, stu.age, stu.score);
}
fclose(fp_read);
fp_read = NULL;
system("pause");
return 0;
}
运行结果:
文件format_stu.txt中会存入格式化的学生信息,控制台会正确读取并打印,格式整齐。
格式化I/O避坑点:
- ❌ 格式控制符不匹配:写入时用
%d,读取时用%f,会导致数据读取错误; - ❌ 文件格式被破坏:如果手动修改文件中的分隔符(如把
\t改成空格),fscanf()会读取失败; - ❌ 字符串没有空格:
fscanf()的%s会以空格/制表符/换行符为分隔符,无法读取带空格的姓名(如"张小三"可以,"张小三 同学"不行)。
3.5 4. 二进制I/O:内存块读写(效率最高)
核心函数:
fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream):将内存块ptr中的nmemb个数据(每个size字节)写入文件;fread(void *ptr, size_t size, size_t nmemb, FILE *stream):从文件读取nmemb个数据(每个size字节)到内存块ptr。
实战:二进制读写结构体数组(嵌入式开发常用)
c
#include >
#include >
typedef struct {
char name[20];
int age;
float score;
} Student;
int main(void) {
// 第一步:二进制写入结构体数组
FILE *fp_write = fopen("bin_stu.dat", "wb"); // 二进制只写模式
if (fp_write == NULL) { exit(1); }
Student stu_arr[2] = {
{"张三", 18, 95.5},
{"李四", 19, 88.0}
};
// 写入:1个数据块(数组),每个元素size=sizeof(Student),共2个元素
size_t write_count = fwrite(stu_arr, sizeof(Student), 2, fp_write);
if (write_count != 2) {
printf("❌ 二进制写入失败!成功写入%d个元素\n", write_count);
fclose(fp_write);
exit(1);
}
fclose(fp_write);
fp_write = NULL;
printf("✅ 结构体数组二进制写入成功!\n");
// 第二步:二进制读取结构体数组
FILE *fp_read = fopen("bin_stu.dat", "rb"); // 二进制只读模式
if (fp_read == NULL) { exit(1); }
Student read_arr[2];
size_t read_count = fread(read_arr, sizeof(Student), 2, fp_read);
printf("\n✅ 二进制读取学生信息:\n");
printf("姓名\t年龄\t成绩\n");
printf("------------------------\n");
for (int i = 0; i _count; i++) {
printf("%s\t%d\t%.1f\n", read_arr[i].name, read_arr[i].age, read_arr[i].score);
}
fclose(fp_read);
fp_read = NULL;
system("pause");
return 0;
}
运行结果:
二进制文件bin_stu.dat打开是乱码(正常),但程序能正确读取结构体数据,效率远高于格式化I/O。
二进制I/O避坑点:
- ❌ 模式不加
'b':用文本模式写入二进制数据,会导致格式转换(如\n变成\r\n),数据损坏; - ❌ 混淆
size和nmemb参数:fwrite(arr, sizeof(int), 5, fp)是"5个int",不是"5字节",写反会导致数据错误; - ❌ 跨平台读写:不同平台的结构体对齐方式可能不同,导致读取失败(需用
#pragma pack(1)统一对齐)。
四、标准文件------ C语言默认的"3个通信通道" 🚦
除了我们手动创建的文件,C语言程序启动时会自动打开3个标准文件 ,它们是程序与外界通信的默认通道,无需手动fopen(),直接用预定义的文件指针操作。
4.1 3个标准文件的核心信息
| 标准文件 | 文件指针 | 对应设备 | 核心用途 | 常用操作函数 |
|---|---|---|---|---|
| 标准输入 | stdin |
键盘 | 程序读取用户输入(如密码、指令) | scanf()、getchar()、fgets(stdin) |
| 标准输出 | stdout |
显示器 | 程序输出正常信息(如结果、提示) | printf()、puts()、fputs(str, stdout) |
| 标准错误 | stderr |
显示器 | 程序输出错误信息(如报错、警告) | fprintf(stderr, "错误信息") |
核心特点:
- 这3个文件指针定义在`,程序启动即存在,退出即关闭;
stdout和stderr默认都输出到显示器,但stdout是行缓冲(遇到\n刷新),stderr是无缓冲(立即输出);- 支持"重定向":通过命令行或代码,将
stdin重定向到文件(从文件读输入),stdout/stderr重定向到文件(输出到文件)。
4.2 实战1:理解stdout和stderr的差异(缓冲机制)
c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// stdout:行缓冲,未遇到\n,数据存在缓冲区
printf("这是stdout输出的正常信息(行缓冲)");
// stderr:无缓冲,立即输出
fprintf(stderr, "\n这是stderr输出的错误信息(无缓冲)\n");
// 程序退出前,stdout缓冲区会刷新,所以最终能看到stdout的内容
system("pause");
return 0;
}
运行结果:
这是stderr输出的错误信息(无缓冲)
这是stdout输出的正常信息(行缓冲)
请按任意键继续. . .
可以看到:stderr的信息先输出(无缓冲),stdout的信息后输出(行缓冲,程序退出前刷新)。
4.3 实战2:标准文件重定向(输出到文件)
场景:将printf()的输出(stdout)重定向到文件,而不是显示器。
c
#include .h>
#include .h>
int main(void) {
// 将stdout重定向到文件"stdout_redirect.txt"
FILE *fp = freopen("stdout_redirect.txt", "w", stdout);
if (fp == NULL) {
fprintf(stderr, "❌ 重定向失败!\n");
exit(1);
}
// 此时printf()的输出会写入文件,而不是屏幕
printf("这是重定向后的stdout输出!\n");
printf("C语言标准文件重定向演示~\n");
// 恢复stdout(可选)
freopen("CON", "w", stdout); // Windows系统用"CON",Linux用"/dev/stdout"
// 恢复后,printf()输出到屏幕
printf("✅ 重定向完成!请查看stdout_redirect.txt文件~\n");
fclose(fp);
system("pause");
return 0;
}
运行结果:
stdout_redirect.txt文件中会存入前两条printf()的内容;- 控制台会打印第三条
printf()的内容(恢复后)。
4.4 实战3:从标准输入(stdin)读取用户输入
c
#include #include int main(void) {
char name[20];
int age;
printf("请输入你的姓名:");
fgets(name, sizeof(name), stdin); // 从stdin(键盘)读取字符串
printf("请输入你的年龄:");
scanf("%d", &age); // scanf()默认从stdin读取
// 输出到stdout(屏幕)
printf("\n你好,%s你的年龄是%d岁!\n", name, age);
system("pause");
return 0;
}
标准文件避坑点:
- ❌ 关闭标准文件指针:
fclose(stdin)后,scanf()、getchar()等函数会失效,无法再读取输入; - ❌ 重定向后不恢复:如果重定向
stdout后不恢复,后续所有printf()都会输出到文件; - ❌ 混淆
stdout和stderr:错误信息应输出到stderr(如fprintf(stderr, "错误")),避免被重定向掩盖。
🚨 新手全网高频踩坑总清单(背下来少走半年弯路)
- 忘记包含
<stdio.h>,导致FILE、stdin、fopen()等未定义; - 打开文件不判断
fp == NULL,文件不存在/路径错误时程序崩溃; - 文本/二进制模式混用,导致换行符转换错误、二进制文件损坏;
- 字符级I/O用
char接收getc()返回值,无法识别EOF; fgets()缓冲区过小,导致一行数据被截断;fprintf()/fscanf()格式控制符不匹配,读取数据错误;fread()/fwrite()参数size和nmemb写反,读写数据量错误;- 不关闭文件,导致数据丢失、资源泄漏;
- 重定向
stdout后不恢复,后续输出无法显示到屏幕; - 用文本模式处理图片、视频等二进制文件,导致文件损坏。
🎯 课后实战练习(动手=学会)
- 基础题:用字符级I/O统计文本文件中某个字符(如
'a')出现的次数; - 进阶题:用行级I/O读取配置文件(
.ini),解析其中的键值对(如name=张三); - 综合题:用二进制I/O实现一个简易通讯录,支持新增、查询、修改、删除联系人(结构体存储);
- 挑战题:将程序的错误信息(
stderr)重定向到日志文件,正常信息(stdout)输出到屏幕。
📝 本章总结
- 文件的本质是"存储在硬盘上的数据容器",C语言通过
FILE*指针操作文件,必须遵循"打开→操作→关闭"的闭环; - 文本模式会自动转换换行符(跨平台友好),二进制模式原样读写(效率高),需根据文件类型选择;
- 4种I/O级别各有适用场景:字符级灵活、行级适合文本、格式化适合结构化数据、二进制级效率最高;
- 3个标准文件(
stdin/stdout/stderr)是程序的默认通信通道,支持重定向,提升程序灵活性; - 文件操作的核心是"细节":模式选择、函数参数、缓冲机制、错误判断,做好这些就能避免90%的问题。
🔥 本文由 BackCatK Chen 原创,全网最细保姆级C语言文件通信教程
👍 觉得有用记得点赞、收藏、关注
#C语言 #C语言入门 #文件通信 #文件I/O #标准文件 #文本模式 #二进制模式 #保姆级教程
🎁欢迎关注,获取更多技术干货!
🚀 C语言宝藏资源包免费送!14 本 C++ 经典书 + 编译工具全家桶 + 高效编程技巧,搭配 C 语言精选书籍、20 + 算法源码 + 项目规范,还有 C51 单片机 400 例实战!从零基础到嵌入式开发全覆盖,学生党、职场人直接抄作业~ 关注文章末尾的博客同名公众号,回复【C 语言】一键解锁全部资源,手慢也有!