格式化输入输出:printf、scanf的格式控制符详解

说到C语言,我最先想聊的就是输入输出。你想想看,学编程第一件事儿是什么?不就是让程序跟你说话嘛。printf和scanf就是C语言里最基础、最常用的两个函数。我当年刚学的时候,被这些格式控制符搞得晕头转向,后来在项目里踩过几次坑,才算真正搞明白。

说白了,格式控制符就是告诉程序:「嘿,这个数据你要怎么显示,或者你要怎么读进来」。不同的数据类型,得用不同的控制符。用错了,轻则显示乱码,重则程序崩溃。嗯,咱们一个一个来看。

1. 格式控制符的基本结构

先看个最简单的例子:

printf("数字是:%d\n", 42);

这里的%d就是格式控制符。%是起始标记,d表示十进制整数。中间还可以加一些修饰符,比如宽度、精度什么的。我习惯把格式控制符拆成三部分来看:

  • 起始符%,告诉编译器后面跟着的是格式控制
  • 修饰符:可选,控制宽度、对齐、精度等
  • 类型符dfc等,指定数据类型
小技巧:写printf的时候,我习惯先写格式字符串,再写变量。这样不容易漏掉参数。

2. 常用格式控制符详解

咱们先看一张表,把最常用的几个控制符列出来:

控制符 说明 示例 输出结果
%d 有符号十进制整数 printf("%d", -10); -10
%u 无符号十进制整数 printf("%u", 10); 10
%f 浮点数(小数形式) printf("%f", 3.14); 3.140000
%c 单个字符 printf("%c", 'A'); A
%s 字符串 printf("%s", "hello"); hello
%x 十六进制整数(小写) printf("%x", 255); ff
%X 十六进制整数(大写) printf("%X", 255); FF
%p 指针地址 printf("%p", &a); 0x7ff...

我在项目中遇到过一个问题:用%d打印无符号整数,结果负数显示成了很大的正数。为什么会这样?因为%d把内存里的二进制解释成了有符号数。所以,类型匹配很重要。

3. 格式修饰符

光有类型符还不够,有时候我们需要控制输出的宽度、精度、对齐方式。修饰符就是干这个的。

3.1 宽度控制

printf("%5d", 42);   // 输出:"   42"(右对齐,占5位)
printf("%-5d", 42);  // 输出:"42   "(左对齐,占5位)

宽度控制说白了就是「这个数最少占几个字符位置」。如果数字本身位数不够,就用空格补齐。我习惯用%-5d这种左对齐,打印表格时特别好用。

3.2 精度控制

printf("%.2f", 3.14159);  // 输出:"3.14"
printf("%.5d", 42);       // 输出:"00042"(前面补0)

精度控制对浮点数来说,就是小数点后保留几位。对整数来说,就是最少显示几位,不够前面补0。嗯,这里要注意:精度和宽度是两回事,别搞混了。

3.3 组合使用

printf("%8.2f", 3.14159);  // 输出:"    3.14"(总宽8位,小数2位)

宽度和精度可以一起用。我个人习惯先写宽度,再写精度,中间用点隔开。

重要提醒:修饰符的顺序是固定的——先宽度,后精度。写反了编译器不会报错,但结果会乱。

4. scanf的格式控制

scanf和printf的格式控制符基本一样,但有个关键区别:scanf需要传变量的地址。我见过太多新手在这里翻车了。

int a;
scanf("%d", &a);   // 正确:传地址
scanf("%d", a);    // 错误:传值,程序会崩溃

为什么会这样?因为scanf要把读到的数据写进变量里,必须知道变量在内存中的位置。传值的话,它不知道往哪儿写。

4.1 scanf的常见陷阱

我曾经在调试一个串口通信程序时,发现scanf读字符串总是少一个字符。后来才意识到,scanf读字符串时会在末尾自动加\0,所以缓冲区必须多留一个字节。

char buf[10];
scanf("%9s", buf);  // 最多读9个字符,留一个给'\0'
避坑指南:我曾经在项目里用scanf("%s", buf),结果用户输入了超长字符串,直接导致缓冲区溢出,程序崩溃。从那以后,我每次都用宽度限制,比如%9s

4.2 scanf的返回值

很多人不知道,scanf其实有返回值。它返回成功读取的项数。我习惯用这个来判断输入是否合法:

int a, b;
if (scanf("%d %d", &a, &b) != 2) {
    printf("输入格式错误!\n");
}

嗯,这个小技巧能帮你避免很多莫名其妙的bug。

5. 知识体系总览

下面这张图把本章的核心知识点串起来了,你可以对照着复习:

格式化输入输出知识体系 格式控制符 printf 输出 scanf 输入 类型符:%d %f %c %s %x %p 修饰符:宽度、精度、对齐 组合使用:%8.2f 必须传地址:&变量 缓冲区溢出:加宽度限制 返回值检查:判断输入是否合法 核心原则:类型匹配 + 地址传递 + 边界检查

6. 实战建议

说了这么多,最后给你几个我自己的经验:

  • 调试时多用%p:打印指针地址,能帮你快速定位内存问题。我每次调试指针相关代码,第一件事就是打印地址看看。
  • 浮点数输出用%.2f:默认的%f会输出6位小数,太长了。我习惯保留2位,够用又清爽。
  • 十六进制用%#x:加个#前缀,输出会变成0xff这种格式,一眼就能看出是十六进制数。
  • scanf别用%s读字符串:除非你确定输入长度可控。否则用fgets更安全。

嗯,格式化输入输出看起来简单,但用好了能省不少事儿。你写代码的时候多留意这些细节,慢慢就形成肌肉记忆了。


公众号:蓝海资料掘金营,微信 deep3321