玩命加载中 . . .

10.3-定制操作


向算法传递函数

谓词(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 typeparameter listfunction 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表示生成的可调用对象中参数的位置:_1newCallable的第一个参数,_2newCallable的第二个参数,依次类推。这些名字都定义在命名空间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, ' '));

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录