Lambda表达式
什么是Lambda表达式
C++11的颁布让C++丰富了起来,任何一本介绍C++11的书籍,都不可能跳过这一个点——Lambda表达式。人们经常称Lambda表达式是一个语法糖,说明这是一个”没有没事,有了更好“的一种语法表达,当然我觉得还有另一种含义——“小用怡情,大用伤身”。
在代码实现中,我们可能会遇到这样一种情况,需要一个函数,这个函数的功能极其简单,而且很有可能也只使用这一次,为了它特地返回用来写函数的代码区没必要,又没有合适的头文件放,这个时候就可以拿出来Lambda表达式了,用一种很简介的方式来满足这种临时需求。
Lambda的定义:
[capture list] (parameter list) mutable exception -> return type { function body }
请不要抓狂,这个只是看起来复杂,实际上用起来,就是这么复杂,但是一旦用会了,就是语法糖。首先我们先看看我们需要什么,毕竟是一个函数,首先得有参数列表,然后得有函数体,以及返回类型,很明显上面都有,这就理解三个部分了,那剩下的中有一个exception
已经被建议弃用了,这个也不用知道了,最难理解的就是这里的capture list
和mutable
。
Lambda表达式的用法
首先,捕获列表是用来得到当前作用域的变量值,比如我这个函数可能需要作用于前面刚创建的某一个变量,那它就应该放在这里面,即捕获
,说实话,这是一个不太好理解的概念,先上代码:
#include <iostream>int main() {int x = 10;auto f = [x]{std::cout << x << std::endl;};f();
}
这里我们在捕获列表中捕获了当前作用域的变量x
并输出,这时候肯定有人要站出来问,那这和参数列表有啥区别,我用参数列表输出不是一样的嘛,上代码:
#include <iostream>int main() {int x = 10;auto f = [x]{std::cout << x << std::endl;};auto g = [](int x){std::cout << x << std::endl;};f(); //10g(x); //10x = 5;f(); //10g(x); //5
}
从这里的代码可以看出,捕获列表只会捕获到Lambda表达式定义时外部变量的状态,其不会随着外部变量的状态的变化而变化,所以参数列表和捕获列表发挥的功能是完全不一样的。
继续上面介绍捕获列表,捕获列表中捕获方式有两种,一种按值捕获,一种按引用捕获。(看过前面引用的文章相信一下子就能理解)。简单来说,按值捕获不会影响外部变量的值,但是按引用会影响,上代码:
#include <iostream>int main() {int x = 10;// auto f = [x]{x = 5;std::cout << x << std::endl;}; // 报错:无法分配给 只读类型const intauto g = [&x]{x = 5; std::cout << x << std::endl;}; //编译通过g(); // 输出:5std::cout << x << std::endl; //输出:5return 0;
}
当然,如果你想将所有的值(或大部分)都按值捕获,或者按引用捕获,可以采取这样的办法:
#include <iostream>int main() {int x = 10, y = 5, z = 3;auto f = [=]{ std::cout << x + y + z << std::endl; };auto g = [&]{x = 3, y = 7, z = 9; };f(); //输出:18g(); std::cout << x << " " << y << " " << z << std::endl; //输出: 3 7 9return 0;
}
如果你想对其他变量按值捕获,对某些变量按引用捕获,也是有办法的:
#include <iostream>int main() {int x = 10, y = 5, z = 3;auto f = [=, &z]{ z = 4; std::cout << x + y + z << std::endl; };f();
}
反之亦可。那么又有人问了,如果我想一半按值捕获,另一半按引用捕获呢?
前面我们讲完了捕获列表,这里我们理解一下mutable
关键字。其实一句话就能说明白,就是对按值捕获的变量允许修改,上面的代码我们看到了,在编译器中,按值捕获的列表是const
类型的,无法在函数体内修改,使用mutable
关键字,可以运行函数体内修改,当然啦,还是不影响外部变量的,上代码:
#include <iostream>int main() {int x = 10, y = 5, z = 3;auto f = [=, &z] mutable{ z = 4; x += 1; std::cout << x + y + z << std::endl; };f(); // 输出:20;return 0;
}
Lambda表达式其实是什么
在有一些编译器中,Lambda表达式会被直接编译成匿名类。我们上代码看看二者是如何转换的,我们这里定义一个稍微复杂一点的Lambda表达式,里面包含返回类型、参数、mutable
关键字:
#include <iostream>class __lambda_uniq {
public:int x;int operator()(int a){x += 5;return a + x;}
};int main() {int x = 10;auto f = [=](int a)mutable ->int{x += 5; return a + x;};std::cout << f(5) << std::endl;__lambda_uniq u;u.x = x;std::cout << u(5) << std::endl;
}
这里就展示了一种可能的转换,将Lambda表达式转换成匿名类,其实功能是基本相同的,甚至我们可以想象,当没有mutable
关键字时,这里的成员x
可能就是const
修饰了。很明显,Lambda表达式的表达方式更简洁,所以以后如果需要临时函数,可以想到Lambda表达式,这样做一定程度上可以提高代码可读性,毕竟为一个简单的函数确实没必要再找它的定义。
但是,Lambda表达式应该熟用而不应该滥用,它可以点缀我们的代码,简化我们的操作,却不应该大量存在。