本文是《C语言期末速成》系列第 4 篇。程序的数据要展示给用户(printf),也要接收用户的输入(scanf)。这两个函数是全卷出现频率最高的”工具人”,而且极容易在格式上丢分。核心思想就四个字:一模一样——格式串怎么写,屏幕就怎么显示、键盘就怎么输入。


上一篇C语言期末速成 3:运算符与表达式

考点清单

  • 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八进制(带前缀 0int
%x十六进制(无前缀,小写)int
%#x十六进制(带前缀 0xint
%%输出一个百分号——
%p地址void*

注意

  • %% 用于输出 % 本身,避免被当成格式符开头;
  • doubleprintf 输出可以用 %f,但 scanfdouble 必须用 %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 读入时按格式串逐个处理,规则是:

  1. %d 会先跳过前面的空白(空格、Tab、换行),再读连续的整数;
  2. %c 不跳过空白,它读的就是”下一个字符”(哪怕是空格);
  3. 读到与格式不匹配的字符,立即停止,后续数据留在缓冲区。
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 读到非数字就停);接着 %4d2345;最后 %d67

/* 输入 "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;
}

两个细节:

  1. getcharputchar 的返回值/参数其实是 int,所以 putchar(66) 会输出 ASCII 码 66 对应的字符 'B'
  2. 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 位为例):

  1. x * 100 → 314.159,把要保留的部分挪到整数位;
  2. + 0.5 → 314.659,第 3 位小数 ≥ 0.5 时能进位到整数部分;
  3. (int) 取整 → 314 或 315(四舍五入完成);
  4. / 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; 是直接去掉小数部分(截断),和四舍五入不同——一个是”砍掉尾巴”,一个是”四舍五入”。


易错点 / 陷阱

  1. scanf 忘写 &scanf("%d", x) 绝对错误,必须 &x
  2. double 输入用 %fscanfdouble 必须用 %lf,用 %f 会读错。
  3. 格式串与输入不匹配scanf("a=%d,b=%d", &a, &b) 输入必须带 a=b=,
  4. %c 读入空格%c 不跳过空白,1%c 组合读的是字符 ‘1’ 而非整数 1。
  5. 多余变量不输出printf("%d", 12, 34) 只输出 12。
  6. 四舍五入公式忘 .0(int)(x*100+0.5)/100 是整数除法,结果恒为 0。
  7. 交换写成 x=y; y=x:值会丢,必须用中间变量。
  8. 宽度不足时右边补%5d左边补空格,不是右边。

自测小练习

  1. printf("%5.2f", 3.14159) 的输出是什么?
  2. int x = 017; printf("%d %#o %#x", x, x, x) 输出什么?
  3. scanf("%2d%3d", &a, &b),输入 1234567,a、b 各是多少?
  4. 判断正误:scanf("%d", p);(p 是指向 int 的指针)。
  5. 写出把 3.14159 四舍五入保留 3 位小数的表达式,并说明最后为什么除 1000.0 而不是 1000
参考答案
  1. 小数 2 位:3.14;整体宽度 5,3.14 占 4 位,左边补 1 个空格 → " 3.14"
  2. 15 017 0xf%d 十进制、%#o 带前缀八进制、%#x 带前缀十六进制)。
  3. a = 12(2 位)、b = 345(3 位),剩下的 67 留在缓冲区。
  4. 对。p 中存放的本来就是 x 的地址,scanf("%d", p) 等价于 scanf("%d", &x)
  5. y = (int)(3.14159 * 1000 + 0.5) / 1000.0。除 1000.0 是浮点除法才能保留小数;若除 1000,整数除法得到 0。

下一篇C语言期末速成 5:程序的三大基本结构

评论区