速查: printf 与转换说明 → 任务 1: 打印对齐的成绩表
速查: 转义序列 → 任务 2: 用转义序列打印三行
速查: scanf 与输入缓冲区 → 任务 3: 遗留换行的坑
格式串中的普通字符 → 任务 4: 一次读入混合类型
printf/scanf 混淆、getchar/putchar → 任务 5: 大小写转换
综合案例: 分数相加 · 课堂小测
先看输出: printf 把值填进格式串的空位:
printf 必须提供格式字符串, 后跟要插入到串中的值:
printf(格式串, 表达式1, 表达式2, ...);
格式串 = 普通字符 (原样打印) + 转换说明 (以 % 开头, 用对应的值替换)
宽度 / 精度 / 左对齐, 一个例子:
printf("|%-8s|%10.1f|\n", "name", 85.5);
输出: |name••••|••••••85.5|
%-8s: 宽 8、左对齐
%10.1f: 宽 10、1 位小数、右对齐
编译器不检查转换说明与参数的数量、类型: 多了少了、或用 %d 打印 double (%d 从整数通道取到的是无关值), 输出都无意义.
刚才的 %-8s、%10.1f 只是其中两例, 完整清单如下:
写法: %[标志][宽度][.精度]X, 如 %-8s (宽 8、左对齐)、%10.1f (宽 10、1 位小数); 标志、宽度、精度都可省略 (省略精度时句点一并去掉).
| 转换说明 | 用于 | 示例 |
|---|---|---|
%d |
int |
42 |
%f |
float / double (printf) |
3.140000 |
%lf |
double (仅 scanf) |
读入 1.75 |
%c |
char |
A |
%s |
字符串 (char []) |
Hi |
%o |
八进制整数 | 12 (由 10 得到) |
%x |
十六进制整数 | ff (由 255 得到) |
%e |
指数格式 | 1.234500e+03 |
%% |
百分号本身 | 100% |
printf 与 scanf 的 double 差异: printf 的 %f 可打印 float/double (float 自动提升); scanf 读 double 必须用 %lf, 读 float 才用 %f.
宽度不够自动加宽: 宽度 m 是最小字段宽度, 值更宽时自动加宽、不截断; - 标志表示左对齐 (%4d→•123, %-4d→123•).
精度 p 随 X 变: %d 的 p 是最少位数 (不足补 0, 默认 1); %f/%e 的 p 是小数位数 (默认 6, p 为 0 不显示小数点); %g 按大小自动选 %e/%f, p 是最大有效位数并去掉尾随零.
任务: 打印下面的表, 三列对齐 (科目左对齐, 分数右对齐保留 1 位小数, 等级右对齐)
科目 分数 等级
语文 85.5 A
数学 92.0 A
英语 78.2 B
提示: 数据列用 %10.1f (总宽 10, 1 位小数) 与 %8c; 中文表头不要用 %8s——%s 的宽度按字节算, 一个汉字 3 字节却只占 2 列, 直接用固定空格对齐表头
#include <stdio.h>
int main(void)
{
printf("科目 分数 等级\n"); /* 中文表头用固定空格 */
printf("%-8s%10.1f%8c\n", "语文", 85.5, 'A');
printf("%-8s%10.1f%8c\n", "数学", 92.0, 'A');
printf("%-8s%10.1f%8c\n", "英语", 78.2, 'B');
return 0;
}
格式: %[标志][宽度][.精度]转换——%-8s 左对齐, %10.1f 右对齐保留 1 位
中文表头不要用 %8s: %s 的宽度按字节算, 一个汉字 3 字节却只占 2 列; 表头用固定空格, 数据列仍用 %10.1f/%8c
列对齐的前提: 每一行同一个位置用相同的显示宽度
宽度不够会自动加宽 (不会截断数字); 字符串超宽则照原样输出
表格类输出是 printf 宽度修饰最常见的用途
对齐靠宽度; 字符串里还有看不见的字符——转义序列:
转义序列 让字符串包含非打印 (控制) 字符或具有特殊含义的字符; 反斜杠 \ 是转义引子——它和后面的一个字符合成"一个字符" (如 \n 是一个字符, 不是 \ 和 n 两个).
| 转义序列 | 含义 | 示例 |
|---|---|---|
\n |
换行符 | 光标移到下一行开头 |
\t |
水平制表符 | printf("A\tB") 两列对齐 |
\" |
双引号 " |
printf("\"Hi\"") → "Hi" |
\\ |
一个反斜杠 \ |
printf("C:\\temp") → C:\temp |
\a |
警报 (铃声) | 响铃 |
\b |
回退符 | 光标退回一格 |
一个字符串可以包含任意数量的转义序列: printf("Item\tUnit\tPurchase\n\tPrice\tDate\n");
要打印双引号必须写 \"; 要打印单个 \ 必须写 \\, 否则它会和后一个字符组成别的转义序列.
任务: 打印下面的内容 (注意引号、反斜杠、制表符):
姓名 分数
"张三" 92
路径: C:\temp
提示: 换行 \n; 制表 \t (列间对齐); 双引号写 \"; 反斜杠本身写 \\
#include <stdio.h>
int main(void)
{
printf("姓名\t分数\n");
printf("\"张三\"\t92\n");
printf("路径: C:\\temp\n");
return 0;
}
\n 换行, \t 制表 (两列对齐比空格稳)
\" 才能在字符串里打印出双引号; \\ 才能打印出一个反斜杠
反斜杠是转义引子: 它和后面的字符合成"一个字符", 如 \n 是一个字符
反斜杠写单个会引发警告/意外转义 (如 \t 变制表符)
输出会了, 反过来看输入——scanf 怎么读?
scanf 按格式串从左到右依次匹配; 格式串通常只含转换说明
通常每个变量前要加 & (取地址), 告诉 scanf 把值存到哪里
返回值 = 成功读入的项数, 可用于检查输入是否完整
读 double 必须用 %lf (读 float 才用 %f)
一次读入两个 int、两个 double:
int i, j;
double x, y;
scanf("%d%d%lf%lf", &i, &j, &x, &y);
样本输入: 1 -20 .3 -4.0e3
i=1 j=-20 x=0.3 y=-4000.0
scanf 不检查转换说明的数量和类型是否与变量匹配; 顺序错了, 读到的是无意义的值.
一次读多个值已经清楚; 连续多次 scanf 时, 数据从哪来?
输入先进入缓冲区; scanf 每次从上次读到的位置接着读, 多次调用共享同一条输入流
对每个转换说明, scanf 在缓冲区里找合适的数据, 必要时跳过空白 (空格 / 制表 / 换行)
读整数时先找数字或正负号, 继续读数字, 直到遇到非数字; 这个字符被放回, 留给下一次读取
缓冲区没有数据时, scanf 等待用户输入——交互式程序"卡住"往往是在等你敲键盘; 数据不匹配时则立即返回
int i, j; double x, y;
scanf("%d%d%lf%lf", &i, &j, &x, &y);
输入的数字可以在同一行, 也可以分布在多行:
••1
-20•••.3
•••-4.0e3
1-20.3-4.0e3 的处理:
%d → i=1, j=-20 (放回 - 和 .)
%lf → x=0.3, y=-4000.0 (放回 - 和换行)
%d / %f 读完后停在第一个不能构成数字的字符; 换行符因此常留在缓冲区里——下一页的 %c 会读到它.
int a, b;
printf("请输入两个整数: ");
scanf("%d", &a); /* 读走 10, 剩 " 20↵" */
printf("a = %d\n", a);
scanf("%d", &b); /* 跳过空格, 接着读 20 */
printf("b = %d\n", b);
用户只输入一行 10 20↵:
请输入两个整数: 10 20↵
a = 10
b = 20
输入先全部进入缓冲区, 两条 scanf 依次"消费": 第一个拿走 10, 第二个接着拿走 20
scanf 读走的数据不会再次被读到; 夹在中间的 printf 只打印已读到的值
若第一次只输入 10↵ 就回车, 第二个 scanf 在缓冲区找不到整数, 程序卡住等待继续输入
%d / %f 跳过空白, %c 不跳过——它读到的可能就是上一次留下的换行
int n; char ch;
scanf("%d", &n);
scanf("%c", &ch); /* ch 读到 '\n', 不是新字符! */
scanf(" %c", &ch); /* 空格: 跳过所有空白 */
修复: 在 %c 前加一个空格 (" %c"), 格式串里的空白会跳过所有空白字符.
scanf 与 getchar() 混用同理: scanf 结束后换行符还留在输入流里, 会被后面的 getchar() 当"第一个字符"吃掉; 先 getchar() 把换行吃掉再读, 也常用于读整行之前.
任务: 先读年龄 (整数), 再读性别 (字符)——结果性别读到了换行, 打印为空。修复它。
样例:
输入: 19 M
期望: age=19 gender=M
提示: scanf("%d") 读完后缓冲区还剩一个 \n; 试在 %c 前加一个空格
#include <stdio.h>
int main(void)
{
int age;
char gender;
scanf("%d", &age);
scanf(" %c", &gender); /* 空格: 跳过空白字符 */
printf("age=%d gender=%c\n", age, gender);
return 0;
}
%d 读完数字就停, 换行还留在输入缓冲区里
%c 不跳过空白, 直接把那个 \n 读走——所以"看起来什么都没读到"
在 %c 前加空格 (" %c"): 格式串里的空白会跳过所有空白字符
同类坑: 用 getchar() 手动吃掉换行, 也常用于读整行之前
换行的坑搞清楚了; 格式串里的普通字符也有讲究:
当在格式字符串中遇到一个或多个空白字符时, scanf 从输入中读取空白字符, 直到它遇到一个非空白字符(即*放回*)
当它在格式字符串中遇到非空白字符时, scanf 将其与下一个输入字符进行比较
如果匹配, 则 scanf 丢弃输入字符并继续处理格式字符串
若不匹配, 则 scanf 会将有问题的字符放回输入中, 然后中止
例子:
如果格式字符串是 "%d/%d" 并且输入是 •5/•96, scanf 成功
如果输入 •5•/•96, 则 scanf 失败, 因为格式字符串中的 / 与输入中的空格不匹配
要在第一个数字后允许空格, 需改用格式字符串 "%d /%d"
任务: 一行输入年龄、身高、性别, 读入 int、double、char 并回显:
输入 (年龄 身高 性别): 19 1.75 M
age=19 height=1.75 gender=M
提示: 三个转换说明按顺序匹配; %c 前留空格 (" %c") 跳过空白; 别忘 &
#include <stdio.h>
int main(void)
{
int age;
double height;
char gender;
printf("输入 (年龄 身高 性别): ");
scanf("%d %lf %c", &age, &height, &gender);
printf("age=%d height=%.2f gender=%c\n", age, height, gender);
return 0;
}
一条 scanf 可以按顺序读多个值: %d → int, %lf → double, %c → char
输入里的空格会被格式串中的空白跳过; %c 不跳过, 所以写 " %c"
scanf 的返回值是成功读入的项数 (这里应为 3), 可用于检查输入
变量与转换说明一一对应, 顺序错了读到的是垃圾值
混合读入没问题; 但 printf 和 scanf 长得像, 容易混:
scanf 和 printf 的调用看起来相似, 但两者之间存在显著差异
一个常见的错误是在printf调用中将&放在变量前面:
printf("%d %d\n", &i, &j); /*** WRONG ***/
不要错误地假设 scanf 格式字符串应该类似于 printf:
scanf("%d, %d", &i, &j);
若输入中逗号的位置实际是空格, 则scanf将终止而不读取j的值
多数情况下, 一定不要在 scanf 格式化字符串的末尾放置换行符, 否则会导致交互式程序 挂起 直到用户输入下一个非空格字符
getchar: 读取单个字符
int ch; /* 注意: 返回值是 int, 不是 char! */
ch = getchar(); /* 从键盘读取一个字符, 存入 ch */
为什么用 int? 因为 getchar 还可能返回 EOF, char 装不下
putchar: 输出单个字符
putchar(ch); /* 在屏幕上显示字符 ch */
putchar('\n'); /* 输出换行符 */
getchar/putchar 读写字符的效率高于 scanf/printf, 在字符处理类程序(如统计输入字符个数)中常用
任务: 输入一个小写字母, 用 getchar/putchar 输出对应大写字母+换行:
输入: b
输出: B
提示: getchar() 返回字符的编码 (int); 小写比大写大 32: c - 'a' + 'A'
#include <stdio.h>
int main(void)
{
int c = getchar();
putchar(c - 'a' + 'A');
putchar('\n');
return 0;
}
getchar() 读一个字符并返回编码 (int), 适合逐字符处理
putchar(c) 输出一个字符; 用 putchar('\n') 换行
字符本质是整数: c - 'a' + 'A' 完成小写→大写 (差 32)
注意: 输入 "b 回车" 时, 回车还留在缓冲区——前面"遗留换行"的坑同理
格式串里的 / 需要原样匹配; 用它读两个分数并相加:
#include <stdio.h>
int main(void)
{
int num1, denom1, num2, denom2, result_num, result_denom;
printf("Enter first fraction: ");
scanf("%d/%d", &num1, &denom1);
printf("Enter second fraction: ");
scanf("%d/%d", &num2, &denom2);
result_num = num1 * denom2 + num2 *denom1;
result_denom = denom1 * denom2;
printf("The sum is %d/%d\n",result_num, result_denom);
return 0;
}
示例程序输出:
Enter first fraction: 5/6 Enter second fraction: 3/4 The sum is 38/24
printf("%-8.3f", x) 中 -、8、.3 分别表示什么?
" 和 \,转义序列分别怎么写?
scanf("%d/%d", &a, &b) 要求用户怎么输入?
getchar() 返回值的类型是什么?为什么它适合读取字符?