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
本文来自博客园,作者:HJfjfK,原文链接:https://www.cnblogs.com/DSCL-ing/p/18302066
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性