系列文章目录
C++高性能优化编程系列
深入理解软件架构设计系列
高级C++并发线程编程
C++技能系列
期待你的关注哦!!!
现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.
详解使用Lambda表达式
- 系列文章目录
- 一、lambda表达式 - 定义
- 二、lambda表达式 - 捕获列表
- 三、lambda表达式 - 延时调用易出错细节分析
- 四、lambda表达式 - 如何使用mutable
- 五、lambda表达式 - 作为匿名的类类型对象
- 六、lambda表达式 - 在for_each和find_if中使用
- 七、小结
lambda表达式
是C++11引入的一个很重要的的特性,
lambda表达式
也是
一个可调用对象,它定义了一个
匿名函数,并且可以
捕获一定范围内的变量。
一、lambda表达式 - 定义
lambda表达式一般形式:
[捕获列表](参数列表)-> 返回类型 { 函数体; };
auto f = [](int a) -> int{
return a + 1;
}
std::cout << f(1) << std::endl;
(1) 参数列表也可以有默认值:
auto f = [](int a = 6) -> int{
return a + 1;
}
std::cout << f(1) << std::endl;
(2) 没有参数的时候,参数列表可以省略,甚至"()"也可以省略,所以如下代码是合法的:
auto f1 = ()[]{return 1;};
auto f2 = []{return 2;};
std::cout << f(1) << std::endl;
std::cout << f(2) << std::endl;
(3) 捕获列表[ ]和函数体不能省略,必须时刻包含。
(4) lambda表达式的调用方法和普通函数相同,都是使用"( )"这种函数调用运算符。
(5) lambda表达式可以不返回任何类型,返回任何类型就是返回void。
(6) 函数体末尾的分号不能省。
二、lambda表达式 - 捕获列表
lambda表达式
通过捕获列表
来捕获一定范围内的变量
,那么,这个范围
究竟是什么意思呢?
(1) [ ]: 不捕获任何变量
看如下范例:
int i = 9;
auto f1 = []{
//报错(无法捕获外部变量),不认识这个i在哪里定义,
//看来lambda表达式毕竟是匿名函数,按常规理解是不行。
return i;
};
⚠️ 但不包括静态局部变量,lambda可以直接使用静态局部变量。例如,上面的int i = 9;修改为static int i = 9; 是可以在lambda表达式中使用的。
(2) [&]: 捕获外部作用域中所有变量,并作为引用在函数体内使用
看如下范例:
int i = 9;
auto f1 = [&]{
//因为&的存在,允许给i赋值,从而也就改变了i的值
i = 5;
return i;
};
//5,调用了lambda表达式,所以i的发生改变
std::cout << f1() << std::endl;
//5,i值发生改变,现在i=5
std::cout << i < std::endl;
⚠️ 既然引用,那么在调用这个lambda表达式的时候,就必须确保该lambda表达式里的引用的变量没有超过这个变量的作用域(保证有效性)。
(3) [=]: 捕获外部作用域中所有变量,并作为副本(按值)在函数中使用,也就可以用它的值,但不能给它赋值
看如下范例:
int i = 9;
auto f1 = [=]{
//这就非法了,不可以给它赋值,因为是以值方式捕获
//使用该值(返回该值),就可以
//i = 5;
return i;
}
//9, 调用了lambda表达式
std::cout << f1() << std::endl;
⚠️ 不可以给它赋值,因为是以值方式捕获,使用该值(返回该值),就可以。
(4) [this]:一般用于类中,捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限。如果已经使用了"&“或者”=",则默认添加了此项(this项)。也就是说,捕获this的目的就是在lambda表达式中使用当前类的成员函数和成员变量。
看如下范例:
class CT{
public:
int m_i = 5;
void myfuncpt(int x, int y){
//无论用this还是&,=都可以读取成员变量的值
auto mylambda1 = [this]{//是获取不到形参x,y的值的
//有this,这个访问才合法,有&、=也可以
return m_i;
};
std::cout << mylambda1() << std::endl;
}
};
//main函数使用如下:
CT ct;
//5
ct.myfuncpt(3, 4);
⚠️ 针对成员变量,[this] 或者[=]可以读取,但不可以修改,如果想修改,可以使用[&]。
(5) [变量名]:按指捕获 和 [& 变量名]:按引用捕获:
[变量名]:
按值捕获(不能修改)变量名所代表的变量,同时不能捕获其他变量。
[& 变量名]:
按引用捕获(可以修改)变量名所代表的变量,同时不能捕获其他变量。
在前面CT类的myfuncpt成员函数中,因为没有捕获形参x和y的值,所以无法在lambda表达式中使用形参x和y。
如果lambda表达式使用x和y的值,可以如下修改:
//不能在lambda表达式中修改x,y值
auto mylambda1 = [this, x, y]{...};
也可以修改如下这样:
//不能在lambda表达式中修改x,y值
auto mylambda1 = [=]{...};
//可以在lambda表达式中修改x,y值
auto mylambda1 = [&]{...};
对于按引用捕获变量名所代表的变量,看看如下范例:
//只可以使用修改x的值
auto mylambda1 = [&x]{...};
//只可以使用修改x和y的值
auto mylambda1 = [&x, &y]{...};
(6) [=, & 变量名]:按值捕获所有外部变量,但按引用&中所指的变量,这里的=必须写在开头的位置,开头的位置表示默认捕获的方式
看如下CT类的myfuncpt成员函数中的lambda表达式:
auto mylambda1 = [this, &x, y]{
x = 8;
...
return m_i;
}
⚠️ auto mylambda1 = [this, &x, y] 也可以写成 auto mylambda1 = [=, &x]也可以。
(7) [&, 变量名]:按引用捕获所有外部变量,但按值捕获变量名所代表的变量。这里的&必须写在开头的位置,开头的位置表示默认捕获的方式
下面这样是不行的:
//这样不行,开始制定了默认捕获,后来又指定引用捕获,编译器汇报错
auto f = [&, &x]{...}
修改为正确如下:
auto f = [&, x]{...}
三、lambda表达式 - 延时调用易出错细节分析
看如下范例:
int x = 5;
auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了
return x;
};
x = 10;
//5, return的x是5而不是10
std::cout << f() << std::endl;
⚠️ auto f = [=]{ //此时已将外部局部变量值复制一份在lambda表达式中了,所以打印的是5而不是10。
那怎么办呢?
办法是按引用方式捕获:
auto f = [&]{...}
四、lambda表达式 - 如何使用mutable
mutable(易变的)并不陌生,mutable关键字,它的作用就是不管是不是一个常量属性的变量,只要mutable在,就能修改其值。
int x = 5;
auto f = [=]() mutable {
//没有mutable,这个x是不允许修改的
x = 6;
return x;
};
x = 10;
//6, return 的x是6而不是10
std::cout << f() << std::endl;
⚠️ 正常lambda表达式没有参数时()是可以省略的,但是如果要是使用mutable,lambda表达式中就算没有参数时()是也不可以省略()的,必须写出来。
下面是不合法的:
auto f = [=] mutable {...}; //即便没有参数,也不可以把mutable前面的()省略
五、lambda表达式 - 作为匿名的类类型对象
lambda表达式的类型被称为闭包类型。闭包先理解成:函数内的函数(可调用对象)。
这个lambda表达式是一种比较特殊的、匿名的、类型(闭包类)的对象
,也就是说有定义了一个类类型,有生成一个匿名的该类的对象(闭包)。可以认为它是一个带有operator()的类类型对象,也就是仿函数(函数对象)或者说是可调用对象。
所以,也可以使用std::function和std::bind来保存和调用lambda表达式。每个lambda都会出发编译器生成一个独一无二的类类型(及所返回的该类类型对象)。
(1)lambda表达式在std::function的使用
看如下两个范例:
范例1:
std::function<int(int)> fc1 = [](int tv){return tv;}
std::cout << fc1(15) << std::endl; //15
范例2:
std::vector<std::function<bool(int)>> gv;
void func(){
srand((unsigned)time(NULL));
int tmpvalue = rand % 6
gv.push_back([=](int tv){ //如果是引用[&],会不会造成未定义行为?思考一下。
if (tv % tmpvalue == 0)
return true;
return false;
});
}
int main(){
func();
std::cout << gv[0](10) << std::endl;
}
(2)lambda表达式在std::bind的使用
看如下范例:
//bind第一个参数是函数指针,第二个参数开始就是真正的函数参数
std::function<int(int)> fc2 = std::bind([](int tv){return tv;}, std::placeholders::_1);
std::cout << fc2(15) << std::endl; //15
在不捕获任何变量,也就是捕获列表为空(因为类是有this的概念,普通函数是没有这个概念的),lambda表达式可以转换成一个普通的函数指针,看如下范例:
using functype = int (*)(int); //定义一个函数指针类型
functype fp = [](int tv){return tv;};
std::cout << fp(17) << std::endl; //17
六、lambda表达式 - 在for_each和find_if中使用
(1)for_each中lambda表达式
for_each
其实是一个函数模版,一般是用来配合函数对象使用的,第三个参数就是一个函数对象(可以给进去一个 lambda
表达式)。
看如下范例:
std::vector<int> myvector = { 10, 20, 30, 40, 50};
int isum = 0;
std::for_each(myvector.begin(), myvector.end(), [&isum](int value){
isum += value;
std::cout << value << std::endl;
});
std::cout << "sum = " << isum << std::endl;
输出结果:
10
20
30
40
50
sum = 150
(2)find_if中lambda表达式
find_if
其实也是一个函数模版,一般用来查找一个什么东西,要查什么取决于他的第三个参数,第三个参数也是一个函数对象(也可以给进去一个 lambda
表达式)。
看如下范例:
只要返回false, find_if
就不停地遍历myvector,一直返回true为止。
auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){
std::cout << value << std::endl;
//只要返回false, find_if就不停地遍历myvector,一直返回true为止
return false;
});
利用 find_if
返回 true 停止这个特性,就可以寻找myvector中第一个值”>15“的元素。
⚠️ find_if的调用返回一个迭代器,只向第一个满足条件的元素。如果这样的元素不存在,则这个迭代器会指向myvector.end()。
修改后代码如下:
auto result = std::find_if(myvector.begin(), myvector.end(), [](int value){
if (value > 15)
return true;
return false;
});
if (result == myvector.end())
std::cout << "没找到" << std::endl;
else
std::cout << "没找到了, 结果为:" << *result<< std::endl; //找到了,结果为20
七、小结
lambda的优点:
善用lambda,让代码更简洁、更灵活、更强大、提高开发效率、可维护性等。