←
C 语言标准库头文件速查手册
📖 目录
第一章 基础输入输出(初学第一站)
1.1 <stdio.h> —— 标准输入输出(初学者第一站!)
1.2 文件操作(fopen / fread / fwrite / fseek)
第二章 基础数据类型与定宽整数
2.1 类型大小与范围(<limits.h> + <float.h>)
2.2 定宽整数(<stdint.h> / <stddef.h>)
2.3 布尔类型 <stdbool.h>(C99)
2.4 断言调试 <assert.h>
第三章 内存管理、错误码与可变参数
3.1 动态内存(malloc / calloc / realloc / free)
3.2 错误码 <errno.h> + perror
3.3 可变参数 <stdarg.h>
第四章 字符串与字符处理
4.3 <ctype.h> —— 字符判断与转换
4.4 <string.h> —— C 风格字符串操作
第五章 数学与数值
5.1 <math.h> —— 数学函数(C89 起)
5.2 <stdlib.h> —— 随机数与通用工具
第六章 时间与日期
6.2 <time.h> —— C 风格时间
第十五章 C兼容头文件速查
15.1 <stdio.h> —— C 风格输入输出
15.2 <errno.h> —— 错误码
15.3 <float.h> —— 浮点数限制
15.4 <limits.h> —— 整数类型限制
15.5 <locale.h> —— 本地化
15.6 <setjmp.h> —— 非局部跳转
15.7 <signal.h> —— 信号处理
15.8 <stdarg.h> —— 可变参数
15.9 <stddef.h> —— 常用类型定义
15.10 <fenv.h> —— 浮点环境控制(fenv.h,C99)
15.11 <inttypes.h> —— 定宽整数格式化(inttypes.h,C99)
15.12 <stdint.h> —— 固定宽度整数(stdint.h,C99)
15.13 <wchar.h> —— 宽字符工具
15.14 <wctype.h> —— 宽字符判断
15.15 <uchar.h> —— Unicode 字符转换(uchar.h,C11)
15.16 <iso646.h> —— 运算符别名宏
15.17 <complex.h> —— 复数运算(complex.h,C99)
15.18 <stdbool.h> —— 布尔类型(stdbool.h,C99)
15.19 <tgmath.h> —— 泛型数学宏(tgmath.h,C99)
15.20 <stdalign.h> —— 对齐控制宏(stdalign.h,C11)
15.21 <stdnoreturn.h> —— 不返回函数标记(stdnoreturn.h,C11)
15.22 <stdatomic.h> —— 原子操作(C11)
15.23 <threads.h> —— 多线程(C11)
第十六章 其他重要头文件
16.1 <assert.h> —— 断言

C 语言标准库头文件速查手册

增强学习版 · 纯 C 语言 · 适合 iPhone 阅读

5 章·29 个小节·199 个代码示例
第一章 基础输入输出(初学第一站)

▼1.1 <stdio.h> —— 标准输入输出(初学者第一站!)C 语言

是什么

📚 <stdio.h> 是 C 语言的“嘴巴和耳朵”——负责把数据输出到屏幕、从键盘读取数据、以及读写文件。几乎每个 C 程序都离不开它。

💡 生活比喻:把程序想象成一个人。printf 是“说话”,scanf 是“听”,getchar/putchar 是“一个字一个字地读和写”,fgets 则是“安全地读一整行”。

⚠️ 注意:本节只讲最常用的控制台输入输出函数;文件操作请见 1.2 文件操作,完整清单请见 15.1 <stdio.h>。

printf(fmt, ...) —— 格式化输出到屏幕

🎯 功能:接受一个格式字符串 fmt 和若干参数,按照格式说明符把它们转换成文本并打印到标准输出。

💡 比喻:就像填空题模板:"姓名: %s, 年龄: %d" 是模板,后面的变量就是答案,printf 帮你把答案填进去并念出来。

🔧 声明:int printf(const char *format, ...);

↩️ 返回值:成功输出的字符数(不含结尾 \0);出错返回负值。

C
#include <stdio.h>
int main() {
    int age = 20;
    double score = 95.5;
    char name[] = "Tom";
    printf("姓名: %s\n", name);
    printf("年龄: %d\n", age);
    printf("分数: %.1f\n", score);
    printf("Hex: 0x%X\n", 255);
    return 0;
}

scanf(fmt, &...) —— 从键盘读数据

🎯 功能:接受格式字符串和变量地址(注意加 &),按格式从标准输入读取数据并写入对应变量。

💡 比喻:就像快递分拣员:根据订单格式(%d、%lf、%s)把键盘输入的“包裹”分发到不同变量里。忘记写 & 就像把包裹扔到空气里,程序会崩溃。

🔧 声明:int scanf(const char *format, ...);

↩️ 返回值:成功读取并赋值的项数;遇到输入结束或匹配失败可能提前返回。

C
#include <stdio.h>
int main() {
    int a, b;
    printf("请输入两个整数(空格隔开): ");
    scanf("%d %d", &a, &b);
    printf("%d + %d = %d\n", a, b, a + b);
    return 0;
}

getchar() / putchar(c) —— 单字符读写

🎯 功能:getchar() 从标准输入读取一个字符;putchar(c) 把字符 c 写到标准输出。

💡 比喻:就像打字机:按一下键 getchar() 读一个字,敲一下字模 putchar() 打出一个字。

🔧 声明:int getchar(void); int putchar(int c);

↩️ 返回值:getchar 返回读到的字符(用 int 表示,以便容纳 EOF);putchar 返回写入的字符,出错返回 EOF。

C
#include <stdio.h>
int main() {
    int c;
    printf("输入字符,按 Ctrl+Z 结束:\n");
    while ((c = getchar()) != EOF) {
        putchar(c);  // 原样回显
    }
    return 0;
}

puts(s) / fputs(s, stream) —— 输出字符串

🎯 功能:puts(s) 把字符串 s 写到屏幕并自动换行;fputs(s, stream) 写到指定流,不自动换行。

💡 比喻:puts 像“自动按回车的喊话器”;fputs 像“写黑板”,写完不自动换行,方便你继续在同一行追加内容。

🔧 声明:int puts(const char *s); int fputs(const char *s, FILE *stream);

↩️ 返回值:成功返回非负值;出错返回 EOF。

C
#include <stdio.h>
int main() {
    puts("Hello World");          // 自动换行
    fputs("Hello ", stdout);      // 不换行
    fputs("World\n", stdout);    // 手动换行
    return 0;
}

fgets(s, n, stream) —— 安全读取一行

🎯 功能:从 stream 最多读取 n-1 个字符到缓冲区 s,遇到换行或文件结束就停止,并自动加字符串结束符 \0。

💡 比喻:就像用有刻度尺的杯子接水:最多接 n-1 毫升,满了就停,绝不会溢出来,比危险的 gets 安全得多。

🔧 声明:char *fgets(char *s, int n, FILE *stream);

↩️ 返回值:成功返回 s;到达文件尾或出错返回 NULL。

C
#include <stdio.h>
int main() {
    char line[256];
    printf("请输入一行:");
    fgets(line, sizeof(line), stdin);  // 安全读取一行
    printf("你输入的是:%s", line);
    return 0;
}

sprintf / snprintf —— 格式化到字符串

🎯 功能:把格式化的结果写进字符数组而不是屏幕。sprintf 不检查长度,snprintf 带长度限制,是更安全的选择。

💡 比喻:就像把要说的话先写在纸条上,而不是直接喊出来。snprintf 的纸条大小固定,写不下会自动截断,不会把墨水洒到外面。

🔧 声明:int snprintf(char *s, size_t n, const char *format, ...);

↩️ 返回值:返回“如果空间足够”会写入的字符数(不含 \0),截断时返回值 ≥ n。

C
#include <stdio.h>
int main() {
    char buf[64];
    int n = 20;
    snprintf(buf, sizeof(buf), "n 的平方是 %d", n * n);
    printf("%s\n", buf);  // n 的平方是 400
    return 0;
}

sscanf(s, fmt, ...) —— 从字符串解析数据

🎯 功能:和 scanf 类似,但不是从键盘读,而是从字符串 s 里按格式提取数据。

💡 比喻:就像从一张已经写好的纸条上提取信息。比如纸条写着 "2024 90.5",sscanf 能把它拆成年份和分数。

🔧 声明:int sscanf(const char *s, const char *format, ...);

↩️ 返回值:成功解析并赋值的项数。

C
#include <stdio.h>
int main() {
    const char *info = "2024 90.5";
    int year;
    double score;
    sscanf(info, "%d %lf", &year, &score);
    printf("年份=%d, 分数=%.1f\n", year, score);
    return 0;
}
⚠️ scanf 常见坑: 1. 读取普通变量必须加 &,否则是未定义行为。 2. %s 遇空格停止,且要给出缓冲区大小,如 %63s,防止溢出。 3. 读完整行应优先用 fgets,而不是 gets(已废弃)。
💡 printf 常用格式符:%d 整数、%u 无符号、%f 浮点、%c 字符、%s 字符串、%p 指针、%x 十六进制、%% 百分号本身。

👉完整 <stdio.h> 函数清单(fopen/fread/fwrite/sprintf/snprintf 等 40+)→

▼1.2 文件操作(fopen / fread / fwrite / fseek)C 语言

是什么

📚 文件操作 让程序把数据保存到硬盘、从硬盘读取数据,或者处理二进制文件。核心流程是:fopen 打开 → 读写 → fclose 关闭,缺一不可。

💡 生活比喻:文件就像抽屉里的文件夹。fopen 是“拉开抽屉”,fread/fwrite 是“拿东西/放东西”,fclose 是“推回抽屉并上锁”。

⚠️ 注意:忘记 fclose 会导致数据丢失、资源泄漏,文件内容也可能没真正写入磁盘。

fopen(path, mode) / fclose(fp) —— 打开与关闭文件

🎯 功能:fopen 按指定模式打开文件,返回一个 FILE* 指针;fclose 关闭文件,释放资源。

💡 比喻:fopen 就像拿钥匙开门:如果门打不开(文件不存在或无权限),返回 NULL;fclose 就像出门时锁门。

🔧 声明:FILE *fopen(const char *path, const char *mode); int fclose(FILE *stream);

↩️ 返回值:fopen 成功返回文件指针,失败返回 NULL;fclose 成功返回 0,失败返回 EOF。

C
#include <stdio.h>
int main() {
    FILE *fp = fopen("test.txt", "w");
    if (!fp) {
        printf("打开失败\n");
        return 1;
    }
    fprintf(fp, "你好 %d %s\n", 2024, "文件");
    fclose(fp);  // 用完一定要关闭
    return 0;
}

fprintf(fp, fmt, ...) / fscanf(fp, fmt, ...) —— 文本文件格式化读写

🎯 功能:和 printf/scanf 类似,只是目标换成了文件指针 fp。

💡 比喻:就像给远方的朋友写信:fprintf 把内容写进信纸(文件),fscanf 从信纸里读出信息。

🔧 声明:int fprintf(FILE *stream, const char *format, ...); int fscanf(FILE *stream, const char *format, ...);

↩️ 返回值:fprintf 返回输出字符数;fscanf 返回成功读取的项数。

C
#include <stdio.h>
int main() {
    FILE *fp = fopen("data.txt", "w");
    fprintf(fp, "姓名:%s 年龄:%d\n", "Tom", 20);
    fclose(fp);

    char name[20];
    int age;
    fp = fopen("data.txt", "r");
    fscanf(fp, "姓名:%s 年龄:%d", name, &age);
    printf("读到:%s, %d\n", name, age);
    fclose(fp);
    return 0;
}

fgets(s, n, fp) / fputs(s, fp) —— 按行读写文本文件

🎯 功能:fgets 从文件读取最多 n-1 个字符的一行;fputs 把字符串写入文件,不自动换行。

💡 比喻:就像按行抄写文章:fgets 每次抄一行,fputs 每次写一句。

🔧 声明:char *fgets(char *s, int n, FILE *stream); int fputs(const char *s, FILE *stream);

↩️ 返回值:fgets 成功返回 s,到文件尾或出错返回 NULL;fputs 成功返回非负值。

C
#include <stdio.h>
int main() {
    FILE *fp = fopen("lines.txt", "w");
    fputs("第一行\n", fp);
    fputs("第二行\n", fp);
    fclose(fp);

    char buf[64];
    fp = fopen("lines.txt", "r");
    while (fgets(buf, sizeof(buf), fp) != NULL) {
        printf("%s", buf);
    }
    fclose(fp);
    return 0;
}

fread(ptr, size, nmemb, fp) / fwrite(ptr, size, nmemb, fp) —— 二进制读写

🎯 功能:fread 从文件读取 nmemb 个、每个 size 字节的数据块;fwrite 写入同样结构的数据块。常用于结构体或数组。

💡 比喻:就像用固定大小的盒子搬货:每个盒子装 size 字节,一次搬 nmemb 盒。二进制读写不关心内容是不是文本。

🔧 声明: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);

↩️ 返回值:返回实际成功读写的数据块个数,可能比 nmemb 小。

C
#include <stdio.h>
int main() {
    int arr[3] = {10, 20, 30};
    FILE *fp = fopen("data.bin", "wb");
    fwrite(arr, sizeof(int), 3, fp);
    fclose(fp);

    int buf[3] = {0};
    fp = fopen("data.bin", "rb");
    fread(buf, sizeof(int), 3, fp);
    printf("%d %d %d\n", buf[0], buf[1], buf[2]);
    fclose(fp);
    return 0;
}

fseek(fp, offset, whence) / ftell(fp) / rewind(fp) —— 文件位置控制

🎯 功能:fseek 把文件读写指针移动到指定位置;ftell 返回当前位置;rewind 回到文件开头。

💡 比喻:就像录音带的播放头:fseek 是快进/快退,ftell 是看当前播放到第几秒,rewind 是倒回开头。

🔧 声明:int fseek(FILE *stream, long offset, int whence); long ftell(FILE *stream); void rewind(FILE *stream);

↩️ 返回值:fseek 成功返回 0,失败返回非 0;ftell 返回当前偏移量;rewind 无返回值。

C
#include <stdio.h>
int main() {
    FILE *fp = fopen("abc.txt", "w+");
    fputs("ABCDEF", fp);      // 文件内容:ABCDEF
    fseek(fp, 3, SEEK_SET);   // 移到 D
    char c = fgetc(fp);       // 读到 D
    printf("位置 %ld 的字符:%c\n", ftell(fp), c);
    rewind(fp);               // 回到 A
    printf("回到开头,位置:%ld\n", ftell(fp));
    fclose(fp);
    return 0;
}

feof(fp) / ferror(fp) —— 检查文件状态

🎯 功能:feof 判断是否已经读到文件末尾;ferror 判断文件流是否发生了错误。

💡 比喻:就像看书时检查“是不是最后一页”和“书有没有被水打湿”。读完文件后,用它们区分是正常结束还是出错。

🔧 声明:int feof(FILE *stream); int ferror(FILE *stream);

↩️ 返回值:状态为真时返回非 0,否则返回 0。

C
#include <stdio.h>
int main() {
    FILE *fp = fopen("test.txt", "r");
    if (!fp) { perror("打开失败"); return 1; }
    int c;
    while ((c = fgetc(fp)) != EOF) {
        putchar(c);
    }
    if (feof(fp)) {
        printf("\n[已到文件尾]\n");
    } else if (ferror(fp)) {
        printf("\n[读取出错]\n");
    }
    fclose(fp);
    return 0;
}
⚠️ 文件操作 checklist: 1. 打开文件后必须检查 fp != NULL。 2. 文本模式("r" / "w" / "a")适合读写人类可读内容;二进制模式("rb" / "wb" / "ab")适合保存结构体/数组。 3. 用完后一定要 fclose。 4. 不要用 feof 控制循环条件,应检查读取函数是否返回 EOF/NULL。
💡 常用打开模式:"r" 只读、"w" 只写(覆盖)、"a" 追加、"r+" 读写(不创建)、"w+" 读写(覆盖)、"a+" 读追加。

👉完整 <stdio.h> 文件函数清单 →

▼2.1 类型大小与范围(<limits.h> + <float.h>)C 语言

是什么

📚 这些宏像不同容量的杯子标签,告诉你 int、float、double 等基本类型在当前平台能装多少,避免硬编码边界值。

💡 生活比喻:<limits.h> 和 <float.h> 就像是超市里各种杯子的容量说明——有的只能装 250ml,有的能装 1L,看清标签才不会把水溢出来。

⚠️ 注意事项:这些值会随编译器和平台变化;sizeof 返回的是当前平台的实际字节数,不要与宏值混为一谈。

INT_MAX / INT_MIN / LONG_MAX / LONG_MIN / CHAR_MAX / CHAR_MIN / SHRT_MAX / SHRT_MIN —— 整数极值宏

🎯 功能:提供 char、short、int、long 等整数类型的最小值与最大值,防止越界或写死常量。

💡 比喻:就像每种杯子都有明确的满水位线,超过就会溢出。

🔧 声明:INT_MAX / INT_MIN / LONG_MAX / LONG_MIN / CHAR_MAX / CHAR_MIN / SHRT_MAX / SHRT_MIN

↩️ 返回值:对应类型的极限整数值。

C
#include <stdio.h>
#include <limits.h>

int main() {
    printf("INT_MAX  = %d,  INT_MIN  = %d\n", INT_MAX, INT_MIN);
    printf("LONG_MAX = %ld, LONG_MIN = %ld\n", LONG_MAX, LONG_MIN);
    printf("CHAR_MAX = %d,  CHAR_MIN = %d\n", CHAR_MAX, CHAR_MIN);
    printf("SHRT_MAX = %d,  SHRT_MIN = %d\n", SHRT_MAX, SHRT_MIN);
    return 0;
}

