5. 输入输出函数:printf格式化输出、scanf格式化输入、getchar与putchar、字符串输入输出
说到C语言的输入输出,我估计很多初学者第一反应就是 printf 和 scanf。没错,这两个函数几乎贯穿了你整个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 格式化输入——小心陷阱
scanf 是 printf 的“反向操作”。它从标准输入读取数据,按照格式字符串解析,存入变量地址中。
常见坑:变量前面一定要加 & 取地址!我见过太多新手写 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 知识体系总览
下面这张图帮你理清本章的知识结构:
5.6 避坑指南
最后,我把自己踩过的坑总结一下,你遇到了可以直接翻到这里查:
- scanf 忘记取地址:
scanf("%d", a)是错的,要写成scanf("%d", &a)。 - printf 类型不匹配:比如用
%d打印float,结果会乱码。编译器一般会警告,别忽略。 - gets 缓冲区溢出:已经被C11废弃,别用了。用
fgets替代。 - getchar 返回值用 char 接收:应该用
int,否则无法判断EOF。 - fgets 保留换行符:如果你不想要,记得手动去掉。
嗯,输入输出这部分内容虽然基础,但细节很多。你想想看,一个程序再复杂,最终不也是靠这些函数跟用户交互吗?把基础打牢,后面学文件操作、网络编程时你会轻松很多。
公众号:蓝海资料掘金营,微信 deep3321