std::midpoint是c++20唯一标准保证不触发有符号整数加法溢出的中点计算工具,底层用a+(b-a)/2或位运算实现,仅对同类型整数或同数组内指针有效,不适用于浮点数或类型混用。

直接用 std::midpoint 替换 (a + b) / 2 并不能自动防溢出——它只在类型严格匹配、编译器启用 C++20、且参数语义合理时才真正起作用。盲目替换可能编译失败,或在无符号坐标、浮点坐标等场景引入逻辑偏差。
std::midpoint(int, int) 为什么能防整数溢出
它不依赖 a + b,而是等价于 a + (b - a) / 2(对同号有符号整型)或底层位运算组合,全程避开加法中间值溢出路径。例如 std::midpoint(INT_MAX, INT_MAX - 1) 返回确定值 INT_MAX - 1,而 (INT_MAX + INT_MAX - 1) / 2 是未定义行为(UB)。
- 仅当
a和b同为有符号整型(如int、long long)且类型完全一致时,该防溢出保证成立 -
std::midpoint(-3, 2)返回-1,符合向下取整中点 ⌊(a+b)/2⌋,不是向零取整 - 支持
a > b输入(如std::midpoint(10, 2)→6),结果数学正确,但业务需自行判断是否允许逆序区间 - 对
size_t等无符号类型,b - a会按模回绕,std::midpoint(1U, 0U)返回0U,这是标准行为,但未必符合你对“中点”的直觉
std::midpoint 指针版本是唯一不可替代的用途
这是它最刚性、最不可替代的价值点:计算大数组内两个指针的中点地址时,std::midpoint(p1, p2) 被标准强制要求规避 p2 - p1 超出 ptrdiff_t 表示范围导致的未定义行为。
- 必须是同一数组内的原生指针(含 one-past-the-end),例如
std::midpoint(&arr[0], &arr[N])合法 -
std::midpoint(v.begin(), v.end())编译失败——std::vector::iterator不是原生指针;应写成std::midpoint(std::data(v), std::data(v) + v.size()) - 不接受
void*、const int*与int*混用、跨分配块指针,类型和 cv 修饰必须精确匹配 - 它不检查指针是否有效、数组是否为空——
&v[0]在空vector上已非法,std::midpoint不兜底
哪些写法会直接编译失败或行为未定义
类型不一致、环境不达标、传参越界,都会让 std::midpoint 失效,而不是静默降级。
- 没启用 C++20:GCC/Clang 必须加
-std=c++20,MSVC 需/std:c++20;否则函数不可见 - 类型不匹配:
std::midpoint(1, 1U)、std::midpoint(1L, 1)、std::midpoint(short{1}, int{2})全部编译失败 - 浮点数滥用:
std::midpoint(1e30f, 1.0f)底层仍是(a + b) / 2,加法直接舍入为1e30f,结果错误;此时应改用a + (b - a) * 0.5f或std::fma(a, 0.5f, b * 0.5f) - 迭代器误传:
std::midpoint(v.begin(), v.end())不合法;std::midpoint(&v[0], &w[0] + w.size())(v、w是不同容器)行为未定义
C++17 及更早环境的手动兜底方案
无法升级 C++ 标准时,需手写等效逻辑,但要注意分支和类型适配。
- 有符号整型(推荐):
a + (b - a) / 2,前提是b - a不溢出(对离散坐标索引天然满足) - 无符号整型(慎用):
(a & b) + ((a ^ b) >> 1)仅对非负数安全;若坐标可能为负,不要用此位运算 - 宽类型提升(保守):用
std::common_type_t<decltype decltype long></decltype>提升后计算,再截断,但需额外检查截断是否溢出 - 宏封装示例:
#define SAFE_MID(a, b) ((a) + ((b) - (a)) / 2),调用前确保a、b同类型且为有符号整型
最容易被忽略的是:它不校验输入有效性,只保“算得对”。传进去两个悬空指针,它也可能返回一个地址;传进去 INT_MIN 和 INT_MAX,它能算对,但业务上这个中点是否合法,得你自己判断。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











