CC++ & Algorithm

算术、关系与逻辑函数对象

极难3
语言版本:通用
概述:STL提供了一组现成的“函数对象小工具”,能帮你快速完成加减乘除、大小比较和逻辑运算,不用自己写仿函数,让代码更干净。

现成的数学工具:STL 算术、关系与逻辑函数对象

开门见山:这是啥?用来干啥?

想象一下,你有一堆数字要排序,要逐对相加,要检查是否所有成绩都及格……在 C++ 里,你可以自己写一个“仿函数”(像函数一样的类对象),但 STL 已经帮你准备好了现成的函数对象小工具——它们都在 <functional> 头文件里,分为算术(加减乘除)、关系(大于小于等于)和逻辑(与或非)三大类。你直接拿过来用,就像从文具盒里掏出铅笔,不用自己削木头。

这些函数对象都是模板类,可以跟 sorttransformaccumulate 等算法完美配合,让代码又短又清楚。下面我们就一个个来看怎么用。

三大类函数对象:看名字就知道意思

算术运算:做数学题,不用手写计算器

函数对象含义例子
std::plus<T>加法 a + b两个零花钱加起来
std::minus<T>减法 a - b零花钱减去零食钱
std::multiplies<T>乘法 a * b苹果单价×数量
std::divides<T>除法 a / b总成绩除以科目数
std::modulus<T>取模 a % b零花钱被5整除剩下的
std::negate<T>取反 -a(一元)温度下降的负数表示

生活中的例子:你每周有20元零花钱,买零食花了8元,还剩多少?用 std::minus<int>()(20, 8) 就算出来了。

关系运算:比大小、判相等

函数对象含义例子
std::equal_to<T>等于 a == b两个人的考试分数相同?
std::not_equal_to<T>不等于 a != b班级人数不等于班主任说的?
std::greater<T>大于 a > b体育成绩高于平均?
std::less<T>小于 a < b排队时你的身高比前面矮?
std::greater_equal<T>大于等于 a >= b身高 ≥ 1.2米可以玩过山车
std::less_equal<T>小于等于 a <= b考试分数 ≤ 60分就不及格

逻辑运算:判断真与假

函数对象含义例子
std::logical_and<T>逻辑与 a && b今天既下雨又刮风?
std::logical_or<T>逻辑或 a || b今天下雨或者刮风?
std::logical_not<T>逻辑非 !a(一元)今天没有下雨?

怎么用?三步走

  1. 包含头文件#include <functional>
  2. 创建对象或直接用临时对象std::plus<int> add; 或直接 std::plus<int>()
  3. 像函数一样调用它add(3,5) 返回 8

典型写法:在算法里直接匿名构造:

sort(v.begin(), v.end(), std::greater<int>());   // 降序排序
transform(a.begin(), a.end(), b.begin(), c.begin(), std::plus<int>()); // 逐对相加

四个常用场景(带生活例子)

场景1:用 greater 给成绩降序排序

小王这次测验成绩:82, 93, 61, 75, 88。老师要按从高到低排名。

vector<int> grades = {82, 93, 61, 75, 88};
sort(grades.begin(), grades.end(), greater<int>()); // 降序:93, 88, 82, 75, 61

场景2:用 plus 把两个队伍的零花钱加起来

小明有零花钱记录:{10, 20, 30},小华有:{5, 15, 25},他们想每天各花多少钱?就是逐对相加。

vector<int> ming = {10, 20, 30};
vector<int> hua = {5, 15, 25};
vector<int> total(ming.size());
transform(ming.begin(), ming.end(), hua.begin(), total.begin(), plus<int>());
// total = {15, 35, 55}

