C 语言第 4 章:顺序结构——printf 与 scanf 的格式控制与典型错误

写于 2026-11-01

顺序结构是最简单的一种结构:语句从上到下依次执行,没有任何跳转。真正需要花时间的不是"顺序"这个概念,而是它的两个入口——printf 怎么把数据排成想要的版式,scanf 怎么把键盘输入正确切分并送进变量。这两件事看着琐碎,却是初学者 bug 最密集的地方:忘了取地址符、%c 读到了回车、5/9 算成了 0、%lf 与 %f 用反。本章的目标是把格式串当成一门小语言来学:一个 % 号后面,依次是修饰符、宽度、精度、长度、转换字符,读懂了这五段,任何格式串都能拆开。

一、顺序结构与三类语句

C 程序由函数组成,main 里的语句按书写顺序执行。构成顺序结构的基本语句有四类:

  • 表达式语句:a = b + c;、i++;,任何表达式加分号。
  • 函数调用语句:printf("hi");。
  • 复合语句:用 { } 把多条语句包成一个逻辑整体,在语法上等价于一条语句。
  • 空语句:单独一个分号,什么都不做,常用在循环体需要留空的地方。

顺序结构没有分支也没有回路,所以它的正确性完全取决于数据流:每个变量在使用前是否已经拿到了正确的值。这也是本章反复强调输入输出的原因。

二、printf:格式字符与修饰符

函数原型是 int printf(const char *format, ...),返回值是实际输出的字符数,出错时返回负数。格式串里的普通字符原样输出,% 开头的叫转换说明,完整的语法是:

%[标志][宽度][.精度][长度]转换字符

转换字符(最常用的几个):

字符 含义 示例(值) 输出
%d / %i 十进制有符号整数 42 42
%u 十进制无符号整数 42u 42
%o 八进制(无前缀) 255 377
%x / %X 十六进制(小写/大写) 255 ff / FF
%c 单个字符 'A' A
%s 字符串 "abc" abc
%f 定点小数,默认 6 位 3.14159 3.141590
%e / %E 科学计数法 1234.5 1.234500e+03
%g / %G 自动在 %f 与 %e 间选较短者 0.0001 0.0001
%% 输出一个百分号 — %

标志控制对齐与填充:- 左对齐(默认右对齐)、+ 强制显示正号、空格 正数前留空格、0 用 0 填充空位、# 加进制前缀(%#x 输出 0xff,%#o 输出 0377)。

宽度是最小字段宽度,精度对浮点是小数位数、对字符串是最多输出的字符数。几个对照记住就够:

printf("[%5d]", 42);      /* [   42] 右对齐补空格 */
printf("[%-5d]", 42);     /* [42   ] 左对齐 */
printf("[%05d]", 42);     /* [00042] 补零 */
printf("[%8.3f]", 3.14159); /* [   3.142] 宽 8、3 位小数 */
printf("[%.2s]", "abcdef"); /* [ab] 只取前 2 个字符 */

注意宽度和精度都会四舍五入(%.3f 把 3.14159 变成 3.142),而宽度不足时不会截断数据,会原样输出更长的内容。

一条容易踩的坑:printf("%d %d", i++, i++) 这类写法里,参数求值顺序标准未规定,不同编译器结果可能不同,属于未定义行为,不要在格式串里塞有副作用的表达式。

三、scanf:格式串与典型错误

原型 int scanf(const char *format, ...),返回值是成功读入并赋值的项数;一个都没读到时返回 0,遇到输入结束返回 EOF(通常是 −1)。

scanf 的行为和 printf 不对称,必须单独记:

  1. 变量前必须加 &。scanf 需要知道往哪写,传的是地址。scanf("%d", a) 把 a 的值当成了地址,程序很可能直接崩溃。数组名与字符数组例外(它们本身就是地址)。
  2. 格式串里的非格式字符要原样输入。scanf("%d,%d", &a, &b) 要求用户输入 3,4,逗号必须敲;写成 scanf("%d %d", ...) 则任意空白(空格、制表、回车)都能分隔。
  3. 不要在格式串末尾加 \n。scanf("%d\n", &n) 中的 \n 表示"跳过所有空白",程序会一直等到用户输入一个非空白字符才返回,表现为"按了回车没反应"。
  4. %d 会跳过前导空白,%c 不会。这是 %c 最经典的坑:先用 scanf("%d", &n) 输入数字并回车,缓冲区里留下 '\n',紧接着的 scanf("%c", &ch) 会把这个回车读走,看起来像"跳过了输入"。两种解法:写成 scanf(" %c", &ch)(%c 前加一个空格,表示跳过空白),或先 getchar(); 吃掉回车。
  5. double 必须用 %lf。因为 scanf 收到的是指针,不存在 printf 那种"float 自动提升为 double"的机制,用 %f 读 double 会把 8 字节按 4 字节解释,数据错乱。(printf 里 %f 与 %lf 在 C99 起等价。)
  6. %s 不检查长度。输入超过数组容量就会越界,应加宽度限制:scanf("%9s", buf),留一位给 '\0'。

