十、C语言文件与标准 I/O

思维导图



一、 printf 与 scanf:格式控制的艺术

1.1 格式化占位符:你真的会用吗?

printfscanf 是 C 语言中最常用的函数,但也是最容易被误用的。

常用占位符速查表:

占位符 类型 说明 示例
%d int 十进制整数 10
%f double 浮点数 (默认6位小数) 3.141593
%c char 单个字符 'A'
%s char* 字符串 (遇 \0 止) "Hello"
%p void* 指针地址 (十六进制) 0x7ffee...
%x unsigned int 十六进制整数 ff
%zu size_t sizeof 的结果 8

1.2 宽度与精度控制

语法: %[flags][width][.precision]type

代码示例:

c 复制代码
double pi = 3.1415926535;
int num = 42;

// 1. 精度控制
printf("%.2f\n", pi); // "3.14" (保留2位小数,<font color="#006400">四舍五入</font>)

// 2. 宽度补零 (常用于日期)
printf("%04d\n", num); // "0042" (总宽4,<font color="#FF4500">不足补0</font>)

// 3. 左对齐
printf("%-10d|\n", num); // "42        |"

1.3 类型不匹配的风险

危险: 如果占位符和后面的参数类型不一致,或者参数数量不对,程序可能会输出乱码甚至崩溃。

c 复制代码
long long big_num = 1234567890123;
printf("%d", big_num); // <font color="#FF0000">错误!</font>%d 只读 4 字节,可能会输出截断后的负数
printf("%lld", big_num); // <font color="#008000">正确</font>

二、 文件打开与关闭

2.1 fopen 的模式魔法

原型: FILE *fopen(const char *filename, const char *mode);

模式 含义 如果文件不存在 如果文件存在
"r" 只读 返回 NULL 正常打开
"w" 只写 创建新文件 清空原内容!
"a" 追加 创建新文件 在末尾添加
"r+" 读写 返回 NULL 覆盖原内容
"rb" 二进制读 (同 r) (同 r)

2.2 标准流程模板

c 复制代码
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "r"); // 尝试打开
    
    // 1. 必须检查是否成功!
    if (fp == NULL) {
        perror("打开失败"); // 打印错误原因 (如 No such file)
        return 1;
    }
    
    // 2. 使用文件...
    
    // 3. 关闭文件 (释放资源)
    fclose(fp);
    return 0;
}

三、 文本读写:fgets 与 fputs

处理文本文件如日志、配置、CSV时,按行读取是最常用的模式。

3.1 统计行数代码

c 复制代码
FILE *fp = fopen("story.txt", "r");
if (!fp) return 1;

char buffer[1024];
int lines = 0;

// fgets 读取一行,遇到换行符或文件结束停止
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    // 这里可以处理 buffer,比如 printf("%s", buffer);
    lines++;
}

printf("总行数: %d\n", lines);
fclose(fp);

注意: fgets 会把换行符 \n 也读进来。如果不想打印空行,通常需要手动去除。

四、 二进制读写:fread 与 fwrite

处理图片、音频或自定义数据结构时,必须用二进制模式("rb" / "wb"),否则换行符转换会破坏数据。

4.1 批量读写数组

写入:

c 复制代码
int data[] = {10, 20, 30, 40, 50};
FILE *fp = fopen("data.bin", "wb"); // <font color="#FF4500">注意 wb</font>
// 参数:数据地址, 元素大小, 元素个数, 文件指针
fwrite(data, sizeof(int), 5, fp);
fclose(fp);

读取:

c 复制代码
int buffer[5];
FILE *fp = fopen("data.bin", "rb"); // <font color="#FF4500">注意 rb</font>
// 返回值是成功读取的"块"数
size_t count = fread(buffer, sizeof(int), 5, fp);
if (count != 5) {
    printf("读取不完整或出错\n");
}
fclose(fp);

4.2 结构体序列化的风险

警告: 直接 fwrite 一个结构体到文件,在另一台电脑上读出来可能会乱码。
原因:

1.内存对齐: 不同编译器填充的字节可能不同。

  1. 字节序: 大端机和小端机存储整数的顺序相反。

工程对策: 定义严格的协议,逐个字节序列化,而不是直接 dump 内存。

五、 错误处理:errno 与 perror

当系统调用(如打开文件、申请内存)失败时,操作系统会把错误代码存在全局变量 errno 中。

5.1 perror:让错误会说话

不要只打印 "Error",要告诉用户为什么错了。

c 复制代码
#include <errno.h> // 包含 errno
#include <string.h>

FILE *fp = fopen("不存在的文件.txt", "r");
if (fp == NULL) {
    // 写法 A: 自动打印 "前缀: 错误描述"
    perror("无法打开配置文件"); 
    
    // 写法 B: 手动获取错误字符串
    // printf("错误代码: %d, 描述: %s\n", errno, strerror(errno));
}

