前面五篇文章,我们熟悉了变量、常量、数据类型,但程序还像个闷葫芦——要么沉默不语,要么只喊一句固定的“Hello, World”。要让程序真正和人互动,就得学会两样本事:
- 输出:把数据展示给用户看(
printf)。 - 输入:接收用户输入的数据(
scanf)。
今天我们就来攻克它们。掌握了输入输出,你的程序就不再是“静态的”,而是能对话的活物了。
一、printf:把数据打扮好了再显示
printf 名字里的 f 是 “formatted” 的意思,也就是“按格式打印”。它能让你决定输出内容的样子:是左对齐还是右对齐,小数点后保留几位,占多宽的位置。这些控制,全靠格式字符串里的转换说明来完成。
1. 基本格式
printf("格式字符串", 参数1, 参数2, ...);
格式字符串里包含两类东西:
- 普通字符:原样输出,比如
"Hello"。 - 转换说明:以
%开头,用来“挖坑”,后面的参数会填进去并格式化。
例:
int age = 20;
printf("我今年 %d 岁。\n", age);
%d 就是一个坑,age 的值 20 填进去,输出 我今年 20 岁。
2. 常见转换说明一览
| 转换说明 | 对应类型 | 输出形式 |
|---|---|---|
%d / %i | int | 有符号十进制整数 |
%u | unsigned int | 无符号十进制整数 |
%f | float / double | 十进制浮点数(默认 6 位小数) |
%e / %E | float / double | 科学计数法(如 1.234568e+03) |
%g / %G | float / double | 自动选 %f 或 %e,去掉多余零 |
%c | char | 单个字符 |
%s | char *(字符串) | 字符串(直到 \0) |
%p | 指针 | 地址(十六进制) |
%% | 无对应参数 | 输出一个 % 符号 |
注意:printf 输出 double 和 float 都可以用 %f,因为 float 在传递时会被自动提升为 double。
示例:
#include <stdio.h>
int main(void) {
int count = 3;
double price = 19.95;
char grade = 'A';
printf("买了 %d 件,单价 %.2f 元,评级 %c\n", count, price, grade);
return 0;
}
输出:
买了 3 件,单价 19.95 元,评级 A
3. 修饰符:控制宽度、精度和对齐
在 % 和字母之间,还可以加修饰符来精细控制:
- 最小字段宽度:
%5d表示至少占 5 个字符宽度,不足则右对齐补空格。%-5d表示左对齐。 - 精度:
%.2f表示浮点数保留 2 位小数;%.5s表示字符串最多输出前 5 个字符。 - 长度前缀:
%ld用于long int,%lld用于long long int,%hd用于short int。printf里%lf也可以用于double(但通常%f足够)。
示例:
int n = 42;
double pi = 3.14159265;
printf("|%8d|\n", n); // 输出 | 42|
printf("|%-8d|\n", n); // 输出 |42 |
printf("%.4f\n", pi); // 输出 3.1416
printf("%10.2f\n", pi); // 输出 3.14(10宽度,2位小数)
4. 常见错误:参数数量或类型不匹配
int x = 10;
printf("%d %d\n", x); // 少参数,可能打印垃圾值
printf("%f\n", x); // 类型不匹配,未定义行为
务必要保证转换说明和后面参数的类型、数量一一对应,否则结果是未定义行为。
二、scanf:读取用户输入
输出是把数据给出去,输入就是把数据收进来。scanf 同样是格式化输入,基本用法和 printf 类似,但有一点致命不同——你必须把变量的地址交给它,而不是变量本身。
1. 基本格式
scanf("格式字符串", 地址列表);
例:
int age;
printf("请输入你的年龄:");
scanf("%d", &age); // 注意 & 取地址符
printf("你输入的年龄是 %d\n", age);
&age 表示“变量 age 的地址”,scanf 需要知道这个地址,才能把读取的值写进去。这个和 printf 直接传值完全不同,是初学者最容易忘记的区别。
2. 常见输入转换说明
| 转换说明 | 对应变量类型 | 说明 |
|---|---|---|
%d | int | 读取十进制整数 |
%u | unsigned int | 读取无符号整数 |
%f | float | 读取浮点数 |
%lf | double | 读取 double(scanf 里必须用 %lf) |
%c | char | 读取一个字符(包括空格、换行!) |
%s | char 数组(即字符串) | 读取一个单词(遇到空白停止) |
特别注意:scanf 里 float 用 %f,double 必须用 %lf,这和 printf 不一样。
3. 多个输入
int day, month, year;
printf("请输入年月日(用空格分开):");
scanf("%d %d %d", &day, &month, &year);
printf("输入的日期是:%d-%d-%d\n", year, month, day);
用户可以在一行或多行输入,空白字符(空格、换行、制表符)会被当作分隔符。
4. scanf 的返回值
scanf 会返回成功读取并赋值的项数,如果遇到输入结束或匹配失败,返回 EOF 或小于你期望的项数。这可以用来判断用户输入是否有效。
int num;
printf("请输入一个整数:");
if (scanf("%d", &num) == 1) {
printf("你输入了 %d\n", num);
} else {
printf("输入无效!\n");
}
三、输入缓冲区的“坑”
scanf 并不是直接从键盘读字符,而是从一个叫做输入缓冲区的地方读取。你用键盘输入的字符先暂存在缓冲区里,按下回车后,scanf 再按格式解析。
这个机制会带来一些陷阱,特别是混合 %c 和 %d 的时候。
陷阱一:残留的换行符
int age;
char first_char;
printf("年龄:");
scanf("%d", &age);
printf("一个字符:");
scanf("%c", &first_char);
printf("age=%d, char='%c'\n", age, first_char);
运行后,你可能发现第二个 scanf 根本没等你输入,直接结束了,first_char 里是 '\n'(回车)。这是因为输入 18 后按下的回车还留在缓冲区,%c 直接把它吃了。
解决办法:在 %c 前加一个空格:scanf(" %c", &first_char);,这个空格会跳过之前的所有空白字符(包括换行符)。
陷阱二:输入不匹配导致死循环
int num;
while (1) {
printf("请输入一个整数:");
if (scanf("%d", &num) != 1) {
printf("输入错误!\n");
// 但错误的输入还在缓冲区里!下次循环会无限失败
} else {
break;
}
}
如果用户输入了字母,scanf 匹配失败,那些字母仍留在缓冲区,下一次循环 scanf 又会读到同样的错误字符,导致死循环。解决方法是清空缓冲区:可以使用一个循环 while (getchar() != '\n'); 来丢弃剩余字符。
后文讲到字符串和文件 I/O 时,会详细介绍更健壮的输入处理方法。现在只需知道“缓冲区”这个概念即可。
四、一个小互动程序
把 printf 和 scanf 组合起来,写一个简单的计算器:
#include <stdio.h>
int main(void) {
double num1, num2;
char op;
printf("请输入算式(例如 3 + 5):");
scanf("%lf %c %lf", &num1, &op, &num2);
if (op == '+') {
printf("结果:%.2lf\n", num1 + num2);
} else if (op == '-') {
printf("结果:%.2lf\n", num1 - num2);
} else if (op == '*') {
printf("结果:%.2lf\n", num1 * num2);
} else if (op == '/') {
if (num2 != 0) {
printf("结果:%.2lf\n", num1 / num2);
} else {
printf("错误:除数不能为零。\n");
}
} else {
printf("不支持的运算符。\n");
}
return 0;
}
这里用了简单的 if-else 分支,下一篇文章我们会系统学习分支结构。现在你只需感受一下——程序终于可以根据你的输入,做出不同的反应了。
五、小结
printf 和 scanf 是你和程序之间的翻译官。printf 把内存里的数据变成人能读的文字,scanf 把人敲的字符变成内存里的变量。它们的核心是格式字符串,学会了 %d、%f、%c 这些符号,就等于学会了和 C 语言对话的基本语法。
但要真正让程序“有判断力”,光会输入输出还不够。下一篇,我们就进入分支结构,让程序学会根据不同条件执行不同代码——从 if 到 switch,让程序聪明起来。
课后小练习
- 写一个程序,提示用户输入圆的半径,然后计算并输出周长和面积(周长 =
2 * 3.14 * r,面积 =3.14 * r * r),输出时保留两位小数。 - 让用户输入三个整数,计算它们的平均值并输出,平均值保留一位小数。
- 写程序让用户输入一个字符,输出该字符的 ASCII 码值(提示:用
%d格式输出char变量)。 - (陷阱挑战)先读取一个整数,再读取一个完整的字符串(可能包含空格),你会怎么实现?提示:
%s会停在空格处,要用其他方法,可以自己搜索fgets试试看。
我们下期见!
445

被折叠的 条评论
为什么被折叠?



