返回介绍

16.5 函数对象

发布于 2024-10-08 23:14:12 字数 8855 浏览 0 评论 0 收藏 0

很多 STL 算法都使用函数对象——也叫函数符(functor)。函数符是可以以函数方式与( ) 结合使用的任意对象。这包括函数名、指向函数的指针和重载了( ) 运算符的类对象(即定义了函数 operator( )( ) 的类)。例如,可以像这样定义一个类:

这样,重载的( ) 运算符将使得能够像函数那样使用 Linear 对象:

其中 y1 将使用表达式 0 + 1 * 12.5 来计算,y2 将使用表达式 10.0 + 2.5 * 0.4 来计算。在表达式 y0 + slope * x 中,y0 和 slope 的值来自对象的构造函数,而 x 的值来自 operator( ) ( ) 的参数。

还记得函数 for_each 吗?它将指定的函数用于区间中的每个成员:

通常,第 3 个参数可以是常规函数,也可以是函数符。实际上,这提出了一个问题:如何声明第 3 个参数呢?不能把它声明为函数指针,因为函数指针指定了参数类型。由于容器可以包含任意类型,所以预先无法知道应使用哪种参数类型。STL 通过使用模板解决了这个问题。for_each 的原型看上去就像这样:

ShowReview( ) 的原型如下:

这样,标识符 ShowReview 的类型将为 void(*)(const Review &),这也是赋给模板参数 Function 的类型。对于不同的函数调用,Function 参数可以表示具有重载的( ) 运算符的类类型。最终,for_each( ) 代码将具有一个使用 f( ) 的表达式。在 ShowReview( ) 示例中,f 是指向函数的指针,而 f( ) 调用该函数。如果最后的 for_each( ) 参数是一个对象,则 f( ) 将是调用其重载的( ) 运算符的对象。

16.5.1 函数符概念

正如 STL 定义了容器和迭代器的概念一样,它也定义了函数符概念。

  • 生成器(generator)是不用参数就可以调用的函数符。
  • 一元函数(unary function)是用一个参数可以调用的函数符。
  • 二元函数(binary function)是用两个参数可以调用的函数符。

例如,提供给 for_each( ) 的函数符应当是一元函数,因为它每次用于一个容器元素。

当然,这些概念都有相应的改进版:

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

一些 STL 函数需要谓词参数或二元谓词参数。例如,程序清单 16.9 使用了 sort( ) 的这样一个版本,即将二元谓词作为其第 3 个参数:

list 模板有一个将谓词作为参数的 remove_if( ) 成员,该函数将谓词应用于区间中的每个元素,如果谓词返回 true,则删除这些元素。例如,下面的代码删除链表 three 中所有大于 100 的元素:

最后这个例子演示了类函数符适用的地方。假设要删除另一个链表中所有大于 200 的值。如果能将取舍值作为第二个参数传递给 tooBig( ),则可以使用不同的值调用该函数,但谓词只能有一个参数。然而,如果设计一个 TooBig 类,则可以使用类成员而不是函数参数来传递额外的信息:

这里,一个值(V)作为函数参数传递,而第二个参数(cutoff)是由类构造函数设置的。有了该定义后,就可以将不同的 TooBig 对象初始化为不同的取舍值,供调用 remove_if( ) 时使用。程序清单 16.15 演示了这种技术。

程序清单 16.15 functor.cpp

一个函数符(f100)是一个声明的对象,而另一个函数符(TooBig<int>(200))是一个匿名对象,它是由构造函数调用创建的。下面是程序清单 16.15 中程序的输出:

假设已经有了一个接受两个参数的模板函数:

则可以使用类将它转换为单个参数的函数对象:

即可以这样做:

因此,调用 tB100(x) 相当于调用 tooBig(x, 100),但两个参数的函数被转换为单参数的函数对象,其中第二个参数被用于构建函数对象。简而言之,类函数符 TooBig2 是一个函数适配器,使函数能够满足不同的接口。

在该程序清单中,可使用 C++11 的初始化列表功能来简化初始化。为此,可将如下代码:

替换为下述代码:

16.5.2 预定义的函数符

STL 定义了多个基本函数符,它们执行诸如将两个值相加、比较两个值是否相等操作。提供这些函数对象是为了支持将函数作为参数的 STL 函数。例如,考虑函数 transform( )。它有两个版本。第一个版本接受 4 个参数,前两个参数是指定容器区间的迭代器(现在您应该已熟悉了这种方法),第 3 个参数是指定将结果复制到哪里的迭代器,最后一个参数是一个函数符,它被应用于区间中的每个元素,生成结果中的新元素。例如,请看下面的代码:

