思维导图



一、 printf 与 scanf:格式控制的艺术
1.1 格式化占位符:你真的会用吗?
printf 和 scanf 是 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.内存对齐: 不同编译器填充的字节可能不同。
- 字节序: 大端机和小端机存储整数的顺序相反。
工程对策: 定义严格的协议,逐个字节序列化,而不是直接 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 解析
答案:
- 资源泄露(文件句柄耗尽)。
- 数据丢失(缓冲区里的数据还没刷入磁盘)。

日期:2025年2月14日
专栏:C语言