场景3:用 multiplies 计算班级成绩的乘积(虽然不常用,但演示 accumulate

体育老师让计算全班跑步成绩的乘积(假设值很小):{1, 2, 3, 4} 的乘积是 24。

vector<int> scores = {1, 2, 3, 4};
int product = accumulate(scores.begin(), scores.end(), 1, multiplies<int>());
// 结果是24

场景4:用逻辑函数对象判断一组布尔值是否全为真

老师记录全班同学“是否完成作业”:{true, false, true}。检查是否所有人都完成?那就需要逻辑与。

vector<bool> homeworks = {true, false, true};
vector<bool> all_done;
transform(homeworks.begin(), homeworks.end(), homeworks.begin(), back_inserter(all_done), logical_and<bool>());
// 但注意:这样只是原地与,实际需要用两个序列。更常见的是用 all_of 配合 lambda。

新手最容易犯的错误(避坑指南)

错误1:忘记包含头文件

// 直接写 greater<int>() 报错!忘记 #include <functional>
sort(v.begin(), v.end(), greater<int>());

错误2:类型不匹配

比如用 plus<int>double 做加法会丢失精度:

plus<int> add;
double result = add(3.5, 2.1);  // 结果是5(因为3.5被截断为3,2.1被截断为2)

正确做法:用 plus<double>

错误3:混淆一元和二元函数对象

negatelogical_not 是一元的(只接受一个参数),其他都是二元的。如果你不小心传了两个参数给一元函数,编译器会报错。

错误4:在 sort 里用关系对象时忘了 () 创建实例

sort(v.begin(), v.end(), greater);   // 错误!greater 是一个模板类,不是对象
sort(v.begin(), v.end(), greater<int>()); // 正确:创建临时对象

错误5:C++14 之前用空模板参数

从C++14开始可以写 greater<> 自动推导,但老编译器可能不支持。

完整的 C++ 代码示例(详细注释)

下面是一个综合程序,演示了算术、关系、逻辑函数对象在多个算法中的用法。代码里每一行变量定义都附有中文注释,方便你理解。

#include <iostream>
#include <vector>
#include <algorithm>      // sort, transform, find_if
#include <functional>     // 预定义函数对象、bind、placeholders
#include <numeric>        // accumulate
using namespace std;

int main() {
    // 1. 算术运算
    cout << "=== 算术运算 ===" << endl;
    
    // 加法:创建加法函数对象
    plus<int> add;               // int 型加法器
    cout << "5 + 3 = " << add(5, 3) << endl;   // 8
    
    // 乘法:直接用临时对象
    cout << "2.5 * 4 = " << multiplies<double>()(2.5, 4) << endl; // 10
    
    // 一元取反
    negate<int> neg;             // int 型取反器
    cout << "取反 10: " << neg(10) << endl;    // -10
    
    // transform 中使用乘法:把两个数组的对应元素相乘
    vector<int> prices = {5, 10, 15};           // 苹果单价(元/斤)
    vector<int> weights = {2, 3, 1};            // 购买重量(斤)
    vector<int> costs(prices.size());           // 每种苹果的总价
    transform(prices.begin(), prices.end(), weights.begin(), costs.begin(), 
              multiplies<int>());
    cout << "每种苹果总价: ";
    for (int cost : costs) cout << cost << " "; // 10 30 15
    cout << endl;
    
    // 2. 关系运算 —— 排序
    cout << "\n=== 关系运算 ===" << endl;
    
    vector<int> grades = {82, 93, 61, 75, 88};       // 考试成绩
    cout << "原始成绩: ";
    for (int g : grades) cout << g << " ";
    cout << endl;
    
    // 升序排序(默认是 less,也可显式写)
    sort(grades.begin(), grades.end(), less<int>());
    cout << "升序: ";
    for (int g : grades) cout << g << " ";   // 61 75 82 88 93
    cout << endl;
    
    // 降序排序
    sort(grades.begin(), grades.end(), greater<int>());
    cout << "降序: ";
    for (int g : grades) cout << g << " ";   // 93 88 82 75 61
    cout << endl;
    
    // 查找第一个大于80的元素(使用 bind 配合 greater)
    // 相当于:find_if 找到元素 x 使 greater<int>()(x, 80) 为真,即 x>80
    auto it = find_if(grades.begin(), grades.end(), 
                      bind(greater<int>(), placeholders::_1, 80));
    if (it != grades.end())
        cout << "第一个大于80的成绩是: " << *it << endl; // 93

    // 3. 逻辑运算
    cout << "\n=== 逻辑运算 ===" << endl;
    
    logical_and<bool> land;          // 逻辑与
    cout << "true && false = " << land(true, false) << endl;  // 0 (false)
    
    logical_or<bool> lor;            // 逻辑或
    cout << "true || false = " << lor(true, false) << endl;   // 1 (true)
    
    logical_not<bool> lnot;          // 逻辑非(一元)
    cout << "!true = " << lnot(true) << endl;                 // 0
    
    // transform 对两个布尔向量做逻辑与(对应元素判断)
    vector<bool> has_rain = {true, false, true};       // 三天是否下雨
    vector<bool> has_wind = {true, true, false};       // 三天是否刮风
    vector<bool> both_rain_wind(has_rain.size());      // 三天是否既下雨又刮风
    transform(has_rain.begin(), has_rain.end(), 
              has_wind.begin(), both_rain_wind.begin(), 
              logical_and<bool>());
    cout << "三天既下雨又刮风: ";
    for (bool b : both_rain_wind) cout << (b ? "是 " : "否 "); // 是 否 否
    cout << endl;
    
    // 4. 用 accumulate 做乘法(乘积)
    vector<int> numbers = {1, 2, 3, 4};
    int product = accumulate(numbers.begin(), numbers.end(), 1, 
                              multiplies<int>());  // 1*2*3*4 = 24
    cout << "\n1*2*3*4 = " << product << endl;
    
    return 0;
}

注意:上面的 bindplaceholders 需要包含 <functional>,我们已经包含了。但更推荐初学者用 Lambda 表达式 代替 bind,因为更直观。例如查找大于80的元素可以写成:

auto it = find_if(grades.begin(), grades.end(), [](int x){ return x > 80; });

Python 版本的等价代码(对比学习)

Python 的 operator 模块提供了类似的函数对象,配合 mapreducesorted 等使用。下面的代码模拟了 C++ 的用法。

import operator
from functools import reduce

def main():
    # 1. 算术运算
    print("=== 算术运算 ===")
    add = operator.add
    print("5 + 3 =", add(5, 3))                     # 8

    mul = operator.mul
    print("2.5 * 4 =", mul(2.5, 4))                 # 10.0

    neg = operator.neg
    print("取反 10:", neg(10))                       # -10

    # 对应 transform 中的乘法
    prices = [5, 10, 15]        # 苹果单价
    weights = [2, 3, 1]         # 购买重量
    costs = list(map(mul, prices, weights))   # 返回 map 对象转列表
    print("每种苹果总价:", costs)                # [10, 30, 15]

    # 2. 关系运算
    print("\n=== 关系运算 ===")
    grades = [82, 93, 61, 75, 88]
    print("原始成绩:", grades)
    asc = sorted(grades, reverse=False)      # 升序
    print("升序:", asc)                        # [61, 75, 82, 88, 93]
    desc = sorted(grades, reverse=True)      # 降序
    print("降序:", desc)                       # [93, 88, 82, 75, 61]

    # 查找第一个大于80的
    for g in grades:
        if g > 80:
            print("第一个大于80的成绩是:", g)   # 93
            break

    # 3. 逻辑运算
    print("\n=== 逻辑运算 ===")
    land = operator.and_
    print("True & False =", land(True, False))   # False
    lor = operator.or_
    print("True | False =", lor(True, False))     # True
    lnot = operator.not_
    print("not True =", lnot(True))               # False

    # 逻辑与的 transform
    has_rain = [True, False, True]
    has_wind = [True, True, False]
    both = list(map(land, has_rain, has_wind))
    print("三天既下雨又刮风:", both)               # [True, False, False]

    # reduce 做乘法
    numbers = [1, 2, 3, 4]
    product = reduce(mul, numbers, 1)
    print("\n1*2*3*4 =", product)                  # 24

if __name__ == "__main__":
    main()

常见问题与更多技巧

问题1:为什么不直接用 sort(v.begin(), v.end()) 还要传 greater

默认是升序,但如果你要降序,就需要告诉它用什么比较方式。greater<int>() 就像一个“比较器”,告诉 sort “如果前者大于后者就交换”,从而得到从大到小的顺序。

问题2:逻辑函数对象怎么用在 all_of 这类算法里?

all_of 需要一元谓词(一个参数),而 logical_and 是二元的。你可以用 bind 或者 Lambda 来实现。比如检查所有成绩是否都大于等于60:

all_of(grades.begin(), grades.end(), bind(greater_equal<int>(), placeholders::_1, 60));
// 或 lambda: [](int x){ return x >= 60; }

问题3:C++14 的空模板参数有什么好处?

std::less<> 可以自动匹配类型,避免写 std::less<int>std::less<double> 等。例如:

sort(v.begin(), v.end(), greater<>());  // 自动推导为值的类型

但注意:如果你在 map 等容器中作为模板参数,就不能省略,例如 map<int, string, greater<int>> m;

问题4:这些函数对象有性能开销吗?

几乎没有。它们都是轻量级的,编译器很可能会内联成一段简单的比较或运算指令,和你手写 if(a > b) 一样快。

问题5:为什么很少看到 logical_and 用在算法里?

因为大部分算法要求一元谓词(比如 all_ofany_of),而逻辑函数对象是二元的。通常我们配合 bind 或 Lambda 使用。但如果你有两个布尔序列要逐元素做逻辑运算,transform + logical_and 就非常合适。

总结:什么时候该用它们?

  • 排序sort 里用 greater 降序,less 升序(默认)
  • 逐元素运算transform 里用 plusmultiplies
  • 累积accumulate 里用 multiplies 求乘积,plus 求和(默认)
  • 查找:配合 bind 做一元条件,或者直接 Lambda 更简单
  • 逻辑判断:处理两个布尔序列时用 logical_and / logical_or

这些内置函数对象就是 STL 算法拼图中的小零件,让你不用重复造轮子。当内置的不能满足你的需求时(比如复杂的自定义比较),就用 Lambda 表达式 吧——它更灵活,也是现代 C++ 的推荐写法。

相关知识点指引

  • Lambda 表达式:更直观地写匿名函数,推荐用于复杂条件
  • std::not_fn(C++17):取反一个谓词,替代旧的 std::not1std::not2
  • std::bind:绑定函数对象的部分参数(已逐渐被 Lambda 取代)
  • STL 算法sorttransformaccumulatefind_ifall_of
  • <numeric> 头文件:提供 accumulatepartial_sum 等数值算法

掌握这些,你就能在编程题和生活项目中又快又准地处理各种数据运算了!

例题精讲

1单选题

在C++ STL中,以下哪个函数对象用于执行两个整数的加法运算?

Astd::plus<int>
Bstd::minus<int>
Cstd::multiplies<int>
Dstd::modulus<int>
2判断题

std::greater<int>()是一个一元谓词,用于判断一个整数是否大于另一个整数。

3填空题
使用STL算法std::transform将vector<int> vec = {1,2,3,4,5}的每个元素变为其两倍,请补全代码:std::transform(vec.begin(), vec.end(), vec.begin(), ___);
4单选题

以下哪个代码片段能够正确使用STL逻辑函数对象,判断一个vector<bool>中所有元素均为true?

Astd::all_of(v.begin(), v.end(), std::logical_and<bool>())
Bstd::all_of(v.begin(), v.end(), std::logical_not<bool>())
Cstd::all_of(v.begin(), v.end(), [](bool x){ return x; })
Dstd::all_of(v.begin(), v.end(), std::identity<bool>())
5填空题
有一个std::vector<int> vec = {3, -1, 0, 5, -2},现要统计其中所有非正数(小于等于0)的个数,使用函数对象填空:int cnt = std::count_if(vec.begin(), vec.end(), ___);