函数对象:定义了调用操作符()的类对象。当用该对象调用此操作符时,其表现形式如同普通函数调用一般,因此取名叫函数对象。举个最简单的例子:
- class A
- {
- public:
- int operator() ( int val )
- {
- return val > 0 ? val : -val;
- }
- };
类A中定义了操作符 ( ),A对象调用语句在形式上跟以下函数的调用完全一样:
- int i = -1;
- A func;
- cout << func(i);
与普通函数相比,函数对象比函数更加灵活,函数对象的优势:
- 函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态;
- 函数对象有自己特有的类型。我们可以传递相应的类型作为参数来实例化相应的模板,比如说带参数的函数形参。
使用函数对象的典型例子:
1、字符串排序规则
在容器set 中对string 进行排序,首先来定义相应的类并定义 () 来规定排序规则:
- class Sort
- {
- public:
- bool operator() (const string &str1, const string &str2) const //带两个参数
- {
- return str1 > str2;
- }
- };
然后我们可以用这个类作为参数来初始化set容器:
- set<string, Sort> myset; //带比较函数的set构造函数,并用函数对象Sort初始化
- myset.insert("A");
- myset.insert("B");
这样容器内容输出为:B,A。
2、谓词函数
谓词函数通常用来对传进来的参数进行判断,并返回布尔值。但是一般的函数形式固化,比如字符串长度比较只能判断是否大于一个确定的长度值。函数对象可以作为谓词函数,并可以在类初始化时传递参数,如字符串长度参考值,因此函数对象比普通函数更加灵活。
现在假设我们有一串数字,要从中找出第一个不小于10的数字。可以定义如下相应的类:
- class Upper
- {
- public:
- Upper(int min = 0):m_min(min){}
- bool operator() (int value) const
- {
- return value >= m_min;
- }
- private:
- int m_min;
- };
从而这样调用 find_if 函数:
find_if( dest.begin(), dest.end(), Upper(10) );
首先生成类 Upper 的对象,并用 10 初始化,调用find_if 时将用该函数对象进行判断。
请注意:在调用用到函数对象的标准库算法时,除非显式地指定模板类型为传引用,否则默认情况下函数对象是按值传递的!因此,如果传递一个具有内部状态的函数对象,则被改变状态的是函数内部被复制的临时对象,函数结束后随之消失。真正传进来的函数对象状态并为改变。
- class B
- {
- public:
- B(int n=0) : th(n),count(1) {}
- bool operator() (int)
- {
- return count++ == th;
- }
- int getCount() const
- {
- return count;
- }
- private:
- int th;
- int count;
- };
测试如下:
- vector<int> vec;
- for( int i = 3; i!= 13; ++i )
- vec.push_back(i);
- B b(3);
- vector<int>::iterator iter = find_if( vec.begin(), vec.end(), b ); //调用函数对象,查找第三个数字
- cout<< "3rd:" << *iter <<endl;
- cout<< "State:" << b.getCount() <<endl; //指向函数对象内容,但内部值却为改变
输出结果为,确实能找到第三个数字(5),但查看b的状态时,返回的 count 依然为0,说明:
- 在find_if 函数执行期间,内部状态的临时对象发生改变;
- 在函数对象调用完成后,临时对象消失,原来的状态保持不变。
原则:
不是所有的返回布尔值的函数对象都适合作为谓词函数,因此用作谓词函数的函数对象,最好不要依赖其内部状态的改变。
标准库定义的函数对象
标准库定义了一组算术、关系与逻辑函数对象类。标准库还定义了一组函数适配器,使我们能够特化或者扩展标准库所定义的以及自定义的函数对象类。这些标准库函数对象类型是在 functional 头文件中定义的。
plus函数对象的应用:
- plus<int> intAdd; // intAdd为函数对象
- int sum = intAdd(10, 20); // 使用函数对象相加两个操作数
- plus<string> stringAdd; //
- string strs = stringAdd("ab", "12"); // 使用函数对象相加两个操作数
函数对象的函数适配器
适配器分为如下两类:
1. 绑定器,是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
2. 求反器,是一种函数适配器,它将谓词函数对象的真值求反。
绑定器
标准库定义了两个绑定器适配器:bind1st 和 bind2nd。每个绑定器接受一个函数对象和一个值。
- bind1st 将给定值绑定到二元函数对象的第一个实参;
- bind2nd 将给定值绑定到二元函数对象的第二个实参。
为了计算一个容器中所有小于或等于 10 的元素的个数,可以这样给 count_if 传递值:
count_if( vec.begin(), vec.end(), bind2nd( less_equal<int>(), 10 ) );
传给 count_if 的第三个实参使用 bind2nd 函数适配器,该适配器返回一个函数对象,该对象用 10 作右操作数应用 <= 操作符。这个 count_if 调用计算输入范围中小于或等于 10 的元素的个数。
求反器
标准库还定义了两个求反器:not1 和 not2。
- not1 将一元函数对象的真值求反;
- not2 将二元函数对象的真值求反。
为了对 less_equal 函数对象的绑定求反,可以编写这样的代码:
count_if( vec.begin(), vec.end(),not1( bind2nd(less_equal<int>(), 10 ) ) );
首先将 less_equal 对象的第二个操作数绑定到 10,实际上是将该二元操作转换为一元操作。再用 not1 对操作的返回值求反,效果是测试每个元素是否 <=。然后,对结果真值求反。这个 count_if 调用的效果是对大于 10 的那些元素进行计数。