C语言期末速成4:格式化输入输出
printf/scanf 的完整语法、格式说明符、宽度精度,以及 scanf 缓冲残留与四舍五入技巧。
本文是《C语言期末速成》系列第 4 篇。程序的数据要展示给用户(
printf),也要接收用户的输入(scanf)。这两个函数是全卷出现频率最高的”工具人”,而且极容易在格式上丢分。核心思想就四个字:一模一样——格式串怎么写,屏幕就怎么显示、键盘就怎么输入。
考点清单
- 用
printf/scanf前要写#include <stdio.h> printf("格式串", 变量列表):把变量按照格式串的样式输出,显示结果与格式串一模一样- 格式说明符必背表:
%d %c %f %lf %s %o %#o %x %#x %ld %% - 宽度与精度:
%5d(宽度 5,不足补空格)、%5.3f(整体宽度 5、小数 3 位)、%.2f scanf的第二部分必须是地址:scanf("%d", &a),不是a- 指定输入宽度:
scanf("%2d%4d%d", ...) getchar()无参数、读取一个字符;putchar(c)输出一个字符- 变量交换用中间变量:
t=x; x=y; y=t; - 保留 N 位小数并四舍五入:
y = (int)(x * 1eN + 0.5) / 1eN
一、printf:按格式输出
1.1 基本结构
printf("第一部分", 第二部分);
- 第一部分:格式串,含普通字符和格式说明符;
- 第二部分:要和第一部分中的格式说明符一一对应的变量/表达式/常量;
- 普通字符会原样显示。
int a = 12, b = 34;
printf("a=%d, b=%d\n", a, b);
/* 屏幕显示:a=12, b=34 ← 除 %d 被替换外,其余字符一字不差 */
这就是”一模一样”:格式串里 a=、=、,、空格、\n 都会照原样打印,%d 的位置填上后面的值。
printf 也可以只有一个参数(只打印格式串,没有待填值):
printf("Hello!\n"); /* 合法,一个参数 */
1.2 格式说明符总表(必背)
| 格式 | 含义 | 对应类型 |
|---|---|---|
%d | 十进制整数 | int |
%ld | 十进制长整数 | long |
%u | 无符号整数 | unsigned int |
%c | 单个字符 | char |
%s | 字符串 | char* |
%f | 浮点数 | float |
%lf | 双精度浮点数 | double |
%e | 科学计数法 | double |
%o | 八进制(无前缀) | int |
%#o | 八进制(带前缀 0) | int |
%x | 十六进制(无前缀,小写) | int |
%#x | 十六进制(带前缀 0x) | int |
%% | 输出一个百分号 | —— |
%p | 地址 | void* |
注意:
%%用于输出%本身,避免被当成格式符开头;double用printf输出可以用%f,但scanf读double必须用%lf(下面的坑);- 第 2 章已经演示过:
017用%d/%o/%#o/%x/%#x输出分别是15/17/017/f/0xf。
1.3 格式符与参数个数不匹配
格式符数量可以和输出参数数量不一致:
printf("%d ", 12, 34); /* 只有 1 个 %d,后面的 34 被忽略 */
printf("%c", z); /* 接着上次输出,效果是 "12A"(若 z='A') */
规则:一个格式说明符对应一个变量,多余的变量不输出。
1.4 宽度与精度:%m.nf 三件套
%[标志][宽度][.精度]类型,最常见的两种控制:
① 宽度:至少占几位。
printf("%2d", 123); /* 数据 3 位 > 宽度 2 → 原样输出 123 */
printf("%5d", 123); /* 数据 3 位 < 宽度 5 → 左边补两个空格 " 123" */
② 精度:小数保留几位,且默认补 0、做四舍五入。
printf("%f", 1.25); /* 默认 6 位小数:1.250000 */
printf("%.2f", 1.25); /* 保留 2 位:1.25 */
printf("%.0f", 1.5); /* 不保留小数并四舍五入:2 */
③ 宽度+精度:%m.nf 表示整体宽度 m(小数点算一位)、小数 n 位。
printf("%5.3f", 1.25); /* 小数 3 位 = 1.250;整体 5 位,补前导空格 → "1.250" */
printf("%3.1f", 1.25); /* 小数 1 位,四舍五入 → 1.3;整体 3 位 → "1.3" */
运算规则梳理:
- 先按精度把小数部分补/舍成 n 位(四舍五入);
- 再算总长度(含小数点)是否达到宽度 m,不够则左边补空格。
二、scanf:按格式读入
2.1 大忌:第二部分必须是地址
scanf("%d", x); /* 错!x 是变量值,scanf 需要"往哪里写" */
scanf("%d", &x); /* 对!&x 是变量 x 的地址 */
scanf 要做的是把读到的数据写进某个变量的内存地址,所以它需要地址。这也是 & 取址符用得最多的地方。
指针变量的地址问题(第 8 章内容,先提前感受):
int x = 2;
int *p = &x; /* p 里存的是 x 的地址 */
scanf("%d", x); /* 错:x 是数值 2,不是地址 */
scanf("%d", p); /* 对:p 里存的就是 x 的地址 */
scanf("%d", &p); /* 错:&p 是"指针变量自己的地址",写进去的是指针变量 */
scanf("%d", *p); /* 错:*p 是 x 的数值,不是地址 */
口诀:scanf 第二部分的每个参数都必须是”地址”——&变量 或”本身就是地址的指针”。
2.2 格式串照样”一模一样”
scanf 格式串里的普通字符必须原样输入,否则读入失败:
scanf("a=%d, b=%d", &a, &b);
/* 键盘必须输入 a=12, b=34,与格式串完全一致才能正确读入 */
多一个空格、少一个逗号都不行——这就是”一模一样”在输入端的体现。
2.3 匹配机制(加深)
scanf 读入时按格式串逐个处理,规则是:
%d会先跳过前面的空白(空格、Tab、换行),再读连续的整数;%c不跳过空白,它读的就是”下一个字符”(哪怕是空格);- 读到与格式不匹配的字符,立即停止,后续数据留在缓冲区。
scanf("%d", &x); /* 输入 "1" → x = 1(整数 1) */
scanf("%c", &x); /* 输入 "1" → x 存的是字符 '1',ASCII 48 */
第 2 章说过字符和整数的”近亲”关系,这里就是它的实战版:同样一个 1,%d 读成数值 1,%c 读成字符 ‘1’(内部是 48)。这是超高频考点。
2.4 指定宽度:一次读几个字符
scanf("%2d%4d%d", &x, &y, &z);
输入 1234567:
| 说明 | 结果 |
|---|---|
%2d 读 2 位 | x = 12 |
%4d 读 4 位 | y = 3456 |
%d 读剩下 | z = 7 |
若输入 1 234567(1 和 2 之间有空格):%2d 想读 2 位,但 1 后面是空格,数字序列断掉,只读得 1(%d 读到非数字就停);接着 %4d 读 2345;最后 %d 读 67。
/* 输入 "1 234567" */
scanf("%2d%4d%d", &x, &y, &z);
/* x = 1, y = 2345, z = 67 */
2.5 %*d:跳过不读
* 表示”读但丢弃”:
scanf("%d%d%*d%d", &a, &b, &c);
/* 输入 1 2 3 4 → a=1, b=2, 3 被跳过, c=4 */
2.6 输入缓冲:%d 后接 %c 的经典翻车问题(注意)
键盘输入先进入输入缓冲区,scanf 从缓冲区取数据。%d 会跳过前面的空白(空格/Tab/换行),但 %c 不跳过空白——它直接把缓冲区里下一个字符读走。
于是出现著名的坑:先 scanf("%d", &n); 再 scanf("%c", &c);,你按完数字后的那个回车键会被 %c 读走,c 得到的不是你以为的字母,而是换行符 '\n':
int n; char c;
scanf("%d", &n); /* 输入 5 并按回车,回车留在缓冲区 */
scanf("%c", &c); /* c 读到的是 '\n',不是你想输入的字符! */
解决办法:在 %c 前面加一个空格让 scanf 跳过空白,或主动清掉缓冲:
scanf(" %c", &c); /* 方法一:%c 前加空格,跳过残留的空白 */
while (getchar() != '\n'); /* 方法二:把缓冲里到换行前的内容全部吞掉 */
2.7 scanf 的返回值
scanf 返回成功读入的变量个数;如果输入无法匹配(如要求数字却输入字母)返回 0;读到文件末尾返回 EOF(-1)。这个返回值可以用来循环读入:
int n;
while (scanf("%d", &n) == 1) { /* 只要成功读到一个整数就继续 */
printf("%d\n", n * n);
}
三、getchar 与 putchar:字符的收发室
getchar()无参数,从键盘读一个字符返回(第 6 章循环常配合它判断输入);putchar(c)把字符c输出到屏幕。
#include <stdio.h>
int main(void) {
char ch = getchar(); /* 从键盘读一个字符给 ch */
putchar(ch); /* 原样打印出来 */
return 0;
}
两个细节:
getchar和putchar的返回值/参数其实是int,所以putchar(66)会输出 ASCII 码 66 对应的字符'B';putchar一次只输出一个字符,换行要自己写putchar('\n')。
配套的判断题:getchar 可以带参数 → 错;putchar 一次输出一个字符 → 对。
四、两个必背”工具箱”程序
4.1 交换两个变量的值
不能写 x = y; y = x;——那会让 x 的值被覆盖丢失。标准做法用中间变量:
int t;
t = x;
x = y;
y = t;
口诀:先存一个到兜里,再腾挪。 交换是后续排序、函数传址(第 6、8 章)的地基。
4.2 保留 N 位小数并四舍五入
用 printf("%.2f", x) 只是”显示”四舍五入,若要真的把值算出来,用公式:
double x = 3.14159;
double y = (int)(x * 100 + 0.5) / 100.0; /* 保留 2 位,对第 3 位四舍五入 */
double z = (int)(x * 1000 + 0.5) / 1000.0; /* 保留 3 位,对第 4 位四舍五入 */
原理拆解(以保留 2 位为例):
x * 100→ 314.159,把要保留的部分挪到整数位;+ 0.5→ 314.659,第 3 位小数 ≥ 0.5 时能进位到整数部分;(int)取整 → 314 或 315(四舍五入完成);/ 100.0→ 3.14 或 3.15(除数必须带.0,否则整数除法直接归零)。
double r = 3.14159;
printf("%.2f\n", r); /* 显示 3.14 */
double r2 = (int)(r * 1000 + 0.5) / 1000.0;
printf("%.3f\n", r2); /* 值真的变成 3.142 */
推广公式:保留 k 位,乘 1e(k)、加 0.5、取整、除以 1e(k),最后那个除数必须是浮点数。
补充:
x = (int)x;是直接去掉小数部分(截断),和四舍五入不同——一个是”砍掉尾巴”,一个是”四舍五入”。
易错点 / 陷阱
scanf忘写&:scanf("%d", x)绝对错误,必须&x。double输入用%f:scanf读double必须用%lf,用%f会读错。- 格式串与输入不匹配:
scanf("a=%d,b=%d", &a, &b)输入必须带a=、b=、,。 %c读入空格:%c不跳过空白,1和%c组合读的是字符 ‘1’ 而非整数 1。- 多余变量不输出:
printf("%d", 12, 34)只输出 12。 - 四舍五入公式忘
.0:(int)(x*100+0.5)/100是整数除法,结果恒为 0。 - 交换写成
x=y; y=x:值会丢,必须用中间变量。 - 宽度不足时右边补:
%5d是左边补空格,不是右边。
自测小练习
printf("%5.2f", 3.14159)的输出是什么?int x = 017; printf("%d %#o %#x", x, x, x)输出什么?scanf("%2d%3d", &a, &b),输入1234567,a、b 各是多少?- 判断正误:
scanf("%d", p);(p 是指向 int 的指针)。 - 写出把
3.14159四舍五入保留 3 位小数的表达式,并说明最后为什么除1000.0而不是1000。
参考答案
- 小数 2 位:
3.14;整体宽度 5,3.14占 4 位,左边补 1 个空格 →" 3.14"。 15 017 0xf(%d十进制、%#o带前缀八进制、%#x带前缀十六进制)。a = 12(2 位)、b = 345(3 位),剩下的67留在缓冲区。- 对。
p中存放的本来就是x的地址,scanf("%d", p)等价于scanf("%d", &x)。 y = (int)(3.14159 * 1000 + 0.5) / 1000.0。除1000.0是浮点除法才能保留小数;若除1000,整数除法得到 0。