C输入输出函数详解:Coursebook printf/scanf完整参考
【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebook
Coursebook 是伊利诺伊大学开源的系统编程入门教材,其"C 语言"一章用精炼的篇幅讲透了 C 输入输出的核心:三大标准流、printf格式占位符、缓冲机制以及scanf家族的安全用法。本文基于教材原文,为你整理一份面向新手的 C 输入输出函数完整参考,帮你一次搞懂printf/scanf背后的原理与常见坑点。
三大标准流:stdin、stdout 与 stderr
每个 C 程序启动时都自带三条数据流,由文件描述符 0、1、2 标识:
| 流 | 描述符 | 用途 | 默认缓冲行为 |
|---|---|---|---|
stdin标准输入 | 0 | 接收程序输入(通常来自终端) | 重定向到文件时全缓冲,终端下行缓冲 |
stdout标准输出 | 1 | 输出程序结果(printf默认写到它) | 同上 |
stderr标准错误 | 2 | 输出错误与调试信息 | 不缓冲,立即写出 |
教材特别强调:stderr按惯例是不缓冲的,而stdin/stdout只有在目标不是交互终端(比如重定向到文件)时才全缓冲。这解释了为什么printf写到终端时看到换行就立刻出现,写到文件时却"憋到最后才出现"。
三条流都可以用 shell 重定向指向文件或别的程序,形成管道。下图展示了多个进程通过标准输出/输入串联成管道的典型场景:
👉 这也正是 Unix 哲学"每个程序只做一件事,通过标准流组合"的体现。
printf 格式字符串与常用占位符
printf的第一个参数是格式字符串,其中的占位符决定后续参数如何被格式化输出:
| 占位符 | 作用 |
|---|---|
%s | 按 C 字符串打印,直到遇到 NUL 字符 |
%d | 按十进制整数打印 |
%c | 打印单个字符 |
%p | 打印指针的内存地址 |
%f/%x | 打印浮点数 / 十六进制 |
一个典型的用法(来自教材示例):
printf("Hello %s, your result is %d\n", name, score);注意:%s需要字符串指针,而打印整数变量的地址时必须写&score——这是新手最常犯的错之一。教材原文见 common_c_functions.tex。
只打印一行文字或单个字符时,还有更轻量的puts("...")和putchar('1')。
缓冲机制:printf 什么时候真正写出去
printf并不直接写系统,它先把数据放进缓冲区,再调用系统调用write真正落盘。ISO 标准定义了三种流类型:
- 不缓冲:内容尽快到达目的地(
stderr通常如此) - 行缓冲:遇到换行符就写出(连终端的
stdout通常如此) - 全缓冲:缓冲区写满才写出(重定向到文件时)
如果等不及自动刷新,可以显式调用fflush()强制把缓冲区内容写出去。理解这一点,你排查"输出为什么没出现在日志文件里"这类问题时就有的放矢了。
其他输出函数:fprintf 与 sprintf 家族
fprintf(stderr, ...):把格式化输出写到任意FILE*流(预定义的stdout/stderr,或fopen返回的文件流)dprintf(fd, ...):直接对文件描述符做格式化输出- 格式化到字符串:教材给出了一个安全优先级排序 👇
| 函数 | 安全性 | 说明 |
|---|---|---|
sprintf | ⚠️ 不检查边界 | 仅在输出长度可确定时使用 |
snprintf | ✅ 永不越界 | 传入缓冲区大小,超长会截断 |
asprintf | ✅✅ 最佳 | 堆上自动分配恰好够大的缓冲区,用完记得free |
snprintf的返回值值得记住:它返回"本应写入的字符数",如果返回值 ≥ 缓冲区大小,说明输出被截断了。
scanf 家族安全用法与避坑指南
读取输入时,教材的核心建议是:检查返回的解析数量是否等于期望值。scanf/fscanf/sscanf都会返回成功解析的项数,从默认输入流、任意文件流或 C 字符串中读取。
三个必记的安全要点:
gets已被淘汰:无法限制读取长度,极易缓冲区溢出,C11 标准直接移除。请改用fgets(buffer, sizeof(buffer), stdin)或能自动扩容的getline。- 用宽度限定符防溢出:
scanf("%9s", buffer)最多读 9 个字符,给 10 字节的缓冲区留一个字节放结尾 NUL。 - 别把指针本身当数据:
sscanf(line, "%c %d", &type, &data)中data是int*,传&data会改写指针本身,后续free(data)直接崩溃。
性能方面,scanf家族因要兼容所有printf规格符而开销较大,追求极致性能的程序可以手写解析。详见教材 introc.tex 引用的完整章节。
项目内延伸阅读资源
想深入 C 输入输出,这些仓库文件值得收藏:
- 函数详解原文:introc/common_c_functions.tex —— 覆盖错误处理、I/O、
string.h全家桶 - 章末练习(含 stdout/stderr 交错输出思考题):introc/introc.tex
- 全书章节顺序:order.yaml
- 构建与部署脚本:Makefile、_scripts/
💡 教材还给了一个实用技巧:用man -S3 printf查阅 C 库函数的 man 手册(第 3 节是 C 库,第 2 节是系统调用),比翻书更快。
【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考