26.1 stdarg.h:可变实参
带有可变参数的函数:带有可变数量参数的函数必须至少有一个“正常的”形式参数,在最后一个正常参数的后边始终会有省略号出现在参数列表的末尾。
可变部分实参提升:当调用带有可变实参列表的函数时,编译器会在匹配省略号的全部参数上执行默认的实参提升
(9.3)
- char -> int
- short -> int
- float -> double
va_arg宏(函数)
说明:获得所在函数的可变参数中的下一个参数
原型:stdarg.h
1 | /** |
va_start宏(函数)
说明:通过支持实参列表中可变长度部分开始的位置初始化va_list
类型的实参列表。
原型:stdarg.h
1 | /** |
va_end宏(函数)
说明:执行对va_list
类型变量的清理,使其无法被继续使用。
原型:stdarg.h
1 | /** |
1 | /** |
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 | /** |
vprintf函数
说明:向stdout输出,利用格式串控制输出的形式
关联的函数:vprintf函数
(22.3.1)
原型:stdio.h
1 | /** |
vsprintf函数
说明:类似printf函数和fprintf函数,唯一的不同是该函数会把输出写入指定字符数组而不是流中(会在末尾添加一个空字符)。
原型:stdio.h
1 | /** |
1 | /** |
26.2 stdlib.h: 通用的使用工具
说明:涵盖累全部不适合于任何其他头的函数。
26.2.1 字符串转换函数
字符串中的特殊子串:
- 十六进制浮点数
0x(X) |
一个或多个十六进制数字 (可能包括小数点 ) |
---|---|
- 无穷数:
INF
(不要求大小写)或`INFINITY(不要求大小写) - NaN
NAN |
( |
空 或一系列字符 |
) |
---|---|---|---|
不要求大小写 | 可以包含字母、数组或下划线,用于为NaN值的二进制表示指定某些位 或被nan函数 (23.4)调用 |
atof函数(c89之前)
说明:将字符串转换为相应的double
类型
- 会跳过字符串开始处的空白字符
- 可以以
+
、-
开头 - 把每个字符看作相应的数字,比如
"234"
会被看作234
- 在遇到第一个不属于数的字符处停止
限制:
- 不能支持转换过程中处理了字符串中的多少字符
- 不能支持转换失败的具体情况(不能保证会修改
errno
变量的值)
原型:stdlib.h
1 | /* |
atoi函数(c89之前)
说明:将字符串转换为相应的int
类型
- 会跳过字符串开始处的空白字符
- 可以以
+
、-
开头 - 把每个字符看作相应的数字,比如
"234"
会被看作234
- 在遇到第一个不属于数的字符处停止
限制:
- 不能支持转换过程中处理了字符串中的多少字符
- 不能支持转换失败的具体情况(不能保证会修改
errno
变量的值)
原型:stdlib.h
1 | /* |
atol函数(c89之前)
说明:将字符串转换为相应的long int
类型
- 会跳过字符串开始处的空白字符
- 可以以
+
、-
开头 - 把每个字符看作相应的数字,比如
"234"
会被看作234
- 在遇到第一个不属于数的字符处停止
限制:
- 不能支持转换过程中处理了字符串中的多少字符
- 不能支持转换失败的具体情况(不能保证会修改
errno
变量的值)
原型:stdlib.h
1 | /* |
strtod函数(c89)
说明:将字符串转换为double
,相比atof函数(c89之前)
- 返回值为
double
而不是float
- 字符串可以
包含16进制的浮点数
、无穷数
或NaN
(同strtold
、strtof
) - 可以检测超出范围:如果转换得到的值超出了函数返回类型的表示范围,那么会在
errno
变量中存储ERANGE
,且返回正或负的HUGE_VAL
(23.3) - 更复杂:第二个参数指定停止转换的位置(当传递NULL是将不会指定)
- 可以检测转换失败的细节:如果不能转换,将把要转换的字符串赋值给第二个参数指向的变量(前提是该参数非
NULL
)
原型:stdlib.h
1 | /* |
strtol函数(c89)
说明:将字符串转换为long int
,相比atol函数(c89之前)
- 可以检测超出范围:如果转换得到的值超出了函数返回类型的表示范围,那么会在
errno
变量中存储ERANGE
,且返回LONG_MIN
或LONG_MAX
- 更复杂:第二个参数指定停止转换的位置(当传递NULL是将不会指定)
- 可以检测转换失败的细节:如果不能转换,将把要转换的字符串赋值给第二个参数指向的变量(前提是该参数非
NULL
)
原型:stdlib.h
1 | /* |
strtoul函数(c89)
说明:将字符串转换为long int
- 可以检测超出范围:如果转换得到的值超出了函数返回类型的表示范围,那么会在
errno
变量中存储ERANGE
,且返回LONG_MIN
或LONG_MAX
- 更复杂:第二个参数指定停止转换的位置(当传递NULL是将不会指定)
- 可以检测转换失败的细节:如果不能转换,将把要转换的字符串赋值给第二个参数指向的变量(前提是该参数非
NULL
)
原型:stdlib.h
1 | /* |
strtold函数(C99)
说明:将字符串转换为long double
,相比strtod函数(c89)
- 转换为
long double
- 字符串可以
包含16进制的浮点数
、无穷数
或NaN
(同strtod
、strtof
)
原型:stdlib.h
1 | /* |
atoll函数(c99)
说明:将字符串转换为long long int
,和atol函数(c89之前)
相比
- 转换为
long long int
- 字符串可以
包含16进制的浮点数
、无穷数
或NaN
原型:stdlib.h
1 | /* |
strtof函数(C99)
说明:将字符串转换为float
,相比strtod函数(c89)
- 转换为
float
- 字符串可以
包含16进制的浮点数
、无穷数
或NaN
(同strtod
、strtold
)
原型:stdlib.h
1 | /* |
strtoll函数(C99)
说明:将字符串转换为long long int
,类似strtol函数(c89)
原型:stdlib.h
1 | /* |
strtoull函数(C99)
说明:将字符串转换为unsigned long long int
,类似strtoul函数(c89)
原型:stdlib.h
1 | /* |
程序:测试数值转换函数
1 | /** |
26.2.3 伪随机序列生成函数
rand函数
描述:返回一个0~RAND_MAX(stdlib.h)
的随机数。
说明:rand函数
返回的数事实上不是随机的,这些数是由“种子”值(默认为1)产生的,但对于偶然的观察者而言,rand函数似乎能能够产生不相关的数值序列。
原型:stdlib.h
1 | /** |
srand函数
描述:在rand函数
执行前执行该函数来为rand函数
提供种子值。
注意:同一个种子值对应着一组特定的随机序列
技巧:把time函数
的返回值传递给srand函数
设置“随机化”的种子值,这样可以使rand函数
在每次运行时的行为都不相同。
1 | /** |
26.2.3 与环境的通信
说明:一组简单的操作系统接口。
- 正常或不正常地终止程序,并且想操作系统反悔一个状态码
- 从用户的外部环境获取信息
- 执行操作系统的命令
exit函数
描述:终止程序,并返回状态码给操作系统。
细节:通常还会在后台做一些最后的动作
- 清洗包好未输出数据的输出缓冲区
- 关闭打开的流
- 删除临时文件
- 调用通过
atexit函数
注册的在程序终止时要调用的函数
说明:通常等价于在main函数
中执行return n
,具有可移植性的状态码包括
- EXIT_SUCCESS宏:正常退出(整数0)
- EXIT_FAILURE宏:非正常退出
注意:exit函数
和return语句
的不同表现在
区别 | exit函数 | return语句 |
---|---|---|
main函数 中局部变量的生命周期 |
不结束 | 结束(如果用atexit函数 注册的函数或清洗输出流的缓冲区 访问这些变量就会出现问题) |
使用int 之外的类型 |
可以 | 必须和mian函数 的返回类型保持一致,否则 |
原型:stdlib.h
1 | /** |
atexit函数
说明:当把函数指针传递给aexit函数
时,它会把指针保存起来,当程序正常终止时会被调用。
- 通过
exit函数
被调用或main函数
中的return
语句触发 - 如果注册了两个或更多函数,那么将按照与注册顺序
相反
的顺序调用它们
原型:stdlib.h
1 | /** |
_Exit函数(c99)
描述:终止程序,并返回状态码给操作系统
说明:类似exit函数
- 不一定会清洗输出缓冲区(由实现定义)
- 不一定会关闭打开的流
- 不一定会删除临时文件
- 不会调用通过
atexit函数
注册的在程序终止时要调用的函数 - 不回发送信号触发
signal函数
(24.3)注册的函数
原型:stdlib.h
1 | /** |
abort函数
描述:通常会导致异常的程序终止,长生SIGABRT信号
并向系统返回一个表示“不成功”的状态码。
注意:有一种情况下不会导致程序终止,那就是通过signal函数
为SIGABRT信号
注册处理函数,且处理函数调用了longjmp函数
恢复到之前的执行环境。
说明:类似exit
函数,特点是
- 不一定会清洗输出缓冲区(由实现定义)
- 不一定会关闭打开的流
- 不一定会删除临时文件
原型:stdlib.h
1 | void abort (void); |
getenv函数
描述:提供了访问用户环境中的任意字符串(环境变量)的功能。
说明:获得的指针指向的字符串是静态的,有可能会被其它函数调用或系统自身修改。
原型:stdlib.h
1 | /** |
1 | /* "/usr/local/bin:/bin:/usr/bin:." */ |
system函数
描述:运行另一个c程序(可以是操作系统命令)
说明:类似在操作系统终端使用命令行
- 参数:以
NULL
作为参数有特殊含义 - 返回值:(由实现定义)通常情况下返回要求它运行的那个程序的终止状态码
原型:stdlib.h
1 | /** |
1 | // 调用UNIX(Linux)的ls命令,并将结果存入myfiles |
26.2.4 搜索和排序使用工具
bsearch函数
描述:根据键在有序数组
中搜索一个特定的值。
说明:通常会使用二分搜索算法
来搜索在数组中搜索
原型:stdlib.h
1 | /** |
qsort函数
说明:为数组进行排序
相关:17.1
原型:stdlib.h
1 | /** |
程序:确定航空里程
1 | /** |
26.2.5 整数运算函数
abs函数
说明:返回int
类型的绝对值。
原型:stdlib.h
1 | /** |
labs函数
说明:返回long int
类型的绝对值。
原型:stdlib.h
1 | /** |
llabs函数
说明:返回long long int
类型的绝对值。
原型:stdlib.h
1 | /** |
div函数
说明:用第一个参数除以第二个参数,返回含有商
和余数
的结构体。
优点(相比%
和/
):同ldiv
|比较|div(ldiv)函数
|%
和/
|
|—|—|—|
|效率|可能更高,许多计算机可以在一条语句中计算出商和余数|效率低,需要多个指令|
|负操作数舍入方式|商:向零舍入;余数:n(原始数) = q(商) x d(除数) + r(余数)
|c89中由实现定义,c99中同div(ldiv)函数
|
原型:stdlib.h
1 | /** |
ldiv函数(c99)
说明:用第一个参数除以第二个参数,返回含有商
和余数
的结构体。
优点(相比%
和/
):同div
|比较|ldiv(div)函数
|%
和/
|
|—|—|—|
|效率|可能更高,许多计算机可以在一条语句中计算出商和余数|效率低,需要多个指令|
|负操作数舍入方式|商:向零舍入;余数:n(原始数) = q(商) x d(除数) + r(余数)
|c89中由实现定义,c99中同ldiv(div)函数
|
原型:stdlib.h
1 | /** |
lldiv函数(c99)
说明:用第一个参数除以第二个参数,返回含有商
和余数
的结构体。
原型:stdlib.h
1 | /** |
26.3 time.h:日期和时间
说明:存储时间的数据结构。
clock_t
:按照“时钟滴答”进行度量的时间值。time_t
:紧凑的时间和日期编码(日历时间)struct tm
:包含分解的时间的结构体
❶ 允许两个额外的“闰秒”。c99中最大值为60。
❷ 如果夏令时有效,就为正数;如果无效,为零;如果这一信息未知,就为负数。
26.3.1 时间处理函数
clock函数
说明:返回程序从开始执行到当前时刻的处理器时间(时钟数)。
原型:stdlib.h
1 | /** |
技巧:
- 为了将处理器时间转换为秒,将其除以
CLOCK_PER_SEC
(定义在time.h
)。 - 当用
clock
函数来确定程序已经运行多长时间时(不包括到达main函数
之前的时间),习惯做法时调用clock函数
两次:一次在main函数
开始处,一次在程序要终止之前。
1 |
|
time函数
说明:返回当前的日历时间。
- 如果参数不为
NULL
,那么time函数
还会把日历时间存储在实参指向的空间中。
原型:time.h
1 | /** |
1 | // 方式一:使用返回值 |
difftime函数
说明:通过两个时间点的日历时间,返回两个时间点之间的时间差(秒)
用途:计算程序的运行时间。
原型:time.h
1 | /** |
1 |
|
mktime函数
说明:把struct tm
(分解时间)类型的时间转换为日历时间并返回。
用途:对于和时间、日期相关的计算非常有用。
副作用:会按照一定规则调整结构的成员
- 改变值不在合法范围内的成员:一个成员的数值的调整可能会导致接连对其它成员的调整,直到全部合法。
- 初始化
tm_wday
(一星期的第几天)和tm_yday
(一年中的第几天)
1 | /* 声明分解时间 */ |
26.3.2 时间转换函数
说明:转换示意图
注意:其中的mktime函数
被C标准定义为处理函数
而不是转换函数
。
gmtime函数
说明:将日历时间转换为分解时间,是UTC
(协调世界时间)。
注意:返回值指向的是一个静态分配的结构,会被后续的getime函数
或localtime函数
调用修改。
1 | /** |
localtime函数
说明:将日历时间转换为分解时间,是本地时间。
原型:time.h
1 | /** |
asctime函数
说明:将分解时间转换为字符串格式。
Sun Jun 3 17:48:34 2007\n
注意:返回的字符串是一个静态分配的结构,会被后续的asctime函数
或strftime函数
调用修改。
原型:time.h
1 | /** |
ctime函数
说明:将日历时间转换为字符串格式。
注意:返回的字符串是一个静态分配的结构,会被后续的asctime函数
或strftime函数
调用修改。
原型:time.h
1 | /** |
strftime函数
描述:把分解时间转换成字符串格式。
注意:函数对地区敏感,改变LC_TIME
可能会影响转换说明符的行为。
转换说明符:
针对
"C"
地区(c89~c99),支持ISO 8601
一些转换说明符在
"C"
地区的替换字符串(c99)E
或O
修饰符:修改特定的转换说明符,替换为依赖当前地区的备选格式("C"
地区忽略E
或O
)(c99)
说明:类似asctime函数
,但提供了大量对时间进行格式化的控制。
1 | /** |
程序:显示日期和时间
1 | /** |
1 | $ ./datetime |