FLT_MAX / FLT_MIN / DBL_MAX / DBL_MIN / FLT_EPSILON / DBL_EPSILON —— 浮点数极值与精度宏

🎯 功能:描述 float 和 double 能表示的最大/最小正值,以及可分辨的最小精度差。

💡 比喻:就像天平和量杯的极限刻度:FLT_MAX 是能称的最大重量,FLT_EPSILON 是还能看出差别的最小砝码。

🔧 声明:FLT_MAX / FLT_MIN / DBL_MAX / DBL_MIN / FLT_EPSILON / DBL_EPSILON

↩️ 返回值:对应浮点类型的极值或精度阈值。

C
#include <stdio.h>
#include <float.h>

int main() {
    printf("FLT_MAX  = %e, FLT_MIN  = %e\n", FLT_MAX, FLT_MIN);
    printf("DBL_MAX  = %e, DBL_MIN  = %e\n", DBL_MAX, DBL_MIN);
    printf("FLT_EPSILON = %e\n", FLT_EPSILON);
    printf("DBL_EPSILON = %e\n", DBL_EPSILON);
    return 0;
}

sizeof 运算符 —— 查看当前平台类型实际大小

🎯 功能:在编译期求出类型或变量所占的字节数,是跨平台估算内存的基础工具。

💡 比喻:就像用尺子量杯子的实际高度——标签可能相同,但不同厂家(平台)做出来的杯子尺寸并不完全一样。

🔧 声明:sizeof(type) 或 sizeof expression

↩️ 返回值:size_t 类型的字节数。

C
#include <stdio.h>

int main() {
    printf("sizeof(char)   = %zu\n", sizeof(char));
    printf("sizeof(short)  = %zu\n", sizeof(short));
    printf("sizeof(int)    = %zu\n", sizeof(int));
    printf("sizeof(long)   = %zu\n", sizeof(long));
    printf("sizeof(float)  = %zu\n", sizeof(float));
    printf("sizeof(double) = %zu\n", sizeof(double));
    return 0;
}

👉下一节:2.2 定宽整数 →

▼2.2 定宽整数(<stdint.h> / <stddef.h>)C 语言

是什么

📚 跨平台编程需要「固定容量的标准集装箱」,无论编译器怎么实现 int,int32_t 都保证是 32 位。

💡 生活比喻:普通 int 像是不同港口对「一箱」定义不一的纸箱;stdint.h 里的定宽类型则是 ISO 标准集装箱,尺寸全球统一,装卸(移植)时才不出错。

⚠️ 注意事项:打印定宽整数时建议用 <inttypes.h> 中的 PRI* 宏,或使用显式类型转换,避免格式符与类型不匹配。

int8_t / uint8_t / int16_t / uint16_t / int32_t / uint32_t / int64_t / uint64_t —— 定宽整数类型

🎯 功能:提供明确位宽的有符号和无符号整数类型,保证跨平台大小一致。

💡 比喻:就像 20 尺、40 尺的标准集装箱,不管船怎么换,容积都按标称尺寸来算。

🔧 声明:#include <stdint.h> int8_t / uint8_t / int16_t / uint16_t / int32_t / uint32_t / int64_t / uint64_t

↩️ 返回值:声明的变量具有固定位宽和对应取值范围。

C
#include <stdio.h>
#include <stdint.h>

int main() {
    int8_t   a = -128;
    uint8_t  b = 255;
    int32_t  c = -2147483648;
    uint64_t d = 18446744073709551615ULL;

    printf("int8_t  = %d,  uint8_t  = %u\n", a, b);
    printf("int32_t = %d,  uint64_t = %llu\n", c, (unsigned long long)d);
    printf("sizeof(int32_t) = %zu, sizeof(uint64_t) = %zu\n", sizeof(c), sizeof(d));
    return 0;
}

intptr_t / uintptr_t —— 可存放指针的整数类型

🎯 功能:保证足以存放任意对象指针的整数类型,常用于地址运算或与指针互转。

💡 比喻:就像把船票编号转换成数字存进账本,既能按号查船,也能把数字还原成船票。

🔧 声明:intptr_t / uintptr_t

↩️ 返回值:与指针宽度相同的整数类型,可安全保存地址数值。

C
#include <stdio.h>
#include <stdint.h>

int main() {
    int n = 42;
    intptr_t  ip = (intptr_t)&n;
    uintptr_t up = (uintptr_t)&n;

    printf("address as uintptr_t = %llu\n", (unsigned long long)up);
    if ((int *)ip == &n) {
        printf("还原指针成功,n = %d\n", *(int *)ip);
    }
    return 0;
}

size_t / ptrdiff_t —— <stddef.h> 常用类型

🎯 功能:size_t 表示大小和数组索引,ptrdiff_t 表示两个指针相减的带符号距离。

💡 比喻:size_t 是仓库里「件数」的计数单位,ptrdiff_t 则是两个货架之间「相距几格」的有向距离。

🔧 声明:size_t / ptrdiff_t

↩️ 返回值:size_t 为无符号整数,ptrdiff_t 为有符号整数。

C
#include <stdio.h>
#include <stddef.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    size_t len = sizeof(arr) / sizeof(arr[0]);
    ptrdiff_t diff = &arr[4] - &arr[0];

    printf("数组长度 len   = %zu\n", len);
    printf("首尾指针差 diff = %td\n", diff);
    return 0;
}

👉下一节:2.3 布尔类型 →

▼2.3 布尔类型 <stdbool.h>(C99)C 语言

是什么

📚 C99 之前没有真正的 bool,这个头文件给 C 增加了 true/false,让逻辑表达更清晰。

💡 生活比喻:以前的 C 程序员用 0 和非 0 表示「是/否」,像是在纸条上写 1 或 0;stdbool.h 则提供了正式的「对勾 / 叉号」印章,一目了然。

⚠️ 注意事项:bool 底层仍是整数,用 printf 打印时会输出 0 或 1,不要误以为它是独立的新类型。

bool / true / false —— 布尔宏

🎯 功能:定义布尔类型和真假常量,让条件判断和返回值语义更明确。

💡 比喻:就像交通信号灯终于统一成红绿两色,而不是靠司机猜「暗一点是停、亮一点是行」。

🔧 声明:#include <stdbool.h> bool variable = true; 或 bool variable = false;

↩️ 返回值:bool 变量的值为 0(false)或 1(true)。

C
#include <stdio.h>
#include <stdbool.h>

int main() {
    bool ok   = true;
    bool fail = false;
    printf("ok   = %d\n", ok);
    printf("fail = %d\n", fail);
    return 0;
}

用 bool 写条件判断函数示例

🎯 功能:把判断逻辑封装成返回 bool 的函数,提高代码可读性和复用性。

💡 比喻:就像把「是否下雨」的判断交给气象站,调用者只需看「带伞 / 不带伞」的结论,不用自己抬头看天。

🔧 声明:bool is_condition(int arg);

↩️ 返回值:true 表示条件成立,false 表示条件不成立。

C
#include <stdio.h>
#include <stdbool.h>

bool is_leap_year(int year) {
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

int main() {
    int year = 2024;
    if (is_leap_year(year)) {
        printf("%d 是闰年\n", year);
    } else {
        printf("%d 不是闰年\n", year);
    }
    return 0;
}

👉下一节:2.4 断言调试 →

▼2.4 断言调试 <assert.h>C 语言

是什么

📚 断言是开发期的「保险丝」:当某个条件一定为真时写下 assert,如果它意外为假,程序会立刻终止并提示位置。

💡 生活比喻:assert 像是电闸里的保险丝——正常运行时毫无存在感,一旦电流(前置条件)异常,立刻熔断并告诉你哪根线出了问题。

⚠️ 注意事项:断言只应检查程序内部逻辑错误,不应用来处理用户输入或运行时外部错误;发布时通常用 NDEBUG 关闭断言。

assert(expr) —— 表达式为假则终止并打印信息

🎯 功能:检查表达式是否为真;为假时输出错误信息(含文件名、行号、函数名)并异常终止程序。

💡 比喻:就像汽车仪表盘上的红色警告灯,一旦关键指标异常就强制停车,而不是继续带病行驶。

🔧 声明:void assert(int expression);

↩️ 返回值:无返回值;失败时调用 abort() 终止程序。

C
#include <stdio.h>
#include <assert.h>

int divide(int a, int b) {
    assert(b != 0);  // 除数不能为 0,否则立刻报错
    return a / b;
}

int main() {
    printf("10 / 2 = %d\n", divide(10, 2));
    // 取消下一行注释,断言会触发并终止程序
    // printf("10 / 0 = %d\n", divide(10, 0));
    return 0;
}

NDEBUG 宏 —— 禁用断言

🎯 功能:在包含 <assert.h> 之前定义 NDEBUG,可让所有 assert 宏在编译期被替换为空,常用于发布版本。

💡 比喻:就像把保险丝换成铜线——调试期需要它保护电路,正式运行时为了效率可以撤掉。

🔧 声明:#define NDEBUG

↩️ 返回值:无;assert(expr) 不再生成任何检查代码。

C
#include <stdio.h>
#define NDEBUG
#include <assert.h>

int main() {
    int x = 0;
    assert(x != 0);  // 已被禁用,不会终止
    printf("NDEBUG 已禁用断言,程序继续执行\n");
    return 0;
}

👉下一节:3.1 动态内存 →

▼3.1 动态内存(malloc / calloc / realloc / free)C 语言

是什么

📚 动态内存是 C 的灵魂操作,它让程序在运行时按需申请和释放内存,而不是依赖编译期固定的栈空间。

💡 生活比喻:栈变量像是自家房子(自动分配、自动回收),而堆内存则是根据需求租用的公寓——malloc 是签合同入住,free 是退房交还钥匙。

⚠️ 注意事项:malloc 分配的内存未初始化,使用前最好赋值;每次 malloc/realloc 都要检查是否为 NULL;free 后要将指针置 NULL,避免野指针和重复释放。

malloc(size) —— 分配 size 字节,未初始化

🎯 功能:在堆区申请一块指定字节数的连续内存,返回指向其起始地址的指针。

💡 比喻:在堆区租了一块指定面积的空地,但里面还没装修,搬进去前需要自己动手布置。

🔧 声明:void *malloc(size_t size);

↩️ 返回值:成功返回指向内存块起始地址的指针,失败返回 NULL。

C
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int *)malloc(sizeof(int) * 5);
    if (p == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        p[i] = (i + 1) * 10;
    }

    for (int i = 0; i < 5; i++) {
        printf("p[%d] = %d\n", i, p[i]);
    }

    free(p);
    p = NULL;
    return 0;
}

calloc(nmemb, size) —— 分配 nmemb*size 字节并清零

🎯 功能:申请能容纳 nmemb 个、每个 size 字节元素的内存块,并将所有字节初始化为 0。

💡 比喻:像租了一间已经打扫干净、家具全部归零的新公寓,适合存放数组并避免未初始化的垃圾值。

🔧 声明:void *calloc(size_t nmemb, size_t size);

↩️ 返回值:成功返回指向清零内存的指针,失败返回 NULL。

C
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = (int *)calloc(5, sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    free(arr);
    arr = NULL;
    return 0;
}

realloc(ptr, size) —— 调整已分配内存大小

🎯 功能:扩大或缩小先前由 malloc/calloc/realloc 分配的内存块,可能搬移到新地址。

💡 比喻:就像公寓住久了东西变多,需要换一间更大或更小的房间;realloc 会尽量原地扩建,原地不够则搬家并自动处理旧家具。

🔧 声明:void *realloc(void *ptr, size_t size);

↩️ 返回值:成功返回新内存块指针(可能与原指针不同),失败返回 NULL 且原内存保持不变;size 为 0 时行为由实现定义。

C
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int *)malloc(sizeof(int) * 3);
    if (p == NULL) return 1;

    for (int i = 0; i < 3; i++) p[i] = i + 1;

    int *tmp = (int *)realloc(p, sizeof(int) * 5);
    if (tmp == NULL) {
        free(p);
        return 1;
    }
    p = tmp;

    p[3] = 4;
    p[4] = 5;

    for (int i = 0; i < 5; i++) {
        printf("p[%d] = %d\n", i, p[i]);
    }

    free(p);
    p = NULL;
    return 0;
}

free(ptr) —— 释放内存

🎯 功能:归还 malloc/calloc/realloc 分配的内存,让运行时库可以重新利用这块空间。

💡 比喻:退房把钥匙交还房东;如果不退房,房间一直占着别人没法用(内存泄漏);把同一把钥匙退两次会惹麻烦(重复释放)。

🔧 声明:void free(void *ptr);

↩️ 返回值:无返回值。

C
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int *)malloc(sizeof(int) * 4);
    if (p == NULL) return 1;

    for (int i = 0; i < 4; i++) {
        p[i] = i * i;
        printf("p[%d] = %d\n", i, p[i]);
    }

    free(p);
    p = NULL;

    printf("内存已释放,指针已置空\n");
    return 0;
}

👉下一节:3.2 错误码 →

▼3.2 错误码 <errno.h> + perrorC 语言

是什么

📚 系统调用或库函数失败时,全局变量 errno 保存错误码,程序员可以通过它判断具体失败原因。

💡 生活比喻:errno 像是医院病历本上的错误编码——函数是医生,看病时把诊断结果写在上面,调用者翻看病历就知道哪里出了问题。

⚠️ 注意事项:errno 只在函数明确返回错误时才可靠;调用其他函数前应及时读取,否则可能被后续调用覆盖。

errno —— 全局错误码变量

🎯 功能:记录最近一次系统调用或库函数失败的原因,定义在 <errno.h> 中。

💡 比喻:就像快递柜的取件码显示屏,只有最近一次投递的码有效,新的快递来了就会刷新。

🔧 声明:extern int errno;

↩️ 返回值:errno 本身保存错误码整数,需配合 strerror 或错误宏解读。

C
#include <stdio.h>
#include <errno.h>

int main() {
    FILE *fp = fopen("不存在的文件.txt", "r");
    if (fp == NULL) {
        printf("打开文件失败,errno = %d\n", errno);
    }
    return 0;
}

perror(s) —— 打印错误信息

🎯 功能:向标准错误输出用户自定义前缀和 errno 当前值对应的错误描述。

💡 比喻:就像把取件码拿到前台,工作人员会念出「您的快递 XXXX:包裹已破损」这样完整的解释。

🔧 声明:void perror(const char *s);

↩️ 返回值:无返回值;输出到 stderr。

C
#include <stdio.h>
#include <errno.h>

int main() {
    FILE *fp = fopen("missing_file.txt", "r");
    if (fp == NULL) {
        perror("无法打开文件");
    }
    return 0;
}

strerror(errnum) —— 返回错误描述字符串

🎯 功能:根据传入的错误码返回对应的可读错误描述字符串,便于自定义输出格式。

💡 比喻:就像一本错误码字典,输入数字编码,翻开会得到完整的中文解释。

🔧 声明:char *strerror(int errnum);

↩️ 返回值:指向描述字符串的指针,不应修改其内容。

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

int main() {
    FILE *fp = fopen("missing_file.txt", "r");
    if (fp == NULL) {
        const char *msg = strerror(errno);
        printf("自定义错误信息:%s\n", msg);
    }
    return 0;
}

👉下一节:3.3 可变参数 →

▼3.3 可变参数 <stdarg.h>C 语言

是什么

📚 C 允许函数接收数量不定的参数,<stdarg.h> 提供 va_list、va_start、va_arg、va_end 四个宏来遍历这些参数。

💡 生活比喻:可变参数函数像一个智能收纳盒——va_start 打开盖子,va_arg 一件一件取出里面的物品,va_end 关上盖子。

⚠️ 注意事项:可变参数没有类型检查,调用者必须按约定传入正确类型和数量;通常需要一个固定参数来指示结束或个数。

va_list / va_start / va_arg / va_end —— 可变参数遍历宏

🎯 功能:va_list 声明参数列表对象;va_start 根据最后一个命名参数初始化;va_arg 按类型取出下一个参数;va_end 清理状态。

💡 比喻:va_list 是购物清单,va_start 是走进超市,va_arg 是按清单一件件拿货,va_end 是结账出门。

🔧 声明:va_list ap;
void va_start(va_list ap, last_arg);
type va_arg(va_list ap, type);
void va_end(va_list ap);

↩️ 返回值:va_start 和 va_end 无返回值;va_arg 返回指定类型的下一个参数值。

C
#include <stdio.h>
#include <stdarg.h>

void show_values(int count, ...) {
    va_list ap;
    va_start(ap, count);

    for (int i = 0; i < count; i++) {
        int value = va_arg(ap, int);
        printf("value[%d] = %d\n", i, value);
    }

    va_end(ap);
}

int main() {
    show_values(4, 10, 20, 30, 40);
    return 0;
}

编写自定义求和函数示例

🎯 功能:通过可变参数实现一个接收任意多个整数并求和的函数。

💡 比喻:就像收银员不知道今天会来几位顾客,但每来一个就累加消费金额,直到收到「结束」口令。

🔧 声明:int sum(int count, ...);

↩️ 返回值:所有传入整数的总和。

C
#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);

    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(ap, int);
    }

    va_end(ap);
    return total;
}

int main() {
    printf("sum(3, 1, 2, 3) = %d\n", sum(3, 1, 2, 3));
    printf("sum(5, 10, 20, 30, 40, 50) = %d\n", sum(5, 10, 20, 30, 40, 50));
    return 0;
}

👉下一节:4.3 进程控制 →

