一、输入输出基础:printf格式化输出、scanf格式化输入、格式说明符详解

说实话,C语言的输入输出,是每个初学者最先接触,也最容易踩坑的地方。我见过不少工作两三年的工程师,还在为printf的格式控制符翻手册。今天咱们就把这块彻底讲透。

1.1 printf格式化输出

printf说白了就是“按指定格式往屏幕上打印东西”。它的原型在stdio.h里:

int printf(const char *format, ...);

返回值是实际打印的字符数,这个细节很多人忽略。我在项目中就遇到过,用printf返回值做调试计数,结果发现换行符也算一个字符。

1.1.1 基本用法

#include <stdio.h>

int main() {
    int a = 42;
    float b = 3.14159;
    char c = 'A';
    char str[] = "Hello C";
    
    printf("整数:%d\n", a);
    printf("浮点数:%f\n", b);
    printf("字符:%c\n", c);
    printf("字符串:%s\n", str);
    
    return 0;
}

1.1.2 格式控制符详解

格式符 说明 示例 输出
%d 有符号十进制整数 printf("%d", 42) 42
%f 浮点数(默认6位小数) printf("%f", 3.14) 3.140000
%c 单个字符 printf("%c", 'A') A
%s 字符串 printf("%s", "Hi") Hi
%x 十六进制(小写) printf("%x", 255) ff
%p 指针地址 printf("%p", &a) 0x7ff...(地址值)

核心要点:格式符必须与变量类型匹配。%d对应int,%f对应double(float会被自动提升为double),%c对应char,%s对应char*。

1.1.3 格式修饰符

光有基本格式符还不够,实际项目中经常需要控制宽度、精度、对齐方式。我个人习惯把这些修饰符记成“三件套”:

  • 宽度修饰:%5d 表示至少占5个字符宽度,右对齐
  • 精度修饰:%.2f 表示保留2位小数
  • 对齐修饰:%-5d 表示左对齐
int num = 42;
float pi = 3.1415926;

printf("右对齐:[%5d]\n", num);    // 输出:[   42]
printf("左对齐:[%-5d]\n", num);   // 输出:[42   ]
printf("补零:[%05d]\n", num);     // 输出:[00042]
printf("两位小数:[%.2f]\n", pi);  // 输出:[3.14]
printf("宽度+精度:[%8.3f]\n", pi);// 输出:[   3.142]

我的小技巧:调试时我喜欢用 %p 打印指针地址,配合 %d 看偏移量,排查内存问题特别管用。

1.2 scanf格式化输入

scanf和printf是“镜像关系”——printf是输出,scanf是输入。但有个关键区别:scanf需要传变量的地址。这个坑我当年栽过不止一次。

int age;
float salary;
char name[50];

printf("请输入年龄:");
scanf("%d", &age);      // 注意 & 符号

printf("请输入薪资:");
scanf("%f", &salary);   // 浮点数也要 &

printf("请输入姓名:");
scanf("%s", name);      // 数组名本身就是地址,不用 &

警告:scanf的%s读取字符串时,遇到空格、制表符、换行符就会停止。如果你想读取带空格的字符串,用 gets() 或 fgets() 更安全。

1.2.1 scanf的返回值

scanf返回成功匹配并赋值的参数个数。这个返回值在健壮性编程中非常有用。我曾经写过一个数据采集程序,就用这个返回值做输入校验:

int a, b;
int result = scanf("%d %d", &a, &b);

if (result != 2) {
    printf("输入格式错误!期望两个整数\n");
    // 清空输入缓冲区
    while (getchar() != '\n');
}

1.2.2 格式控制符的微妙之处

嗯,这里要注意:scanf的格式控制符和printf虽然长得一样,但行为有差异。比如 %f 在scanf中对应float*,而 %lf 才对应double*。printf里 %f 和 %lf 都可以打印double,但scanf必须严格区分。

格式符 printf对应类型 scanf对应类型
%d int int*
%f double(float自动提升) float*
%lf double double*
%c int(char自动提升) char*
%s char* char*(数组名)

1.3 常见陷阱与避坑指南

做嵌入式这些年,我总结出几个输入输出的“经典翻车现场”:

  • 缓冲区残留问题:scanf读取后,换行符会留在缓冲区,影响后续的%c读取。解决方案是在%c前加空格:scanf(" %c", &ch);
  • 浮点数精度丢失:printf默认6位小数,但float的有效数字只有6-7位,double有15-16位。别指望%.10f能打印出精确的10位小数。
  • 字符串溢出:scanf("%s", buf) 不检查缓冲区长度。安全做法是用 scanf("%49s", buf) 限制最大读取长度。

我曾经踩过的坑:有一次调试串口通信,发现printf打印的十六进制数总是少一位。排查了半天,原来是 %02x 写成了 %2x——少了前导零。从那以后,我打印十六进制数据一律用 %02x 或 %04x,确保对齐。

1.4 知识体系总览

下面这张图,把本章的核心知识点串起来了。你想想看,输入输出其实就三件事:格式符、修饰符、类型匹配。搞懂这三样,基本就掌握了八成。

C语言输入输出知识体系 输入输出基础 printf 格式化输出 scanf 格式化输入 格式说明符详解 宽度修饰 精度修饰 对齐修饰 地址传递 返回值校验 缓冲区处理 %d %f %c %s %x %p %u 类型匹配 核心原则:格式符匹配类型,修饰符控制格式,地址传递要牢记 —— 嵌入式开发中90%的输入输出问题都出在这三点上

1.5 实战建议

最后,给你几个我实际项目中的建议:

  1. 调试时多用printf:在关键变量处加printf,打印格式统一用 %d 或 %f,别偷懒。
  2. 生产代码慎用scanf:嵌入式产品中,scanf容易导致输入阻塞。建议用 fgets + sscanf 组合,更可控。
  3. 养成检查返回值的习惯:不管是printf还是scanf,返回值里藏着很多调试信息。

我的习惯:每个printf后面加个 fflush(stdout),确保数据立即输出。特别是在嵌入式裸机开发中,没有操作系统帮你缓冲,不fflush的话,数据可能一直憋在缓冲区里。

好了,输入输出基础就讲到这里。记住:格式符是“语法”,修饰符是“修辞”,类型匹配是“逻辑”。三样都拿捏住,你就能写出既漂亮又健壮的输入输出代码。


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