C++标准库之时间戳、时间段

以前的时间日期库只能支持到秒、毫秒。并不能支持微妙纳秒,C++11带来了chrono,来提供高精度的时间日期库。头文件 < chrono >

chrono这个库主要目的是为不同的系统提供高精度的时间和时钟。为了不用每隔10年为一种时间类型重新解读,这次chrono干脆整出了两个新的概念: 
duration:时间段 eg:2分钟、120秒 
timepoint:时间点,时间点是由两部分组成:时间段 + 起始时间。eg:2016年新年夜(解释方式:1970/01/01 + xxx秒。 这是unix和posix系统的计时方式)

命名空间:std::chrono

一个duration对象包含两个部分:一个表示ticks的值,一个秒的单位。

std::ratio可以用来表示小数点后面的部分 
ratio的英文翻译:比率、系数 
用x/y秒来描述时间的单位。eg:60s表示分钟单位;1/1000s表示毫秒单位等

std::chrono::duration d1(20);
std::chrono::duration, std::ratio<60>> d2(0.5);
std::chrono::duration, std::ratio<1, 1000>> d3(1);
 
   
   
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

分析一下上面的3行,duration是一个类模版,有两个模版参数,第一个表示数值ticks,第二个表示单位(可选,默认是秒)。 
先分析一下第二个参数:std::ratio< 60 > 也有两个参数,第二个参数是可选的,默认为1,第一个参数表示分子,第二个表示分母。结合duration来讲,就可以任意指定秒的单位:60s(一分钟)为单位,0.001s(1毫秒),100s(百秒,这个是自定义的)。正常生活中是年月日 时分秒 毫秒 微妙 纳秒 皮秒(c++11可以支持到纳秒)。

再看看上面的例子:第一个是20秒,第二个是半分钟,第三个是1毫秒。 
实际场景中绝大多数都是时分秒 一直到纳秒。c++11已经考虑到这些单位的常用性,为我们定义了6个常用的单位: 
std::chrono::hours 
std::chrono::minutes 
std::chrono::seconds 
std::chrono::milliseconds 
std::chrono::microseconds 
std::chrono::nanoseconds 
对应上面3个定义,可以用下面的代替

std::chrono::seconds d1(20);
std::chrono::minutes d2(0.5);
std::chrono::milliseconds(1);
 
   
   
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

下面说一下duration的算术操作: 
两个durtion的加减乘除 
durtion和ticks的之间的加减 
比较

这些算术操作需要注意他们的单位。

std::chrono::seconds d1(1); // 1std::chrono::milliseconds d2(1); // 1毫秒
 
   
   
   
   
  • 1
  • 2
  • 1
  • 2

d1 - d2: 
结果是999 单位是 毫秒 
如果不用标准库提供的单位:

std::chrono::durtion, std::ratio<1, 5>> d1(2); // 2/5std::chrono::durtion, std::ratio<1, 3>> d2(1); // 1/3
 
   
   
   
   
  • 1
  • 2
  • 1
  • 2

d1 + d2: 
结果是11 单位是1/15秒。15就是3和5的最大公约数

大部分算术操作和比较操作都适用于duration:eg:数值1和duration就没法进行比较操作。

不同的秒单位都可以进行隐式转换

duration的默认构造函数,只指定默认单位为秒,但是ticks值是未定的, 
拷贝构造函数可能发生单位的隐式转换

对于打印duration 标准库并没有提供<<操作 
我们可以重载操作符<<来做到:

template <typename V, typename R>
std::ostream& operator << (std::ostream& s, const std::chrono::duration& d)
{
  s << d.count() << " of " << R::num << "/" << R::den;

  return s;
}

// 调用
std::chrono::milliseconds d2(3);
std::cout << d2 << std::endl;
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

需要注意的是:上面这种写法,如果adl不工作,那么上面的函数也不会工作 
关于adl,以后再说。

这上面说的单位转换都是隐式的,但是有一个问题:向高精度转换是没问题的,向低精度转换有可能会丢失数据。这时可以用显示转换来避免这种情况:

std::chrono::seconds d1(55); // 55std::chrono::minutes d2(d1); // error
std::chrono::minutes d3 = std::chrono::duration_cast::chrono::minutes>(d1); // ok
 
   
   
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

使用durtion_cast来进行显示的单位转换。 
下面是一种特殊的情况,来补充上面”向高精度转换是没问题的”这点。 
半分钟(0.5) 转换成30秒,秒单位是从低精度转向了高精度(分钟转秒),但是ticks的值从double转成了int,从高精度转成了低精度,这时也会发生数据丢失,隐式转换会发生错误,需要使用显示转换duration_cast。

显示转换另一个用的比较多的地方:取一个duration的时分秒,是截断一部分信息,常用在打印部分。

std::chrono::milliseconds ms(12345679);
std::chrono::hours hour = duration_cast<std::chrono::hours>(ms % std::chrono::hours(1));
 
   
   
   
   
  • 1
  • 2
  • 1
  • 2

利用%(取模操作)可以轻易进行取时取分取秒。

duration还有3个静态成员函数: 
zeor() : 产生一个0秒的duration对象 
max() min() :产生一个duration对象,里面的ticks尽可能是最大最小值。

下面说一下由duration + 起始点epoch =的时间点timepoint: 
起始点epoch可以由clock产生,每个clock产生的起始点都有可能不一样,不过一般都是用1970.0.1.01这个起始点。

clock定义了一个epoch(起点)和一个period(时间段)。 
clock可以计算从1970.01.01到现在的毫秒数,也可以通过now接口产生一个当前时间对象。 
timepoint就是clock +/- 一个duration。

