C++11日期与时间🕒

C++11为了表示时间,新增加了chrono库。这是一个比原本C库更加好用的时间表达库。 想要使用chrono,请包含chrono头文件并且使用命名空间chrono

一个最基本的例子

首先我们来通过一个基本的例子学习chrono库的操作,现在我想要测量下面这个函数运行的时间:

1
2
3
4
5
void fillGreen(Window* window){
  for(int i=0;i<window->w;i++)
    for(int j=0;j<window->h;j++)
			window->pixel[i*window->w+j] = MakeRGB(0, 255, 255);
}

首先,我们需要得到函数运行之间的时间:

1
chrono::steady_clock::time_point start = chrono::steady_clock::now();

steady_clock是一个稳定时钟,最适用于测量时间,所以我们用这个时钟。它是一个静态类,直接通过其静态函数now()(也是唯一一个函数)以获得当前的时间点。

注意now()返回的是时间点,而不是C语言通常意义上的时间。时间点用time_point模板类表示。

所以我们的返回值是一个steady_clock::time_point类型,表示是steady_clock的时间点类型(不同的时钟时间点不一样,后面会说道)。

然后我们可以如法炮制,得到函数运行后的时间:

1
2
3
chrono::steady_clock::time_point start = chrono::steady_clock::now();
fillGreen(window);
chrono::steady_clock::time_point end = chrono::steady_clock::now(); //得到运行后的时间

接下来我们需要得到经过的时间,方法是将两个时间点相减:

1
chrono::duration<float> durationSeconds = end - start;

这里需要用一下常识:时间点和时间点相减,显然代表经过的时间段。时间段使用duration模板类表示,其模板参数为时间段秒数的数据类型。这里使用浮点型。

最后我们输出经过的时间:

1
cout << durationSeconds.count() << endl;

通过count()可以得到经过的秒数。

深入chrono

从上面的例子中,我们可以得知chrono时间库的基本概念:

  • 时间点time_point:用于表示一个时间点
  • 时间段duration:用于表示一段时间
  • 时钟:时钟有三个,都是静态的
    • system_clock:系统时钟,通过调用其now()函数来得到当前系统时间
    • steady_clock:稳定时钟,最适用于测量时间间隔
    • high_resolution_clock:高精度时钟。不推荐使用,理由是不跨平台,而且通常的实现就是system_clock或者steady_clock

接下来我们更加深入地看一看这些概念

时钟

时钟的精确定义是:

时钟由起点(或纪元)及计次频率组成。例如,时钟可以拥有 1970 年 1 月 1 日的纪元,和每一秒的计次。

简单来说,时钟首先需要一个起点,比如1970年1月1日凌晨12点整。然后由于是时钟,需要计时的嘛,还得给一个计次频率,就是隔多长时间时钟的指针动一下。我们日常生活以秒计,那么计次频率就是1秒。而有些时候你可能需要以15分钟计,或者30分钟计,那计次频率就是15分钟或30分钟。

chrono不允许我们定义时钟,我们只能用其定义的两个时钟之一(high_resolution_clock不做说明)。这些时钟都是静态类

system_clock

定义如下:

大多数系统上,系统时间可以在任何时候被调节。它是唯一有能力映射其时间点到 C 风格时间的 C++ 时钟。

不指定 system_clock 的起始时间,但多数实现使用 Unix 时间(即从协调世界时 (UTC) 1970 年 1 月 1 日星期四 00:00:00 开始的时间,不计闰秒)。

注意,这是唯一有能力映射时间点到C风格(time_t类型)的C++时钟,这意味着steady_clock没办法做到和C时间相互转换。

system_clock的计次频率不同机器不一样,一般都是一纳秒或者几纳秒。

通过其now()函数得到当前的时间点,返回值类型为system_clock::time_point

例子-获得当前时间点:

chrono::system_clock::time_point time = chrono::system_clock::now();
cout<<time.time_since_epoch().count()<<endl;

注意这里的第二行获得时间的方式,time_point模板类可以通过time_since_epoch()的方式得到从时钟起始时间到当前时间点经过的时间间隔。这里其实就是返回系统当前的时间,和C语言的time()一样(而且比C语言还精确)。

其和C语言时间转换通过其静态函数to_time_t()from_time_t()实现。

steady_clock

steady_clock不随着系统时钟改变的时钟,最适用于记录时间间隔的时钟

意思就是说,在你使用steady_clock的使用修改了系统时间,这个时钟的结果是不会受到影响的。

其只有一个静态方法now()用于得到当前时间点。

时间点time_point

类模板 std::chrono::time_point 表示时间中的一个点。它被实现成如同存储一个 Duration 类型的自 Clock 的纪元起始开始的时间间隔的值。

简单来说,time_point一般内含一个duration

现在我们来看一下其模板声明:

1
2
3
4
template<
    class Clock,
    class Duration = typename Clock::duration
> class time_point;

可以看到,其有两个模板参数,分别是时间点对应的时钟的类型,和时钟对应的时间段类型。所以不同的时钟是拥有自己的时间点的,不同时钟之间的时间点不能相互转换。

使用time_since_epoch可以得到时钟起始时间到当前时间点之间的时间段

时间段duration

通过时间点之间的加减法(也没有其他运算法则),我们可以得到时间段。其模板定义如下

1
2
3
4
template<
    class Rep,
    class Period = std::ratio<1>
> class duration;

它由 Rep 类型的计次数和计次周期组成,其中计次周期是一个编译期有理数常量,表示从一个计次到下一个的秒数。

存储于 duration 的数据仅有 Rep 类型的计次数。若 Rep 是浮点数,则 duration 能表示小数的计次数。 Period 被包含为时长类型的一部分,且只在不同时长间转换时使用。

简单来说,Rep表示了计次数这个数据的类型,而Period则代表计次周期。如果你的Rep是int,那么duration只能记录一个计次周期,两个计次周期。若为float,则可以记录1.5个计次周期。

所以可以看到,不同的时钟和时间点对应的时间段也不一样。同类型的时间段存在四则运算和自增,自减运算。不同时间段之间可以使用duration_cast()转换函数来转换。

通过count()函数得到其记录的时间(实际是计次周期的计数)。

chrono还给出了一些常用duration的typedef:

1
2
3
4
5
6
std::chrono::nanoseconds	 duration</*至少 64 位的有符号整数类型*/, std::nano>
std::chrono::microseconds	 duration</*至少 55 位的有符号整数类型*/, std::micro>
std::chrono::milliseconds	 duration</*至少 45 位的有符号整数类型*/, std::milli>
std::chrono::seconds	     duration</*至少 35 位的有符号整数类型*/>
std::chrono::minutes	     duration</*至少 29 位的有符号整数类型*/, std::ratio<60>>
std::chrono::hours	       duration</*至少 23 位的有符号整数类型*/, std::ratio<3600>>

总结

其实不需要自己定义时间点和时间段,但是深入理解他们的实现方式,有助于我们了解不同时间段和时间点之间的转换关系。

updatedupdated2023-06-182023-06-18