C++ `std::chrono` 高级用法:时间点、时长与时钟操作

好的,让我们开始一场关于 C++ std::chrono 的深度旅行,目标是让你彻底掌握时间点、时长和时钟的奥秘,并能像时间旅行者一样操控它们。准备好了吗?系好安全带,我们要起飞了!

开场白:时间是个啥?

各位观众,晚上好!今天我们要聊的是一个既熟悉又陌生的东西:时间。说它熟悉,是因为我们每天都在和它打交道;说它陌生,是因为它实在太抽象了。在计算机世界里,时间更是个磨人的小妖精,一不小心就会让你掉进各种陷阱。

C++ 的 std::chrono 库就是你的时间武器库,它提供了一套强大的工具,让你能够精确地测量、表示和操作时间。掌握了它,你就能像一位时间大师一样,掌控你的代码的时序。

第一幕:时间点 (Time Point) – 宇宙中的坐标

想象一下,时间点就像宇宙中的一个坐标,它代表着时间轴上的一个特定位置。std::chrono::time_point 就是 C++ 中表示时间点的工具。

  • time_point 的基本概念

    time_point 依赖于两个要素:

    • 时钟 (Clock): 决定了时间点的起点(epoch)和时间流逝的速度。
    • 时长 (Duration): 从时钟的起点到该时间点所经过的时间。

    简单来说,time_point 就是 "哪个时钟的起点 + 过了多久"。

  • time_point 的声明和初始化

    #include <iostream>
    #include <chrono>
    
    int main() {
        // 使用 system_clock 获取当前时间点
        std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
    
        // 也可以使用 duration 进行初始化
        std::chrono::time_point<std::chrono::system_clock> later = now + std::chrono::seconds(10);
    
        std::cout << "现在的时间点: " << std::chrono::system_clock::to_time_t(now) << std::endl;
        std::cout << "10秒后的时间点: " << std::chrono::system_clock::to_time_t(later) << std::endl;
    
        return 0;
    }

    代码解读:

    • std::chrono::system_clock::now() 获取当前系统时钟的时间点。
    • now + std::chrono::seconds(10) 在当前时间点上加上 10 秒,得到一个新的时间点。
    • std::chrono::system_clock::to_time_t()time_point 转换为 time_t 类型,方便输出(但要注意精度损失)。
  • time_point 的运算

    time_point 支持加减运算,可以加上或减去一个 duration,得到一个新的 time_point。 也可以计算两个 time_point 之间的差值,得到一个 duration

    #include <iostream>
    #include <chrono>
    
    int main() {
        std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
        // 模拟一些耗时操作
        for (int i = 0; i < 1000000; ++i) {
            // do nothing
        }
        std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
    
        // 计算时间差
        std::chrono::duration<double> elapsed_seconds = end - start;
    
        std::cout << "耗时: " << elapsed_seconds.count() << " 秒" << std::endl;
    
        return 0;
    }

    这段代码演示了如何使用 time_point 测量一段代码的执行时间。

第二幕:时长 (Duration) – 时间的长度

时长 (Duration) 表示时间的长度,比如 10 秒、5 分钟、2 小时等等。std::chrono::duration 就是 C++ 中表示时长的工具。

  • duration 的基本概念

    duration 包含两个要素:

    • 数值 (Rep): 表示时长的数值,可以是整数或浮点数。
    • 时间单位 (Period): 表示数值的单位,比如秒、毫秒、纳秒等等。

    duration 的类型定义如下:

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

    Period 是一个 std::ratio 类型,表示时间单位的比例。例如,std::ratio<1, 1000> 表示 1/1000 秒,也就是 1 毫秒。

  • 预定义的 duration 类型

    std::chrono 库预定义了一些常用的 duration 类型,方便使用:

    类型 单位
    std::chrono::nanoseconds 纳秒
    std::chrono::microseconds 微秒
    std::chrono::milliseconds 毫秒
    std::chrono::seconds
    std::chrono::minutes 分钟
    std::chrono::hours 小时
  • duration 的声明和初始化

    #include <iostream>
    #include <chrono>
    
    int main() {
        // 使用预定义的类型
        std::chrono::seconds ten_seconds(10);
        std::chrono::milliseconds fifty_milliseconds(50);
    
        // 使用自定义的类型
        std::chrono::duration<double, std::ratio<60>> one_minute(1); // double 类型的分钟
    
        std::cout << "10 秒: " << ten_seconds.count() << " 秒" << std::endl;
        std::cout << "50 毫秒: " << fifty_milliseconds.count() << " 毫秒" << std::endl;
        std::cout << "1 分钟: " << one_minute.count() << " 分钟" << std::endl;
    
        return 0;
    }

    代码解读:

    • std::chrono::seconds(10) 创建一个表示 10 秒的 duration 对象。
    • std::chrono::duration<double, std::ratio<60>>(1) 创建一个表示 1 分钟的 duration 对象,数值类型为 double
    • duration.count() 返回 duration 的数值。
  • duration 的运算

    duration 支持加减乘除运算,可以进行时长的加减、缩放等操作。

    #include <iostream>
    #include <chrono>
    
    int main() {
        std::chrono::seconds ten_seconds(10);
        std::chrono::milliseconds fifty_milliseconds(50);
    
        // 加法
        std::chrono::duration auto total_time = ten_seconds + fifty_milliseconds;
        std::cout << "总时长: " << total_time.count() << " 秒" << std::endl; //注意精度转换
    
        // 乘法
        std::chrono::duration auto doubled_time = ten_seconds * 2;
        std::cout << "双倍时长: " << doubled_time.count() << " 秒" << std::endl;
    
        // 除法
        std::chrono::duration auto half_time = ten_seconds / 2;
        std::cout << "一半时长: " << half_time.count() << " 秒" << std::endl;
    
        return 0;
    }
  • duration_cast – 时长转换的魔法棒

    duration_cast 可以将一个 duration 转换为另一个 duration 类型。这在处理不同时间单位时非常有用。

    #include <iostream>
    #include <chrono>
    
    int main() {
        std::chrono::seconds ten_seconds(10);
    
        // 转换为毫秒
        std::chrono::milliseconds ten_seconds_in_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(ten_seconds);
        std::cout << "10 秒等于 " << ten_seconds_in_milliseconds.count() << " 毫秒" << std::endl;
    
        // 转换为纳秒
        std::chrono::nanoseconds ten_seconds_in_nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(ten_seconds);
        std::cout << "10 秒等于 " << ten_seconds_in_nanoseconds.count() << " 纳秒" << std::endl;
    
        return 0;
    }

    重要提示: duration_cast 可能会导致精度损失,尤其是在将高精度的时间单位转换为低精度的时间单位时。

