【C++Primer】泛型编程之定制操作(谓词、lambda表达式、参数绑定bind)

定制操作

C++允许我们自己决定算法的操作方式,这就叫做定制操作

1、 向算法传递函数

向算法传递函数是通过谓词完成的,
谓词
是什么:一个可调用的表达式(如同平常的“<”, “>”, “==”)
返回结果:其返回结果是一个能 用作条件的值(这个条件可以用来判断元素的删、改、排序等操作)
类型:标准库算法所使用的谓词可分为两类:一元谓词和二元谓词。
例子:为了使排序先按单词的长度,再按字母表中的顺序进行排序,故采用接受一个二元谓词参数的sort版本用 这个谓词 代替 “<” 来比较元素。完成这个操作的函数为isShorter()。

//比较函数,用来按长度排序单词
bool isShorter(const string &s1, const string &s2)
{
    return s1.size()<s2.size();
}

//按长度由短至长排序words
sort(words.begin(), words.end(), isShorter);	

2、 lambda表达式

C++Primer中这一节根据解决“完整的biggies”这一方式介绍lambda表达式,其中调用find_if、for_each算法都是为了解决这一项目的补充,看这一节要抓住主线!

为什么要用到lambda表达式呢?(或者什么情况下用它呢?)[来自知乎:Lambda 表达式有何用处?]
在某处就真的只需要一个能做一件事情的函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。Lambda 表达式就可以用来做这件事。利用Lambda表达式,可以方便的定义和创建匿名函数

2.1 lambda简介

一个lambda表达式表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数

lambda函数与普通函数类似:
与普通函数相同点:具有一个返回类型、一个参数列表、一个函数体
与普通函数不同点:①lambda可能定义在函数内部 ②lambda必须使用尾置返回

lambda函数形式:

[capture list] (params list) mutable exception-> return type { function body }

各项具体含义如下

  1. capture list:捕获外部变量列表
  2. params list:形参列表
  3. mutable指示符:用来说用是否可以修改捕获的变量
  4. exception:异常设定
  5. return type:返回类型
  6. function body:函数体

此外,可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种:

序号 格式

序号格式
1[capture list] (params list) -> return type {function body}
2[capture list] (params list) {function body}
3[capture list] {function body}

其中:

  1. 格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值。
  2. 格式2在格式1基础上省略了返回类型,编译器根据函数题内是否有return语句进行推断返回类型或viod类型。
  3. 格式3为最简化形式,必须永远包含捕获列表和函数体。

2.2 使用捕获列表

[capture list] 即为捕获列表, 为捕获外部变量的列表。
芮苒一个lambda可以出现在一个函数中,使用其局部变量(该局部变量也为lambda的外部变量),但它只能使用那些明确指明的变量。也就是使用 [capture list]来指明lambda的内部可以使用的变量,这一过程称过Lambda表达式“捕获”了外部变量。
捕获外部变量分为以下几种:
1、值捕获
类似参数传递中的值传递,采用值捕获的
前提
是变量可以拷贝。
与参数传递不同的是,被捕捉的变量的值是在lambda创建时拷贝,而不是调用时拷贝:

void fcn1()
{
	size_t v1 =42; //局部变量
	//将v1拷贝到名为f的可调用对象
	auto f = [v1]{return v1;};
	v1 = 0;
	auto j = f(); //j为42;f保存了我们创建它时v1的拷贝
}

由于被捕获变量的值实在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值。

2、引用捕获
使用引用捕获一个外部变量,只需要在捕获列表变量前面加上一个引用说明符&。代码如下:

void fcn2()
{
	size_t v1 =42; //局部变量
	//将v1拷贝到名为f的可调用对象
	auto f = [v1]{return v1;};
	v1 = 0;
	auto j = f(); //j为0;f2保存v2的引用,而非拷贝
}

注意:①当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。②引用捕获的变量使用的实际上就是该引用所绑定的对象。③尽量减少捕捉数据量,如果有可能的话,应该避开捕捉指针和引用。

3、隐式捕获
上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。

隐式值捕获示例:

int main()
{
    int a = 123;
    auto f = [=] { cout << a << endl; };    // 值捕获
    f(); // 输出:123
}

隐式引用捕获示例:

int main()
{
    int a = 123;
    auto f = [&] { cout << a << endl; };    // 引用捕获
    a = 321;
    f(); // 输出:321

总结:

捕获形式说明
[]不捕获任何外部变量
[names]names是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量
[&]以引用形式捕获所有外部变量
[=]以值的形式捕获所有外部变量
[=, &x]变量x以引用形式捕获,其余变量以传值形式捕获
[&, x]变量x以值的形式捕获,其余变量以引用形式捕获

3、参数绑定

①如果lambda 的捕获列表为空,通常可以用函数来代替它。
②若捕获列表不为空,不容易用函数替换,
某些函数只接受一元谓词,导致重复编写lambda,为解决这个问题引进的bind函数。

对于那种只在一两个地方使用的简单操作, lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数, 而不是多次编写相同的lambda表达式。类似的,如果一个操作需要很多语句才能完成,通常使用函数更好,于是会出现了bind函数。

3.1 bind函数简介

头文件:functional
性质:一个函数模板,类似一个函数适配器,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
函数原型:std::bind返回一个基于 f 的函数对象,其参数被绑定到args上。

template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
 
template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );

bind的参数:假定f是一个可调用对象,它有5个参数,则bind的调用为:

//g是一个有两个参数的可调用对象
auto g = bind(f, a, b, _2, c, _1);//将 _2(第三个参数)和_1(第五个参数)传递给 f
								//f的第一个、第二个和第四个参数分别绑定到给定的值a、b和c上

对于g参数来说,其形式为g(_1, _2),映射为f(a, b, _2, c, _1),即g(X, Y) = f(a, b, Y, C, X);

原理:通过bind函数,可以将多个可调用对象绑定到一个或几个参数上。上例中就是将多个可调用参数绑定到Args上。

auto newCallable = bind(callable, arg_list); //arg_list是一个逗号分隔符的参数列表

arg_list多个参数绑定到callable上,当其他函数调用newCallable时,newCallable会调用callable。

参考文献
[1] C++Primer第五版
[2] C++ 11 Lambda表达式(作者:dsw846)
[3] C++11新特性:参数绑定——std::bind (作者:___Blue_H)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值