向算法传递函数
谓词(predicate)是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法使用的谓词分为一元谓词(unary predicate,接受一个参数)和二元谓词(binary predicate,接受两个参数)。接受谓词参数的算法会对输入序列中的元素调用谓词,因此元素类型必须能转换为谓词的参数类型。
// 按照单词的长度排序
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
// 在sort算法中调用谓词
sort(words.begin(), words.end(), isShorter);
lambda表达式
find_if
函数接受两个迭代器参数和一个谓词参数。迭代器参数用于指定序列范围,之后对序列中的每个元素调用给定谓词,并返回第一个使谓词返回非0值的元素。如果不存在,则返回尾迭代器。
对于一个对象或表达式,如果可以对其使用调用运算符()
,则称它为可调用对象(callable object)。可以向算法传递任何类别的可调用对象。
一个lambda表达式表示一个可调用的代码单元,类似未命名的内联函数,但可以定义在函数内部。其形式如下:
[capture list] (parameter list) -> return type { function body }
其中,capture list
(捕获列表)是一个由lambda所在函数定义的局部变量的列表(通常为空)。return type
、parameter list
和function body
与普通函数一样,分别表示返回类型、参数列表和函数体。但与普通函数不同,lambda必须使用尾置返回类型,且不能有默认实参。
定义lambda时可以省略参数列表和返回类型,但必须包含捕获列表和函数体。省略参数列表等价于指定空参数列表。省略返回类型时,若函数体只是一个return
语句,则返回类型由返回表达式的类型推断而来。否则返回类型为void
auto f = [] { return 42; }; // 省略参数列表
cout << f() << endl; // 42
lambda可以使用其所在函数的局部变量,但必须先将其包含在捕获列表中。捕获列表只能用于局部非static
变量,lambda可以直接使用局部static
变量和其所在函数之外声明的名字。
// 返回第一个长度大于sz的单词的迭代器
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a) { return a.size() >= sz; });
for_each
函数接受一个输入序列和一个可调用对象,它对输入序列中的每个元素调用此对象。
// 输出长度大于sz的所有单词,已经按长度排序了,wc是第一个大于sz的迭代器
for_each(wc, words.end(),
[] (const string &s) { cout << s << " "; });
lambda捕获和返回
被lambda捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。在lambda创建后修改局部变量不会影响lambda内对应的值。
size_t v1 = 42;
auto f = [v1] { return v1; }; // lambda内是v1的拷贝,不能修改
v1 = 0;
auto j = f(); // 42,v1的修改不影响lambda内的拷贝
lambda可以以引用方式捕获变量,但必须保证lambda执行时变量存在。
size_t v1 = 42;
auto f2 = [&v1] { return v1; }; // 按引用捕获
v1 = 0;
auto j = f2(); // 0,按引用捕获会影响
可以让编译器根据lambda代码隐式捕获函数变量,方法是在捕获列表中写一个&
或=
符号。&
为引用捕获,=
为值捕获。
可以混合使用显式捕获和隐式捕获。混合使用时,捕获列表中的第一个元素必须是&
或=
符号,用于指定默认捕获方式。显式捕获的变量必须使用与隐式捕获不同的方式
// os是隐式的按引用捕获,c是显式的按值捕获
for_each(words.begin(), words.end(),
[&, c] (const string &s) { os << s << c; });
// os是显式的按引用捕获,c是隐式的按值捕获
for_each(words.begin(), words.end(),
[=, &os] (const string &s) { os << s << c; });
默认情况下,对于值方式捕获的变量,lambda不能修改其值。如果希望修改,就必须在参数列表后添加关键字mutable
size_t v1 = 42;
auto f = [v1] () mutable { return ++v1; }; // 加mutable才能修改
v1 = 0;
auto j = f(); // j is 43
对于引用方式捕获的变量,lambda是否可以修改依赖于此引用指向的是否是const
类型。
transform
函数接受三个迭代器参数和一个可调用对象。前两个迭代器参数指定输入序列,第三个迭代器参数表示目的位置。它对输入序列中的每个元素调用可调用对象,并将结果写入目的位置。
transform(vi.begin(), vi.end(), vi.begin(),
[](int i) -> int { if (i < 0) return -i; else return i; });
为lambda定义返回类型时,必须使用尾置返回类型
参数绑定
bind
函数定义在头文件functional
中,相当于一个函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适配原对象的参数列表。一般形式如下:
auto newCallable = bind(callable, arg_list);
其中,newCallable
本身是一个可调用对象,arg_list
是一个以逗号分隔的参数列表,对应给定的callable
的参数。之后调用newCallable
时,newCallable
会再调用callable
,并传递给它arg_list
中的参数。arg_list
中可能包含形如_n
的名字,其中n
是一个整数。这些参数是占位符,表示newCallable
的参数,它们占据了传递给newCallable
的参数的位置。数值n
表示生成的可调用对象中参数的位置:_1
为newCallable
的第一个参数,_2
为newCallable
的第二个参数,依次类推。这些名字都定义在命名空间placeholders
中,它又定义在命名空间std
中,因此使用时应该进行双重限定。
using std::placeholders::_1;
using namespace std::placeholders;
bool check_size(const string &s, string::size_type sz);
auto check6 = bind(check_size, _1, 6); // 绑定第二个参数
string s = "hello";
bool b1 = check6(s); // check6(s) 调用 check_size(s, 6)
bind函数可以调整给定可调用对象中的参数顺序。
// 按长度从小到大排序
sort(words.begin(), words.end(), isShorter);
// 反过来了,按长度从小到大排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1));
默认情况下,bind
函数的非占位符参数被拷贝到bind
返回的可调用对象中。但有些类型不支持拷贝操作。
如果希望传递给bind
一个对象而又不拷贝它,则必须使用标准库的ref
函数。ref
函数返回一个对象,包含给定的引用,此对象是可以拷贝的。cref
函数生成保存const
引用的类。
ostream &print(ostream &os, const string &s, char c);
for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));