C 语言标准库头文件速查手册
📖 目录
第四章 字符串与字符处理
4.3 <ctype.h> —— 字符判断与转换
4.4 <string.h> —— C 风格字符串操作
第五章 数学与数值
5.1 <math.h> —— 数学函数
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 个代码示例
第四章 字符串与字符处理

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

是什么

一组函数,判断单个字符是什么类型,或转换大小写。

常用函数(每个函数附完整示例)

isalpha(c) —— 是否字母(a-z, A-Z)

#include <ctype.h>
#include <stdio.h>
int main() {
    char c = 'A';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

isdigit(c) —— 是否数字(0-9)

#include <ctype.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

isalnum(c) —— 是否字母或数字

#include <ctype.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

isspace(c) —— 是否空白(空格/Tab/换行)

#include <ctype.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

isupper(c) —— 是否大写字母

#include <ctype.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

islower(c) —— 是否小写字母

#include <ctype.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

ispunct(c) —— 是否标点

#include <ctype.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

isprint(c) —— 是否可打印

#include <ctype.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

toupper(c) —— 转大写

#include <ctype.h>
#include <stdio.h>
int main() {
    char d = toupper('a');  // d = 'A'
    printf("%c\n", d);
    return 0;
}

tolower(c) —— 转小写

#include <ctype.h>
#include <stdio.h>
int main() {
    char d = tolower('A');  // d = 'a'
    printf("%c\n", d);
    return 0;
}
#include <ctype.h>
#include <stdio.h>
int main() {
    char c = 'A';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    char d = tolower(c);  // d = 'a'
    printf("转换后:%c\n", d);
    return 0;
}

isblank(字符) —— 是否为空白字符(空格或制表符,C99)

#include <ctype.h>
#include <stdio.h>
int main() {
    printf("%d\n", isblank(' '));
    printf("%d\n", isblank('\t'));
    printf("%d\n", isblank('a'));
    return 0;
}

iscntrl(字符) —— 是否为控制字符(如\n、\t、\r等)

isgraph(字符) —— 是否为可打印非空白字符(有可见图形)

isxdigit(字符) —— 是否为十六进制数字(0-9, a-f, A-F)

#include <ctype.h>
#include <stdio.h>
int main() {
    printf("%d\n", isxdigit('A'));
    printf("%d\n", isxdigit('g'));
    printf("%d\n", isgraph('!'));
    return 0;
}
⚠️ 参数是 int,char 为负数时可能出问题,建议转 unsigned char:

1. isalpha((unsigned char)c)

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

是什么

📚 C 语言字符串函数,操作以 '\0' 结尾的字符数组。

📚 C++ 推荐用 string,但和 C 代码交互时还会遇到。

常用函数(每个函数附完整示例)

strlen(s) —— 字符串长度(不含 '\0')

#include <string.h>
#include <stdio.h>
int main() {
    char src[50] = "Hello";
    int n = strlen(src);  // n = 5
    printf("长度:%d\n", n);
    return 0;
}

strcpy(dest, src) —— 复制(不安全,可能越界!)

#include <string.h>
#include <stdio.h>
int main() {
    char src[50] = "Hello", dest[50];
    strcpy(dest, src);  // dest = "Hello"
    printf("%d\n", dest);
    return 0;
}

strncpy(dest, src, n) —— 最多复制 n 个(相对安全)

#include <string.h>
#include <stdio.h>
int main() {
    char src[50] = "Hello", dest[50] = {0};
    strncpy(dest, src, 3);  // dest = "Hel"
    printf("%d\n", dest);
    return 0;
}

strcat(dest, src) —— 拼接

#include <string.h>
#include <stdio.h>
int main() {
    char dest[50] = "Hello";
    strcat(dest, " World");  // dest = "Hello World"
    printf("%s\n", dest);
    return 0;
}

strncat(dest, src, n) —— 最多拼 n 个

#include <string.h>
#include <stdio.h>
int main() {
    char dest[50] = "Hello";
    strncat(dest, " ABC", 2);  // 拼 " A"
    printf("%s\n", dest);
    return 0;
}

strcmp(s1, s2) —— 比较,相等返回0

#include <string.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

strncmp(s1, s2, n) —— 比较前 n 个

#include <string.h>
#include <stdio.h>
int main() {
    int r = strncmp("abc", "abd", 2);  // 前2个相等,返回0
    printf("比较结果:%d\n", r);
    return 0;
}

strchr(s, c) —— 字符 c 首次出现位置

#include <string.h>
#include <stdio.h>
int main() {
    char* p = strchr("Hello", 'l');  // 指向第一个'l'
    printf("%d\n", p);
    return 0;
}

strrchr(s, c) —— 字符 c 末次出现位置

#include <string.h>
#include <stdio.h>
int main() {
    char* p = strrchr("Hello", 'l');  // 指向最后一个'l'
    printf("%d\n", p);
    return 0;
}

strstr(s1, s2) —— 子串 s2 在 s1 中位置

#include <string.h>
#include <stdio.h>
int main() {
    char* p = strstr("Hello World", "World");  // 指向"World"
    printf("%d\n", p);
    return 0;
}

内存操作

memset(ptr, val, n) —— n 字节设为 val

#include <string.h>
#include <stdio.h>
int main() {
    int arr[5];
    memset(arr, 0, sizeof(arr));  // 全部清零
    printf("%d\n", arr[0]);
    return 0;
}

memcpy(dest, src, n) —— 复制 n 字节(不允许重叠)

#include <string.h>
#include <stdio.h>
int main() {
    int a[5]={1,2,3,4,5}, b[5];
    memcpy(b, a, sizeof(a));
    printf("%d\n", b[0]);
    return 0;
}

memmove(dest, src, n) —— 复制 n 字节(允许重叠,更安全)

#include <string.h>
#include <stdio.h>
int main() {
    int arr[5] = {1,2,3,4,5};
    memmove(arr+1, arr, 4*sizeof(int));  // 向后移动
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    printf("\n");
    return 0;
}

memcmp(p1, p2, n) —— 比较前 n 字节

#include <string.h>
#include <stdio.h>
int main() {
    int a[5]={1,2,3,4,5}, b[5]={1,2,3,4,5};
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

strerror(错误码) —— 把错误码转成人类可读的字符串(常配合 errno 使用)

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdio.h>
int main() {
    FILE* fp = fopen("不存在的文件.txt", "r");
    if (fp == NULL) {
        printf("错误:%d\n", strerror(errno));
    }
    return 0;
}

strtok(字符串, 分隔符) —— 字符串分割(注意:会修改原字符串,且不可重入)

#include <string.h>
#include <stdio.h>
int main() {
    char str[] = "apple,banana,orange";
    char* token = strtok(str, ",");
    while (token != NULL) {
        printf("%d\n", token);
        token = strtok(NULL, ",");
    }
    return 0;
}

memchr(指针, 字符, 字节数) —— 在内存中查找字符首次出现位置

#include <string.h>
#include <stdio.h>
int main() {
    char str[] = "Hello World";
    char* p = (char*)memchr(str, 'W', strlen(str));
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

strspn(字符串1, 字符串2) —— 字符串1开头连续包含字符串2中字符的个数

strcspn(字符串1, 字符串2) —— 字符串1开头连续不包含字符串2中字符的个数

strpbrk(字符串1, 字符串2) —— 查找字符串2中任一字符在字符串1中首次出现的位置

⚠️ strcpy、strcat 不检查缓冲区大小,易造成缓冲区溢出。

1. C++ 中尽量用 string,不要用这些函数。

第五章 数学与数值

5.1 <math.h> —— 数学函数C 语言

常用函数一览(每个函数附完整示例)

【基本】

abs(x) / fabs(x) —— 绝对值(fabs 专门用于浮点)

#include <math.h>
#include <stdio.h>
int main() {
    int a = abs(-5);      // a = 5
    double b = fabs(-3.14); // b = 3.14
    printf("a=%d, b=%lf\n", a, b);
    return 0;
}

fmod(x, y) —— 浮点数取余

#include <math.h>
#include <stdio.h>
int main() {
    double r = fmod(5.5, 2.0);  // r = 1.5
    printf("r=%lf\n", r);
    return 0;
}

【幂与指数】

pow(x, y) —— x 的 y 次方

#include <math.h>
#include <stdio.h>
int main() {
    double r = pow(2, 10);  // r = 1024
    printf("r=%lf\n", r);
    return 0;
}

sqrt(x) —— 平方根

#include <math.h>
#include <stdio.h>
int main() {
    double r = sqrt(2);    // r ≈ 1.414
    printf("r=%lf\n", r);
    return 0;
}

cbrt(x) —— 立方根(C99)

#include <math.h>
#include <stdio.h>
int main() {
    double r = cbrt(27);   // r = 3
    printf("r=%lf\n", r);
    return 0;
}

exp(x) —— e 的 x 次方

#include <math.h>
#include <stdio.h>
int main() {
    double r = exp(1);     // r ≈ 2.718
    printf("r=%lf\n", r);
    return 0;
}

exp2(x) —— 2 的 x 次方(C99)

#include <math.h>
#include <stdio.h>
int main() {
    double r = exp2(10);   // r = 1024
    printf("r=%lf\n", r);
    return 0;
}

log(x) —— 自然对数(ln)

#include <math.h>
#include <stdio.h>
int main() {
    double r = log(2.718); // r ≈ 1
    printf("r=%lf\n", r);
    return 0;
}

log2(x) —— 以2为底(C99)

#include <math.h>
#include <stdio.h>
int main() {
    double r = log2(1024); // r = 10
    printf("r=%lf\n", r);
    return 0;
}

log10(x) —— 以10为底

#include <math.h>
#include <stdio.h>
int main() {
    double r = log10(100); // r = 2
    printf("r=%lf\n", r);
    return 0;
}

【三角函数】(参数是弧度,不是角度!)

sin / cos / tan

#include <math.h>
#include <stdio.h>
int main() {
    double s = sin(3.14159/2);  // ≈ 1
    double c = cos(0);          // = 1
    double t = tan(0);          // = 0
    printf("sin=%lf, cos=%lf, tan=%lf\n", s, c, t);
    return 0;
}

asin / acos / atan —— 反三角

#include <math.h>
#include <stdio.h>
int main() {
    double a = asin(1);    // ≈ π/2
    double b = acos(0);    // ≈ π/2
    printf("asin=%lf, acos=%lf\n", a, b);
    return 0;
}

atan2(y, x) —— 反正切(能判断象限)

#include <math.h>
#include <stdio.h>
int main() {
    double a = atan2(1, 1);  // ≈ π/4
    printf("atan2=%lf\n", a);
    return 0;
}

角度转弧度:弧度 = 角度 * π / 180

【双曲函数】

sinh / cosh / tanh

#include <math.h>
#include <stdio.h>
int main() {
    double s = sinh(0);    // = 0
    double c = cosh(0);    // = 1
    printf("sinh=%lf, cosh=%lf\n", s, c);
    return 0;
}

asinh / acosh / atanh(C99)

#include <math.h>
#include <stdio.h>
int main() {
    double a = asinh(0);   // = 0
    printf("asinh=%lf\n", a);
    return 0;
}

【取整】

floor(x) —— 向下取整

#include <math.h>
#include <stdio.h>
int main() {
    printf("floor(3.7)=%d\n", floor(3.7));
    printf("floor(-3.7)=%d\n", floor(-3.7));
    return 0;
}

ceil(x) —— 向上取整

#include <math.h>
#include <stdio.h>
int main() {
    printf("ceil(3.2)=%d\n", ceil(3.2));
    printf("ceil(-3.2)=%d\n", ceil(-3.2));
    return 0;
}

round(x) —— 四舍五入

#include <math.h>
#include <stdio.h>
int main() {
    printf("round(3.5)=%d\n", round(3.5));
    printf("round(-3.5)=%d\n", round(-3.5));
    return 0;
}

trunc(x) —— 向零取整

#include <math.h>
#include <stdio.h>
int main() {
    printf("trunc(3.7)=%d\n", trunc(3.7));
    printf("trunc(-3.7)=%d\n", trunc(-3.7));
    return 0;
}

【其他】

min(x,y) / max(x,y) —— 两数最小/最大(注意:在 <algorithm> 中,不在 <math.h>)

📌 C++ 中 std::min/std::max 定义在 <algorithm> 头文件中,C 语言没有这两个函数

/* 警告:此示例使用了 C++ 特有语法,无法自动转换为纯 C 代码。
   C 语言没有对应的等价写法,请参考 C++ 版本理解逻辑,
   如需 C 实现,需手动重写。*/
/* 原始 C++ 代码:
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
    int m = min(3, 5);  // 3
    int M = max(3, 5);  // 5
    cout << "min=" << m << ", max=" << M << endl;
    return 0;
}
*/

hypot(x, y) —— sqrt(x²+y²),斜边

#include <math.h>
#include <stdio.h>
int main() {
    double h = hypot(3, 4);  // h = 5
    printf("hypot=%lf\n", h);
    return 0;
}

isnan(x) —— 是否非数(NaN)

#include <math.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

isinf(x) —— 是否无穷大

#include <math.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

isfinite(x) —— 是否有限值

#include <math.h>
#include <stdio.h>
int main() {
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}
#include <math.h>
#include <stdio.h>
int main() {
    printf("%d\n", sin(3.14159));
    printf("%d\n", sqrt(2));
    printf("%d\n", pow(2, 10));
    printf("%d\n", floor(3.9));
    return 0;
}
⚠️ 三角函数用弧度不是角度。

1. M_PI(圆周率)非标准,部分编译器需 #define _USE_MATH_DEFINES。

2. C++20 可用 <numbers> 中的 std::numbers::pi。

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

常用函数(每个函数附完整示例)

rand() —— 生成 0 到 RAND_MAX 的随机整数

#include <stdlib.h>
#include <stdio.h>
int main() {
    int r = rand();
    printf("随机数:%d\n", r);
    return 0;
}

srand(seed) —— 设置随机数种子(不设则每次序列相同)

#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main() {
    srand(time(0));  // 用时间播种,需 <time.h>
    printf("%d\n", rand());
    return 0;
}

生成指定范围

#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main() {
    srand(time(0));
    int r1 = rand() % 100;          // 0~99
    int r2 = rand() % 100 + 1;      // 1~100
    double r3 = (double)rand() / RAND_MAX;  // 0.0~1.0
    printf("r1=%d, r2=%d, r3=%lf\n", r1, r2, r3);
    return 0;
}

abs / labs / llabs —— 整数绝对值

#include <stdlib.h>
#include <stdio.h>
int main() {
    int a = abs(-5);        // 5
    long b = labs(-100L);   // 100
    printf("a=%d, b=%ld\n", a, b);
    return 0;
}

div(x, y) —— 整数除法,返回商和余数

#include <stdlib.h>
#include <stdio.h>
int main() {
    div_t r = div(10, 3);   // r.quot=3, r.rem=1
    printf("商=%d, 余数=%d\n", r.quot, r.rem);
    return 0;
}

exit(0) —— 立即退出程序

#include <stdlib.h>
#include <stdio.h>
int main() {
    printf("准备退出\n");
    exit(0);  // 正常退出
    printf("不会执行到这里\n");
    return 0;
}

abort() —— 异常终止

#include <stdlib.h>
#include <stdio.h>
int main() {
    // abort();  // 异常终止,不做清理(取消注释可测试)
    printf("abort会异常终止程序\n");
    return 0;
}

system("命令") —— 执行系统命令

#include <stdlib.h>
#include <stdio.h>
int main() {
    // system("pause");  // Windows下暂停,等待按键
    printf("system可执行系统命令\n");
    return 0;
}

malloc(字节数) —— 动态分配内存(返回 void*,失败返回 NULL)

#include <stdlib.h>
#include <stdio.h>
int main() {
    int* arr = (int*)malloc(5 * sizeof(int));  // 分配5个int的空间
    if (arr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
        printf("%d ", arr[i]);
    }
    printf("\n");
    free(arr);  // 释放内存
    return 0;
}

calloc(个数, 大小) —— 分配内存并初始化为0

#include <stdlib.h>
#include <stdio.h>
int main() {
    int* arr = (int*)calloc(5, sizeof(int));  // 分配5个int,全部初始化为0
    if (arr) {
        for (int i = 0; i < 5; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
        free(arr);
    }
    return 0;
}

realloc(指针, 新大小) —— 重新分配内存(可扩大或缩小)

#include <stdlib.h>
#include <stdio.h>
int main() {
    int* arr = (int*)malloc(3 * sizeof(int));
    arr[0] = 1; arr[1] = 2; arr[2] = 3;
    // 扩大到5个int
    int* new_arr = (int*)realloc(arr, 5 * sizeof(int));
    if (new_arr) {
        arr = new_arr;
        arr[3] = 4; arr[4] = 5;
        /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
        printf("\n");
    }
    free(arr);
    return 0;
}

free(指针) —— 释放动态分配的内存

见上面 malloc 示例。注意:free(NULL) 是安全的,什么都不做。

atoi(字符串) —— 字符串转 int

#include <stdlib.h>
#include <stdio.h>
int main() {
    int num = atoi("12345");
    printf("atoi(\"12345\") = %d\n", num);
    int num2 = atoi("   -42abc");
    printf("atoi(\"   -42abc\") = %d\n", num2);
    return 0;
}

atof(字符串) —— 字符串转 double

#include <stdlib.h>
#include <stdio.h>
int main() {
    double pi = atof("3.14159");
    printf("atof(\"3.14159\") = %lf\n", pi);
    return 0;
}

atol(字符串) —— 字符串转 long

atoll(字符串) —— 字符串转 long long(C99)

strtol(字符串, &结束指针, 进制) —— 字符串转 long(更安全,可检测错误)

#include <stdlib.h>
#include <stdio.h>
int main() {
    char str[] = "12345abc";
    char* end;
    long num = strtol(str, &end, 10);  // 10进制
    printf("数字部分:%ld\n", num);
    printf("剩余部分:%d\n", end);
    return 0;
}

strtod(字符串, &结束指针) —— 字符串转 double(更安全)

用法类似 strtol。

getenv("变量名") —— 获取环境变量值

#include <stdlib.h>
#include <stdio.h>
int main() {
    const char* path = getenv("PATH");
    if (path) {
        printf("PATH = %d\n", path);
    }
    return 0;
}

qsort(数组, 个数, 大小, 比较函数) —— 快速排序

#include <stdlib.h>
#include <stdio.h>
int compare(const void* a, const void* b) {
    return *(int*)a - *(int*)b;  // 升序
}
int main() {
    int arr[] = {5, 2, 8, 1, 9, 3};
    int n = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, n, sizeof(int), compare);
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    printf("\n");
    return 0;
}

exit(状态码) —— 正常终止程序(状态码0表示成功)

_Exit(状态码) —— 立即终止,不做清理(C99)

atexit(函数) —— 注册程序退出时调用的函数

第六章 时间与日期

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

常用函数(每个函数附完整示例)

time(&t) —— 当前时间(time_t,1970年至今秒数)

#include <time.h>
#include <stdio.h>
int main() {
    time_t now = time(0);  // 或 time(&t);
    printf("时间戳:%d\n", now);
    return 0;
}

clock() —— 程序CPU时间(用于计时)

#include <time.h>
#include <stdio.h>
int main() {
    clock_t c0 = clock();
    // ... 代码 ...
    int sum = 0;
    for (int i = 0; i < 1000000; i++) sum += i;
    clock_t c1 = clock();
    double sec = (double)(c1-c0) / CLOCKS_PER_SEC;
    printf("耗时:%lf秒\n", sec);
    return 0;
}

difftime(t1, t0) —— 时间差(秒)

#include <time.h>
#include <stdio.h>
int main() {
    time_t t0 = time(0);
    // ... 一些操作 ...
    time_t t1 = time(0);
    double diff = difftime(t1, t0);
    printf("时间差:%lf秒\n", diff);
    return 0;
}

localtime(&t) —— 转本地时间 struct tm

#include <time.h>
#include <stdio.h>
int main() {
    time_t t = time(0);
    struct tm* lt = localtime(&t);
    printf("年份:%d\n", lt->tm_year + 1900);
    printf("月份:%d\n", lt->tm_mon + 1);
    return 0;
}

gmtime(&t) —— 转 UTC 时间 struct tm

#include <time.h>
#include <stdio.h>
int main() {
    time_t t = time(0);
    struct tm* gt = gmtime(&t);
    printf("UTC小时:%d\n", gt->tm_hour);
    return 0;
}

asctime(&tm) —— 转字符串

#include <time.h>
#include <stdio.h>
int main() {
    time_t t = time(0);
    printf("%d", asctime(localtime(&t)));
    return 0;
}

ctime(&t) —— 直接转本地时间字符串

#include <time.h>
#include <stdio.h>
int main() {
    time_t t = time(0);
    printf("%d", ctime(&t));
    return 0;
}

strftime(buf,size,format,&tm) —— 格式化时间字符串

#include <time.h>
#include <stdio.h>
int main() {
    time_t t = time(0);
    char buf[100];
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&t));
    // 格式:%Y年 %m月 %d日 %H时 %M分 %S秒
    printf("%s\n", buf);
    return 0;
}

