C++ Primer笔记——lambda表达式与bind函数
创始人
2024-04-27 04:11:10
0

目录

一.lambda介绍

(一).总体介绍

(二).参数列表

(三).尾置返回

(四).捕捉列表

①值捕捉

②引用捕捉

③隐式捕捉

 (五).lambda与捕捉列表的本质

二. 参数绑定bind

(一).什么情况下会用bind

(二).使用方式

①参数列表 

②参数顺序

③引用类型参数


一.lambda介绍

(一).总体介绍

lambda是C++11所规定的一种新方法。一般用于泛型算法传递自定义函数。

其形式如下:

[ 捕捉列表 ]  ( 参数列表 )  ->返回值类型  { 函数体 }

lambda表达式会返回一个函数对象。

举个例子:

int a = 1, b = 3;
auto f = [](int i, int j)->int {return i + j; };
f(a, b);等价于下列函数
int f(int i, int j)
{return i + j;
}

实际开发中,一般用于给算法传仿函数,比如:

vector a = { 4,3,5,6,2,5,7,8,5,3 };
//按降序排序
sort(a.begin(), a.end(), [](const int i, const int j)->bool { return i > j; });

使用lambda表达式时,有几点需要注意:

1.捕捉列表可以为空

2.尾置返回类型和参数列表可以忽略,即[]{};

3.捕捉列表和函数体不能忽略。

(二).参数列表

使用参数列表时,按照普通函数的参数列表使用即可,唯一的不同是不能有缺省参数。

因此,lambda的实参数量永远与形参保持一致。 

当然,参数可以是引用、指针类型,const修饰也没问题。

(三).尾置返回

与普通函数不同,lambda只能使用尾置返回类型,如果忽略尾置返回,那么编译器会自行推断返回类型,当然这个推断也是有条件的:

1.函数体没有return语句,自动推断为void类型,即无需返回。

2.只有一条return语句,根据return类型推断返回类型。

3.多条return语句,返回void(实际上在vs环境下也会根据return自行推到返回类型)

(四).捕捉列表

顾名思义,捕捉列表就是捕捉已存在的局部变量,因此使用捕捉列表必须捕捉那些已存在且可访问的对象。

同时,即便lambda在函数作用域内,如果没有进行捕捉或列表传参,那么也不能直接使用该变量。

尤其需要注意的是,捕捉列表只能捕捉临时变量,static、全局变量因为具有静态属性不能捕捉。

即声明周期不随函数作用域的变量不能捕捉,因为静态变量本来就可以直接使用自然不需要捕捉。

示例如下:

int i = 2, j = 4;
//捕捉i和j
auto f = [i, j] { return i < j; };
int i = 2;
static int j = 4;
auto f = [i, j] { return i < j; };//错误,j是静态变量

 当然捕捉也分三种情况:

①值捕捉

默认情况下,捕捉列表按照传值的方式捕捉变量,因此,传递给lambda的并不是变量本身,而是变量的一份拷贝,lambda的修改不会影响变量本身。

②引用捕捉

在捕捉参数前加上&即引用捕捉。

在这种情况下,传递给lambda的即是变量本身,当在lambda函数体中进行修改时,变量本身也进行修改。

引用捕捉的常用场景是那些拷贝代价很大的对象,或者不支持拷贝的对象,比如iostream对象就不支持拷贝,只能引用捕捉。

③隐式捕捉

除了可以显式捕捉变量的方式外,还可以使用=或&进行隐式捕捉。

使用=是值捕捉,使用&是引用捕捉。

int i = 0, j = 4;
//将i和j全部按值捕捉
auto f = [=] { return i < j; };
//将i和j全部按引用捕捉
auto f = [&] { return i < j; };

使用时,隐式捕捉可以和显式捕捉(值、引用捕捉)混合使用,但是有如下要求:

1.隐式捕捉必须在捕捉列表第一个位置。

2.定义的隐式捕捉方式即是指定默认捕捉方式,捕捉列表后续参数默认该方式捕捉。

3.后续显式捕捉的方式必须和隐式捕捉不同。

int i = 0, j = 4;
auto f = [=, &j] { return i < j; };//j按引用捕捉,其余全部按值捕捉
auto f = [&, j] { return i < j; };//j按值捕捉,其余全部按引用捕捉
auto f = [&, &j] { return i < j; };//错误
auto f = [=, j] { return i < j; };//错误

 (五).lambda与捕捉列表的本质

《C++ Primer》中指出,lambda本质是一个函数对象,或者是仿函数。默认情况下,只有一个operator()函数,没有默认构造、赋值、析构函数。

当我们使用lambda时,会生成一个未命名对象,使用时通过该对象调用重载的()完成函数功能。

大致如下:

auto f = [](const int& a, const int& b) { return a < b; };
//等价于
class XXXXX {
public:bool operator()(const int& a, const int& b) {return a < b;}
};

当使用捕捉列表时,情况分为两种:

1.按引用捕捉,编译器会将捕捉对象直接在类内使用,无需定义成员。

