C++(20/23)标准模板库编程 - 16 时间库

本章解释了时间库中重要的类和算法。它还涵盖了比例库中的补充类。讨论的主题包括:

  • 使用 std::ratio
  • 使用 std::chrono::duration
  • 使用时钟类
  • 使用 std::chrono::time_point
  • 日期和时间格式化
  • 软件基准测试

时间库实体属于命名空间 std::chrono。在本章中,为了与源代码中使用的命名空间 chrono = std::chrono 语句保持一致,对 std::chrono 的类和算法的文本引用大多使用 chrono:: 作为前缀。

比例


比例库定义了一个名为 std::ratio 的模板类。这个类使用整数 (std::intmax_t) 分子和分母来表示一个有限的有理数。比例库还定义了支持 std::ratio 算术和比较的模板类。时间库利用 std::ratio 来简化使用时间段 (chrono::duration) 和时间点 (chrono::time_point) 的计算。在本章的后面,您将了解更多关于这些类的信息。

关于 std::ratio,最重要的一点是分子和分母都是编译时常量。使用 std::ratio 的算术和比较也是编译时操作,这很有利,因为它有助于在编译时检测某些错误,例如除以零。它还显著降低了程序执行期间发生算术溢出错误的风险。

清单 16-1-1 显示了示例 Ch16_01_ex1() 的源代码,该示例演示了 std::ratio 的基本用法。该函数的开头语句 using ra = std::ratio<12, 20> 定义了一个名为 ra 的别名,它表示比例 12/20。通常在不创建显式对象的情况下操作比例,如当前示例所示。类 std::ratio 包含两个名为 num 和 den 的成员,它们的类型都是 static constexpr intmax_t。这些成员在随后的打印语句中被引用。如果您提前浏览到结果部分,ra 的输出显示 num 和 den 分别为 3 和 5。原因是编译器确保 std::ratio 始终被简化为最简形式。

//-------------------------------------------------------------------------
// Ch16_01_ex.cpp
//-------------------------------------------------------------------------
#include <ratio>
#include "Ch16_01.h"
void Ch16_01_ex1()
{
    // using std::ratio
    using ra = std::ratio<12, 20>;      // reduced to 3 / 5
    std::println("ra::num: {:3d} ra::den: {:3d}", ra::num, ra::den);
    using rb = std::ratio<1, 15>;
    std::println("rb::num: {:3d} rb::den: {:3d}", rb::num, rb::den);
    using rc = std::ratio<42>;          // 42 / 1
    std::println("rc::num: {:3d} rc::den: {:3d}", rc::num, rc::den);
    // std::ratio arithmetic
    using rd_add = std::ratio_add<ra, rb>;
    using rd_sub = std::ratio_subtract<ra, rb>;
    using rd_mul = std::ratio_multiply<ra, rb>;
    using rd_div = std::ratio_divide<ra, rb>;
    std::println("");
    std::println("rd_add::num: {:3d} rd_add::den: {:3d}", rd_add::num, rd_add::den);
    std::println("rd_sub::num: {:3d} rd_sub::den: {:3d}", rd_sub::num, rd_sub::den);
    std::println("rd_mul::num: {:3d} rd_mul::den: {:3d}", rd_mul::num, rd_mul::den);
    std::println("rd_div::num: {:3d} rd_div::den: {:3d}", rd_div::num, rd_div::den);
    // std::ratio relational operators
    std::println("\nra == rb: {:s}", std::ratio_equal<ra, rb>::value);
    std::println("ra == rb: {:s}", std::ratio_not_equal<ra, rb>::value);
    std::println("ra <  rb: {:s}", std::ratio_less<ra, rb>::value);
    std::println("ra <= rb: {:s}", std::ratio_less_equal<ra, rb>::value);
    std::println("ra >  rb: {:s}", std::ratio_greater<ra, rb>::value);
    std::println("ra >= rb: {:s}", std::ratio_greater_equal<ra, rb>::value);
}

Ch16_01_ex1() 中的接下来的两个语句定义并打印 rb = std::ratio<1, 15>。最后一个 std::ratio 示例,使用 rc = std::ratio<42>,创建一个 std::ratio<42, 1>,因为 den 的默认值为 1。

