C++11—this_thread

this_thread

1. 概述

std::this_thread 是 C++11 标准库中一个特殊的命名空间,它提供了一系列用于操作当前线程 的函数。与 std::thread 类不同,std::this_thread 中的函数都是命名空间函数(自由函数),不需要通过线程对象来调用,而是直接作用于调用它的线程本身。

这个命名空间的设计理念非常直观:当你需要让当前线程执行某些操作(比如休眠、让出 CPU、获取自己的 ID)时,直接调用 std::this_thread 中的函数即可,无需持有任何线程对象。

2. 头文件

使用 std::this_thread 需要包含头文件:

cpp 复制代码
#include <thread>

注意:std::this_threadstd::thread 都在同一个头文件 <thread> 中。

3. 核心函数

std::this_thread 命名空间提供了四个核心函数,让我们逐一深入了解。

3.1 get_id() - 获取当前线程 ID

函数签名
cpp 复制代码
std::thread::id get_id() noexcept;
功能说明

get_id() 返回当前线程的唯一标识符,类型为 std::thread::id。在同一时刻,每个线程都有唯一的 ID,可以用来区分不同的线程。注意:线程结束后,其 ID 可能会被系统重用。

使用场景
  • 线程识别:在多线程程序中,通过 ID 来识别和区分不同的线程
  • 调试:在日志或调试信息中输出线程 ID,帮助追踪问题
  • 线程映射:将线程 ID 作为键,存储线程相关的数据
代码示例
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

std::mutex coutMutex;

void PrintThreadInfo()
{
    std::thread::id threadId = std::this_thread::get_id();
    
    // 使用互斥锁保护输出,避免多线程输出混乱
    std::lock_guard<std::mutex> lock(coutMutex);
    std::cout << "当前线程 ID: " << threadId << std::endl;
}

int main()
{
    // 主线程的 ID
    std::cout << "主线程 ID: " << std::this_thread::get_id() << std::endl;
    
    // 创建多个线程,每个线程打印自己的 ID
    std::thread t1(PrintThreadInfo);
    std::thread t2(PrintThreadInfo);
    std::thread t3(PrintThreadInfo);
    
    t1.join();
    t2.join();
    t3.join();
    
    return 0;
}
注意事项
  1. 默认构造的 thread::id :默认构造的 std::thread::id 表示"非线程"(not a thread),它不等于任何有效的线程 ID
  2. 可比较性std::thread::id 支持 ==!=< 等比较操作,可以用于排序和查找
  3. 可输出性std::thread::id 可以输出到流,但输出格式是实现定义的(通常是数字或十六进制)

3.2 yield() - 让出 CPU 时间片

函数签名
cpp 复制代码
void yield() noexcept;
功能说明

yield() 是一个提示性 函数,它建议当前线程让出 CPU 时间片,允许其他线程运行。这是一个非阻塞操作,调用后立即返回,不会导致线程休眠。

使用场景
  • 忙等待优化 :在自旋锁或忙等待循环中,使用 yield() 可以降低 CPU 占用率
  • 协作式多任务:在协作式多任务系统中,主动让出 CPU 给其他线程
  • 性能优化 :当线程在等待某个条件时,使用 yield() 而不是忙等待,可以减少 CPU 资源浪费
代码示例
cpp 复制代码
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
#include <mutex>

std::atomic<bool> ready(false);
std::mutex coutMutex;

void WorkerThread()
{
    // 忙等待,但使用 yield() 让出 CPU
    while (!ready.load())
    {
        std::this_thread::yield();  // 让出 CPU,降低 CPU 占用
    }
    
    // 使用互斥锁保护输出
    std::lock_guard<std::mutex> lock(coutMutex);
    std::cout << "工作线程 " << std::this_thread::get_id() 
              << " 开始工作" << std::endl;
}

