26 其他库函数

26.1 stdarg.h:可变实参

带有可变参数的函数:带有可变数量参数的函数必须至少有一个“正常的”形式参数,在最后一个正常参数的后边始终会有省略号出现在参数列表的末尾。
可变部分实参提升:当调用带有可变实参列表的函数时,编译器会在匹配省略号的全部参数上执行默认的实参提升9.3

  • char -> int
  • short -> int
  • float -> double

va_arg宏(函数)


说明:获得所在函数的可变参数中的下一个参数
原型:stdarg.h

1
2
3
4
5
6
/**
* @param {va_list} ap 指向可变参数列表的某个位置
* @param {类型} 期待相应位置实参的类型
* @return {类型} 相应位置(下一个)实参
*/

类型 va_arg(va_list ap, 类型);

va_start宏(函数)


说明:通过支持实参列表中可变长度部分开始的位置初始化va_list类型的实参列表。
原型:stdarg.h

1
2
3
4
5
/**
* @param {va_list} ap 存储参数列表的变量
* @param {*} paramN 可变参数前的最后一个正常实参
*/

void va_start(va_list ap, paramN);

va_end宏(函数)


说明:执行对va_list类型变量的清理,使其无法被继续使用。
原型:stdarg.h

1
2
3
4
/**
* @param {va_list} ap 可变实参部分的列表
*/

void va_end(va_list ap);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @param {int} n 后面可变参数的数量
* @param {*} ... 可变参数部分
* @return {int} 可变实参中的最大值
*/

int max_int (int n, ...) {
// 声明存储可变实参列表的变量
va_list ap;
int i, current, largest;
// 初始化存储可变部分的实参的列表
va_start(ap, n);
// 遍历寻找最大值
largest = va_arg(ap, int);
for (i = i; i < n; i ++) {
current = va_arg(ap, int);
if (current > largest) {
largest = current;
}
}
// 清理存储可变实参的列表
va_end(ap);
return largest;
}

26.1.1 调用可变实参列表的函数

说明:调用带有可变实参列表的函数带有一定风险,需要一些措施规避

  • 确定可变参数数量
    例子:max_int函数依靠第一个是惨知名跟随其后的其他参数的数量

  • 确定每种参数的类型
    例子:像printf函数和scanf函数依靠格式化字符串来描述其他的参数的数量和每种参数的类型

  • 特别处理NULL作为参数:当把NULL(0)传递给带有可变实参列表的函数时,编译器会假定它表示的是一个整数
    强制类型转换:(void *) NULL代替NULL

26.1.2 v…printf类函数

说明:...printf类函数相比,由一个va_list类型的参数取代了可变参数部分。
用途:编写“包装”函数。外部的包装函数接受可变数量的实参,并且稍后把这些参数传递给v...printf类函数

vfprintf函数


说明:向第一个参数说明的流(任何输出流)中写输出。
关联的函数:vfprintf函数(22.3.1)
原型:stdio.h

1
2
3
4
5
6
7
/**
* @param {FILE *} stream 文件指针(流)
* @param {char *} format 格式串
* @param {va_list} arg 可变参数数组
* @return {int} 写入的字符数:成功;负值:写入出错
*/

int vfprintf(FILE * restrict stream, const char * restrict format, va_list arg);

vprintf函数


说明:向stdout输出,利用格式串控制输出的形式
关联的函数:vprintf函数(22.3.1)
原型:stdio.h

1
2
3
4
5
6
/**
* @param {char *} format 格式串
* @param {va_list} arg 可变参数数组
* @param {int} 写入的字符数:成功;负值:写入出错
*/

int vprintf(const char * restrict format, va_list rga);

vsprintf函数


说明:类似printf函数和fprintf函数,唯一的不同是该函数会把输出写入指定字符数组而不是流中(会在末尾添加一个空字符)。
原型:stdio.h

