一文带你学会C++11新特性之Lambda表达式
·
Lambda教程
一、基本语法
lambda表达式是C++11最重要也是最常用的特性之一,这是现代编程语言的一个特点,lambda表达式有如下的一些优点:
- 声明式的编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
- 简洁:避免了代码膨胀和功能分散,让开发更加高效。
- 在需要的时间和地点实现功能闭包,使程序更加灵活。
lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式简单归纳如下:
[capture-list](parameters) opt -> return type {function boby;};
其中capture-list是捕获列表,parameters是参数列表,opt是函数选项,return type是返回值类型,function boby是函数体。
- [capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节在第二部分我们再细讲。捕捉列表为空也不能省略。
- (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略。
auto f = [](){return 1;} // 没有参数, 参数列表为空
auto f = []{return 1;} // 没有参数, 参数列表省略不写
- opt :函数选项, 不需要可以省略。
- mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
- exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
- return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- function body:函数体,函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,这部分不能省略,但函数体可以为空。
下面是一个简单的lambda的示例代码:
int main()
{
// ⼀个简单的lambda表达式
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl; //输出3
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto func1 = []
{
cout << "hello world" << endl; //输出hello world
return 0;
};
func1();
int a = 0, b = 1;
auto swap1 = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};
swap1(a, b);
cout << a << ":" << b << endl; //输出 1:0
return 0;
}
二、捕获列表
lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想用外层作用域中的变量就
需要进行捕捉。具体使用方法如下:
- [] - 不捕捉任何变量
- [&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
- [=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
- 拷贝的副本在匿名函数体内部是只读的
- [=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
- [bar] - 按值捕获 bar 变量, 同时不捕获其他变量
- [&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
- [this] - 捕获当前类中的this指针
- 让lambda表达式拥有和当前类成员函数同样的访问权限
- 如果已经使用了 & 或者 =, 默认添加此选
- lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态
局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使
用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。 - 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。
- 在捕捉列表中混合使用按值捕捉和引用捕捉。[=, &x]表示其他变量按值捕捉,x引用捕捉;[&, x, y]表示其他变量引用捕捉,x和y按值捕捉。当使用混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。
下面是一个示例代码:
#include <iostream>
#include <functional>
using namespace std;
int x = 0;
// 捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = []()
{
x++;
};
class Test
{
public:
void output(int x, int y)
{
auto x1 = [] {return m_number; }; // error
auto x2 = [=] {return m_number + x + y; }; // ok
auto x3 = [&] {return m_number + x + y; }; // ok
auto x4 = [this] {return m_number; }; // ok
auto x5 = [this] {return m_number + x + y; }; // error
auto x6 = [this, x, y] {return m_number + x + y; }; // ok
auto x7 = [this] {return m_number++; }; // ok
}
int m_number = 100;
};
int main()
{
int a=0,b=1;
auto func=[a,&b]
{
b++;
int ret=a+b; // 值捕捉的变量不能修改,引用捕捉的变量可以修改
return ret;
}
return 0;
}
- x1:错误,没有捕获外部变量,不能使用类成员 m_number
- x2:正确,以值拷贝的方式捕获所有外部变量
- x3:正确,以引用的方式捕获所有外部变量
- x4:正确,捕获this指针,可访问对象内部成员
- x5:错误,捕获this指针,可访问类内部成员,没有捕获到变量x,y,因此不能访问。
- x6:正确,捕获this指针,x,y
- x7:正确,捕获this指针,并且可以修改对象内部变量的值
三、返回值
很多时候,lambda表达式的返回值是非常明显的,因此在C++11中允许省略lambda表达式的返回值。
// 完整的lambda表达式定义
auto f = [](int a) -> int
{
return a+10;
};
// 忽略返回值的lambda表达式定义
auto f = [](int a)
{
return a+10;
};
一般情况下,不指定lambda表达式的返回值,编译器会根据return语句自动推导返回值的类型,但需要注意的是labmda表达式不能通过列表初始化自动推导出返回值类型。
// ok,可以自动推导出返回值类型
auto f = [](int i)
{
return i;
}
// error,不能推导出返回值类型
auto f1 = []()
{
return {1, 2}; // 基于列表初始化推导返回值,错误
}
四、函数本质
我们来剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的:
- lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
- 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。
mutable选项的作用就在于取消operator()的const属性。因为lambda表达式在C++中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操作lambda表达式:
#include <iostream>
#include <functional>
using namespace std;
int main(void)
{
// 包装可调用函数
std::function<int(int)> f1 = [](int a) {return a; };
// 绑定可调用函数
std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);
// 函数调用
cout << f1(100) << endl;
cout << f2(200) << endl;
return 0;
}
五、Lambda的应用
- 在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义⼀个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。
- lambda 在很多其他地⽅用起来也很好⽤。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
// 构造函数
Goods(const char* str, double price, int evaluate)
: _name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
// 仿函数:按价格升序比较
struct ComparePriceLess
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price < gr._price;
}
};
// 仿函数:按价格降序比较
struct ComparePriceGreater
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price > gr._price;
}
};
// 辅助函数:打印商品列表
void PrintGoods(const vector<Goods>& v)
{
for (const auto& goods : v)
{
cout << "名称:" << goods._name
<< " 价格:" << goods._price
<< " 评价:" << goods._evaluate << endl;
}
cout << "-------------------------" << endl;
}
int main()
{
vector<Goods> v = {
{ "苹果", 2.1, 5 },
{ "香蕉", 3, 4 },
{ "橙子", 2.2, 3 },
{ "菠萝", 1.5, 4 }
};
cout << "原始商品列表:" << endl;
PrintGoods(v);
// 1. 用仿函数按价格升序排序
sort(v.begin(), v.end(), ComparePriceLess());
cout << "按价格升序(仿函数):" << endl;
PrintGoods(v);
// 2. 用仿函数按价格降序排序
sort(v.begin(), v.end(), ComparePriceGreater());
cout << "按价格降序(仿函数):" << endl;
PrintGoods(v);
// 3. 用lambda按价格升序排序(替代ComparePriceLess)
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
cout << "按价格升序(lambda):" << endl;
PrintGoods(v);
// 4. 用lambda按价格降序排序(替代ComparePriceGreater)
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price > g2._price;
});
cout << "按价格降序(lambda):" << endl;
PrintGoods(v);
// 5. 用lambda按评价升序排序(无需额外写仿函数)
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate < g2._evaluate;
});
cout << "按评价升序(lambda):" << endl;
PrintGoods(v);
// 6. 用lambda按评价降序排序(无需额外写仿函数)
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate;
});
cout << "按评价降序(lambda):" << endl;
PrintGoods(v);
return 0;
}
更多推荐
所有评论(0)