1. C语言输入输出函数概述
在C语言编程中,输入输出(I/O)函数是与外界交互的基础桥梁。作为一门系统级编程语言,C语言通过标准库<stdio.h>提供了一系列强大的I/O函数,这些函数构成了程序与用户、文件系统乃至硬件设备通信的通道。
初学者常犯的错误是认为printf和scanf就是C语言I/O的全部。实际上,标准I/O库提供了丰富的函数家族,包括:
- 格式化I/O:printf、scanf、fprintf、fscanf等
- 字符I/O:getchar、putchar、fgetc、fputc等
- 字符串I/O:gets、puts、fgets、fputs等
- 文件I/O:fopen、fclose、fread、fwrite等
特别注意:现代C编程中应避免使用不安全的gets()函数,因其无法限制输入长度,容易导致缓冲区溢出漏洞。推荐使用fgets()替代。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 格式化输出函数printf详解
2.1 基本用法与格式控制
printf函数是C语言中最常用的输出函数,其原型为:
c复制int printf(const char *format, ...);
格式字符串(format)可以包含:
- 普通字符:原样输出
- 转义序列:如\n(换行)、\t(制表符)
- 格式说明符:以%开头,如%d(整数)、%f(浮点数)
常见格式说明符:
| 说明符 | 类型 | 示例输出 |
|---|---|---|
| %d | 十进制整数 | 123 |
| %f | 浮点数 | 3.141593 |
| %c | 字符 | A |
| %s | 字符串 | Hello |
| %p | 指针地址 | 0x7ffd4f8e3a5c |
| %% | 百分号字符 | % |
2.2 高级格式控制
printf支持精细的格式控制,这是许多初学者容易忽略的强大功能:
c复制// 控制小数位数
printf("%.2f", 3.14159); // 输出3.14
// 控制输出宽度(右对齐)
printf("%10s", "Hello"); // 输出" Hello"
// 控制输出宽度(左对齐)
printf("%-10s", "Hello"); // 输出"Hello "
// 填充前导0
printf("%05d", 42); // 输出00042
调试技巧:当遇到中文乱码问题时,检查源文件编码格式(建议UTF-8)和终端编码设置是否一致。在Windows平台,可能需要使用_setmode(_fileno(stdout), _O_U16TEXT)来支持Unicode输出。
3. 格式化输入函数scanf详解
3.1 基本用法与常见陷阱
scanf函数用于从标准输入读取格式化数据,其原型为:
c复制int scanf(const char *format, ...);
常见问题1:忘记取地址符&
c复制int num;
scanf("%d", num); // 错误!缺少&
scanf("%d", &num); // 正确
常见问题2:字符串输入不需要&
c复制char name[20];
scanf("%s", name); // 正确,数组名本身就是地址
安全提示:使用%s时务必指定最大长度防止溢出,如"%19s"对应char[20]
3.2 高级输入控制
scanf的格式字符串可以包含:
- 空白字符:匹配任意空白(空格、制表符、换行)
- 非空白字符:必须精确匹配输入中的字符
- 扫描集:[...]匹配特定字符集合
c复制// 读取特定格式的日期
int year, month, day;
scanf("%d-%d-%d", &year, &month, &day);
// 使用扫描集读取只包含字母的字符串
char alpha[20];
scanf("%19[a-zA-Z]", alpha);
4. 文件I/O操作
4.1 文件打开与关闭
文件操作的基本流程:
c复制FILE *fp = fopen("data.txt", "r"); // 打开文件
if(fp == NULL) {
perror("打开文件失败");
return 1;
}
// 文件操作...
fclose(fp); // 关闭文件
常见文件打开模式:
| 模式 | 描述 |
|---|---|
| "r" | 只读(文件必须存在) |
| "w" | 只写(创建/截断文件) |
| "a" | 追加(创建/追加到文件尾) |
| "r+" | 读写(文件必须存在) |
| "w+" | 读写(创建/截断文件) |
| "a+" | 读写(创建/追加) |
4.2 文件读写函数
除了fprintf和fscanf,还有更底层的文件I/O函数:
c复制// 二进制I/O(适合结构体读写)
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
// 字符I/O
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
// 字符串I/O
char *fgets(char *str, int n, FILE *stream);
int fputs(const char *str, FILE *stream);
5. 常见问题与调试技巧
5.1 printf输出异常问题排查
- 浮点数精度问题:
c复制float f = 0.1;
printf("%f", f); // 输出0.100000
printf("%.20f", f); // 显示精度问题
- 缓冲区刷新问题:
c复制printf("Debug info"); // 可能不会立即显示
printf("Debug info\n"); // 换行会刷新缓冲区
fflush(stdout); // 手动刷新缓冲区
5.2 scanf输入问题排查
- 输入残留问题:
c复制int num;
char ch;
scanf("%d", &num); // 输入42<回车>
scanf("%c", &ch); // ch会读取到回车符
解决方案:
c复制scanf("%d", &num);
while(getchar() != '\n'); // 清空输入缓冲区
scanf("%c", &ch);
- 输入验证:
c复制int items_read;
do {
printf("请输入1-100的数字: ");
items_read = scanf("%d", &num);
while(getchar() != '\n'); // 清除非数字输入
} while(items_read != 1 || num < 1 || num > 100);
6. 性能优化与最佳实践
6.1 减少I/O调用次数
频繁的I/O操作会显著降低程序性能。优化方法:
c复制// 低效方式
for(int i=0; i<100; i++) {
printf("%d ", i);
}
// 高效方式
char buffer[1024];
int pos = 0;
for(int i=0; i<100; i++) {
pos += sprintf(buffer+pos, "%d ", i);
if(pos > 1000) {
fwrite(buffer, 1, pos, stdout);
pos = 0;
}
}
if(pos > 0) {
fwrite(buffer, 1, pos, stdout);
}
6.2 错误处理最佳实践
健壮的I/O代码应始终检查返回值:
c复制FILE *fp = fopen("data.bin", "rb");
if(fp == NULL) {
perror("fopen失败");
exit(EXIT_FAILURE);
}
int num;
if(fread(&num, sizeof(int), 1, fp) != 1) {
if(feof(fp)) {
fprintf(stderr, "意外到达文件末尾\n");
} else if(ferror(fp)) {
perror("读取错误");
}
fclose(fp);
exit(EXIT_FAILURE);
}
7. 特殊应用场景
7.1 重定向标准I/O
在嵌入式系统中,经常需要重定向printf输出:
c复制// 重定向到串口(示例代码)
int _write(int fd, char *ptr, int len) {
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, HAL_MAX_DELAY);
return len;
}
7.2 内存流操作
使用内存作为I/O目标:
c复制char buffer[100];
FILE *mem = fmemopen(buffer, sizeof(buffer), "w");
fprintf(mem, "当前温度: %.1f℃", 25.5);
fclose(mem);
printf("内存内容: %s", buffer);
8. 现代替代方案
虽然标准I/O函数功能强大,但在某些场景下可以考虑:
- 使用更安全的函数:
c复制// 替代gets
char buf[100];
fgets(buf, sizeof(buf), stdin);
// 替代sprintf
snprintf(buf, sizeof(buf), "格式字符串", ...);
- 第三方库:
- GLib的GString提供更安全的字符串操作
- Apache的apr_file_t提供跨平台文件I/O
在实际项目中,我通常会封装一层I/O函数,便于统一错误处理和后期维护。例如:
c复制int safe_printf(const char *format, ...) {
va_list args;
va_start(args, format);
int ret = vprintf(format, args);
va_end(args);
if(ret < 0) {
log_error("printf失败: %d", ret);
}
return ret;
}