1
2
3
4
5
6
7
/**
* @param {char *} s 字符数组
* @param {char *} format 格式串
* @param {va_list} arg 可变参数数组
* @return {int} 写入的字符数(不包括空字符):成功;负值:写入出错
*/

int vsprintf(char * restrict s, const char * restrict format, va_list arg);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 定制错误输出
* @param {char *} format 格式化字符串
* @param {...*} 可变参数部分
* @return {int} 输出的字符数
*/

int errorf(const char *format, ...) {
static int num_errors = 0;
int n;
// 声明可变数组列表
va_list ap;
num_errors++;
// 打印需要增强的部分
fprintf(stderr, "** Error %d: ", num_errors);
// 初始化可变数组列表
va_start(ap, format);
// 按照格式串向stderr写入字符串(打印到屏幕)
n = vfprintf(stderr, format, ap);
// 清理可变参数列表
va_end(ap);
// 按照格式串向stderr写入“换行”
fprintf(stderr, "\n");
return n;
}

26.2 stdlib.h: 通用的使用工具

说明:涵盖累全部不适合于任何其他头的函数。

  • 字符串转换函数
  • 伪随机序列生成函数
  • 内存管理函数
  • 与外部环境的通信
  • 搜索与排序使用工具
  • 整数算术运算函数(17)
  • 多子节字符和字符串函数(25.2)

26.2.1 字符串转换函数

字符串中的特殊子串:

  • 十六进制浮点数
