第27章 标准库函数:数学、时间、随机数与字符串转换

说实话,标准库函数这块内容,很多初学者觉得就是查查手册的事。但我在实际项目中吃过不少亏——比如时间函数用错了导致日志时间错乱,随机数没初始化结果每次运行结果都一样。这些坑,今天咱们一个一个说清楚。

27.1 数学函数(math.h)

数学库提供了常用的数学运算函数。我个人习惯在涉及浮点计算时,一定先确认编译器的数学库是否完整——嵌入式环境里有些库是裁剪过的。

函数 说明 注意点
sin(), cos(), tan() 三角函数,参数为弧度 角度转弧度:rad = deg * M_PI / 180.0
asin(), acos(), atan() 反三角函数 返回值范围有限,注意输入域
sqrt() 平方根 参数不能为负数
pow() 幂运算 底数为负且指数非整数时出错
fabs() 浮点绝对值 整型绝对值用 abs()
floor(), ceil() 向下/向上取整 返回 double 类型
fmod() 浮点数取余 与 % 运算符不同,支持小数

避坑指南:我曾经在STM32上写了一个三角函数计算,结果发现运行时间长达几百微秒。后来查资料才知道,嵌入式MCU的数学库如果没有开启硬件浮点单元(FPU),纯软件模拟会非常慢。所以,记得在编译器选项中启用FPU。

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

int main() {
    double angle = 45.0;
    double rad = angle * M_PI / 180.0;
    printf("sin(45°) = %f\n", sin(rad));
    printf("sqrt(144) = %f\n", sqrt(144.0));
    printf("pow(2, 10) = %f\n", pow(2.0, 10.0));
    printf("floor(3.7) = %f, ceil(3.7) = %f\n", floor(3.7), ceil(3.7));
    return 0;
}

27.2 时间函数(time.h)

时间函数主要用来获取系统时间、计算程序运行时长。我建议你在做日志系统或性能测试时,一定要用好这些函数。

函数 说明 典型用途
time() 获取当前时间戳(秒) 记录事件发生时间
localtime() 将时间戳转为本地时间结构体 格式化输出日期
strftime() 格式化时间字符串 生成日志时间戳
clock() 获取程序运行的CPU时钟数 测量代码段执行时间
difftime() 计算两个时间戳的差值(秒) 统计耗时

小技巧:clock() 返回的是时钟周期数,要换算成秒需要除以 CLOCKS_PER_SEC。这个宏在 time.h 里定义,通常是 1000000。

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

int main() {
    // 获取当前时间
    time_t now = time(NULL);
    printf("当前时间戳: %ld\n", now);

    // 转为本地时间
    struct tm *local = localtime(&now);
    printf("本地时间: %s", asctime(local));

    // 格式化输出
    char buf[64];
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local);
    printf("格式化时间: %s\n", buf);

    // 测量代码执行时间
    clock_t start = clock();
    for (long i = 0; i < 100000000; i++);  // 空循环
    clock_t end = clock();
    double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
    printf("循环耗时: %f 秒\n", elapsed);

    return 0;
}

27.3 随机数函数(stdlib.h)

随机数在游戏、测试、加密场景中很常用。但很多人不知道,C语言的随机数是伪随机——如果不设置种子,每次运行结果都一样。

重要提醒:我曾经在写一个抽奖程序时,忘了调用 srand() 设置种子,结果每次重启程序抽奖结果都一样。测试同事还以为我作弊了……所以,一定要用 srand() 初始化随机数种子。

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

int main() {
    // 用当前时间作为种子
    srand((unsigned int)time(NULL));

    // 生成 0 到 RAND_MAX 之间的随机数
    int r1 = rand();
    printf("随机数: %d\n", r1);

    // 生成 1 到 100 之间的随机数
    int r2 = rand() % 100 + 1;
    printf("1~100 随机数: %d\n", r2);

    // 生成 0.0 到 1.0 之间的浮点随机数
    double r3 = (double)rand() / RAND_MAX;
    printf("0~1 随机浮点数: %f\n", r3);

    return 0;
}

注意:rand() 的随机质量其实不高,不适合用于加密场景。如果你需要高质量的随机数,建议使用操作系统提供的随机数接口(如 Linux 的 /dev/urandom)。

27.4 字符串转换函数(stdlib.h)

字符串和数字之间的转换,是日常开发中高频使用的功能。我建议你优先使用带安全检测的函数,比如 strtol() 而不是 atoi()。

函数 功能 返回值 错误处理
atoi() 字符串转 int int 无法检测错误
atol() 字符串转 long long 无法检测错误
atof() 字符串转 double double 无法检测错误
strtol() 字符串转 long(带错误检测) long 可检测非法字符
strtod() 字符串转 double(带错误检测) double 可检测非法字符
sprintf() 格式化输出到字符串 int(写入字符数) 缓冲区溢出风险
snprintf() 安全版 sprintf int(应写入字符数) 指定缓冲区大小

我的建议:在嵌入式开发中,我几乎不用 atoi() 和 atof()。因为一旦输入字符串格式不对,它们会静默返回 0,你根本不知道转换失败了。用 strtol() 和 strtod() 配合 endptr 参数,可以精确判断哪里出错了。

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

int main() {
    // 安全的字符串转整数
    const char *str = "123abc";
    char *endptr;
    long val = strtol(str, &endptr, 10);
    
    if (*endptr != '\0') {
        printf("转换部分成功: %ld, 非法字符: %s\n", val, endptr);
    } else {
        printf("完全转换: %ld\n", val);
    }

    // 安全的字符串转浮点数
    const char *fstr = "3.14xyz";
    double fval = strtod(fstr, &endptr);
    if (*endptr != '\0') {
        printf("浮点转换部分成功: %f, 非法字符: %s\n", fval, endptr);
    }

    // 使用 snprintf 安全格式化
    char buf[32];
    int n = snprintf(buf, sizeof(buf), "温度: %.1f°C", 25.5);
    if (n >= sizeof(buf)) {
        printf("缓冲区太小,需要 %d 字节\n", n + 1);
    } else {
        printf("格式化结果: %s\n", buf);
    }

    return 0;
}

27.5 知识体系总览

下面这张图,把本章涉及的标准库函数之间的关系梳理清楚了。你想想看,这些函数虽然分属不同头文件,但在实际项目中经常配合使用——比如用 time() 获取种子,用 rand() 生成随机数,再用 snprintf() 格式化成字符串。

标准库函数(本章核心) 数学函数 math.h sin/cos/tan sqrt/pow/fabs floor/ceil/fmod 注意FPU开启 时间函数 time.h time() 获取时间戳 localtime() 转换 strftime() 格式化 clock() 测性能 随机数函数 stdlib.h srand() 设种子 rand() 生成随机数 取模控制范围 伪随机,不用于加密 字符串转换 stdlib.h atoi/atol/atof strtol/strtod snprintf 安全格式化 优先用带错误检测的 实际项目中的配合使用 time() 获取种子 → srand() 初始化 → rand() 生成随机数 snprintf() 格式化时间/数值 → 输出到日志或显示 strtol() 安全解析用户输入 → 避免程序崩溃

核心总结:标准库函数是C语言的基石。数学函数注意精度和性能,时间函数注意跨平台差异,随机数函数一定要初始化种子,字符串转换优先用带错误检测的版本。这些经验,都是我一行一行代码踩坑踩出来的。


公众号:蓝海资料掘金营,微信deep3321