可调用对象

可调用对象有函数,函数指针,lambda表达式,函数对象, bind创建的对象等.
函数和函数指针不必多说.那么什么是函数对象呢?函数对象是一种重载了调用运算符的类类型.因为重载了调用运算符,所以我们可以像调用函数一样调用类的对象,因为称为函数对象.lambda表达式表示一个可调用的代码单元.

函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* absInt类只定义了一种操作:函数调用运算符
* 此类为函数对象
*/
struct absInt {
int operator()(int val) const {
return val < 0 ? -val : val;
}
};

int main(){
absInt absObj;
cout << absObj(42) << endl; // 42
cout << absObj(-10) << endl; // 10
return 0;
}

以上代码即定义了一个函数对象.调用该对象就像调用函数一样.仿佛该类型对象是一个函数.
还可以定义有数据成员的函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PrintString {
public:
PrintString(ostream &o = cout, char c = ' ') : os(o), sep(c) {}

void operator()(const string &s) const { os << s << sep; }

private:
ostream &os;
char sep;
};
int main(){
PrintString printer;
printer("hello"); // 输出hello
PrintString errors(cerr, '\n');
string s = "wrong answer";
errors(s); // 输出wrong answer
return 0;
}

lambda表达式

lambda表达式在很多语言中都有.lambda可以理解为一个未命名的内联函数,但是与普通函数不同的是,lambda可能定义在函数内部;lambda表达式必须尾置返回; lambda表达式不能有默认参数; lambda表达式的参数列表和返回类型可以省略. 那么在c++中该怎么定义一个lambda表达式呢?
一个lambda表达式具有以下形式:

1
[capure list](parameter list)-> return type {function body}

其中,捕获列表(capure list)是该lambda表达式所在函数中定义的局部变量(通常为空).
参数列表(parameter list), 返回类型, 返回类型(return type)和函数体(function body)与普通函数相同.

当定义一个lambda时,编译器生成一个与lambda对象的新的(未命名的)类类型.当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象.

1
2
3
4
5
6
vector<string> vc{"good", "to", "see", "you", "again"};
for_each(vc.begin(), vc.end(), PrintString(cout, '\n')); // 函数对象

// 等价的lambda表达式
string sep("\n");
for_each(vc.begin(), vc.end(), [sep](const string &s) { cout << s << sep; });

使用函数对象打印出可变数组vc中的元素.同时定义了一个与函数对象PrintString等价的lambda表达式.

捕获变量

与参数传递类似,捕获变量也有两种方式,值捕获和引用捕获.采用值捕获需要变量可以拷贝.与参数传递不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝.引用捕获需要保证lambda表达式执行时,该引用指向的对象仍然存在.

在一个基本类型为string类型的可变数组中找出第一个长度大于给定值sz的元素.下面分别用函数对象和lambda表达式的形式实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class SizeCmp {
public:
SizeCmp(size_t n) : sz(n) {}

bool operator()(const string &a) { return a.size() >= sz; }

private:
size_t sz;
};

int main(){
vector<string> vc{"good", "to", "see", "mango", "again", "day", "nana"};

size_t sz = 6;
// 使用lambda表达式
// auto wc = find_if(vc.begin(), vc.end(), [sz](const string &a) { return a.size() >= sz; });

// 使用等价的函数对象
auto wc = find_if(vc.begin(), vc.end(), SizeCmp(sz));

// wc 为指向第一个长度不小于给定值sz的元素的迭代器
if (wc != vc.end())
cout << (*wc) << endl;
else {
cout << "empty result" << endl;
} //当sz=5时,输出mango;当sz=6时,输出empty result
}

bind参数绑定

标准库的bind函数可以看做一个函数适配器.它接受一个可调用对象,生成一个新的可调用对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool check_size(const string& s, string::size_type sz){
return s.size() >= sz;
}

// 给check_size函数的第一个参数绑定一个值
// auto check6 = bind(check_size, std::placeholders::_1, 6);
auto check6 = bind(check_size, std::placeholders::_1, 5);

// 调用check6函数
string s = "hello";
cout << check6(s) << endl;

vector<string> vc{"good", "to", "see", "mango", "again", "day", "nana"};
auto wc = find_if(vc.begin(), vc.end(), check6);
// wc 为指向第一个长度不小于给定值sz的元素的迭代器
if (wc != vc.end())
cout << (*wc) << endl;
else {
cout << "empty result" << endl;

可调用对象与function

每一个lambda表达式都是一种未命名的新类型;函数对象的类型是该对象的类类型;函数和函数指针的类型由参数列表和返回值决定.

多个调用对象有不同的类型,但是却共享相同的调用形式.调用形式指明了调用返回的类型以及传递给调用的实参类型(如何SizeCmp类型的函数对象和lambda表达式,类型不同,调用形式相同).一种调用形式对应一个函数类型.标准库中function模板可以表示函数类型.

1
2
3
4
5
6
7
8
9
10
11
12
map<string, function<int(int, int)>> binops = {
{"+", add},
{"-", std::minus<int>()},
{"/", Divide()},
{"*", [](int i, int j) { return i * j; }},
{"%", mod}
};

cout << binops["+"](10, 5) << endl; // 15
cout << binops["-"](10, 5) << endl; // 5
cout << binops["%"](11, 4) << endl; // 3
cout << binops["*"](13, 2) << endl; // 26

add, Divide(),mod等各个可调用对象的类型各不相同,但是它们都是调用形式为int(int, int)的可调用对象,因此可以赋值给function<int(int,int)>类型的对象.