10.3 定制操作

文章目录

    • 向算法传递函数
      • 谓词
      • 排序算法
    • lambda表达式
      • 可调用对象
      • 介绍lambda
      • find_if 和 find_each的介绍
    • lambda的捕获和返回
      • 值捕获
      • 引用捕获
      • 隐式捕获
      • 可变lambda
      • 指定lambda返回的类型
      • 函数体
    • 参数绑定
      • 标准库bind函数
      • 占位符_n
      • 具体使用
      • bind的参数
      • 使用bind重排参数顺序
      • 绑定引用参数

向算法传递函数

可以重载sort的默认排序操作:

sort(beg,end,comp);
partition(beg,end,comp);//符合条件在前半段,不符合在后半段

此处的comp,我们将其称之为谓词。

谓词

谓词分为两种,一元谓词和二元谓词,分别接受一个参数和两个参数。
谓词可调用表达式,返回结果是作为条件的值。

//sort中可以设计一个二元谓词,重载sort函数,改变排序方法
	bool isshorter(const string& s1, const string& s2) {
		return s1.size() < s2.size();
	}
	sort(strs.begin(), strs.end(), isshorter);
	//partition接收一元谓词
	bool letterbiggerthanh(char a) { 
	return a > 'h';
	}
	string s = { "hhdyzuhyaaa" };
	partition(s.begin(), s.end(), letterbiggerthanh);
	cout << s << endl; //yuzydhhhaaa改变字典序
	s = { "hhdyzuhyaaa" };
	stable_partition(s.begin(), s.end(), letterbiggerthanh);
	cout << s << endl; //yzuyhhdhaaa不改变字典序

排序算法

stable_sort内部实现是归并排序,sort是快速排序。
stable_sort算法是稳定排序算法,维持了相等元素的原有顺序

lambda表达式

对于函数,严格接收一元或二元谓词,然而考虑到有些操作可能需要更多的参数,引入lambda表达式。

lambda表达式是可调用的代码单元,可以理解为未命名的内联函数。

可调用对象

对一个对象或表达式,可以对其使用调用运算符(一对圆括号()),则称它是可调用的。调用运算符中防置实参列表。

可调用对象有:函数、函数指针、重载调用运算符的类、lambda表达式等。

介绍lambda

对于函数,严格接收一元或二元谓词,然而考虑到有些操作可能需要更多的参数,引入lambda表达式。

lambda表达式是可调用的代码单元,可以理解为未命名的内联函数。

lambda的形式:

> [capture list](parameter list) ->return type{function body}

其中,capture list (捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空); return type、parameter list和function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。但是,与普通函数不同,lambda 必须使用尾置返回来指定返回类型。

我们可以进行这么一个理解,()内部的值是算法默认传入的参数,我们如果需要额外的参数,就需要[],内部传入捕获的值。

比如我们可以完成对于上面sort的改写:

sort(strs.begin(), strs.end(), [](const string& s1, const string& s2){return s1.size() < s2.size()});

find_if 和 find_each的介绍

> find_if(beg,end,comp)  //找第一个符合条件的,返回其迭代器。
> for_each(beg,end,comp) //此算法接受一个可调用对象(comp),并对身处beg-end之间的元素调用此可调用函数。

lambda的捕获和返回

当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。我们将在之后介绍这种类是如何生成的。目前,可以这样理解,当向一个函数传递一一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。

默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。

值捕获

#include

using namespace std;

void func()
{
	int val = 42;
	auto f = [val]()->void {cout << val; };
	val = 0;
	f();
}

int main() 
{
	func();
	return 0;
}

输出结果:
10.3 定制操作_第1张图片
由于被捕获变量的值是在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值。

引用捕获

#include

using namespace std;

void func()
{
	int val = 42;
	auto f = [&val]()->void {cout << val; };
	val = 0;
	f();
}

int main() 
{
	func();
	return 0;
}

输出结果:
10.3 定制操作_第2张图片
对于引用捕获,常用于一些无法进行拷贝的地方,如ostream,无法进行拷贝。
例子:

void biggies(vector<string>& words, vector<string>::size_type sz, ostream& os = cout, char c = ' ')
{
	for_each(words.cbegin(), words.cend(), [&os, c](const string& s) {os << s << c; });
}

如果我们捕获一个指针或迭代器,或采用引用捕获方式,就必须确保在lambda 执行时,绑定到迭代器、指针或引用的对象仍然存在。而且,需要保证对象具有预期的值。在lambda从创建到它执行的这段时间内,可能有代码改变绑定的对象的值。也就是说,在指针(或引用)被捕获的时刻,绑定的对象的值是我们所期望的,但在lambda执行时,该对象的值可能已经完全不同了。

隐式捕获

除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。

例子:

void biggies(vector<string>& words, vector<string>::size_type sz, ostream& os = cout, char c = ' ')
{
	//os隐式捕获,引用捕获,c显式捕获,值捕获
	for_each(words.cbegin(), words.cend(), [&, c](const string& s) {os << s << c; });
	//os显式捕获,引用捕获,c隐式捕获,值捕获
	for_each(words.cbegin(), words.cend(), [=, &os](const string& s) {os << s << c; });
}

当我们混合使用隐式捕获和显式捕获时,捕获列表中的第一一个元素必须是一个&或=。此符号指定了默认捕获方式为引用或值。