2.按值捕捉,因为有拷贝的发生,不能直接使用捕捉的对象。

此时,lambda类会定义一个带参的构造函数并在类内定义值捕捉的成员变量。构造函数使用值捕捉对象给成员变量赋值。

auto f = [i, j]{ return i < j; };//值捕捉
class XXXXX {
public:XXXXX(int a, int b)//生成构造函数:i(a), j(b){}bool operator()(const int& a, const int& b) {return a < b;}
private://值捕捉的变量的拷贝int i;int j;
};

lambda是否具有构造、拷贝构造、析构函数,要看具体捕捉成员类型而定。

二. 参数绑定bind

(一).什么情况下会用bind

以find_if函数举例,其传入的第三个是函数对象,且函数参数只能有一个

但加入我们需要两个参数呢,比如想通过比较来判断是否是要找的值呢?

//通过判断i是否大于j,来确定我们要找的值
bool judge(int i, int j) {return i > j;
}vector a = { 4,3,5,6,2,5,7,8,5,3 };
auto x = find_if(a.begin(), a.end(), judge);//错误,judge参数只能唯一

这种情况下,可以通过使用lambda表达式解决,将j传进捕捉列表。

vector a = { 4,3,5,6,2,5,7,8,5,3 };
//lambda方式解决
int j = 7;
auto x = find_if(a.begin(), a.end(), [j](int i) {return i > j; });

但是,如果我们就想用judge函数代替lambda呢😂,使用bing参数绑定。

(二).使用方式

bind函数定义在头文件中。

使用形式为:

auto 返回对象 = bind(函数名, ...);

其中返回类型为_Binder类(vs环境下),但具体函数会有具体的类型,因为_Binder是类模板。

①参数列表 

...省略符所代表的就是函数参数列表,其中需要直接绑定给函数的在bind中给出,需要使用函数传参的使用占位符代替。

占位符在placeholders命名空间中,placeholders又在std中。

占位符使用时如下:

std::placeholders::_1 //1号占位符

以judge为例,使用bind函数方式如下:

int j = 7;
//bind返回值类型使用auto即可
//直接将j绑定到judge第二参数,第一参数需要函数传参
//生成x对象代替judge函数
auto x = bind(judge, std::placeholders::_1, j);
//此时x只需要一个参数即可
auto y = find_if(a.begin(), a.end(), x);

②参数顺序

占位符表示的参数,不受出现顺序限制。

比如如下函数:

bool judge(int i, int j, int z) {return i > j && i > z;
}
int a = 2, b = 3, c = 1;
auto x = bind(judge, std::placeholders::_1, a, std::placeholders::_2);
auto y = bind(judge, std::placeholders::_2, a, std::placeholders::_1);

当调用x和y时可以表示成下面情况:

//x
x(1, 2) == judge(1, a, 2);
//y
y(1, 2) == judge(2, a, 1);

 即_1占位符表示的永远是函数第一个需要传入的参数,与bind中位置无关。

但是bind中直接绑定的参数,与函数中对应位置绑定。

③引用类型参数

如果函数参数是引用类型呢,比如传递iostream对象(禁止拷贝)。

那么bind中需要绑定引用类型参数,就要使用标准库中的ref函数。

int a = 0;
//传a的引用
auto x = bind(judge, std::placeholders::_1, ref(a), std::placeholders::_2);

 参考书籍:

《C++ Primer》P349-P357、P507-P509

一个好的程序员应该是那种过单行线都要往两边看的人———Doug Linder


如有错误,敬请斧正 

相关内容

热门资讯

STL中的队列 STL中包括三种队列:queue、deque和priority_queue。 其中&#...
企业举办发布会展览会议活动如何... 电视作为现代信息社会中最有影响力的媒体,在传达公共政策、引导社会舆论、影响消费者决策等...
【华为机试真题详解】查找单入口... 文章目录 前言题目描述输入描述输出描述示例 1示例 2示例 3示例 4题目解析参考代码 前言 ...
Redis 配置详解 —— 全... 文章目录一、撰文目的二、配置详解1、 EXAMPLE(概要说明)2、IN...
PostgreSQL数据库Ta... 当多个后端在同一个表上运行顺序扫描时,我们尝试使它们保持同步,以减少所需...
【软件项目管理PMP】-- 敏... 1、敏捷团队正处于项目执行中,其中一名项目发起人向产品负责人报告,项目的一个中心部件对用户不重要,团...
单调队列优化的DP问题 概述 单调队列就是通过排除求最值时候的冗余,从而是队列具有性质,可以方便...
Java面试经验分享 面试经验分享奇安信启慧众智中海庭小源科技普罗格云粒科技星辰腾讯DaoCloud网化化工源启科技旷世科...
第27章 SQL CREATE... CREATE TABLE 语句用于创建database中的表。 表由行和列组成,每个表...
【C++】缺省参数 缺省参数1. 缺省的概念和来源2.C++中的缺省参数(默认参数...