一、谓词
谓词是一个调用表达式,其返回结果是一个能用做条件的值。
标准库算法为此分为两类:
1、一元谓词(意味着只能接受单一参数)
2、二元谓词(意味着他们有两个参数)
接受谓词的算法对输入序列中的元素调用谓词。因此元素类型必须能转换为谓词的参数类型。
以sort和isShorter举例
eg:
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size();
}
sort(word.begin(), words.end(), isShorter);
二、lambda表达式
问题背景:根据算法接受一元谓词还是二元谓词,我们传递给算法的为此必须严格接受一个或两个参数,当我们想用find_if判断一个string长度是否大于一个给定长度,我们需要传入一个string::size_type sz的参数,以及string,但是find_if只接受一元谓词,为了解决这个问题,我们引入lambda表达式。
介绍lambda:一个lambda表达式表示一个可调用的代码单元。我们可以理解为一个未命名的内联函数。其包括一个返回类型,一个参数列表和一个函数体。
[capture list](parameter list) -> return type{function body} //基本形式
//可以忽略参数列表和返回类型,但当我们想制定返回类型时,必须使用该形式中所用的尾置返回
1、捕获列表:
使用函数中定义的局部变量,且必须是那些明确指定的变量。
捕获方式分为三种:值捕获、引用捕获、隐式捕获。
首先是值捕获:
采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值实在lambda创建时拷贝,而不是调用时拷贝:
void fcn1(){
size_t v1 = 42; //局部变量
//将v1拷贝到名为f的可调用对象
auto f = [v1]{return v1;};
v1 = 0;
auto j = f(); //j为42,f保存了我们创建时的拷贝
}
接着介绍引用捕获:
一个以引用方式捕获的变量与其他任何引用行为类似。这里只谈问题和限制。如果我们引用捕获了一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。
由于lambda捕获的都是局部变量,加入lambda在函数结束后执行,则其捕获的变量已经不存在了。
引用捕获有时候是必要的,比如在接受一个流参数时候,因为流无法拷贝,只能采取引用的策略。
void fcn2(){
size_t v1 = 42; //局部变量
auto f = [&v1]{return v1;};
v1 = 0;
auto j = f(); //j为0,f保存的是引用而不是拷贝
}
使用注意:
尽量保持lambda的变量捕获简单化。确保lambda每次执行的时候这些信息都有与其意义是程序员的责任。
一般来说我们应该尽量减少捕获的数据量,来避免捕获导致的潜在的问题。同时,如果可能的话我们应该尽量避免捕获指针和引用。
最后介绍隐式捕获:
除了显示的列出我们希望使用的变量外,我们还可以让编译器根据lambda体中的代码来推断我们要使用的哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用引用捕获的方式,=表示采用值捕获方式。
wc = find_if(words.begin(), words.end(), [=](const string &s){return s.szie() >= sz;});
也可以混用隐式捕获和显示捕获,当我们混用时候捕获列表的第一个元素必须是一个&或=,同时第二个元素必须与第一个元素类型不同。
for_each(words.begin(), words.end(), [=,&os](const string &s){os << s << c;});
2、可变lambda
默认情况下对于一个值拷贝的变量lambda不改变其值,但当我们想改变时候需要在参数列表首加上关键字mutable。因此可便lambda能省略参数列表。
void fcn3(){
size_t v1 = 42; //局部变量
//f可以改变所捕获变量的值
auto f = [v1] () mutable {return ++v1;};
v1 = 0;
auto j = f(); //j为43
}
引用捕获变量是否可以修改依赖于此引用指向的是一个const类型,还是一个非const类型:
void fcn4(){
size_t v1 = 42; //局部变量
//v1为非const变量引用
auto f = [&v1]{return ++v1;};
v1 = 0;
auto j = f(); //j为1
}
3、指定lambda返回类型
默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。
因此当我们写如下语句时会报错
transform(vi.begin(), vi.end(), vi.begin(), [](int i) {if (i < 0) return -i;else return i; });
所以此时我们需要使用尾置返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i) ->int {if (i < 0) return -i;else return i; });
4、向lambda传递参数
与普通函数不同,lambda不能有默认参数,因此lambda调用的实参数目与形参数目相等。
//isShorter
[](const string &a, const string &b){return a.size() < b.size();}