▼4.3 <ctype.h> —— 字符判断与转换C 语言

是什么

📚 <ctype.h> 提供一组宏/函数,用来判断字符是否属于字母、数字、空白、标点、控制字符等,以及进行大小写转换。

💡 生活比喻:这些函数像邮件分拣中心的智能扫描仪——拿到一个字符,立刻判断它是「信件」「包裹」「垃圾邮件」还是「需要转交国际部(大小写转换)」。

⚠️ 注意事项:这些函数的参数必须是 unsigned char 值或 EOF,直接传入 char 在 signed char 平台上可能因负数导致未定义行为;转换函数返回值是 int,通常需要强转回 char。

isalpha / isdigit / isalnum / isspace / ispunct / iscntrl / isprint —— 字符类别判断

🎯 功能:根据字符的类别返回非零(真)或 0(假)。

💡 比喻:就像海关的多个通道标识:isalpha 是「字母通道」,isdigit 是「数字通道」,ispunct 是「标点通道」,各司其职。

🔧 声明:int isalpha(int c);
int isdigit(int c);
int isalnum(int c);
int isspace(int c);
int ispunct(int c);
int iscntrl(int c);
int isprint(int c);

↩️ 返回值:条件成立返回非零整数,不成立返回 0。

C
#include <stdio.h>
#include <ctype.h>

int main() {
    char str[] = "Hello 42!\n";
    for (int i = 0; str[i] != '\0'; i++) {
        unsigned char c = (unsigned char)str[i];
        printf("'%c': alpha=%d digit=%d space=%d punct=%d\n",
               c, isalpha(c), isdigit(c), isspace(c), ispunct(c));
    }
    return 0;
}

isupper / islower —— 大小写判断

🎯 功能:判断字符是否为大写或小写英文字母。

💡 比喻:就像人事部的「大写档案柜」和「小写档案柜」,把字母按大小写分开归档。

🔧 声明:int isupper(int c);
int islower(int c);

↩️ 返回值:是大写/小写字母返回非零,否则返回 0。

C
#include <stdio.h>
#include <ctype.h>

int main() {
    char str[] = "Hello World";
    for (int i = 0; str[i] != '\0'; i++) {
        unsigned char c = (unsigned char)str[i];
        if (isupper(c)) {
            printf("'%c' 是大写字母\n", c);
        } else if (islower(c)) {
            printf("'%c' 是小写字母\n", c);
        }
    }
    return 0;
}

toupper / tolower —— 大小写转换

🎯 功能:把字符转换为大写或小写形式;若本身不是字母则保持不变。

💡 比喻:就像文本编辑器的「全部大写 / 全部小写」按钮,按一下统一格式。

🔧 声明:int toupper(int c);
int tolower(int c);

↩️ 返回值:返回转换后的字符(int 类型),若无需转换则返回原字符。

C
#include <stdio.h>
#include <ctype.h>

int main() {
    char str[] = "Hello World 123";
    for (int i = 0; str[i] != '\0'; i++) {
        unsigned char c = (unsigned char)str[i];
        putchar(tolower(c));
    }
    putchar('\n');

    for (int i = 0; str[i] != '\0'; i++) {
        unsigned char c = (unsigned char)str[i];
        putchar(toupper(c));
    }
    putchar('\n');
    return 0;
}

👉下一节:4.4 string.h →

▼4.4 <string.h> —— C 风格字符串操作C 语言

是什么

📚 C 语言没有专门的字符串类型,字符串是以空字符 '\0' 结尾的字符数组,<string.h> 提供了复制、连接、比较、查找等操作函数。

💡 生活比喻:C 风格字符串像一列货运火车,最后一节车厢必定是「空车厢 \0」作为终点标记;所有 string.h 函数都靠这个标记来判断火车到底有多长。

⚠️ 注意事项:操作目标缓冲区必须足够大,避免缓冲区溢出;strncpy 等带 n 的函数不会自动补 \0,需要手动处理;尽量使用更安全的 strnlen_s / strcpy_s 等(C11 可选边界检查接口)。

strlen —— 计算字符串长度

🎯 功能:计算字符串长度,即从头开始到第一个 '' 之间的字符个数。

💡 比喻:就像从火车头数到尾部的空车厢,数过的车厢数就是字符串长度。

🔧 声明:size_t strlen(const char *s);

↩️ 返回值:返回字符串长度(不含 '')。

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

int main() {
    const char *s = "Hello";
    printf("字符串: %s\n", s);
    printf("strlen = %zu\n", strlen(s));
    return 0;
}

strcpy / strncpy —— 复制字符串

🎯 功能:strcpy 把源字符串(含 '')复制到目标缓冲区;strncpy 最多复制 n 个字符,不保证以 结尾。

💡 比喻:strcpy 是整列火车照搬过去;strncpy 是只复制前 n 节车厢,若原火车更长,需要自己补上空车厢标记。

🔧 声明:char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);

↩️ 返回值:返回 dest 指针。

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

int main() {
    char src[] = "Hello";
    char dest1[20];
    strcpy(dest1, src);
    printf("strcpy 结果: %s\n", dest1);

    char dest2[20] = {0};
    strncpy(dest2, "Hello, World", 5);
    dest2[5] = '\0';  // 手动补结束符
    printf("strncpy 结果: %s\n", dest2);
    return 0;
}

strcat / strncat —— 连接字符串

🎯 功能:strcat 把源字符串追加到目标字符串末尾;strncat 最多追加 n 个字符,并保证结果以 结尾。

💡 比喻:就像把一列火车接到另一列火车尾部;strncat 是只挂上前 n 节车厢,并自动挂上空车厢标记。

🔧 声明:char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n);

↩️ 返回值:返回 dest 指针。

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

int main() {
    char dest[30] = "Hello";
    strcat(dest, " World");
    printf("strcat 结果: %s\n", dest);

    char dest2[30] = "Hello";
    strncat(dest2, "World", 3);
    printf("strncat 结果: %s\n", dest2);
    return 0;
}

strcmp / strncmp —— 比较字符串

🎯 功能:按字典序比较两个字符串;strncmp 只比较前 n 个字符。

💡 比喻:就像两个人逐字对比身份证姓名:strcmp 比完整姓名,strncmp 只比前几个字。

🔧 声明:int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);

↩️ 返回值:s1 < s2 返回负数,相等返回 0,s1 > s2 返回正数。

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

int main() {
    const char *a = "apple";
    const char *b = "banana";
    printf("strcmp(a, b) = %d\n", strcmp(a, b));
    printf("strcmp(b, a) = %d\n", strcmp(b, a));
    printf("strncmp(a, b, 1) = %d\n", strncmp(a, b, 1));
    return 0;
}

strchr / strstr —— 查找字符或子串

🎯 功能:strchr 查找字符首次出现位置,strstr 查找子串首次出现位置。

💡 比喻:strchr 是在一本书里找到某个字第一次出现的位置,strstr 是找到某个词组第一次出现的位置。

🔧 声明:char *strchr(const char *s, int c);
char *strstr(const char *haystack, const char *needle);

↩️ 返回值:返回指向首次匹配位置的指针,未找到返回 NULL。

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

int main() {
    const char *s = "Hello, World!";
    char *p1 = strchr(s, 'o');
    printf("strchr('o'): %s\n", p1 ? p1 : "未找到");

    char *p2 = strstr(s, "World");
    printf("strstr('World'): %s\n", p2 ? p2 : "未找到");
    return 0;
}

memcpy / memmove / memset / memcmp —— 内存块操作

🎯 功能:对任意内存块进行拷贝、移动、填充、比较,不依赖 结束符。

💡 比喻:memcpy 像复印机逐页复印;memmove 像搬家时允许临时堆放重叠物品;memset 像把每个房间刷成同色;memcmp 像两摞文件逐页比对。

🔧 声明:void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
void *memset(void *s, int c, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);

↩️ 返回值:memcpy/memmove/memset 返回 dest;memcmp 返回负数/0/正数。

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

int main() {
    char src[] = "abcdef";
    char dest[20];

    memcpy(dest, src, strlen(src) + 1);
    printf("memcpy: %s\n", dest);

    char overlap[] = "abcdef";
    memmove(overlap + 2, overlap, 4);  // 允许重叠
    printf("memmove: %s\n", overlap);

    char buf[10];
    memset(buf, 'A', sizeof(buf) - 1);
    buf[9] = '\0';
    printf("memset: %s\n", buf);

    char a[] = "abc";
    char b[] = "abd";
    printf("memcmp: %d\n", memcmp(a, b, 3));
    return 0;
}

👉下一节:5.1 进程控制 →

▼5.1 <math.h> —— 数学函数(C89 起)C 语言

是什么

📚 <math.h> 是 C 语言的“浮点数计算器工具箱”。它提供了一套处理小数、指数、三角、对数、取整等常见数学运算的函数。你可以把它想象成一个随身携带的科学计算器——只要按下对应的函数按钮,就能得出结果。

⚠️ 注意:某些 Linux/Unix 平台在编译时需要链接数学库,即在 gcc 命令后加 -lm,例如 gcc main.c -lm。

fabs(x) —— 绝对值(把负数“翻正”)

🎯 功能:接受一个浮点数 x,返回它的绝对值。负数变正,正数和零保持不变。

💡 比喻:就像一面镜子:不管你在镜子左边还是右边,镜中人离镜面的距离总是正的。fabs(-3.14) 得到 3.14。

🔧 声明:double fabs(double x);

↩️ 返回值:x 的绝对值。对 ±0 返回 +0,对 ±∞ 返回 +∞,对 NaN 返回 NaN。

C
#include <math.h>
#include <stdio.h>
int main() {
    double a = fabs(-3.14);
    printf("fabs(-3.14) = %f\n", a);   // 3.140000
    printf("fabs(5.0) = %f\n", fabs(5.0)); // 5.000000
    return 0;
}

sqrt(x) —— 平方根(开平方)

🎯 功能:接受非负浮点数 x,返回它的算术平方根,即哪个数自乘等于 x。

💡 比喻:平方根就像“反过来的面积”。已知正方形面积是 x,sqrt(x) 就是它的边长。

🔧 声明:double sqrt(double x);

↩️ 返回值:x 的平方根。如果 x < 0 会发生域错误,返回 NaN。

C
#include <math.h>
#include <stdio.h>
int main() {
    printf("sqrt(16) = %f\n", sqrt(16.0));  // 4.000000
    printf("sqrt(2) = %f\n", sqrt(2.0));   // 1.414214
    printf("sqrt(-1) = %f\n", sqrt(-1.0));  // nan(域错误)
    return 0;
}

pow(base, exp) —— 幂运算(求 base 的 exp 次方)

🎯 功能:接受底数 base 和指数 exp,返回 base 的 exp 次幂。

💡 比喻:就像“连乘的快捷键”。pow(2, 10) 表示 2 连续乘 10 次,结果是 1024。

🔧 声明:double pow(double base, double exp);

↩️ 返回值:baseexp。若 base < 0 且 exp 不是整数,会发生域错误。

C
#include <math.h>
#include <stdio.h>
int main() {
    printf("pow(2, 10) = %.0f\n", pow(2.0, 10.0)); // 1024
    printf("pow(9, 0.5) = %f\n", pow(9.0, 0.5));  // 3.000000
    printf("pow(2, -1) = %f\n", pow(2.0, -1.0)); // 0.500000
    return 0;
}

sin / cos / tan —— 三角函数(弧度制)

🎯 功能:分别计算 x 的正弦、余弦、正切值。注意它们使用的单位是弧度,不是角度。

💡 比喻:弧度就像“绕圆心走了多远”。一圈是 2π 弧度(约 6.28),半圈是 π。角度转弧度:rad = deg * PI / 180.0。

🔧 声明:double sin(double x); double cos(double x); double tan(double x);

↩️ 返回值:对应三角函数值。sin 和 cos 的结果在 [-1, 1] 之间。

C
#include <math.h>
#include <stdio.h>
int main() {
    double pi = 3.1415926535;
    printf("sin(pi/2) = %f\n", sin(pi / 2)); // 1.0
    printf("cos(pi) = %f\n", cos(pi));       // -1.0
    printf("tan(pi/4) = %f\n", tan(pi / 4)); // 约 1.0
    return 0;
}

log(x) / log10(x) —— 对数(自然对数 / 常用对数)

🎯 功能:log 计算自然对数(底数为 e),log10 计算常用对数(底数为 10)。

💡 比喻:对数是“反过来的指数”。如果 10y = x,那么 log10(x) = y。比如 log10(1000) = 3,因为 10³ = 1000。

🔧 声明:double log(double x); double log10(double x);

↩️ 返回值:对应的对数值。若 x ≤ 0 会发生域错误。

C
#include <math.h>
#include <stdio.h>
int main() {
    printf("log(e) = %f\n", log(2.718281828));    // 约 1.0
    printf("log10(1000) = %f\n", log10(1000.0));  // 3.000000
    printf("log10(0.01) = %f\n", log10(0.01));    // -2.000000
    return 0;
}

ceil / floor / round —— 取整三兄弟

🎯 功能:把浮点数 x 变成整数。ceil 向上取整,floor 向下取整,round 四舍五入。

💡 比喻:想象你有一摞硬币,要装进整数个盒子里。ceil 是多出 1 枚也要再开一盒;floor 是只装满整盒,零头不要;round 是零头超过一半就进一盒。

🔧 声明:double ceil(double x); double floor(double x); double round(double x);

↩️ 返回值:返回距离目标方向最近的整数值(仍用浮点数表示)。

C
#include <math.h>
#include <stdio.h>
int main() {
    double x = 3.2;
    printf("ceil(%.1f) = %.0f\n", x, ceil(x));    // 4
    printf("floor(%.1f) = %.0f\n", x, floor(x));  // 3
    printf("round(%.1f) = %.0f\n", x, round(x));  // 3
    printf("round(3.6) = %.0f\n", round(3.6));    // 4
    return 0;
}

fmod(x, y) —— 浮点取余(求“除不尽”的部分)

🎯 功能:接受被除数 x 和除数 y,返回 x 除以 y 后的余数,和整数 % 类似,但用于浮点数。

💡 比喻:把 x 米长的绳子每 y 米剪一段,最后剩下不足一段的长度就是 fmod(x, y)。

🔧 声明:double fmod(double x, double y);

↩️ 返回值:x - n*y,其中 n 是使结果绝对值小于 y 的整数。若 y 为 0,行为未定义。

C
#include <math.h>
#include <stdio.h>
int main() {
    printf("fmod(7.5, 2.0) = %f\n", fmod(7.5, 2.0));  // 1.500000
    printf("fmod(10.0, 3.0) = %f\n", fmod(10.0, 3.0)); // 1.000000
    return 0;
}

modf(x, &iptr) —— 拆分整数和小数部分

🎯 功能:把浮点数 x 拆成整数部分和小数部分。小数部分作为返回值,整数部分存到 iptr 指向的变量里。

💡 比喻:就像把 3.14 元钱拆成 3 元整钞和 0.14 元零钱。modf 把整钞“递”给 iptr,自己手里拿着零钱。

🔧 声明:double modf(double x, double *iptr);

↩️ 返回值:x 的小数部分,符号与 x 相同。*iptr 得到整数部分。

C
#include <math.h>
#include <stdio.h>
int main() {
    double intpart;
    double frac = modf(3.14159, &intpart);
    printf("整数部分 = %.0f\n", intpart); // 3
    printf("小数部分 = %f\n", frac);      // 0.141590
    return 0;
}
⚠️ 重要提醒:<math.h> 中的三角函数全部使用弧度而非角度。如果你手头是角度,请先用 rad = deg * PI / 180.0 转换。

👉更多数学函数(complex.h 复数、fenv.h 浮点环境)→

▼5.2 <stdlib.h> —— 随机数与通用工具C 语言

是什么

📚 <stdlib.h> 是 C 标准库的「杂货铺」,除了内存分配,还提供随机数生成、字符串转数字、程序退出、快速排序与二分查找等通用工具。

💡 生活比喻:stdlib.h 像工具箱里的瑞士军刀——有骰子(rand)、翻译官(atoi/qsort)、紧急停止按钮(exit/abort),什么杂活都能应付。

⚠️ 注意事项:rand 默认种子固定,先用 srand 设置随机种子;字符串转数字函数对非法输入容错差,strtol/strtod 更安全;exit 会刷新并关闭所有打开的文件流。

rand / srand —— 随机数生成

🎯 功能:rand 返回伪随机整数,srand 设置随机种子以改变序列。

💡 比喻:rand 像一台按固定剧本播出的抽奖机,srand 则像按下「重新洗牌」按钮,让每次结果不同。

🔧 声明:int rand(void);
void srand(unsigned int seed);

↩️ 返回值:rand 返回 0 到 RAND_MAX 之间的整数;srand 无返回值。

C
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    srand((unsigned int)time(NULL));  // 用当前时间做种子
    for (int i = 0; i < 5; i++) {
        printf("rand() = %d\n", rand());
    }
    printf("RAND_MAX = %d\n", RAND_MAX);
    return 0;
}

atoi / atol / atof / strtol / strtod —— 字符串转数字

🎯 功能:把字符串转换为整数或浮点数;atox 系列简单但容错差,strtox 系列更健壮并给出结束位置。

💡 比喻:atoi 像直接报数员的快速口算,strtol 则是带验算和回执的正式转账——知道转了多少、还剩多少没处理。

🔧 声明:int atoi(const char *nptr);
long atol(const char *nptr);
double atof(const char *nptr);
long strtol(const char *nptr, char **endptr, int base);
double strtod(const char *nptr, char **endptr);

↩️ 返回值:返回转换后的数值;strtol/strtod 还通过 endptr 输出未处理部分。

