一、谓词&向算法传递函数

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引用的类