第三幕:时钟 (Clock) – 时间的裁判

时钟 (Clock) 决定了时间流逝的速度和时间点的起点 (epoch)。std::chrono 库提供了几种不同的时钟类型,每种时钟都有其特定的用途。

  • clock 的基本概念

    clock 包含以下三个要素:

    • time_point: 表示当前时间的类型。
    • duration: 表示时钟的最小刻度。
    • is_steady: 一个 static const bool 值,表示时钟是否稳定。 稳定时钟意味着它的时间永远不会向后跳跃。
  • 常用的时钟类型

    时钟类型 描述 is_steady
    std::chrono::system_clock 系统时钟,表示当前的系统时间。可能会受到系统时间调整的影响。 可能为 false
    std::chrono::steady_clock 稳定时钟,保证时间永远不会向后跳跃。适合用于测量时间间隔。 true
    std::chrono::high_resolution_clock 提供最高可能精度的时钟。可能是 system_clocksteady_clock 的别名。 取决于实现
  • 选择合适的时钟

    • 如果你需要获取当前的系统时间,可以使用 system_clock
    • 如果你需要测量时间间隔,并且需要保证时间的单调性,可以使用 steady_clock
    • 如果你需要最高可能精度的时钟,可以使用 high_resolution_clock
  • 时钟的使用示例

    #include <iostream>
    #include <chrono>
    
    int main() {
        // 获取 system_clock 的当前时间
        std::chrono::time_point<std::chrono::system_clock> system_now = std::chrono::system_clock::now();
        std::time_t system_time = std::chrono::system_clock::to_time_t(system_now);
        std::cout << "系统时间: " << std::ctime(&system_time);
    
        // 获取 steady_clock 的当前时间
        std::chrono::time_point<std::chrono::steady_clock> steady_start = std::chrono::steady_clock::now();
        // 模拟一些耗时操作
        for (int i = 0; i < 1000000; ++i) {
            // do nothing
        }
        std::chrono::time_point<std::chrono::steady_clock> steady_end = std::chrono::steady_clock::now();
    
        // 计算时间差
        std::chrono::duration<double> elapsed_seconds = steady_end - steady_start;
        std::cout << "耗时: " << elapsed_seconds.count() << " 秒" << std::endl;
    
        return 0;
    }

    代码解读:

    • std::chrono::system_clock::now() 获取 system_clock 的当前时间点。
    • std::chrono::steady_clock::now() 获取 steady_clock 的当前时间点。
    • std::ctime()time_t 类型的时间转换为可读的字符串。

第四幕:时间区 (Time Zone) – 地球上的时间差异

