Qt工作总结07 <qBound和std::clamp>

一、qBound简介

1. 定义

  • 是 Qt 框架中一个非常实用的边界限制函数(也称为 "clamp" 函数),用于将一个值限制在指定的最小值和最大值之间。
  • 头文件:#include <QtGlobal>

2. 函数原型

template <typename T>
const T &qBound(const T &min, const T &value, const T &max);

3. 作用

  • 确保返回值始终满足:min ≤ result ≤ max

4. 行为准则

  • 如果value < min,返回 min;
  • ​​​​​如果value > max,返回 max;
  • 如果 min < value < max,返回 value;

5. 等效代码

template <typename T>
const T &qBound(const T &min, const T &val, const T &max) {
    if (val < min)
        return min;
    if (val > max)
        return max;
    return val;
}

6. 使用示例

#include <QtGlobal>
#include <QDebug>

int main() {
    // 整数边界限制
    int val1 = qBound(0, -5, 100);   // 返回 0 (小于最小值)
    int val2 = qBound(0, 50, 100);   // 返回 50 (在范围内)
    int val3 = qBound(0, 150, 100);  // 返回 100 (大于最大值)

    // 浮点数边界限制
    double dval = qBound(0.0, -1.5, 1.0);  // 返回 0.0

    // 颜色分量限制 (0-255)
    int red = qBound(0, 300, 255);  // 返回 255

    qDebug() << val1 << val2 << val3;  // 输出: 0 50 100
    qDebug() << dval;                 // 输出: 0
    qDebug() << red;                  // 输出: 255
    
    return 0;
}

7. 注意事项

  • 参数顺序:

        必须确保 min <= max,如果min > max,结果未定义(可能返回错值);

  • 安全用法:
qBound(std::min(a,b),value,std::max(a,b));
  • 返回值类型: 

        返回的是 const T& 引用:

        避免返回临时对象的引用,当参数是临时对象时可能有问题,如下示例;

int bad = qBound(0, 50, 100) + 10; 

二、std::clamp(C++17)

1. 定义

  • C++17标准库中引入的函数模板,用于将值限制在指定范围内。

2. 函数原型

template <typename T>
const T& clamp(const T& value, const T& low, const T& high);

3. 用法

#include <algorithm>
int val = std::clamp(rawValue, minValue, maxValue);

4. 行为准则

  • 如果 value < low,返回low;
  • 如果value > high,返回high;
  • 否则返回 value;

5. 前提条件

  • 必须满足 low <= high,否则行为未定义;

6. 使用示例

#include <algorithm>
#include <iostream>

int main() {
    int val = 150;
    
    // 将值限制在 0-100 范围内
    int clamped = std::clamp(val, 0, 100);
    
    std::cout << "Original: " << val      // 输出 150
              << "\nClamped: " << clamped // 输出 100
              << std::endl;
}

三、区别对比

1. 边界处理

  • qBound()  :边界可能无效
  • std::clamp :已知边界有效

2. 参数顺序