上述代码计算每个元素的平方根,并将结果发送到输出流。目标迭代器可以位于原始区间中。例如,将上述示例中的 out 替换为 gr8.begin( ) 后,新值将覆盖原来的值。很明显,使用的函数符必须是接受单个参数的函数符。

第 2 种版本使用一个接受两个参数的函数,并将该函数用于两个区间中元素。它用另一个参数(即第 3 个)标识第二个区间的起始位置。例如,如果 m8 是另一个 vector<double>对象,mean(double,double)返回两个值的平均值,则下面的的代码将输出来自 gr8 和 m8 的值的平均值:

现在假设要将两个数组相加。不能将+作为参数,因为对于类型 double 来说,+是内置的运算符,而不是函数。可以定义一个将两个数相加的函数,然后使用它:

然而,这样必须为每种类型单独定义一个函数。更好的办法是定义一个模板(除非 STL 已经有一个模板了,这样就不必定义)。头文件 functional(以前为 function.h)定义了多个模板类函数对象,其中包括 plus< >( )。

可以用 plus< >类来完成常规的相加运算:

它使得将函数对象作为参数很方便:

这里,代码没有创建命名的对象,而是用 plus<double>构造函数构造了一个函数符,以完成相加运算(括号表示调用默认的构造函数,传递给 transform( ) 的是构造出来的函数对象)。

对于所有内置的算术运算符、关系运算符和逻辑运算符,STL 都提供了等价的函数符。表 16.12 列出了这些函数符的名称。它们可以用于处理 C++内置类型或任何用户定义类型(如果重载了相应的运算符)。

表 16.12 运算符和相应的函数符

运 算 符

相应的函数符

+

plus

-

minus

*

multiplies

/

divides

%

modulus

-

negate

= =

equal_to

! =

not_equal_to

>

greater

<

less

>=

greater_equal

<=

less_equal

&&

logical_and

logical_or

!

logical_not

警告:

老式 C++实现使用函数符名 times,而不是 multiplies。

16.5.3 自适应函数符和函数适配器

表 16.12 列出的预定义函数符都是自适应的。实际上 STL 有 5 个相关的概念:自适应生成器(adaptable generator)、自适应一元函数(adaptable unary function)、自适应二元函数(adaptable binary function)、自适应谓词(adaptable predicate)和自适应二元谓词(adaptable binary predicate)。

使函数符成为自适应的原因是,它携带了标识参数类型和返回类型的 typedef 成员。这些成员分别是 result_type、first_argument_type 和 second_argument_type,它们的作用是不言自明的。例如,plus<int>对象的返回类型被标识为 plus<int>::result_type,这是 int 的 typedef。

函数符自适应性的意义在于:函数适配器对象可以使用函数对象,并认为存在这些 typedef 成员。例如,接受一个自适应函数符参数的函数可以使用 result_type 成员来声明一个与函数的返回类型匹配的变量。

STL 提供了使用这些工具的函数适配器类。例如,假设要将矢量 gr8 的每个元素都增加 2.5 倍,则需要使用接受一个一元函数参数的 transform( ) 版本,就像前面的例子那样:

multiplies( ) 函数符可以执行乘法运行,但它是二元函数。因此需要一个函数适配器,将接受两个参数的函数符转换为接受 1 个参数的函数符。前面的 TooBig2 示例提供了一种方法,但 STL 使用 binder1st 和 binder2nd 类自动完成这一过程,它们将自适应二元函数转换为自适应一元函数。

来看 binder1st。假设有一个自适应二元函数对象 f2( ),则可以创建一个 binder1st 对象,该对象与一个将被用作 f2( ) 的第一个参数的特定值(val)相关联:

这样,使用单个参数调用 f1(x) 时,返回的值与将 val 作为第一参数、将 f1( ) 的参数作为第二参数调用 f2( ) 返回的值相同。即 f1(x) 等价于 f2(val, x),只是前者是一元函数,而不是二元函数。f2( ) 函数被适配。同样,仅当 f2( ) 是一个自适应函数时,这才能实现。

看上去有点麻烦。然而,STL 提供了函数 bind1st( ),以简化 binder1st 类的使用。可以问其提供用于构建 binder1st 对象的函数名称和值,它将返回一个这种类型的对象。例如,要将二元函数 multiplies( ) 转换为将参数乘以 2.5 的一元函数,则可以这样做:

因此,将 gr8 中的每个元素与 2.5 相乘,并显示结果的代码如下:

binder2nd 类与此类似,只是将常数赋给第二个参数,而不是第一个参数。它有一个名为 bind2nd 的助手函数,该函数的工作方式类似于 bind1st。

程序清单 16.16 将一些最近的示例合并成了一个小程序。

程序清单 16.16 funadap.cpp

程序清单 16.16 中程序的输出如下:

C++11 提供了函数指针和函数符的替代品——lambda 表达式,这将在第 18 章讨论。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文