C scanf 与输入缓冲区陷阱
更新时间:2026-08-26。本文是
languages/c/主题入门层第 8 篇,也是"输入输出"双子星的收尾。printf 管输出,scanf 管输入。scanf 是老牌"坑王":缓冲区残留、&忘写、不校验返回值……这一篇把这些坑一次讲清。
本文要回答的问题
- 为什么 scanf 的每个变量前都要加
&? - 为什么
scanf("%d", &n)之后紧跟%c会"跳过"输入? - 读取字符串为什么不该用
gets?
一、基本用法:& 是灵魂
#include <stdio.h>
int main(void) {
int age;
char name[32];
printf("输入年龄:");
scanf("%d", &age); // 整数:必须加 &
printf("输入名字:");
scanf("%s", name); // 字符串:数组名本身就是地址,不用 &
return 0;
}规律就一条:scanf 要往变量里写值,得告诉它"这个变量在内存哪个位置"——所以传地址(&)。数组名 name 本身是地址,所以不加 &。
忘写 & 的典型报错:format '%d' expects argument of type 'int *', but argument 2 has type 'int'——gcc 的 -Wall 会警告,但更危险的是浮点的情况,值写进野地址,直接崩溃或悄悄破坏内存。
二、格式匹配:类型必须对上
double pi;
scanf("%f", &pi); // 错!%f 对应 float,%lf 才对应 double| 格式符 | 类型 | 说明 |
|---|---|---|
%d | int* | 有符号整数 |
%u | unsigned* | 无符号整数 |
%f | float* | 单精度 |
%lf | double* | 双精度(注意和 printf 不同) |
%c | char* | 读一个字符 |
%s | char* | 读字符串(到空白符为止) |
和 printf 最容易混的一点:printf 里 %f 打 double(float 提升成 double),scanf 里 %f 读 float、%lf 才读 double——因为 scanf 需要知道确切的指针类型,无法做提升。
三、返回值:scanf 的"体检报告"
scanf 返回成功读取的项数。忽略它 = 把输入错误吞掉:
int n;
if (scanf("%d", &n) != 1) {
printf("输入无效,请重新输入\n");
return 1;
}用户输入了 abc,%d 匹配失败,n 保持原值,返回值是 0。不检查返回值的程序会拿一个未初始化的 n 继续往下算——这就是"我输入了字母程序就疯了"的根源。任何 scanf 调用都应该检查返回值。
四、缓冲区陷阱:最经典的坑
先看现象:
int n;
char ch;
printf("输入数字:");
scanf("%d", &n); // 输入 42 并回车
printf("输入字符:");
scanf("%c", &ch); // 期望用户输入,但根本没等你!
printf("n=%d ch=%c(%d)\n", n, ch, ch);输出:n=42 ch= (10)——ch 拿到的是换行符 \n!原因:%d 读到数字就停,回车键留下的 \n 还躺在缓冲区里;下一个 %c 直接就把 \n 读走了。而 %d、%s 会自动跳过开头的空白符,所以连续读两个数字没这问题——只有 %c 和 gets 这种按字符读的会踩中残留换行。
解法:在 %c 前加一个空格,让 scanf 先吞掉空白:
scanf(" %c", &ch); // 空格会跳过缓冲区里残留的换行或者用 getchar() 把残留的换行吃掉。
五、读字符串:远离 gets
char buf[16];
gets(buf); // 危险!输入超过 15 个字符就写爆栈gets 不知道缓冲区多大,输入多长它写多长——这就是缓冲区溢出漏洞的经典入口。C11 已经把它从标准库移除。安全替代是 fgets:
char buf[16];
if (fgets(buf, sizeof(buf), stdin) != NULL) {
// 最多读 15 个字符,多了截断,绝不溢出
}fgets 的三个参数:目标缓冲、最大字符数(含结尾空字符)、输入流(stdin 表示键盘)。字符串 IO 的完整处理见字符串函数与字符数组。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
忘写 & | 崩溃或值写进野地址 | 除数组名外全加 & |
%f 读 double | 值错乱 | double 用 %lf |
| 不检查返回值 | 输入非法时用垃圾值 | if (scanf(...) != 1) |
%c 读到残留换行 | 输入被"跳过" | 用 " %c" 或先吃换行 |
用 gets | 缓冲区溢出 | 用 fgets |
七、与本站主线衔接
一句话总结
scanf 的规则就几条:传地址(&)、类型对应(double 用 %lf)、检查返回值、警惕 %c 吃残留换行;读字符串永远用 fgets 别用 gets——把这几条刻进肌肉记忆,输入这关就过了。
上一篇:printf 格式化输出 下一篇:运算符入门