1. C语言输入输出函数概述
在C语言编程中,输入输出(I/O)函数是与用户交互的基础工具链。作为一门系统级编程语言,C的I/O功能通过标准库<stdio.h>提供,这套API设计精炼却功能强大,直接影响程序的数据处理能力和用户体验。
我见过太多初学者因为对printf和scanf理解不透彻而陷入调试困境。比如最近有个学员的案例:他在STM32项目中使用printf重定向到串口时,输出始终是乱码,花了三天时间才发现是格式说明符与变量类型不匹配导致的。这正是我们需要系统掌握I/O函数的原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 标准输出函数printf详解
2.1 基础格式控制
printf的核心在于格式字符串(Format String),它包含普通字符和格式说明符(Format Specifier)。最常见的格式说明符包括:
- %d:十进制整数
- %f:浮点数
- %c:单个字符
- %s:字符串
实际开发中容易忽略的是格式修饰符,它们可以精确控制输出样式:
c复制printf("%-10.3f", 3.1415926); // 输出:3.142
// -表示左对齐,10为最小宽度,.3指定小数位数
2.2 高级特性与陷阱
缓冲区问题是printf的典型陷阱。在嵌入式系统中,输出可能不会立即显示:
c复制printf("Debug info"); // 可能不会立即输出
while(1);
// 解决方案是手动刷新缓冲区:
printf("Debug info\n"); // 换行符会触发刷新
// 或显式调用:
fflush(stdout);
重要提示:在跨平台开发时,注意Windows和Linux对换行符(\n与\r\n)的处理差异
3. 标准输入函数scanf深度解析
3.1 基础用法与安全风险
scanf的格式说明符与printf类似,但有个关键区别:
c复制int age;
char name[20];
scanf("%d", &age); // 基本类型需要取地址符&
scanf("%s", name); // 数组名本身就是地址,不需要&
最危险的莫过于缓冲区溢出问题:
c复制char buffer[10];
scanf("%s", buffer); // 用户输入超过9字符就会溢出
// 安全做法:
scanf("%9s", buffer); // 限制最大读取长度
3.2 输入验证与错误处理
完善的输入验证应该包括:
c复制int num;
while(1) {
printf("请输入1-100的整数:");
int ret = scanf("%d", &num);
if(ret != 1) { // 输入不是整数
scanf("%*s"); // 清空输入缓冲区
continue;
}
if(num >=1 && num <=100) break;
}
4. 文件I/O操作实战
4.1 文本文件读写
文件操作三部曲:
c复制FILE *fp = fopen("data.txt", "r+"); // 读写模式打开
if(fp == NULL) {
perror("文件打开失败"); // 比直接printf更专业
return -1;
}
// 写入示例
fprintf(fp, "当前温度:%.1f℃\n", 25.5);
// 读取示例
char line[100];
while(fgets(line, sizeof(line), fp)) {
printf("%s", line);
}
fclose(fp); // 必须关闭!
4.2 二进制文件处理
结构体读写是常见需求:
c复制typedef struct {
int id;
char name[20];
float score;
} Student;
// 写入
Student s = {1, "张三", 89.5};
fwrite(&s, sizeof(Student), 1, fp);
// 读取
fseek(fp, 0, SEEK_SET); // 重置文件指针
Student read_s;
fread(&read_s, sizeof(Student), 1, fp);
5. 高级话题与性能优化
5.1 重定向标准I/O
在嵌入式开发中,重定向printf到串口很常见:
c复制// 在STM32的HAL库中实现:
int __io_putchar(int ch) {
HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 10);
return ch;
}
5.2 格式化字符串性能
频繁调用printf会影响性能,替代方案:
c复制// 低效做法
for(int i=0; i<1000; i++) {
printf("Count: %d\n", i);
}
// 高效做法
char buffer[100];
for(int i=0; i<1000; i++) {
int len = sprintf(buffer, "Count: %d\n", i);
write(1, buffer, len); // 直接系统调用
}
6. 常见问题排错指南
6.1 中文乱码问题
乱码通常由以下原因导致:
- 编码不一致(源文件编码与终端编码不同)
- 宽字符处理不当
解决方案:
c复制// 方法1:设置本地化
#include <locale.h>
setlocale(LC_ALL, "zh_CN.UTF-8");
// 方法2:使用宽字符
wprintf(L"中文内容");
6.2 scanf输入残留
输入残留是常见陷阱:
c复制int a;
char c;
scanf("%d", &a); // 输入"123\n"
scanf("%c", &c); // 会读取到换行符
// 解决方案:
scanf("%d", &a);
while(getchar() != '\n'); // 清空缓冲区
scanf("%c", &c);
7. 最佳实践总结
经过多年项目实践,我总结出以下黄金法则:
- 所有scanf调用都必须检查返回值
- 对用户输入永远做长度限制
- 文件操作必须检查fopen返回值
- 重要输出添加fflush确保及时显示
- 在性能敏感场景避免频繁调用printf
一个健壮的输入处理模板:
c复制#define CLEAR_BUFFER while(getchar() != '\n')
int get_int_input(const char* prompt, int min, int max) {
int value;
while(1) {
printf("%s [%d-%d]: ", prompt, min, max);
int ret = scanf("%d", &value);
CLEAR_BUFFER;
if(ret == 1 && value >= min && value <= max) {
return value;
}
printf("输入无效!\n");
}
}
最后分享一个实用技巧:在调试复杂程序时,可以使用fprintf将日志同时输出到屏幕和文件:
c复制void debug_log(const char* format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args); // 输出到控制台
FILE* log = fopen("debug.log", "a");
if(log) {
vfprintf(log, format, args);
fclose(log);
}
va_end(args);
}