struct tm 结构体

tm_year 年(从1900开始,要+1900)

tm_mon 月(0-11,要+1)

tm_mday 日(1-31)

tm_hour 时(0-23)

tm_min 分(0-59)

tm_sec 秒(0-60)

tm_wday 星期(0-6,0是周日)

#include <time.h>
#include <stdio.h>
int main() {
    time_t now = time(0);
    printf("%d", ctime(&now));
    return 0;
}

mktime(&tm结构) —— 将 tm 结构转为 time_t 时间戳(同时规范化tm结构)

#include <time.h>
#include <stdio.h>
int main() {
    struct tm t = {};
    t.tm_year = 2024 - 1900;  // 年(从1900开始)
    t.tm_mon = 0;             // 月(0-11,0是1月)
    t.tm_mday = 1;            // 日(1-31)
    t.tm_hour = 12;
    t.tm_min = 0;
    t.tm_sec = 0;
    time_t timestamp = mktime(&t);
    printf("2024年1月1日12:00的时间戳:%d\n", timestamp);
    printf("对应时间:%d", ctime(&timestamp));
    return 0;
}

difftime(时间1, 时间2) —— 计算两个时间差(秒)

见前面示例。

第十五章 C兼容头文件速查

从 C 标准库继承,C++ 中加 c 前缀,内容在 std 命名空间。

下面逐个详解常用的 C 兼容头文件。

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

是什么

📚 C 语言标准输入输出库的 C++ 版本。

📚 C 语言里叫 <stdio.h>,C++ 里改叫 <stdio.h>,所有函数都放在 std 命名空间里。

📚 它提供了 printf、scanf、 fopen 等经典的格式化 IO 函数。

类比

🏠 如果说 <iostream> 是"用流的方式搬数据"(cin >> x,cout << x), 那 <stdio.h> 就是"用格式化字符串搬数据"(printf("%d", x))。

