Wikibooks zhwikibooks https://zh.wikibooks.org/wiki/Wikibooks:%E9%A6%96%E9%A1%B5 MediaWiki 1.45.0-wmf.7 first-letter Media Special Talk User User talk Wikibooks Wikibooks talk File File talk MediaWiki MediaWiki talk Template Template talk Help Help talk Category Category talk Transwiki Transwiki talk Wikijunior Wikijunior talk Subject Subject talk TimedText TimedText talk Module Module talk C++/STL/chrono 0 22433 181595 180904 2025-06-30T03:16:39Z 140.82.205.235 /* 时钟clock */ 181595 wikitext text/x-wiki [[:w:C++程序设计语言|程序设计语言]]中,'''<code>chrono</code>'''是[[:w:标准模板库|标准模板库]](STL)中与时间有关的头文件(自[[:w:C++11|C++11]]开始)。该头文件中所有函数与类模板均定义在std::chrono[[:w:命名空间|命名空间]]中。 C++20增加了很多功能: * 新的时钟 * 时间点和时长duration的直接输出 * 方便的日期支持 ==概念== *duration:时间长度,如1分钟、2小时、10毫秒等。表示为类模板duration的对象,用一个count representation与一个period precision表示。例如,10毫秒的10为count representation,毫秒为period precision。 *time points:表示一个时间点。例如某人的生日、今天的日出时间等。表示为类模板time_point的对象。用相对于一个固定时间点epoch的duration来表示。 *clocks:时间点相对于真实物理时间的框架。C++11提供了3个clock,C++20还提供了4个clock: **system_clock:可被管理员手工调整 **steady_clock:不受管理员人为影响,适用于时间流逝的计量 **high_resolution_clock **utc_clock — UTC时间,包括闰秒 **tai_clock — 国际原子时间,不含闰秒 **gps_clock — GPS时间,不含闰秒 **file_clock — 文件系统时间 ==类模板== ===时间长度duration=== 表示time span。该类模板声明为: template <class Rep, class Period=ratio<1> > class duration; 第一个模板参数为存储时间计数的数据类型,如整型、浮点型等。成员函数count()返回该计数。第二个模板参数表示计数的一个周期,一般是std::ratio(即有理数)类型,表示一个周期(即一个时间嘀嗒tick)是[[:w:秒钟|秒钟]]的倍数或分数,在[[:w:编译时|编译时]]应为一个[[:w:有理数|有理数]]常量。 duration模板类实例化typedef: {| class="wikitable" |- ! 类型 !! Rep !! Period |- | years|| signed integral至少17比特 || std::ratio<86400> |- | months|| signed integral至少20比特 || ratio<2629746,1> |- | weeks|| signed integral至少22比特 || ratio<604800,1> |- | days|| signed integral至少25比特 || ratio<86400,1> |- | hours || signed integral至少23比特 || ratio<3600,1> |- | minutes || signed integral至少29比特 || ratio<60,1> |- | seconds || signed integral至少35比特 || ratio<1,1> |- | miliseconds || signed integral至少45比特 || ratio<1,1000> |- | microseconds || signed integral至少55比特 || ratio<1,1000000> |- | nanoseconds || signed integral至少64比特 || ratio<1,1000000000> |- |rep||对应于模板的第1个参数|| |- |period|| 对应于模板的第2个参数|| |} chrono提供的预定义的时间长度单位都是int64类型的内部存储数据。 *duration模板类成员函数: **构造函数 **析构函数 **count 返回时间嘀嗒计数 **zero: 静态函数,返回0值实例 **min:静态函数,表示最小可能值。例如<code>std::cout << (seconds::min)();</code> **max:静态函数,表示最大可能值 **operator= 赋值运算符 **operator+ 酉运算符 **operator- 酉运算符 **operator++ 前缀++ **operator++(int) 后缀++ **operator-- 前缀-- **operator--(int) 后缀-- **operator+= **operator-= **operator*= **operator/= **operator%= *非成员函数 **std::common_type<std::chrono::duration> 是类模板std::common_type的特化版本。给出两个duration类的共同类型,即分母是最小公倍数。 **operator+ **operator- **operator* **operator/ **operator% **operator== **operator!= **operator< **operator<= **operator> **operator>= **duration_cast 把时间长度转换到不同时间嘀嗒单位的另一个时间长度。存在截断误差时才需要这种显式类型转换。 **floor(std::chrono::duration) [[:w:C++17|C++17]] 转换时间长度向下近似 **ceil(std::chrono::duration) [[:w:C++17|C++17]] 转换时间长度向上近似 **round(std::chrono::duration) [[:w:C++17|C++17]] 转换时间长度向最近偶数近似 **abs(std::chrono::duration) [[:w:C++17|C++17]] 时间长度的绝对值 *帮助类Helper classes **treat_as_floating_point 表示一个时间长度可转换为不同的时间嘀嗒单位 **duration_values 构造给定类型的repr类型的零值zero、最小值min、最大值max 等3个constexpr函数。例如<code>auto iiii = std::chrono::duration_values<int>::min; auto jj = iiii();</code> ====[[:w:C++14|C++14]]标准定义的[[:w:字面常量 (C语言)|字面量]]==== {{main|w:用户定义字面量}} 均定义在std::literals::chrono_literals命名空间中。因此需要先声明<code>using namespace std::chrono_literals;</code> *operator""h *operator""min *operator""s *operator""ms *operator""us *operator""ns 未定义大于小时的字面常量。 ====初始化==== duration初始化的形式: * std::chrono::seconds v1{3}; //不允许用圆括号或者赋值号 * std::chrono::seconds v2{4min}; //相当于拷贝赋值 ====例子==== 为了区分秒数和普通的数值之间的差异,seconds类在设计的时候就禁止了数值隐式转换为seconds类 <syntaxhighlight lang="c++"> seconds s = 3; // 编译出错 seconds s1{3}; // 这样是可以的 s1 = 4; // 编译出错 std::cout << s << "\n"; // C++20可以,输出为 3s std::cout << s.count() << "s\n"; // 这样是可以的 seconds s2 = 3s; // C++14支持字面常量 seconds s3 = s2 + s1; // 支持基本的算术操作,但是不支持seconds和一个普通数值进行算术操作 seconds::min(); // 获取秒类型可以表示的范围, seconds::max(); minutes m1{2}; seconds s2 = m1; // m1隐式转换为seconds seconds s3 = s2 + m1; // m1隐式转换为seconds,然后运算 //不同时间长度单位的隐式转换必须是向下转换,如minutes转seconds。如果向上转换需用duration_cast显式转换。因为这些转换是截断近似,而不是四舍五入。 seconds s2{1000}; minutes m3 = std::chrono::duration_cast<minutes>(s2); </syntaxhighlight> ===时间点time_point=== 表示一个时间点,即一个时刻的值。它存储为从一个固定时刻(epoch)开始的一段时间的长度(duration)。C++20规定system clock采用的是Unix的时间戳即UTC 1970年1月1日 00:00。时间点的类模板形如: <syntaxhighlight lang="cpp"> template <class Clock, class Duration = typename Clock::duration> class time_point { Duration d_; public: using clock = Clock; using duration = Duration; // ... }; </syntaxhighlight> *类型定义 **clock 使用的时钟 **duration 一个std::chrono::duration类型,用于度量从固定时刻开始的时间的长度 **rep 算术类型,表示时间长度的嘀嗒(tick)数 **period 一个std::ratio类型,表示一个时间嘀嗒的单位 *成员函数 **(constructor) **time_since_epoch 返回从固定时刻开始的时间长度。 **operator+= 修改时间长度 **operator-= 修改时间长度 **min 静态成员函数,最小可能的时间长度 **max 静态成员函数,最大可能的时间长度 *非成员函数 **std::common_type<std::chrono::time_point> 是类模板std::common_type的特化版本 **operator+ 修改时间长度 **operator- 修改时间长度 **operator== 比较时间点 **operator!= 比较时间点 **operator< 比较时间点 **operator<= 比较时间点 **operator> 比较时间点 **operator>= 比较时间点 **time_point_cast 函数模板,在同一时钟下把一个时间点转化为具有不同时钟滴答长度的另一个时间点。所以第一个模板参数是目标的时钟抵达长度类型。 **clock_cast [[:w:C++20|C++20]]引入,可以转换不同时钟下的时间点。例如<code>utc_clock::time_point t = clock_cast<utc_clock>(file_clock::now());</code> **floor(std::chrono::time_point) [[:w:C++17|C++17]] 函数模板 转化时间点并向下近似 **ceil(std::chrono::time_point) [[:w:C++17|C++17]] 函数模板 转化时间点并向上近似 **round(std::chrono::time_point) [[:w:C++17|C++17]] 函数模板 转化时间点并近似到最近邻的偶数 不同类型的时间点之间也可以相互转换,可隐式的向下转换;如果向上转换就必须显式的使用time_point_cast: <syntaxhighlight lang="cpp"> using namespace std::chrono; template <class D> using sys_time = time_point<system_clock, D>; sys_time<seconds> tp{5s}; // 5s sys_time<milliseconds> tp2 = tp; // 5000ms 隐式的向下转换 tp = time_point_cast<seconds>(tp2); // 5s 向下转换,需要显示的使用time_point_cast </syntaxhighlight> ===时钟clock=== C++11预定义3个时钟,形如: <syntaxhighlight lang="cpp"> struct some_clock { using duration = chrono::duration<int64_t, microseconds>; using rep = duration::rep; using period = duration::period; using time_point = chrono::time_point<some_clock>; static constexpr bool is_steady = false; static time_point now() noexcept; }; </syntaxhighlight> C++20之前,不同时钟的时间点无法用标准库的工具相互转换: <syntaxhighlight lang="cpp"> system_clock::time_point tp = system_clock::now(); steady_clock::time_point tp2 = tp; // no viable conversion </syntaxhighlight> [[:w:C++20|C++20]]引入结构模板clock_time_conversion,可以转换不同时钟下的时间点。其()运算符可把一个时间点转化为目标时钟下的对应时间点。以system时钟或者utc时钟为转换中介。例如: <syntaxhighlight lang="cpp"> auto sd = sys_days{ 2021y / July / 26 }; auto time = clock_time_conversion<utc_clock, system_clock>{}(sd); std::cout << time << "\n"; </syntaxhighlight> Visual C++ 2015把steady_clock都用QueryPerformanceCounter实现;把high_resolution_clock定义为steady_clock的别名。 ====system_clock==== 当前系统范围(即对各进程都一致)的一个实时的日历时钟(wall clock)。但是这个值并不一定是单调的,因为操作系统可能会修改时间,导致system_clock返回的时间不单调。 *成员类型: **rep 表示时钟滴答计数的算术类型 **period 表示时钟滴答长度的std::ratio类型,以秒钟为单位长度 **duration std::chrono::duration<rep, period> 微软实现为 duration<long long, ratio<1, 10'000'000>即100纳秒。 **time_point std::chrono::time_point<std::chrono::high_resolution_clock> 。微软实现epoch为1970-01-01 00:00:00 UTC *成员常量: **constexpr bool is_steady (public static member constant) 表示该时钟的每个时间嘀嗒单位是否为均匀(即长度相等) 。 *成员函数 **now 静态函数 返回时钟的当前值,类型为std::chrono::time_point **to_time_t 静态函数 把系统时钟的时间点转化为C风格的std::time_t **from_time_t 静态函数 把C风格的std::time_t转化为系统时钟的时间点 C++20引入3个预定义的time_point特化类: * sys_time * sys_seconds * sys_days ====steady_clock==== 当前系统实现的一个稳定时钟。表示的是单调时钟,随着物理时间向前,这个时钟的时间点不会减少,最适合进行间隔的测量。 *成员类型: **rep 表示时钟滴答计数的算术类型 **period 表示时钟滴答长度的std::ratio类型,以秒钟为单位长度 **duration std::chrono::duration<rep, period> 微软实现为duration<long long, nano> **time_point std::chrono::time_point<std::chrono::high_resolution_clock> *成员常量: **constexpr bool is_steady (public static member constant) 表示该时钟的每个时间嘀嗒单位是否为均匀(即长度相等) 。该值总为true *成员函数 **now 静态函数 返回时钟的当前值,类型为std::chrono::time_point 注意,steady_clock的epoch不能转化为system timepoint;steady_clock的时间点不能输出输入给stl的流。实质上这相当于steady_clock是一个计时器而不是时钟,两个时间点相减得到的duration是有意义的,不存在时间原点epoch。 ====high_resolution_clock==== 当前系统实现的最高分辨率的时钟。微软实现该时钟为steady_clock的类型别名。 *成员类型: **rep 表示时钟滴答计数的算术类型 **period 表示时钟滴答长度的std::ratio类型,以秒钟为单位长度 **duration std::chrono::duration<rep, period> **time_point std::chrono::time_point<std::chrono::high_resolution_clock> *成员常量: **constexpr bool is_steady (静态) 表示该时钟的每个时间嘀嗒单位是否为均匀(即长度相等) *成员函数 **now 静态函数 返回时钟的当前值,类型为std::chrono::time_point ====utc_clock==== 此时钟自1970-01-01 00:00:00 UTC开始计时。此时钟计算闰秒,是世界各地民用时间的基础。MSVC2022已经完全实现它。自2016年最后一次闰秒以来,utc_clock的时间戳比system_clock的时间戳大27秒钟。 3个静态成员函数: * now * to_sys * from_sys 非成员函数: *get_leap_second_info C++20引入2个预定义的time_point特化类: * utc_time * utc_seconds ====tai_clock==== epoch为1958-01-01 00:00:00.0000000UTC。 duration微软实现为duration<long long, ratio<1, 10'000'000>,即100纳秒。 3个静态成员函数: * from_utc * now * to_utc C++20引入2个预定义的time_point特化类: * tai_time * tai_seconds ====gps_clock==== epoch为1980-01-06 00:00:00.0000000UTC duration微软实现为duration<long long, ratio<1, 10'000'000>,即100纳秒。 3个静态成员函数: * from_utc * now * to_utc C++20引入2个预定义的time_point特化类: * gps_time * gps_seconds ====file_clock==== 为std::filesystem::file_time_type的别名类型。不是steady时钟,即有闰秒插入。 epoch微软实现为1601-01-01 00:00:00.0000000UTC duration微软实现为duration<long long, ratio<1, 10'000'000>,即100纳秒。 静态成员函数: * now * to_utc * from_utc * to_sys * from_sys 由于C++20要求编译器实现to_utc/from_utc或to_sys/from_sys中的一套,因此建议使用clock_cast,例如<code>clock_cast<file_clock>(utc_clock::now())</code> C++20引入2个预定义的time_point特化类:file_time ===local_t=== C++20引入,表示本地时间的伪时钟。用于std::chrono::time_point的构造函数的第一个参数,实际上表示还没有跟具体哪个时钟相结合的时间点。 以此特化了3个std::chrono::time_point模板实例类: * template<class Duration> using '''local_time''' = std::chrono::time_point<std::chrono::local_t, Duration>; * using '''local_seconds''' = local_time<std::chrono::seconds>; * using '''local_days''' = local_time<std::chrono::days>; ==一天之内的时间== C++20用类模板std::chrono::hh_mm_ss把时间滴答长度表示为小时、分钟、秒钟、小数秒。主要用于格式化。成员函数有is_negative、hours、minutes、seconds、subseconds等。例如:<code> cout << std::chrono::hh_mm_ss{ chrono::milliseconds{1003} } << '\n';</code> 对于std::chrono::hours,std::chrono包含函数is_am、is_pm、make12、make24用于判断是否为上午[0h, 11h],否则为下午;转换为12小时/24小时表示。 ==日期== C++20在std::chrono中增加: *last_spec:标记类用于定义一个类实例last,可表示月度最后一天,月度最后一个星期几。特殊的 last 常量,表示一个月的最后一天或星期几 *day 类,表示月度中的一天,范围为[1,31],但也可[0, 255]. *month 类,表示月度。范围为[1,12],但也可[0, 255]。预定义了12个月的英文名字作为类的实例如January值为1。 * year 类,表示年份。范围为 [-32767, 32767] * weekday 类,表示星期各天名称。范围为[0,6],但也可[0, 255]。预定义了7个星期内各天名字作为类的实例如Sunday值为0 . **重载了运算符[],下标如果为整型值i,表示未指定的月度内的第i个星期几;如果下标值为std::chrono::last_spec,表示未指定月度内的最后一个星期几。 * weekday_indexed 类,表示月度内的第几个星期几 * weekday_last 类,表示月度内的最后一个星期几 * month_day 类,表示指定月的第几天 * month_day_last类,表示月度的最后一天 * month_weekday类,表示指定月度的第几个星期几 * month_weekday_last类,表示制定月度的最后一个星期几 * year_month类,表示指定年度的指定月度 * year_month_day类,表示指定的日期 * year_month_day_last类,表示指定年月的最后一天 * year_month_weekday类,表示指定年月的第几个星期几。 * year_month_weekday_last类,表示指定年月的最后一个星期几 *operator/ 运算符重载,为方便创建格里高利历日期 **使用 / 运算符来拼接日期(结果是更复杂的类型,如 year_month、year_month_day 等)。创建完整日期,允许3种顺序: *** year/month/day *** month/day/year *** day/month/year. *** 其中允许day替换为: **** std::chrono::last, 月度最后一天 **** weekday[i], 月度内第i个星期几 **** weekday[std::chrono::last], 月度内最后一个星期几 ***纯整数可被接手,如果不引起歧义,如2005y/4/5可以,但5/April/2005不行。 **部分日期类型,如year_month、month_day等可以按上述3种顺序,不使用第二个operator/ * 单独的 years、months、days、weeks 时长类型,以及跟这些类型相关的特殊运算 * Sunday、Monday、Tuesday 等表示星期几的常量,通常结合 [] 运算符表示某月的第几个 * January、February、March 等表示月份的常量 *两种新的自定义字面量后缀:表示年的 y,和表示月中某天的 d 示例,注意日期的头两项里应当明确指定一个年、月、日或星期几相关的类型,否则解析存在歧义,会导致编译失败: * 2025y/3/10:2025 年 3 月 10 日 * 2025y/March/10d:同样,2025 年 3 月 10 日 * 10d/3/2025:还是 2025 年 3 月 10 日 * March/10/2025:还是 2025 年 3 月 10 日 * Sunday[2]/5/2025:2025 年 5 月第 2 个星期天 表达式的类型都是year_month_weekday * 2025y/5/last:2025 年 5 月的最后一天 表达式的类型都是year_month_day_last 目前 chrono 库里跟月加减的逻辑是这样的: *对于 year_month_day、year_month_day_last 之类的类型,加减月的运算是对底层表示里的月的部分进行调整(有进位或借位时可能修改年)。 *对于普通的时间点类型,那加减月的时候,月将会被简单理解成一年的十二分之一,即 30 天 10 小时 29 分钟 06 秒。 显然,需要时间点跟 year_month_day 等类型之间的转换,这是在后者的构造函数和转换函数里实现的。这里,我们需要用到 C++20 引入的 sys_days 类型: template <class Duration> using sys_time = time_point<system_clock, Duration>; using sys_days = sys_time<days>; 也就是说,sys_days 是以天为单位的时间点。year_month_day、year_month_day_last 和 year_month_weekday 可以转换成 sys_days,下面的代码可以看出这些不同类型的效果: ==时区== 用 get_tzdb() 获取时区数据库的引用,调用其 current_zone 成员函数获得本地时区的指针,然后用 zoned_time 创建出一个带时区的时间点对象(zoned_tp )。 * tzdb: 时区库 * tzdb_list:tzdb的链表 * get_tzdb * get_tzdb_list * reload_tzdb * remote_version * locate_zone函数:按照名字返回一个time_zone。例如:get_tzdb().locate_zone("US/Pacific"); * current_zone 函数,返回当前的time_zone对象。其name如为“Asia/Shanghai” * time_zone类:表示一个时区。const访问。有成员函数name、get_info等。 * sys_info 底层类,一般不用于编程。 * local_info 类:represents information about a local time to UNIX time conversion * choose 枚举类,成员值为earliest,latest * zoned_traits * zoned_time 表示一个时区及时间点。也可做同一时间点不同时区的换算。例如:<code>std::chrono::zoned_time zt{ "Asia/Shanghai",std::chrono::local_days{18d / std::chrono::January / 2022} + 15h + 15min }; std::chrono::zoned_time zt2{ "America/New_York", zt };</code> * leap_second * leap_second_info 类:给出一个UTC时间点的闰秒总数。 * get_leap_second_info 函数模板:给出一个UTC时间点返回leap_second_info实例。 * time_zone_link 类:一个时区的可选名字。 * nonexistent_local_time 异常类 * ambiguous_local_time 异常类 ==chrono I/O== std::chrono::parse函数模板:从输入流中分析一个chrono对象。对时间字符串流的解析(parse 和 from_stream) ==C++20的一个例子== <syntaxhighlight lang="cpp"> // C++20 #include <chrono> #include <format> #include <iostream> int main() { using TwentyMins = std::chrono::duration<int, std::ratio<20*60>>; std::chrono::time_zone const* myTimeZone = std::chrono::current_zone(); std::chrono::time_point p1 = std::chrono::system_clock::now(); std::chrono::time_point p2 = myTimeZone->to_local(p1); std::chrono::time_point p3 = std::chrono::floor<TwentyMins>(p2); std::cout << std::format("{:%Y-%m-%d %H:%M}\n", p3); } </syntaxhighlight> ==表示为字符串的示例== <syntaxhighlight lang="cpp"> #include <iostream> #include <chrono> #include <ratio> #include <time.h> #include <iomanip> #include <sstream> #include <string> int main() { using namespace std::chrono; auto now = system_clock::now(); time_t t = system_clock::to_time_t(now); std::tm tm; localtime_s(&tm, &t); auto time = std::put_time(&tm, "%b %d %Y %H:%M:%S"); std::cout << time << std::endl; std::stringstream ss; ss << time; std::string a{ ss.str() }; } </syntaxhighlight> ==例子== ===时间点的示例=== <syntaxhighlight lang="cpp"> #include <iostream> #include <iomanip> #include <ctime> #include <chrono> int main() { std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); std::time_t now_c = std::chrono::system_clock::to_time_t(now - std::chrono::hours(24)); std::cout << "24 hours ago, the time was " << std::put_time(std::localtime(&now_c), "%F %T") << '\n'; std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); std::cout << "Hello World\n"; std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); std::cout << "Printing took " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << "us.\n"; } </syntaxhighlight> ===时间长度的示例=== <syntaxhighlight lang="cpp"> #include <iostream> #include <chrono> int main() { using shakes = std::chrono::duration<int, std::ratio<1, 100000000>>; using jiffies = std::chrono::duration<int, std::centi>; using microfortnights = std::chrono::duration<float, std::ratio<12096,10000>>; using nanocenturies = std::chrono::duration<float, std::ratio<3155,1000>>; std::chrono::seconds sec(1); std::cout << "1 second is:\n"; std::cout << std::chrono::duration_cast<shakes>(sec).count() << " shakes\n"; std::cout << std::chrono::duration_cast<jiffies>(sec).count() << " jiffies\n"; std::cout << microfortnights(sec).count() << " microfortnights\n"; std::cout << nanocenturies(sec).count() << " nanocenturies\n"; } </syntaxhighlight> ===综合示例=== <syntaxhighlight lang="cpp"> #include <iostream> #include <chrono> #include <ctime> long fibonacci(unsigned n) { if (n < 2) return n; return fibonacci(n-1) + fibonacci(n-2); } int main() { std::chrono::time_point<std::chrono::system_clock> start, end; start = std::chrono::system_clock::now(); std::cout << "f(42) = " << fibonacci(42) << '\n'; end = std::chrono::system_clock::now(); std::chrono::duration<double> elapsed_seconds = end-start; std::time_t end_time = std::chrono::system_clock::to_time_t(end); std::cout << "finished computation at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << "s\n"; } </syntaxhighlight> ==参考文献== * [http://en.cppreference.com/w/cpp/chrono Data and Time Utilities in cppreference.com] {{reflist}} 1ph3qoer7z0kbs1051dln6j8cvr4wr2