C
#include <stdio.h>
#include <stdlib.h>

int main() {
    const char *s = "  -123abc  45.67xyz";
    printf("atoi = %d\n", atoi(s));

    char *end;
    long n = strtol(s, &end, 10);
    printf("strtol = %ld, 剩余: '%s'\n", n, end);

    double d = strtod(end, &end);
    printf("strtod = %.2f, 剩余: '%s'\n", d, end);
    return 0;
}

exit / abort / atexit —— 程序终止控制

🎯 功能:exit 正常结束程序并执行清理,abort 异常终止不清理,atexit 注册在 exit 时调用的函数。

💡 比喻:exit 像下班前关灯锁门;abort 像拉下消防闸直接撤离;atexit 则是预先登记的「出门前记得倒垃圾」提醒。

🔧 声明:void exit(int status);
void abort(void);
int atexit(void (*func)(void));

↩️ 返回值:exit 和 abort 不返回;atexit 成功返回 0,失败返回非零。

C
#include <stdio.h>
#include <stdlib.h>

void cleanup() {
    printf("atexit 清理函数执行\n");
}

int main() {
    atexit(cleanup);
    printf("即将正常退出\n");
    exit(0);
    // abort(); // 异常终止,不执行 cleanup
}

qsort / bsearch —— 快速排序与二分查找

🎯 功能:qsort 对数组快速排序,bsearch 在已排序数组中二分查找元素。

💡 比喻:qsort 像把乱序书架按编号快速整理好;bsearch 则是在整理好的书架上用折半查找迅速定位一本书。

🔧 声明:void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

↩️ 返回值:qsort 无返回值;bsearch 找到返回元素指针,未找到返回 NULL。

C
#include <stdio.h>
#include <stdlib.h>

int cmp_int(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}

int main() {
    int arr[] = {3, 1, 4, 1, 5, 9, 2, 6};
    size_t n = sizeof(arr) / sizeof(arr[0]);

    qsort(arr, n, sizeof(int), cmp_int);
    printf("排序后: ");
    for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    int key = 5;
    int *p = (int *)bsearch(&key, arr, n, sizeof(int), cmp_int);
    if (p) {
        printf("找到 %d\n", *p);
    } else {
        printf("未找到 %d\n", key);
    }
    return 0;
}

👉下一节:6.2 time.h →

▼6.2 <time.h> —— C 风格时间C 语言

是什么

📚 <time.h> 提供获取日历时间、计算程序运行时间、格式化输出以及本地/UTC 转换等函数。

💡 生活比喻:time.h 就像一座车站的调度系统——time 看当前时刻表,clock 测列车跑了多少圈,strftime 把时刻表打印成乘客能看懂的格式。

⚠️ 注意事项:time_t 和 clock_t 的具体类型由实现定义;localtime/gmtime 返回静态结构体指针,不可长期保存且非线程安全;mktime 会自动处理日期归一化(如 32 日会进位)。

time —— 获取当前日历时间

🎯 功能:获取当前日历时间,通常返回自 1970-01-01 00:00:00 UTC 以来的秒数。

💡 比喻:就像车站大钟显示当前的标准时间戳,全世界都能按统一规则解读。

🔧 声明:time_t time(time_t *timer);

↩️ 返回值:返回当前时间的时间戳;失败返回 (time_t)-1。

C
#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL);
    printf("当前时间戳: %lld\n", (long long)now);
    return 0;
}

clock —— 获取处理器时间

🎯 功能:返回程序自启动以来消耗的处理器时间,用于性能测量。

💡 比喻:就像赛车上的圈速计时器,记录发动机转了多少个时钟滴答。

🔧 声明:clock_t clock(void);

↩️ 返回值:返回处理器时钟滴答数;失败返回 (clock_t)-1。

C
#include <stdio.h>
#include <time.h>

int main() {
    clock_t start = clock();
    long sum = 0;
    for (int i = 0; i < 1000000; i++) sum += i;
    clock_t end = clock();

    printf("sum = %ld\n", sum);
    printf("耗时: %.3f 秒\n", (double)(end - start) / CLOCKS_PER_SEC);
    return 0;
}

difftime —— 计算时间差

🎯 功能:计算两个 time_t 时间戳之间的秒数差。

💡 比喻:就像计算两趟列车发车时刻的间隔,结果以秒为单位。

🔧 声明:double difftime(time_t end, time_t start);

↩️ 返回值:返回 end - start 的秒数(double)。

C
#include <stdio.h>
#include <time.h>

int main() {
    time_t start = time(NULL);
    // 模拟一段耗时操作
    for (volatile long i = 0; i < 100000000; i++) {}
    time_t end = time(NULL);

    printf("运行时间: %.2f 秒\n", difftime(end, start));
    return 0;
}

localtime / gmtime —— 时间转换

🎯 功能:localtime 把 time_t 转换为本地时区的 struct tm;gmtime 转换为 UTC 时间。

💡 比喻:localtime 把车站时间翻译成你所在城市的表盘;gmtime 则是翻译成格林尼治标准时间的表盘。

🔧 声明:struct tm *localtime(const time_t *timer);
struct tm *gmtime(const time_t *timer);

↩️ 返回值:返回指向 struct tm 的指针(静态分配),失败返回 NULL。

C
#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL);
    struct tm *local = localtime(&now);
    struct tm *utc = gmtime(&now);

    printf("本地时间: %d-%02d-%02d %02d:%02d:%02d\n",
           local->tm_year + 1900, local->tm_mon + 1, local->tm_mday,
           local->tm_hour, local->tm_min, local->tm_sec);

    printf("UTC 时间: %d-%02d-%02d %02d:%02d:%02d\n",
           utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday,
           utc->tm_hour, utc->tm_min, utc->tm_sec);
    return 0;
}

strftime —— 格式化时间字符串

🎯 功能:按照指定格式把 struct tm 转换为人类可读的字符串。

💡 比喻:就像把车站时刻表按不同版式打印:有的只要日期,有的要精确到秒。

🔧 声明:size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

↩️ 返回值:返回写入的字符数(不含 ''),若缓冲区不够返回 0。

C
#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL);
    struct tm *local = localtime(&now);
    char buf[100];

    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local);
    printf("格式 1: %s\n", buf);

    strftime(buf, sizeof(buf), "%A, %B %d, %Y", local);
    printf("格式 2: %s\n", buf);
    return 0;
}

mktime —— 将 tm 结构体转换为 time_t

🎯 功能:把 struct tm 转换为 time_t,并自动归一化各字段(如 32 日会进位)。

💡 比喻:就像把分散的年、月、日、时、分、秒拼回一个标准时间戳,并自动纠正写错的天数。

🔧 声明:time_t mktime(struct tm *timeptr);

↩️ 返回值:返回对应的 time_t 时间戳;无法表示时返回 (time_t)-1。

C
#include <stdio.h>
#include <time.h>

int main() {
    struct tm t = {0};
    t.tm_year = 2024 - 1900;  // 自 1900 起的年数
    t.tm_mon = 0;             // 0 表示一月
    t.tm_mday = 32;           // 故意写错,mktime 会归一化

    time_t ts = mktime(&t);
    printf("归一化后: %d-%02d-%02d %02d:%02d:%02d\n",
           t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
           t.tm_hour, t.tm_min, t.tm_sec);
    printf("时间戳: %lld\n", (long long)ts);
    return 0;
}

👉下一节:15.1 宏与预处理器 →

▼15.1 <stdio.h> —— C 风格输入输出C 语言

是什么

📚 <stdio.h> 是 C 标准库中最常用的头文件,提供格式化输出、格式化输入、字符级 I/O、文件打开关闭、块读写以及流定位等完整函数族。

💡 生活比喻:stdio.h 像一座邮政枢纽——printf/scanf 处理普通信件,fopen/fclose 管理信箱,fread/fwrite 搬运大宗包裹,fseek/ftell 则负责追踪每封信在仓库里的位置。

⚠️ 注意事项:使用格式化函数时注意缓冲区溢出,优先用 snprintf 替代 sprintf;gets 已被废弃且不安全,应使用 fgets;每次 fopen 后都要 fclose;读写文件后检查返回值和 feof/ferror 状态。

printf / fprintf / sprintf / snprintf —— 格式化输出

🎯 功能:按格式字符串把数据输出到标准输出、指定流、字符串缓冲区;snprintf 带缓冲区大小限制,更安全。

💡 比喻:printf 像前台广播,fprintf 像寄到指定房间的快递,sprintf/snprintf 则是把内容抄写到指定笔记本上,snprintf 还会先量好笔记本页数避免写爆。

🔧 声明:int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

↩️ 返回值:返回写入的字符数(不含 );出错返回负值;snprintf 若缓冲区不够返回本会写入的字符数(不含 )。

C
#include <stdio.h>

int main() {
    printf("Hello %s, number = %d\n", "C", 42);

    FILE *fp = fopen("output.txt", "w");
    if (fp) {
        fprintf(fp, "写入文件: %d\n", 100);
        fclose(fp);
    }

    char buf[32];
    snprintf(buf, sizeof(buf), "value=%d", 12345);
    printf("buf = %s\n", buf);
    return 0;
}

scanf / fscanf / sscanf —— 格式化输入

🎯 功能:从标准输入、文件流或字符串中按格式解析数据。

💡 比喻:scanf 像前台接待员口头询问并记录信息;fscanf 像从已有档案中摘录;sscanf 则是从一张纸条上按格式抓取数据。

🔧 声明:int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

↩️ 返回值:成功匹配并赋值的输入项数量;EOF 或匹配失败时返回值小于预期。

C
#include <stdio.h>

int main() {
    const char *input = "Alice 25 3.14";
    char name[32];
    int age;
    double pi;

    int n = sscanf(input, "%s %d %lf", name, &age, &pi);
    printf("匹配项数: %d, 姓名=%s, 年龄=%d, pi=%.2f\n", n, name, age, pi);

    char line[64] = "2024 10";
    int y, m;
    sscanf(line, "%d %d", &y, &m);
    printf("日期: %d-%02d\n", y, m);
    return 0;
}

getchar / putchar / gets / fgets / puts / fputs —— 字符与行 I/O

🎯 功能:getchar/putchar 读写单个字符;fgets 安全读取一行;puts/fputs 输出字符串。

💡 比喻:getchar/putchar 像收发一颗一颗的糖果;fgets 像用固定容量的饭盒盛一勺汤,保证不溢出;puts 会自动在末尾补一个响铃(换行)。

🔧 声明:int getchar(void);
int putchar(int c);
char *fgets(char *s, int size, FILE *stream);
int puts(const char *s);
int fputs(const char *s, FILE *stream);

↩️ 返回值:getchar 返回读取字符或 EOF;putchar 返回写入字符或 EOF;fgets 返回 s 或 NULL;puts/fputs 成功返回非负,失败返回 EOF。

C
#include <stdio.h>

int main() {
    const char *msg = "Hello fgets\n";
    fputs(msg, stdout);

    char line[128];
    printf("请输入一行文字: ");
    if (fgets(line, sizeof(line), stdin)) {
        printf("读取到: %s", line);
    }

    putchar('A');
    putchar('\n');
    return 0;
}

fopen / fclose / freopen —— 文件流开关

🎯 功能:fopen 打开文件并返回 FILE 指针;fclose 关闭流;freopen 把已有流重定向到新文件。

💡 比喻:fopen 像租用一个文件柜抽屉并拿到钥匙;fclose 是用完归还钥匙;freopen 则是把原来的抽屉钥匙换到另一个柜子上。

🔧 声明:FILE *fopen(const char *pathname, const char *mode);
int fclose(FILE *stream);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);

↩️ 返回值:fopen/freopen 成功返回 FILE*,失败返回 NULL;fclose 成功返回 0,失败返回 EOF。

C
#include <stdio.h>

int main() {
    FILE *fp = fopen("demo.txt", "w+");
    if (!fp) {
        perror("fopen 失败");
        return 1;
    }

    fprintf(fp, "hello file\n");
    rewind(fp);

    char buf[64];
    if (fgets(buf, sizeof(buf), fp)) {
        printf("读出: %s", buf);
    }

    fclose(fp);

    /* 把 stdout 重定向到 log.txt */
    freopen("log.txt", "w", stdout);
    printf("这行会写入 log.txt\n");
    fclose(stdout);
    return 0;
}

fread / fwrite / fgetc / fputc —— 二进制与字符块读写

🎯 功能:fread/fwrite 按块读写二进制数据;fgetc/fputc 逐字符读写。

💡 比喻:fread/fwrite 像成箱搬运货物;fgetc/fputc 像一件一件地手工搬运。二进制读写保持内存中的原始字节不变。

🔧 声明: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);
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);

↩️ 返回值:fread/fwrite 返回实际读写的完整数据项数;fgetc 返回读取字符或 EOF;fputc 返回写入字符或 EOF。

C
#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    FILE *fp = fopen("data.bin", "wb");
    if (!fp) return 1;

    fwrite(arr, sizeof(int), 5, fp);
    fclose(fp);

    fp = fopen("data.bin", "rb");
    int read[5];
    size_t n = fread(read, sizeof(int), 5, fp);
    printf("读到 %zu 个整数: ", n);
    for (size_t i = 0; i < n; i++) printf("%d ", read[i]);
    printf("\n");
    fclose(fp);

    fputc('X', stdout);
    putchar('\n');
    return 0;
}

fseek / ftell / rewind / feof / ferror —— 流定位与状态检测

🎯 功能:fseek 移动文件位置指针;ftell 返回当前位置;rewind 回到开头;feof/ferror 检测结束或错误状态。

💡 比喻:fseek/ftell/rewind 像录音带的快进、看当前时间和倒带按钮;feof/ferror 则像播放器上的「已到片尾」和「卡带报错」指示灯。

🔧 声明:int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);

↩️ 返回值:fseek 成功返回 0,失败返回非零;ftell 返回当前偏移量或 -1;feof/ferror 条件成立返回非零。

C
#include <stdio.h>

int main() {
    FILE *fp = fopen("demo.txt", "w+");
    fprintf(fp, "ABCDEFG");

    fseek(fp, 3, SEEK_SET);
    int c = fgetc(fp);
    printf("偏移 3 处字符: %c\n", c);

    long pos = ftell(fp);
    printf("当前偏移: %ld\n", pos);

    rewind(fp);
    printf("rewind 后偏移: %ld\n", ftell(fp));

    fclose(fp);
    return 0;
}

👉下一节:15.2 errno.h →

▼15.2 <errno.h> —— 错误码C 语言

是什么

📚 <errno.h> 定义了外部变量 errno 以及一组标准错误码宏(如 EDOM、ERANGE、EILSEQ 等),用于在 C 标准库函数失败时报告具体的错误原因。

💡 生活比喻:errno 像医院挂号单上的错误代码——医生(程序员)看到代码就能快速判断是哪个科室(哪类函数)出了问题,而不用重新检查全身。

⚠️ 注意事项:errno 不会在成功调用后自动清零,判断前应先手动置 0;其值是线程局部的(C11 起明确);不同操作系统可能定义额外错误码,跨平台代码不要依赖未标准化宏。

errno 变量与常用错误码(EDOM / ERANGE / EILSEQ 等)

🎯 功能:errno 是报告库函数错误的整型变量;EDOM 表示数学定义域错误,ERANGE 表示结果超出范围,EILSEQ 表示非法字符序列。

💡 比喻:errno 像快递柜上的状态灯:EDOM 是「地址不合法」,ERANGE 是「包裹太大放不下」,EILSEQ 是「条码无法识别」。

🔧 声明:extern int errno;
#define EDOM
#define ERANGE
#define EILSEQ

↩️ 返回值:errno 的整数值由具体错误决定;可配合 strerror(errno) 获得可读描述。

C
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <math.h>

int main() {
    errno = 0;
    double r = sqrt(-1.0);
    if (errno == EDOM) {
        printf("sqrt(-1) 触发 EDOM: %s\n", strerror(errno));
    }

    errno = 0;
    double x = exp(1000.0);
    if (errno == ERANGE) {
        printf("exp(1000) 触发 ERANGE: %s\n", strerror(errno));
    }

    printf("当前 errno 描述: %s\n", strerror(errno));
    return 0;
}

errno 使用规范

🎯 功能:使用可能设置 errno 的函数前,先清零 errno;调用后检查其值,并用 perror 或 strerror 输出可读错误信息。

💡 比喻:像做实验前先清空天平托盘,称完再读数并记录误差;若不先清零,可能把上一次的残留误差当成本次结果。

🔧 声明:void perror(const char *s); /* 在 <stdio.h> */
char *strerror(int errnum); /* 在 <string.h> */

↩️ 返回值:perror 输出 s: 错误描述 到 stderr;strerror 返回错误描述字符串指针。

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

int main() {
    FILE *fp;

    errno = 0;
    fp = fopen("不存在的文件.txt", "r");
    if (!fp) {
        perror("打开文件失败");
        printf("strerror: %s\n", strerror(errno));
    }

    return 0;
}

👉下一节:15.3 float.h →

▼15.3 <float.h> —— 浮点数限制C 语言

是什么

📚 <float.h> 定义了浮点类型的实现相关限制,包括基数、有效数字位数、最小/最大值、机器 epsilon 等,帮助程序了解 float、double、long double 的能力边界。

💡 生活比喻:float.h 像一份工程图纸上的公差表——告诉你浮点数这把尺子能精确到几位小数、最长能量到多大、最小能分辨多小的差异。

⚠️ 注意事项:浮点数比较应避免直接使用 ==,通常用相对误差或 epsilon;FLT_EPSILON 是使 1.0 + eps != 1.0 的最小值,不是绝对精度;不同平台和编译器结果可能不同。