当混合使用隐式捕获和显式捕获时,显式捕获的变量必须使用与隐式捕获不同的方式。即,如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因此不能在其名字前使用&。类似的,如果隐式捕获采用的是值方式(使用了=),则显式捕获命名变量必须采用引用方式,即,在名字前使用&。

构造捕获列表方式如下:

构造方式 说明
[ ] 空捕获列表。不使用lambda所在函数里的局部变量
[names] 逗号分隔开的名字列表,是lambda所在函数里的局部变量,默认背靠背,如果名字前加了&,则表示引用捕获
[&] 隐式捕获列表,引用捕获方式。
[=] 隐式捕获列表,值捕获方式。
[&,identifier_list] 混合使用隐式捕获和显式捕获。隐式捕获为引用捕获或,显示捕获为值捕获
[=,identifier_list] 混合使用隐式捕获和显式捕获。隐式捕获为值捕获或,显示捕获为引用捕获

可变lambda

对于值被拷贝的变量,lambda不会改变其值(不是左值),如果希望能改变一个被捕获变量的值,要在参数列表首加上mutable。

int v1 = 42;
auto f = [v1]()mutable {return ++v1; }; //加mutable,v1可以修改,v1被拷贝传递进去,此时值为42
v1 = 0;
cout << f() << endl; //43
cout << v1 << endl; //0
v1 = 42;
auto f3 = [v1](){return ++v1; }; //报错,v1不是可修改左值
v1 = 0;
cout << f3() << endl;
cout << v1 << endl;
v1 = 42;
auto f2 = [&v1]() {return ++v1; }; //引用v1
v1 = 0;
cout << f2() << endl; //1
cout << v1 << endl; //1
return 0;

指定lambda返回的类型

lambda必须使用尾置返回。

当编写lambda只包含单一return语句时,编译器可以推算返回类型。当lambda函数体包含除return之外任何语句时,编译器假定此lambda返回void。

	transform(s.begin(), s.end(),s.begin(), [c](char s_c) {return (s_c > 'h'? s_c : 'h'); });
	std::cout << s << endl;
	transform(s.begin(), s.end(), s.begin(), [](char s_c) ->char{if (s_c > 'h') return s_c; else return 'h'; });
	std::cout << s << endl;
	transform(s.begin(), s.end(), s.begin(), [](char s_c) {if (s_c > 'h') return s_c; else return 'h'; });  //这个版本按理应当错误,但在vs2022可以通过
	std::cout << s << endl;

函数体

只对lambda所造函数中定义的非static的局部变量使用捕获列表,lambda可以直接使用局部static变量和它所在函数之外声明的名字,如定义在头文件中的名字。

参数绑定

lambda表达式不能有默认参数,lambda调用实参数目与形参数目相等

lambda适用于在少数地方使用,且函数体语句数目少的情况。

其他情况,如果lambda捕获列表为空,可以用函数替代。

如果使用find_if调用check_size函数,考虑find_if只接受一元谓词,需要解决向sz形参传递参数的问题。可以使用bind函数。

标准库bind函数

bind函数可以看作一个通用函数适配器,他接受一个可调用对象并生成一个新的可调用对象适应原对象参数列表。

头文件:

#include 

函数形式:调用newCallable时,newCallable调用callable,向callable传递arg_list中的参数

auto newCallable = bind(callable,arg_list); 
newCallable:可调用对象
callable:目标调用对象
arg_list:逗号分隔参数列表,对应callable参数。

占位符_n

arg_list中可能出现_n,其中n是整数,表示可调用对象中参数位置。这个参数是占位符,占据该位置,可以在函数调用时输入该参数。

名字_n定义在std::placeholders命名空间中,该命名空间在头文件#include中。使用_n需要分别对对应名字进行声明,或直接声明对应命名空间:

using std::placeholders::_1; //分别对对应名字进行声明
using namespace std::placeholders; //直接声明对应命名空间

建议直接使用后者。

具体使用

//eg:bind绑定check_size,并使用find_if调用
bool check_list(const string& a, int b) {
	return a.size() > b;
}
int main()
{
	vector<string> strs = {...};
	int sz = 5;
	//placeholders::_1指向传入参数const string& a,sz指向传入参数int b
	//对于checksz来说此时他仅仅接受一个参数,也就是placeholders::_1,也就是仅支持一元谓词
	auto checksz = bind(check_list, placeholders::_1, sz);
	auto iter = find_if(strs.begin(), strs.end(), checksz);
}

bind的参数

auto g = (f,a,b,_2,c,_1);
实际上调用g(x,y)
传入的数据是f(a,b,y,c,x)

使用bind重排参数顺序

	//从短到长进行排列。
	sort(words.begin(),words.end(),isShorter());
	//从长到短进行排列。
	sort(words.begin(),words.end(),bind(isShorter,_2,_1))

绑定引用参数

bind中不是占位符的参数被拷贝到bind返回的可调用对象中,如果希望采用引用的方式传递,必须使用标准库ref或cref函数。

所在头文件:#include
ref:采用引用方式传递参数。
cref:生成保存const引用的类。

例子:

ostream& print(ostream& os ,const string& s ,char c)
{
	return os << s << c;
}

for_each(words.begin(),words.end(),bind(print,ret(os),_1,' '))

你可能感兴趣的:(C++,算法,c++)