算术、关系与逻辑函数对象
极难3现成的数学工具:STL 算术、关系与逻辑函数对象
开门见山:这是啥?用来干啥?
想象一下,你有一堆数字要排序,要逐对相加,要检查是否所有成绩都及格……在 C++ 里,你可以自己写一个“仿函数”(像函数一样的类对象),但 STL 已经帮你准备好了现成的函数对象小工具——它们都在 <functional> 头文件里,分为算术(加减乘除)、关系(大于小于等于)和逻辑(与或非)三大类。你直接拿过来用,就像从文具盒里掏出铅笔,不用自己削木头。
这些函数对象都是模板类,可以跟 sort、transform、accumulate 等算法完美配合,让代码又短又清楚。下面我们就一个个来看怎么用。
三大类函数对象:看名字就知道意思
算术运算:做数学题,不用手写计算器
| 函数对象 | 含义 | 例子 |
|---|---|---|
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(一元) | 今天没有下雨? |
怎么用?三步走
- 包含头文件:
#include <functional> - 创建对象或直接用临时对象:
std::plus<int> add;或直接std::plus<int>() - 像函数一样调用它:
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:混淆一元和二元函数对象
negate 和 logical_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;
}
注意:上面的 bind 和 placeholders 需要包含 <functional>,我们已经包含了。但更推荐初学者用 Lambda 表达式 代替 bind,因为更直观。例如查找大于80的元素可以写成:
auto it = find_if(grades.begin(), grades.end(), [](int x){ return x > 80; });
Python 版本的等价代码(对比学习)
Python 的 operator 模块提供了类似的函数对象,配合 map、reduce、sorted 等使用。下面的代码模拟了 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_of、any_of),而逻辑函数对象是二元的。通常我们配合 bind 或 Lambda 使用。但如果你有两个布尔序列要逐元素做逻辑运算,transform + logical_and 就非常合适。
总结:什么时候该用它们?
- 排序:
sort里用greater降序,less升序(默认) - 逐元素运算:
transform里用plus、multiplies等 - 累积:
accumulate里用multiplies求乘积,plus求和(默认) - 查找:配合
bind做一元条件,或者直接 Lambda 更简单 - 逻辑判断:处理两个布尔序列时用
logical_and/logical_or
这些内置函数对象就是 STL 算法拼图中的小零件,让你不用重复造轮子。当内置的不能满足你的需求时(比如复杂的自定义比较),就用 Lambda 表达式 吧——它更灵活,也是现代 C++ 的推荐写法。
相关知识点指引
- Lambda 表达式:更直观地写匿名函数,推荐用于复杂条件
std::not_fn(C++17):取反一个谓词,替代旧的std::not1和std::not2std::bind:绑定函数对象的部分参数(已逐渐被 Lambda 取代)- STL 算法:
sort、transform、accumulate、find_if、all_of等 <numeric>头文件:提供accumulate、partial_sum等数值算法
掌握这些,你就能在编程题和生活项目中又快又准地处理各种数据运算了!
例题精讲
在C++ STL中,以下哪个函数对象用于执行两个整数的加法运算?
std::greater<int>()是一个一元谓词,用于判断一个整数是否大于另一个整数。
使用STL算法std::transform将vector<int> vec = {1,2,3,4,5}的每个元素变为其两倍,请补全代码:std::transform(vec.begin(), vec.end(), vec.begin(), ___);以下哪个代码片段能够正确使用STL逻辑函数对象,判断一个vector<bool>中所有元素均为true?
有一个std::vector<int> vec = {3, -1, 0, 5, -2},现要统计其中所有非正数(小于等于0)的个数,使用函数对象填空:int cnt = std::count_if(vec.begin(), vec.end(), ___);