C++11时间工具<chrono>梳理
<chrono>
C++11中提供了日期和时间相关的库chrono。
chrono库主要包含三种类型的类:时间间隔duration
、时钟clocks
、时间点time point
。
时间间隔duration
- 常用类成员
duration表示一段时间间隔
,用来记录时间长度,可以表示几秒、几分钟、几个小时的时间间隔。duration的原型如下:
// 定义于头文件 <chrono>
template<
class Rep, //单位类型 == 单位次数(多少个单位) == 多少个周期数
class Period = std::ratio<1> //单位 Period:周期,默认周期为1s
> class duration;
模板参数:
-
Rep
:Representation(表示),这是一个数值类型,用于表示时钟数(周期)的类型(默认为整形)。若Rep
是浮点数,则duration
能使用小数描述时钟周期的数目。 -
Period
:表示时钟的周期,它的原型如下:// 定义于头文件 <ratio> template< std::intmax_t Num, std::intmax_t Denom = 1 > class ratio;
位于命名空间std
ratio
(比率;比例)类表示每个时钟周期的单位
,如秒、毫秒、微秒,其中第一个模板参数Num(Numerator)代表分子
,Denom(denominator)代表分母
,该分母值默认为1,因此,ratio代表的是一个分子除以分母的数值
,比如:ratio<2>代表一个时钟周期是2秒,ratio<60>代表一分钟,ratio<60*60>代表一个小时,ratio<60*60*24>代表一天。而ratio<1,1000>代表的是1/1000秒,也就是1毫秒,ratio<1,1000000>代表一微秒,ratio<1,1000000000>代表一纳秒。
常用的duration
为了方便使用,在标准库中定义了一些常用的时间间隔,比如:时、分、秒、毫秒、微秒、纳秒,它们都位于chrono命名空间下,定义如下:
类型 定义 纳秒:std::chrono::nanoseconds using nanoseconds = duration<long long, nano>; 微秒:std::chrono::microseconds using microseconds = duration<long long, micro>; 毫秒:std::chrono::milliseconds using milliseconds = duration<long long, milli>; 秒 :std::chrono::seconds using seconds = duration ; 分钟:std::chrono::minutes using minutes = duration<int, ratio<60>>; 小时:std::chrono::hours using hours = duration<int, ratio<3600>>;
- duration类的构造函数原型如下:
// 1. 拷贝构造函数
duration( const duration& ) = default; //浅拷贝
// 2. 通过指定时钟周期的类型和次数来构造对象(以缺省单位秒直接构造)j
template< class Rep2 >
constexpr explicit duration( const Rep2& r ); //std::chrono::duration<int> sec(1);//1秒
// 3. 通过指定时钟周期类型,和时钟周期长度来构造对象
template< class Rep2, class Period2 >
constexpr duration( const duration<Rep2,Period2>& d );//改变单位
- 为了更加方便的进行duration对象之间的操作,类内部进行了操作符重载:
duration& operator= (const duration& rhs) = default;
constexpr duration operator+() const;
constexpr duration operator-() const;
duration& operator++();
duration operator++(int);
duration& operator--();
duration operator--(int);
duration& operator+= (const duration& rhs);
duration& operator-= (const duration& rhs);
duration& operator*= (const rep& r);
duration& operator/= (const rep& r);
duration& operator%= (const rep& r);
duration& operator%= (const duration& rhs);
注意事项:duration的加减运算有一定的规则,当两个duration时钟周期不相同的时候,会先统一成一种时钟,然后再进行算术运算,统一的规则如下:假设有ratio<x1,y1> 和 ratio<x2,y2>两个时钟周期,首先需要求出x1,x2的最大公约数X,然后求出y1,y2的最小公倍数Y,统一之后的时钟周期ratio为ratio<X,Y>。
exam:
std::chrono::duration<double, std::ratio<9, 7>> d1(3); //单位为9/7秒
std::chrono::duration<double, std::ratio<6, 5>> d2(1); //单位为6/5秒
/*
9和6的最大公约数是3;
7和5的最小公倍数是35;
*/
// d1 和 d2 统一之后的时钟周期
std::chrono::duration<double, std::ratio<3, 35>> d4 = d1 - d2;
auto d3 = d1 - d2;
std::cout<<d3.count()<<"\n";
- duration类还提供了获取时间间隔的时钟周期数的方法count(),函数原型如下:
constexpr rep count() const; //计算有多少个单位
时间点time_point
注:这个类需要有一个时钟才可以使用,一般搭配system_clock、steady_clock使用,这两个类中缺省已将time_point等初始化好,方便用户使用.
用法展示在system_clock、steady_clock小节描述中.以下内容仅补充定义
// 定义于头文件 <chrono>
template<
class Clock,
class Duration = typename Clock::duration //缺省使用时钟内置的duration,一般不需要手动写
> class time_point;
它被实现成如同存储一个 Duration 类型的自 Clock 的纪元起始开始的时间间隔的值,通过这个类最终可以得到时间中的某一个时间点。
- Clock:此时间点在此时钟上计量
- Duration:用于计量从纪元起时间的 std::chrono::duration 类型
// 1. 构造一个以新纪元(epoch,即:1970.1.1)作为值的对象,需要和时钟类一起使用,不能单独使用该无参构造函数
time_point();
// 2. 构造一个对象,表示一个时间点,其中d的持续时间从epoch开始,需要和时钟类一起使用,不能单独使用该构造函数
explicit time_point( const duration& d );
// 3. 拷贝构造函数,构造与t相同时间点的对象,使用的时候需要指定模板参数
template< class Duration2 >
time_point( const time_point<Clock,Duration2>& t );
operator重载和duration类似
在这个类中除了构造函数还提供了另外一个time_since_epoch()函数,用来获得1970年1月1日到time_point对象中记录的时间经过的时间间隔(duration),函数原型如下:
duration time_since_epoch() const;
实际应用中将单位转换成秒后就是常说的时间戳.
在线时间戳转换工具(Unix timestamp) - 在线工具 (tools.fun)
时钟system_clock & steady_clock
system_clock
struct system_clock { // wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime
using rep = long long;
using period = ratio<1, 10'000'000>; // 100 nanoseconds
using duration = chrono::duration<rep, period>;
using time_point = chrono::time_point<system_clock>;
static constexpr bool is_steady = false; //不是单调时钟
/* 3个静态函数 */
// get current time
// 返回当前计算机系统时间的时间点。
_NODISCARD static time_point now() noexcept
{
return time_point(duration(_Xtime_get_ticks()));
}
// convert to __time64_t
// 将 time_point 时间点类型转换为 std::time_t 类型
_NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept
{
return duration_cast<seconds>(_Time.time_since_epoch()).count();
}
// convert from __time64_t
// 将 std::time_t 类型转换为 time_point 时间点类型
_NODISCARD static time_point from_time_t(__time64_t _Tm) noexcept
{
return time_point{seconds{_Tm}};
}
};
system_clock中的time_point类型通过系统时钟做了初始化chrono::time_point<system_clock>,里面记录了新纪元时间点
system_clock还提供了3个静态函数:
static std::chrono::time_point<std::chrono::system_clock> now() noexcept;
// 将 time_point 时间点类型转换为 std::time_t 类型
static std::time_t to_time_t( const time_point& t ) noexcept;
// 将 std::time_t 类型转换为 time_point 时间点类型
static std::chrono::system_clock::time_point from_time_t( std::time_t t ) noexcept;
代码举例
- 计算一段时间
int main(){
//新纪元起始时间点:
std::chrono::system_clock::time_point epoch;//系统时间的时间点(缺省为新纪元)
std::cout<<epoch.time_since_epoch().count()<<"\n"; //
//一日时间段
std::chrono::duration<long long> day(std::chrono::hours(24));
//std::chrono::hours day(24); //相同
//新纪元后的一天的时间点:
std::chrono::system_clock::time_point epoch1 = epoch+day;
//std::chrono::system_clock::time_point epoch1(epoch+day); //相同
std::cout<<epoch1.time_since_epoch().count()<<"\n";
return 0;
}
证明获取到的是新纪元起始一天之后的时间:
将周期单位从100纳秒换成s,即:
864000000000 (100ns) = 864000000000 00(1ns) * 10^(-9) = 86400 (s)
再将时间戳转换,得证
新纪元的时间戳是0,得证
- 获取当前计算机系统时间
int main(){
std::chrono::system_clock::time_point now_time = std::chrono::system_clock::now();
//法一:
time_t time = std::chrono::system_clock::to_time_t(now_time);
std::cout<<ctime(&time)<<"\n";
//法二:获取后还需要单位转换+时间戳工具
std::cout<<now_time.time_since_epoch().count()<<"\n";
}
运行结果:
steady_clock
别名:
using high_resolution_clock = steady_clock;
如果我们通过时钟不是为了获取当前的系统时间,而是进行程序耗时的时长,此时使用syetem_clock就不合适了,因为这个时间可以跟随系统的设置发生变化。在C++11中提供的时钟类steady_clock相当于秒表,只要启动就会进行时间的累加,并且不能被修改,非常适合于进行耗时的统计。
定义:
类 std::chrono::steady_clock
表示单调时钟。此时钟的时间点无法随物理时间向前推进而减少。此时钟与壁钟时间无关(例如,它能是上次重启开始的时间),且最适于度量间隔。
struct steady_clock { // wraps QueryPerformanceCounter 包装查询性能计数器-- VS
using rep = long long;
using period = nano;
using duration = nanoseconds; //单位是1ns,精度比system高了100倍
using time_point = chrono::time_point<steady_clock>; //
static constexpr bool is_steady = true; //稳定时钟标志,始终为 true
/* 1个静态方法 */
// get current time
// 获取一个稳定增加的时间点
_NODISCARD static time_point now() noexcept
{
//VS版实现,需要可以查VS中的定义
}
};
这个类只提供了一个now方法,就用于统计时长.
例程:
int main() {
// 获取开始时间点
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
// 执行业务流程
std::cout << "print 1000 stars ...." << "\n";
for (int i = 0; i < 1000; ++i)
{
std::cout << "*";
}
std::cout << "\n";
// 获取结束时间点
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
// 计算差值
//std::chrono::duration<long long,std::nano> dt = end - start; //相同
//std::chrono::nanoseconds dt = end - start; //相同
auto dt = end - start; //相同
std::cout << "总共耗时: " << dt.count() << "纳秒" << "\n";
}
结果:
转换函数
1.duration_cast
duration_cast是chrono库提供的一个模板函数,这个函数不属于duration类,属于chrono命名空间.
通过这个函数可以对duration类对象内部的时钟周期Period,和周期次数的类型Rep进行修改,该函数原型如下:
template <class ToDuration, class Rep, class Period>
constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);
参数:
ToDuration:为转换目的对象的类型.
std::chrono::hours h = std::chrono::duration_cast<std::chrono::hours>(std::chrono::minutes(60));
Rep和Period都是duration模板参数,已经存在,不需要提供.
Description:
这个函数用于duration对象不能隐式转换的时候,即提供给用户用于强制转换.
duration_cast提供给用户使用,即数据安全交由程序员负责,底层不再负责.
duration支持隐式转换的规则
-
如果是对时钟周期进行转换:原时钟周期必须能够整除目的时钟周期(比如:小时到分钟)。
-
如果是对时钟周期次数的类型进行转换:低等类型默认可以向高等类型进行转换(比如:int 转 double)
(1和2点反过来都会损失精度,是不安全的,因此默认不支持.)
-
如果时钟周期和时钟周期次数类型都变了,只看第二点(也就是只看时间周期次数类型)。
-
以上条件都不满足,那么就需要使用 duration_cast 进行显示转换。
Exam:
- 周期: 分钟 -> 小时
int main() {
//分钟 -> 小时
std::chrono::hours h = std::chrono::minutes(60);
return 0;
}
默认不支持小周期向大周期转换.需要使用duration_cast.
正确格式:
std::chrono::hours h= std::chrono::duration_cast<std::chrono::hours>(std::chrono::minutes(60));
-
类型 浮点 -> 整型
报错:
正确:牺牲精度完成转换
-
类型+周期
- 类型不满足,周期大小满足
std::chrono::duration<double,std::ratio<1,1000>> t1(2.2); std::chrono::duration<int,std::ratio<1,100>> t2 = t1;
根据规则2,只看类型,类型不满足,因此需要转换.
-
类型满足,周期大小不满足
std::chrono::duration<int,std::ratio<1,100>> t3(1); std::chrono::duration<double,std::ratio<1,1000>> t2 = t3;
没有警告,说明可以隐式转换,即只看类型.
2. time_point_cast
time_point_cast 和 duration_cast类似.也是chrono库提供的一个模板函数,属于chrono命名空间,不属于time_point类.
转换规则也和duration_cast一样.
template <class ToDuration, class Clock, class Duration>
time_point<Clock, ToDuration> time_point_cast(const time_point<Clock, Duration> &t);
Exam:
std::chrono::time_point<std::chrono::system_clock,std::chrono::milliseconds> millis;
std::chrono::time_point<std::chrono::system_clock,std::chrono::seconds> s = millis;
修改:
反之可以支持隐式转换
引用 :
https://subingwen.cn/tags/C-11/
https://zh.cppreference.com/w/cpp
https://zh.cppreference.com/w/cpp/chrono