输出示例: 无法打开配置文件: No such file or directory

六、 练习题

题目 1: printf("%5d", 10) 会输出什么(用空格表示)?

题目 2: scanf("%d", &num) 的返回值代表什么?

题目 3: 如果用 "w" 模式打开一个已经存在且有内容的文件,会发生什么?

题目 4: fgets 读取文件时,什么时候会停止?(列举两种情况)

题目 5: 为什么在 Windows 上处理二进制文件必须加 b 模式?

题目 6: feof(fp) 是用来预测 文件结束,还是确认文件结束?

题目 7: 下面代码有什么问题?

c 复制代码
while (!feof(fp)) {
    fgets(buf, 100, fp);
    printf("%s", buf);
}

题目 8: fflush(stdin) 是标准行为吗?

题目 9: 如何将标准输出(stdout)重定向到一个文件?

题目 10: fread 返回 0 一定代表文件读完了吗?

题目 11: 结构体中有指针成员 char *name,直接 fwrite 这个结构体能保存名字内容吗?

题目 12: fprintf(stderr, ...)printf(...) 有什么区别?

题目 13: 什么是文件缓冲区?

题目 14: 如何删除一个文件?

题目 15: 打开文件后忘记 fclose 会有什么后果?

七、 解析

题 1 解析
答案: " 10" (三个空格+10)。
详解:

%5d 表示最小宽度为 5,右对齐,不足补空格。

题 2 解析
答案: 成功读取并赋值的变量个数。
详解:

这是一个重要的检查点。如果用户输入字母,scanf 读不了整数,返回 0。

题 3 解析
答案: 原文件内容被**瞬间清空**。
详解:

"w" 是破坏性极强的模式。如果只是想看,一定要用 "r"

题 4 解析
答案: 1. 读满了 n-1 个字符;2. 遇到了换行符 \n(或 EOF)。

题 5 解析
答案: 防止换行符转换。
详解:

Windows 会把 \n (0x0A) 存成 \r\n (0x0D 0x0A)。读取时反之。二进制数据(如 JPG)里可能恰好有 0x0A,如果被转义,图片就坏了。

题 6 解析
答案: 确认。
详解:

只有当读取函数(如 fgetc/fgets)试图读取并失败后,feof 才会变成真。

题 7 解析
答案: 最后一行会打印两次。
详解:

经典错误。最后一次 fgets 失败(读到 EOF),buf 内容没变,但循环还在继续打印旧的 buf。正确写法是 while(fgets(...) != NULL)

题 8 解析
答案: 不是。
详解:

C 标准只定义了 fflush(输出流)。清空输入缓冲区应使用循环 getchar

题 9 解析
答案: freopen("out.txt", "w", stdout); 或在命令行用 > out.txt

题 10 解析
答案: 不一定。
详解:

也可能是出错了(Error)。需要检查 ferror(fp)

题 11 解析
答案: 不能。
详解:

只保存了指针的值(一个地址),而不是字符串内容。下次读回来这个地址早就无效了。这叫**"浅拷贝"**。

题 12 解析
答案:

stdout 是带缓冲的,通常用于正常输出。
stderr 是无缓冲的,用于报错,即使程序崩溃也能立即显示。

题 13 解析
答案: 内存里的一块区域。数据先攒在缓冲区,满了一起写入磁盘,为了减少 IO 次数提高效率。

题 14 解析
答案: remove("filename.txt");

题 15 解析
答案:

  1. 资源泄露(文件句柄耗尽)。
  2. 数据丢失(缓冲区里的数据还没刷入磁盘)。

日期:2025年2月14日

专栏:C语言

相关推荐
新缸中之脑2 小时前
SaaS 大灭绝
开发语言·ios·swift
娇娇乔木2 小时前
模块十四--String/StringBuilder--尚硅谷Javase笔记总结
java·开发语言
脏脏a2 小时前
【C++篇】面向对象编程的三大特性:深入解析继承机制
开发语言·c++·继承·组合
csdn2015_2 小时前
mybatisplus 获得新增id
java·开发语言·mybatis
ghie90902 小时前
差速转向移动机器人基于速度的动力学模型与自适应控制器 MATLAB实现
开发语言·matlab
JiL 奥2 小时前
AWS之Gitlab增量架构(c/c++项目)
c语言·gitlab·aws
寻寻觅觅☆2 小时前
东华OJ-基础题-124-分数化小数(C++)-难度中
开发语言·c++·算法
hanbr10 小时前
C++ 初涉
开发语言·c++
Дерек的学习记录10 小时前
C++:入门基础(下)
开发语言·数据结构·c++·学习·算法·visualstudio