此前我们借助 printf 单向打印信息。这一部分补充输入功能,使用 scanf 获取键盘传来的数据,让程序实现和用户交互。

printf:格式化输出

printf 的完整形态是:

printf("格式串", 值1, 值2, ...);

格式串里的占位符(format specifier)和后面的参数按顺序一一对应:第一个 % 配第一个参数,第二个配第二个,以此类推。

C 语言 printf 格式化输出示例运行
#include <stdio.h>

int main(void) {
    int year = 1972;
    double pi = 3.14159;
    printf("C 诞生于 %d 年。\n", year);
    printf("圆周率 = %.2f\n", pi);   // 保留 2 位小数
    printf("%d + %d = %d\n", year, 1, year + 1);  // 参数也可以是表达式
    return 0;
}

输出:

C 诞生于 1972 年。
圆周率 = 3.14
1972 + 1 = 1973

最容易犯的错误:占位符数量和后面的参数数量不一致,或类型不匹配,都会打印出垃圾值,属于未定义行为。写几个、跟几个,需要一一对应。

常用占位符一览

占位符用于示例
%dintprintf("%d", 42);
%ccharprintf("%c", 'A');
%ffloat / doubleprintf("%f", 3.14);
%lfdouble(scanf 读入时必用)scanf("%lf", &x);
%s字符串(char*)printf("%s", "hi");
%x / %o十六进制 / 八进制printf("%x", 255);ff
%e / %g科学计数法 / 自动选短%.2e1.23e-06

控制宽度与精度

%f 默认打印 6 位小数,所以 3.14 会输出成 3.140000,噪音很大。用精度修饰符来压住它。格式是 %W.Nf:W 是总宽度(含小数点),N 是小数位数。

格式含义对 b=3.14159
%f默认 6 位小数3.141590
%.0f只留整数部分(四舍五入)3
%.1f1 位小数3.1
%.2f2 位小数(金额、百分比常用)3.14
%8.2f宽度 8 右对齐,2 位小数    3.14
%-8.2f宽度 8 左对齐,2 位小数3.14    
%08.2f宽度 8 前导补零,2 位小数00003.14

一个容易误解的点:小数位是四舍五入,不是直接截断。比如 printf("%.2f", 3.145) 会输出 3.15,而 3.144 输出 3.14

数值极大或极小时,用 %e(科学计数)或 %g(自动选更短的表示)能保住有效数字:

double v = 0.00000123;
printf("%.2f\n", v);   // 0.00 ,信息全丢
printf("%.2e\n", v);   // 1.23e-06 ,科学计数法

scanf:从键盘读入

scanf 把输入按占位符解析,存入变量。最大区别是:必须在变量前加 &(取地址操作符),因为 scanf 需要知道把数据写到哪块内存:

#include <stdio.h>

int main(void) {
    int age;
    double height;

    printf("请输入年龄和身高(空格或回车分隔):");
    scanf("%d %lf", &age, &height);

    printf("你 %d 岁,身高 %.1f 厘米。\n", age, height);
    return 0;
}

运行示例:

请输入年龄和身高(空格或回车分隔):18 170.5
你 18 岁,身高 170.5 厘米。

这里 & 的意义留到指针章再细讲。现在只记死规则:scanf 里,整数/浮点/字符变量前都要加 &,唯一例外是字符串(它本身就是地址,后面讲)。

关于格式符再强调一次:double 用 %lf(printf 里 %f%lf 都行,但 scanf 里 double 必须 %lf),int 用 %d,char 用 %c

scanf 读字符串的两个易错点

字符串不加 &

char name[50];
printf("你叫什么:");
scanf("%49s", name);   // 最多读 49 个字符,给结尾 \0 留位置
printf("你好,%s!\n", name);

两个坑:

  • %s 遇到空格或换行就停止。输入 Zhang San 只会读到 Zhang
  • 不限制宽度时,输入超出缓冲区会内存越界,安全漏洞的常见来源。务必写 %49s 这类上限(49 是数组大小减 1)。

更稳健的处理方式是 fgets,它能按行读取整行文本,后面字符串章节会介绍

scanf 的返回值

scanf 会返回成功读入的字段数。用它判断输入是否合法:

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

养成检查返回值的习惯,程序会健壮很多。

小结

printf 用于输出,scanf 用于输入。核心要点:占位符与变量匹配、普通变量前添加 &、字符串读取设置宽度限制。下一章讲解条件分支,介绍 if 与循环。