本章解释了时间库中重要的类和算法。它还涵盖了比例库中的补充类。讨论的主题包括:
- 使用 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

最低0.47元/天 解锁文章
2万+

被折叠的 条评论
为什么被折叠?



