chrono 模块详解
std::chrono
是一个与时间运算相关的模块,是 C++11 引入的,它提供了一系列函数和类都处于 std::chrono
命名空间下。
使用该库需要了解它如下三个核心概念,了解了之后便可灵活运用。
- duration: 时间段
- time_point: 时间点
- clock: 时钟
由于每次都写 std::chrono
这个很长的命名空间略显冗余,后文中只写 chrono
,
时间段
时间段的表示
chrono::duration
用来表示一段时间,1小时,12分钟,0.01 秒都可以表示。
不同的时间单位都能换算成秒,duration
正是通过秒来表示其他的时间段的。
chrono::duration<int, std::ratio<3600, 1>> two_hours(2);
上面的代码定义的时间段表示 2 个小时,这很难理解吧。看我慢慢解释。
duration
是一个模板类,它的定义如下:
template<typename Rep, typename Period = std::ratio<1>>
class duration;
template<std::intmax_t Num, std::intmax_t Denom = 1>
class ratio;
Period
是一个 std::ratio
类型, std::ratio
表示一个分数,第一个参数是分子
第二个是分子。duration<Rep, Period>
就表示这个时间段有 Period
秒。
模板参数 Rep
是一种数据类型,用来表示 Period
的数量。
chrono::duration<int, std::ratio<60, 1>> minute(1) // 1 分钟
chrono::duration<int, std::ratio<1, 1000>> ms(2) // 2 毫秒
chrono::duration<int, ratio<1, 1>> n_second(22); // 22秒
chrono::duration<double, ratio<60, 1>> n_minute(n_second); // 用 22 秒初始化此时间段
// duration.count() 用于返回该时间段的数量,比如有多少个一分钟,几个一小时
cout << n_minute.count() << '\n'; // 0.366667 - 22 秒为 0.367 分钟
经过上面的分析,应该已经明白时间段是如何表示的了吧。
在 chrono
中定义了常见的 duration
类型,下至纳秒上至小时:
chrono::nanoseconds
chrono::microseconds;
chrono::milliseconds;
chrono::seconds;
chrono::minutes;
chrono::hours;
时间段的转换
前面的例子中,我用 22 秒来初始化浮点表示的分钟,这是可行的。但是如果分钟也是用整形表示的,由于会损失精度,此时整形表示的分钟无法用 秒来表示。
chrono::duration<int, ratio<1, 1>> n_second(22); // 22秒
chrono::duration<double, ratio<60, 1>> n_minute(n_second); // 用 22 秒初始化此时间段
chrono::duration<int, ratio<60, 1>> n_minute(n_second); // 不行,不能通过编译
但有的时候有确实需要这样的转换,此时可以使用 duration_cast
,此时会损失精度。
chrono::seconds n_second(122); // 122秒
chrono::minutes n_minute = chrono::duration_cast<chrono::minutes>(n_second);
cout << n_minute.count() << '\n'; // 2 分钟
时间段的运算
不同类型的时间段间可以进行加减乘除取模等操作,比如一小时加一分钟,一小时除以一秒钟,一小时除以 4。运算结果是符合直觉的。 即时间段与时间段相加自然还是时间段,而相除的结果自然是数值了。
chrono::seconds n_second(12);
chrono::minutes n_minute(1);
auto sum = n_minute + n_second;
cout << sum.count() << '\n'; // 72s
时间点
时间点的表示
时间点,顾名思义表示某个时刻。它的定义如下:
template<typename Clock, typename Duration = typename Clock::duration>
class time_point;
其中 Clock
表示一个时钟,下一节介绍,可想象家里挂了好多个时钟,这里指明哪一个时钟的时间。Duration
表示从时钟的起始点开始经过的时间。
下面是个例子:
chrono::time_point<chrono::system_clock, chrono::seconds> tp(chrono::seconds(1));
上面的时间点表示时间起点后的一秒。通常是 1970-01-01 00:00:01
。
时间点的运算
一个很有用的时间点就是当前时间点,可以通过如下方式获得:
chrono::system_clock::time_point now = chrono::system_clock::now();
时间点之间可以做减法得到时间段,时间点加时间段自然就是新的时间点了。
chrono::system_clock::time_point now = chrono::system_clock::now();
auto tomorrow = now + chrono::hours(24); // 明日此时
// 获得时间点距离时钟起始时刻的 duration
decltype(now)::duration dur = now.time_since_epoch();
cout << dur.count();
epoch
的意思是纪元,也就是某个时期的开始。不同的时钟可能对 epoch
选择不同,system_clock
选择的是 1970-01-01 00:00:00
。
时间点之间可能采用的 duration
不同,此时可以使用 time_point_cast
进行不同 duration
间的转换。
chrono::time_point<chrono::system_clock> tp(chrono::hours(1));
auto time_point_sec = chrono::time_point_cast<chrono::seconds>(tp);
cout << time_point_sec.time_since_epoch().count();
转换的时候存在精度丢失的问题,比如把分钟表示的时间点转为小时,零头就没了。
时钟
标准库中定义了大量的时钟,在 C++ 11 中存在下面三个时钟:
chrono::system_clock
chrono::steady_clock
chrono::high_resolution_clock
: 只是system_clock
的别名罢了。
system_clock
系统时钟,其 epoch 为 1970-01-01 00:00:00
,通常用于表示具体的日期。它具有一下实用方法:
// 当前时间点
chrono::system_clock::time_point now = chrono::system_clock::now();
// 转换为 time_t 类型
time_t now_t = chrono::system_clock::to_time_t(now);
// 从 time_t 转为 time_point
time_t t = time(nullptr);
chrono::system_clock::time_point now = chrono::system_clock::from_time_t(t);
system_clock
提供了静态方法,可以获取当前时间,并且提供了与 time_t
类型的相互转化的接口。这样就可以利用 ctime
函数库中
时间格式化、转换时区等接口了。
steady_clock
字面上的意思是稳定的时钟,什么意思呢。此时钟的 epoch 是开机时间,因此,就算用户修改了系统事件,这个时钟也不会受到影响。它的计时始终是
增加的。但是 system_clock
就不同了,如果把时间向前设置到 2000 年,那么获取到的系统时间一下子就比先前小了。
下面例子中取 steady_clock
的当前时间,然后看看它距离 epoch
的时间长短:
auto now = chrono::steady_clock::now().time_since_epoch();
using hours = chrono::duration<double, ratio<3600, 1>>;
hours now_h = chrono::duration_cast<hours>(now);
cout << now_h.count(); // 79.66 - 说明我的机器开机 79.66 个小时了
此时钟常常用来对程序运行时间进行计时,或设置定时器的定时时长:
// 代码运行时间
chrono::system_clock::time_point start = chrono::steady_clock::now();
sleep(2);
chrono::system_clock::time_point stop = chrono::steady_clock::now();
chrono::nanoseconds ns = stop - start;
cout << ns.count(); // 约 2 000 000 000 ns
steady_clock
没有与 time_t
相互转换的接口,这很好理解,因为它不是干那事的。