5. 输入输出函数:printf格式化输出、scanf格式化输入、getchar与putchar、字符串输入输出

说到C语言的输入输出,我估计很多初学者第一反应就是 printfscanf。没错,这两个函数几乎贯穿了你整个C语言学习生涯。但说实话,很多人用了很久也只是“会用”,离“用好”还有一段距离。今天我就带你把这些函数彻底捋一遍。

5.1 printf 格式化输出——不只是打印

printf 是C标准库中最常用的输出函数。它的核心在于“格式化”三个字。你给它一个格式字符串,它按规则把数据转换成字符串,然后输出到标准输出(通常是屏幕)。

核心语法:

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

返回值是成功打印的字符数,如果出错返回负数。这个返回值我建议你偶尔检查一下,尤其是在嵌入式或文件输出场景中。

5.1.1 格式占位符

格式字符串里用 % 开头的东西叫占位符。我列几个最常用的:

占位符含义示例
%d有符号十进制整数printf("%d", 10);
%f浮点数(默认6位小数)printf("%f", 3.14);
%c单个字符printf("%c", 'A');
%s字符串printf("%s", "hello");
%x十六进制(小写)printf("%x", 255);
%p指针地址printf("%p", &a);

嗯,这里要注意:%f 默认打印6位小数。如果你只想保留两位,可以写成 %.2f。我在项目中调试传感器数据时经常用这个,避免满屏的小数点。

5.1.2 宽度与对齐

你可以在 % 和格式字符之间加一个数字,指定最小宽度。比如 %5d 表示至少占5个字符位置,右对齐。加个负号就是左对齐:%-5d

printf("%5d\n", 42);   // 输出 "   42"
printf("%-5d\n", 42);  // 输出 "42   "

我个人习惯在做表格输出时用这个,能让数据对齐得整整齐齐。

5.2 scanf 格式化输入——小心陷阱

scanfprintf 的“反向操作”。它从标准输入读取数据,按照格式字符串解析,存入变量地址中。

常见坑:变量前面一定要加 & 取地址!我见过太多新手写 scanf("%d", a); 然后程序崩溃。记住:scanf 需要的是指针,不是值。

5.2.1 基本用法

int age;
printf("请输入年龄:");
scanf("%d", &age);

这里 %d 告诉 scanf 去读一个整数。如果用户输入了非数字字符,scanf 会返回0(匹配失败),并且那个坏字符会留在输入缓冲区里。我曾经在做一个命令行工具时,用户不小心输入了字母,结果程序陷入死循环——就是因为没处理输入缓冲区残留。

5.2.2 输入缓冲区问题

为什么会这样?因为 scanf 读取时,遇到空白字符(空格、换行、制表符)会停止。但那些空白字符还留在缓冲区里。如果你紧接着用 getchar 或另一个 scanf,可能会读到意料之外的字符。

解决办法:在 %d 前面加一个空格,让 scanf 跳过所有空白字符:

scanf(" %d", &age);  // 注意 %d 前面有个空格

或者用 getchar 手动清空缓冲区。我个人更推荐前者,简洁。

5.3 getchar 与 putchar——字符级输入输出

这两个函数是C语言最基础的字符I/O函数。它们一次只处理一个字符,但效率高、控制力强。

5.3.1 getchar

getchar 从标准输入读取下一个字符。它的返回值是 int 类型,不是 char。为什么?因为要能返回 EOF(文件结束标志),而 EOF 通常定义为 -1,char 装不下。

int ch;
while ((ch = getchar()) != EOF) {
    putchar(ch);
}

这段代码可以原样输出你输入的所有字符,直到你按 Ctrl+D(Linux)或 Ctrl+Z(Windows)。

小技巧:getchar 可以轻松实现“按任意键继续”的效果。我早期写控制台小游戏时经常这么用。

5.3.2 putchar

putchar 把一个字符输出到标准输出。它比 printf("%c", ch) 快得多,因为省去了格式解析的开销。

putchar('A');  // 输出 A
putchar('\n'); // 换行

5.4 字符串输入输出——gets 与 puts 的恩怨

字符串的输入输出,C语言提供了几个专门的函数。但这里有个大坑,我必须提醒你。

5.4.1 gets 已被废弃

gets 函数曾经是读取字符串的“标准答案”。但它不检查缓冲区大小,如果输入过长,就会发生缓冲区溢出。这是安全漏洞的常见来源。

重要:永远不要使用 gets!C11标准已经把它删除了。用 fgets 代替。

5.4.2 fgets 才是正道

fgets 可以指定最大读取长度,避免溢出:

char name[50];
printf("请输入姓名:");
fgets(name, sizeof(name), stdin);

它会读取最多 sizeof(name)-1 个字符,或者遇到换行符为止。注意:它会保留换行符。如果你不想要换行符,可以手动去掉:

name[strcspn(name, "\n")] = '\0';

这行代码我几乎每次用 fgets 都会写,已经形成肌肉记忆了。

5.4.3 puts 与 fputs

puts 输出一个字符串,并自动追加一个换行符。简单粗暴:

puts("Hello, world!");

如果你不想自动换行,用 fputs

fputs("Hello, world!", stdout);

5.5 知识体系总览

下面这张图帮你理清本章的知识结构:

C语言输入输出函数 格式化I/O printf scanf 字符I/O getchar putchar 字符串I/O fgets puts printf 格式控制 %d %f %c %s %x %p 宽度、对齐、精度 scanf 注意事项 取地址 & 缓冲区残留 字符串安全 禁用 gets fgets 指定长度 核心原则:类型匹配 + 缓冲区安全 + 错误处理

5.6 避坑指南

最后,我把自己踩过的坑总结一下,你遇到了可以直接翻到这里查:

  • scanf 忘记取地址scanf("%d", a) 是错的,要写成 scanf("%d", &a)
  • printf 类型不匹配:比如用 %d 打印 float,结果会乱码。编译器一般会警告,别忽略。
  • gets 缓冲区溢出:已经被C11废弃,别用了。用 fgets 替代。
  • getchar 返回值用 char 接收:应该用 int,否则无法判断 EOF
  • fgets 保留换行符:如果你不想要,记得手动去掉。

嗯,输入输出这部分内容虽然基础,但细节很多。你想想看,一个程序再复杂,最终不也是靠这些函数跟用户交互吗?把基础打牢,后面学文件操作、网络编程时你会轻松很多。


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