每周技巧 #231:这里和那里之间:几个容易忽视的小算法
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #231: Between Here and There: Some Minor Overlooked Algorithms。
原文最初作为 TotW #231 发布于 2024 年 3 月 7 日。
更新于 2024 年 9 月 30 日。
快捷链接:abseil.io/tips/231
概览
在较新的 C++ 版本中,标准库添加了几个函数,它们唯一的工作是在两个点 x 和 y 之间的某处提供某个(特定的!)点:std::clamp(C++17 起),以及 std::midpoint 和 std::lerp(C++20 起)。
把这些函数模板加入标准库有两个主要目的。第一,它为这些操作建立了可能被广泛识别的通用术语(词汇)。第二,特别是在 std::midpoint 和 std::lerp 的情况下,它确保我们可以使用高质量实现,来避免常见陷阱。
这些操作都是 constexpr,意味着它们可以在运行期和编译期使用。能传给它们的类型取决于具体操作;它们都支持浮点类型,而 std::midpoint 和 std::clamp 提供了额外灵活性。细节如下。
std::clamp
std::clamp(x, min, max) 会把 x “夹紧”到 [min, max] 范围内。更明确地说,如果 x 已经在 min 到 max 的范围内(含端点),那么 std::clamp(x, min, max) 返回 x;如果 x 在范围之外,std::clamp 返回 min 或 max 中更接近 x 的那个。这等价于 std::max(std::min(x, max), min),但它是更直接表达意图的方式(也不那么考验读者)。
警告:虽然
std::clamp返回一个引用,但依赖这一点的代码微妙且不常见,应该添加注释提醒读者。向std::clamp传入临时对象并把结果绑定到引用,很容易意外创建悬垂引用:
1 2 3// `std::clamp(1, 3, 4)` 返回一个引用,指向由 `3` 初始化的临时 int, // 不能在该临时对象生命周期结束后使用它。见技巧 #101。 const int& dangling = std::clamp(1, 3, 4);
std::clamp 适用于任何可用 < 比较的类型(或使用用户传给 std::clamp(x, min, max, cmp) 的比较器)。
std::midpoint
std::midpoint 的作用没有惊喜:std::midpoint(x, y) 返回 x 和 y 中间的点(当 x 和 y 是整数类型时,向 x 方向舍入)。
std::midpoint(x, y) 适用于任意浮点或整数类型(不包括 bool)的值 x、y。额外 bonus 是,std::midpoint(p, q) 也适用于数组中的指针 p、q。
std::lerp
std::lerp 中的 lerp 是 “linear interpolation”(线性插值)的缩写,std::lerp(x, y, t) 返回从 x 到 y 路程中比例为 t 的某个值。例如,std::lerp(x, y, 0) 返回 x,std::lerp(x, y, 1) 返回 y,std::lerp(x, y, 0.5) 可以简化为 std::midpoint(x, y)。
注意:尽管名字如此,如果传入范围 [0, 1] 之外的 t,std::lerp 也可用于外推。例如,std::lerp(100, 101, -2) 求值为 98,std::lerp(100, 101, +2) 为 102。
std::lerp 适用于浮点类型。
建议
- 这些库函数的主要好处之一是提供通用词汇。一如既往,优先使用这些标准设施,而不是从头编写。
- 适用时,优先使用
std::midpoint(x, y),而不是std::lerp(x, y, 0.5)。 - 避免声明对
std::clamp结果的引用。