mmxingye

导航

01 | 处理日期和时间的 chrono 库

C++11 中提供了日期和时间相关的库 chrono,通过 chrono 库可以很方便地处理日期和时间,为程序的开发提供了便利。chrono 库主要包含三种类型的类:时间间隔duration、时钟clocks、时间点time point。

基本常识

1.时间点的来源:钟(c++的不同类)
2.时间点和时间点之间的运算。比如 时间点-时间点=时间间隔
3.时间点和时间间隔的运算等
4.时间间隔由 周期长短 和 周期次数 决定

时间间隔 duration


duration 的原型如下:

// 定义于头文件 <chrono>
template<
    class Rep,          //时钟周期类型默认为整形——“也就是周期数只能为1,2,3……”
    class Period = std::ratio<1>  //ratio 表示时钟的周期——"也就是周期长度,事实上是一个分数,单位秒"——而 ratio<1,1000 > 代表的是 1/1000 秒,也就是 1 毫秒
> class duration;

在chrono命名空间下的 时间间隔

简单看几个代码

chrono::hours h(1);                          // 一小时
    chrono::milliseconds ms{ 3 };                // 3 毫秒
    chrono::duration<int, ratio<1000>> ks(3);    // 3000 秒

    // chrono::duration<int, ratio<1000>> d3(3.5);  // error
    chrono::duration<double> dd(6.6);               // 6.6 秒

    // 使用小数表示时钟周期的次数
    chrono::duration<double, std::ratio<1, 30>> hz(3.5);

时间间隔类同样提供了 运算符重载 ,返回的时间间隔类型有固定的规则。详见下面参考资料

时间点 time point


类的定义如下

// 定义于头文件 <chrono>
template<
    class Clock,                                  //此时间点在此时钟上计量
    class Duration = typename Clock::duration     //用于计量从纪元起时间的 std::chrono::duration 类型
> class time_point;

在这个类中除了构造函数还提供了另外一个 time_since_epoch() 函数,用来获得 1970 年 1 月 1 日到 time_point 对象中记录的时间经过的时间间隔(duration),函数原型如下:

duration time_since_epoch() const;

此类同样提供了运算符重载,也可以和时间间隔运算

时钟 clocks


chrono 库中提供了获取当前的系统时间的时钟类,包含的时钟一共有三种:

  • system_clock:系统的时钟,系统的时钟可以修改,甚至可以网络对时,因此使用系统时间计算时间差可能不准。
  • steady_clock:是固定的时钟,相当于秒表。开始计时后,时间只会增长并且不能修改,适合用于记录程序耗时
  • high_resolution_clock:和时钟类 steady_clock 是等价的(是它的别名)。

在使用chrono提供的时钟类的时候,不需要创建类对象,直接调用类的静态方法就可以得到想要的时间了。

system_clock 类

system_clock 类一共提供了三个静态成员函数

// 返回表示当前时间的时间点。
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;

比如,我们要获取当前的系统时间,并且需要将其以能够识别的方式打印出来,示例代码如下:

#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
    // 新纪元1970.1.1时间
    system_clock::time_point epoch;

    duration<int, ratio<60*60*24>> day(1);
    // 新纪元1970.1.1时间 + 1天
    system_clock::time_point ppt(day);

    using dday = duration<int, ratio<60 * 60 * 24>>;
    // 新纪元1970.1.1时间 + 10天
    time_point<system_clock, dday> t(dday(10));

    // 系统当前时间
    system_clock::time_point today = system_clock::now();
    
    // 转换为time_t时间类型
    time_t tm = system_clock::to_time_t(today);
    cout << "今天的日期是:    " << ctime(&tm);

    time_t tm1 = system_clock::to_time_t(today+day);
    cout << "明天的日期是:    " << ctime(&tm1);

    time_t tm2 = system_clock::to_time_t(epoch);
    cout << "新纪元时间:      " << ctime(&tm2);

    time_t tm3 = system_clock::to_time_t(ppt);
    cout << "新纪元时间+1天:  " << ctime(&tm3);

    time_t tm4 = system_clock::to_time_t(t);
    cout << "新纪元时间+10天: " << ctime(&tm4);
}

steady_clock

