C++divides modulus negate函数对象

2026-04-11 06:30:35 1067阅读 0评论

C++里那些“不声不响”的函数对象:divides、modulus、negate 实用指南

写C++模板代码时,你有没有试过这样写:

std::transform(a.begin(), a.end(), b.begin(), c.begin(), std::plus<int>());

plus大家熟,但翻到 <functional> 头文件里往下扫两眼,很快会撞见几个名字有点“数学课本味儿”的家伙:dividesmodulusnegate。它们不像 lessequal_to 那样高频出现,可一旦用对了场景,真能省掉一堆 lambda——而且比手写更清晰、更安全。

先说个常见误区:modulus 不是取模运算符 % 的简单替代,它专为整数除法余数设计,且对负数有明确定义;而 divides 在浮点数下才真正“除得安心”,整数用它反而容易踩坑。


divides:除法?先看清类型再说

std::divides<T> 对应 operator/,乍看很直白。但实际用起来,得盯紧模板参数 T:

  • 若 T 是 intdivides<int>()(7, 2) 返回 3(整数截断),不是 3.5
  • 若 T 是 double,结果才是预期的浮点商。

更关键的是:它不检查除零divides<int>()(5, 0) 是未定义行为——这点和手写 / 完全一致,但容易被忽略,因为函数对象自带“安全假象”。

实用建议:
✅ 浮点计算中用 divides<double> 替代 [](auto a, auto b){ return a / b; },语义更紧凑;
❌ 整数除法慎用,除非你明确需要截断语义,且已确保分母非零(此时不如直接用 /,少一层封装)。


modulus:别再用 % 写泛型了

std::modulus<T> 看似就是 % 的包装,但它解决了 C++ 里一个长期被轻视的问题:负数取余的歧义

C++ 标准规定:a % b 的符号与被除数 a 相同(如 -7 % 3 == -1)。但数学上,我们常想要非负余数(即 -7 mod 3 == 2)。modulus<int> 严格遵循 % 的语义,不作调整。

那它价值在哪?
→ 在泛型算法中,当你需要把 vector<int> 映射到固定大小哈希桶时:

std::transform(keys.begin(), keys.end(), buckets.begin(),
               std::modulus<size_t>{}); // 桶索引 = key % bucket_count

这里用 modulus<size_t> 比手写 lambda 更直观,且避免类型推导歧义(比如 keyintbucket_countsize_t% 可能触发隐式转换警告)。

⚠️ 注意:modulus 要求两个操作数类型相同。modulus<int>()(7LL, 3) 编译不过——这不是缺陷,而是强制你在类型层面厘清意图。


negate:不止是“加个负号”

std::negate<T> 对应一元 -,看起来最无脑。但它的存在意义,恰恰藏在“一元”这个属性里。

对比两种写法:

// 方案A:lambda
std::transform(v.begin(), v.end(), v.begin(), [](auto x) { return -x; });

// 方案B:negate
std::transform(v.begin(), v.end(), v.begin(), std::negate<>{});

后者优势明显:
🔹 自动推导 T(C++17起支持类模板参数推导),不用写 negate<int>
🔹 语义无歧义——看到 negate 就知道是取反,不是 0 - x~x
🔹 在 std::accumulate 中配合 plus 使用时,逻辑链条更干净:

int sum_of_abs = std::accumulate(v.begin(), v.end(), 0,
    [](int acc, int x) { return acc + (x < 0 ? -x : x); });
// → 改成:
int sum_of_abs = std::accumulate(v.begin(), v.end(), 0,
    std::plus<>{});
std::transform(v.begin(), v.end(), v.begin(), std::abs<int>);

等等,这里用了 std::abs?没错——negate 不等价于 abs,它只翻符号,不取绝对值。这是新手常混的一点:negate(-5)5,但 negate(5)-5。要绝对值,该用 std::abs,别硬拗。


真实协作场景:组合使用更见功力

这三个函数对象真正发光的地方,是和其他 STL 组件“搭伙干活”。比如:

// 计算向量点积:Σ a[i] * b[i]
auto dot = std::inner_product(a.begin(), a.end(), b.begin(), 0,
                               std::plus<>{}, std::multiplies<>{});

// 如果想算 Σ a[i] * (-b[i]) 呢?
auto dot_neg_b = std::inner_product(a.begin(), a.end(), b.begin(), 0,
                                    std::plus<>{},
                                    [](auto x, auto y) { return x * (-y); });

但更清爽的写法是:

std::vector<int> neg_b = b;
std::transform(neg_b.begin(), neg_b.end(), neg_b.begin(), std::negate<>{});
auto dot_neg_b = std::inner_product(a.begin(), a.end(), neg_b.begin(), 0,
                                     std::plus<>{}, std::multiplies<>{});

这里 negate 提前“预处理”了数据,让 inner_product 保持纯数学语义,而不是塞进业务逻辑。函数对象的价值,不在于单点替代,而在于让每层职责更纯粹。


C++ 的函数对象不是语法糖展览馆。dividesmodulusnegate 这些名字朴素的家伙,用好了,能让泛型代码从“能跑”走向“好懂”、“好改”、“好验”。它们不炫技,但每次调用都在悄悄加固类型安全和语义清晰的边界。

下次写 transformaccumulate 时,别急着开 lambda——先翻翻 <functional>,说不定那个“课本味儿”名字,正是你代码里缺的那块拼图。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,1067人围观)

还没有评论,来说两句吧...

目录[+]