// 助记:std::clamp 是 "值-低-高",qBound 是 "低-值-高"
auto cpp = std::clamp(VALUE, LOW, HIGH);
auto qt = qBound(LOW, VALUE, HIGH);
<think>我们有一个使用std::ostringstream格式化输出操作符(<<)以及一些I/O操纵器(如std::setfillstd::setw)的代码片段。这段代码的目的是将小时、分钟、秒毫秒格式化为一个字符串,确保每个部分都有固定的宽度,并且用冒号分隔,同时不足位时用0填充。 代码解释: std::ostringstream oss; // 创建一个输出字符串流 oss << std::setfill('0') // 设置填充字符为'0',这样当数字位数不足时,会用0填充 << std::setw(2) << hours << ":" // 设置字段宽度为2,然后输出小时,后面跟冒号 << std::setw(2) << minutes << ":" // 同样,分钟占2位,后面跟冒号 << std::setw(2) << seconds << ":" // 秒占2位,后面跟冒号 << std::setw(3) << milliseconds; // 毫秒占3位 注意:这里毫秒部分用了3位,并且前面也会用0填充,例如5毫秒会输出为"005"。 但是,这里有一个问题:在毫秒前面也加了一个冒号,而通常毫秒前面是用点号(.)或者逗号(,)分隔的,或者有时毫秒部分直接跟在秒后面没有分隔符(但这里用冒号分隔秒毫秒不太常见)。不过,根据代码,它确实是在秒毫秒之间也使用了冒号。 如果我们希望输出的格式是:HH:MM:SS:mmm,例如12:05:30:123,那么这段代码是符合的。 但是,如果我们希望毫秒部分用点号分隔(如HH:MM:SS.mmm),那么应该将最后一个冒号改为点号。 另外,注意std::setw的作用是一次性的,也就是说,它只对紧接着的下一个输出项有效。因此,我们需要在每个变量输出前都设置一次std::setw。 下面我们详细说明一下各个I/O操纵器: 1. std::setfill(char c):设置填充字符,这个设置会一直有效,直到下一次设置填充字符。 2. std::setw(int n):设置下一个输出项的字段宽度。注意,它只对下一个输出项有效。 因此,代码中在输出hours之前设置setw(2),那么hours将会以宽度2输出(不足两位前面补0,超过两位则按实际输出)。然后输出一个冒号,这个冒号不受setw(2)影响,因为它只对下一个输出项(即hours)有效。然后,在输出minutes之前,我们又设置setw(2),这样minutes也会以宽度2输出,依此类推。 示例: 假设 hours=9, minutes=5, seconds=30, milliseconds=1 则输出:09:05:30:001 但是,这里在秒毫秒之间用了冒号,通常我们可能希望是点号,所以我们可以修改为: oss << std::setfill('0') << std::setw(2) << hours << ":" << std::setw(2) << minutes << ":" << std::setw(2) << seconds << "." // 这里改为点号 << std::setw(3) << milliseconds; 这样输出:09:05:30.001 另外,如果毫秒部分我们希望是三位,但有时毫秒可能超过999,那么这里就会输出4位,因为setw(3)只是设置最小宽度,如果实际数字超过999,那么会按实际位数输出。因此,我们需要确保milliseconds的值在0到999之间。 如果milliseconds可能超过999,那么我们需要先进行取模运算,例如: milliseconds = milliseconds % 1000; // 确保不超过999 或者,如果milliseconds是总毫秒数,我们可能需要将其拆分为秒毫秒,但在这个代码片段中,我们假设milliseconds已经是毫秒部分(0-999)。 最后,我们通常需要从oss中获取字符串,使用oss.str()。 完整示例代码: #include <iostream> #include <iomanip> #include <sstream> int main() { int hours = 9; int minutes = 5; int seconds = 30; int milliseconds = 1; std::ostringstream oss; oss << std::setfill('0') << std::setw(2) << hours << ":" << std::setw(2) << minutes << ":" << std::setw(2) << seconds << "." << std::setw(3) << milliseconds; std::string formatted_time = oss.str(); std::cout << formatted_time << std::endl; // 输出: 09:05:30.001 return 0; } 注意:如果小时、分钟、秒或毫秒是负数,那么输出会包含负号,并且填充在负号之后(例如-1会变成-01)。因此,我们需要确保这些值是非负的。 如果这些值可能为负,我们需要进行适当的处理(比如取绝对值,然后调整上一个字段等),但通常时间分量不会为负。 所以,总结一下,这段代码是用于将时间格式化为固定宽度并用0填充的字符串,但需要注意分隔符的选择数值范围的控制。 现在,根据要求,我们最后提出四个相关问题。</think>### `std::ostringstream` 格式化输出详解 您展示的代码使用 `std::ostringstream` I/O 操纵器进行时间格式化,这是 C++ 中字符串格式化的标准方法。我将详细解析其工作原理并提供优化建议。 #### 代码解析与工作原理 ```cpp std::ostringstream oss; oss << std::setfill('0') // 设置填充字符为'0' << std::setw(2) << hours // 小时固定2位宽度 << ":" // 分隔符 << std::setw(2) << minutes // 分钟固定2位宽度 << ":" // 分隔符 << std::setw(2) << seconds // 秒固定2位宽度 << ":" // 分隔符(此处可能应为.) << std::setw(3) << milliseconds; // 毫秒固定3位宽度 ``` **关键组件解析**: 1. **`std::ostringstream`**:输出字符串流,用于构建格式化字符串 2. **`std::setfill('0')`**:设置填充字符(持续有效直到被修改) 3. **`std::setw(n)`**:设置字段宽度(仅对下一个输出有效) 4. **数值输出**:自动将整数转换为字符串并应用格式化规则 #### 问题分析与改进建议 1. **分隔符问题**: - 当前代码在秒毫秒间使用 `:`,但标准时间格式通常使用 `.` 或 `,` - 建议修改: ```cpp // 将最后一个冒号改为点号 << std::setw(2) << seconds << "." ``` 2. **毫秒位数处理**: - 当 `milliseconds < 100` 时,输出可能不足3位(如 `005` 变成 `5`) - 解决方案:使用 `std::setw(3)` 确保3位宽度(您的代码已正确处理) 3. **负值处理**: - 时间值不应为负,但若出现负值,输出会包含负号 - 防御性编程: ```cpp hours = std::max(0, hours); // 确保非负 ``` #### 完整优化实现 ```cpp #include <sstream> #include <iomanip> #include <string> std::string format_time(int hours, int minutes, int seconds, int milliseconds) { // 验证输入范围 hours = std::clamp(hours, 0, 23); minutes = std::clamp(minutes, 0, 59); seconds = std::clamp(seconds, 0, 60); // 60允许闰秒 milliseconds = std::clamp(milliseconds, 0, 999); std::ostringstream oss; oss << std::setfill('0') << std::setw(2) << hours << ":" << std::setw(2) << minutes << ":" << std::setw(2) << seconds << "." // 使用标准毫秒分隔符 << std::setw(3) << milliseconds; return oss.str(); } // 使用示例 auto time_str = format_time(9, 5, 30, 1); // 返回 "09:05:30.001" ``` #### 高级格式化技巧 1. **自定义格式化类**(避免重复设置): ```cpp struct TimeFormatter { int value; int width; TimeFormatter(int v, int w) : value(v), width(w) {} }; std::ostream& operator<<(std::ostream& os, const TimeFormatter& tf) { return os << std::setfill('0') << std::setw(tf.width) << tf.value; } // 使用方式 oss << TimeFormatter(hours, 2) << ":" << TimeFormatter(minutes, 2) << ":" << TimeFormatter(seconds, 2) << "." << TimeFormatter(milliseconds, 3); ``` 2. **C++20 `<format>` 替代方案**: ```cpp #if __cplusplus >= 202002L #include <format> std::string format_time(int h, int m, int s, int ms) { return std::format("{:02d}:{:02d}:{:02d}.{:03d}", h, m, s, ms); } #endif ``` #### 性能优化建议 1. **重用字符串流对象**(减少内存分配): ```cpp thread_local std::ostringstream oss; // 线程局部存储 oss.str(""); // 清空内容 oss.clear(); // 清除状态标志 oss << std::setfill('0') << std::setw(2) << hours << ":" // ... 其他部分 auto result = oss.str(); ``` 2. **直接使用 `snprintf`**(高性能场景): ```cpp char buffer[16]; snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds); return std::string(buffer); ``` #### 边界情况处理 1. **超大数值处理**: ```cpp // 自动处理超过24小时的时间 hours = hours % 24 + (minutes / 60); minutes %= 60; ``` 2. **毫秒进位处理**: ```cpp if(milliseconds >= 1000) { seconds += milliseconds / 1000; milliseconds %= 1000; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值