C++11时间详解

转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46854229

C++ 11增加了三个与时间相关的类型:时间段、时钟、时间点。

以史为鉴

现有的系统API中,时间太过于碎片化了。有time_t(秒)、struct timeval(微秒)、struct timespec(纳秒)这几个时间单位,他们的接口非常不统一,点击这里可以体会一下。主要原因:是由于新业务的需求,要求提供不同精度的时间。于是每次出现新需求就定义一个新类型。为此,C++11提出一个”一统江湖”的做法,避免这类现象的发生。

时间精度其实也就是时间分辨率。抛开时间量纲单论分辨率,其实就是一个比率。如:10001、101、11 、110、11000。这些比率加上距离量纲就变成距离分辨率,加上时间量纲就变成时间分辨率了。为此,C++11定义了一个新的模板类ratio,用于表示比率,定义如下:

//#include<ration>
template<std::intmax_t Num, std::intmax_t Denom = 1> //前者是分子,后者是分母
class ratio;

为了方便,C++标准委员会还预定义了下面这些分辨率,供用户使用。

//#include<ratio>
typedef ratio<1,       1000000000000000000> atto;
typedef ratio<1,          1000000000000000> femto;
typedef ratio<1,             1000000000000> pico;
typedef ratio<1,                1000000000> nano;
typedef ratio<1,                   1000000> micro;
typedef ratio<1,                      1000> milli;
typedef ratio<1,                       100> centi;
typedef ratio<1,                        10> deci;
typedef ratio<                       10, 1> deca;
typedef ratio<                      100, 1> hecto;
typedef ratio<                     1000, 1> kilo;
typedef ratio<                  1000000, 1> mega;
typedef ratio<               1000000000, 1> giga;
typedef ratio<            1000000000000, 1> tera;
typedef ratio<         1000000000000000, 1> peta;
typedef ratio<      1000000000000000000, 1> exa;

时间段

30分钟,0.5秒都表示一段时间。由此可以得到:一段时间是由”数值+时间单位”组成的。反映在编程上就是要存储两个变量。显然,数值有整数和小数两种,究竟是使用整数(int)还是小数(double)应该由用户指定。无疑,用模板表示之是非常合适的。结合前面提到的时间精度,不难得出一段时间应该定义为如下形式:

//#include<chrono>
template<class Rep, class Period = std::ratio<1> >
class duration;

于是,30秒就有下面几种表示方式:

//Author: luotuo44   http://blog.csdn.net/luotuo44
std::chrono::duration<int, std::ratio<1,1>> a(30);//30秒
std::chrono::duration<int> b(30);//30秒
std::chrono::duration<int, std::ratio<1, 1000>> c(30000);//30 000毫秒
std::chrono::duration<double, std::ratio<60,1>> d(0.5);//半分钟

同样,C++标准委员会预定义了下面这些类型,供我们直接使用。

std::chrono::nanoseconds    duration</*signed integer type of at least 64 bits*/, std::nano>
std::chrono::microseconds   duration</*signed integer type of at least 55 bits*/, std::micro>
std::chrono::milliseconds   duration</*signed integer type of at least 45 bits*/, std::milli>
std::chrono::seconds    duration</*signed integer type of at least 35 bits*/>
std::chrono::minutes    duration</*signed integer type of at least 29 bits*/, std::ratio<60>>
std::chrono::hours  duration</*signed integer type of at least 23 bits*/, std::ratio<3600>>

又是讨厌的implementation-defined,gcc4.9.0的一个实现为:

typedef duration<int64_t, nano>         nanoseconds;
typedef duration<int64_t, micro>        microseconds;
typedef duration<int64_t, milli>        milliseconds;
typedef duration<int64_t>           seconds;
typedef duration<int64_t, ratio< 60>>   minutes;
typedef duration<int64_t, ratio<3600>>  hours;

当定义了一个时间段后,如何从中得知该时间段类型的rep和period模板参数的类型呢?其实,当我们用具体的类型实例化duration模板类后,实例化的类就会有rep和period这两个成员类型。于是,可以借助C++11中的关键字decltype以及直接从定义中获取。

//Author: luotuo44   http://blog.csdn.net/luotuo44
typedef std::chrono::duration<int, std::ratio<60>> Minus;
Minus::rep a = 30; //a是int类型
Minus::period per;//per的类型是 std::ration<60, 1>。即per.num 为60, per.den 为1

