Menu

C 标准库:常用头文件一览

每个标准头文件提供了什么:stdio.h、stdlib.h、string.h、math.h、time.h、ctype.h、limits.h、stdbool.h、stdint.h 和 assert.h,每个都附有一个简短的可运行示例。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

C 语言本身非常小:类型、运算符、控制流、函数、指针。它不能打印,不能读文件,不能比较字符串,也不能分配内存。这些全都由标准库提供:一组每个符合标准的实现都会附带的头文件。

本页是一张地图。每一节说明一个头文件的用途,给出一个简短的可运行示例,并在有详细页面时附上链接。

stdio.h:输入和输出

几乎每个程序第一个包含的头文件。它包含 printf 和 scanf、FILE 类型和文件函数,以及更安全的按行输入函数。

核心函数:输出用 printf、fprintf、sprintf、snprintf;输入用 scanf、fscanf、sscanf、fgets、getchar;文件操作用 fopen、fclose、fread、fwrite、fseek。更深入的内容见 printf、scanf 和文件操作。

有一条规则值得牢记:永远不要用 gets()。它无法知道缓冲区的大小,已在 C11 中从标准里移除。替代写法是 fgets(buf, sizeof buf, stdin)。

stdlib.h:通用工具

什么都有:内存、转换、随机数、进程控制、排序。

重点:malloc、calloc、realloc、free(见动态内存);用于转换的 atoi、atof、strtol、strtod,优先用 strto* 系列,它能报告失败,而 atoi 失败时只会悄悄返回 0;rand 和 srand(见随机数);exit、abort、getenv、system;以及 qsort/bsearch。

string.h:字符串和内存块

C 字符串是以 '\0' 结尾的 char 数组,对它们的每个操作都是一次函数调用。

有两个习惯能避免这个头文件变成缓冲区溢出的源头:始终清楚目标缓冲区的大小;记住 strcmp 在相等时返回 0,也就是说 if (strcmp(a, b)) 在两个字符串不同时才为真。字符串函数详细介绍了更安全的 strncpy/snprintf 写法。

math.h:数学

sqrt、pow、fabs、floor、ceil、round、fmod,三角函数和对数函数系列,以及 INFINITY 和 NAN。

这是唯一可能需要链接选项的头文件:在 Linux 上,要在编译命令末尾加 -lm,否则链接器会报“undefined reference to sqrt”。完整介绍见数学函数。

time.h:时钟和日期

struct tm 中的两个偏移量谁都会踩坑:tm_year 从 1900 开始计数,tm_mon 从 0 开始。time(NULL) 也是 srand 最常用的种子。

ctype.h:字符分类

很小,但比看上去有用。每个函数接收一个字符,回答一个是或否的问题,或者转换大小写。

完整列表:isalpha、isdigit、isalnum、isspace、isupper、islower、ispunct、isxdigit、isprint,还有 toupper 和 tolower。

注意那个 (unsigned char) 转换。这些函数只对能用 unsigned char 表示的值和 EOF 有定义;在大多数系统上普通 char 是有符号的,所以大于 127 的字节会变成负数,调用就成了未定义行为。这个转换没有任何开销,却能消除这一整类问题。

另外,优先用这些函数,而不是自己写判断。c >= '0' && c <= '9' 碰巧能用,但 isdigit(c) 表达的意图更清楚,而且在哪里都正确。

limits.h 和 float.h:类型的边界

在这台机器上 int 最大能有多大?这两个头文件给出答案,而正因为答案因机器而异,可移植的代码才要去查询而不是假设。

INT_MAX 让你能在溢出发生之前就检查它。这一点很重要,因为有符号整数溢出是未定义行为,而不是能事后检测的回绕。

stdbool.h 和 stdint.h:更好的类型

C99 之前,C 没有布尔类型。<stdbool.h> 提供了一个:

bool、true 和 false 是 _Bool、1 和 0 的宏,但它们让意图一目了然。(在 C23 中它们成了关键字,不再需要这个头文件。)

<stdint.h> 提供宽度有保证的类型。在文件格式、网络协议和嵌入式开发中,这是必不可少的,因为“int 大概是 32 位”远远不够:

<inttypes.h> 中 PRId32 这类宏会展开成当前平台上对应定宽类型的正确 printf 格式说明符。这是打印它们的可移植写法,因为 %d 对 int32_t 在某些系统上是对的,在另一些系统上就是错的。

assert.h:检查你的假设

assert(expr) 在表达式为真时什么也不做,为假时打印一条消息并终止程序。

用 -DNDEBUG 编译会去掉所有断言,所以在发布版本中它们没有任何开销。这也是要警惕的地方:永远不要在 assert 里放有副作用的代码。assert(read_next() > 0) 在发布版本中会完全不再读取,由此产生的 bug 只会在生产环境中出现。

断言是用来抓你自己的错误的:如果代码正确,这些条件就不可能发生。用户输入错误不属于断言的范畴,应该用 if 加一条错误消息来处理。

其余头文件,简要介绍

  • <stddef.h>:size_t、ptrdiff_t、NULL、offsetof
  • <stdarg.h>:可变参数函数,printf 背后的机制
  • <errno.h>:errno,以及用于报告调用失败原因的 perror 和 strerror
  • <signal.h>:处理 Ctrl-C 等信号
  • <setjmp.h>:非局部跳转,很少是正确的选择
  • <locale.h>、<wchar.h>、<wctype.h>:区域设置和宽字符
  • <stdnoreturn.h>、<stdalign.h>、<threads.h>、<stdatomic.h>:C11 新增的头文件

两个习惯能让标准库用起来更顺手。先读手册页,不要去猜函数签名:在 macOS 和 Linux 上,man 3 strtol 既权威又快捷。另外要检查返回值:malloc、fopen 和 strtol 都会报告失败,忽略它们的程序会在很久以后才出错,而且错得莫名其妙。

常见问题

C 标准库是什么?

每个符合标准的 C 实现都会提供的一组函数、类型和宏,分布在大约 30 个头文件中。它涵盖输入输出(<stdio.h>)、内存与转换(<stdlib.h>)、字符串(<string.h>)、数学(<math.h>)、时间、字符分类,以及数值类型的取值范围。C 语言本身没有内置的输入输出和字符串处理,这些全部来自标准库。

stdio.h 和 stdlib.h 有什么区别?

<stdio.h> 负责输入输出:printf、scanf、fopen、fgets、FILE。<stdlib.h> 是通用工具:malloc 和 free、atoi 和 strtol、rand 和 srand、exit、qsort、bsearch。一般的程序两个都会包含。

ctype.h 是做什么的?

它用于判断和转换单个字符:isdigit、isalpha、isalnum、isspace、isupper、islower、ispunct,还有 toupper 和 tolower。用它们代替 c >= '0' && c <= '9' 这样手写的比较,代码更清晰,并且在任何区域设置和字符集下都正确。

使用 C 标准库需要链接什么吗?

不需要,只有一个例外:数学函数。标准 C 库会自动链接,但在 Linux 上 <math.h> 的实现位于一个单独的库中,所以 sqrt 这类函数需要在编译命令末尾加上 -lm。其他的只需要正确的 #include。

Coddy 编程语言插图

和 Coddy 一起学编程

开始