🏠 iostream 类型安全、可扩展;

🏠 cstdio 更简洁、格式化更灵活、运行更快, 但写错格式化符会出 bug。

常用函数(每个函数附完整示例)

控制台输入输出

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

#include <stdio.h>
int main() {
    printf("年龄:%d,成绩:%.1f\n", 20, 95.5);
    return 0;
}

scanf(格式, 地址...) —— 从键盘格式化读入(变量要加 &)

#include <stdio.h>
int main() {
    int x;
    printf("请输入一个整数:");
    scanf("%d", &x);
    printf("你输入的是:%d\n", x);
    return 0;
}

puts(字符串) —— 输出字符串并自动换行

#include <stdio.h>
int main() {
    puts("Hello World");  // 输出后自动换行
    return 0;
}

gets(字符串) —— 读一行(已废弃,不安全,勿用,用 fgets)

#include <stdio.h>
int main() {
    // 不要使用 gets(),缓冲区无边界检查,会溢出
    printf("gets()已废弃,请使用fgets()\n");
    return 0;
}

getchar() —— 读一个字符,返回 int(EOF 时为 -1)

#include <stdio.h>
int main() {
    printf("请输入一个字符:");
    int c = getchar();
    putchar(c);
    putchar('\n');
    return 0;
}

putchar(字符) —— 输出一个字符

#include <stdio.h>
int main() {
    putchar('A');
    putchar('\n');
    return 0;
}

文件操作

🎯 fopen(文件名, 模式) —— 打开文件,成功返回 FILE*,失败返回 NULL

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp) {
        printf("文件打开成功\n");
        fclose(fp);
    } else {
        printf("文件打开失败\n");
    }
    return 0;
}

fopen(文件名, 模式) —— 打开文件,成功返回 FILE*,失败返回 NULL

📌 模式:"r"读、"w"写(覆盖)、"a"追加、"r+"读写、"w+"读写(覆盖)、"a+"读写(追加)

📌 加 b 表示二进制:"rb"、"wb"、"ab"

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "w");
    if (fp == NULL) {
        printf("文件打开失败\n");
        return 1;
    }
    fprintf(fp, "Hello World\n");
    fclose(fp);
    printf("文件写入成功\n");
    return 0;
}

fclose(文件指针) —— 关闭文件,刷新缓冲区

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "w");
    if (fp) {
        fprintf(fp, "hello\n");
        fclose(fp);
        printf("文件已关闭\n");
    }
    return 0;
}

fprintf(文件, 格式...) —— 格式化写入文件

#include <stdio.h>
int main() {
    FILE* fp = fopen("scores.txt", "w");
    if (fp) {
        fprintf(fp, "%s 考了 %d 分\n", "小明", 88);
        fclose(fp);
    }
    return 0;
}

fscanf(文件, 格式...) —— 从文件格式化读取

#include <stdio.h>
int main() {
    FILE* fp = fopen("scores.txt", "r");
    if (fp) {
        int score;
        fscanf(fp, "%d", &score);
        printf("分数:%d\n", score);
        fclose(fp);
    }
    return 0;
}

fgets(缓冲区, 大小, 文件) —— 读一行(含换行符),最多读 大小-1 个字符

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp) {
        char buf[256];
        fgets(buf, sizeof(buf), fp);
        printf("读到:%s", buf);
        fclose(fp);
    }
    return 0;
}

fputs(字符串, 文件) —— 写字符串(不自动加换行)

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "w");
    if (fp) {
        fputs("一行文字\n", fp);
        fclose(fp);
    }
    return 0;
}

fread(缓冲区, 大小, 个数, 文件) —— 二进制读,返回实际读到的元素数

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.bin", "rb");
    if (fp) {
        int arr[10];
        size_t n = fread(arr, sizeof(int), 10, fp);
        printf("读到 %zu 个整数\n", n);
        fclose(fp);
    }
    return 0;
}

fwrite(缓冲区, 大小, 个数, 文件) —— 二进制写,返回实际写入的元素数

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.bin", "wb");
    if (fp) {
        int arr[10] = {1,2,3,4,5,6,7,8,9,10};
        size_t n = fwrite(arr, sizeof(int), 10, fp);
        printf("写入 %zu 个整数\n", n);
        fclose(fp);
    }
    return 0;
}

feof(文件) —— 是否到文件末尾(到末尾返回非0)

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp) {
        char buf[256];
        while (!feof(fp)) {
            if (fgets(buf, 256, fp))
                printf("%s", buf);
        }
        fclose(fp);
    }
    return 0;
}

ferror(文件) —— 是否出错(出错返回非0)

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp) {
        char buf[256];
        fgets(buf, 256, fp);
        if (ferror(fp)) printf("读取出错\n");
        fclose(fp);
    }
    return 0;
}

rewind(文件) —— 把文件指针移回开头

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp) {
        char buf[256];
        fgets(buf, 256, fp);
        rewind(fp);  // 读完后回到开头再读一遍
        fgets(buf, 256, fp);
        fclose(fp);
    }
    return 0;
}

fseek(文件, 偏移, 起点) —— 移动文件指针;起点:SEEK_SET/SEEK_CUR/SEEK_END

#include <stdio.h>
int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp) {
        fseek(fp, 10, SEEK_SET);  // 从开头向后移10字节
        char buf[256];
        fgets(buf, 256, fp);
        fclose(fp);
    }
    return 0;
}

字符串缓冲区操作

🎯 sprintf(缓冲区, 格式...) —— 格式化写入字符数组(不安全,可能溢出)

#include <stdio.h>
int main() {
    char buf[100];
    sprintf(buf, "x=%d", 42);
    printf("%s\n", buf);
    return 0;
}

snprintf(缓冲区, 大小, 格式...) —— 安全版本,最多写 大小-1 个字符

#include <stdio.h>
int main() {
    char buf[100];
    snprintf(buf, sizeof(buf), "x=%d", 42);
    printf("%s\n", buf);
    return 0;
}

sscanf(字符串, 格式...) —— 从字符串格式化读取

#include <stdio.h>
int main() {
    int a, b;
    sscanf("10 20", "%d %d", &a, &b);
    printf("a=%d, b=%d\n", a, b);
    return 0;
}

格式化说明符(printf / scanf 通用核心)

说明符 对应类型 示例

%d / %i int printf("%d", 42)

%ld long printf("%ld", 100000L)

%lld long long printf("%lld", 9999999999LL)

%u unsigned int printf("%u", 100u)

%f float/double printf("%f", 3.14)

%lf double(scanf) scanf("%lf", &x)

%e / %E 科学计数法 printf("%e", 1000.0) → 1.000000e+03

%g / %G 自动选格式 printf("%g", 3.14) → 3.14

%c char printf("%c", 'A')

%s char* 字符串 printf("%s", "hello")

%p 指针 printf("%p", ptr)

%% 百分号本身 printf("100%%") → 100%

%x / %X 十六进制 printf("%x", 255) → ff

%o 八进制 printf("%o", 8) → 10

进阶格式:%[宽度][.精度]说明符

printf("%5d", 42) → " 42"(占5位,右对齐)

printf("%-5d", 42) → "42 "(左对齐)

printf("%.2f", 3.14159) → "3.14"(保留2位小数)

printf("%05d", 42) → "00042"(补零)

#include <stdio.h>

int main() {
    // 基本输出
    int age = 20;
    double score = 95.5;
    printf("年龄:%d,成绩:%.1f\n", age, score);

    // 基本输入(注意变量要加 & 取地址)
    int x;
    printf("请输入一个整数:");
    scanf("%d", &x);
    printf("你输入的是:%d\n", x);

    // 写文件
    FILE* fp = fopen("data.txt", "w");
    if (fp) {
        fprintf(fp, "Hello %s, 你考了 %d 分\n", "小明", 88);
        fclose(fp);
    }

    // 读文件
    fp = fopen("data.txt", "r");
    if (fp) {
        char buf[256];
        while (fgets(buf, sizeof(buf), fp)) {
            printf("%s", buf);
        }
        fclose(fp);
    }

    // 安全格式化到字符串
    char buffer[100];
    snprintf(buffer, sizeof(buffer), "PI = %.4f", 3.14159);
    printf("%s\n", buffer);

    return 0;
}

文件打开模式

"r" —— 只读(文件必须存在)

"w" —— 只写(文件不存在则创建,存在则清空!)

"a" —— 追加写(文件不存在则创建,存在则在末尾加)

"r+" —— 读写(文件必须存在)

"w+" —— 读写(创建或清空)

"a+" —— 读+追加写

加 'b' 表示二进制模式,如 "rb"、"wb"

⚠️ 1. scanf 的变量必须加 &(取地址),否则会崩溃。 2. 正确:scanf("%d", &x); 错误:scanf("%d", x); 2. printf 的格式化符和参数类型必须匹配,否则结果未定义。 3. 比如用 %d 输出 double 会得到乱码。 4. 3. sprintf 不检查缓冲区大小,容易溢出,推荐用 snprintf。 5. 4. fopen 可能失败(文件不存在、权限不足),一定要检查返回值 是否为 NULL,不要直接使用。 6. 5. 打开的文件必须 fclose,否则资源泄漏。 7. 6. C++ 中 <stdio.h> 和 <iostream> 可以混用,但默认是同步的, 混合大量输出时可能变慢。 8. 可用 ios::sync_with_stdio(false) 取消同步(但之后不要混用)。
💡 💡 竞赛中很多人喜欢用 printf/scanf,因为比 cin/cout 快、格式化方便。 💡 两者各有场景, 了解 cstdio 能看懂大量 C 语言代码和老项目。

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

是什么

📚 C 语言标准错误码库。

📚 很多库函数失败时会设置一个全局变量 errno, 你可以通过检查 errno 的值来判断具体出了什么错。

📚 C 语言中叫

类比

🏠 就像快递员送件失败后,会在系统里填一个"失败原因代码" (比如"地址不存在""客户拒收"),你查这个代码就知道为啥失败了。

常用错误码(每个附完整示例)

errno —— 全局错误码变量

函数失败时被设置,成功时通常不修改(所以调用前最好先置0)。

#include <errno.h>
#include <math.h>
#include <stdio.h>
int main() {
    errno = 0;  // 调用前先清零
    double result = sqrt(-1);  // 对负数开平方,会失败
    if (errno != 0) {
        printf("出错了,错误码:%d\n", errno);
    } else {
        printf("结果:%lf\n", result);
    }
    return 0;
}

EDOM —— 数学域错误(参数不在函数定义域内)

比如 sqrt(-1)、log(-1)。