Minus twenty_seconds(20);
decltype(twenty_seconds.count()) b = 30;//后面会介绍count()的返回值类型, b的类型为int
decltype(twenty_seconds)::rep e = 40;//e的类型为int

duration模板类提供了一个成员函数count()用户获取该时间段的值,这个值也称为滴答数。它的返回值类型就是实例化后成员类型rep。

//Author: luotuo44   http://blog.csdn.net/luotuo44
std::chrono::seconds twenty_sec(20);
int c = twenty_sec.count();//c等于20
std::chrono::duration<double> half_sec(0.5);
double d = half_sec.count();//d等于0.5

时钟

时钟有电子表、怀表、秒表、原子钟等。这些时钟的时间精度有所不同。此外,对于电子表的时间是无法取小数的,而怀表则可以人为地取小数。这说明不同的时钟也会有前面时间段类型duration的rep和period这两个属性。并且也是以成员类型的形式出现。但时钟类本身不是模板类。

C++11为我们提供了三种时钟类型:system_clock、steady_clock、high_resolution_clock。 这三个时间类都提供了rep、period、duration成员类型。因为各个系统能提供的时间精度可能不同,所以period的真正类型是implementation-defined的。这三个时钟类都提供了一个静态成员函数now()用于获取当前时间,该函数的返回值是一个time_point类型,后面会介绍。

注意:,虽然这三个时钟都很多相同的成员类型和成员函数,但它们是没有亲缘关系的。这三个时钟类型都是类,并非模板类。

这三个时钟有什么区别呢?system_clock就类似Windows系统右下角那个时钟,是系统时间。明显那个时钟是可以乱设置的。明明是早上10点,却可以设置成下午3点。steady_clock则针对system_clock可以随意设置这个缺陷而提出来的,他表示时钟是不能设置的。想了解更多,可以点击这里。high_resolution_clock则是一个高分辨率时钟。

system_clock除了now()函数外,还提供了to_time_t()静态成员函数。用于将系统时间转换成熟悉的std::time_t类型,得到了std::time_t类型的值,就可以很方便地打印当前时间了。

//Author: luotuo44   http://blog.csdn.net/luotuo44
auto tp = std::chrono::system_clock::now();
std::time_t cur_time = std::chrono::system_clock::to_time_t(tp);
std::string str_time = std::ctime(&cur_time);
std::cout<<str_time<<std::endl;

注意:不要将steady_clock、high_resolution_clock时钟的now()返回值作为to_time_t的参数,这会导致编译通不过。因为类型不匹配,后面会详解。

其实,如果读者有把刚才那个链接点开,并去了解的steady_clock的话,不用了解now()返回值类型就能知道这样做肯定是不行的了。steady_clock的实现是使用monotonic时间,而monotonic时间一般是从boot启动后开始计数的。明显这不能获取日历时间(年月日时分秒)。那么steady_clock有什么用途呢?时间比较!并且是不受用户调整系统时钟影响的时间比较。简单的例子如下:

//Author: luotuo44   http://blog.csdn.net/luotuo44
auto begin = std::chrono::steady_clock::now();
for(int i = 0; i < 10000000; ++i)
{
    //复杂的数学运算
}
auto end = std::chrono::steady_clock::now();
auto diff = (end - begin).count();//end-begin得到一个duration类型
std::cout<<diff<<std::endl;

即使在做数学运算的时候,有人修改了系统时间,也能准确计算出for循环消耗的时间。

时间点

前面已经指出:时钟类的now()函数的返回值是一个time_point类(型)。不言而喻,时间点(time_point)必然是时钟相关的。从怀表读取到的时间(点)只有小时、分钟、秒,手机则能提供日期+时分秒。此外,不同时钟提供的时间精度也有所不同。所以,时间点(time_point)类至少得有时钟类型、时间精度这两个属性。同前面介绍的时间段模板类一样,这两个属性也是以模板参数的形式出现。time_point的定义如下:

//#include<chrono>
template< class Clock, class Duration = typename Clock::duration >
class time_point;

时间精度是duration类,默认的时钟模板参数提供的duration。时钟模板参数除了前面介绍的那三个时钟(这是C++默认提供的)外,还可以是码农自己定义的时钟类。

同前面的时间段类和时钟类一样,时间点类也提供了rep、period、duration、clock这几个成员类型。