FLT_RADIX / FLT_DIG / DBL_DIG —— 基数与十进制精度

🎯 功能:FLT_RADIX 表示浮点数底数(通常是 2);FLT_DIG/DBL_DIG 表示 float/double 能精确表示的十进制有效数字位数。

💡 比喻:FLT_RADIX 像尺子的最小刻度单位是二进制格;FLT_DIG/DBL_DIG 则是这把尺子在十进制下能稳稳读出的有效位数。

🔧 声明:#define FLT_RADIX
#define FLT_DIG
#define DBL_DIG
#define LDBL_DIG

↩️ 返回值:均为编译期整型常量;FLT_RADIX 一般为 2,FLT_DIG 常见为 6,DBL_DIG 常见为 15。

C
#include <stdio.h>
#include <float.h>

int main() {
    printf("FLT_RADIX = %d\n", FLT_RADIX);
    printf("FLT_DIG   = %d\n", FLT_DIG);
    printf("DBL_DIG   = %d\n", DBL_DIG);
    printf("LDBL_DIG  = %d\n", LDBL_DIG);

    float f = 1.23456789f;
    double d = 1.234567890123456789;
    printf("float  存储: %.9f\n", f);
    printf("double 存储: %.18f\n", d);
    return 0;
}

FLT_MIN / FLT_MAX / DBL_MIN / DBL_MAX / FLT_EPSILON —— 极值与精度

🎯 功能:FLT_MIN/DBL_MIN 是可表示的最小正规化正数;FLT_MAX/DBL_MAX 是最大值;FLT_EPSILON/DBL_EPSILON 是 1.0 与下一个可表示值的差。

💡 比喻:FLT_MIN/DBL_MIN 像天平能称出的最轻重量;FLT_MAX/DBL_MAX 是最大称重上限;FLT_EPSILON 则是天平能分辨的两份 1 克样品之间的最小差异。

🔧 声明:#define FLT_MIN
#define FLT_MAX
#define DBL_MIN
#define DBL_MAX
#define FLT_EPSILON
#define DBL_EPSILON

↩️ 返回值:均为浮点常量;最小值、最大值和 epsilon 因类型而异。

C
#include <stdio.h>
#include <float.h>

int main() {
    printf("FLT_MIN = %e, FLT_MAX = %e\n", FLT_MIN, FLT_MAX);
    printf("DBL_MIN = %e, DBL_MAX = %e\n", DBL_MIN, DBL_MAX);
    printf("FLT_EPSILON = %e\n", FLT_EPSILON);
    printf("DBL_EPSILON = %e\n", DBL_EPSILON);

    double a = 1.0;
    double b = a + DBL_EPSILON;
    printf("1.0 + DBL_EPSILON == 1.0 ? %s\n", (a == b) ? "是" : "否");
    return 0;
}

👉下一节:15.4 limits.h →

▼15.4 <limits.h> —— 整数类型限制C 语言

是什么

📚 <limits.h> 定义了 C 语言各整数类型(char、short、int、long、long long 等)的取值范围宏,程序可据此做边界检查与可移植性处理。

💡 生活比喻:limits.h 像道路上的限高限重标志牌——不同整数类型对应不同容量的货车,超载(溢出)会导致未定义行为甚至事故。

⚠️ 注意事项:不同平台上 int/long 的位数可能不同;需要固定宽度时优先使用 <stdint.h> 中的 int32_t/int64_t;有符号整数溢出是未定义行为,不能依赖回绕。

CHAR_BIT / SCHAR_MIN / SCHAR_MAX —— 字符类型限制

🎯 功能:CHAR_BIT 表示一个字节的位数;SCHAR_MIN/SCHAR_MAX 是有符号 char 的取值范围。

💡 比喻:CHAR_BIT 像一条传送带上能同时并排经过的零件数量;SCHAR_MIN/SCHAR_MAX 则是超小号货箱能装的数值范围。

🔧 声明:#define CHAR_BIT
#define SCHAR_MIN
#define SCHAR_MAX
#define UCHAR_MAX

↩️ 返回值:均为整型常量;CHAR_BIT 常见为 8;SCHAR_MIN/SCHAR_MAX 常见为 -128/127。

C
#include <stdio.h>
#include <limits.h>

int main() {
    printf("CHAR_BIT  = %d\n", CHAR_BIT);
    printf("SCHAR_MIN = %d\n", SCHAR_MIN);
    printf("SCHAR_MAX = %d\n", SCHAR_MAX);
    printf("UCHAR_MAX = %u\n", UCHAR_MAX);
    printf("CHAR_MIN  = %d\n", CHAR_MIN);
    printf("CHAR_MAX  = %d\n", CHAR_MAX);
    return 0;
}

SHRT_MIN / SHRT_MAX / INT_MIN / INT_MAX / LONG_MIN / LONG_MAX / LLONG_MIN / LLONG_MAX —— 整数极值

🎯 功能:分别定义 short、int、long、long long 类型的最小值和最大值宏。

💡 比喻:像停车场对不同车型的限位:short 是微型车位,int 是标准车位,long/long long 是加长车位,limits.h 在入口就把各自的边界标清楚。

🔧 声明:#define SHRT_MIN / SHRT_MAX
#define INT_MIN / INT_MAX
#define LONG_MIN / LONG_MAX
#define LLONG_MIN / LLONG_MAX

↩️ 返回值:均为对应类型的编译期常量极值。

C
#include <stdio.h>
#include <limits.h>

int main() {
    printf("short  范围: %d ~ %d\n", SHRT_MIN, SHRT_MAX);
    printf("int    范围: %d ~ %d\n", INT_MIN, INT_MAX);
    printf("long   范围: %ld ~ %ld\n", LONG_MIN, LONG_MAX);
    printf("long long 范围: %lld ~ %lld\n", LLONG_MIN, LLONG_MAX);

    int a = INT_MAX;
    printf("INT_MAX + 1 触发溢出(未定义行为),当前值 %d\n", a);
    return 0;
}

👉下一节:15.5 locale.h →

▼15.5 <locale.h> —— 本地化C 语言

是什么

📚 <locale.h> 提供本地化(localization)支持,允许程序按用户所在地区设置数字格式、货币符号、日期时间格式以及字符分类规则等。

💡 生活比喻:locale.h 像跨国公司的本地化部门——让同一份报告在美国显示 "$1,234.56",在德国显示 "1.234,56 €",在法国使用逗号作为小数点。

⚠️ 注意事项:setlocale 修改的是全局 C locale,多线程环境下需谨慎;localeconv 返回静态结构体指针,结果可能被后续调用覆盖;C 标准 locale 能力有限,复杂国际化需求通常依赖平台特定库。

setlocale —— 设置或查询当前 locale

🎯 功能:设置程序的当前 locale,或查询当前 locale 名称;常用类别有 LC_ALL、LC_NUMERIC、LC_TIME、LC_MONETARY、LC_CTYPE。

💡 比喻:setlocale 像给程序换一张「地区通行证」——拿着德国通行证,小数点就按德国规矩显示;只换 LC_TIME,则只有日期格式变。

🔧 声明:char *setlocale(int category, const char *locale);

↩️ 返回值:返回当前 locale 名称字符串;设置失败返回 NULL。

C
#include <stdio.h>
#include <locale.h>

int main() {
    printf("默认 C locale 下: %.2f\n", 1234.56);

    char *old = setlocale(LC_ALL, NULL);
    printf("当前 locale: %s\n", old ? old : "未知");

    /* 尝试切换到系统默认 locale */
    char *new_locale = setlocale(LC_ALL, "");
    if (new_locale) {
        printf("切换后 locale: %s\n", new_locale);
        printf("本地格式下: %.2f\n", 1234.56);
    } else {
        printf("切换 locale 失败\n");
    }

    /* 恢复 C locale */
    setlocale(LC_ALL, "C");
    return 0;
}

localeconv —— 获取数字与货币格式信息

🎯 功能:返回一个指向 struct lconv 的指针,其中包含小数点、千位分隔符、货币符号、正负号位置等本地化格式信息。

💡 比喻:localeconv 像一本「本地书写规范手册」——告诉你这里的小数点用点还是逗号、货币符号写在金额前面还是后面。

🔧 声明:struct lconv *localeconv(void);

↩️ 返回值:返回指向静态 struct lconv 的指针,其成员在后续调用中可能被覆盖。

C
#include <stdio.h>
#include <locale.h>

int main() {
    struct lconv *lc = localeconv();

    printf("小数点字符: '%s'\n", lc->decimal_point);
    printf("千位分隔符: '%s'\n", lc->thousands_sep ? lc->thousands_sep : "");
    printf("本国货币符号: '%s'\n", lc->currency_symbol ? lc->currency_symbol : "");
    printf("国际货币符号: '%s'\n", lc->int_curr_symbol ? lc->int_curr_symbol : "");

    /* 切换到系统 locale 后再次查看 */
    setlocale(LC_ALL, "");
    lc = localeconv();
    printf("切换后小数点: '%s'\n", lc->decimal_point);

    setlocale(LC_ALL, "C");
    return 0;
}

👉下一节:15.6 setjmp.h →

▼15.6 <setjmp.h> —— 非局部跳转C 语言

是什么

📚 <setjmp.h> 提供 setjmp 和 longjmp 两个函数,实现跨函数的非局部跳转,常用于深层嵌套调用中的错误恢复或协程式控制流。

💡 生活比喻:setjmp/longjmp 像蹦床公园里的安全垫标记和急救按钮——setjmp 在地面铺好安全垫并记住位置,longjmp 从高处一键弹回安全垫。

⚠️ 注意事项:longjmp 跳转会跳过中间所有栈帧,局部变量值在跳转后可能不确定(C 标准未保证);不要用于替代正常 return;在 C++ 中 longjmp 不会调用析构函数,极不推荐与对象混用。

setjmp / longjmp —— 非局部跳转

🎯 功能:setjmp 保存当前调用环境到 jmp_buf;longjmp 恢复该环境,使程序回到 setjmp 处继续执行,并携带一个整型返回值。

💡 比喻:setjmp 像在游戏里设置一个存档点;longjmp 像读档——无论后面进了多少层关卡,瞬间回到存档点,并带回一个「读档原因码」。

🔧 声明:int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

↩️ 返回值:setjmp 首次调用返回 0,被 longjmp 弹回时返回 val(若 val 为 0 则替换为 1);longjmp 不返回。

C
#include <stdio.h>
#include <setjmp.h>

jmp_buf env;

void deep_call(int n) {
    printf("进入 deep_call, n=%d\n", n);
    if (n >= 3) {
        longjmp(env, 42);  /* 弹回 setjmp 处 */
    }
    deep_call(n + 1);
}

int main() {
    int ret = setjmp(env);
    if (ret == 0) {
        printf("首次设置跳转点\n");
        deep_call(0);
        printf("这行不会执行\n");
    } else {
        printf("从 longjmp 返回,返回值 = %d\n", ret);
    }
    return 0;
}

👉下一节:15.7 signal.h →

▼15.7 <signal.h> —— 信号处理C 语言

是什么

📚 <signal.h> 提供 C 程序与操作系统之间的「中断通知」机制,可捕获或发送信号,并在收到信号时执行自定义处理函数。

💡 生活比喻:signal.h 像办公室的火警与门铃系统——火警(SIGINT)响起时,你可以安排值班员去安全疏散(handler),也可以按默认流程处理;raise 则像手动按下测试按钮。

⚠️ 注意事项:信号处理函数中只能调用异步信号安全函数,避免 printf/malloc 等;SIGKILL 和 SIGSTOP 无法捕获或忽略;修改全局状态请用 volatile sig_atomic_t;信号处理是进程级行为。

signal —— 注册或恢复信号处理函数

🎯 功能:为指定信号 sig 安装新的处理函数 handler;handler 可以是自定义函数、SIG_DFL(默认处理)或 SIG_IGN(忽略)。

💡 比喻:signal 像给每类突发事件指定值班表——告诉门卫:看到火灾(SIGINT)就按 A 方案执行,看到下班铃(SIGTERM)就按 B 方案执行。

🔧 声明:void (*signal(int sig, void (*func)(int)))(int);

↩️ 返回值:返回该信号原先的处理函数指针;设置失败返回 SIG_ERR。

C
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

volatile sig_atomic_t g_stop = 0;

void sigint_handler(int sig) {
    (void)sig;
    g_stop = 1;
}

int main() {
    void (*old_handler)(int) = signal(SIGINT, sigint_handler);
    if (old_handler == SIG_ERR) {
        perror("signal");
        return 1;
    }

    printf("按 Ctrl+C 触发 SIGINT,程序将优雅退出...\n");
    while (!g_stop) {
        /* 实际运行时可按 Ctrl+C */
    }
    printf("收到 SIGINT,正在退出\n");

    /* 恢复默认处理 */
    signal(SIGINT, SIG_DFL);
    return 0;
}

raise —— 向当前进程发送信号

🎯 功能:主动给本进程发送指定信号,触发对应的信号处理函数;常用于测试 handler 或在代码中模拟信号。

💡 比喻:raise 像手动按下火警测试按钮——不真的发生火灾,但能验证值班员(handler)是否会按流程响应。

🔧 声明:int raise(int sig);

↩️ 返回值:成功返回 0,失败返回非零值。

C
#include <signal.h>
#include <stdio.h>

volatile sig_atomic_t g_got_sig = 0;

void handler(int sig) {
    g_got_sig = sig;
}

int main() {
    if (signal(SIGINT, handler) == SIG_ERR) {
        perror("signal");
        return 1;
    }

    printf("主动发送 SIGINT\n");
    raise(SIGINT);

    printf("收到信号编号: %d\n", g_got_sig);
    return 0;
}

👉下一节:15.8 stdarg.h →

▼15.8 <stdarg.h> —— 可变参数C 语言

是什么

📚 <stdarg.h> 让 C 函数接收可变数量和类型的参数,典型应用是自定义格式化输出、求和函数等。

💡 生活比喻:可变参数函数像一个「万能收纳盒」——你先约定好清单格式(比如第一个数告诉后面有几个数),然后按顺序把物品一件一件取出来。

⚠️ 注意事项:编译器不检查可变参数的类型和个数,必须通过命名参数约定;va_arg 的类型必须与实际传入类型严格匹配;va_start/va_end 必须成对出现;va_copy 用于需要遍历两遍的场景。

va_list / va_start / va_arg / va_copy / va_end —— 可变参数宏族

🎯 功能:va_list 保存参数状态;va_start 初始化;va_arg 按类型依次取参数;va_copy 复制状态;va_end 清理资源。

💡 比喻:va_list 像一盒索引卡片,va_start 把卡片翻到第一个位置,va_arg 一张张读出内容;va_copy 复印一份卡片盒,让两人分别清点;va_end 最后把卡片盒收回。

🔧 声明:va_list ap;
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_copy(va_list dest, va_list src);
void va_end(va_list ap);

↩️ 返回值:va_arg 返回下一个参数值(按指定类型);va_start/va_copy/va_end 无返回值。

C
#include <stdarg.h>
#include <stdio.h>

/* 计算 count 个 int 的平均值 */
double average(int count, ...) {
    va_list ap;
    va_start(ap, count);

    int sum = 0;
    for (int i = 0; i < count; i++) {
        sum += va_arg(ap, int);
    }

    va_end(ap);
    return count ? (double)sum / count : 0.0;
}

/* 先打印、再求和,演示 va_copy */
void print_and_sum(int count, ...) {
    va_list ap, ap_copy;
    va_start(ap, count);
    va_copy(ap_copy, ap);

    printf("参数: ");
    for (int i = 0; i < count; i++) {
        printf("%d ", va_arg(ap, int));
    }
    printf("\n");

    int sum = 0;
    for (int i = 0; i < count; i++) {
        sum += va_arg(ap_copy, int);
    }
    printf("总和: %d\n", sum);

    va_end(ap);
    va_end(ap_copy);
}

int main() {
    printf("平均值: %.2f\n", average(5, 1, 2, 3, 4, 5));
    print_and_sum(4, 10, 20, 30, 40);
    return 0;
}

👉下一节:15.9 stddef.h →

▼15.9 <stddef.h> —— 常用类型定义C 语言

是什么

📚 <stddef.h> 定义了 C 标准库中最常用的类型别名和宏:NULL、size_t、ptrdiff_t 以及计算成员偏移的 offsetof。

💡 生活比喻:stddef.h 像工具箱里的通用螺丝、螺母和垫片——虽然不起眼,但几乎所有「家具」(其他头文件)都要用到它们。

⚠️ 注意事项:size_t 是无符号类型,与有符号整数比较时小心环绕;C++ 中优先用 nullptr 替代 NULL;offsetof 仅对标准布局类型有效;很多头文件会间接包含 stddef.h。

NULL / size_t / ptrdiff_t / offsetof —— 常用类型与宏

🎯 功能:NULL 表示空指针常量;size_t 表示大小/计数的无符号类型;ptrdiff_t 表示两个指针相减的有符号结果;offsetof 返回结构体成员相对于起始地址的字节偏移。

💡 比喻:NULL 像「查无此人」的标记;size_t 像衡量箱子容积的尺子(永远非负);ptrdiff_t 像两把尺子刻度差的带符号读数;offsetof 则是家具安装说明书上「第 3 个孔距顶端多少厘米」的标注。

🔧 声明:#define NULL /* 实现定义的空指针常量 */
typedef size_t;
typedef ptrdiff_t;
#define offsetof(type, member) /* 成员偏移字节数 */