C++20 引入了对时区 (Time Zone) 的支持,这使得处理跨时区的时间变得更加容易。

  • time_zone 的基本概念

    time_zone 表示一个特定的地理区域,该区域使用相同的时区规则。

  • time_zone 的获取

    #include <iostream>
    #include <chrono>
    
    int main() {
        // 获取本地时区
        std::chrono::time_zone const* local_tz = std::chrono::locate_zone("Asia/Shanghai"); // 使用具体时区名称
        if (local_tz) {
            std::cout << "本地时区: " << local_tz->name() << std::endl;
        } else {
            std::cerr << "无法找到本地时区" << std::endl;
        }
    
        return 0;
    }

    注意: 需要包含 <chrono> 头文件,并且编译器需要支持 C++20。 locate_zone函数接受一个时区名称作为参数。 时区名称通常采用 "Area/Location" 的格式,例如 "America/New_York" 或 "Europe/London"。 你可以使用 std::chrono::get_tzdb().list_zones() 获取所有可用的时区名称. 如果给定的时区名称无效,locate_zone 函数将返回 nullptr

  • zoned_time – 带时区的时间

    zoned_timetime_pointtime_zone 关联起来,表示一个带时区的时间。

    #include <iostream>
    #include <chrono>
    
    int main() {
        // 获取当前时间
        std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
    
        // 获取本地时区
        std::chrono::time_zone const* local_tz = std::chrono::locate_zone("Asia/Shanghai");
    
        // 创建 zoned_time 对象
        std::chrono::zoned_time local_time(local_tz, now);
    
        std::cout << "本地时间: " << local_time << std::endl;
    
        return 0;
    }

    这段代码演示了如何创建一个 zoned_time 对象,并输出本地时间。

  • 时区转换

    zoned_time 可以方便地进行时区转换。

    #include <iostream>
    #include <chrono>
    
    int main() {
        // 获取当前时间
        std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
    
        // 获取本地时区
        std::chrono::time_zone const* shanghai_tz = std::chrono::locate_zone("Asia/Shanghai");
    
        // 创建 zoned_time 对象 (上海时间)
        std::chrono::zoned_time shanghai_time(shanghai_tz, now);
    
        // 获取纽约时区
        std::chrono::time_zone const* new_york_tz = std::chrono::locate_zone("America/New_York");
    
        // 转换为纽约时间
        std::chrono::zoned_time new_york_time(new_york_tz, shanghai_time.get_local_time()); //注意 这里需要获取local_time
    
        std::cout << "上海时间: " << shanghai_time << std::endl;
        std::cout << "纽约时间: " << new_york_time << std::endl;
    
        return 0;
    }

    代码解读:

    • shanghai_time.get_local_time() 获取 shanghai_time 的本地时间,然后将其传递给 new_york_time 的构造函数,完成时区转换。

第五幕:时间格式化 (Formatting) – 让时间更易读

std::chrono 结合 std::format (C++20) 提供了强大的时间格式化功能。

  • 格式化字符串

    std::format 使用格式化字符串来控制时间的输出格式。 一些常用的格式化选项如下:

    格式化选项 描述
    %Y 四位数的年份
    %m 月份 (01-12)
    %d 日期 (01-31)
    %H 小时 (00-23)
    %M 分钟 (00-59)
    %S 秒 (00-59)
    %F 毫秒 (千分之一秒)
    %p 上午或下午 (AM/PM)
    %Z 时区名称
    %z UTC 偏移量 (+HHMM 或 -HHMM)
  • 时间格式化示例

    #include <iostream>
    #include <chrono>
    #include <format>
    
    int main() {
        // 获取当前时间
        std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
    
        // 格式化时间
        std::string formatted_time = std::format("{:%Y-%m-%d %H:%M:%S}", now);
        std::cout << "格式化后的时间: " << formatted_time << std::endl;
    
        // 带毫秒的时间
        std::string formatted_time_with_ms = std::format("{:%Y-%m-%d %H:%M:%S.%F}", now);
        std::cout << "带毫秒的格式化时间: " << formatted_time_with_ms << std::endl;
    
        // 带时区的时间
        std::chrono::time_zone const* local_tz = std::chrono::locate_zone("Asia/Shanghai");
        std::chrono::zoned_time local_time(local_tz, now);
        std::string formatted_time_with_tz = std::format("{:%Y-%m-%d %H:%M:%S %Z %z}", local_time);
        std::cout << "带时区的格式化时间: " << formatted_time_with_tz << std::endl;
    
        return 0;
    }

    注意: 要使用 std::format,需要确保你的编译器支持 C++20,并且包含 <format> 头文件。 如果你的编译器不支持 std::format,可以使用 std::put_time 代替,但 std::put_time 的功能相对较弱。

总结:时间大师的炼成

恭喜你!经过这场时间之旅,你已经掌握了 C++ std::chrono 库的核心概念和用法。现在,你可以自信地处理时间点、时长和时钟,并能像一位时间大师一样操控它们。

记住,时间是一种宝贵的资源,合理地利用它可以让你的代码更加高效和可靠。

最后忠告:

  • 务必理解不同时钟的特性,选择合适的时钟类型。
  • 注意 duration_cast 导致的精度损失。
  • 善用 std::format 格式化时间,让时间信息更易读。
  • C++20 引入的时区支持,让跨时区的时间处理变得更加简单。

希望这篇文章能帮助你更好地理解和使用 C++ std::chrono 库。祝你编程愉快,时间掌控自如!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注