养成检查返回值的习惯,是让程序"抗摔"的第一步:

if (scanf("%d", &n) != 1) {
    printf("输入不是合法整数\n");
    return 1;
}

四、字符型变量与字符编码

char 在内存里就是一个字节的整数,存的是字符的编码值。C 保证所有字符编码中数字 0~9 是连续的、字母 a~z 与 A~Z 各自连续,所以可以直接做算术:

字符 ASCII 码 常用运算
'0' 48 ch - '0' 得数值
'A' 65 ch + 32 得小写
'a' 97 ch - 32 得大写
空格 32 ——
'\0' 0 字符串结束标志

'0' 与 0 差 48,这是"字符转数字"和"数字转字符"的唯一秘密。判断字符类别时可以用 <ctype.h>:isdigit、isalpha、isspace、toupper、tolower,比手写区间判断更清楚,也不会在非 ASCII 编码下出错。

转义字符用来表示不可打印或特殊的字符:\n 换行、\t 制表、\\ 反斜杠、\" 双引号、\' 单引号、\0 空字符、\r 回车、\b 退格、\a 响铃,以及 \ddd(1~3 位八进制)和 \xhh(十六进制)两种按码值写法。

五、温度换算实例

华氏转摄氏的公式是 C = 5/9 × (F − 32)。

#include <stdio.h>

int main(void)
{
    double f, c;
    printf("请输入华氏温度:");
    if (scanf("%lf", &f) != 1) { printf("输入错误\n"); return 1; }
    c = 5.0 / 9 * (f - 32);        /* 不能写 5/9 */
    printf("摄氏温度 = %.2f\n", c);
    return 0;
}

这段代码藏着本章几乎全部的考点:%lf 读 double、检查返回值、5.0 / 9 避免整数除法、%.2f 控制两位小数。输入 100 时输出 37.78,输入 32 时输出 0.00,输入 −40 时输出 −40.00——最后这个巧合可以用来自测公式有没有写错。

把 5.0 / 9 写成 5 / 9 是本章最高频的错误:两个 int 相除,结果取整得 0,整个表达式恒为 0。同类问题还有 1 / 2、a / b 求平均。判断标准很简单:只要这一步的数学含义是"小数",就至少让一个操作数变成浮点。

易错点

  1. scanf 忘写 &。编译能过(最多一个警告),运行崩溃或写坏别的变量。
  2. 在 scanf 格式串末尾加 \n,导致程序卡住等待输入。
  3. %c 读到上一次输入残留的回车。用 " %c" 或 getchar() 清掉。
  4. 5 / 9 整数除法得 0,温度换算全错。
  5. scanf 用 %f 读 double。printf 里两者等价,scanf 里不等价。
  6. 用 %d 输出 float,或用 %f 输出 int。类型不匹配是未定义行为,输出常是垃圾值。
  7. %s 没有宽度限制,输入过长时数组越界。

这一章在整门课里的位置

本章是第 3 章流程图中"平行四边形输入输出框"的代码落地,也是第 2 章数据类型的第一次实战检验——格式符与类型必须一一对应。它同时是第 5、6 章的前置:后面所有分支与循环程序,都要靠 scanf 拿数据、靠 printf 出结果。可以说第 5 章的选择结构、第 6 章的循环结构,本质上都是在本章这套输入输出骨架外面套上控制流。

本文是《C 语言程序设计》第 4 章的知识点摘要。完整板书与代码见 课程页;上一章 算法基础,下一章 选择结构。

相关课程

这篇笔记涉及的主题,我录过对应的视频课程: