C++11标准库 时间工具<chrono>梳理

<chrono>

C++11中提供了日期和时间相关的库chrono。

chrono库主要包含三种类型的类:时间间隔duration时钟clocks时间点time point

时间间隔duration

  1. 常用类成员

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>代表一纳秒。

    std::ratio - cppreference.com

常用的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>>;

  1. 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 );//改变单位

  1. 为了更加方便的进行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";

  1. 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;
代码举例
  1. 计算一段时间
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,得证

  1. 获取当前计算机系统时间
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支持隐式转换的规则
  1. 如果是对时钟周期进行转换:原时钟周期必须能够整除目的时钟周期(比如:小时到分钟)。

  2. 如果是对时钟周期次数的类型进行转换:低等类型默认可以向高等类型进行转换(比如:int 转 double)

    (1和2点反过来都会损失精度,是不安全的,因此默认不支持.)

  3. 如果时钟周期和时钟周期次数类型都变了,只看第二点(也就是只看时间周期次数类型)。

  4. 以上条件都不满足,那么就需要使用 duration_cast 进行显示转换。

Exam:

  1. 周期: 分钟 -> 小时
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));
  1. 类型 浮点 -> 整型

    报错:

    正确:牺牲精度完成转换

  2. 类型+周期

    • 类型不满足,周期大小满足
        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

热门相关:诱惑   视死如归魏君子   遥望行止   极品医圣   青春不要脸粤语   超级英雄