std::midpoint能防有符号整型溢出,因其用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),结果数学正确,但业务需自行判断是否允许逆序区间 - 头文件是
<utility></utility>(C++20),不是<numeric></numeric>——错包含会导致编译器找不到函数
std::midpoint(T*, T*) 是大数组指针中点不可替代的写法
这是它最刚性、最不可替代的价值点:计算大数组内两个指针的中点地址时,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++免费学习笔记(深入)”;
- 没启用 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,结果错误;此时应改用std::fma(a, 0.5f, b * 0.5f)或手写a + (b - a) * 0.5f -
std::midpoint对NaN、inf无特殊处理,和裸算一样传播异常值
对无符号坐标的模意义中点要特别小心
当坐标使用 size_t、uint32_t 等无符号类型时,std::midpoint 仍可安全调用,但减法操作按模算术回绕,结果在数学上等价于模意义下的中点——这未必符合你对“中点”的直觉。
-
std::midpoint(1U, 0U)返回0U(因(1U - 0U) / 2 == 0,再加0U),这是标准行为,但可能违背业务预期 - 若需保持与有符号版本一致的舍入方向(向零取整),应额外添加条件分支并手动实现
a + (b > a ? (b - a) / 2 : 0)等逻辑 - 始终显式统一类型,例如将
int偏移转为size_t前确认其非负:static_cast<size_t>(x)</size_t> - 避免将结果用于要求严格线性顺序的场景(如非递减区间断言)
真正容易被忽略的是:它不处理「数值语义」以外的上下文——比如二分查找中,你仍需确保 a 和 b 是有效索引、区间非空;std::midpoint 只管算得对,不管用得对。


