0x(X) 一个或多个十六进制数字(可能包括小数点
  • 无穷数INF(不要求大小写)或`INFINITY(不要求大小写)
  • NaN
NAN ( 一系列字符 )
不要求大小写 可以包含字母、数组或下划线,用于为NaN值的二进制表示指定某些位或被nan函数(23.4)调用

atof函数(c89之前)


说明:将字符串转换为相应的double类型

  • 会跳过字符串开始处的空白字符
  • 可以以+-开头
  • 把每个字符看作相应的数字,比如"234"会被看作234
  • 在遇到第一个不属于数的字符处停止

限制:

  • 不能支持转换过程中处理了字符串中的多少字符
  • 不能支持转换失败的具体情况(不能保证会修改errno变量的值)

原型:stdlib.h

1
2
3
4
5
/*
* @param {char *} nptr 字符串
* @return {double} 相应的数字:成功;0:不能执行转换(字符串为空或镇前导空白之后的字符的形式不符合函数的要求)
*/

double atof (const char *nptr);

atoi函数(c89之前)


说明:将字符串转换为相应的int类型

  • 会跳过字符串开始处的空白字符
  • 可以以+-开头
  • 把每个字符看作相应的数字,比如"234"会被看作234
  • 在遇到第一个不属于数的字符处停止

限制:

  • 不能支持转换过程中处理了字符串中的多少字符
  • 不能支持转换失败的具体情况(不能保证会修改errno变量的值)

原型:stdlib.h

1
2
3
4
5
/*
* @param {char *} nptr 字符串
* @return {int} 相应的数字:成功;0:不能执行转换(字符串为空或镇前导空白之后的字符的形式不符合函数的要求)
*/
int atoi (const chat *nptr);

atol函数(c89之前)


说明:将字符串转换为相应的long int类型

  • 会跳过字符串开始处的空白字符
  • 可以以+-开头
  • 把每个字符看作相应的数字,比如"234"会被看作234
  • 在遇到第一个不属于数的字符处停止

限制:

  • 不能支持转换过程中处理了字符串中的多少字符
  • 不能支持转换失败的具体情况(不能保证会修改errno变量的值)

原型:stdlib.h

1
2
3
4
5
/*
* @param {char *} nptr 字符串
* @return {long int} 相应的数字:成功;0:不能执行转换(字符串为空或镇前导空白之后的字符的形式不符合函数的要求)
*/

long int atol (const char *nptr);

strtod函数(c89)


说明:将字符串转换为double,相比atof函数(c89之前)

  • 返回值为double而不是float
  • 字符串可以包含16进制的浮点数无穷数NaN(同strtoldstrtof)
  • 可以检测超出范围:如果转换得到的值超出了函数返回类型的表示范围,那么会在errno变量中存储ERANGE,且返回正或负的HUGE_VAL(23.3)
  • 更复杂:第二个参数指定停止转换的位置(当传递NULL是将不会指定)
  • 可以检测转换失败的细节:如果不能转换,将把要转换的字符串赋值给第二个参数指向的变量(前提是该参数非NULL
    原型:stdlib.h
1
2
3
4
5
6
/*
* @param {char *} nptr 字符串
* @param {char **} endptr 转换停止的位置
* @return {double} 正或负的HUGE_VAL(23.3):转换得到的值超过反回类型范围(并在errno中存储RANGE);浮点数:成功转换;0:不能转换
*/

double strtod (const char * restrict nptr, char ** restrict endptr);

strtol函数(c89)


说明:将字符串转换为long int,相比atol函数(c89之前)

  • 可以检测超出范围:如果转换得到的值超出了函数返回类型的表示范围,那么会在errno变量中存储ERANGE,且返回LONG_MINLONG_MAX
  • 更复杂:第二个参数指定停止转换的位置(当传递NULL是将不会指定)
  • 可以检测转换失败的细节:如果不能转换,将把要转换的字符串赋值给第二个参数指向的变量(前提是该参数非NULL

原型:stdlib.h

1
2
3
4
5
6
7
/*
* @param {char *} nptr 字符串
* @param {char **} endptr 转换停止的位置
* @param {int} base 待转换数的基数(进制,2~36)
* @return {long int} LONG_MIN或LONG_MAX:转换得到的值超过反回类型范围(并在errno中存储RANGE);浮点数:成功转换;0:不能转换
*/

long int strtol (const char * restrict nptr, char ** restrict endptr, int base);

strtoul函数(c89)


说明:将字符串转换为long int

  • 可以检测超出范围:如果转换得到的值超出了函数返回类型的表示范围,那么会在errno变量中存储ERANGE,且返回LONG_MINLONG_MAX
  • 更复杂:第二个参数指定停止转换的位置(当传递NULL是将不会指定)
  • 可以检测转换失败的细节:如果不能转换,将把要转换的字符串赋值给第二个参数指向的变量(前提是该参数非NULL

原型:stdlib.h

1
2
3
4
5
6
7
/*
* @param {char *} nptr 字符串
* @param {char **} endptr 转换停止的位置
* @param {int} base 待转换数的基数(进制,2~36)
* @return {unsigned long int} ULONG_MAX或ULONG_MIN:转换得到的值超过反回类型范围(并在errno中存储RANGE);浮点数:成功转换;0:不能转换
*/

unsigned long int strtoll (const char * restrict nptr, char ** restrict endptr, int base);

strtold函数(C99)


说明:将字符串转换为long double,相比strtod函数(c89)

  • 转换为long double
  • 字符串可以包含16进制的浮点数无穷数NaN(同strtodstrtof)

原型:stdlib.h

1
2
3
4
5
6
/*
* @param {char *} nptr 字符串
* @param {char **} endptr 转换停止的位置
* @return {long double} 正或负的HUGE_VAL(23.3):转换得到的值超过反回类型范围(并在errno中存储RANGE);浮点数:成功转换;0:不能转换
*/

long double strtold (const char * restrict nptr, char ** restrict endptr);

atoll函数(c99)


说明:将字符串转换为long long int,和atol函数(c89之前)相比

  • 转换为long long int
  • 字符串可以包含16进制的浮点数无穷数NaN

原型:stdlib.h

1
2
3
4
5
/*
* @param {char *} nptr 字符串
* @return {long int} 相应的数字:成功;0:不能执行转换(字符串为空或镇前导空白之后的字符的形式不符合函数的要求)
*/

long long int atol (const char *nptr);

strtof函数(C99)


说明:将字符串转换为float,相比strtod函数(c89)

  • 转换为float
  • 字符串可以包含16进制的浮点数无穷数NaN(同strtodstrtold)

原型:stdlib.h

1
2
3
4
5
6
/*
* @param {char *} nptr 字符串
* @param {char **} endptr 转换停止的位置
* @return {float} 正或负的HUGE_VAL(23.3):转换得到的值超过反回类型范围(并在errno中存储RANGE);浮点数:成功转换;0:不能转换
*/

float strtof (const char * restrict nptr, char ** restrict endptr);

strtoll函数(C99)


说明:将字符串转换为long long int,类似strtol函数(c89)
原型:stdlib.h

1
2
3
4
5
6
7
/*
* @param {char *} nptr 字符串
* @param {char **} endptr 转换停止的位置
* @param {int} base 待转换数的基数(进制,2~36)
* @return {long int} LONG_MIN或LONG_MAX:转换得到的值超过反回类型范围(并在errno中存储RANGE);浮点数:成功转换;0:不能转换
*/

long long int strtoll (const char * restrict nptr, char ** restrict endptr, int base);

strtoull函数(C99)


说明:将字符串转换为unsigned long long int,类似strtoul函数(c89)
原型:stdlib.h

1
2
3
4
5
6
7
/*
* @param {char *} nptr 字符串
* @param {char **} endptr 转换停止的位置
* @param {int} base 待转换数的基数(进制,2~36)
* @return {unsigned long long int} ULONG_MAX或ULONG_MIN:转换得到的值超过反回类型范围(并在errno中存储RANGE);浮点数:成功转换;0:不能转换
*/

unsigned long long int strtoull (const char * restrict nptr, char ** restrict endptr, int base);

程序:测试数值转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Test c89 numeric conversion functions
*/

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

#define CHK_VALID printf(" %s %s\n", \
errno != ERANGE ? "Yes" : "No ", \
*ptr == '\0' ? "Yes" : "No");

int main (int argc, char const *argv[]) {

char *ptr;
/* 检测程序启动方式是否正确 */
if (argc != 2) {
printf("usage: tnumconv string\n");
exit(EXIT_FAILURE);
}
/* 使用c89之前的3个旧转换函数 */
printf("Function Return Value\n");
printf("-------- --------\n");
printf("atof %g\n", atof(argv[1]));
printf("atoi %d\n", atoi(argv[1]));
printf("atol %ld\n", atol(argv[1]));
printf("Function Return Value Valid? "
"String Consumed?\n"
"-------- ------------ ------ "
"------------------\n");
errno = 0;
printf("strtod %-12g\n", strtod(argv[1], &ptr));
CHK_VALID;

errno = 0;
printf("strtol %-12ld\n", strtol(argv[1], &ptr, 10));
CHK_VALID;

errno = 0;
printf("strtoul %-12lu\n", strtoul(argv[1], &ptr, 10));
CHK_VALID;
return 0;
}

Alt text

26.2.3 伪随机序列生成函数

rand函数


描述:返回一个0~RAND_MAX(stdlib.h)的随机数。
说明:rand函数返回的数事实上不是随机的,这些数是由“种子”值(默认为1)产生的,但对于偶然的观察者而言,rand函数似乎能能够产生不相关的数值序列。
原型:stdlib.h

1
2
3
4
/**
* @return {int} `0~RAND_MAX(stdlib.h)`的随机数
*/

int rand (void);

srand函数

描述:rand函数执行前执行该函数来为rand函数提供种子值。
注意:同一个种子值对应着一组特定的随机序列
技巧:time函数的返回值传递给srand函数设置“随机化”的种子值,这样可以使rand函数在每次运行时的行为都不相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Tests the pseudo-random sequence generation functions
*/

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

int main (void) {
int i, seed;
printf("This program displays the first five value of "
"rand.\n");
for (;;) {
for (i = 0; i < 5; i++) {
printf("%d\n", rand());
}
printf("\n\n");
printf("Enter new seed value (0 to terminate):");
scanf("%d", &seed);
if (seed == 0) {
break;
}
srand(seed);
}
return 0;
}

26.2.3 与环境的通信

说明:一组简单的操作系统接口。

  1. 正常或不正常地终止程序,并且想操作系统反悔一个状态码
  2. 从用户的外部环境获取信息
  3. 执行操作系统的命令

exit函数


描述:终止程序,并返回状态码给操作系统。
细节:通常还会在后台做一些最后的动作

  • 清洗包好未输出数据的输出缓冲区
  • 关闭打开的流
  • 删除临时文件
  • 调用通过atexit函数注册的在程序终止时要调用的函数

说明:通常等价于在main函数中执行return n,具有可移植性的状态码包括

  • EXIT_SUCCESS宏:正常退出(整数0)
  • EXIT_FAILURE宏:非正常退出

注意:exit函数return语句的不同表现在

区别 exit函数 return语句
main函数中局部变量的生命周期 不结束 结束(如果用atexit函数注册的函数或清洗输出流的缓冲区访问这些变量就会出现问题)
使用int之外的类型 可以 必须和mian函数的返回类型保持一致,否则

原型:stdlib.h

1
2
3
4
/**
* @param {int} status 状态码
*/

void exit (int status);

atexit函数


说明:当把函数指针传递给aexit函数时,它会把指针保存起来,当程序正常终止时会被调用。

  • 通过exit函数被调用或main函数中的return语句触发
  • 如果注册了两个或更多函数,那么将按照与注册顺序相反的顺序调用它们

原型:stdlib.h

1
2
3
4
5
/**
* @param {void (*func)(void)} func 函数指针
* @return {int} 未知
*/
int atexit (void (*func)(void));

_Exit函数(c99)


描述:终止程序,并返回状态码给操作系统
说明:类似exit函数

  • 不一定会清洗输出缓冲区(由实现定义)
  • 不一定会关闭打开的流
  • 不一定会删除临时文件
  • 不会调用通过atexit函数注册的在程序终止时要调用的函数
  • 不回发送信号触发signal函数24.3)注册的函数

原型:stdlib.h

1
2
3
4
/**
* @param {int} status 状态码
*/

void _Exit (int status);

abort函数


描述:通常会导致异常的程序终止,长生SIGABRT信号并向系统返回一个表示“不成功”的状态码。
注意:有一种情况下不会导致程序终止,那就是通过signal函数SIGABRT信号注册处理函数,且处理函数调用了longjmp函数恢复到之前的执行环境。
说明:类似exit函数,特点是

  • 不一定会清洗输出缓冲区(由实现定义)
  • 不一定会关闭打开的流
  • 不一定会删除临时文件

原型:stdlib.h

1
void abort (void);

getenv函数


描述:提供了访问用户环境中的任意字符串(环境变量)的功能。
说明:获得的指针指向的字符串是静态的,有可能会被其它函数调用或系统自身修改。
原型:stdlib.h

1
2
3
4
5
/**
* @param {char *} name 环境变量名
* @return {char *} 指向静态分配的字符串的指针
*/

char *getenv (const char *name);
1
2
/* "/usr/local/bin:/bin:/usr/bin:." */
char *p = getenv("PATH");

system函数


描述:运行另一个c程序(可以是操作系统命令)
说明:类似在操作系统终端使用命令行

  • 参数:NULL作为参数有特殊含义
  • 返回值:(由实现定义)通常情况下返回要求它运行的那个程序的终止状态码

原型:stdlib.h

1
2
3
4
5
/**
* @param {char *} string 命令
* @return {int} 由实现定义)通常情况下返回要求它运行的那个程序的终止状态码
*/

int system (const char *string);
1
2
// 调用UNIX(Linux)的ls命令,并将结果存入myfiles
system("ls >myfiles");

26.2.4 搜索和排序使用工具

bsearch函数


描述:根据键在有序数组中搜索一个特定的值。
说明:通常会使用二分搜索算法来搜索在数组中搜索
原型:stdlib.h

1
2
3
4
5
6
7
8
/**
* @param {void *} key 指向键(匹配依据)的指针
* @param {void *} base 数组
* @param {size_t} size 每个元素的大小(字节)
* @param {Function *} compar 指向比较函数的指针
* @return {void *} 指向与键匹配的指针:匹配到;NULL:没匹配到
*/
void *bsearch (const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

qsort函数


说明:为数组进行排序
相关:17.1
原型:stdlib.h

1
2
3
4
5
6
/**
* @param {void *} base 指向数组需要排序的部分的第一个元素
* @param {size_t} nmemb 要排序的元素的数量
* @param {Function *} compare 指向排序函数的指针
*/
void qsort (void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

程序:确定航空里程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Determine air mileage from New York City.
*/

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

struct city_info {
char *city;
int miles;
};

int compare_cityes (const void *key_ptr, const void *element_ptr);

int main (void) {
char city_name[5];
struct city_info *ptr;
const struct city_info mileage[] = {
{
"Berlin", 3965
},
{
"Buenos Aires", 5297
},
{
"Cairo", 5602
},
{
"Calcutta", 7918
},
{
"Cape Town", 7764
}
};
printf("Enter city name:\n");
scanf("%80[^\n]", city_name);
ptr = bsearch(city_name, mileage, sizeof(mileage) / sizeof(mileage[0]), sizeof(mileage[0]), compare_cityes);
if (ptr != NULL) {
printf("%s is %d miles from New York City.\n", city_name, ptr->miles);
}
else {
printf("%s wasn't found.", city_name);
}
return 0;
}

int compare_cityes (const void *key_ptr, const void *element_ptr) {
return strcmp((char *) key_ptr, ((struct city_info *) element_ptr)->city);
}

26.2.5 整数运算函数

abs函数


说明:返回int类型的绝对值。
原型:stdlib.h

1
2
3
4
5
/**
* @param {int} j 需要求绝对值的整数
* @return {int} 绝对值
*/

int abs (int j);

labs函数


说明:返回long int类型的绝对值。
原型:stdlib.h

1
2
3
4
5
/**
* @param {long int} j 需要求绝对值的long int值
* @return {long int} 绝对值
*/

long int abs (long int j);

llabs函数


说明:返回long long int类型的绝对值。
原型:stdlib.h

1
2
3
4
5
/**
* @param {long long int} j 需要求绝对值的long int值
* @return {long long int} 绝对值
*/

long long int abs (long long int j);

div函数


说明:用第一个参数除以第二个参数,返回含有余数的结构体。
优点(相比%/):ldiv
|比较|div(ldiv)函数|%/|
|—|—|—|
|效率|可能更高,许多计算机可以在一条语句中计算出商和余数|效率低,需要多个指令|
|负操作数舍入方式|商:向零舍入;余数:n(原始数) = q(商) x d(除数) + r(余数)|c89中由实现定义,c99中同div(ldiv)函数|
原型:stdlib.h

1
2
3
4
5
6
/**
* @param {int} number 除数
* @param {int} denom 被除数
* @return {div_t} 含有商(quot)和余数(rem)的结构体
*/

div_t div (int number, int denom);

ldiv函数(c99)


说明:用第一个参数除以第二个参数,返回含有余数的结构体。
优点(相比%/):div
|比较|ldiv(div)函数|%/|
|—|—|—|
|效率|可能更高,许多计算机可以在一条语句中计算出商和余数|效率低,需要多个指令|
|负操作数舍入方式|商:向零舍入;余数:n(原始数) = q(商) x d(除数) + r(余数)|c89中由实现定义,c99中同ldiv(div)函数|
原型:stdlib.h

1
2
3
4
5
6
/**
* @param {long int} number 除数
* @param {long int} denom 被除数
* @return {div_t} 含有商(quot)和余数(rem)的结构体
*/

div_t ldiv (long int number, long int denom);

lldiv函数(c99)


说明:用第一个参数除以第二个参数,返回含有余数的结构体。
原型:stdlib.h

1
2
3
4
5
6
/**
* @param {long long int} number 除数
* @param {long long int} denom 被除数
* @return {div_t} 含有商(quot)和余数(rem)的结构体
*/

div_t ldiv (long long int number, long long int denom);

26.3 time.h:日期和时间

说明:存储时间的数据结构。

  • clock_t:按照“时钟滴答”进行度量的时间值。
  • time_t:紧凑的时间和日期编码(日历时间)
  • struct tm:包含分解的时间的结构体
    Alt text
    ❶ 允许两个额外的“闰秒”。c99中最大值为60。
    ❷ 如果夏令时有效,就为正数;如果无效,为零;如果这一信息未知,就为负数。

26.3.1 时间处理函数

clock函数


说明:返回程序从开始执行到当前时刻的处理器时间(时钟数)。
原型:stdlib.h

1
2
3
4
/**
* @return {clock_t} 程序从此启动到当前经过的处理器时间
*/

clock_t clock (void);

技巧:

  • 为了将处理器时间转换为秒,将其除以CLOCK_PER_SEC(定义在time.h)。
  • 当用clock函数来确定程序已经运行多长时间时(不包括到达main函数之前的时间),习惯做法时调用clock函数两次:一次在main函数开始处,一次在程序要终止之前。
1
2
3
4
5
6
7
8
9
10
11
#inlcude <stdio.h>
#include <time.h>

int main (void) {
/* 第一次调用 */
clock_t start_clock = clock();
...
/* 第二次调用 */
/* CLOCK_PER_SEC:类型由实现定义,c99指定其为clock_t类型 */
printf("Processor time used: %g sec.\n", (clock() - start_clock / (double) CLOCKS_PER_SEC);
}

time函数


说明:返回当前的日历时间。

  • 如果参数不为NULL,那么time函数还会把日历时间存储在实参指向的空间中。

原型:time.h

1
2
3
4
5
/**
* @param {time_t *} timer 指向存储当前日历时间的存储空间(可以为NULL)
* @return {time_t} 当前的日历时间
*/

time_t time (time_t *timer);
1
2
3
4
5
// 方式一:使用返回值
cur_time = time(NULL);

// 方式二:通过参数指定存储的位置
time(&cur_time);

difftime函数


说明:通过两个时间点的日历时间,返回两个时间点之间的时间差(秒)
用途:计算程序的运行时间。
原型:time.h

1
2
3
4
5
6
/**
* @param {time_t} 日历时间
* @param {time_t} 日历时间(较早的时间)
* @return {double} time0和time1之间按秒衡量的差值
*/

double difftime(time_t time1, time_t time0);
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <time.h>

int main () {
// 第一个日历时间
time_t start_time = time(NUULL);
...
// 第二个日历时间,并计算时间差
ptintf("Return time: %g sec.\n", difftime(NULL), start_time);
return 0;
}

mktime函数


说明:struct tm(分解时间)类型的时间转换为日历时间并返回。
用途:对于和时间、日期相关的计算非常有用。
副作用:会按照一定规则调整结构的成员

  • 改变值不在合法范围内的成员:一个成员的数值的调整可能会导致接连对其它成员的调整,直到全部合法。
  • 初始化tm_wday(一星期的第几天)和tm_yday(一年中的第几天)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 声明分解时间 */
struct tm t;

/* 初始化分解时间 */
t.tm_mday = 27;/* 日 */
t.tm_mon = 6;/* 月 */

/* 保证其他成员被正确初始化 */
t.tm_sec = 0;
t.tm_min = 0;
t.tm_hour = 0;
t.tm_isdst = -1;

/* 制造超出取值范围的情况 */
t.tm_mday = += 16; /* 43 */

/* 将分解时间转为日历时间,将导致原本的分解时间成员被修改 */
mktime(&t);

Alt text

26.3.2 时间转换函数

说明:转换示意图
Alt text
注意:其中的mktime函数被C标准定义为处理函数而不是转换函数

gmtime函数


说明:将日历时间转换为分解时间,是UTC(协调世界时间)。
注意:返回值指向的是一个静态分配的结构,会被后续的getime函数localtime函数调用修改。

1
2
3
4
5
/**
* @param {time_t} timer 日历时间
* @return {struct tm} 分解时间
*/

struct tm *gmtime (const time_t *timer);

localtime函数


说明:将日历时间转换为分解时间,是本地时间。
原型:time.h

1
2
3
4
/**
* @param {}
*/

struct tm *localtime (const time_t *timer);

asctime函数


说明:将分解时间转换为字符串格式。

Sun Jun 3 17:48:34 2007\n

注意:返回的字符串是一个静态分配的结构,会被后续的asctime函数strftime函数调用修改。
原型:time.h

1
2
3
4
5
/**
* @param {struct tm *} timeptr 指向分解时间结构体的指针
* @return {char *} 字符串形式的时间(ASCII时间)
*/

char *asctime (const struct tm *timeptr);

ctime函数


说明:将日历时间转换为字符串格式。
注意:返回的字符串是一个静态分配的结构,会被后续的asctime函数strftime函数调用修改。
原型:time.h

1
2
3
4
5
/**
* @param {time_t} time 日历时间
* @return {char *} 描述本地时间的字符串
*/

char *ctime (const time_t *time);

strftime函数


描述:把分解时间转换成字符串格式。
注意:函数对地区敏感,改变LC_TIME可能会影响转换说明符的行为。
转换说明符:

  • 针对"C"地区(c89~c99),支持ISO 8601
    Alt text
    Alt text

  • 一些转换说明符在"C"地区的替换字符串(c99)
    Alt text

    • EO修饰符:修改特定的转换说明符,替换为依赖当前地区的备选格式("C"地区忽略EO)(c99)
      Alt text

说明:类似asctime函数,但提供了大量对时间进行格式化的控制。

1
2
3
4
5
6
7
8
/**
* @param {char *} s 指向用来存储字符串形式的时间的空间
* @param {size_t} maxsize 存储在s中的字符数量上限
* @param {char *} format 格式串
* @param {struct tm *} timeptr 分解时间
* @return {size_t}
*/

size_t strftime (char * restrict s, size_t maxsize, const char * restrict format, const struct tm * restricy timeptr);

程序:显示日期和时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Display the current date and time in three formats
*/


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

int main(int argc, char const *argv[]) {
/* 1. 获得日历时间 */
time_t current = time(NULL);
struct tm *ptr;
char date_time[21];
int hour;
char am_or_pm;

/* 2. 打印日期时间:ctime默认的的字符串形式 */
puts(ctime(&current));

/* 2. 打印日期时间:strftime默认的字符串形式 */
strftime(date_time, sizeof(date_time), "%m-%d-%Y %I:%M%p\n", localtime(&current));
puts(date_time);

/* 2. 打印日期和时间:使用printf结合分解时间 */
ptr = localtime(&current);
hour = ptr->tm_hour;
if (hour <= 11) {
am_or_pm = 'a';
}
else {
hour -= 12;
am_or_pm = 'p';
}
if (hour == 0) {
hour = 12;
}
printf("%.2d-%.2d-%.2d %2d:%.2d%c\n", ptr->tm_mon + 1, ptr->tm_mday, ptr->tm_year + 1900, hour, ptr->tm_min, am_or_pm);
return 0;
}
1
2
3
4
5
6
$ ./datetime
Wed Nov 4 10:41:12 2015

11-04-2015 10:41AM

11-04-2015 10:41a