14.12 time.h

“time.h”描述操作时间/日期的数据类型和函数原型,其中定义了在很多标准头中都定义过的宏“NULL”和数据类型“size_t”。

“time.h”中涉及到两种时间。一种是我们平时使用的自然时间也叫日历时间(Calendar Time),“time.h”用“time_t”这种数据类型来描述这种自然时间。

这个时间有一个起点,这个起点是1970年1月1日0点0分0秒,UNIX把这年看成是计算机元年。自然时间的数值为从那时开始经过的秒数。由于在UNIX中“time_t”被委托给为“long”类型,所以可以推算出“世界末日”应该在2038年(long数据能表示的最大值)。再以后就该时光倒流了(long数据能表示的最大值再增加,在一般的计算机上都会变成负值)。

这可能是UNIX设计时考虑的不够周到造成的。事实上C语言标准规定“size_t”应该是某种算术类型。然而算术类型也很讨厌,因为它可能是整数类型也可能是浮点类型,这会造成很难移植。

比如说在一种编译器下是“time_t”整数类型,再到“time_t”为浮点类型的环境中,要么代码必须写得很复杂,要么就必须改代码。

“time.h”中涉及的另一种时间是CPU所用的时间。无疑,这个时间用秒来度量是极其不合适的。不少书中把这种时间单位叫做“Clock Tick”(时钟周期或时钟滴答)。存储这种时间的数据的类型被定义为:clock_t。很遗憾,这也是某种算术类型。

由此产生了一个时间换算的问题。“time.h”通过定义一个宏“CLOCKS_PER_SEC”给出了这种换算关系,这个宏的意义是每秒钟合多少“Clock Tick”。显然在不同的计算机上这个值是不同的。

最后还有一个问题:用秒为单位来表示时间显然与我们平时的习惯相去甚远。如果不经过仔细烦琐的换算,没有人知道1970年1月1日0点0分0秒之后再过1269909084秒是个什么概念。

因此,为了和我们平时的习惯相适应,“time.h”又定义了一种时间数据类型,它把“time_t”换算为我们平时习惯的“1970年1月1日0点0分0秒”这样的由几个分量描述的形式。这种数据类型就是“struct tm”。C语言规定它至少要有下面一些成员。

14.12 time.h - 图1

不知道你是否已经培养出了足够细致的职业敏感。如果看到“又过了几秒钟”之后的“[0,60]”感觉有些奇怪,说明你已经具备了相当强烈的职业敏感。没察觉它实际是一个具有61个数的数值区间吗?没觉得它和“又过了几分钟”后面的“[0,59]”是如此强烈的不同吗?

这是因为地球的运转并不规律,天文学家和历法学家有时不得不在一年的最后一分钟加上一秒进行修正,即所谓的“润秒”。但是K&R居然把这个区间写成了[0,61],却是令我始终感到疑惑的问题。姑且存疑吧,也许本书的下一版时我会弄明白这个问题,那时再和你分享我的心得。

此外,C标准没有具体规定这些数据成员的次序。然而对于结构体数据来说,在初始化的时候这是必然要遇到的问题。所以在使用前应该查阅一下你的编译器的“time.h”中是按照什么样的顺序定义这个数据的。

这样,需要的数据类型和符号常量就定义完了。“time.h”在其余的部分描述了库函数中的几个与此有关的函数原型。

14.12 time.h - 图2

这个函数返回的是“从程序执行开始”到这个函数被调用时,CPU用了多少时间。没有人清楚地知道“从程序执行开始”的确切含义。函数调用返回的值没有绝对的意义,这个值只有相对意义。所以你会发现,一旦定义这种类型的变量,通常都需要定义两个这样的变量或等价于定义了两个。

在C语言的早期,这个函数常用来考察程序运行的时间。

程序代码14-8

14.12 time.h - 图3

14.12 time.h - 图4

不过在多任务操作系统下,这个方法还剩几分效果是很值得怀疑的。

14.12 time.h - 图5

这个函数是用来求当前相对“计算机元年”过了多少秒。很奇怪的是它有两种返回值的途径,随便你用哪个。比如,你可以

14.12 time.h - 图6

也可以

14.12 time.h - 图7

前一种方法比较直观,后一种方法通常速度比较快。

14.12 time.h - 图8

这个函数是求两个时间的差,单位是秒。

14.12 time.h - 图9

这个函数的功能是把“timeptr”结构体中的时间数据折换成“time_t”类型的数据返回。在返回之前,它还会对“timeptr”这个数据做些调整,把各个分量调整为恰当的取值范围。比如说原始的“*timeptr”数据中的“tm_sec”(//又过了几秒钟—[0,60])的值是68秒,那么函数调用完成时这个成员的值会变为8,多的60秒会被作为1分钟加到“tm_min”成员中。

14.12 time.h - 图10

这个函数的功能是把抽象的分量形式的“*timeptr”转换为我们能直接读阅的文字。下面来看一下其效果。

程序代码14-9

14.12 time.h - 图11

14.12 time.h - 图12

运行结果如图14-7所示。

14.12 time.h - 图13

图14-7 “asctime”函数的作用

在写这种类型数据初始化段的代码时,特别要注意的是各个分量的值除了“tm_mday”以外都是从0开始记数的。

14.12 time.h - 图14

这个函数的功能是把“*timer”转换成转换为我们能直接读阅的文字形式,它的功能和前面的asctime()函数类似,只是参数的类型不同而已。

14.12 time.h - 图15

由“*timer”求出对应的“struct tm”量,返回指向该数据的指针。这个函数让我感到非常困惑,原因在于我不清楚指针所指向的对象是在何处定义的。这个对象显然不可以是auto类别的局部变量,只能是static类别的局部变量或者是外部变量。我不清楚的是这样处理的理由是什么。

此外,要说的是这个函数得到的时间是UTC时间而不是本地时间。Localtime()函数得到的时间是本地时间。它的函数原型是:

14.12 time.h - 图16

可以看到两者的参数类型和返回值完全一样。

14.12 time.h - 图17

这个函数的功能有点像sprintf(),只是所输出的内容为时间。同时它也是一种格式化输出,同样用“%”作为格式转换的引导标志,但是这种转换的格式很多,所以就不占用篇幅逐个详细地解释了。下面是使用这个函数的一个示例。

程序代码14-10

14.12 time.h - 图18

输出结果如图14-8所示。

14.12 time.h - 图19

图14-8 “locltime”函数的作用