C++ 函数调用运算符与可调用对象
函数调用运算符
函数调用运算符用“operator ()”表示。如果类重载了函数调用运算符,可以像使用函数一样使用该类对象。
例如,像下面一样重载了某个struct或class的operator(),其对象就能像函数一样调用。
// 类重载operator()
struct absInt { // class一样可以
int operator()(int val) const { // 定义运算符operator(), 求val绝对值
return val < 0 ? -val : val;
}
// ... 其他成员函数或者成员变量声明(如果有的话)
};
// “调用”该类对象
absInt obj; // 函数对象
int res = obj(-10); // 传递参数10给obj.operator(), res值为10
[======]
函数对象
函数对象基本概念
如果类定义了operator()(函数调用运算符),那么这样的类的对象称为函数对象(function object),其调用行为类似于调用函数。
函数对象跟普通对象没有本质区别,也可以有自己的成员变量和成员函数。
例如,定义class PrintString
// 定义函数对象对应的类
class PrintString {
public:
PrintString(ostream& o = cout, char c = ' ') : os(o), seperator(c) { }
void operator()(const string &s) const { os << s << seperator; } // 定义operator()
~PrintString(){}
private:
ostream &os; // 输出流
char seperator; // 不同输出分隔符
};
/* 使用函数对象 */
string s = "test";
PrintString printer; // 定义函数对象, 使用构造函数默认值, 打印到cout, 分隔符为' '
printer(s);
PrintString errors(cerr, '\n'); // 定义函数对象, 打印到cerr, 分隔符'\n'
errors(s);
// 打印一个数组
for_each(vec.begin(), vec.end(), PrintString(cerr, '\n'));
[======]
lambda与函数对象
lambda本质是匿名函数对象,其产生的类中含有一个重载的函数调用运算符。
例如,对vec数组进行排序。下面的lambda表达式[](const int a, const int b) { return a < b;}
其实是一个函数对象
#include <algorithm>
vector<int> vec;
// ... // 往vec添加数据
// lambda表达式
stable_sort(vec.begin(), vec.end(), [](const int a, const int b) {
return a < b;
});
// <=> 等价于(函数指针)
stable_sort(vec.begin(), vec.end(), compareFunc);
bool compareFunc(int a, int b)
{
return a < b;
}
// <=> 等价于(函数对象)
struct Compare
{
bool operator()(int a, int b)
{
return a < b;
}
};
Compare cobj;
stable_sort(vec.begin(), vec.end(), cobj);
[======]
lambda值捕获与函数对象
通过值捕获的局部变量,在lambda对应类中,相当于private数据成员。
// 在words数组中, 找到第一个满足条件(lambda表示返回true)的元素的迭代器. 条件是对应元素的size() >= sz
auto wc = find_if (words.begin(), words.end(), [sz](const string& a) {
return a.size() >= sz;
});
// 该lambda对应类
class SizeComp {
public:
SizeComp(size_t n) : sz(n) { }
bool operator()(const string& s) const {
return s.size() >= sz;
}
private:
size_t sz;
};
[======]
标准库定义的函数对象
标准库已经定义了一组表示算术运算符、关系运算符、逻辑运算符的类,每个类各自定义了一个执行类名称对应操作的运算符。
比如,plus类定义了operator()用于加法运算;modulus类定义了opeartor()用于求%运算;equal_to类定义了operator()用于判断2个参数是否相等,等等。
这些类都被定义成了模板形式,例如,其使用方法见下:
#include <functional>
// 定义函数对象
plus<int> intAdd; // 可执行加法运算的函数对象
negate<int> intNegate; // 可对int值加上负号的函数对象
greater<int> comp1; // 执行比较运算的函数对象
less<int> comp2; // 执行比较运算的函数对象
// 调用函数对象
int sum = intAdd(1,2); // 使用intAdd::operator(int,int)对1和2求和, sum值为3
sum = intNegate(intAdd(3,4)); // sum值为-7
bool res1 = comp1(1,2); // false
bool res2 = comp2(1,2); // true
在算法中使用标准库函数对象
比如,使用sort进行排序
vector<string> vec;
// ... 向vec插入数据
// 传入临时函数对象, 用于2个string对象的 > 比较运算
sort(vec.begin(), vec.end(), greater<string>()); // 降序排序
// 传入临时函数对象, 用于2个string对象的 < 比较运算
sort(vec.begin(), vec.end(), less<string>()); // 升序排序
可调用对象与function
C++可调用的对象包括:函数、函数指针、lambda表达式、bind创建的对象、重载了函数调用运算符(operator())的类。
- 调用形式
可调用的对象也有自己的类型,2个不同类型的可调用对象可能共享一种调用形式(call signature)。一种调用形式对应一个函数类型,指明了返回类型、参数列表,如:
int (int, int)
返回类型为int;参数列表int,int,即接受2个int参数。
- 不同类型可调用参数,具有相同的调用形式
多种不同类型的可调用参数,可能具有相同的调用形式。例如,
// 普通函数
int add(int i, int j) { return i + j; }
// lambda表达式
auto s = [](int i, int j) -> int{ return i + j; }
// 函数对象类
struct sum {
int operator()(int i, int j) {
return i + j;
}
};
标准库function类型
在C中,当需要一个函数指针时,可以直接传函数或者函数指针,但是无法传lambda表达式或者函数对象类,因为后者并不是函数指针类型。
C++中的function能解决这个问题,代表任意一种C++可调用对象。
function是模板,当创建一个具体的function类型时,必须提供额外信息。
// C的使用函数指针的做法
typedef int (*AddFunc)(int , int);
AddFunc handle = add;
int s = handle(1,2); // 3
// C++使用function的做法
function<int(int, int)> f1 = add; // 函数指针
function<int(int, int)> f2 = sum(); // 函数对象
function<int(int, int)> f3 = [](int i, int j){ return i + j; }; // lambda
int s1 = f1(3, 4); // 7
int s2 = f2(3, 4); // 7
int s3 = f3(3, 4); // 7
[======]