一、谓词&向算法传递函数
1. 某些算法其中一个参数可以是谓词
2. 谓词:一个可调用的表达式,其返回结果是一个能用作条件的值
bool cmp(const int &a, const int &b) { return a < b; } sort(vec.begin(), vec.end(), cmp);
- 如函数cmp就是一个谓词(返回能作为条件的bool值,能调用cmp(a, b))
- 接受谓词参数的算法对输入序列中的元素调用谓词,如cmp(vec.begin(), vec.begin()+1)
- 接受谓词参数的sort版本用这个谓词代替<来比较元素
3. 一元谓词&二元谓词:标准库算法或使用一元谓词或使用二元谓词,一元谓词意味着该谓词只能接受一个参数
- 根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须严格接受一个或两个参数
二、lambda表达式
1. 放弃谓词,改用lambda表达式
- 当算法要求使用X元谓词,而实际使用到的谓词接受的参数个数大于X时,我们需要使用lambda表达式
- 如find_if接受一个一元谓词,故传递给find_if的可调用对象必须接受单一参数
2. 可调用对象:可以对该对象使用调用运算符,如e是一个可调用对象,则我们可以编写代码e(args)
- 可调用对象包含:函数、函数指针、lambda表达式、重载了函数调用运算符的类
3. 形式:[捕获列表](参数列表) -> 返回类型 { 函数体 }
- 一个lamda表达式表示一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数
- 可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体,如auto f = [] { return 24; };,f就是可调用对象,可调用它:cout << f() << endl;
- lambda默认返回void类型,除非其函数体只有一条return语句
- lambda不能有默认参数
4. 捕获列表
- lambda只能在其函数体中使用它在捕获列表中捕获的局部变量,但可直接使用局部static变量和全局变量
5. lambda是类?
- 定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型
- 当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象(传递的参数就是此编译器生成的类类型的未命名对象)
- 如用auto定义一个用lambda初始化的变量,就定义了一个从lambda生成的类型的对象,如auto f = [] { return 24; }
- 从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员
6. 值捕获
- 前提:变量可以拷贝
- 被捕获的变量是在lambda创建时拷贝,而不是调用它时拷贝
7. 引用捕获
- 形式:auto f = [&v] { return v; }
- 在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象
- 采用此方式,就必须确保被引用的对象在lambda执行的时候是存在的
8. 隐式捕获
- 让编译器推断要使用哪些变量
- 形式:[=]或[&],前者是值捕获,后者是引用捕获
9. 可变lambda:修改捕获列表中变量的值
- 值捕获:使用mutable关键字
- 引用捕获:引用指向的要是一个非const类型
三、标准库bind函数
1. 放弃lambda表达式,改用函数?
- 在很多地方使用相同的操作,那么应该定义一个函数;此外,一个操作如果需要很多语句才能完成,则应使用函数
- 因为函数接受的参数个数可能有悖于算法,所以就要着手解决该问题
2. bind函数
- 定义在头文件functional中
- 可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表
3. bind调用的形式:auto newCallable = bind(callable, arg_list);
- newCallable是一个可调用对象,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数,如:
bool check_size(const string &s, string::size_type sz) { return s.size() >= sz; } //check6接受一个string类型的参数,并用此string和值6来调用check_size auto check6 = bind(check_size, _1, 6);
- 此bind调用只有一个占位符,表示check6只接受一个参数,占位符为_1,表示check6的此参数对应check_size的第一个参数
- 于是,bool b1 = check6(s)会调用check_size(s, 6)
4. 参数绑定
auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));
- 此bind调用生成一个可调用对象,将check_size的第二个参数绑定到sz的值
- 即bind调用生成的新的可调用对象只接受一个参数,即它是一元谓词,但它的参数列表中有参数绑定到局部变量
5. 名字_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身又定义在std命名空间内
6. bind的参数:可以用bind绑定给定可调用对象中的参数或重新安排其顺序
//g是一个有两个参数的可调用对象 auto g = bind(f, a, b, _2, c, _1);
- 此bind调用生成一个可调用对象g,它有两个参数,传递给它的第一个参数绑定到_1,第二个参数绑定到_2
- 当我们调用g时,其第一个参数将被传递给f作为f作为最后一个参数,第二个参数将被传递给f作为第三个参数
7. 其他
- bind拷贝其参数,故我们不能用bind代替捕获引用的lambda
- 如果我们希望传递给bind一个对象而又不拷贝它,就必须使用标准库ref函数
for_each(words.begin(), words.end(), [&os, c](const string &s) { os << s << c; }); for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));
- 函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的
- 标准库还有一个cref函数,生成一个保存const引用的类