随后的代码块演示了 std::ratio 算术的用法。每个编译时算术运算的结果总是被简化为最简形式。Ch16_01_ex1() 的最后一个代码块重点介绍了 std::ratio 比较。请注意,对于每种比较类型,成员值都会产生 true 或 false。

列表 16-1-2 显示了下一个 std::ratio 示例的源代码,该示例详细介绍了如何使用一些预定义的 SI2 std::ratio。

void Ch16_01_ex2()
{
    // using SI ratios
    using ra = std::milli;
    using rb = std::nano;
    std::ratio_add<ra, rb> rc_add;
    std::ratio_subtract<ra, rb> rc_sub;
    std::ratio_multiply<ra, std::micro> rc_mul;
    std::ratio_divide<ra, rb> rc_div;
    std::println("ra::num:    {:<20d} ra::den:    {:<20d}", ra::num, ra::den);
    std::println("rb::num:    {:<20d} rb::den:    {:<20d}", rb::num, rb::den);
    std::println("");
    std::println("rc_add.num: {:<20d} rc_add.den: {:<20d}", rc_add.num, rc_add.den);
    std::println("rc_sub.num: {:<20d} rc_sub.den: {:<20d}", rc_sub.num, rc_sub.den);
    std::println("rc_mul.num: {:<20d} rc_mul.den: {:<20d}", rc_mul.num, rc_mul.den);
    std::println("rc_div.num: {:<20d} rc_div.den: {:<20d}", rc_div.num, rc_div.den);
    // compile-time arithmetic overflow
//  using bad = std::ratio_multiply<rb, std::atto>;
    // more SI ratios
    using rd = std::giga;
    using re = std::tera;
    std::ratio_add<rd, std::mega> rf_add;
    std::ratio_subtract<std::peta, rd> rf_sub;
    std::ratio_multiply<std::kilo, rd> rf_mul;
    std::ratio_divide<rd, re> rf_div;
    std::println("");
    std::println("rd::num:    {:<20d} rd::den:    {:<20d}", rd::num, rd::den);
    std::println("re::num:    {:<20d} re::den:    {:<20d}", re::num, re::den);
    std::println("");
    std::println("rf_add.num: {:<20d} rf_add.den: {:<20d}", rf_add.num, rf_add.den);
    std::println("rf_sub.num: {:<20d} rf_sub.den: {:<20d}", rf_sub.num, rf_sub.den);
    std::println("rf_mul.num: {:<20d} rf_mul.den: {:<20d}", rf_mul.num, rf_mul.den);
    std::println("rf_div.num: {:<20d} rf_div.den: {:<20d}", rf_div.num, rf_div.den);
}

在定义了别名 `rb = std::nano` 之后,`Ch16_01_ex2()` 执行了一些 `std::ratio` 算术运算。再次强调,重要的是要记住这些都是编译时计算。如果编译器检测到算术溢出,它会标记一个错误。要查看实际效果,请移除 `using bad = std::ratio_multiply<rb, std::atto>` 语句的注释并编译代码。

`Ch16_01_ex2()` 的第二部分中的代码与其第一部分类似,但使用了更大的预定义 `std::ratios`。值得一提的是,示例函数 `Ch16_01_ex2()` 使用了 `using` 语句来定义 SI `std::ratios`,以避免代码行过长。对于生产代码,通常更倾向于直接使用预定义的 SI `std::ratios`。以下是示例 `Ch16_01` 的结果:

----- Results for example Ch16_01 -----
----- Ch16_01_ex1() -----
ra::num:   3 ra::den:   5
rb::num:   1 rb::den:  15
rc::num:  42 rc::den:   1
rd_add::num:   2 rd_add::den:   3
rd_sub::num:   8 rd_sub::den:  15
rd_mul::num:   1 rd_mul::den:  25
rd_div::num:   9 rd_div::den:   1
ra == rb: false
ra == rb: true
ra <  rb: false
ra <= rb: false
ra >  rb: true
ra >= rb: true
----- Ch16_01_ex2() -----
ra::num:    1                    ra::den:    1000
rb::num:    1                    rb::den:    1000000000
rc_add.num: 1000001              rc_add.den: 1000000000
rc_sub.num: 999999               rc_sub.den: 1000000000
rc_mul.num: 1                    rc_mul.den: 1000000000
rc_div.num: 1000000              rc_div.den: 1
rd::num:    1000000000           rd::den:    1
re::num:    1000000000000        re::den:    1
rf_add.num: 1001000000           rf_add.den: 1
rf_sub.num: 999999000000000      rf_sub.den: 1
rf_mul.num: 1000000000000        rf_mul.den: 1
rf_div.num: 1                    rf_div.den: 1000