#include <errno.h>
#include <math.h>
#include <stdio.h>
int main() {
    errno = 0;
    sqrt(-1);
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

ERANGE —— 结果范围错误(结果太大或太小无法表示)

🎯 比如 strtol("99999999999999999999") 超出 long 范围。

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
    errno = 0;
    strtol("99999999999999999999", NULL, 10);
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

ENOENT —— 文件或目录不存在

常见于文件操作失败。

#include <errno.h>
#include <stdio.h>
#include <stdio.h>
int main() {
    errno = 0;
    FILE* fp = fopen("不存在的文件.txt", "r");
    if (fp == NULL) {
        printf("打开失败,错误码:%d\n", errno);
        /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    }
    return 0;
}

EACCES —— 权限不足

试图打开没有权限的文件。

#include <errno.h>
#include <stdio.h>
int main() {
    printf("EACCES = %d(权限不足)\n", EACCES);
    return 0;
}

strerror(errno) —— 把错误码转成人类可读的字符串(在 <string.h> 中)

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdio.h>
int main() {
    FILE* fp = fopen("不存在的文件.txt", "r");
    if (fp == NULL) {
        printf("错误:%d\n", strerror(errno));
    }
    return 0;
}
⚠️ 1. 函数成功时通常不会把 errno 置0,所以调用前要手动 errno = 0。 2. errno 是全局变量,多线程中每个线程有自己的 errno(线程局部存储)。
💡 💡 用 strerror(errno) 可以直接打印出错误描述,不用记每个错误码的数字。

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

是什么

📚 定义了 float、double、long double 三种浮点类型的各种极限值, 比如最大值、最小值、精度、指数范围等。

📚 C 语言中叫

类比

🏠 就像水杯的"容量参数表":最大装多少、最小刻度是多少、 精度能到小数点后几位。

常用宏(每个附完整示例)

FLT_MAX / DBL_MAX / LDBL_MAX —— 各类型最大正值

#include <float.h>
#include <stdio.h>
int main() {
    printf("float 最大值:%d\n", FLT_MAX);
    printf("double 最大值:%d\n", DBL_MAX);
    printf("long double 最大值:%d\n", LDBL_MAX);
    return 0;
}

FLT_MIN / DBL_MIN / LDBL_MIN —— 各类型最小正值(最接近0的正数)

#include <float.h>
#include <stdio.h>
int main() {
    printf("float 最小正值:%d\n", FLT_MIN);
    printf("double 最小正值:%d\n", DBL_MIN);
    return 0;
}

FLT_EPSILON / DBL_EPSILON —— 精度(1和最接近1的数之间的差)

比这个数小的浮点运算误差可以忽略。

#include <float.h>
#include <math.h>
#include <stdio.h>
int main() {
    printf("float 精度:%d\n", FLT_EPSILON);
    printf("double 精度:%d\n", DBL_EPSILON);
    // 判断两个浮点数是否相等
    double a = 0.1 + 0.2;
    double b = 0.3;
    if (fabs(a - b) < DBL_EPSILON)
        printf("a 和 b 相等\n");
    return 0;
}

FLT_DIG / DBL_DIG —— 十进制有效数字位数

#include <float.h>
#include <stdio.h>
int main() {
    printf("float 有效数字:%d 位\n", FLT_DIG);
    printf("double 有效数字:%d 位\n", DBL_DIG);
    return 0;
}

FLT_MAX_EXP / DBL_MAX_EXP —— 最大指数(以2为底)

#include <float.h>
#include <stdio.h>
int main() {
    printf("float 最大指数:%d\n", FLT_MAX_EXP);
    printf("double 最大指数:%d\n", DBL_MAX_EXP);
    return 0;
}

double 类型限制宏(DBL_ 前缀)

宏名说明
DBL_MAXdouble 最大值
DBL_MINdouble 最小正正常值
DBL_EPSILONdouble 最小可分辨差值
DBL_DIGdouble 可精确表示的十进制位数
DBL_MANT_DIGdouble 尾数的二进制位数
DBL_MAX_EXPdouble 最大指数

long double 类型限制宏(LDBL_ 前缀)

宏名说明
LDBL_MAXlong double 最大值
LDBL_MINlong double 最小正正常值
LDBL_EPSILONlong double 最小可分辨差值
LDBL_DIGlong double 可精确表示的十进制位数

其他重要宏

📌 FLT_RADIX:浮点数的基数(通常为2)

📌 FLT_ROUNDS:浮点加法的舍入模式

📌 FLT_EVAL_METHOD:浮点表达式的求值精度(C99)

💡 💡 初学者只需记住:float 约 6-7 位有效数字,double 约 15-16 位有效数字。 💡 float 通常 4 字节,double 通常 8 字节,long double 在 x86 上通常 8/12/16 字节。

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

是什么

📚 定义了各种整数类型的最大值和最小值,比如 int 最大是多少、 char 范围是多少等。

📚 C 语言中叫

类比

🏠 就像不同型号的行李箱,有不同的"最大承重"和"尺寸范围"。

常用宏(每个附完整示例)

INT_MAX / INT_MIN —— int 类型的最大/最小值

#include <limits.h>
#include <stdio.h>
int main() {
    printf("int 最大值:%d\n", INT_MAX);
    printf("int 最小值:%d\n", INT_MIN);
    return 0;
}

CHAR_MAX / CHAR_MIN —— char 类型的最大/最小值

#include <limits.h>
#include <stdio.h>
int main() {
    printf("char 最大值:%d\n", (int)CHAR_MAX);
    printf("char 最小值:%d\n", (int)CHAR_MIN);
    return 0;
}

SHRT_MAX / SHRT_MIN —— short 类型的最大/最小值

#include <limits.h>
#include <stdio.h>
int main() {
    printf("short 最大值:%d\n", SHRT_MAX);
    printf("short 最小值:%d\n", SHRT_MIN);
    return 0;
}

LONG_MAX / LONG_MIN —— long 类型的最大/最小值

#include <limits.h>
#include <stdio.h>
int main() {
    printf("long 最大值:%d\n", LONG_MAX);
    printf("long 最小值:%d\n", LONG_MIN);
    return 0;
}

LLONG_MAX / LLONG_MIN —— long long 类型的最大/最小值(C99/C++11)

#include <limits.h>
#include <stdio.h>
int main() {
    printf("long long 最大值:%d\n", LLONG_MAX);
    printf("long long 最小值:%d\n", LLONG_MIN);
    return 0;
}

UINT_MAX / ULONG_MAX —— unsigned 类型的最大值

#include <limits.h>
#include <stdio.h>
int main() {
    printf("unsigned int 最大值:%d\n", UINT_MAX);
    printf("unsigned long 最大值:%d\n", ULONG_MAX);
    return 0;
}

CHAR_BIT —— 一个 char 占多少位(通常是8)

#include <limits.h>
#include <stdio.h>
int main() {
    printf("一个 char 占 %d 位\n", CHAR_BIT);
    return 0;
}

各类型最小值宏

宏名说明
CHAR_MINchar 最小值(可能为负或0)
SCHAR_MINsigned char 最小值
SHRT_MINshort 最小值
INT_MINint 最小值(通常 -2147483648)
LONG_MINlong 最小值
LLONG_MINlong long 最小值(C99)

无符号类型最大值宏

宏名说明
UCHAR_MAXunsigned char 最大值
USHRT_MAXunsigned short 最大值
UINT_MAXunsigned int 最大值
ULONG_MAXunsigned long 最大值
ULLONG_MAXunsigned long long 最大值(C99)

其他宏

📌 SCHAR_MAX:signed char 最大值

📌 MB_LEN_MAX:多字节字符的最大字节数

💡 💡 常见平台上:int 通常是 4 字节(约 ±21亿),long long 是 8 字节。 💡 有符号类型的最小值通常比最大值的绝对值大1(如 INT_MIN = -2147483648,INT_MAX = 2147483647)。 💡 无符号类型的最小值恒为 0,所以没有 U*_MIN 宏。

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

是什么

📚 让程序适应不同地区的文化习惯,比如日期格式、货币符号、 数字千分位、字符分类等。

📚 C 语言中叫

类比

🏠 就像手机的"语言和地区"设置,切换后日期、货币、输入法都会变。

常用函数(每个附完整示例)

setlocale(category, locale) —— 设置本地化

🎯 category:LC_ALL(全部)、LC_NUMERIC(数字格式)、LC_TIME(时间)、 LC_MONETARY(货币)、LC_CTYPE(字符分类)、LC_COLLATE(排序) locale:"" 表示系统默认,"C" 表示经典C环境(默认)。

#include <locale.h>
#include <stdio.h>
#include <stdio.h>
int main() {
    // 设置为系统默认本地化
    char* old = setlocale(LC_ALL, "");
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    // 恢复为C默认
    setlocale(LC_ALL, "C");
    return 0;
}

localeconv() —— 获取当前本地化的数值/货币格式信息

🎯 返回 struct lconv 指针,包含 decimal_point、thousands_sep 等字段。

#include <locale.h>
#include <stdio.h>
int main() {
    setlocale(LC_ALL, "");
    struct lconv* lc = localeconv();
    printf("小数点:%d\n", lc->decimal_point);
    printf("千分位:%d\n", lc->thousands_sep);
    printf("货币符号:%d\n", lc->currency_symbol);
    return 0;
}
⚠️ 1. setlocale 会影响全局状态,多线程中要小心。 2. 可用的本地化名称因平台而异,Windows 和 Linux 不同。
💡 💡 初学者一般用不到这个头文件,了解有这个东西就行。

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

是什么

📚 提供 setjmp 和 longjmp,可以从一个函数直接跳转到另一个函数的位置, 类似于"全局 goto",常用于错误恢复。

📚 C 语言中叫

类比

🏠 就像游戏里的"存档点":先在某个位置存档(setjmp), 后面遇到危险可以直接读档回到这个位置(longjmp)。

常用函数(每个附完整示例)

setjmp(env) —— 保存当前执行环境到 env,第一次调用返回0

env 是 jmp_buf 类型变量。

#include <setjmp.h>
#include <stdio.h>
jmp_buf env;
void do_something() {
    printf("执行中...\n");
    longjmp(env, 1);  // 跳回 setjmp 处,返回1
    printf("这行不会执行\n");
}
int main() {
    if (setjmp(env) == 0) {
        printf("第一次调用 setjmp\n");
        do_something();
    } else {
        printf("从 longjmp 跳回来了\n");
    }
    return 0;
}

longjmp(env, value) —— 跳回到 setjmp 处,setjmp 返回 value

value 不能为0(如果传0,setjmp会返回1)。

#include <setjmp.h>
#include <stdio.h>
jmp_buf env;
void error_recovery() {
    printf("发生错误,准备跳转...\n");
    longjmp(env, 42);  // 跳回,setjmp返回42
}
int main() {
    int ret = setjmp(env);
    if (ret == 0) {
        printf("正常执行\n");
        error_recovery();
    } else {
        printf("错误恢复,返回码:%d\n", ret);
    }
    return 0;
}
💡 💡 C++ 开发者了解即可,实际写 C++ 代码用异常处理,不要用 setjmp/longjmp。

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

是什么

📚 处理操作系统发送给程序的信号,比如 Ctrl+C 中断、段错误、 浮点异常等。

📚 可以注册信号处理函数,在收到信号时执行自定义操作。

📚 C 语言中叫

类比

🏠 就像火警报警器:平时正常工作,一旦检测到火情(信号), 就触发预设的应急程序(信号处理函数)。

常用信号(每个附完整示例)

signal(sig, handler) —— 注册信号处理函数

🎯 handler 可以是函数指针,或 SIG_DFL(默认处理)、SIG_IGN(忽略)。

#include <signal.h>
#include <stdio.h>
void handle_sigint(int sig) {
    printf("\n收到 Ctrl+C 信号(%d),程序即将退出\n", sig);
    exit(0);
}
int main() {
    signal(SIGINT, handle_sigint);  // 捕获 Ctrl+C
    printf("程序运行中,按 Ctrl+C 测试...\n");
    while (true) {}  // 死循环等待信号
    return 0;
}

SIGINT —— 中断信号(通常由 Ctrl+C 触发)

#include <signal.h>
#include <stdio.h>
volatile sig_atomic_t stop = 0;
void handle(int sig) { stop = 1; }
int main() {
    signal(SIGINT, handle);
    printf("运行中,按Ctrl+C停止\n");
    while (!stop) {}
    printf("已停止\n");
    return 0;
}

SIGTERM —— 终止请求(kill 命令默认发送)

#include <signal.h>
#include <stdio.h>
void handle_term(int sig) {
    printf("收到终止信号,正在清理...\n");
    exit(0);
}
int main() {
    signal(SIGTERM, handle_term);
    printf("等待 SIGTERM 信号...\n");
    while (true) {}
    return 0;
}

SIGSEGV —— 段错误(非法内存访问)

#include <signal.h>
#include <stdio.h>
void handle_segv(int sig) {
    printf("段错误!访问了非法内存\n");
    exit(1);
}
int main() {
    signal(SIGSEGV, handle_segv);
    printf("SIGSEGV 信号处理已注册\n");
    // 注意:实际触发段错误后程序状态已损坏,不建议继续运行
    return 0;
}

SIGFPE —— 浮点异常(除以零、溢出等)

#include <signal.h>
#include <stdio.h>
void handle_fpe(int sig) {
    printf("浮点异常!\n");
    exit(1);
}
int main() {
    signal(SIGFPE, handle_fpe);
    printf("SIGFPE 信号处理已注册\n");
    return 0;
}

SIGABRT —— 异常终止(由 abort() 触发)

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
void handle_abrt(int sig) {
    printf("程序被 abort() 终止\n");
}
int main() {
    signal(SIGABRT, handle_abrt);
    printf("3秒后调用 abort()...\n");
    // abort();  // 取消注释测试
    return 0;
}

raise(sig) —— 主动发送一个信号

#include <signal.h>
#include <stdio.h>
void handle(int sig) {
    printf("收到信号:%d\n", sig);
}
int main() {
    signal(SIGUSR1, handle);
    printf("主动发送 SIGUSR1\n");
    raise(SIGUSR1);
    return 0;
}
⚠️ 1. 信号处理函数中只能做安全的操作(如设置标志位),不能调用 非异步信号安全的函数(如 printf、malloc),否则可能死锁。 2. 信号处理是全局的,会影响整个进程。 3. C++ 中信号处理和异常不能混用,信号处理函数中不能抛异常。 4. SIGKILL 和 SIGSTOP 不能被捕获或忽略。
💡 💡 初学者只需了解 SIGINT(Ctrl+C)的处理,其他信号在系统编程中才会用到。

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

是什么

📚 让函数可以接受可变数量的参数,比如 printf("%d %s", 1, "hi") 就是典型的可变参数函数。

📚 C 语言中叫

类比

🏠 就像一个"万能收纳盒",不管你放多少东西进去,它都能装下, 你可以按顺序一个个拿出来。

常用宏(每个附完整示例)

va_list —— 可变参数列表类型

用来保存可变参数的状态。

#include <stdarg.h>
#include <stdio.h>
// 计算任意个整数的和,第一个参数是参数个数
int sum(int count, ...) {
    va_list args;
    va_start(args, count);  // 初始化,count是最后一个固定参数
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);  // 依次取参数
    }
    va_end(args);  // 清理
    return total;
}
int main() {
    printf("%d\n", sum(3, 10, 20, 30));
    printf("%d\n", sum(5, 1, 2, 3, 4, 5));
    return 0;
}

