【C 语言标准 IO 入门】第二天学习笔记:文件操作核心函数 + 实战案例 + 踩坑合集

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

第二天学习 C 语言标准 IO 库,核心围绕文件打开、读写、关闭三步法展开,掌握按字符读写的核心函数,理解缓冲区机制,并结合链表完成字符频次统计实战。本文梳理所有核心知识点,整理编码过程中实际遇到的段错误、double free、统计不准等高频坑点,方便复习与避坑。


一、文件基础概念

1. 什么是文件

文件是存放在磁盘空间中的一段数据的集合。程序运行时数据在内存中,持久化保存需要写入磁盘文件。

2. 文件操作固定三步法

所有文件操作严格遵循固定流程,缺一不可:

  1. 打开文件 :调用fopen建立程序与文件的连接,获取文件指针
  2. 读写文件 :通过fgetc/fputc等函数完成数据读写
  3. 关闭文件 :调用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

规范:fopenfclose必须成对出现,打开的文件用完一定要关闭。

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 函数,基础函数的规范写法一定要打牢。

相关推荐
MartinYeung51 小时前
[论文学习]MPMA:针对模型上下文协议的偏好操纵攻击
人工智能·学习·安全
疯狂打码的少年1 小时前
【数据结构】图的应用:最短路径(Dijkstra / Floyd)
数据结构·笔记·算法
波特率1152001 小时前
pthread库函数解析
linux·线程·pthread
Fa_Mian_Tuan2 小时前
图论基础 | 邻接多重表原理剖析 + 完整可运行C语言代码(插边/删边/释放)
c语言·数据结构·笔记·算法·图论
Escalating_xu2 小时前
【Linux线程同步】从数据竞争到 mutex、条件变量与生产者消费者(上篇)
android·java·linux
晨曦花锦2 小时前
Vim基础操作与Ollama模型管理,顺便配置Nginx反向代理
linux·nginx·编辑器·vim
Eloudy2 小时前
Ubuntu 22.04 从源码编译安装 ROS 2 Jazzy 笔记
linux·笔记·ubuntu
_Narcissus_2 小时前
B+树的概念和操作笔记(含完整代码实现)
c语言·数据结构·数据库·c++·笔记·b树·算法
raindayinrain2 小时前
深入理解Linux内核-页表,TLB,高速缓存,性能优化
linux·性能优化·高速缓存·页表·tlb