前面已经指出,各个时钟能提供的时间精度是implementation-defined的。所以应该如下面代码那样获取时钟的now()函数返回值。

auto tp = std::chrono::system_clock::now();
std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();

下面代码是不正确的,不具有可移植性。例子来自stackoverflow

std::chrono::time_point<std::chrono::system_clock,std::chrono::nanoseconds> time_point;
time_point = std::chrono::system_clock::now();

除了从时钟类的now函数中获取一个time_point,还可以直接构造一个time_point对象。time_point模板类的构造函数有无参的、用duration作为参数这两个构造函数。对此,我感到很奇怪:直接构造出来的time_point有什么含义呢?

time_point有一个time_since_epoch()成员函数,返回从epoch时间到此刻的时间段。epoch时间是时钟的开启时间。对于system_clock和high_resolution_clock来说,epoch是1970-01-01T00:00:00。熟悉C语言time()函数的读者对此应该不陌生。steady_clock时钟则是boot启动时间,所以不应该对steady_clock::now()返回的time_point调用time_since_epoch()。下面是霸王强上弓使用,打印出来的时间是1970年的。

//Author: luotuo44   http://blog.csdn.net/luotuo44
auto tp = std::chrono::steady_clock::now();
std::time_t cur_time = std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch()).count();
std::string str_time = std::ctime(&cur_time);
cout<<str_time<<endl;

如果time_point不是从时钟类的now()返回值得来的,而是直接构造出来的话,那么它调用time_since_epoch()的返回值就等同于构造函数中的duration参数。

运算

一段时间的加减乘除无疑是合理的,当然关系比较也是合理的。C++标准委员会还把取模运算也变成合法的了,不仅仅模一个数值合法,模duration类也是合法的。于是有下面这些运算:

具体的使用例子就不写了,读者可以参考en.cppreference.com

上面的运算都是二元运算,如果参与运算的两个duration具有相同的模板参数,那直接运行即可。如果不同,那么将发生隐式转换:int->double; 低分辨率->高分辨率。比如:

//Author: luotuo44   http://blog.csdn.net/luotuo44
std::chrono::duration<int> two(2);
std::chrono::duration<double, std::ratio<60>> thirty(0.5);

auto ad = two + thirty;
cout<<sizeof(decltype(ad)::rep)<<endl;//8
cout<<ad.count()<<"\t"<<decltype(ad)::period::num<<"/"<<decltype(ad)::period::den<<endl;//32  1/1

如果参与运算的两个duration具有不同的模板参数,那么能不能进行+=和-=这里运算呢?a+=b等同于a=a+b。a+b肯定是可以的,现在问题转换为a=c是否可行?从低精度(包含Rep和Period)到高精度的隐式转换是可行的,反之则是不可以的。因为这将导致截断,丢失部分信息。

//Author: luotuo44   http://blog.csdn.net/luotuo44
std::chrono::minutes a(30);
std::chrono::seconds b = a;//OK
std::chrono::minutes c = b;//compile error

std::chrono::duration<double, std::ratio<60>> d(3);
std::chrono::seconds e = d;//compile error
std::chrono::duration<double> f = d;//OK

如果确实需要进行转换的话,那么需要使用duration_cast进行显式转换。

//Author: luotuo44   http://blog.csdn.net/luotuo44
std::chrono::seconds a(30);
std::chrono::minutes b = std::chrono::duration_cast<std::chrono::minutes>(a);
cout<<b.count()<<endl;//结果是0

//下面是system_clock时钟类to_time_t函数的gcc实现
static std::time_t to_time_t(const time_point& __t) noexcept
{
    return std::time_t(duration_cast<chrono::seconds> (__t.time_since_epoch()).count());
}

time_point类也有一些运算,如下图:

gcc4.9.0实现time_point模板类时,time_point内部有一个duration类型的成员变量。所以对于tp+=d和dp-=d的实现,都是直接对内部的成员变量直接调用+=,-=。所以调用者必须保证精度的正确性,否则编译出错。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-15 20:07:49

C++11时间详解的相关文章

C# winform 类型转换和时间详解

C# winform 类型转换和时间详解 1. // C 货币 2. 2.5.ToString("C"); // ¥2.50 3. // D 10进制数 4. 25.ToString("D5"); // 25000 5. // E 科学型 6. 25000.ToString("E"); // 2.500000E+005 7. // F 固定点 8. 25.ToString("F2"); // 25.00 9. // G 常规

