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);
malloc:分配 size 字节,不初始化calloc:分配 count*size 字节,全部初始化为 0realloc:调整已分配内存大小,可能移动地址free:释放内存
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 判断用于处理可能发生的运行时错误,始终存在。
📖 小节
math.h提供数学函数,三角函数参数是弧度,编译时需加-lmrand/srand生成伪随机数,srand(time(NULL))用时间做种子strtol/strtod比atoi/atof更安全,能检测转换错误qsort/bsearch是标准库的排序搜索工具,需要自定义比较函数assert用于开发调试,发布版本通过NDEBUG禁用
📝 作业
- 编写程序,生成 100 个 1-1000 的随机整数,用 qsort 排序后输出最大值和最小值
- 编写程序,用 clock() 比较冒泡排序和 qsort 排序 10000 个整数的时间差异
- 编写函数,用 ctype.h 的函数实现字符串的大小写转换(输入一个字符串,输出全大写和全小写版本)