va_start(ap, last) —— 初始化可变参数列表

last 是最后一个固定参数的名字。

#include <stdarg.h>
#include <stdio.h>
void print_names(int count, ...) {
    va_list args;
    va_start(args, count);
    for (int i = 0; i < count; i++) {
        const char* name = va_arg(args, const char*);
        printf("%d ", name);
    }
    va_end(args);
    printf("\n");
}
int main() {
    print_names(3, "Alice", "Bob", "Charlie");
    return 0;
}

va_arg(ap, type) —— 获取下一个参数,type 是参数类型

#include <stdarg.h>
#include <stdio.h>
double average(int count, ...) {
    va_list args;
    va_start(args, count);
    double sum = 0;
    for (int i = 0; i < count; i++) {
        sum += va_arg(args, double);  // 取double类型参数
    }
    va_end(args);
    return sum / count;
}
int main() {
    printf("%d\n", average(3, 1.0, 2.0, 3.0));
    return 0;
}

va_end(ap) —— 清理可变参数列表

必须在函数返回前调用,否则行为未定义。

#include <stdarg.h>
#include <stdio.h>
int max_of(int count, ...) {
    va_list args;
    va_start(args, count);
    int max_val = va_arg(args, int);
    for (int i = 1; i < count; i++) {
        int val = va_arg(args, int);
        if (val > max_val) max_val = val;
    }
    va_end(args);  // 必须调用
    return max_val;
}
int main() {
    printf("%d\n", max_of(4, 3, 7, 2, 5));
    return 0;
}

va_copy(dest, src) —— 复制可变参数列表(C99/C++11)

当需要多次遍历参数时使用。

#include <stdarg.h>
#include <stdio.h>
void print_and_sum(int count, ...) {
    va_list args, args_copy;
    va_start(args, count);
    va_copy(args_copy, args);  // 复制一份
    // 第一遍:打印
    printf("参数:");
    for (int i = 0; i < count; i++)
        printf("%d ", va_arg(args, int));
    printf("\n");
    // 第二遍:求和
    int sum = 0;
    for (int i = 0; i < count; i++)
        sum += va_arg(args_copy, int);
    printf("和:%d\n", sum);
    va_end(args_copy);
    va_end(args);
}
int main() {
    print_and_sum(3, 10, 20, 30);
    return 0;
}
⚠️ 1. 可变参数函数不知道参数的个数和类型,必须通过固定参数约定 (比如第一个参数传个数,或用特殊结束标记如 NULL)。 2. va_arg 的类型必须和实际参数类型匹配,否则未定义行为。 4. 省略号 ... 必须是最后一个参数。
💡 💡 printf 系列函数就是用可变参数实现的。 💡 C++ 中能用 std::cout 就别自己写 可变参数函数,类型不安全。

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

是什么

📚 定义了一些常用的类型和宏,比如 size_t、ptrdiff_t、NULL、offsetof 等。

📚 很多其他头文件都会自动包含它,所以通常不需要手动包含。

📚 C 语言中叫

类比

🏠 就像工具箱里的"通用零件":螺丝、螺母、垫片,到处都要用, 但你不会专门去买,通常随其他工具一起送。

常用类型和宏(每个附完整示例)

size_t —— 无符号整数类型,表示大小/计数

sizeof 运算符返回的类型,数组下标、字符串长度都用它。

#include <stddef.h>
#include <stdio.h>
int main() {
    size_t len = sizeof(int);
    printf("int 大小:%u 字节\n", len);
    size_t count = 100;
    printf("count = %u\n", count);
    return 0;
}

ptrdiff_t —— 有符号整数类型,表示两个指针的差

#include <stddef.h>
#include <stdio.h>
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* p1 = &arr[0];
    int* p2 = &arr[3];
    ptrdiff_t diff = p2 - p1;
    printf("指针差:%d\n", diff);
    return 0;
}

NULL —— 空指针常量

C++11 后推荐用 nullptr 替代 NULL。

