这一章在干嘛?
按头文件分逛标准库:stdlib/math 的数值函数、time 时间、setjmp 非本地跳转、signal 信号、assert 断言、qsort/bsearch 排序查找。不求背,求「知道有这个东西」。
16.1 整型与浮点函数:stdlib.h / math.h
16.1 整型与浮点函数:stdlib.h / math.h
| 头文件 | 函数 | 一句话 |
|---|---|---|
| stdlib.h | abs / labs / div | 绝对值 / 商+余数一次算 |
| stdlib.h | rand / srand | 伪随机数:rand 返回 0~RAND_MAX,srand 设种子(不设每次运行同序列) |
| stdlib.h | strtol / strtod | 字符串转数值的严谨版:能报错、能拿「没读完的尾巴」 |
| math.h | sin/cos/tan、exp/log/log10、pow/fabs/floor/ceil/fmod | 三角、指数对数、幂、取整;参数和返回都是 double |
/* strtol 优于 atoi:能检测「根本不是数字」和「溢出」 */
char *end;
errno = 0;
long v = strtol("123xyz", &end, 10);
if (end == "123xyz") /* 一个字符都没转 */
fprintf(stderr, "不是数字\n");
else if (*end != '\0') /* 123 读到了,后面剩 xyz */
fprintf(stderr, "尾巴:%s\n", end);
16.2 时间与随机数:time.h
clock_t c0 = clock(); /* 处理器时间(测耗时) */
do_work();
double sec = (double)(clock() - c0) / CLOCKS_PER_SEC;
time_t now = time(NULL); /* 当天时间:从纪元起的秒数 */
struct tm *t = localtime(&now);
printf("%04d-%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);
char buf[64];
strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", t); /* 格式化时间 */
puts(buf);
两套时钟:clock() 测「程序用了多少 CPU 时间」,time() 给「墙上的钟」。测性能用前者,打日志用后者。struct tm 的年份从 1900 数、月份从 0 数——两个经典 off-by-one。
16.3 非本地跳转、信号与断言
/* setjmp/longjmp:跨函数「弹射」,错误处理的前身 */
jmp_buf env;
void deep(void)
{
longjmp(env, 1); /* 直接弹回 setjmp 处,跳过所有中间层 */
}
int main(void)
{
if (setjmp(env) == 0) /* 首次调用返回 0:正常流程 */
deep();
else /* longjmp 弹回后返回 1:错误流程 */
puts("从深处逃出来了");
}
/* signal:异步事件(Ctrl+C 等)的处理钩子 */
void on_int(int sig) { (void)sig; exit(0); }
signal(SIGINT, on_int); /* SIGFPE 算术错误 / SIGSEGV 段违例 ... */
/* assert:调试期契约检查,发布版 #define NDEBUG 一键关闭 */
assert(p != NULL && "指针不能为空"); /* 失败即打印位置并终止 */
setjmp 的限制:
弹回时中间函数的局部变量不可依赖(已销毁);volatile 修饰的局部变量才能保证值留存。signal 处理器内只做「设标志 + 返回」,别在里面干重活。
16.4 qsort 与 bsearch:标准排序查找
static int cmp_int(const void *a, const void *b)
{
int x = *(const int *)a, y = *(const int *)b;
return (x > y) - (x < y);
}
int arr[] = { 5, 3, 9, 1, 7 };
qsort(arr, 5, sizeof arr[0], cmp_int); /* 快排:任意类型 */
int key = 7;
int *hit = bsearch(&key, arr, 5, sizeof arr[0], cmp_int);
if (hit)
printf("找到了:%d\n", *hit);
两者共用同一套「元素比较函数」约定:返回负/零/正。bsearch 要求已排序——先 qsort 再 bsearch 是固定组合。比较函数写 (x>y)-(x<y) 而不是 x-y,可避免极端差值溢出。
本章通关标准:
转换字符串数值优先想到 strtol 而不是 atoi;记住 struct tm 的年月偏移;会用 qsort + 自定义比较函数排任意结构体数组。
1. atoi 和 strtol 谁更可靠?为什么?
strtol。atoi 出错时行为未定义(返回 0 或任意值),无法区分「输入是 0」和「输入非法」;strtol 通过 end 指针报告解析到哪、通过 errno 报告溢出,还能指定进制。生产代码一律 strtol/strtod。
2. clock() 和 time() 各测什么?
clock() 返回程序消耗的处理器时间(配合 CLOCKS_PER_SEC 换算秒),适合测代码耗时;time() 返回日历时间秒数(可转 struct tm 取年月日时分秒),适合日志时间戳。
3. qsort 的比较函数约定是什么?为什么写 (x>y)-(x<y)?
约定返回负数表示第一参数排前、正数排后、零相等。写 (x>y)-(x<y) 只产生 -1/0/1,而 x-y 在极端值(如 INT_MAX 减负数)会溢出得到相反符号,导致排序错乱。