int main()
{
    std::thread worker(WorkerThread);
    
    // 模拟一些准备工作
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // 设置就绪标志
    ready.store(true);
    
    worker.join();
    return 0;
}
注意事项
  1. 非强制性yield() 只是一个提示,操作系统可能忽略这个提示,特别是在单核系统上
  2. 不保证休眠 :调用 yield() 后,线程可能立即继续执行,也可能被调度器暂停
  3. 性能影响 :在忙等待循环中使用 yield() 可以显著降低 CPU 占用率,但可能增加延迟
  4. 替代方案 :对于需要等待的场景,通常使用 sleep_for() 或条件变量更合适

3.3 sleep_for() - 休眠指定时长

函数签名
cpp 复制代码
template<class Rep, class Period>
void sleep_for(const std::chrono::duration<Rep, Period>& sleepDuration);
功能说明

sleep_for() 让当前线程休眠指定的时间长度。这是一个阻塞操作,线程在休眠期间不会执行任何代码。

参数说明
  • sleepDuration:休眠的时长,类型为 std::chrono::duration,可以使用 std::chrono::millisecondsstd::chrono::seconds
使用场景
  • 定时任务:实现定时执行某些操作
  • 延迟执行:在操作之间添加延迟
  • 节流控制:限制某些操作的执行频率
  • 测试和演示:在示例代码中模拟耗时操作
代码示例
cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

int main()
{
    std::cout << "开始执行..." << std::endl;
    
    // 休眠 1 秒
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "1 秒后" << std::endl;
    
    // 休眠 500 毫秒
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    std::cout << "再 500 毫秒后" << std::endl;
    
    // 使用不同的时间单位
    std::this_thread::sleep_for(std::chrono::microseconds(100000));  // 0.1 秒
    std::cout << "再 0.1 秒后" << std::endl;
    
    return 0;
}
注意事项
  1. 实际休眠时间 :实际休眠时间可能略长于指定的时间,因为线程调度、系统负载等因素
  2. 最小精度:休眠的最小精度取决于操作系统的时钟精度,通常为毫秒级
  3. 可中断性:在某些系统上,休眠可能被信号中断
  4. 异常安全 :在 C++11 中,sleep_for() 可能抛出异常(例如时钟操作失败时);在 C++14 及以后版本中,sleep_for() 被标记为 noexcept,不会抛出异常

3.4 sleep_until() - 休眠到指定时间点

函数签名
cpp 复制代码
template<class Clock, class Duration>
void sleep_until(const std::chrono::time_point<Clock, Duration>& sleepTime);
功能说明

sleep_until() 让当前线程休眠到指定的时间点。与 sleep_for() 不同,sleep_until() 是基于绝对时间 的,而 sleep_for() 是基于相对时间的。

参数说明
  • sleepTime:目标时间点,类型为 std::chrono::time_point
使用场景
  • 定时执行:在指定的时间点执行某个操作
  • 周期性任务:实现精确的周期性执行(比如每 1 秒执行一次)
  • 截止时间控制:在某个截止时间之前执行操作
代码示例
cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

void PeriodicTask()
{
    // 获取当前时间
    auto startTime = std::chrono::steady_clock::now();
    
    for (int i = 0; i < 5; ++i)
    {
        // 计算下一次执行的时间点(每秒执行一次)
        auto nextTime = startTime + std::chrono::seconds(i + 1);
        
        // 休眠到指定时间点
        std::this_thread::sleep_until(nextTime);
        
        std::cout << "第 " << (i + 1) << " 次执行,时间: " 
                  << std::chrono::duration_cast<std::chrono::milliseconds>(
                      std::chrono::steady_clock::now() - startTime).count()
                  << " 毫秒" << std::endl;
    }
}

int main()
{
    PeriodicTask();
    return 0;
}
sleep_for() vs sleep_until()
特性 sleep_for() sleep_until()
时间类型 相对时间(时长) 绝对时间(时间点)
参数类型 std::chrono::duration std::chrono::time_point
适用场景 简单的延迟 精确的定时执行
周期性任务 可能累积误差 更精确,不累积误差
代码示例:对比两种方式
cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

