C: 常用标准库

标准库就像工具箱——你不需要自己造锤子螺丝刀,拿来用就行。了解每个工具的用途,才能选对工具。

1. math.h 数学运算

使用数学函数需要包含头文件并链接数学库:

C
#include <math.h>

编译时加 -lm 选项:gcc program.c -lm

(1) 常用数学函数

函数 功能 示例
fabs(x) 绝对值 fabs(-3.5) → 3.5
sqrt(x) 平方根 sqrt(16.0) → 4.0
pow(x, y) x的y次幂 pow(2.0, 10.0) → 1024.0
ceil(x) 向上取整 ceil(3.2) → 4.0
floor(x) 向下取整 floor(3.8) → 3.0
round(x) 四舍五入 round(3.5) → 4.0
fmod(x, y) 浮点取余 fmod(7.5, 2.5) → 0.0
log(x) 自然对数 log(2.718) → 1.0
log10(x) 常用对数 log10(100.0) → 2.0
sin(x) 正弦 sin(3.14/2) → 1.0
cos(x) 余弦 cos(0.0) → 1.0
tan(x) 正切 tan(0.0) → 0.0
⚠️ 三角函数的参数是弧度,不是角度!角度转弧度:rad = deg * 3.14159265 / 180.0

▶ 示例

计算两点之间的距离:

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

typedef struct {
    double x;
    double y;
} Point;

