C++ lambda表达式简介及作用
C/C++的可调用对象
在C语言中,可调用对象仅有函数指针。但在C++中,可调用对象增加了两类,
- 其一是利用C++操作符重载实现的functor,functor本身是一个struct/class的实例,其特殊的地方在于重载了小括号(调用)操作符
- 其二则是C++2.0引入的lambda表达式,也成为匿名函数,其语法为
// lambda
[ <捕获列表> ] ( <参数列表> ) [options] { <函数体> }
// 返回值即为一个lambda表达式,每个lambda表达式在全局范围上都属于特有的一个类别
// 写出类别很难,所以用auto关键字指定收变量的类型
// 譬如
auto add = [] (int lv, int rv) { return lv + rv; };
lambda各部分的格式及作用
- 捕获列表,捕获列表是lambda表达式与普通函数的一个较为明显的区别,主要是用于lambda表达式函数体中使用外层作用域中的变量的情况,捕获可以以值捕获或以引用捕获,效果见名知意。值得注意的是,慎用引用捕获,因为如果lambda表达式被拷贝到作用域外面可能会由于引用对象不存在而发生错误行为。
// 例子
// 实现将外层作用域的一个int值输出
{ // 外层作用域
int val = 10;
auto printVal = [val] { std::cout << val << "\n"; };
printVal();
val = 100;
printVal();
}
输出结果为
10
10
为什么第二行输出不是100呢?
是因为捕获列表中写了“val”,默认为值捕获,如果想要引用捕获则写作[&val],输出则为10 100, 值得注意的是,上述lambda表达式没有写小括号,这是因为在不需要参数的时候,小括号可以省略
- 参数列表,和普通的函数类似,特殊的是没有参数时小括号可以省略。
- [options],可选部分,包括mutable关键字、异常、返回值。mutable关键字用于指定允许修改按值捕获参数,如果省略则按值捕获者不可修改;异常和普通函数的格式类似;返回值一般不用写,但是也可以使用后置返回值指定,类似[] (int s) -> int { return s; }
- 函数体,类似普通函数
lambda表达式结合STL使用
可以接受lambda表达式的STL-algorithm有很多,比如
- std::sort
- std::find_if
- std::transform
- std::remove_if
- std::for_each
- …
凡是能接受一个条件或者操作作为参数传入的STL算法均可传入lambda表达式,例子
// a vector<int> v 逆序排序
std::sort(v.begin(), v.end(), [] ( int n1, int n2 ) { return n1 > n2; });
将lambda表达式作为函数参数
- 将lambda表达式作为函数参数一般借助模板,譬如STL的可接受比较标准的sort(取自gcc)
template<typename _RandomAccessIterator, typename _Compare>
inline void sort(
_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp);
// __comp可以接受lambda表达式,并且也可以接受functor或函数指针
将lambda表达式作为函数返回值
- 在C++11中只能使用std::function指定返回值,将欲返回的lambda表达式转换成function对象,譬如
std::function<void(int)> func() {
return [] (int e) { std::cout << e };
}
// in main-function
func()(10); // STDOUT : 10
- 在C++14中auto关键值指定返回值无需像C++11中使用后置返回值指定,编译器可以推断,所以可以直接返回一个lambda表达式
auto func() {
return [] (int e) { std::cout << e };
}
// in main-function
func()(10); // STDOUT : 10
// g++编译选项添加 -std=c++14
使用lambda表达式代替bind类函数
在C++11之前,标准库提供了bind1st和bind2nd用于将一个二元函数装饰为一元函数,C++11后标准库提供了std::bind函数,将利用占位符(std::placeholders::_1、_2、…)可以对一个函数灵活装饰(改变参数列表的顺序、个数),利用Lambda表达式也可以实现这种效果。
利用Lambda实现bind效果的核心就是利用捕获列表,譬如
// 假如由一个函数bool greater(int lv, int rv),返回是否lv > rv,有这么一个场景,
// 有一个有序容器,一群数字,用这一群数字切分这个有序容器。
// 利用std::find_if
std::vector<int> nums = {-3, 1, 1, 3, 3, 4, 4, 5, 6, 8, 8, 10, 12}; // 待切分的容器
std::vector<int> sliceFlag = {1, 4, 8}; // 切分标志
std::vector<std::vector<int>::iterator> res = { nums.begin() }; // 用来存储切分点
auto greaterThan = [] (int lv) {
return [lv] (int rv) { return greater(lv, rv); };
}; //
for (auto & f : sliceFlag) {
res.push_back( std::find_if(nums.begin(), nums.end(), greaterThan(f)) );
}
res.push_back(nums.end());
// Test
for (int i = 0; i < res.size() - 1; ++i) {
std::for_each(res[i], res[i + 1], [] (int e) { std::cout << e << ", "; });
std::cout << "\n";
}
/****************************STDOUT********************************/
PS ~~~> .\Test
-3, 1, 1,
3, 3, 4, 4,
5, 6, 8, 8,
10, 12,
将函数签名相同的lambda表达式存入容器
C风格的可调用对象即函数指针作为元素存入容器时比较简单的,但是对于Lambda表达式,每个表达式都属于一个特有的类型,为了将很多lambda存入容器需要借助std::function,函数签名同为Type(args…)的Lambda可以转化为std::function<Type(arg…)>,然后再将其都存入元素类型为std::function的容器即可,例子
int main () {
// input 一个string,
// 格式为num1[+-*/]num2, ( “1+2”, “32/20" . . . )
std::map<std::string, std::function<int(int, int)>> opts { // 将Lambda存入容器形成”命令集“
{"+", [] (int lv, int rv) { return lv + rv; }},
{"-", [] (int lv, int rv) { return lv - rv; }},
{"*", [] (int lv, int rv) { return lv * rv; }},
{"/", [] (int lv, int rv) { return lv / rv; }}
};
std::string exp;
while (true) {
std::getline(std::cin, exp);
if (exp == "exit") break;
int lv = atoi(exp.c_str()); // 运算符左值
auto iter = std::find_if(exp.begin(), exp.end(), [] (char ch) -> bool { return !isdigit(ch); } );
int rv = atoi(exp.c_str() + (iter - exp.begin()) + 1); // 运算符右值
std::string op; op.push_back(*iter);
std::printf("--> %d\n", opts.at(op)(lv, rv));
}
return 0;
}
// Test
PS \\\\> ./Test
1+1
--> 2
2/3
--> 0
456+9544
--> 10000
12*12
--> 144
1-90
--> -89
Qt中槽函数直接使用Lambda
有时候,connect到的槽函数仅作为槽函数使用,没有必要封成一个member,使用Lambda是一个较好的选择,这种情况下,原来形式的connect的第3个参数(接受者)就没有用了。
// in constructor of class MainWidget(继承自QWidget)
// 有一个QPushButton的指针, 名字为 pExitButton
connect(pExitButton, &QPushButton::clicked, [this] { close(); }); // 需要注意的是,要捕获this
注: 有时候可能要在QT的.pro文件中的CONFIG += C++11