// 方式1:使用 sleep_for() - 可能累积误差
void PeriodicTaskWithSleepFor()
{
    for (int i = 0; i < 5; ++i)
    {
        std::cout << "sleep_for: 第 " << (i + 1) << " 次执行" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        // 注意:如果循环体执行需要时间,会导致实际间隔 > 1 秒
    }
}

// 方式2:使用 sleep_until() - 更精确
void PeriodicTaskWithSleepUntil()
{
    auto startTime = std::chrono::steady_clock::now();
    
    for (int i = 0; i < 5; ++i)
    {
        auto nextTime = startTime + std::chrono::seconds(i + 1);
        std::this_thread::sleep_until(nextTime);
        std::cout << "sleep_until: 第 " << (i + 1) << " 次执行" << std::endl;
        // 即使循环体执行需要时间,下一次执行也会在正确的时间点
    }
}

int main()
{
    std::cout << "=== 使用 sleep_for() ===" << std::endl;
    PeriodicTaskWithSleepFor();
    
    std::cout << "\n=== 使用 sleep_until() ===" << std::endl;
    PeriodicTaskWithSleepUntil();
    
    return 0;
}
注意事项
  1. 时钟类型sleep_until() 使用的时钟类型会影响行为
    • std::chrono::system_clock:系统时钟,可能被调整(比如 NTP 同步)
    • std::chrono::steady_clock:单调时钟,不会倒退,适合测量时间间隔
  2. 时间调整 :如果使用 system_clock,系统时间调整可能导致意外行为
  3. 已过期时间:如果指定的时间点已经过去,函数会立即返回
  4. 异常安全 :在 C++11 中,sleep_until() 可能抛出异常(例如时钟操作失败时);在 C++14 及以后版本中,sleep_until() 被标记为 noexcept,不会抛出异常

4. 实际应用示例

4.1 线程池中的线程管理

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>
#include <mutex>

class SimpleWorkerPool
{
public:
    SimpleWorkerPool(size_t workerCount)
    {
        for (size_t i = 0; i < workerCount; ++i)
        {
            workers.emplace_back([this, i]()
            {
                // 每个工作线程的 ID
                std::thread::id workerId = std::this_thread::get_id();
                
                while (!stopFlag.load())
                {
                    // 模拟工作
                    {
                        std::lock_guard<std::mutex> lock(mtx);
                        std::cout << "工作线程 " << workerId 
                                  << " 正在工作..." << std::endl;
                    }
                    
                    // 工作完成后,休眠一段时间
                    std::this_thread::sleep_for(std::chrono::milliseconds(100));
                }
                
                std::lock_guard<std::mutex> lock(mtx);
                std::cout << "工作线程 " << workerId << " 退出" << std::endl;
            });
        }
    }

    ~SimpleWorkerPool()
    {
        stopFlag.store(true);
        for (auto& worker : workers)
        {
            if (worker.joinable())
            {
                worker.join();
            }
        }
    }
private:
    std::vector<std::thread> workers;
    std::atomic<bool> stopFlag{false};
    std::mutex mtx;
};

int main()
{
    SimpleWorkerPool pool(3);
    
    // 主线程休眠 1 秒,让工作线程运行
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    return 0;
}

4.2 精确的定时任务

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>
#include <functional>

class Timer
{
public:
    // 在指定时间点执行任务
    static void ExecuteAt(const std::chrono::steady_clock::time_point& timePoint,
                          std::function<void()> task)
    {
        std::this_thread::sleep_until(timePoint);
        task();
    }
    
    // 延迟指定时间后执行任务
    static void ExecuteAfter(const std::chrono::milliseconds& delay,
                            std::function<void()> task)
    {
        std::this_thread::sleep_for(delay);
        task();
    }
};