↩️ 返回值:NULL 是空指针常量;sizeof 返回 size_t;指针差返回 ptrdiff_t;offsetof 返回 size_t 类型的偏移量。

C
#include <stddef.h>
#include <stdio.h>

struct Point {
    int x;
    char y;
    double z;
};

int main() {
    int *p = NULL;
    printf("p == NULL: %s\n", p == NULL ? "是" : "否");

    size_t sz = sizeof(size_t);
    printf("sizeof(size_t) = %zu\n", sz);

    struct Point s;
    ptrdiff_t diff = (char *)&s.z - (char *)&s.x;
    printf("x 到 z 的字节距离: %td\n", diff);

    size_t off_x = offsetof(struct Point, x);
    size_t off_y = offsetof(struct Point, y);
    size_t off_z = offsetof(struct Point, z);
    printf("偏移: x=%zu, y=%zu, z=%zu\n", off_x, off_y, off_z);

    return 0;
}

👉下一节:15.10 fenv.h →

▼15.10 <fenv.h> —— 浮点环境控制(C99)C 语言

是什么

📚 <fenv.h>(C99)提供对浮点异常标志、舍入模式及浮点环境的查询和修改能力,适用于科学计算、金融精度控制等场景。

💡 生活比喻:fenv.h 像一台高精度计算器的「设置菜单」——你可以查看有没有出现除零或溢出(异常标志),也可以切换四舍五入还是向上取整(舍入模式),还能保存/恢复整套设置。

⚠️ 注意事项:修改浮点环境会影响整个线程的浮点运算;部分编译器需要 #pragma STDC FENV_ACCESS ON 或特殊编译选项;初学者日常使用较少,了解机制即可。

feclearexcept / feraiseexcept / fetestexcept —— 浮点异常标志

🎯 功能:feclearexcept 清除指定异常标志;feraiseexcept 人为触发指定异常;fetestexcept 检测当前已设置的异常标志。

💡 比喻:这组函数像汽车仪表盘上的故障灯系统:feclearexcept 是手动清零故障灯,feraiseexcept 是模拟一个故障看灯亮不亮,fetestexcept 则是读取当前有哪些灯在报警。

🔧 声明:int feclearexcept(int excepts);
int feraiseexcept(int excepts);
int fetestexcept(int excepts);

↩️ 返回值:成功返回 0,失败返回非零;fetestexcept 返回当前已设置的异常标志位集合。

C
#include <fenv.h>
#include <stdio.h>

int main() {
    /* 清除所有异常标志 */
    feclearexcept(FE_ALL_EXCEPT);

    /* 人为触发除零异常 */
    feraiseexcept(FE_DIVBYZERO);

    int flags = fetestexcept(FE_ALL_EXCEPT);
    if (flags & FE_DIVBYZERO) {
        printf("检测到 FE_DIVBYZERO\n");
    }

    /* 清除后再检查 */
    feclearexcept(FE_DIVBYZERO);
    flags = fetestexcept(FE_ALL_EXCEPT);
    printf("清除后标志: %d\n", flags);

    return 0;
}

fegetround / fesetround —— 浮点舍入模式

🎯 功能:fegetround 返回当前舍入模式;fesetround 设置新的舍入模式,如 FE_TONEAREST、FE_UPWARD、FE_DOWNWARD、FE_TOWARDZERO。

💡 比喻:舍入模式像超市收银台的「分币处理方式」:向最近靠拢(FE_TONEAREST)、总是向上收(FE_UPWARD)、总是向下找零(FE_DOWNWARD)或直接抹零(FE_TOWARDZERO)。

🔧 声明:int fegetround(void);
int fesetround(int rounding_mode);

↩️ 返回值:fegetround 返回当前舍入宏;fesetround 成功返回 0,失败返回非零。

C
#include <fenv.h>
#include <math.h>
#include <stdio.h>

int main() {
    int old_mode = fegetround();
    printf("默认舍入模式: %d\n", old_mode);

    /* 切换到向上舍入 */
    if (fesetround(FE_UPWARD) != 0) {
        printf("设置舍入模式失败\n");
        return 1;
    }

    double a = 2.1;
    double b = 2.9;
    printf("向上舍入: rint(%.1f)=%.1f, rint(%.1f)=%.1f\n", a, rint(a), b, rint(b));

    /* 恢复默认 */
    fesetround(old_mode);
    printf("恢复默认: rint(%.1f)=%.1f\n", a, rint(a));

    return 0;
}

fegetenv / fesetenv —— 保存与恢复浮点环境

🎯 功能:fegetenv 把当前浮点环境(异常标志、舍入模式等)保存到 fenv_t;fesetenv 恢复之前保存的环境。

💡 比喻:fegetenv/fesetenv 像相机拍照与还原设置——先拍一张「环境快照」,临时改参数做实验,实验完一键还原,保证不影响后续计算。

🔧 声明:int fegetenv(fenv_t *envp);
int fesetenv(const fenv_t *envp);

↩️ 返回值:成功返回 0,失败返回非零。

C
#include <fenv.h>
#include <math.h>
#include <stdio.h>

int main() {
    fenv_t env;

    /* 保存当前环境 */
    if (fegetenv(&env) != 0) {
        printf("保存环境失败\n");
        return 1;
    }

    /* 临时切换到向零舍入 */
    fesetround(FE_TOWARDZERO);
    printf("临时模式: rint(2.7)=%.1f\n", rint(2.7));

    /* 恢复之前保存的环境 */
    if (fesetenv(&env) != 0) {
        printf("恢复环境失败\n");
        return 1;
    }
    printf("恢复后: rint(2.7)=%.1f\n", rint(2.7));

    return 0;
}

👉下一节:15.11 inttypes.h →

▼15.11 <inttypes.h> —— 定宽整数格式化(C99)C 语言

是什么

📚 <inttypes.h> 为 <stdint.h> 中的定宽整数类型提供可移植的 printf/scanf 格式化宏,避免不同平台 %d/%ld 宽度不一致的问题。

💡 生活比喻:inttypes.h 像一套国际通用充电转接头——int32_t、int64_t 等是不同国家的插头,PRId32、PRIu64 等宏就是能适配各国插座的转换头。

⚠️ 注意事项:PRIdN/PRIuN 等宏展开为字符串字面量,必须和格式字符串用空格拼接;通常与 <stdint.h> 配合使用;跨平台代码不要直接用 %d 输出 int64_t。

PRIdN / PRIuN / PRIxN 等 —— 定宽整数打印宏

🎯 功能:PRIdN 输出有符号十进制,PRIuN 输出无符号十进制,PRIxN/PRIxFASTN/PRIxLEASTN 输出十六进制;N 可以是 8/16/32/64、MAX、PTR、FAST/LEAST 等。

💡 比喻:就像不同国家的电压插座,定宽整数不能直接插进 %d/%ld 的插孔;PRIdN/PRIuN/PRIxN 提供了正确规格的转接头,保证在哪里都能通电。

🔧 声明:#define PRId32 "d"
#define PRIu64 "llu"
#define PRIx16 "hx"
/* 更多 N 与 PTR/MAX/FAST/LEAST 变体 */

↩️ 返回值:宏展开为适合当前平台的 printf 格式说明符字符串。

C
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

int main() {
    int32_t a = -1234567890;
    uint32_t b = 3735928559u;       /* 0xDEADBEEF */
    int64_t c = -9223372036854775807LL;

    printf("int32: %" PRId32 "\n", a);
    printf("uint32 十六进制: %" PRIx32 "\n", b);
    printf("int64: %" PRId64 "\n", c);

    uintptr_t p = (uintptr_t)&a;
    printf("指针值: %" PRIxPTR "\n", p);

    return 0;
}

SCNdN / SCNuN 等 —— 定宽整数扫描宏

🎯 功能:SCNdN、SCNiN、SCNuN、SCNxN 等为 scanf 家族提供与定宽整数匹配的输入格式宏。

💡 比喻:如果说 PRIdN 是把数据「翻译出去」的转接头,SCNdN 就是把外部数据「翻译进来」的转接头,保证 int16_t/int64_t 等都能正确读入。

🔧 声明:#define SCNd32 "d"
#define SCNu64 "llu"
#define SCNx16 "hx"
/* 更多变体 */

↩️ 返回值:宏展开为适合当前平台的 scanf 格式说明符字符串。

C
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

int main() {
    const char *input = "2024 17 deadbeef";
    int16_t year = 0;
    uint16_t month = 0;
    uint32_t hex = 0;

    sscanf(input, "%" SCNd16 " %" SCNu16 " %" SCNx32,
           &year, &month, &hex);

    printf("解析结果: year=%" PRId16 ", month=%" PRIu16 ", hex=%" PRIx32 "\n",
           year, month, hex);

    return 0;
}

👉下一节:15.12 stdint.h →

▼15.12 <stdint.h> —— 固定宽度整数(C99)C 语言

是什么

📚 <stdint.h>(C99)定义了精确宽度、最小宽度、最快宽度整数类型以及对应的指针宽度整数类型和极限宏,解决 int/long 随平台变化的问题。

💡 生活比喻:stdint.h 像国际标准的螺丝规格——M3、M4、M5 的直径精确到毫米,不管你从哪家厂商采购,尺寸都一致。

⚠️ 注意事项:并非所有平台都保证 int8_t 等精确宽度类型存在;输出时请配合 <inttypes.h> 的宏;日常小程序用 int 即可,网络协议、文件格式和嵌入式场景优先使用定宽类型。

intN_t / uintN_t —— 精确宽度整数

🎯 功能:intN_t 和 uintN_t 提供恰好 N 位(N=8/16/32/64)的有符号/无符号整数类型。

💡 比喻:int32_t 像标称「正好 32 毫米」的螺栓,uint64_t 像「正好 64 毫米」的套筒——长度不随厂家(平台)变化。

🔧 声明:typedef int8_t / int16_t / int32_t / int64_t;
typedef uint8_t / uint16_t / uint32_t / uint64_t;

↩️ 返回值:编译期确定宽度的整数类型;sizeof(intN_t) * CHAR_BIT == N。

C
#include <stdint.h>
#include <stdio.h>

int main() {
    int32_t a = INT32_MAX;
    uint64_t b = UINT64_MAX;

    printf("sizeof(int32_t)  = %zu\n", sizeof(int32_t));
    printf("sizeof(uint64_t) = %zu\n", sizeof(uint64_t));
    printf("INT32_MAX = %d\n", a);
    printf("UINT64_MAX = %llu\n", (unsigned long long)b);

    return 0;
}

int_leastN_t / uint_leastN_t —— 最小宽度整数

🎯 功能:保证至少有 N 位宽度的整数类型,平台若不存在精确 N 位类型,会自动选择更大的兼容类型。

💡 比喻:最小宽度类型像「至少能装 32 升水的桶」——平台给 32 升桶就用 32 升,没有就给 64 升桶,保证不溢出。

🔧 声明:typedef int_least8_t / int_least16_t / int_least32_t / int_least64_t;
typedef uint_least8_t / uint_least16_t / uint_least32_t / uint_least64_t;

↩️ 返回值:满足最小宽度要求的整数类型。

C
#include <stdint.h>
#include <stdio.h>

int main() {
    uint_least32_t x = 3000000000u;
    int_least16_t y = -12345;

    printf("sizeof(uint_least32_t) = %zu\n", sizeof(uint_least32_t));
    printf("sizeof(int_least16_t)  = %zu\n", sizeof(int_least16_t));
    printf("x = %u, y = %d\n", (unsigned)x, (int)y);

    return 0;
}

int_fastN_t / uint_fastN_t —— 最快运算宽度整数

🎯 功能:至少 N 位、同时在该平台上运算速度最快的整数类型,常用于对性能敏感的循环计数器。

💡 比喻:int_fast8_t 像「至少装 8 升但倒水最快的水壶」——不追求最小体积,而追求在平台上倒起来最顺手。

🔧 声明:typedef int_fast8_t / int_fast16_t / int_fast32_t / int_fast64_t;
typedef uint_fast8_t / uint_fast16_t / uint_fast32_t / uint_fast64_t;

↩️ 返回值:满足最小宽度且被实现认为最快的整数类型。

C
#include <stdint.h>
#include <stdio.h>

int main() {
    int_fast8_t a = 127;
    uint_fast16_t b = 65000;

    printf("sizeof(int_fast8_t)   = %zu\n", sizeof(int_fast8_t));
    printf("sizeof(uint_fast16_t) = %zu\n", sizeof(uint_fast16_t));
    printf("a = %d, b = %u\n", (int)a, (unsigned)b);

    return 0;
}

intptr_t / uintptr_t / INTN_MAX / UINTN_MAX 等 —— 指针宽度整数与极限宏

🎯 功能:intptr_t / uintptr_t 分别是有符号/无符号的、能保存任意 void* 指针值的整数类型;INTN_MAX、INTN_MIN、UINTN_MAX 等给出各类型的极限值。

💡 比喻:intptr_t 像一把「和门框等宽的尺子」——门(指针)多宽,尺子就多宽,保证指针地址能完整存放再还原回去。

🔧 声明:typedef intptr_t / uintptr_t;
#define INT8_MAX / INT16_MAX / INT32_MAX / INT64_MAX
#define INT8_MIN / INT16_MIN / INT32_MIN / INT64_MIN
#define UINT8_MAX / UINT16_MAX / UINT32_MAX / UINT64_MAX

↩️ 返回值:指针宽度整数类型和编译期极限宏。

C
#include <stdint.h>
#include <stdio.h>

int main() {
    int local = 42;
    intptr_t p = (intptr_t)&local;
    uintptr_t up = (uintptr_t)&local;

    printf("地址 (intptr):  %td\n", p);
    printf("地址 (uintptr): %tu\n", up);

    printf("INT8_MAX  = %d,  INT8_MIN  = %d\n", INT8_MAX, INT8_MIN);
    printf("INT32_MAX = %d,  INT32_MIN = %d\n", INT32_MAX, INT32_MIN);
    printf("UINT8_MAX = %u\n", (unsigned)UINT8_MAX);

    return 0;
}

👉下一节:15.13 wchar.h →

▼15.13 <wchar.h> —— 宽字符工具C 语言

是什么

📚 <wchar.h> 提供宽字符(wchar_t)类型与相关 I/O、字符串操作函数,用于处理多语言字符集,弥补 char 只能保存单个字节的不足。

💡 生活比喻:如果把 char 看作只能装一件小物品的收纳盒,wchar_t 就是能装一套茶具的大礼盒;<wchar.h> 则提供了搬运、比较、展示这些礼盒的一整套工具。

⚠️ 注意事项:宽字符 IO 依赖当前 locale,示例中需调用 setlocale(LC_ALL, ""); 与本地编码同步;宽字符串字面量用 L"..." 前缀;宽字符与多字节转换要关注编码一致性问题。

wchar_t / wint_t / WEOF —— 宽字符类型与文件尾标记

🎯 功能:wchar_t 是宽字符类型,通常可保存一个 Unicode 码位;wint_t 是宽字符的整型提升类型;WEOF 是宽字符流的文件结束标记。

💡 比喻:wchar_t 像专用的宽信封,能装下普通信封装不下的字符;wint_t 是信封在运输过程中统一使用的托盘尺寸;WEOF 则是快递员用来表示「没有更多信件」的特殊暗号。

🔧 声明:typedef /* 实现定义 */ wchar_t;
typedef /* 实现定义 */ wint_t;
#define WEOF /* 实现定义的宽文件结束常量 */

↩️ 返回值:无;它们是类型别名与宏常量。

C
#include <wchar.h>
#include <locale.h>
#include <stdio.h>

int main() {
    setlocale(LC_ALL, "");

    wchar_t wc = L'中';
    wint_t wi = wc;

    wprintf(L"宽字符: %lc\n", wc);
    wprintf(L"wint_t 值: %d\n", (int)wi);
    wprintf(L"WEOF 值: %d\n", (int)WEOF);

    return 0;
}

wprintf / wscanf / fwprintf / fwscanf —— 宽字符格式化 I/O

🎯 功能:wprintf/wscanf 对宽字符标准流进行格式化输入输出;fwprintf/fwscanf 对指定的宽字符文件流进行操作。

💡 比喻:wprintf 像一位会多国语言的广播员,能把格式化的宽字符消息念给大家听;fwprintf 则是把同样内容录到指定磁带(文件)里的版本。

🔧 声明:int wprintf(const wchar_t *format, ...);
int wscanf(const wchar_t *format, ...);
int fwprintf(FILE *stream, const wchar_t *format, ...);
int fwscanf(FILE *stream, const wchar_t *format, ...);

↩️ 返回值:成功返回写入/读取的项目数;发生错误或到达文件尾返回 EOF/WEOF。

C
#include <wchar.h>
#include <locale.h>
#include <stdio.h>

int main() {
    setlocale(LC_ALL, "");

    int year = 2024;
    wchar_t name[64] = {0};

    wprintf(L"请输入你的名字和出生年份: ");
    /* 演示交互式读取;直接运行时可输入 "张三 2000" */
    fwscanf(stdin, L"%ls %d", name, &year);

    fwprintf(stdout, L"你好,%ls!你出生于 %d 年。\n", name, year);

    return 0;
}

fgetwc / fputwc / fgetws / fputws —— 宽字符流读写

🎯 功能:fgetwc/fputwc 读写单个宽字符;fgetws/fputws 读写宽字符串。

💡 比喻:fgetwc/fputwc 像从文件里一张一张抽取或放入宽字符卡片;fgetws/fputws 则是一把抓一叠卡片,效率更高。

🔧 声明:wint_t fgetwc(FILE *stream);
int fputwc(wchar_t c, FILE *stream);
wchar_t *fgetws(wchar_t *s, int n, FILE *stream);
int fputws(const wchar_t *s, FILE *stream);