持续时间


chrono::duration 用于测量两个时间点之间的时间量。每个 chrono::duration 包含一个节拍计数和一个节拍周期。后者是两个节拍之间经过的时间量(以秒为单位)。chrono::duration 是一个有理常数,并通过模板 std::ratio 以编程方式表示。命名空间 std::chrono 还包括其他日期和时间类,您将在本章中了解更多关于这些类的信息。

清单 16-2-1 显示了示例 Ch16_02_ex1() 的源代码。此示例演示了 chrono::durations 的基本用法。在此清单的顶部附近,包含语句 namespace chrono = std::chrono 以减少行长度并提高可读性。第一个 chrono::duration 用法 chrono::duration<int, std::ratio<3600 * 24>> day {1},将名为 day 的持续时间定义为 std::ratio<3600 * 24>(即 86400 / 1)(以秒为单位)。换句话说,一天的节拍对应于 86,400 秒的节拍周期。一天的节拍类型是 int。您可以指定节拍的其他类型,您很快就会看到。

//-------------------------------------------------------------------------
// Ch16_02_ex.cpp
//-------------------------------------------------------------------------
#include <chrono>
#include <typeinfo>
#include "Ch16_02.h"
namespace chrono = std::chrono;
//#define PRINT_DURATION_TYPEID  // remove comment to print duration typeids
void Ch16_02_ex1()
{
    // using chrono::duration
    chrono::duration<int, std::ratio<3600 * 24>> day {1};
    chrono::duration<int, std::ratio<3600 * 24 * 7>> week {1};
    chrono::duration<int, std::ratio<3600 * 24 * 14>> fortnight {1};
    // chrono::duration arithmetic - days
    auto num_days = 2 * fortnight - 4 * day + 3 * week;
    std::println("num_days:    {}", num_days);
    // using chrono::duration_cast<>
    auto num_hours = chrono::duration_cast<chrono::hours>(num_days);
    std::println("num_hours:   {}", num_hours);
    // using chrono::duration (non-int tick type)
    chrono::duration<double, std::ratio<3600>> minutes90 {1.5};
    // more chrono::duration arithmetic
    auto num_hours_d = 20.0 * minutes90 + 0.5 * minutes90;
    auto num_minutes = chrono::duration_cast<chrono::minutes>(num_hours_d);
    std::println("\nnum_hours_d:   {}", num_hours_d);
    std::println("num_minutes:   {}", num_minutes);
#ifdef PRINT_DURATION_TYPEID
    std::println("\ntypeid(num_days):    {}", typeid(num_days).name());
    std::println("typeid(num_hours):   {}", typeid(num_hours).name());
    std::println("typeid(num_hours_d): {}", typeid(num_hours_d).name());
    std::println("typeid(num_minutes): {}", typeid(num_minutes).name());
#endif
}

在列表 16-2-1 中使用的第二个 chrono::duration,chrono::duration<int, std::ratio<3600 * 24 * 7>> week {1},定义了 week,其以秒为单位的 tick 周期等于 std::ratio<3600 * 24 * 7>(或 604800 / 1)。第一个代码块中的最后一个 chrono::duration 示例,chrono::duration<int, std::ratio<3600 * 24 * 14>> fortnight {1},定义了一个为期两周的持续时间。

Ch16_02_ex1() 中的下一个代码块演示了使用先前定义的 chrono::durations 进行的简单算术运算。执行 num_days = 2 * fortnight - 4 * day + 3 * week 计算由算术表达式指定的天数。为什么是天数?当使用 chrono::durations 执行算术运算时,编译时计算是使用表达式中所有操作数的最大公约数进行的。因此,num_days 的结果类型是 chrono::duration<int, std::ratio<86400, 1>>,它表示一天中的秒数。要查看 chrono::duration typeids,请启用预处理器符号 PRINT_DURATION_TYPEID

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

akluse

失业老程序员求打赏,求买包子钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值