int main()
{
    auto startTime = std::chrono::steady_clock::now();
    
    // 1 秒后执行
    Timer::ExecuteAfter(std::chrono::seconds(1), []()
    {
        std::cout << "1 秒后执行的任务" << std::endl;
    });
    
    // 在指定时间点执行
    auto targetTime = startTime + std::chrono::seconds(2);
    Timer::ExecuteAt(targetTime, []()
    {
        std::cout << "在指定时间点执行的任务" << std::endl;
    });
    
    return 0;
}

4.3 节流控制(Rate Limiting)

什么是节流控制?

节流控制(Rate Limiting)是一种限制操作执行频率的机制。它确保在给定的时间窗口内,操作执行的次数不超过指定的限制。这在很多场景中都非常有用,比如:

  • API 调用限制:防止对服务器发送过多请求,避免触发限流或导致服务器过载
  • 资源保护:控制对共享资源的访问频率,避免资源耗尽
  • 网络请求:限制网络请求的频率,避免网络拥塞
  • 日志记录:控制日志输出的频率,避免日志文件过大
工作原理

节流控制的核心思想是:记录上次操作的时间,如果距离上次操作的时间间隔小于最小间隔,则休眠直到达到最小间隔

具体实现步骤:

  1. 初始化:设置允许的请求频率(如每秒 10 次),计算最小时间间隔(1000 毫秒 / 10 = 100 毫秒)
  2. 记录时间:保存上次操作的时间点
  3. 检查间隔:每次操作前,计算距离上次操作的时间间隔
  4. 等待调整 :如果间隔小于最小间隔,使用 sleep_for() 休眠剩余时间
  5. 更新时间:操作完成后,更新上次操作的时间点
代码示例
cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>

class RateLimiter
{
public:
    explicit RateLimiter(int requestsPerSecond)
        : interval(1000 / requestsPerSecond)  // 注意:实际应用中需要检查 requestsPerSecond > 0
        , lastTime(std::chrono::steady_clock::now())
    {
    }

    void WaitIfNeeded()
    {
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
            now - lastTime);
        
        if (elapsed < interval)
        {
            std::this_thread::sleep_for(interval - elapsed);
        }
        
        lastTime = std::chrono::steady_clock::now();
    }
private:
    std::chrono::milliseconds interval;
    std::chrono::steady_clock::time_point lastTime;
};

int main()
{
    RateLimiter limiter(10);  // 限制为每秒 10 次请求
    
    for (int i = 0; i < 20; ++i)
    {
        limiter.WaitIfNeeded();
        std::cout << "请求 " << (i + 1) << std::endl;
    }
    
    return 0;
}
代码详解

让我们逐步分析这个 RateLimiter 类的实现:

1. 构造函数

cpp 复制代码
explicit RateLimiter(int requestsPerSecond)
    : interval(1000 / requestsPerSecond)  // 计算最小时间间隔(毫秒)
    , lastTime(std::chrono::steady_clock::now())  // 记录当前时间作为起始时间
{
}
  • interval:计算最小时间间隔。例如,如果 requestsPerSecond = 10,则 interval = 100 毫秒,意味着每两次请求之间至少间隔 100 毫秒
  • lastTime:记录上次操作的时间点,初始化为当前时间

2. WaitIfNeeded() 方法

cpp 复制代码
void WaitIfNeeded()
{
    // 1. 获取当前时间
    auto now = std::chrono::steady_clock::now();
    
    // 2. 计算距离上次操作的时间间隔
    auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
        now - lastTime);
    
    // 3. 如果时间间隔小于最小间隔,则休眠剩余时间
    if (elapsed < interval)
    {
        std::this_thread::sleep_for(interval - elapsed);
    }
    
    // 4. 更新上次操作的时间点
    lastTime = std::chrono::steady_clock::now();
}

工作流程示例

假设 RateLimiter(10)(每秒 10 次,即每 100 毫秒一次):

  • 第 1 次调用elapsed = 0(刚初始化),0 < 100,休眠 100 毫秒
  • 第 2 次调用 :如果距离第 1 次已经过了 50 毫秒,50 < 100,休眠 50 毫秒
  • 第 3 次调用 :如果距离第 2 次已经过了 120 毫秒,120 >= 100,不休眠,直接执行
