三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

24. 函数符

24. 函数符
函数对象
函数对象(函数符 functor)

函数对象也叫函数符(functor),是可以以函数方式与 () 结合使用的任意对象。这包括函数名、指向函数的指针,以及重载了 () 运算符的类对象(即定义了 operator()() 的类)。例如定义一个 Linear 类,重载的 () 运算符使得能够像函数那样使用 Linear 对象:f1(12.5) 的右侧就是 f1.operator()(12.5)。

class Linear { // 函数符类:重载 () 运算符 private: double slope; // 斜率:由构造函数设置 double y0; // 截距:由构造函数设置 public: Linear(double sl, double y) // 构造函数绑定状态 : slope(sl), y0(y) {} double operator()(double x) { // 重载 ():使对象可像函数调用 return y0 + slope * x; // x 来自调用参数 } }; // Linear f1(1.0, 0.0); double y1 = f1(12.5); // 计算 0 + 1*12.5
for_each 的第三个参数与模板机制

for_each 本质上就是一个“包装好的 for 循环”,它负责从头到尾遍历容器,并把每个元素依次传递给你提供的函数(或函数对象)去执行。

for_each() 的第三个参数可以是常规函数,也可以是函数符。

for_each( 起始位置, 结束位置, 函数/对象 );

不能把它声明为函数指针,因为函数指针指定了参数类型;由于容器可以包含任意类型,预先无法知道应使用哪种参数类型。STL 通过使用模板解决这个问题,其原型为

template <class InputIterator, class Function> Function for_each(InputIterator first, InputIterator last, Function f);
// for_each 的原型:Function 可为函数指针或函数符类 template <class InputIterator, class Function> Function for_each(InputIterator first, InputIterator last, Function f) { while (first != last) { // 遍历整个区间 f(*first); // 对每个元素调用 f ++first; } return f; // 返回函数对象 }
函数符概念(generator / unary / binary / predicate)

函数符概念(Generator / Unary / Predicate)是 STL 算法对“传入参数个数”和“返回值类型”的一种“硬性接口约定”。

生成器(generator)是不用参数就可以调用的函数符;

一元函数(unary function)是用一个参数可以调用的函数符;

二元函数(binary function)是用两个参数可以调用的函数符;

谓词(predicate)是返回 bool 值的一元函数;

二元谓词(binary predicate)是返回 bool 值的二元函数。

如: for_each() 的函数符应当是一元函数,因为它每次用于一个容器元素;一些 STL 函数需要谓词参数或二元谓词参数。

remove_if 与类函数符传递额外信息
形态用法是否真的释放内存(改变容器大小)?后续必须做的事
通用算法(头文件<algorithm>std::remove_if(v.begin(), v.end(), pred)。它只是把“被删的元素”挪到末尾,把“保留的元素”挪到前面,容器大小不变。必须配合erase()才能真正删除(即Erase-Remove 惯用法)。
list成员函数(头文件<list>my_list.remove_if(pred)。链表节点被直接销毁,list的大小自动变小。不需要任何后续操作。

算法限制:remove_if 只能接受一元谓词(一个参数),用于接收当前遍历到的元素。

信息瓶颈:普通函数只能写死阈值,无法动态传入第二个参数。

解决方法(函数符):利用类成员变量(如 cutoff)携带“额外信息”。构造函数负责设置规则,operator() 负责结合元素和规则做出判断。

template <class T> class TooBig { // 类函数符:用成员携带额外信息 private: T cutoff; // 取舍值:由构造函数设置 public: TooBig(const T& t) : cutoff(t) {} // 构造函数绑定阈值 bool operator()(const T& v) { // 谓词:单个参数 return v > cutoff; // 判断是否大于阈值 } }; // scores.remove_if(TooBig<int>(200)); // 删除所有大于 200 的元素
类函数符作为函数适配器(TooBig2)

如果已经有一个接受两个参数的模板函数 tooBig(val, lim),可以用类将它转换为单参数的函数对象——TooBig2 类把第二个参数用于构建函数对象,operator() 内部调用二元模板函数并传入已绑定的 cutoff。

template <class T> bool tooBig(const T& val, const T& lim) { // 已有的二元模板函数 return val > lim; } ​ template <class T> class TooBig2 { // 函数适配器:二元→一元 private: T cutoff; // 绑定的第二个参数 public: TooBig2(const T& t) : cutoff(t) {} bool operator()(const T& v) { // 单参数接口 return tooBig<T>(v, cutoff); // 内部调用二元函数 } }; // TooBig2<int> tB100(100); if (tB100(x)) // 等价于 if (tooBig(x, 100))
transform() 算法

transform() 算法用于对一个(或两个)输入区间中的每个元素执行指定操作,并将计算结果写入目标区间,实现“批量转换”。

一元:将一个区间中的每个元素,通过一元函数op转换,输出到另一个区间。

template <class InputIt, class OutputIt, class UnaryOp> OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOp op);
参数含义
first1, last1输入区间的起点和终点
d_first目标区间的起点(输出结果)
op一元函数符(接收一个元素,返回转换后的值)
#include <algorithm> #include <vector> ​ std::vector<int> src = {1, 2, 3, 4}; std::vector<int> dst(4); // 预留空间 ​ // 把 src 中的每个元素平方,存入 dst std::transform(src.begin(), src.end(), dst.begin(), [](int x) { return x * x; }); // dst 变为 {1, 4, 9, 16}

二元版:将两个区间中对应位置的元素,通过二元函数 binary_op 结合,输出到目标区间。

template <class InputIt1, class InputIt2, class OutputIt, class BinaryOp> OutputIt transform(InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOp binary_op);
参数含义
first1, last1第一个输入区间的起点和终点
first2第二个输入区间的起点(长度由第一个区间决定)
d_first目标区间的起点
binary_op二元函数符(接收两个元素,返回结合后的值)
std::vector<int> a = {1, 2, 3}; std::vector<int> b = {10, 20, 30}; std::vector<int> result(3); ​ // 把 a 和 b 的对应元素相加,存入 result std::transform(a.begin(), a.end(), b.begin(), result.begin(), [](int x, int y) { return x + y; }); // result 变为 {11, 22, 33}

注意:第二个输入区间必须至少有 last1 - first1 个元素,否则行为未定义。

目标迭代器可以等于输入迭代器,实现“原地修改”。

预定义的函数符(头文件 functional)

预定义函数符是“运算符的‘函数对象’包装器”。因为STL 算法的参数是“对象”,而不是“符号”。

分类函数符名称对应的运算符中文含义
算术plus<T>+加法
minus<T>-减法
multiplies<T>*乘法
divides<T>/除法
modulus<T>%取模(余数)
negate<T>-(一元)取负(如-5
比较(关系)equal_to<T>==等于
not_equal_to<T>!=不等于
greater<T>>大于
less<T><小于
greater_equal<T>>=大于等于
less_equal<T><=小于等于
逻辑logical_and<T>&&逻辑与
logical_or<T>||逻辑或
logical_not<T>!逻辑非
自适应函数符(adaptable functor)与函数适配器

STL 有 5 个相关概念:自适应生成器(adaptable generator)、自适应一元函数(adaptable unary function)、自适应二元函数(adaptable binary function)、自适应谓词(adaptable predicate)和自适应二元谓词(adaptable binary predicate)。使函数符成为自适应的原因是它携带了标识参数类型和返回类型的 typedef 成员:result_typefirst_argument_typesecond_argument_type(如plus<int>对象的返回类型被标识为plus<int>::result_type)。

binder1st,它的数学本质是:“固定二元函数的一个参数,把它变成一元函数。”它把2.5“绑定”到multiplies的第一个参数上,生成一个新的临时对象。这个新对象只需要接收一个参数(x),内部执行2.5 * x

现代 C++(C++11 起)中,它已经被 Lambda 表达式和 std::bind 彻底取代了。

知识点描述
函数对象(函数符)可以函数方式与()结合使用的对象:函数名、函数指针、重载()的类对象;类函数符用构造函数保存状态,x来自operator()参数。
for_each 第三参数与模板第三参数不能声明为函数指针(参数类型不确定);用模板参数Function同时接受函数指针与函数符类;for_each内部用f()统一调用。
函数符概念生成器(无参数)、一元函数(一个参数)、二元函数(两个参数)、谓词(返回 bool 的一元函数)、二元谓词(返回 bool 的二元函数);for_each需要一元函数。
remove_if 与类函数符list::remove_if(predicate)对每个元素应用谓词,返回 true 则删除;谓词只能一个参数,用类成员(构造函数设置 cutoff)传递额外信息。
类函数符作为函数适配器用类把二元模板函数转换为单参数函数对象:第二参数由构造函数绑定、operator()内部调用二元函数;TooBig2是函数适配器,使函数满足不同接口。
transform() 算法一元版:对区间每元素施加函数符生成新序列;二元版:对两个区间对应元素施加二元函数;目标迭代器可在原区间内覆盖旧值。
预定义的函数符<functional>提供与内置算术/关系/逻辑运算符等价的函数符(plus、multiplies、greater 等);因运算符不是函数,用plus<double>()等作为算法参数;老式实现用times代替multiplies
自适应函数符与函数适配器自适应函数符携带result_typefirst_argument_typesecond_argument_typetypedef;binder1st/binder2nd将自适应二元函数适配为一元(绑定第一/第二参数),助手函数bind1st()/bind2nd()简化创建。
← 返回列表