double distance(Point a, Point b) {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

int main(void) {
    Point p1 = {3.0, 4.0};
    Point p2 = {0.0, 0.0};
    printf("距离: %.2f\n", distance(p1, p2));
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
距离: 5.00

2. stdlib.h 通用工具

(1) 随机数

C
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(void) {
    srand((unsigned int)time(NULL));

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

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

    return 0;
}
TEXT 📖 仅展示
1804289383 846930886 1681692777 1714636915 1957747793
83 86 77 15 93
⚠️ rand() % N 在大多数实现中随机性不够好,低位可能呈规律分布。对随机性要求高的场景应使用更高级的随机数生成器。

(2) 类型转换

函数 功能
atoi(str) 字符串转 int
atol(str) 字符串转 long
atof(str) 字符串转 double
strtol(str, &end, base) 字符串转 long(指定进制)
strtod(str, &end) 字符串转 double(带错误检测)
C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int a = atoi("42");
    double b = atof("3.14");
    long c = strtol("0xFF", NULL, 16);

    printf("a = %d\n", a);
    printf("b = %.2f\n", b);
    printf("c = %ld\n", c);

    char *end;
    long d = strtol("123abc", &end, 10);
    printf("d = %ld, 未转换部分: %s\n", d, end);

    return 0;
}
TEXT 📖 仅展示
a = 42
b = 3.14
c = 255
d = 123, 未转换部分: abc
💡 atoi 无法检测错误——输入非法字符串返回 0,和真正的 0 无法区分。建议用 strtol/strtod,通过 end 指针判断是否转换成功。

(3) 动态内存管理

C
void *malloc(size_t size);
void *calloc(size_t count, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *arr = calloc(5, sizeof(int));
    if (arr == NULL) {
        return 1;
    }

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

    int *new_arr = realloc(arr, 10 * sizeof(int));
    if (new_arr == NULL) {
        free(arr);
        return 1;
    }
    arr = new_arr;

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

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

    free(arr);
    return 0;
}
TEXT 📖 仅展示
0 10 20 30 40 50 60 70 80 90
⚠️ realloc 失败时返回 NULL,但原内存不会被释放!所以必须用临时变量接收 realloc 的返回值,失败时仍可 free 原内存。

(4) 排序与搜索

qsort

C
void qsort(void *base, size_t count, size_t size,
           int (*compare)(const void *, const void *));

比较函数规则:返回负数表示 a < b,0 表示相等,正数表示 a > b。

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

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

int cmp_str(const void *a, const void *b) {
    return strcmp(*(const char **)a, *(const char **)b);
}

int main(void) {
    int nums[] = {42, 17, 8, 95, 3, 61};
    int n = sizeof(nums) / sizeof(nums[0]);

    qsort(nums, n, sizeof(int), cmp_int);
    for (int i = 0; i < n; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");

    const char *names[] = {"张三", "李四", "王五", "赵六"};
    int m = sizeof(names) / sizeof(names[0]);

    qsort(names, m, sizeof(char *), cmp_str);
    for (int i = 0; i < m; i++) {
        printf("%s ", names[i]);
    }
    printf("\n");

    return 0;
}
TEXT 📖 仅展示
3 8 17 42 61 95
张三 李四 王五 赵六

bsearch

C
void *bsearch(const void *key, const void *base, size_t count,
              size_t size, int (*compare)(const void *, const void *));

在已排序数组中二分查找,找到返回指向元素的指针,未找到返回 NULL。

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

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

int main(void) {
    int nums[] = {3, 8, 17, 42, 61, 95};
    int n = sizeof(nums) / sizeof(nums[0]);

    int key = 42;
    int *result = bsearch(&key, nums, n, sizeof(int), cmp_int);

    if (result != NULL) {
        printf("找到 %d,下标 %ld\n", key, result - nums);
    } else {
        printf("未找到 %d\n", key);
    }

    return 0;
}
TEXT 📖 仅展示
找到 42,下标 3

3. time.h 时间处理

(1) 时间函数

函数/类型 功能
time_t 时间类型(通常是从1970-01-01起的秒数)
time(&t) 获取当前时间
clock() 获取程序运行的CPU时钟数
localtime() 转换为本地时间结构体
gmtime() 转换为UTC时间结构体
strftime() 格式化时间字符串
difftime() 计算两个时间的差(秒)

(2) struct tm

C
struct tm {
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;
    int tm_yday;
    int tm_isdst;
};

tm_mon 范围 0-11(0=一月),tm_year 是从 1900 年起的年数,tm_wday 中 0 表示周日。

▶ 示例

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

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

    char buf[64];
    strftime(buf, sizeof(buf), "%Y年%m月%d日 %H:%M:%S", local);
    printf("当前时间: %s\n", buf);

    printf("今天是星期%d\n", local->tm_wday == 0 ? 7 : local->tm_wday);

    return 0;
}
▶ 试一试
TEXT 📖 仅展示
当前时间: 2025年03月15日 14:30:22
今天是星期6

(3) 程序计时

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

int main(void) {
    clock_t start = clock();

    volatile long sum = 0;
    for (long i = 0; i < 100000000L; i++) {
        sum += i;
    }

    clock_t end = clock();
    double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
    printf("耗时: %.3f 秒\n", elapsed);

    return 0;
}
TEXT 📖 仅展示
耗时: 0.235 秒
⚠️ clock() 测量的是 CPU 时间,不是墙上时钟时间。如果程序中有 sleep 或等待 I/O,这些时间不计入。需要测量实际经过时间用 difftime


4. ctype.h 字符处理

函数 判断条件
isalpha(c) 字母
isdigit(c) 数字
isalnum(c) 字母或数字
isupper(c) 大写字母
islower(c) 小写字母
isspace(c) 空白字符(空格、制表、换行等)
ispunct(c) 标点符号
isprint(c) 可打印字符
toupper(c) 转大写
tolower(c) 转小写

这些函数的参数必须是 unsigned char 的值或 EOF。传入负值的 char 是未定义行为。

▶ 示例

统计字符串中字母、数字和其他字符的个数:

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

int main(void) {
    char str[] = "Hello, World! 123";
    int letters = 0, digits = 0, others = 0;

    for (int i = 0; str[i] != '\0'; i++) {
        if (isalpha((unsigned char)str[i])) {
            letters++;
        } else if (isdigit((unsigned char)str[i])) {
            digits++;
        } else {
            others++;
        }
    }

    printf("字母: %d, 数字: %d, 其他: %d\n", letters, digits, others);
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
字母: 10, 数字: 3, 其他: 6

5. assert.h 断言

C
void assert(int expression);

当表达式为假时,程序终止并打印错误信息(文件名、行号、表达式)。在 #include <assert.h> 之前定义 NDEBUG 宏可以禁用所有断言。

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

double safe_divide(double a, double b) {
    assert(b != 0 && "除数不能为零");
    return a / b;
}

int main(void) {
    printf("10 / 2 = %.1f\n", safe_divide(10.0, 2.0));
    printf("10 / 0 = %.1f\n", safe_divide(10.0, 0.0));
    return 0;
}
TEXT 📖 仅展示
10 / 2 = 5.0
Assertion failed: b != 0 && "除数不能为零", file main.c, line 5
💡 assert 的字符串字面量会被打印出来,所以 "除数不能为零" 这种写法比注释更有效——断言失败时能直接看到原因。

❓ 常见问题

Q 为什么 rand() 每次运行结果一样?
A 没有调用 srand 设置随机种子。默认种子是1,所以序列固定。用 srand(time(NULL)) 以当前时间做种子。
Q qsort 的比较函数为什么要用 void 指针?
A qsort 是通用函数,不知道排序什么类型。void 指针可以指向任何类型,比较函数内部再转换回具体类型。
Q ctype.h 函数为什么参数要转 unsigned char?
A 如果 char 是有符号的,高位为1的字符可能是负值,传入 ctype 函数是未定义行为。转 unsigned char 确保值在 0-255 范围内。
Q assert 和 if 判断有什么区别?
A assert 用于开发阶段检查不该发生的情况(逻辑错误),定义 NDEBUG 后自动消失。if 判断用于处理可能发生的运行时错误,始终存在。

📖 小节

📝 作业

  1. 编写程序,生成 100 个 1-1000 的随机整数,用 qsort 排序后输出最大值和最小值
  2. 编写程序,用 clock() 比较冒泡排序和 qsort 排序 10000 个整数的时间差异
  3. 编写函数,用 ctype.h 的函数实现字符串的大小写转换(输入一个字符串,输出全大写和全小写版本)
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