1. C语言输入输出基础:stdio.h与标准流
在C语言的世界里,输入输出(I/O)是与外界交互的桥梁。所有I/O操作都通过标准库<stdio.h>实现,这个头文件定义了三种标准流:
- stdin(标准输入流):默认关联键盘输入
- stdout(标准输出流):默认关联显示器输出
- stderr(标准错误流):默认关联显示器错误输出
这些流本质上是文件指针(FILE*类型),这种设计源于Unix"一切皆文件"的哲学。初学者常困惑为什么printf和putchar都能输出到屏幕——因为它们默认都操作stdout流。
注意:在嵌入式开发中,这些标准流经常需要重定向。比如STM32上printf输出到串口,就需要重写fputc函数。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 字符级I/O:getchar与putchar详解
2.1 getchar的工作原理
getchar()是最基础的输入函数,其原型为:
c复制int getchar(void);
它从stdin读取一个字符并返回其ASCII码(int类型)。关键点在于:
- 使用int而非char作为返回值,是为了能返回EOF(通常为-1)表示输入结束
- 实际实现通常带缓冲,需要按Enter键后才会提交输入
常见误区:
c复制char c = getchar(); // 错误!可能丢失EOF信息
正确做法:
c复制int c;
while((c = getchar()) != EOF) {
putchar(c);
}
2.2 putchar的输出机制
与getchar对应,putchar(int c)向stdout输出一个字符。有趣的是,以下两种写法等价:
c复制putchar('A');
fputc('A', stdout);
在Linux系统调用层面,最终会执行write(1, &c, 1)(1是stdout的文件描述符)。
3. 格式化I/O:printf/scanf深度解析
3.1 printf的格式控制
printf的完整格式说明符为:
code复制%[flags][width][.precision][length]specifier
常见坑点:
- 浮点数精度:%.2f会四舍五入
- 宽度补零:%05d输出至少5位,不足补零
- 参数匹配:格式说明符与参数类型必须严格匹配
特殊用法示例:
c复制printf("%*d", 10, 123); // 输出10位宽度的123
printf("%hhd", 256); // 输出0(截断到char范围)
3.2 scanf的输入陷阱
scanf的问题远比printf多:
- 缓冲区问题:混合使用gets/scanf时容易出错
- 匹配失败:输入与格式不匹配会导致后续读取全部失败
- 安全性:%s可能导致缓冲区溢出(应用%10s限制)
安全用法示例:
c复制char buf[100];
scanf("%99s", buf); // 限制最大长度
while(getchar() != '\n'); // 清空输入缓冲区
4. 文件I/O:fprintf/fscanf实战
4.1 文件操作基础流程
标准文件操作三部曲:
c复制FILE *fp = fopen("file.txt", "r+");
if(fp == NULL) { /* 错误处理 */ }
fprintf(fp, "写入内容");
fclose(fp);
重要模式说明:
- "r+":读写模式,文件必须存在
- "w+":创建/清空文件后读写
- "a+":追加模式,保留原内容
4.2 二进制文件操作
与文本模式不同,二进制I/O使用:
c复制size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
典型应用场景:
c复制struct Student stu;
fwrite(&stu, sizeof(struct Student), 1, fp);
注意:二进制文件跨平台时需考虑字节序问题
5. 高级话题与性能优化
5.1 缓冲机制详解
C标准定义了三种缓冲模式:
- 全缓冲(_IOFBF):缓冲区满才刷新
- 行缓冲(_IOLBF):遇到换行符刷新
- 无缓冲(_IONBF):立即输出
设置方法:
c复制setvbuf(stdout, buf, _IOFBF, sizeof(buf));
5.2 重定向实现原理
以STM32的printf重定向为例:
c复制int _write(int fd, char *ptr, int len) {
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 100);
return len;
}
这种实现利用了底层系统调用的重定向机制。
6. 常见问题排查指南
6.1 中文乱码问题
根源通常是编码不匹配:
- 确保源代码文件保存为UTF-8
- 终端设置为UTF-8编码
- Windows下可能需要_setmode(_fileno(stdout), _O_U16TEXT)
6.2 输入输出错误处理
检查错误的标准方法:
c复制if(ferror(stdin)) {
perror("输入错误");
clearerr(stdin);
}
特别关注errno值:
- EAGAIN:非阻塞I/O时资源暂时不可用
- EBADF:文件描述符无效
- EIO:底层I/O错误
7. 最佳实践与性能对比
7.1 输入函数性能测试
实测不同方法的性能差异(读取10MB数据):
| 方法 | 时间(ms) |
|---|---|
| getchar() | 120 |
| fgets()+sscanf | 85 |
| fread() | 45 |
结论:批量读取总是优于单字符操作
7.2 输出优化技巧
- 减少小数据量write调用:
c复制// 不佳
for(int i=0; i<100; i++) printf("%d", i);
// 优化
char buf[256];
int len = 0;
for(int i=0; i<100; i++) len += sprintf(buf+len, "%d", i);
puts(buf);
- 使用setbuf禁用缓冲时,确保足够大的缓冲区
8. 嵌入式开发特别注意事项
8.1 资源受限环境下的I/O
- 避免使用浮点格式(%f),会显著增加代码体积
- 简化printf实现:使用-nano库或自定义轻量级版本
- 慎用动态内存分配的函数(如asprintf)
8.2 中断安全的I/O实现
在多任务/中断环境中:
c复制void USART1_IRQHandler() {
char c = USART1->DR;
// 使用环形缓冲区而非直接printf
ringbuf_put(&rx_buf, c);
}
9. 现代C标准的新特性
C11新增的格式化宏:
c复制#define __STDC_FORMAT_MACROS
#include <inttypes.h>
printf("int64_t: %" PRId64 "\n", i64);
安全版本函数:
c复制char buf[10];
scanf_s("%9s", buf, sizeof(buf));
10. 调试技巧与工具推荐
10.1 使用strace追踪系统调用
Linux下观察I/O行为:
bash复制strace -e trace=write,read ./a.out
10.2 十六进制查看文件内容
调试二进制输出的利器:
bash复制hexdump -C output.bin
10.3 自定义printf调试宏
开发时建议使用:
c复制#ifdef DEBUG
#define DBG(fmt, ...) printf("[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...)
#endif
在实际项目中,我发现最容易被忽视的是流的状态检查。很多奇怪的I/O问题都源于没有正确处理错误状态和文件尾标志。建议在每个关键I/O操作后都检查ferror()和feof(),这能节省大量调试时间。