↩️ 返回值:fgetwc 返回读取的宽字符或 WEOF;fputwc 返回写入的字符或 WEOF;fgetws 返回 s 或 NULL;fputws 成功返回非负值,失败返回 EOF。

C
#include <wchar.h>
#include <locale.h>
#include <stdio.h>

int main() {
    setlocale(LC_ALL, "");

    FILE *fp = fopen("wide_demo.txt", "w+, ccs=UTF-8");
    if (!fp) {
        wprintf(L"无法打开文件\n");
        return 1;
    }

    fputws(L"你好\n世界\n", fp);
    rewind(fp);

    wchar_t line[128];
    while (fgetws(line, 128, fp) != NULL) {
        fputws(line, stdout);
    }

    fclose(fp);
    return 0;
}

wcslen / wcscpy / wcsncpy / wcscmp / wcsstr —— 宽字符串操作

🎯 功能:wcslen 求长度;wcscpy/wcsncpy 复制;wcscmp 比较;wcsstr 查找子串。它们分别是宽字符版的 strlen/strcpy/strncpy/strcmp/strstr。

💡 比喻:这组函数像处理普通文本绳结的宽版工具箱:量长度、剪一段、接一段、比长短、找一段图案,全部换成能夹住宽字符的大号钳子。

🔧 声明:size_t wcslen(const wchar_t *s);
wchar_t *wcscpy(wchar_t *dest, const wchar_t *src);
wchar_t *wcsncpy(wchar_t *dest, const wchar_t *src, size_t n);
int wcscmp(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcsstr(const wchar_t *haystack, const wchar_t *needle);

↩️ 返回值:wcslen 返回字符数;wcscpy/wcsncpy 返回 dest;wcscmp 返回差值;wcsstr 返回首次出现位置或 NULL。

C
#include <wchar.h>
#include <locale.h>
#include <stdio.h>

int main() {
    setlocale(LC_ALL, "");

    wchar_t src[] = L"你好,宽字符世界!";
    wchar_t dest[128];

    wcscpy(dest, src);
    wprintf(L"复制后: %ls\n", dest);
    wprintf(L"长度: %zu\n", wcslen(dest));

    if (wcscmp(src, dest) == 0) {
        wprintf(L"两串相等\n");
    }

    wchar_t *pos = wcsstr(dest, L"宽字符");
    if (pos) {
        wprintf(L"找到子串: %ls\n", pos);
    }

    wchar_t buf[8] = {0};
    wcsncpy(buf, src, 7);
    buf[7] = L'\0';
    wprintf(L"受限复制: %ls\n", buf);

    return 0;
}

wcstombs / mbstowcs —— 宽字符与多字节字符串转换

🎯 功能:wcstombs 把宽字符串按当前 locale 编码转为多字节字符串;mbstowcs 则执行相反方向转换。

💡 比喻:wcstombs 像把宽信封里的信件重新折叠成普通信封能寄出的形式;mbstowcs 则是收到普通信件后,把它展开放回宽信封。

🔧 声明:size_t wcstombs(char *dest, const wchar_t *src, size_t n);
size_t mbstowcs(wchar_t *dest, const char *src, size_t n);

↩️ 返回值:返回实际转换的字节/宽字符数(不含终止符);如果无法转换则返回 (size_t)-1。

C
#include <wchar.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    setlocale(LC_ALL, "");

    wchar_t wstr[] = L"你好";
    char mbs[64];

    size_t n = wcstombs(mbs, wstr, sizeof(mbs));
    if (n == (size_t)-1) {
        printf("wcstombs 失败\n");
        return 1;
    }
    printf("多字节长度: %zu,内容: %s\n", n, mbs);

    wchar_t back[64];
    size_t m = mbstowcs(back, mbs, 64);
    if (m == (size_t)-1) {
        printf("mbstowcs 失败\n");
        return 1;
    }
    wprintf(L"还原回宽字符: %ls(共 %zu 个)\n", back, m);

    return 0;
}

👉下一节:15.14 wctype.h →

▼15.14 <wctype.h> —— 宽字符判断C 语言

是什么

📚 <wctype.h> 提供宽字符的分类判断和大小写转换函数,是 <ctype.h> 的宽字符版本,可正确处理 Unicode 等字符集。

💡 生活比喻:<ctype.h> 像只能识别英文字母的检查员,<wctype.h> 则像懂多国语言的签证官,能判断中文、日文、希腊文等字符的属性。

⚠️ 注意事项:同样依赖 locale 设置;宽字符参数类型为 wint_t;不要传入普通 char 值;判断函数的返回值是布尔意义的整数(0 为假,非 0 为真)。

iswalpha / iswdigit / iswspace / iswupper / iswlower —— 宽字符分类判断

🎯 功能:判断宽字符是否为字母、数字、空白、大写或小写;支持多语言字符。

💡 比喻:iswalpha 像问「你是字母吗?」;iswdigit 像问「你是数字吗?」;iswspace 像问「你是空格或换行吗?」;iswupper/iswlower 则分别检查是不是大写/小写签名。

🔧 声明:int iswalpha(wint_t wc);
int iswdigit(wint_t wc);
int iswspace(wint_t wc);
int iswupper(wint_t wc);
int iswlower(wint_t wc);

↩️ 返回值:若属于对应类别返回非零(真),否则返回 0。

C
#include <wctype.h>
#include <wchar.h>
#include <locale.h>
#include <stdio.h>

int main() {
    setlocale(LC_ALL, "");

    wchar_t samples[] = {L'A', L'9', L'中', L' ', L'Ä', L'ñ'};
    size_t n = sizeof(samples) / sizeof(samples[0]);

    for (size_t i = 0; i < n; i++) {
        wchar_t c = samples[i];
        wprintf(L"'%lc': alpha=%d digit=%d space=%d upper=%d lower=%d\n",
                c,
                iswalpha(c), iswdigit(c), iswspace(c),
                iswupper(c), iswlower(c));
    }

    return 0;
}

towupper / towlower —— 宽字符大小写转换

🎯 功能:towupper 把宽字符转为大写;towlower 转为小写;若字符本身无大小写区别则返回原字符。

💡 比喻:towupper 像把名片上的名字全部改成大写印刷体;towlower 则改成全小写手写体;没有大小写之分的符号保持原样。

🔧 声明:wint_t towupper(wint_t wc);
wint_t towlower(wint_t wc);

↩️ 返回值:返回转换后的宽字符;若无需转换或无法转换,返回原字符。

C
#include <wctype.h>
#include <wchar.h>
#include <locale.h>
#include <stdio.h>

int main() {
    setlocale(LC_ALL, "");

    wchar_t chars[] = {L'a', L'Z', L'ä', L'中', L'3'};
    size_t n = sizeof(chars) / sizeof(chars[0]);

    for (size_t i = 0; i < n; i++) {
        wchar_t c = chars[i];
        wprintf(L"%lc -> upper=%lc, lower=%lc\n",
                c, towupper(c), towlower(c));
    }

    return 0;
}

👉下一节:15.15 uchar.h →

▼15.15 <uchar.h> —— Unicode 字符转换(C11)C 语言

是什么

📚 <uchar.h>(C11)定义 char16_t 与 char32_t 类型,并提供多字节字符与 UTF-16/UTF-32 编码之间的可重启转换函数。

💡 生活比喻:char16_t/char32_t 像是专门保存不同「字符集装箱规格」的货架;mbrtoc16/c16rtomb 等函数则像在不同规格集装箱之间装卸货物的起重机,且能记住当前装到一半的状态。

⚠️ 注意事项:转换依赖当前 locale 与 C11 支持;转换状态 mbstate_t 必须正确初始化和维护;字符字面量前缀 u"..." 和 U"..." 分别对应 UTF-16 与 UTF-32;不完整序列会产生部分结果。

char16_t / char32_t —— UTF-16/UTF-32 字符类型

🎯 功能:char16_t 至少 16 位,用于表示 UTF-16 编码单元;char32_t 至少 32 位,通常可保存任意 Unicode 码位。

💡 比喻:char16_t 像能装下大多数常见字符(但 surrogate pair 需要两个)的 16 格抽屉;char32_t 像一整个 32 格大抽屉,任意一个 Unicode 字符都能单独放下。

🔧 声明:typedef uint_least16_t char16_t;
typedef uint_least32_t char32_t;

↩️ 返回值:无;它们是整数类型别名。

C
#include <uchar.h>
#include <stdio.h>

int main() {
    char16_t c16[] = u"Hello 中文";
    char32_t c32[] = U"Hello 中文";

    printf("sizeof(char16_t) = %zu\n", sizeof(char16_t));
    printf("sizeof(char32_t) = %zu\n", sizeof(char32_t));

    printf("UTF-16 序列前几个值: ");
    for (int i = 0; i < 8; i++) {
        printf("%04x ", (unsigned)c16[i]);
    }
    printf("\n");

    printf("UTF-32 序列前几个值: ");
    for (int i = 0; i < 8; i++) {
        printf("%08x ", (unsigned)c32[i]);
    }
    printf("\n");

    return 0;
}

mbrtoc16 / c16rtomb —— 多字节与 UTF-16 转换

🎯 功能:mbrtoc16 把多字节字符转换为 UTF-16 编码单元;c16rtomb 把 UTF-16 编码单元转回多字节字符。

💡 比喻:mbrtoc16 像把本地包装箱拆开,按 UTF-16 规格重新装箱;c16rtomb 则把 UTF-16 小盒还原成本地包装箱。

🔧 声明:size_t mbrtoc16(char16_t *pc16, const char *s, size_t n, mbstate_t *ps);
size_t c16rtomb(char *s, char16_t c16, mbstate_t *ps);

↩️ 返回值:mbrtoc16 返回消耗的字节数或特殊状态码;c16rtomb 返回写入的字节数,失败返回 (size_t)-1。

C
#include <uchar.h>
#include <locale.h>
#include <stdio.h>
#include <string.h>

int main() {
    setlocale(LC_ALL, "");

    const char *mbs = "中";
    mbstate_t state = {0};
    char16_t c16;

    size_t r = mbrtoc16(&c16, mbs, strlen(mbs), &state);
    if (r == (size_t)-1 || r == (size_t)-2) {
        printf("mbrtoc16 失败\n");
        return 1;
    }
    printf("mbrtoc16 返回 %zu,UTF-16 值: 0x%04x\n", r, (unsigned)c16);

    char out[16];
    memset(&state, 0, sizeof(state));
    size_t w = c16rtomb(out, c16, &state);
    if (w == (size_t)-1) {
        printf("c16rtomb 失败\n");
        return 1;
    }
    out[w] = '\0';
    printf("还原多字节: %s\n", out);

    return 0;
}

mbrtoc32 / c32rtomb —— 多字节与 UTF-32 转换

🎯 功能:mbrtoc32 把多字节字符转换为 UTF-32 码位;c32rtomb 把 UTF-32 码位转回多字节字符。

💡 比喻:mbrtoc32 像把多字节货物直接摊开成 32 位的「全展开视图」;c32rtomb 则把这张全展开视图按本地编码重新折叠打包。

🔧 声明:size_t mbrtoc32(char32_t *pc32, const char *s, size_t n, mbstate_t *ps);
size_t c32rtomb(char *s, char32_t c32, mbstate_t *ps);

↩️ 返回值:mbrtoc32 返回消耗的字节数或特殊状态码;c32rtomb 返回写入的字节数,失败返回 (size_t)-1。

C
#include <uchar.h>
#include <locale.h>
#include <stdio.h>
#include <string.h>

int main() {
    setlocale(LC_ALL, "");

    const char *mbs = "国";
    mbstate_t state = {0};
    char32_t c32;

    size_t r = mbrtoc32(&c32, mbs, strlen(mbs), &state);
    if (r == (size_t)-1 || r == (size_t)-2) {
        printf("mbrtoc32 失败\n");
        return 1;
    }
    printf("mbrtoc32 返回 %zu,UTF-32 码位: 0x%08x\n", r, (unsigned)c32);

    char out[16];
    memset(&state, 0, sizeof(state));
    size_t w = c32rtomb(out, c32, &state);
    if (w == (size_t)-1) {
        printf("c32rtomb 失败\n");
        return 1;
    }
    out[w] = '\0';
    printf("还原多字节: %s\n", out);

    return 0;
}

👉下一节:15.16 iso646.h →

▼15.16 <iso646.h> —— 运算符别名宏C 语言

是什么

📚 <iso646.h> 为 C 语言提供与 ISO 646 字符集兼容的逻辑/位运算符拼写别名,例如用 and 代替 &&、or 代替 ||、not 代替 !。

💡 生活比喻:某些键盘打不出 &&、|| 等符号,iso646.h 就像为这些键盘准备的「替代拼音输入法」——用 and、or、not 等英文单词也能写出同样的逻辑。

⚠️ 注意事项:这些宏在现代 ASCII/Unicode 键盘上已不常用;C++ 把它们作为关键字保留,无需包含头文件;在 C 代码中使用时需要包含本头文件。

and / or / not / bitand / bitor / compl / and_eq / or_eq / xor / xor_eq / not_eq —— 运算符拼写别名

🎯 功能:提供 &&、||、!、&、|、~、&=、|=、^、^=、!= 的英文单词替代宏,方便缺少相应符号的键盘输入。

💡 比喻:and、or、not 像逻辑门上的文字标签;bitand、bitor、compl 像位运算开关的说明牌;and_eq、or_eq、xor_eq 则是把开关状态写回寄存器的记录符号。

🔧 声明:#define and &&
#define or ||
#define not !
#define bitand &
#define bitor |
#define compl ~
#define and_eq &=
#define or_eq |=
#define xor ^
#define xor_eq ^=
#define not_eq !=

↩️ 返回值:无;它们展开为对应运算符。

C
#include <iso646.h>
#include <stdio.h>

int main() {
    int a = 5;   /* 二进制 0101 */
    int b = 3;   /* 二进制 0011 */

    if (a > 0 and b > 0) {
        printf("a 和 b 都大于 0\n");
    }

    if (not (a == b)) {
        printf("a 不等于 b\n");
    }

    int c = a bitand b;
    int d = a bitor b;
    int e = a xor b;
    int f = compl a;

    printf("bitand = %d, bitor = %d, xor = %d, compl = %d\n", c, d, e, f);

    c and_eq b;
    d or_eq b;
    e xor_eq b;
    printf("and_eq 后 c = %d, or_eq 后 d = %d, xor_eq 后 e = %d\n", c, d, e);

    return 0;
}

👉下一节:15.17 complex.h →

▼15.17 <complex.h> —— 复数运算(C99)C 语言

是什么

📚 <complex.h>(C99)为 C 提供复数类型 complex 与大量复数数学函数,包括实部/虚部提取、模长、辐角、指数、对数、幂和三角函数。

💡 生活比喻:复数像平面上的矢量箭头——既有长度(模)又有方向(辐角)<complex.h> 就是能对这种箭头做加减乘除、旋转、伸缩的专用计算尺。

⚠️ 注意事项:complex 是关键字宏,展开为 _Complex;I 展开为虚数单位;C++ 使用 std::complex,与本头文件不完全兼容;复数运算函数有 float/double/long double 版本。

complex / _Complex_I / I —— 复数类型与虚数单位

🎯 功能:complex 是 _Complex 的便捷宏;_Complex_I 是虚数单位常量;I 是更简短的虚数单位宏。

💡 比喻:complex 像一张能同时记录东西方向和南北方向的坐标纸;_Complex_I / I 则是坐标纸上标记「向北一个单位」的专用符号。

🔧 声明:#define complex _Complex
#define _Complex_I /* 虚数单位 */
#define I _Complex_I

↩️ 返回值:无;它们是类型宏与常量宏。

C
#include <complex.h>
#include <stdio.h>

int main() {
    double complex z1 = 1.0 + 2.0 * I;
    double complex z2 = 3.0 - 4.0 * I;

    double complex sum = z1 + z2;
    double complex prod = z1 * z2;

    printf("z1 = %.2f %+.2fi\n", creal(z1), cimag(z1));
    printf("z2 = %.2f %+.2fi\n", creal(z2), cimag(z2));
    printf("z1 + z2 = %.2f %+.2fi\n", creal(sum), cimag(sum));
    printf("z1 * z2 = %.2f %+.2fi\n", creal(prod), cimag(prod));

    return 0;
}

creal / cimag / carg / cabs —— 复数的实部、虚部、辐角与模

🎯 功能:creal 返回实部;cimag 返回虚部;carg 返回辐角(弧度);cabs 返回模长。

💡 比喻:creal/cimag 像把矢量箭头投影到 x 轴和 y 轴;carg 像量出箭头与 x 轴的夹角;cabs 则是用尺子量出箭头的长度。

🔧 声明:double creal(double complex z);
double cimag(double complex z);
double carg(double complex z);
double cabs(double complex z);

↩️ 返回值:creal/cimag/carg/cabs 分别返回 double 类型的实部、虚部、辐角和模长。

C
#include <complex.h>
#include <math.h>
#include <stdio.h>

int main() {
    double complex z = 3.0 + 4.0 * I;

    printf("z = %.2f %+.2fi\n", creal(z), cimag(z));
    printf("实部 creal(z) = %.2f\n", creal(z));
    printf("虚部 cimag(z) = %.2f\n", cimag(z));
    printf("模 cabs(z)    = %.2f\n", cabs(z));
    printf("辐角 carg(z)  = %.4f 弧度\n", carg(z));

    return 0;
}

cexp / clog / csqrt / cpow —— 复数指数、对数、开方与幂