下面先讲一下clock: 
c++标准库定了了3中clock:

system_clock:系统时钟,提供了to_time_t()from_time_t()接口来将timepoint和c系统时间time_t做转换。
steady_clock:不变时钟,并不是使用物理时间的增长来描述的,而是以一个固定的比率来增长。
high_resolution_clock:高精度时钟。
 
   
   
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

为什么一个叫系统时钟一个加不变时钟? 
系统时钟就是电脑右下角的时钟,我们可以任意设置,eg:现在是早上10点,我们可以把系统时钟设置为下午3点,也可以设置9点。不变时钟就是针对系统时钟这种变化性设计出的另一种不可更改的时钟,意思是我们无法去调整时钟的值,只能看着她以一个不变的比率,一直增加。

c++新标准并没有为这三种时钟提供必须要的精度、起始时间、范围。 
如果需要一个自定义的起始时间、或是时钟并不覆盖的一个时间点,这时,需要用到转换函数。

namespace
{

template <typename C>
void print_clock()
{
  std::cout << " - precision: ";

  typedef typename C::period P;
  if ( std::ratio_less_equalstd::milli>::value )
  {
    typedef typename std::ratio_multiplystd::kilo> T;
    std::cout << std::fixed << double(T::num) / T::den << " milliseconds" << std::endl;
  }
  else
    std::cout << std::fixed << double(P::num) / P::den << " seconds" << std::endl;

  std::cout << " - is_steady: " << std::boolalpha << C::is_steady << std::endl << std::endl;
}

}


void test_cpp2()
{
  std::cout << "----------------- test clock----------------" << std::endl;

  std::cout << " system_clock/high_resolution_clock/steady_clock " << std::endl;
  print_clock<std::chrono::system_clock>();
  print_clock<std::chrono::high_resolution_clock>();
  print_clock<std::chrono::steady_clock>();

}

 
   
   
   
   
  • 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
  • 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

运行结果是:

----------------- test clock----------------
 system_clock/high_resolution_clock/steady_clock
 - precision: 0.000100 milliseconds
 - is_steady: false

 - precision: 0.000001 milliseconds
 - is_steady: true

 - precision: 0.000001 milliseconds
 - is_steady: true
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面的代码说明 
system_clock 、high_resolution_clock的精度都是100ns,steady_clock的精度是1ms。 
high_resolution_clock、steady_clock的时钟都不可调整 
测试环境:win10 x64 + vs2015,不同的系统检测出的可能有所不同(eg:高精度和系统时钟又可能是一样的)。

steady_clock主要用在处理两个时钟的比较和计算,如果是用system_clock来完成这件事,其中,如果时钟被改变了,那么基于system_clock的计算就会出现错误,同样的,如果计算程序执行时间,如果使用system_clock,那么得出来的结果,也算不了数,甚至执行时间为负。所以说,使用steady_clock来计算两个时钟的比较和计算是比较合适的。

上面已经说过了duration和clock,那么基于duration和clock的timepoint在下面开始分析:

namespace std { namespace chrono {

template <typename Clock,
                  typename Durtion = typename Clock:Durtion>
class time_point;               

}}
 
   
   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

说到时间点,有4个比较特殊的: 
epoch:可由任意时钟的time_point默认构造产生 
current time:由任意时钟的静态成员函数now()产生 
minimum timepoint:有任意时钟的time_point静态成员函数min()产生 
maximum timepoint:有任意时钟的time_point静态成员函数max()产生

下面两种写法是一样的意思:

std::chrono::system_clock::time_point t;
std::chrono::time_point::chrono::system_clock> t;
 
   
   
   
   
  • 1
  • 2
  • 1
  • 2

time_point 对象只有一个成员 duration,这个值可以被成员函数time_since_epoch()获取到,同时,time_point对象之间的比较或是和duration之间的算术操作,类time_point都有提供。

只有当time_point 和 durtion组合起来,才能表达出更丰富的时间点,但这其中还需要考虑很多问题:秒单位转换时的截取和四舍五入;闰年和闰秒等等。

总结:chrono描述的更多的是chrono和duration而不是日期时间库。

以上是c++新标准库对时钟和时间的描述,c标准和posix的接口,在c++程序中也是可用的,下面介绍一下time.h(在c++可写成ctime)。 命名空间是std

CLOCKS_PER_SEC : 表示1s内有多少个ticks,1个嘀嗒(ticks)占的时间是1/CLOCKS_PER_SEC。

ctime() 将一个time_t转换成一个日期字符串, mktime()将struct tm转换成time_t 
而c++标准则提供了frome_time_t(),to_time_t() 来将time_point和time_t进行转换

接下来看看duration和timepoint在定时器和阻塞方面的知识: 
阻塞线程:由this_thread提供的sleep_for和sleep_until 
等待互斥量:try_lock_fro和try_lock_unitl 
等待条件变量:wait_for和wait_until 
所有以_for结尾的接口都是利用duration来实现阻塞;所有以_until都是利用timepoint来实现阻塞。

这里有一个需要注意的地方:以_for结尾的接口用的是duration时间段来处理的,和时钟无关;而以_until结尾的接口用的是time_point来处理,这里如果将时钟修改了,那么会影响到执行的逻辑。eg:现在是下午3点,sleep_unitl(下午4点),如果此时将时间改为下午4点,那么定时器会立马结束。

但计算机的事情并没有绝对,例如上面说的_for不会收到时钟调整的影响,但是如果硬件平台不提供steady_clock,那么软件就没办法在保证计时不会被影响,所以,在这种情况下,_for接口也会被时钟调整所影响。

你可能感兴趣的:(C++,C++)