使用场景示例

场景 1:API 请求限制

cpp 复制代码
RateLimiter apiLimiter(5);  // 限制为每秒 5 次 API 调用

for (int i = 0; i < 100; ++i)
{
    apiLimiter.WaitIfNeeded();  // 确保不超过频率限制
    // 执行 API 调用(示例代码,实际使用时替换为真实的 API 调用函数)
    // MakeApiCall();
}

场景 2:日志输出控制

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>
#include <chrono>

RateLimiter logLimiter(1);  // 限制为每秒 1 条日志

void LogMessage(const std::string& msg)
{
    logLimiter.WaitIfNeeded();  // 控制日志输出频率
    std::cout << msg << std::endl;
}
注意事项
  1. 线程安全 :当前的实现不是线程安全的 。如果多个线程同时调用 WaitIfNeeded(),需要使用互斥锁保护
  2. 精度问题:使用毫秒级精度,对于需要更高精度的场景,可以使用微秒或纳秒
  3. 边界检查 :构造函数中需要检查 requestsPerSecond > 0,避免除零错误
  4. 时钟选择 :使用 steady_clock 而不是 system_clock,因为 steady_clock 是单调的,不会因为系统时间调整而受影响
线程安全版本(可选)

如果需要多线程环境下的节流控制,可以这样改进:

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>

class ThreadSafeRateLimiter
{
public:
    explicit ThreadSafeRateLimiter(int requestsPerSecond)
        : interval(1000 / requestsPerSecond)
        , lastTime(std::chrono::steady_clock::now())
    {
    }

    void WaitIfNeeded()
    {
        std::lock_guard<std::mutex> lock(mtx);  // 加锁保护
        
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
            now - lastTime);
        
        if (elapsed < interval)
        {
            std::this_thread::sleep_for(interval - elapsed);
        }
        
        lastTime = std::chrono::steady_clock::now();
    }

private:
    std::chrono::milliseconds interval;
    std::chrono::steady_clock::time_point lastTime;
    std::mutex mtx;  // 互斥锁保护共享数据
};

5. 常见错误和注意事项

5.1 不要在主线程中过度使用 sleep

cpp 复制代码
#include <thread>
#include <chrono>

// ❌ 错误示例:在主线程中长时间休眠
int main()
{
    std::thread worker([]()
    {
        // 工作线程执行任务
    });
    
    std::this_thread::sleep_for(std::chrono::seconds(10));  // 主线程休眠
    // 问题:如果工作线程提前完成,主线程仍然会等待 10 秒
    
    worker.join();
    return 0;
}

// ✅ 正确示例:使用 join() 等待线程完成
int main()
{
    std::thread worker([]()
    {
        // 工作线程执行任务
    });
    
    worker.join();  // 等待工作线程完成,无论需要多长时间
    
    return 0;
}

5.2 sleep_for() 的实际休眠时间可能不精确

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

void TestSleepPrecision()
{
    auto start = std::chrono::steady_clock::now();
    
    // 请求休眠 10 毫秒
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    
    auto end = std::chrono::steady_clock::now();
    auto actual = std::chrono::duration_cast<std::chrono::microseconds>(
        end - start).count();
    
    std::cout << "请求休眠: 10 毫秒" << std::endl;
    std::cout << "实际休眠: " << actual << " 微秒" << std::endl;
    // 实际休眠时间可能略大于 10 毫秒
}

5.3 使用正确的时钟类型

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