LoadRunner中响应时间与事物时间详解

1. 响应时间 事务是指用户在客户端做一种或多种业务所需要的操作集,通过事务函数可以标记完成该业务所需要的操作内容:另一方面事务可以用来统计用户操作的响应时间,事务响应时间是通过记录用户请求的开始时间和服务器返回内容到客户端时间的差值来计算用户操作响应时间的,如图1所示. 图1  事务响应时间计算方式 这里的响应时间不包含客户端GUI时间(例如浏览器解释页面所消耗的时间). 前面说响应时间是用户请求发出和服务器返回之间的时间差,那么得到这个时间就够了吗? 例如:现在有一场跑步比赛.当比赛完成后,

C#获取当前时间详解

[转]C#获取当前日期时间(转) http://blog.163.com/[email protected]/blog/static/549639712010112921658843/ 我们可以通过使用DataTime这个类来获取当前的时间.通过调用类中的各种方法我们可以获取不同的时间:如:日期(2008-09-04).时间(12:12:12).日期+时间(2008-09-04 12:11:10)等. //获取日期+时间DateTime.Now.ToString();            //

Linux 文件时间详解 ctime mtime atime以及 find 命令

Linux系统文件中三个主要的时间属性: atime(access time)   mtime(modify time)   ctime(change time) 这三个时间很容易混淆,须加以区分 atime(access time):在读取文件或者执行文件时更改,即文件最后一次被读取或执行的时间. mtime(modify time):在写入文件时随文件内容的更改而更改,是指文件内容最后一次被修改的时间. ctime(change time):在写入文件.更改所有者.权限或链接设置时随 Ino

Linux文件时间详解ctime、mtime、atime【转】

本文转载自:http://blog.csdn.net/doiido/article/details/43792561 Linux系统文件有三个主要的时间属性,分别是 ctime(change time), atime(access time), mtime(modify time).这三个时间很容易混淆,准备深入了解linux的童鞋请区分这三者的区别 atime:Access time, 是在读取文件或者执行文件时更改,即文件最后一次被读取的时间.说明: st_atime           T

Linux 文件时间详解 ctime mtime atime

Linux系统文件有三个主要的时间属性,分别是 ctime(change time), atime(access time), mtime(modify time).这三个时间很容易混淆,准备深入了解linux的童鞋请区分这三者的区别 atime:Access time, 是在读取文件或者执行文件时更改,即文件最后一次被读取的时间. 说明: st_atime Time when file data was last accessed. Changed by  the following   fu

redis 下key的过期时间详解 :expire

Redis是一个开源的Key-Value数据缓存,和Memcached类似. Redis多种类型的value,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型). Jedis 是 Redis 官方首选的 Java 客户端开发包. redis通过expire命令来设置key的过期时间. 语法:redis.expire(key, expiration) 1. 在小于2.1.3的redis版本里,只能对key设置一次exp

fping ping 包间隔时间详解

服务器间检查会用到fping的命令,期间遇到了一个问题,需要将ping包间的间隔时间设置为100毫秒,查看fping -h看下,找到了-i和-p两个参数: 看到这两个参数,我当时的表情是这样的: 看不懂,那就测吧: 先来-i: 间隔1s,没有生效.! 再试试-p OK,这个生效了,但-i 和-p的区别是什么?我又尝试了下面的操作: 得出结论: -i 多个目的地址ping包的发送间隔时间 -p 单个IP地址ping包的发送间隔时间 又有一个问题: -i -p是否可以同时使用? 得出结论: 同时使用

提高Cacti数据采集精度 rrdtool保存图的时间详解

默认的Cacti监控图形是以日.周.月.年 4个时间,每个时间都可以任意缩放查看,但是大家肯定也发现了, 默认的情况下,日图是每5分钟频率的平均值,周图是30分钟,月图是2小时,年图是1天:这样的话,如果查看一周前的图就会比当时的实际图相差很大,监控值会低不少,查看一月或一年之前的就会相差更大:这里我们来说说怎么解决此问题,或者说是缩小差值. Cacti主要是通过rrdtool这个工具来绘图的,其实cacti只是个构造比较合理的框架.rrdtool 有一套自己的数据文件供其绘图使用,Cacti主