如果我们通过时钟不是为了获取当前的系统时间,而是进行程序耗时的时长,此时使用 syetem_clock 就不合适了,因为这个时间可以跟随系统的设置发生变化。在 C++11 中提供的时钟类 steady_clock 相当于秒表,只要启动就会进行时间的累加,并且不能被修改,非常适合于进行耗时的统计

在这个类中也提供了一个静态的 now () 方法,用于得到当前的时间点,函数原型如下:

static std::chrono::time_point<std::chrono::steady_clock> now() noexcept;

假设要测试某一段程序的执行效率,可以计算它执行期间消耗的总时长,示例代码如下:

#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
    // 获取开始时间点
    steady_clock::time_point start = steady_clock::now();
    // 执行业务流程
    cout << "print 1000 stars ...." << endl;
    for (int i = 0; i < 1000; ++i)
    {
        cout << "*";
    }
    cout << endl;
    // 获取结束时间点
    steady_clock::time_point last = steady_clock::now();
    // 计算差值
    auto dt = last - start;
    cout << "总共耗时: " << dt.count() << "纳秒" << endl;
}

high_resolution_clock


high_resolution_clock 的使用方式和 steady_clock 是一样的

转换函数


duration_cast

duration_cast 是 chrono 库提供的一个模板函数,这个函数不属于 duration 类。通过这个函数可以对 duration 类对象内部的时钟周期 Period,和周期次数的类型 Rep 进行修改,该函数原型如下:

template <class ToDuration, class Rep, class Period>
  constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);

我们可以修改一下上面测试程序执行时间的代码,在代码中修改 duration 对象的属性:

#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;

void f()
{
    cout << "print 1000 stars ...." << endl;
    for (int i = 0; i < 1000; ++i)
    {
        cout << "*";
    }
    cout << endl;
}

int main()
{
    auto t1 = steady_clock::now();
    f();
    auto t2 = steady_clock::now();

    // 整数时长:时钟周期纳秒转毫秒,精度降低,要求 duration_cast
    auto int_ms = duration_cast<chrono::milliseconds>(t2 - t1);

    // 小数时长:不要求 duration_cast
    duration<double, ratio<1, 1000>> fp_ms = t2 - t1;

    cout << "f() took " << fp_ms.count() << " ms, "
        << "or " << int_ms.count() << " whole milliseconds\n";
}

time_point_cast


time_point_cast 也是 chrono 库提供的一个模板函数,这个函数不属于 time_point 类。函数的作用是对时间点进行转换,因为不同的时间点对象内部的时钟周期 Period,和周期次数的类型 Rep 可能也是不同的,一般情况下它们之间可以进行隐式类型转换,也可以通过该函数显示的进行转换,函数原型如下:

template <class ToDuration, class Clock, class Duration>
time_point<Clock, ToDuration> time_point_cast(const time_point<Clock, Duration> &t);

关于函数的使用,示例代码如下:

#include <chrono>
#include <iostream>
using namespace std;

using Clock = chrono::high_resolution_clock;
using Ms = chrono::milliseconds;
using Sec = chrono::seconds;
template<class Duration>
using TimePoint = chrono::time_point<Clock, Duration>;

void print_ms(const TimePoint<Ms>& time_point)
{
    std::cout << time_point.time_since_epoch().count() << " ms\n";
}

int main()
{
    TimePoint<Sec> time_point_sec(Sec(6));
    // 无精度损失, 可以进行隐式类型转换
    TimePoint<Ms> time_point_ms(time_point_sec);
    print_ms(time_point_ms);    // 6000 ms

    time_point_ms = TimePoint<Ms>(Ms(6789));
    // error,会损失精度,不允许进行隐式的类型转换
    TimePoint<Sec> sec(time_point_ms);

    // 显示类型转换,会损失精度。6789 truncated to 6000
    time_point_sec = std::chrono::time_point_cast<Sec>(time_point_ms);
    print_ms(time_point_sec); // 6000 ms
}

注意事项:关于时间点的转换如果没有没有精度的损失可以直接进行隐式类型转换,如果会损失精度只能通过显示类型转换,也就是调用 time_point_cast 函数来完成该操作。

参考

爱编程的大丙:https://subingwen.cn/cpp/chrono/#4-2-time-point-cast

posted on 2022-04-21 19:59  独立树  阅读(1184)  评论(0)    收藏  举报