void DemonstrateClockTypes()
{
    // ❌ 错误:使用 system_clock 进行时间间隔测量
    // system_clock 可能被调整(比如 NTP 同步),不适合测量时间间隔
    auto systemStart = std::chrono::system_clock::now();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    auto systemEnd = std::chrono::system_clock::now();
    
    // ✅ 正确:使用 steady_clock 进行时间间隔测量
    // steady_clock 是单调的,不会倒退,适合测量时间间隔
    auto steadyStart = std::chrono::steady_clock::now();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    auto steadyEnd = std::chrono::steady_clock::now();
    
    auto systemDuration = std::chrono::duration_cast<std::chrono::milliseconds>(
        systemEnd - systemStart).count();
    auto steadyDuration = std::chrono::duration_cast<std::chrono::milliseconds>(
        steadyEnd - steadyStart).count();
    
    std::cout << "system_clock 测量: " << systemDuration << " 毫秒" << std::endl;
    std::cout << "steady_clock 测量: " << steadyDuration << " 毫秒" << std::endl;
}

6. 与 std::thread 的配合使用

std::this_threadstd::thread 是互补的关系:

  • std::thread:用于创建和管理其他线程
  • std::this_thread:用于操作当前线程本身
cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>

std::mutex coutMutex;

void WorkerFunction(int workerId)
{
    // 使用 this_thread 获取当前线程 ID
    std::thread::id currentId = std::this_thread::get_id();
    
    {
        std::lock_guard<std::mutex> lock(coutMutex);
        std::cout << "工作线程 " << workerId 
                  << " (ID: " << currentId << ") 开始工作" << std::endl;
    }
    
    // 模拟工作
    for (int i = 0; i < 3; ++i)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        {
            std::lock_guard<std::mutex> lock(coutMutex);
            std::cout << "工作线程 " << workerId << " 完成步骤 " << (i + 1) << std::endl;
        }
    }
    
    {
        std::lock_guard<std::mutex> lock(coutMutex);
        std::cout << "工作线程 " << workerId << " 完成" << std::endl;
    }
}

int main()
{
    std::cout << "主线程 ID: " << std::this_thread::get_id() << std::endl;
    
    // 使用 std::thread 创建多个工作线程
    std::vector<std::thread> workers;
    for (int i = 0; i < 3; ++i)
    {
        workers.emplace_back(WorkerFunction, i + 1);
    }
    
    // 主线程等待所有工作线程完成
    for (auto& worker : workers)
    {
        worker.join();
    }
    
    std::cout << "所有工作线程完成" << std::endl;
    return 0;
}

7. 总结

std::this_thread 命名空间提供了四个核心函数,用于操作当前线程:

  1. get_id():获取当前线程的唯一标识符
  2. yield():让出 CPU 时间片(提示性操作)
  3. sleep_for():休眠指定的时间长度(相对时间)
  4. sleep_until():休眠到指定的时间点(绝对时间)

这些函数在多线程编程中非常有用,可以帮助我们:

  • 识别和区分不同的线程
  • 控制线程的执行时机
  • 实现定时任务和周期性操作
  • 优化 CPU 资源使用

记住:std::this_thread 是操作当前线程 的工具,而 std::thread 是管理其他线程的工具,两者配合使用可以构建强大的多线程应用。

相关推荐
mjhcsp2 小时前
C++ Manacher 算法:原理、实现与应用全解析
java·c++·算法·manacher 算法
Z1Jxxx2 小时前
0和1的个数
数据结构·c++·算法
朔北之忘 Clancy2 小时前
2020 年 6 月青少年软编等考 C 语言二级真题解析
c语言·开发语言·c++·学习·青少年编程·题解·尺取法
消失的旧时光-19432 小时前
C++ 中的 auto 与 nullptr:不是语法糖,而是类型系统升级
开发语言·c++
fpcc3 小时前
跟我学C++中级篇—C++17中的元编程逻辑操作
c++·模板编程
HABuo3 小时前
【Linux进程(五)】进程地址空间深入剖析-->虚拟地址、物理地址、逻辑地址的区分
linux·运维·服务器·c语言·c++·后端·centos
AuroraWanderll3 小时前
类和对象(六)--友元、内部类与再次理解类和对象
c语言·数据结构·c++·算法·stl
Tim_103 小时前
【C++入门】05、复合类型-数组
开发语言·c++·算法
jikiecui3 小时前
信奥崔老师:三目运算 (Ternary Operator)
数据结构·c++·算法