欢迎光临
我们一直在努力

6. 格式化输入输出——和程序说说话

前面五篇文章,我们熟悉了变量、常量、数据类型,但程序还像个闷葫芦——要么沉默不语,要么只喊一句固定的“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 试试看。
  • 我们下期见!

    赞(0)
    未经允许不得转载:171主机测评 » 6. 格式化输入输出——和程序说说话
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址