C 标准库 – <stdio.h>(建议收藏)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

前言

在 C 语言编程的世界中,<stdio.h> 是一座连接开发者与计算机输入输出系统的桥梁。作为 C 标准库的核心组成部分,它提供了丰富的函数和接口,帮助开发者实现数据的输入、输出以及文件操作等基础功能。无论是初学者编写第一个“Hello World”程序,还是中级开发者构建复杂的应用系统,<stdio.h> 都是不可或缺的工具库。本文将从基础到进阶,系统解析 <stdio.h> 的核心知识点,并通过实际案例和类比,帮助读者建立直观的理解。


输入输出基础:printf 和 scanf 的核心用法

1.1 格式化输出:printf 函数

printf 是 C 语言中最为经典的输出函数,其名称来源于“print formatted”,即“格式化打印”。它允许开发者通过特定的格式说明符(format specifier)控制输出内容的类型和格式。

核心语法

int printf(const char *format, ...);  
  • format 是一个包含普通字符和格式说明符的字符串。
  • 格式说明符以 % 开头,例如 %d 表示十进制整数,%f 表示浮点数。

示例 1:基础输出

#include <stdio.h>  

int main() {  
    int num = 42;  
    float pi = 3.1415926;  
    printf("整数:%d,浮点数:%.2f", num, pi);  // 输出:整数:42,浮点数:3.14  
    return 0;  
}  

类比解析
可以将 printf 的格式字符串想象为一个“模板”,其中的格式说明符类似模板中的“占位符”。例如 %d 就像一张只接受整数的卡片,当程序运行时,它会根据参数列表中的值动态填充内容。

1.2 格式化输入:scanf 函数

printf 相对应,scanf 是用于从标准输入(如键盘)读取数据的函数。其名称来源于“scan formatted”,即“格式化扫描”。

核心语法

int scanf(const char *format, ...);  
  • format 中的格式说明符用于指定输入数据的类型,并将读取的值存储到对应地址。

示例 2:基础输入

#include <stdio.h>  

int main() {  
    int age;  
    printf("请输入年龄:");  
    scanf("%d", &age);  // 注意:必须传递变量的地址  
    printf("您输入的年龄是:%d 岁", age);  
    return 0;  
}  

常见误区

  • 必须使用 & 符号传递变量的地址,否则会导致内存错误。
  • 输入时若格式与实际数据类型不匹配(例如用 %d 读取浮点数),可能导致不可预测的结果。

文件操作:打开、读取与写入

<stdio.h> 不仅支持标准输入输出(如屏幕和键盘),还能通过文件流(file stream)操作磁盘文件。

2.1 文件打开与关闭:fopen 和 fclose

核心函数

FILE *fopen(const char *filename, const char *mode);  
int fclose(FILE *stream);  
  • fopen 返回一个指向 FILE 结构的指针,该结构管理文件的所有状态(如当前位置、缓冲区等)。
  • mode 参数指定文件打开方式,例如:
    • "r":只读模式(文件必须存在)。
    • "w":只写模式(若文件存在则清空内容,否则创建新文件)。
    • "a":追加模式(在文件末尾添加内容)。

示例 3:写入文件

#include <stdio.h>  

int main() {  
    FILE *file = fopen("data.txt", "w");  
    if (file == NULL) {  
        printf("文件打开失败!\n");  
        return 1;  
    }  
    fprintf(file, "Hello File Stream!\n");  // 使用 fprintf 写入文件  
    fclose(file);  
    return 0;  
}  

2.2 文件读取:fscanf 和 fgets

  • fscanf:类似 scanf,但作用于指定的文件流。
  • fgets:按行读取字符串,安全性更高(可指定缓冲区大小)。

示例 4:读取文件内容

#include <stdio.h>  
#include <string.h>  

int main() {  
    FILE *file = fopen("data.txt", "r");  
    if (file == NULL) {  
        printf("文件不存在!\n");  
        return 1;  
    }  
    char buffer[100];  
    while (fgets(buffer, sizeof(buffer), file)) {  
        printf("%s", buffer);  // 输出文件内容  
    }  
    fclose(file);  
    return 0;  
}  

高级功能:缓冲机制与流控制

3.1 缓冲机制:提高 I/O 效率

文件操作中,缓冲机制是性能优化的关键。<stdio.h> 的流默认采用全缓冲行缓冲

  • 全缓冲:当流关联到磁盘文件时,数据会在缓冲区累积到一定大小后才写入磁盘。
  • 行缓冲:当流关联到终端(如标准输入输出)时,遇到换行符 \n 或缓冲区满时触发写入。

手动控制缓冲:setvbuf 函数

int setvbuf(FILE *stream, char *buf, int mode, size_t size);  

例如,关闭缓冲:

setvbuf(file, NULL, _IONBF, 0);  // _IONBF 表示无缓冲  

3.2 标准流与重定向

C 标准库预定义了三个标准流:
| 流名称 | 作用域 | 默认方向 |
|--------|----------------------|----------------|
| stdin | 标准输入 | 读取 |
| stdout | 标准输出 | 写入 |
| stderr | 标准错误输出 | 写入(非缓冲) |

案例:错误信息输出

fprintf(stderr, "致命错误:内存不足!\n");  // 即使 stdout 被重定向,stderr 仍会显示  

常见问题与最佳实践

4.1 输入函数的陷阱

  • 输入缓冲区残留问题:若前一个输入操作(如 scanf)未读取换行符 \n,后续输入可能提前终止。
    解决方法:使用 fgets 结合 sscanf,或在格式字符串前加空格(例如 %d%d)。

4.2 文件操作的注意事项

  • 检查文件操作的成功性:每次调用 fopenfprintf 等函数后,务必验证返回值。
  • 及时关闭文件:避免文件句柄泄漏。

4.3 格式化字符串漏洞防范

若格式字符串来自不可信输入(如用户输入),可能引发安全漏洞。例如:

char user_input[20];  
scanf("%19s", user_input);  
printf(user_input);  // 危险!用户可输入 %x%x%x… 导致内存泄露  

解决方案:使用 snprintf 或严格限制输入格式。


结论

C 标准库 – <stdio.h> 是 C 语言开发者必须掌握的核心工具库。从基础的 printfscanf 到复杂的文件流操作,它提供了构建应用程序所需的输入输出能力。通过理解缓冲机制、流类型和安全编码规范,开发者能够编写高效、可靠的代码。建议读者通过实际项目(如日志系统、数据处理工具)巩固知识,逐步探索更高级的用法。掌握 <stdio.h>,不仅是对 C 语言的入门,更是通向系统编程与底层开发的重要一步。


(全文约 1800 字)

最新发布