🎯 功能:cexp 计算 e^z;clog 计算自然对数;csqrt 计算平方根;cpow 计算 z1^z2。

💡 比喻:cexp 像把复数矢量按指数规则螺旋放大或旋转;clog 是反向测量这个螺旋的「对数刻度」;csqrt 像找到原矢量角度减半、长度开方的位置;cpow 则是任意次方的组合操作。

🔧 声明:double complex cexp(double complex z);
double complex clog(double complex z);
double complex csqrt(double complex z);
double complex cpow(double complex x, double complex y);

↩️ 返回值:均返回 double complex 类型的结果。

C
#include <complex.h>
#include <stdio.h>

int main() {
    double complex z = 0.0 + 1.0 * I;  /* 纯虚数 i */

    double complex e = cexp(z);
    double complex l = clog(z);
    double complex s = csqrt(z);
    double complex p = cpow(z, z);

    printf("exp(i)  = %.4f %+.4fi\n", creal(e), cimag(e));
    printf("log(i)  = %.4f %+.4fi\n", creal(l), cimag(l));
    printf("sqrt(i) = %.4f %+.4fi\n", creal(s), cimag(s));
    printf("i^i     = %.4f %+.4fi\n", creal(p), cimag(p));

    return 0;
}

csin / ccos / ctan —— 复数三角函数

🎯 功能:csin、ccos、ctan 分别计算复数正弦、余弦和正切。

💡 比喻:如果把实数三角函数看作圆周运动的投影,复数三角函数就像把投影面扩展到整个复平面,sin/cos/tan 的结果可以同时具有实部和虚部。

🔧 声明:double complex csin(double complex z);
double complex ccos(double complex z);
double complex ctan(double complex z);

↩️ 返回值:均返回 double complex 类型的结果。

C
#include <complex.h>
#include <stdio.h>

int main() {
    double complex z = 1.0 + 0.5 * I;

    double complex s = csin(z);
    double complex c = ccos(z);
    double complex t = ctan(z);

    printf("sin(%.2f%+.2fi) = %.4f %+.4fi\n", creal(z), cimag(z), creal(s), cimag(s));
    printf("cos(%.2f%+.2fi) = %.4f %+.4fi\n", creal(z), cimag(z), creal(c), cimag(c));
    printf("tan(%.2f%+.2fi) = %.4f %+.4fi\n", creal(z), cimag(z), creal(t), cimag(t));

    return 0;
}

👉下一节:15.18 stdbool.h →

▼15.18 <stdbool.h> —— 布尔类型(C99)C 语言

是什么

📚 <stdbool.h>(C99)引入 bool、true、false 宏,使 C 代码能够以更直观的方式表达布尔逻辑,而不再依赖 int 和 0/1。

💡 生活比喻:在 stdbool.h 出现之前,C 程序员只能用整数 0/1 表示开关;stdbool.h 则给这个开关贴上了 true/false 标签,并提供了 bool 型的专用开关盒。

⚠️ 注意事项:bool 是 _Bool 的类型别名宏;true/false 展开为 1/0;sizeof(bool) 等于 sizeof(_Bool),通常为 1;C++ 把 bool、true、false 作为关键字,不需要也不应包含本头文件。

bool / true / false —— 布尔类型与字面量

🎯 功能:bool 表示布尔类型;true 表示真;false 表示假。

💡 比喻:bool 像一个只能显示两态的指示灯:true 是绿灯亮,false 是绿灯灭;它让程序中的「是/否」判断一目了然。

🔧 声明:#define bool _Bool
#define true 1
#define false 0

↩️ 返回值:无;它们是类型别名与常量宏。

C
#include <stdbool.h>
#include <stdio.h>

int main() {
    bool is_ok = true;
    bool has_error = false;

    if (is_ok && !has_error) {
        printf("状态正常\n");
    }

    int score = 85;
    bool passed = score >= 60;
    printf("score = %d, passed = %s\n", score, passed ? "true" : "false");

    printf("sizeof(bool) = %zu\n", sizeof(bool));

    return 0;
}

👉下一节:15.19 后续章节 →

▼15.19 <tgmath.h> —— 泛型数学宏(C99)C 语言

是什么

📚 <tgmath.h> 提供一组根据实参类型自动选择对应 <math.h> 或 <complex.h> 版本的泛型宏。

💡 生活比喻:像一位精通多国语言的翻译官,看到参数类型就知道该调用 float、double、long double 还是复数版本的函数。

⚠️ 注意事项:使用泛型宏时不需要手动区分实参类型,编译器会自动选择最合适的重载;若同时包含 <tgmath.h> 与 <math.h>,注意避免同名宏冲突。

sqrt / sin / cos / tan / log / exp 等泛型宏

🎯 功能:根据参数类型自动派发到底层函数,统一接口处理各种浮点与复数类型。

💡 比喻:像超市里的自助收银机,你把商品(参数)放上去,它自动识别条码并调用对应的价格系统。

🔧 声明:#include <tgmath.h>
double d = sqrt(2.0);
float f = sqrtf(2.0f); // 实际由泛型宏自动选择

↩️ 返回值:返回与参数类型相匹配的运算结果;对复数参数返回复数结果。

C
#include <stdio.h>
#include <tgmath.h>

int main() {
    double d = 2.0;
    float f = 2.0f;
    long double ld = 2.0L;

    printf("sqrt(double)  = %f\n", (double)sqrt(d));
    printf("sqrt(float)   = %f\n", (double)sqrt(f));
    printf("sqrt(ld)      = %Lf\n", sqrt(ld));
    printf("sin(pi/2)     = %f\n", (double)sin(3.1415926535 / 2));
    printf("log(e)        = %f\n", (double)log(2.718281828));
    printf("exp(1)        = %f\n", (double)exp(1));
    return 0;
}

👉下一节:15.20 <stdalign.h> —— 对齐控制宏(C11) →

▼15.20 <stdalign.h> —— 对齐控制宏(C11)C 语言

是什么

📚 <stdalign.h> 提供 alignas 与 alignof 两个宏,用于控制对象的对齐方式并查询类型的最小对齐要求。

💡 生活比喻:像仓库货架的格位规划:alignas 决定货物必须放在几倍宽的格子里,alignof 告诉你某种箱子最小需要几倍宽。

⚠️ 注意事项:alignas 只能放大对齐,不能缩小;非法或冲突的对齐请求会导致编译错误;在结构体中可用于减少填充或满足硬件要求。

alignas / alignof

🎯 功能:alignas 指定变量或类型的最小对齐字节数;alignof 返回某类型所需的对齐字节数。

💡 比喻:像城市地铁的换乘通道宽度,alignas 是施工时强行留出的宽度,alignof 则是根据客流量计算出的最小宽度。

🔧 声明:alignas(alignment) declaration;
size_t alignof(type);

↩️ 返回值:alignof 返回 size_t 类型的对齐值;alignas 本身无返回值,仅影响布局。

C
#include <stdio.h>
#include <stdalign.h>

int main() {
    alignas(64) char buffer[128];

    struct S {
        char c;
        alignas(8) int x;
    } s;

    printf("alignof(int)      = %zu\n", alignof(int));
    printf("alignof(double)   = %zu\n", alignof(double));
    printf("buffer address %% 64 = %zu\n", (size_t)buffer % 64);
    printf("sizeof(struct S)  = %zu\n", sizeof(s));
    return 0;
}

👉下一节:15.21 <stdnoreturn.h> —— 不返回函数标记(C11) →

▼15.21 <stdnoreturn.h> —— 不返回函数标记(C11)C 语言

是什么

📚 <stdnoreturn.h> 提供 noreturn 宏,用于显式标记那些不会把控制权交还给调用者的函数。

💡 生活比喻:像飞机上的紧急跳伞舱门:一旦跳下去就不会再回来,编译器也知道后续代码不会继续执行。

⚠️ 注意事项:若函数被标记为 noreturn 却实际返回,属于未定义行为;noreturn 常用于错误处理、终止程序或无限循环的函数。

noreturn

🎯 功能:放在函数声明前,告诉编译器该函数不会正常返回,可优化调用点后续代码并消除“函数可能无返回值”的警告。

💡 比喻:像一场没有返程票的旅行:机票上写着单程,家人和调度系统都会按“不回家”来安排后续计划。

🔧 声明:#include <stdnoreturn.h>
noreturn void fatal_error(const char *msg);

↩️ 返回值:noreturn 函数通常通过 exit、abort、longjmp 或无限循环结束;本身不改变返回值语义。

C
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>

noreturn void fatal_error(const char *msg) {
    fprintf(stderr, "FATAL: %s\n", msg);
    abort();
}

int main() {
    int ok = 0;
    if (!ok) {
        fatal_error("initialization failed");
    }
    printf("never reached\n");
    return 0;
}

👉下一节:15.22 <stdatomic.h> —— 原子操作(C11) →

▼15.22 <stdatomic.h> —— 原子操作(C11)C 语言

是什么

📚 <stdatomic.h> 提供原子类型与原子操作,用于多线程环境下安全地读写共享变量,避免数据竞争。

💡 生活比喻:像银行柜台的一次性业务窗口:同一时间只能有一位客户办理存取款,账目因此不会出现混乱。

⚠️ 注意事项:原子操作比互斥锁更轻量,但通常只适用于单个变量;复杂临界区或多步操作仍需使用互斥锁保护。

atomic_int / atomic_bool 等类型

🎯 功能:标准原子类型是 _Atomic(T) 的别名,如 atomic_int、atomic_bool、atomic_long,保证读写不可再分。

💡 比喻:像火车站的自动检票闸机:每次只能通过一个人,计数器对每个人只增减一次,不会重复也不会漏掉。

🔧 声明:_Atomic(int) counter;
atomic_int counter;

↩️ 返回值:原子变量本身不返回额外值,读取和写入都在内存顺序约束下完成。

C
#include <stdio.h>
#include <stdatomic.h>

int main() {
    atomic_int counter = 0;
    atomic_store(&counter, 10);

    int old = atomic_fetch_add(&counter, 5);
    int current = atomic_load(&counter);

    printf("old value     = %d\n", old);
    printf("current value = %d\n", current);

    atomic_bool flag = 0;
    bool prev = atomic_exchange(&flag, 1);
    printf("previous flag = %d, current flag = %d\n", (int)prev, (int)atomic_load(&flag));
    return 0;
}

atomic_load / atomic_store / atomic_fetch_add / atomic_exchange

🎯 功能:atomic_load 读取、atomic_store 写入、atomic_fetch_add 自增并返回旧值、atomic_exchange 交换新值并返回旧值。

💡 比喻:像保险箱的存取窗口:load 是查看余额,store 是存钱,fetch_add 是“先拿到旧钞再存入新钞”,exchange 是用新钞换走旧钞。

🔧 声明:int atomic_load(const atomic_int *obj);
void atomic_store(atomic_int *obj, int val);
int atomic_fetch_add(atomic_int *obj, int arg);
int atomic_exchange(atomic_int *obj, int val);

↩️ 返回值:atomic_load / atomic_store 返回或设置目标值;atomic_fetch_add / atomic_exchange 返回操作前的旧值。

C
#include <stdio.h>
#include <stdatomic.h>

int main() {
    atomic_int value = 100;

    int loaded = atomic_load(&value);
    atomic_store(&value, 200);
    int added = atomic_fetch_add(&value, 50);
    int swapped = atomic_exchange(&value, 999);

    printf("loaded  = %d\n", loaded);
    printf("added   = %d (value after add = %d)\n", added, atomic_load(&value));
    printf("swapped = %d\n", swapped);
    return 0;
}

👉下一节:15.23 <threads.h> —— 多线程(C11) →

▼15.23 <threads.h> —— 多线程(C11)C 语言

是什么

📚 <threads.h> 是 C11 提供的可移植线程接口,包含线程创建、同步、互斥锁与条件变量等基础能力。

💡 生活比喻:像一位乐队指挥同时安排多个乐手演奏:thrd_create 是点名上台,mtx_lock/unlock 是通过节拍器保持同步。

⚠️ 注意事项:部分旧编译器或平台对 <threads.h> 支持不完整,生产环境若受限可考虑 POSIX threads(pthread)。

thrd_create / thrd_join

🎯 功能:thrd_create 创建并启动一个新线程;thrd_join 阻塞等待指定线程结束并获取其返回值。

💡 比喻:像建筑工地派出一支施工队:thrd_create 是发派出工单,thrd_join 是在工地门口等他们收工并交回工具。

🔧 声明:int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
int thrd_join(thrd_t thr, int *res);

↩️ 返回值:成功返回 thrd_success;失败返回 thrd_error 等错误码;thrd_join 可通过 res 获得线程的返回码。

C
#include <stdio.h>
#include <threads.h>

int thread_func(void *arg) {
    int n = *(int *)arg;
    printf("thread received %d\n", n);
    return n * 2;
}

int main() {
    thrd_t t;
    int arg = 21;
    int result = 0;

    if (thrd_create(&t, thread_func, &arg) != thrd_success) {
        fprintf(stderr, "failed to create thread\n");
        return 1;
    }
    thrd_join(t, &result);
    printf("thread returned %d\n", result);
    return 0;
}

thrd_sleep / thrd_yield / thrd_exit

🎯 功能:thrd_sleep 让当前线程休眠指定时长;thrd_yield 提示调度器让出 CPU;thrd_exit 提前结束当前线程。

💡 比喻:像排练间隙的休息安排:thrd_sleep 是小睡片刻,thrd_yield 是主动把麦克风递给队友,thrd_exit 是直接退场。

🔧 声明:int thrd_sleep(const struct timespec *duration, struct timespec *remaining);
void thrd_yield(void);
void thrd_exit(int res);

↩️ 返回值:thrd_sleep 返回 0 表示睡到点,-1 表示被信号中断;thrd_yield 与 thrd_exit 不返回值。

C
#include <stdio.h>
#include <threads.h>

int sleepy_thread(void *arg) {
    (void)arg;
    struct timespec ts = {1, 0};
    thrd_sleep(&ts, NULL);
    thrd_yield();
    printf("sleepy thread finishing\n");
    thrd_exit(42);
}

int main() {
    thrd_t t;
    int res = 0;
    thrd_create(&t, sleepy_thread, NULL);
    thrd_join(t, &res);
    printf("result = %d\n", res);
    return 0;
}

mtx_init / mtx_lock / mtx_unlock

🎯 功能:mtx_init 初始化互斥锁;mtx_lock 加锁,若已被占用则阻塞;mtx_unlock 解锁,让其他线程进入临界区。

💡 比喻:像公共卫生间的一把锁:mtx_init 是安装门锁,mtx_lock 是进门反锁,mtx_unlock 是用完开门。

🔧 声明:int mtx_init(mtx_t *mtx, int type);
int mtx_lock(mtx_t *mtx);
int mtx_unlock(mtx_t *mtx);

↩️ 返回值:成功返回 thrd_success;mtx_lock 阻塞直到获得锁;使用完毕后应调用 mtx_destroy 释放资源。

C
#include <stdio.h>
#include <threads.h>

static mtx_t lock;
static int shared = 0;

int worker(void *arg) {
    (void)arg;
    for (int i = 0; i < 100000; i++) {
        mtx_lock(&lock);
        shared++;
        mtx_unlock(&lock);
    }
    return 0;
}

int main() {
    mtx_init(&lock, mtx_plain);
    thrd_t t1, t2;
    thrd_create(&t1, worker, NULL);
    thrd_create(&t2, worker, NULL);
    thrd_join(t1, NULL);
    thrd_join(t2, NULL);
    printf("shared = %d\n", shared);
    mtx_destroy(&lock);
    return 0;
}

👉下一节:16.1 <assert.h> —— 断言调试 →

▼16.1 <assert.h> —— 断言调试C 语言

是什么

📚 <assert.h> 提供 assert 宏,用于在调试阶段检查程序假设是否成立;定义 NDEBUG 宏后所有断言会被禁用。

💡 生活比喻:像电路中的保险丝:条件成立时安静工作,一旦条件错误就立即熔断并报警,帮助定位问题。

⚠️ 注意事项:assert 只在未定义 NDEBUG 时生效,因此不能用它处理可预期的运行时错误;发布版本中应通过错误码或日志处理异常。

assert

🎯 功能:assert(expression) 在表达式为假时向标准错误输出诊断信息并调用 abort 终止程序。

💡 比喻:像餐厅后厨的卫生检查表:每天打烊前逐项勾选,若发现某一项未达标,当晚就不能结业。

🔧 声明:#include <assert.h>
void assert(int expression);

↩️ 返回值:断言宏无返回值;触发时打印文件名、行号和失败表达式,然后终止程序。

C
#include <stdio.h>
#include <assert.h>

int divide(int a, int b) {
    assert(b != 0);
    return a / b;
}

int main() {
    printf("10 / 2 = %d\n", divide(10, 2));
    // 取消下一行注释,断言会触发并终止程序
    // printf("10 / 0 = %d\n", divide(10, 0));
    return 0;
}

NDEBUG

🎯 功能:NDEBUG 是一个控制宏:在包含 <assert.h> 之前定义它,assert 会被展开为空语句,从而消除调试检查。

💡 比喻:像演出前的彩排标记:正式演出时把“彩排中”的牌子翻过去,所有断言检查就全部失效。

🔧 声明:#define NDEBUG
#include <assert.h>

↩️ 返回值:无返回值;定义 NDEBUG 后 assert 表达式不会被求值,因此不要在其中放置副作用代码。

C
#include <stdio.h>
#define NDEBUG
#include <assert.h>

int main() {
    int x = 0;
    assert(x == 1);  // 被禁用,不会触发
    printf("assertion disabled, continuing...\n");
    return 0;
}