#include <stddef.h>
#include <stdio.h>
int main() {
    int* p = NULL;
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    // C++11 推荐:
    int* p2 = NULL;
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

offsetof(type, member) —— 计算结构体成员的偏移量(字节)

#include <stddef.h>
#include <stdio.h>
struct Point {
    char a;     // 偏移0
    int b;      // 偏移4(可能有填充)
    double c;   // 偏移8
};
int main() {
    printf("a 的偏移:%d\n", offsetof(Point, a));
    printf("b 的偏移:%d\n", offsetof(Point, b));
    printf("c 的偏移:%d\n", offsetof(Point, c));
    return 0;
}

max_align_t —— 最大对齐类型(C11/C++11)

所有标量类型中对齐要求最严格的类型。

#include <stddef.h>
#include <stdio.h>
int main() {
    printf("max_align_t 大小:%d\n", sizeof(max_align_t));
    printf("max_align_t 对齐:%d\n", alignof(max_align_t));
    return 0;
}

nullptr_t —— nullptr 的类型(C++11)

#include <stddef.h>
#include <stdio.h>
int main() {
    NULL_t n = NULL;
    printf("NULL_t 大小:%d\n", sizeof(n));
    return 0;
}
⚠️ 1. size_t 是无符号类型,和 int 比较时要小心(比如 size_t i; i >= 0 永远为真)。 2. C++11 后用 nullptr 替代 NULL,类型更安全。 3. offsetof 只能用于标准布局类型(standard-layout type)。 4. 这个头文件通常被其他头文件自动包含,很少需要手动 #include。

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

是什么

📚 控制浮点运算的环境,包括舍入模式、浮点异常标志等。

📚 可以获取和设置浮点数的舍入方式,检测是否发生了除零、溢出等异常。

📚 C 语言中叫

类比

🏠 就像计算器的"设置菜单":可以设置四舍五入还是向上取整, 还能查看之前计算时有没有出错(除零、溢出等)。

常用函数和宏(每个附完整示例)

feclearexcept(exceptions) —— 清除指定的浮点异常标志

🎯 exceptions:FE_DIVBYZERO(除零)、FE_INEXACT(不精确)、FE_INVALID(无效)、 FE_OVERFLOW(上溢)、FE_UNDERFLOW(下溢)、FE_ALL_EXCEPT(全部)

#include <fenv.h>
#include <math.h>
#include <stdio.h>
int main() {
    feclearexcept(FE_ALL_EXCEPT);  // 清除所有异常标志
    sqrt(-1);  // 触发无效操作异常
    if (fetestexcept(FE_INVALID))
        printf("发生了无效操作异常\n");
    return 0;
}

fetestexcept(exceptions) —— 测试指定的浮点异常标志

返回当前被设置的异常标志位。

#include <fenv.h>
#include <math.h>
#include <stdio.h>
int main() {
    feclearexcept(FE_ALL_EXCEPT);
    double x = 1.0 / 0.0;  // 除零,得到 inf
    if (fetestexcept(FE_DIVBYZERO))
        printf("发生了除零异常\n");
    if (fetestexcept(FE_OVERFLOW))
        printf("发生了溢出异常\n");
    return 0;
}

fesetround(round_mode) —— 设置舍入模式

🎯 round_mode:FE_TONEAREST(就近舍入,默认)、FE_DOWNWARD(向负无穷)、 FE_UPWARD(向正无穷)、FE_TOWARDZERO(向零)

#include <fenv.h>
#include <math.h>
#include <stdio.h>
int main() {
    fesetround(FE_UPWARD);  // 向上取整
    printf("向上取整 1.5 = %d\n", rint(1.5));
    fesetround(FE_DOWNWARD);  // 向下取整
    printf("向下取整 1.5 = %d\n", rint(1.5));
    fesetround(FE_TONEAREST);  // 恢复默认
    return 0;
}

fegetround() —— 获取当前舍入模式

#include <fenv.h>
#include <stdio.h>
int main() {
    int mode = fegetround();
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

fegetenv(envp) / fesetenv(envp) —— 保存/恢复整个浮点环境

#include <fenv.h>
#include <stdio.h>
int main() {
    fenv_t env;
    fegetenv(&env);  // 保存当前环境
    fesetround(FE_UPWARD);
    printf("已改为向上舍入\n");
    fesetenv(&env);  // 恢复之前的环境
    printf("已恢复原环境\n");
    return 0;
}

feholdexcept(envp) —— 保存环境并清除异常,屏蔽异常

#include <fenv.h>
#include <math.h>
#include <stdio.h>
int main() {
    fenv_t env;
    feholdexcept(&env);  // 保存并清除异常
    sqrt(-1);  // 不会触发异常
    printf("异常被屏蔽,不会报错\n");
    feupdateenv(&env);  // 恢复环境并重新触发之前的异常
    return 0;
}
⚠️ 1. 浮点异常不是 C++ 异常,不会被 try/catch 捕获,只是设置标志位。 2. 修改舍入模式会影响整个线程的所有浮点运算,用完记得恢复。 3. 某些编译器需要开启特定选项才能完整支持(如 #pragma STDC FENV_ACCESS ON)。 4. 初学者一般用不到,了解有这个东西就行。
💡 💡 科学计算、金融计算等对精度要求高的场景才会用到这个头文件。

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

是什么

📚 为 <stdint.h> 中的定宽整数类型提供 printf/scanf 的格式化宏, 以及整数转换函数(strtoimax、wcstoimax、imaxabs、imaxdiv)。

📚 C 语言中叫

类比

🏠 就像不同型号的电池需要不同的充电器:int32_t、int64_t 等定宽整数 在 printf 中需要用专门的格式化宏,不能直接用 %d 或 %ld。

常用格式化宏(每个附完整示例)

PRIdN / PRIuN / PRIxN —— printf 格式化宏(d=有符号十进制,u=无符号,x=十六进制)

🎯 N 可以是 8、16、32、64、FAST8、FAST16、FAST32、FAST64、LEAST8 等, 还有 MAX(最大宽度)、PTR(指针宽度)。

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main() {
    int32_t a = 42;
    int64_t b = 9223372036854775807LL;
    uint32_t c = 0xFFFFFFFF;
    printf("int32_t: %" PRId32 "\n", a);
    printf("int64_t: %" PRId64 "\n", b);
    printf("uint32_t 十六进制: %" PRIx32 "\n", c);
    return 0;
}

SCNdN / SCNuN / SCNxN —— scanf 格式化宏

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main() {
    int32_t a;
    printf("请输入一个整数:");
    scanf("%" SCNd32, &a);
    printf("你输入的是:%" PRId32 "\n", a);
    return 0;
}

imaxabs(n) —— 求最大宽度整数的绝对值

#include <inttypes.h>
#include <stdio.h>
int main() {
    intmax_t x = -123456789;
    printf("绝对值:%" PRIdMAX "\n", imaxabs(x));
    return 0;
}

imaxdiv(n, d) —— 最大宽度整数的除法,返回商和余数

#include <inttypes.h>
#include <stdio.h>
int main() {
    imaxdiv_t result = imaxdiv(100, 7);
    printf("商:%" PRIdMAX ",余数:%" PRIdMAX "\n",
           result.quot, result.rem);
    return 0;
}

strtoimax(str, endptr, base) —— 字符串转最大宽度整数

#include <inttypes.h>
#include <stdio.h>
int main() {
    const char* s = "12345abc";
    char* end;
    intmax_t val = strtoimax(s, &end, 10);
    printf("数值:%" PRIdMAX ",剩余:%s\n", val, end);
    return 0;
}
⚠️ 1. PRId32 等宏展开后是字符串字面量,要和前面的 "%" 写在一起,中间不能有空格。 3. 这个头文件通常和 <stdint.h> 一起使用。

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

是什么

📚 定义了精确宽度的整数类型,比如 int32_t(恰好32位)、uint64_t(恰好64位无符号), 让代码在不同平台上整数大小一致。

📚 C 语言中叫

类比

🏠 就像标准尺寸的螺丝:M3、M4、M5,不管哪个厂家生产,尺寸都一样。

🏠 int 在不同平台可能是16位、32位或64位,但 int32_t 永远是32位。

常用类型(每个附完整示例)

intN_t / uintN_t —— 精确宽度整数(N=8,16,32,64)

🎯 intN_t 是有符号,uintN_t 是无符号,恰好占 N 位。

#include <stdint.h>
#include <stdio.h>
int main() {
    int8_t a = 127;        // 8位有符号,范围 -128~127
    uint8_t b = 255;       // 8位无符号,范围 0~255
    int32_t c = 2147483647; // 32位有符号
    uint64_t d = 18446744073709551615ULL; // 64位无符号
    printf("int8_t 大小:%d 字节\n", sizeof(a));
    printf("int32_t 大小:%d 字节\n", sizeof(c));
    printf("uint64_t 大小:%d 字节\n", sizeof(d));
    return 0;
}

int_leastN_t / uint_leastN_t —— 至少 N 位的最小类型

保证至少 N 位,但可能更大(平台上最小的满足要求的类型)。

#include <stdint.h>
#include <stdio.h>
int main() {
    int_least32_t x = 100;
    printf("int_least32_t 大小:%d 字节\n", sizeof(x));
    return 0;
}

int_fastN_t / uint_fastN_t —— 至少 N 位的最快类型

保证至少 N 位,且是该平台上运算最快的类型。

#include <stdint.h>
#include <stdio.h>
int main() {
    int_fast32_t x = 100;
    printf("int_fast32_t 大小:%d 字节\n", sizeof(x));
    return 0;
}

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

可以把指针转成整数保存,再转回来。

#include <stdint.h>
#include <stdio.h>
int main() {
    int x = 42;
    int* p = &x;
    uintptr_t addr = (uintptr_t)(p);
    printf("指针地址(整数):%d\n", addr);
    int* p2 = (int*)(addr);
    printf("通过地址取值:%d\n", *p2);
    return 0;
}

intmax_t / uintmax_t —— 平台上最大宽度的整数类型

#include <stdint.h>
#include <stdio.h>
int main() {
    printf("intmax_t 大小:%d 字节\n", sizeof(intmax_t));
    printf("uintmax_t 大小:%d 字节\n", sizeof(uintmax_t));
    return 0;
}
⚠️ 1. 不是所有平台都支持 int8_t 等精确宽度类型(比如某些DSP没有8位类型), 但主流平台(x86、ARM)都支持。 2. 用 printf 输出这些类型需要配合 <inttypes.h> 的 PRId32 等宏。 3. C++ 中用 std::cout 输出不需要特殊处理。
💡 💡 写跨平台代码、网络协议、文件格式时,用定宽整数能避免大小不一致的问题。 💡 日常编程用 int 就够了。

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

是什么

📚 宽字符(wchar_t)版本的字符串和输入输出函数,对应 <string.h> 和 <stdio.h> 的宽字符版。

📚 用于处理 Unicode 等多字节字符集。

📚 C 语言中叫

类比

🏠 如果说 char 是"单人间"(只能放ASCII字符),那 wchar_t 就是"套间" (能放更大的字符,如中文、日文)。

🏠 <wchar.h> 就是宽字符版的工具箱。

常用函数(每个附完整示例)

wcslen(s) —— 宽字符串长度

#include <wchar.h>
#include <stdio.h>
int main() {
    const wchar_t* s = L"Hello 世界";
    printf("长度:%d\n", wcslen(s));
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

wcscpy(dest, src) / wcsncpy(dest, src, n) —— 宽字符串复制

#include <wchar.h>
#include <stdio.h>
int main() {
    wchar_t dest[50];
    wcscpy(dest, L"Hello");
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    wcsncpy(dest, L"World", 3);
    dest[3] = L'\0';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

wcscat(dest, src) / wcsncat(dest, src, n) —— 宽字符串拼接

#include <wchar.h>
#include <stdio.h>
int main() {
    wchar_t s[50] = L"Hello ";
    wcscat(s, L"World");
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

wcscmp(s1, s2) / wcsncmp(s1, s2, n) —— 宽字符串比较

#include <wchar.h>
#include <stdio.h>
int main() {
    const wchar_t* s1 = L"abc";
    const wchar_t* s2 = L"abd";
    int result = wcscmp(s1, s2);
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

wcschr(s, c) / wcsrchr(s, c) —— 查找字符(首次/末次出现)

#include <wchar.h>
#include <stdio.h>
int main() {
    const wchar_t* s = L"Hello World";
    const wchar_t* p = wcschr(s, L'o');
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

wcsstr(s1, s2) —— 查找子串

#include <wchar.h>
#include <stdio.h>
int main() {
    const wchar_t* s = L"Hello World";
    const wchar_t* p = wcsstr(s, L"World");
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

wprintf / wscanf —— 宽字符版本的 printf/scanf

#include <wchar.h>
int main() {
    wprintf(L"宽字符输出:%ls\n", L"你好世界");
    return 0;
}

fgetws / fputws —— 宽字符版本的 fgets/fputs

#include <wchar.h>
#include <stdio.h>
int main() {
    wchar_t buf[100];
    FILE* fp = fopen("test.txt", "r");
    if (fp) {
        if (fgetws(buf, 100, fp))
            wprintf(L"%ls", buf);
        fclose(fp);
    }
    return 0;
}

mbstowcs / wcstombs —— 多字节字符串和宽字符串互转

#include <wchar.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
    const char* mbstr = "Hello";
    wchar_t wstr[50];
    mbstowcs(wstr, mbstr, 50);
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}
⚠️ 1. wchar_t 的大小因平台而异(Windows 上是2字节,Linux 上通常是4字节)。 3. 宽字符和普通 char 字符串不能直接混用。
💡 💡 Windows API 大量使用宽字符(wchar_t),写 Windows 程序时经常遇到。 💡 跨平台开发推荐用 UTF-8 和 std::string。

15.14 <wctype.h> —— 宽字符判断C 语言

是什么

📚 宽字符版本的字符分类和转换函数,对应 <ctype.h> 的宽字符版。

📚 判断宽字符是否是字母、数字、空格等,以及大小写转换。

📚 C 语言中叫

类比

🏠 <ctype.h> 是"普通字符的分类器",<wctype.h> 就是"宽字符的分类器", 能识别中文、日文等宽字符的属性。

常用函数(每个附完整示例)

iswalpha(c) —— 是否是字母

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L'A';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

iswdigit(c) —— 是否是数字

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L'5';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

iswalnum(c) —— 是否是字母或数字

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L'Z';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

iswspace(c) —— 是否是空白字符(空格、制表符、换行等)

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L' ';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

iswupper(c) / iswlower(c) —— 是否是大写/小写字母

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L'A';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

iswpunct(c) —— 是否是标点符号

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L'!';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

iswprint(c) —— 是否是可打印字符

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L'A';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}

towupper(c) / towlower(c) —— 转大写/小写

#include <wctype.h>
#include <stdio.h>
int main() {
    wchar_t c = L'a';
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    /* 此 cout 语句较复杂,C 版本需手动改写为 printf(...) */
    return 0;
}
⚠️ 1. 参数是 wint_t 类型(通常是 wchar_t 的无符号版本),传 wchar_t 即可。 2. 这些函数的行为受当前本地化(locale)影响。
💡 💡 处理宽字符时用 <wctype.h>,处理普通 char 用 <ctype.h>,两者功能对应。

15.15 <uchar.h> —— Unicode 字符转换(uchar.h,C11)C 语言

是什么

📚 提供 UTF-8、UTF-16、UTF-32 字符之间的转换函数。

📚 C 语言中叫 (C11 新增)。

类比

🏠 就像翻译官:能把 UTF-8 编码的字符翻译成 UTF-16 或 UTF-32, 也能反过来翻译。

常用类型和函数(每个附完整示例)

char16_t / char32_t —— 16位/32位字符类型(C++11 内置类型)

#include <uchar.h>
#include <stdio.h>
int main() {
    printf("char16_t 大小:%d 字节\n", sizeof(char16_t));
    printf("char32_t 大小:%d 字节\n", sizeof(char32_t));
    char16_t c16 = u'A';  // u前缀表示char16_t字符
    char32_t c32 = U'A';  // U前缀表示char32_t字符
    printf("char16_t 值:%d\n", (int)c16);
    printf("char32_t 值:%d\n", (int)c32);
    return 0;
}

mbrtoc16(pc16, s, n, ps) —— 多字节(UTF-8)转 char16_t

#include <uchar.h>
#include <string.h>
#include <stdio.h>
int main() {
    const char* utf8 = "A";  // UTF-8编码的字符
    char16_t c16;
    mbstate_t state = {};
    size_t result = mbrtoc16(&c16, utf8, strlen(utf8), &state);
    if (result != (size_t)-1) {
        printf("转换成功,char16_t 值:%d\n", (int)c16);
    } else {
        printf("转换失败\n");
    }
    return 0;
}

c16rtomb(s, c16, ps) —— char16_t 转多字节(UTF-8)

#include <uchar.h>
#include <stdio.h>
int main() {
    char16_t c16 = u'A';
    char buf[10];
    mbstate_t state = {};
    size_t result = c16rtomb(buf, c16, &state);
    if (result != (size_t)-1) {
        buf[result] = '\0';
        printf("UTF-8:%s\n", buf);
    }
    return 0;
}

mbrtoc32(pc32, s, n, ps) —— 多字节(UTF-8)转 char32_t

#include <uchar.h>
#include <string.h>
#include <stdio.h>
int main() {
    const char* utf8 = "A";
    char32_t c32;
    mbstate_t state = {};
    size_t result = mbrtoc32(&c32, utf8, strlen(utf8), &state);
    if (result != (size_t)-1) {
        printf("char32_t 值:%d\n", (int)c32);
    }
    return 0;
}

c32rtomb(s, c32, ps) —— char32_t 转多字节(UTF-8)

#include <uchar.h>
#include <stdio.h>
int main() {
    char32_t c32 = U'A';
    char buf[10];
    mbstate_t state = {};
    size_t result = c32rtomb(buf, c32, &state);
    if (result != (size_t)-1) {
        buf[result] = '\0';
        printf("UTF-8:%s\n", buf);
    }
    return 0;
}

mbstate_t —— 多字节转换状态类型

用于保持转换过程中的状态,处理多字节字符的中间状态。

#include <uchar.h>
#include <stdio.h>
int main() {
    mbstate_t state = {};  // 初始化为零状态
    printf("mbstate_t 大小:%d 字节\n", sizeof(state));
    return 0;
}
⚠️ 1. 这些函数在不同编译器上的支持程度不同,MSVC 早期版本支持不完善。 2. C++ 中还有 <codecvt> 头文件提供更面向对象的编码转换(但 C++17 已弃用)。 3. char16_t 和 char32_t 是 C++11 内置类型,不需要 <uchar.h> 也能用。 4. 转换失败时返回 (size_t)-1,并设置 errno。

【已弃用的 C 兼容头文件说明】

以下头文件在 C++17 中已弃用,C++20 中已移除,因为 C++ 有更好的替代方案。

但在纯 C 语言中仍然常用,下面逐个详解:

15.16 <iso646.h> —— 运算符别名宏C 语言

是什么

📚 C 语言中为了方便不支持某些符号的键盘和字符集,提供了运算符的文字别名。

📚 比如 and 代替 &&,or 代替 ||,not 代替 !。

📚 在 C++ 中这些是关键字,不需要包含头文件;但在 C 语言中需要包含 <iso646.h>。

类比

🏠 就像有些键盘没有 @ 符号,就用 "at" 代替一样。

🏠 iso646.h 提供了一套"文字版运算符",让代码在任何键盘上都能输入。

常用宏对照表

宏名运算符说明
and&&逻辑与
and_eq&=按位与赋值
bitand&按位与
bitor|按位或
compl~按位取反
not!逻辑非
not_eq!=不等于
or||逻辑或
or_eq|=按位或赋值
xor^按位异或
xor_eq^=按位异或赋值
#include <iso646.h>
#include <stdio.h>
int main() {
    int a = 5, b = 3;
    // 使用文字运算符
    if (a > 0 and b > 0) {
        printf("a 和 b 都大于 0\n");
    }
    if (a not_eq b) {
        printf("a 不等于 b\n");
    }
    int c = a bitand b;  // 按位与
    int d = a bitor b;   // 按位或
    printf("a & b = %d\n", c);
    printf("a | b = %d\n", d);
    return 0;
}
⚠️ 1. C++ 中 and、or、not 等是关键字,不需要包含 <iso646.h>。 2. 这个头文件在 C++17 中已弃用,C++20 中已移除。 3. 纯 C 语言中必须包含 <iso646.h> 才能使用这些宏。
💡 💡 现代 C++ 代码中可以直接用 &&、||、! 等符号运算符,不需要用文字别名。 但在一些老旧的 C 代码或特定编码环境中可能会遇到这些宏。

15.17 <complex.h> —— 复数运算(complex.h,C99)C 语言

是什么

📚 C99 引入的复数数学库,提供复数类型和复数运算函数。

📚 C 语言中用 _Complex 关键字定义复数,C++ 中用 std::complex 类。

📚 <complex.h> 是 C++ 对 C 语言 <complex.h> 的兼容版本。

⚠️ 注意:下面的代码示例使用 C 语言语法(double complex、I 宏),因为 <complex.h> 是 C 兼容头文件。

在纯 C++ 中这些代码可能无法编译,C++ 程序员应使用 <complex> 头文件的 std::complex 类。

类比

🏠 如果说普通数学是"实数的世界",那 complex.h 就是"复数的世界"。

🏠 它让你能直接计算带虚数 i 的数学问题,比如 sqrt(-1) = i。

C 语言复数类型

类型说明
float _Complex单精度复数(float 实部+虚部)
double _Complex双精度复数(最常用)
long double _Complex长双精度复数
_Complex_I虚数单位 i 的宏(const float _Complex)
I虚数单位 i 的简写宏

常用函数

creal(z) —— 取复数的实部

#include <complex.h>
#include <stdio.h>
int main() {
    double complex z = 3.0 + 4.0 * I;  // 3 + 4i
    printf("实部:%d\n", creal(z));
    printf("虚部:%d\n", cimag(z));
    return 0;
}

cimag(z) —— 取复数的虚部

见上面示例。

cabs(z) —— 复数的模(绝对值)

#include <complex.h>
#include <stdio.h>
int main() {
    double complex z = 3.0 + 4.0 * I;
    printf("模:%d\n", cabs(z));
    return 0;
}

carg(z) —— 复数的辐角(弧度)

#include <complex.h>
#include <stdio.h>
int main() {
    double complex z = 1.0 + 1.0 * I;
    printf("辐角:%d 弧度\n", carg(z));
    return 0;
}

csqrt(z) —— 复数平方根

#include <complex.h>
#include <stdio.h>
int main() {
    double complex z = -1.0;  // -1
    double complex r = csqrt(z);
    printf("sqrt(-1) = %d + %di\n", creal(r), cimag(r));
    // 输出 0 + 1i
    return 0;
}

cexp(z) —— 复数指数函数

clog(z) —— 复数自然对数

cpow(z1, z2) —— 复数幂运算

csin(z), ccos(z), ctan(z) —— 复数三角函数

casin(z), cacos(z), catan(z) —— 复数反三角函数

15.18 <stdbool.h> —— 布尔类型(stdbool.h,C99)C 语言

是什么

📚 C99 引入的布尔类型支持头文件。

📚 在 C 语言中,C99 之前没有真正的 bool 类型,通常用 int 代替(0 为假,非 0 为真)。

📚 <stdbool.h> 定义了 bool、true、false 三个宏,让 C 语言也能写布尔类型。

类比

🏠 如果说 C89 的"真假"是用 0 和 1 表示的"暗号",那 stdbool.h 就是把暗号翻译成"true/false"的"翻译官"。

🏠 让代码更易读:if (is_ready) 比 if (is_ready == 1) 更清晰。

定义的宏

宏名说明
bool_Bool布尔类型(C99 内置)
true1
false0
__bool_true_false_are_defined1标准宏
#include <stdbool.h>
#include <stdio.h>
int main() {
    bool is_student = true;
    bool has_passed = false;

    if (is_student) {
        printf("是学生\n");
    }
    if (!has_passed) {
        printf("未通过考试\n");
    }

    printf("bool 大小:%d 字节\n", sizeof(bool));
    printf("true = %d, false = %d\n", true, false);
    return 0;
}
⚠️ 1. C++ 中 bool、true、false 是关键字,不需要包含 <stdbool.h>。 2. <stdbool.h> 在 C++17 中已弃用,C++20 中已移除。 3. C 语言中 _Bool 是内置类型,stdbool.h 只是提供 bool/true/false 的宏定义。 4. C 语言中 bool 变量只能存 0 或 1,赋值非 0 值会被转为 1。
💡 💡 C++ 中直接用 bool 即可,不需要任何头文件。 但在纯 C 语言项目中,记得 #include <stdbool.h> 才能用 bool/true/false。

15.19 <tgmath.h> —— 泛型数学宏(tgmath.h,C99)C 语言

是什么

📚 C99 引入的"类型通用数学宏"(Type-Generic Math)。

📚 它让你用一个函数名(如 sqrt)自动根据参数类型调用对应的函数版本。

📚 比如 sqrt(x):x 是 float 调 sqrtf,x 是 double 调 sqrt,x 是 long double 调 sqrtl。

类比

🏠 就像自动售货机:你投入不同大小的硬币,它自动识别并给出对应商品。

🏠 tgmath.h 让你不用手动区分 sqrtf/sqrt/sqrtl,写一个 sqrt 就行。

为什么需要

📌 C 语言中数学函数有三个版本:

- float 版本:sqrtf、sinf、cosf(后缀 f)

- double 版本:sqrt、sin、cos(无后缀)

- long double 版本:sqrtl、sinl、cosl(后缀 l)

📌 手动选择版本很麻烦,tgmath.h 用宏自动选择。

#include <tgmath.h>
#include <stdio.h>
int main() {
    float f = 4.0f;
    double d = 9.0;
    long double ld = 16.0L;

    // 同一个 sqrt,自动根据参数类型选择版本
    printf("sqrt(float) = %d\n", sqrt(f));
    printf("sqrt(double) = %d\n", sqrt(d));
    printf("sqrt(long double) = %d\n", sqrt(ld));

    // 复数也支持
    double complex z = -1.0;
    printf("sqrt(complex) = %d+%di\n", creal(sqrt(z)), cimag(sqrt(z)));
    return 0;
}

支持的泛型函数

📌 三角函数:sin、cos、tan、asin、acos、atan、atan2、sinh、cosh、tanh

📌 指数对数:exp、exp2、expm1、log、log2、log10、log1p

📌 幂运算:pow、sqrt、cbrt、hypot

📌 其他:fabs、floor、ceil、round、trunc、fmod、remainder 等

⚠️ 1. C++ 中通过函数重载自动实现类型通用,不需要 <tgmath.h>。 2. <tgmath.h> 在 C++17 中已弃用,C++20 中已移除。 3. tgmath.h 的宏是通过 C11 的 _Generic 实现的(C99 用编译器内置支持)。
💡 💡 C++ 中直接用 sqrt(x) 即可,编译器会根据 x 的类型自动选择重载版本。 纯 C 语言中如果想偷懒不写后缀,就包含 <tgmath.h>。

15.20 <stdalign.h> —— 对齐控制宏(stdalign.h,C11)C 语言

是什么

📚 C11 引入的内存对齐控制宏。

📚 提供 alignas 和 alignof 两个宏,用于控制变量的内存对齐方式。

📚 内存对齐影响数据访问速度和某些硬件要求。

类比

🏠 如果说内存是"停车场",对齐就是"车位大小"。

🏠 4 字节对齐的变量只能停在 4 的倍数车位,虽然可能浪费一点空间,但访问更快。

🏠 alignas 就是"指定车位大小",alignof 就是"查询某车型需要多大车位"。

定义的宏

宏名说明
alignas指定变量或类型的对齐字节数(C++ 中是关键字)
alignof查询类型的对齐字节数(C++ 中是关键字)
__alignas_is_defined标准宏,值为 1
__alignof_is_defined标准宏,值为 1
#include <stdalign.h>
#include <stdio.h>
int main() {
    // 查询基本类型的对齐
    printf("int 对齐:%d 字节\n", alignof(int));
    printf("double 对齐:%d 字节\n", alignof(double));

    // 指定变量对齐到 32 字节边界
    alignas(32) int aligned_array[100];
    printf("aligned_array 地址:%d\n", &aligned_array);
    // 地址应该是 32 的倍数

    // 结构体中使用
    struct alignas(16) MyStruct {
        char a;      // 1 字节
        int b;       // 4 字节
        // 整个结构体对齐到 16 字节
    };
    printf("MyStruct 对齐:%d 字节\n", alignof(MyStruct));
    printf("MyStruct 大小:%d 字节\n", sizeof(MyStruct));
    return 0;
}
⚠️ 1. C++11 中 alignas 和 alignof 是关键字,不需要包含 <stdalign.h>。 2. <stdalign.h> 在 C++17 中已弃用,C++20 中已移除。 3. alignas 的值必须是 2 的幂(1、2、4、8、16、32...)。 4. 过大的对齐可能浪费内存,一般用默认对齐即可。
💡 💡 对齐在 SIMD 编程(如 SSE/AVX)中很重要,某些指令要求数据 16 或 32 字节对齐。 普通编程不需要手动指定对齐,编译器会自动选择最优值。

15.21 <stdnoreturn.h> —— 不返回函数标记(stdnoreturn.h,C11)C 语言

是什么

📚 C11 引入的函数属性宏,用于标记"不会返回的函数"。

📚 比如 exit()、abort() 这类函数,调用后程序直接结束,不会返回到调用点。

📚 标记后编译器可以做更好的优化(不需要保存返回地址等)。

类比

🏠 如果说普通函数是"借出去的东西会还回来",那 noreturn 函数就是"肉包子打狗——有去无回"。

🏠 告诉编译器:这个函数调用后不用等它返回,直接处理后续逻辑或结束。

定义的宏

宏名说明
noreturn标记函数不返回(C++ 中用 [[noreturn]] 属性)
__noreturn_is_defined标准宏,值为 1
#include <stdnoreturn.h>
#include <stdlib.h>
#include <stdio.h>

// 标记这个函数不会返回
noreturn void fatal_error(const char* msg) {
    fprintf(stderr, "致命错误:%d\n", msg);
    exit(1);  // exit 不会返回
    // 这里不需要写 return,编译器知道不会执行到这里
}

int main() {
    int score = -5;
    if (score < 0) {
        fatal_error("分数不能为负数");
        // 编译器知道上面的函数不会返回,这里不会有"缺少返回"警告
    }
    printf("分数:%d\n", score);
    return 0;
}

常见的不返回函数

📌 exit() —— 正常结束程序

📌 abort() —— 异常终止程序

📌 longjmp() —— 非局部跳转(配合 setjmp)

📌 自定义的致命错误处理函数

⚠️ 1. C++11 中用 [[noreturn]] 属性,不需要包含 <stdnoreturn.h>。 2. <stdnoreturn.h> 在 C++17 中已弃用,C++20 中已移除。 3. 如果标记了 noreturn 的函数实际上返回了,行为是未定义的(可能崩溃)。 4. main 函数不能标记为 noreturn。

15.22 <stdatomic.h> —— 原子操作(C11)C 语言

是什么

📚 C11 引入的原子操作库,用于多线程环境下的无锁编程。

📚 原子操作保证"要么全部完成,要么不执行",不会被其他线程中断。

📚 C++ 中对应 <atomic> 头文件的 std::atomic 模板类。

类比

🏠 如果说普通变量是"共享笔记本",两个人同时写会乱。

🏠 原子变量就是"带锁的笔记本",每次只能一个人写,写完另一个人才能写。

🏠 但原子操作比锁更轻量,通常用 CPU 指令直接实现。

原子类型

类型说明
atomic_bool原子布尔值
atomic_char原子字符
atomic_schar原子有符号字符
atomic_uchar原子无符号字符
atomic_short原子短整型
atomic_ushort原子无符号短整型
atomic_int原子整型(最常用)
atomic_uint原子无符号整型
atomic_long原子长整型
atomic_ulong原子无符号长整型
atomic_llong原子长长整型
atomic_ullong原子无符号长长整型
atomic_size_t原子 size_t
atomic_intptr_t原子指针整数
atomic_uintptr_t原子无符号指针整数

常用操作

atomic_init(obj, value) —— 初始化原子变量

#include <stdatomic.h>
#include <stdio.h>
int main() {
    atomic_int counter;
    atomic_init(&counter, 0);  // 初始化为 0
    printf("初始值:%d\n", atomic_load(&counter));
    return 0;
}

atomic_load(obj) —— 原子读取

atomic_store(obj, value) —— 原子写入

#include <stdatomic.h>
#include <stdio.h>
int main() {
    atomic_int x;
    atomic_init(&x, 10);
    int val = atomic_load(&x);  // 安全读取
    printf("x = %d\n", val);
    atomic_store(&x, 20);  // 安全写入
    printf("x = %d\n", atomic_load(&x));
    return 0;
}

atomic_fetch_add(obj, value) —— 原子加法(返回旧值)

atomic_fetch_sub(obj, value) —— 原子减法

atomic_fetch_or(obj, value) —— 原子按位或

atomic_fetch_and(obj, value) —— 原子按位与

atomic_fetch_xor(obj, value) —— 原子按位异或

#include <stdatomic.h>
#include <stdio.h>
int main() {
    atomic_int count;
    atomic_init(&count, 0);

    int old = atomic_fetch_add(&count, 5);  // 原子加 5
    printf("旧值:%d,新值:%d\n", old, atomic_load(&count));
    // 输出:旧值:0,新值:5

    old = atomic_fetch_sub(&count, 2);  // 原子减 2
    printf("旧值:%d,新值:%d\n", old, atomic_load(&count));
    // 输出:旧值:5,新值:3
    return 0;
}

atomic_compare_exchange_strong(obj, expected, desired) —— 比较并交换

#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
int main() {
    atomic_int val;
    atomic_init(&val, 10);
    int expected = 10;
    int desired = 20;
    // 如果 val == expected,则设为 desired,返回 true
    // 否则把当前值写入 expected,返回 false
    bool success = atomic_compare_exchange_strong(&val, &expected, desired);
    printf("成功:%d,值:%d\n", success, atomic_load(&val));
    // 输出:成功:1,值:20
    return 0;
}

内存序(memory_order)

📌 memory_order_relaxed —— 只保证原子性,不保证顺序

📌 memory_order_acquire —— 读操作,防止后续操作重排到前面

📌 memory_order_release —— 写操作,防止前面操作重排到后面

📌 memory_order_acq_rel —— 同时 acquire 和 release

📌 memory_order_seq_cst —— 最强顺序保证(默认)

15.23 <threads.h> —— 多线程(C11)C 语言

是什么

📚 C11 引入的标准多线程库。

📚 提供线程创建、互斥锁、条件变量等多线程同步原语。

📚 C++ 中对应 <thread>、<mutex>、<condition_variable> 等头文件。

类比

🏠 如果说单线程是"一个人干活",多线程就是"多个人同时干活"。

🏠 threads.h 提供了"招工(创建线程)"、"分工(互斥锁)"、"交接(条件变量)"的工具。

🏠 但多人干活需要协调,否则会出现"抢资源"的问题。

线程操作

thrd_create(thread, func, arg) —— 创建线程

#include <threads.h>
#include <stdio.h>

int print_message(void* arg) {
    const char* msg = (const char*)arg;
    for (int i = 0; i < 3; i++) {
        printf("%s\n", msg);
    }
    return 0;
}

int main() {
    thrd_t t1, t2;
    thrd_create(&t1, print_message, (void*)"线程 A");
    thrd_create(&t2, print_message, (void*)"线程 B");
    thrd_join(t1, NULL);  // 等待线程结束
    thrd_join(t2, NULL);
    printf("所有线程结束\n");
    return 0;
}

thrd_join(thread, result) —— 等待线程结束

thrd_detach(thread) —— 分离线程(结束后自动清理)

thrd_sleep(duration, remaining) —— 线程休眠

thrd_yield() —— 让出 CPU

thrd_current() —— 获取当前线程 ID

thrd_equal(t1, t2) —— 判断两个线程是否相同

互斥锁(mtx)

mtx_init(mutex, type) —— 初始化互斥锁

mtx_lock(mutex) —— 加锁(阻塞)

mtx_trylock(mutex) —— 尝试加锁(不阻塞)

mtx_unlock(mutex) —— 解锁

mtx_destroy(mutex) —— 销毁互斥锁

#include <threads.h>
#include <stdio.h>

mtx_t mutex;
int counter = 0;

int increment(void* arg) {
    for (int i = 0; i < 1000; i++) {
        mtx_lock(&mutex);      // 加锁
        counter++;
        mtx_unlock(&mutex);    // 解锁
    }
    return 0;
}

int main() {
    mtx_init(&mutex, mtx_plain);
    thrd_t t1, t2;
    thrd_create(&t1, increment, NULL);
    thrd_create(&t2, increment, NULL);
    thrd_join(t1, NULL);
    thrd_join(t2, NULL);
    mtx_destroy(&mutex);
    printf("counter = %d\n", counter);  // 2000
    return 0;
}

条件变量(cnd)

cnd_init(cond) —— 初始化条件变量

cnd_wait(cond, mutex) —— 等待条件

cnd_signal(cond) —— 唤醒一个等待线程

cnd_broadcast(cond) —— 唤醒所有等待线程

cnd_destroy(cond) —— 销毁条件变量

线程本地存储(tss)

tss_create(key, destructor) —— 创建线程本地存储键

tss_set(key, value) —— 设置当前线程的值

tss_get(key) —— 获取当前线程的值

tss_delete(key) —— 删除键

【总结:29 个 C 语言标准库头文件】

C89/C90(15 个):assert.h、ctype.h、errno.h、float.h、limits.h、locale.h、

math.h、setjmp.h、signal.h、stdarg.h、stddef.h、stdio.h、stdlib.h、string.h、time.h

C95(3 个):iso646.h、wchar.h、wctype.h

C99(6 个):complex.h、fenv.h、inttypes.h、stdbool.h、stdint.h、tgmath.h

C11(5 个):stdalign.h、stdatomic.h、stdnoreturn.h、threads.h、uchar.h

总计:15 + 3 + 6 + 5 = 29 个

第十六章 其他重要头文件

16.1 <assert.h> —— 断言C 语言

assert(表达式):表达式为 false 时程序崩溃并打印错误位置。

用于调试时检查"不可能发生"的情况。

定义 NDEBUG 后(Release模式)assert 失效。

#include <assert.h>
#include <stdio.h>
int main() {
    int x = 5;
    assert(x > 0);           // x应大于0
    printf("断言通过\n");
    // assert(ptr != NULL);  // 指针不应为空
    return 0;
}

C++11 静态断言(编译期):

static_assert(sizeof(int) == 4, "int必须是4字节");