01 | 處理日期和時間的 chrono 庫
C++11 中提供了日期和時間相關的庫 chrono,通過 chrono 庫可以很方便地處理日期和時間,為程式的開發提供了便利。chrono 庫主要包含三種類型的類:時間間隔duration、時鐘clocks、時間點time point。
基本常識
1.時間點的來源:鍾(c++的不同類)
2.時間點和時間點之間的運算。比如 時間點-時間點=時間間隔
3.時間點和時間間隔的運算等
4.時間間隔由 週期長短 和 週期次數 決定
時間間隔 duration
duration 的原型如下:
// 定義於標頭檔案 <chrono> template< class Rep, //時鐘週期型別預設為整形——“也就是週期數只能為1,2,3……” class Period = std::ratio<1> //ratio 表示時鐘的週期——"也就是週期長度,事實上是一個分數,單位秒"——而 ratio<1,1000 > 代表的是 1/1000 秒,也就是 1 毫秒 > class duration;
在chrono名稱空間下的 時間間隔
簡單看幾個程式碼
chrono::hours h(1); // 一小時 chrono::milliseconds ms{ 3 }; // 3 毫秒 chrono::duration<int, ratio<1000>> ks(3); // 3000 秒 // chrono::duration<int, ratio<1000>> d3(3.5); // error chrono::duration<double> dd(6.6); // 6.6 秒 // 使用小數表示時鐘週期的次數 chrono::duration<double, std::ratio<1, 30>> hz(3.5);
時間間隔類同樣提供了 運算子過載 ,返回的時間間隔型別有固定的規則。詳見下面參考資料
時間點 time point
類的定義如下
// 定義於標頭檔案 <chrono>
template<
class Clock, //此時間點在此時鐘上計量
class Duration = typename Clock::duration //用於計量從紀元起時間的 std::chrono::duration 型別
> class time_point;
在這個類中除了建構函式還提供了另外一個 time_since_epoch() 函式,用來獲得 1970 年 1 月 1 日到 time_point 物件中記錄的時間經過的時間間隔(duration),函式原型如下:
duration time_since_epoch() const;
此類同樣提供了運算子過載,也可以和時間間隔運算
時鐘 clocks
chrono 庫中提供了獲取當前的系統時間的時鐘類,包含的時鐘一共有三種:
- system_clock:系統的時鐘,系統的時鐘可以修改,甚至可以網路對時,因此使用系統時間計算時間差可能不準。
- steady_clock:是固定的時鐘,相當於秒錶。開始計時後,時間只會增長並且不能修改,適合用於記錄程式耗時
- high_resolution_clock:和時鐘類 steady_clock 是等價的(是它的別名)。
在使用chrono提供的時鐘類的時候,不需要建立類物件,直接呼叫類的靜態方法就可以得到想要的時間了。
system_clock 類
system_clock 類一共提供了三個靜態成員函式
// 返回表示當前時間的時間點。
static std::chrono::time_point<std::chrono::system_clock> now() noexcept;
// 將 time_point 時間點型別轉換為 std::time_t 型別
static std::time_t to_time_t( const time_point& t ) noexcept;
// 將 std::time_t 型別轉換為 time_point 時間點型別
static std::chrono::system_clock::time_point from_time_t( std::time_t t ) noexcept;
比如,我們要獲取當前的系統時間,並且需要將其以能夠識別的方式打印出來,示例程式碼如下:
#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
// 新紀元1970.1.1時間
system_clock::time_point epoch;
duration<int, ratio<60*60*24>> day(1);
// 新紀元1970.1.1時間 + 1天
system_clock::time_point ppt(day);
using dday = duration<int, ratio<60 * 60 * 24>>;
// 新紀元1970.1.1時間 + 10天
time_point<system_clock, dday> t(dday(10));
// 系統當前時間
system_clock::time_point today = system_clock::now();
// 轉換為time_t時間型別
time_t tm = system_clock::to_time_t(today);
cout << "今天的日期是: " << ctime(&tm);
time_t tm1 = system_clock::to_time_t(today+day);
cout << "明天的日期是: " << ctime(&tm1);
time_t tm2 = system_clock::to_time_t(epoch);
cout << "新紀元時間: " << ctime(&tm2);
time_t tm3 = system_clock::to_time_t(ppt);
cout << "新紀元時間+1天: " << ctime(&tm3);
time_t tm4 = system_clock::to_time_t(t);
cout << "新紀元時間+10天: " << ctime(&tm4);
}
steady_clock
如果我們通過時鐘不是為了獲取當前的系統時間,而是進行程式耗時的時長,此時使用 syetem_clock 就不合適了,因為這個時間可以跟隨系統的設定發生變化。在 C++11 中提供的時鐘類 steady_clock 相當於秒錶,只要啟動就會進行時間的累加,並且不能被修改,非常適合於進行耗時的統計
在這個類中也提供了一個靜態的 now () 方法,用於得到當前的時間點,函式原型如下:
static std::chrono::time_point<std::chrono::steady_clock> now() noexcept;
假設要測試某一段程式的執行效率,可以計算它執行期間消耗的總時長,示例程式碼如下:
#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;
int main()
{
// 獲取開始時間點
steady_clock::time_point start = steady_clock::now();
// 執行業務流程
cout << "print 1000 stars ...." << endl;
for (int i = 0; i < 1000; ++i)
{
cout << "*";
}
cout << endl;
// 獲取結束時間點
steady_clock::time_point last = steady_clock::now();
// 計算差值
auto dt = last - start;
cout << "總共耗時: " << dt.count() << "納秒" << endl;
}
high_resolution_clock
high_resolution_clock 的使用方式和 steady_clock 是一樣的
轉換函式
duration_cast
duration_cast 是 chrono 庫提供的一個模板函式,這個函式不屬於 duration 類。通過這個函式可以對 duration 類物件內部的時鐘週期 Period,和週期次數的型別 Rep 進行修改,該函式原型如下:
template <class ToDuration, class Rep, class Period>
constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);
我們可以修改一下上面測試程式執行時間的程式碼,在程式碼中修改 duration 物件的屬性:
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
void f()
{
cout << "print 1000 stars ...." << endl;
for (int i = 0; i < 1000; ++i)
{
cout << "*";
}
cout << endl;
}
int main()
{
auto t1 = steady_clock::now();
f();
auto t2 = steady_clock::now();
// 整數時長:時鐘週期納秒轉毫秒,精度降低,要求 duration_cast
auto int_ms = duration_cast<chrono::milliseconds>(t2 - t1);
// 小數時長:不要求 duration_cast
duration<double, ratio<1, 1000>> fp_ms = t2 - t1;
cout << "f() took " << fp_ms.count() << " ms, "
<< "or " << int_ms.count() << " whole milliseconds\n";
}
time_point_cast
time_point_cast 也是 chrono 庫提供的一個模板函式,這個函式不屬於 time_point 類。函式的作用是對時間點進行轉換,因為不同的時間點物件內部的時鐘週期 Period,和週期次數的型別 Rep 可能也是不同的,一般情況下它們之間可以進行隱式型別轉換,也可以通過該函式顯示的進行轉換,函式原型如下:
template <class ToDuration, class Clock, class Duration>
time_point<Clock, ToDuration> time_point_cast(const time_point<Clock, Duration> &t);
關於函式的使用,示例程式碼如下:
#include <chrono>
#include <iostream>
using namespace std;
using Clock = chrono::high_resolution_clock;
using Ms = chrono::milliseconds;
using Sec = chrono::seconds;
template<class Duration>
using TimePoint = chrono::time_point<Clock, Duration>;
void print_ms(const TimePoint<Ms>& time_point)
{
std::cout << time_point.time_since_epoch().count() << " ms\n";
}
int main()
{
TimePoint<Sec> time_point_sec(Sec(6));
// 無精度損失, 可以進行隱式型別轉換
TimePoint<Ms> time_point_ms(time_point_sec);
print_ms(time_point_ms); // 6000 ms
time_point_ms = TimePoint<Ms>(Ms(6789));
// error,會損失精度,不允許進行隱式的型別轉換
TimePoint<Sec> sec(time_point_ms);
// 顯示型別轉換,會損失精度。6789 truncated to 6000
time_point_sec = std::chrono::time_point_cast<Sec>(time_point_ms);
print_ms(time_point_sec); // 6000 ms
}
注意事項:關於時間點的轉換如果沒有沒有精度的損失可以直接進行隱式型別轉換,如果會損失精度只能通過顯示型別轉換,也就是呼叫 time_point_cast 函式來完成該操作。
參考
愛程式設計的大丙:https://subingwen.cn/cpp/chrono/#4-2-time-point-cast