提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
第二天学习 C 语言标准 IO 库,核心围绕文件打开、读写、关闭三步法展开,掌握按字符读写的核心函数,理解缓冲区机制,并结合链表完成字符频次统计实战。本文梳理所有核心知识点,整理编码过程中实际遇到的段错误、double free、统计不准等高频坑点,方便复习与避坑。
一、文件基础概念
1. 什么是文件
文件是存放在磁盘空间中的一段数据的集合。程序运行时数据在内存中,持久化保存需要写入磁盘文件。
2. 文件操作固定三步法
所有文件操作严格遵循固定流程,缺一不可:
- 打开文件 :调用
fopen建立程序与文件的连接,获取文件指针 - 读写文件 :通过
fgetc/fputc等函数完成数据读写 - 关闭文件 :调用
fclose释放资源,防止内存泄漏与数据丢失
3. 文本文件与二进制文件
ASCII 码文件本质是特殊的二进制文件,二者按内容可读性区分:
- 文本文件(ASCII 文件) :文件中每个字节都是可显示的 ASCII 字符,例如
.c代码文件、.txt记事本文件,可直接在终端查看内容。 - 二进制文件:文件中包含无法在终端直接显示的字节数据,例如图片、音视频、压缩包、可执行程序,读取后不能直接看懂内容。
二、标准流与缓冲区机制
1. 三个默认标准流
C 语言程序启动时,系统会自动打开 3 个FILE*类型的流,无需手动fopen,也不要随意关闭:
| 流名称 | 全称 | 对应设备 | 作用 |
|---|---|---|---|
stdin |
标准输入流 | 键盘 | 程序读取键盘输入,scanf/getchar/fgetc(stdin)都走这里 |
stdout |
标准输出流 | 终端屏幕 | 程序打印普通信息,printf/putchar/fputc(ch,stdout)都走这里 |
stderr |
标准错误流 | 终端屏幕 | 专门输出错误信息,perror()默认走这里 |
等价关系:
getchar()=fgetc(stdin);putchar(ch)=fputc(ch, stdout)
2. 三种缓存类型
标准 IO 是带缓存的 IO,数据不会直接写入磁盘 / 屏幕,先进入缓冲区,满足刷新条件才真正输出。
(1)全缓存
- 大小:4KB(4096 字节)
- 刷新条件:缓冲区满、调用
fflush强制刷新、程序结束 /fclose关闭 - 适用场景:所有通过
fopen打开的磁盘文件,都是全缓存
(2)行缓存
- 大小:1KB(1024 字节)
- 刷新条件:缓冲区满、
fflush强制刷新、程序结束 /fclose、遇到换行符\n - 适用场景:对接终端的流,
stdout标准输出是行缓存
经典现象:
printf("hello");不加\n,程序死循环卡住时屏幕看不到输出,就是因为数据停在行缓存里没刷新。
(3)无缓存
- 大小:0KB,数据立即输出,不经过缓存
- 特点:实时性高,用于错误信息输出、人机交互
- 适用场景:
stderr标准错误流,错误信息立刻打印,不会因为缓冲区丢失
三、核心 IO 函数详解
1. fopen:打开文件
FILE *fopen(const char *pathname, const char *mode);
-
功能:打开指定路径的文件,建立与之关联的流
-
参数 :
-
pathname:要打开的文件路径字符串 -
mode:打开方式,常用模式对照表:表格
模式 含义 文件不存在 文件已存在 r只读 报错 正常打开只读 r+读写 报错 正常打开读写 w只写 创建新文件 清空原有全部内容,只写 w+写读 创建新文件 清空原有内容,读写 a追加只写 创建新文件 末尾追加写入,不清空 a+追加读写 创建新文件 末尾追加,可读可写
-
-
返回值 :成功返回
FILE*文件指针;失败返回NULL
重要:打开文件后必须判断返回值是否为 NULL,否则空指针访问直接段错误。
2. fclose:关闭文件
int fclose(FILE *stream);
- 功能:关闭已打开的流,刷新缓冲区数据,释放系统资源
- 参数 :
stream:已打开的文件流指针 - 返回值 :成功返回 0;失败返回
EOF
规范:
fopen和fclose必须成对出现,打开的文件用完一定要关闭。
3. fgetc:按字符读文件
int fgetc(FILE *stream);
- 功能:从指定流中读取一个字符
- 参数 :
stream:文件流指针 - 返回值 :成功返回读取字符的 ASCII 码;读到文件末尾 / 读取失败返回
EOF(-1)
考点:返回值是
int类型,必须用int变量接收,不能用char,否则 EOF (-1) 会出现符号位异常。
4. fputc:按字符写文件
int fputc(int c, FILE *stream);
- 功能:将一个字符写入指定流中
- 参数 :
c:要写入的字符stream:目标文件流指针
- 返回值 :成功返回写入字符的 ASCII 码;失败返回
EOF(-1)
四、实战代码案例
案例 1:统计文件行数
基础版(统计换行符个数)
通过统计\n换行符数量计算行数,考点写法。
#include <stdio.h>
int main(void)
{
FILE *fp = NULL;
int ch = 0;
int cnt = 0;
fp = fopen("a.txt", "r");
if (fp == NULL)
{
perror("fopen failed");
return -1;
}
while ((ch = fgetc(fp)) != EOF)
{
if(ch == '\n')
{
cnt++;
}
}
fclose(fp);
printf("文件行数:%d\n", cnt);
return 0;
}
坑点:文件最后一行末尾没有
\n时,会少统计一行。
优化版(兼容末尾无换行)
文件不为空时额外加一行,得到真实物理行数。
#include <stdio.h>
int main(void)
{
FILE *fp = NULL;
int ch = 0;
int cnt = 0;
int has_data = 0;
fp = fopen("a.txt", "r");
if (fp == NULL)
{
perror("fopen failed");
return -1;
}
while ((ch = fgetc(fp)) != EOF)
{
has_data = 1;
if(ch == '\n') cnt++;
}
if(has_data) cnt++;
fclose(fp);
printf("文件真实行数:%d\n", cnt);
return 0;
}
案例 2:逐字符实现文件拷贝
读取源文件每个字符,原样写入目标文件,完成文件复制。
#include <stdio.h>
int main(void)
{
FILE *fp = NULL;
FILE *fc = NULL;
int ch = 0;
fp = fopen("src.txt","r");
if(fp == NULL)
{
perror("open src failed");
return -1;
}
fc = fopen("dst.txt","w");
if(fc == NULL)
{
perror("open dst failed");
fclose(fp);
return -1;
}
while(1)
{
ch = fgetc(fp);
if(EOF == ch) break;
fputc(ch, fc);
}
fclose(fp);
fclose(fc);
return 0;
}
五、高频踩坑与排错指南
1. if(fp = NULL) 笔误 → 空指针段错误
- 现象 :运行报
Segmentation fault段错误 - 原因 :把判断相等
==写成赋值=,强制把 fp 置为 NULL,后续fgetc(fp)访问空指针崩溃 - 修复 :
if(fp == NULL),推荐常量写左边if(NULL == fp),写错会编译报错
2. char ch接收 fgetc 返回值 → EOF 判断异常
- 原因 :
fgetc返回int,EOF 值为 - 1,char 类型无法正确表示负数,导致文件末尾判断失效 - 修复 :定义
int ch = 0;接收返回值
3. 链表遍历漏写指针后移 → 程序卡死
- 现象:运行后终端无输出,程序卡住不动
- 原因 :while 循环里只做判断,没写
pTmpNode = pTmpNode->pNext;,指针永远停在第一个节点,死循环 - 修复:循环末尾必须添加指针后移语句
4. 链表销毁重复释放 → double free
- 现象 :运行报
free(): double free detected - 原因 :while 循环已经释放了头结点,函数末尾又
free(*ppTmpList)重复释放同一块内存 - 修复:删除循环外多余的 free,循环内已经完整释放所有节点
5. 文件打开成功但统计结果为 0
- 原因 1:文件是空文件,没有任何字符
- 原因 2 :文件只有一行内容,末尾没有回车换行,基础版只统计
\n自然结果为 0 - 排查命令 :终端执行
od -c 文件名查看文件原始字节,确认是否有\n
6. 编辑器显示有内容,程序读不到文件
- 原因:VSCode 编辑器显示的是本地缓存,虚拟机磁盘上的文件未同步更新,程序读取的是虚拟机真实文件
- 排查 :终端执行
ls确认文件存在,cat 文件名确认内容 - 解决 :直接在终端用
echo命令写入测试内容,最稳妥。
六、学习总结
第二天标准 IO 的核心是掌握文件指针、打开关闭、按字符读写三个核心点,理解缓冲区机制是理解输出现象的关键。编码中最容易出错的是空指针判断、返回值类型、循环指针移动、内存释放这几类细节问题,写完代码建议对照排错清单逐一检查。后续还会学习按行读写、格式化读写、块读写等更多 IO 函数,基础函数的规范写法一定要打牢。