门户网站建设工作的自查报告,法治建设网站模块名称,上海公司章程在哪里下载打印,HTML和PHP怎么做网站文章目录1. 概念2. 语法3. 示例示例1示例2示例3示例44. 捕捉方式基本方式隐式和混合补充5. 传递lambda表达式示例6. 原理7. 内联属性1. 概念
lambda表达式实际上是一个匿名类的成员函数#xff0c;该类由编译器为lambda创建#xff0c;该函数被隐式地定义为内联。因此#…
文章目录1. 概念2. 语法3. 示例示例1示例2示例3示例44. 捕捉方式基本方式隐式和混合补充5. 传递lambda表达式示例6. 原理7. 内联属性1. 概念
lambda表达式实际上是一个匿名类的成员函数该类由编译器为lambda创建该函数被隐式地定义为内联。因此调用lambda表达式相当于直接调用它的operator()函数这个函数可以被编译器内联优化建议。
例如快速排序算法STL允许用户自定义比较方式在C11之前通常使用仿函数实现。但是代码一旦很长使用之处和仿函数实现的地方相隔甚远而且如果仿函数的命名不规范很容易造成使用上的困难。 仿函数又叫做函数对象functorfunction object因为实现仿函数的方式就是重载一个类的operator()只是用起来跟函数一样其本质依然是一个对象。 2. 语法
C11的lambda表达式是一种允许内联函数的特性它可以用于不需要重用和命名的代码片段。lambda表达式的一般形式是
[capture clause] (parameters) mutable - return-type { function body }[captureclause]捕捉列表。该列表总是出现在lambda函数的开始位置编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。(parameters)参数列表。与普通函数的参数列表一致如果不需要参数传递则可以连同()一起省略。mutable默认情况下lambda函数总是一个const函数mutable可以取消其常量性。使用该修饰符时参数列表不可省略即使参数为空。-return-type返回值类型。用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可以省略。返回值类型明确情况下也可省略由编译器对返回类型进行推导。{statement}函数体。在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量。
以一个简单的例子理解lambda表达式的语法
[]
{cout hello lambda endl;
}这一大坨就表示它是一个类型/对象type、class我们知道在类型后加上()就表示调用这个对象的构造函数**而lambda的语法有些不同在它后面加上括号就表示调用它。**就像
#include iostream
using namespace std;int main()
{[]{cout hello lambda endl;}();return 0;
}输出
hello lambda而一般没有人会这样调用lambda表达式因为这样还不如直接写中间的语句所以可以暂时给它一个名字
auto l []
{cout hello lambda endl;
};l在这里是任意取的名字实际上要根据含义刚才提到方括号和后面一坨东西就相当于一个对象一般情况下我们是不知道它的类型的所以通常用auto接收。
现在最简形式的lambda表达式就写出来了试着调用它
#include iostream
using namespace std;int main()
{auto l []{cout hello lambda endl;};l();return 0;
}输出
hello lambda注意
上面的例子和概念中的“内联”属性并没有关系。lambda表达式在main函数之外定义也不影响结果。lambda表达式的最后有分号。lambda函数的参数列表和返回值类型不是必要的但捕捉列表和函数体是不可省略的。习惯上不写返回值让表达式自动推导。
3. 示例
现在lambda表达式的基本用法已经有大概的了解了下面将介绍它的细节。
示例1
int main()
{int id 0;auto f [id] () mutable{cout id: id endl;id;};id 42;f();f();f();cout id: id endl;
}注在这里先不管mutable。
[id]表示在lambda表达式外部的所有名为id的变量都能被使用()表示这个lambda表达式不用传参函数内部的语句打印变量id的值且累加在更新变量id的值为42后调用3次lambda表达式f最后再打印变量id的值
输出
id:0
id:1
id:2
id:42这与设想的结果应该不同既然变量id的值已经被更新为42那么lambda表达式f打印出的值应该是42、43、44这是为何呢这个问题会在看完所有例子以后揭晓答案。
通过例1最重要的是理解lambda的本质是一个仿函数例1的lambda表达式等价于下面的代码 [id]中没有任何特殊符号表示默认以传值方式捕捉变量也就相当于拷贝一份变量id的副本到这个匿名函数中且捕捉后不再受外界变量id的影响mutable表示可以修改捕捉的变量那么也就可以执行自增语句例1按照语句的执行顺序在执行lambda表达式时变量id的值为0时就已经被捕捉了即使它被更新为42后续调用lambda表达式时变量id仍然从0开始
但是即使lambda表达式内部对变量id自增最终变量id仍然为最后一次修改的值42。
示例2
int main()
{int id 0;auto f [id] (int param){cout id: id , ;cout param: param endl;id;param;};id 42;f(7);f(7);f(7);cout id: id endl;return 0;
}输出
id:42, param:7
id:43, param:7
id:44, param:7
id:45例2和例1的区别去掉了mutable以传引用方式捕捉变量新增参数param。
[id]表示以引用方式捕捉变量id。那么匿名函数内部和外界处理的变量id就是同一个函数内部和外部都会互相影响。因此在调用lambda表达式之前变量id被更新为42仍然会影响lambda表达式内部。参数param是值传递每次传值都是一次临时拷贝。
示例3
int main()
{int id 0;auto f [id] (){cout id: id , ;id; // 编译错误};id 42;f();f();f();cout id: id endl;return 0;
}在自增语句编译错误
error: cannot assign to a variable captured by copy in a non-mutable lambda提示不能赋值给一个非可变的non-mutable变量。结合例1言外之意是mutable的修饰使得lambda表达式能够在内部修改捕捉到的变量。
示例4
int main()
{int id 0;auto f [id] () mutable{cout id: id endl;id;static int x 5;int y 6;return id;};f();return 0;
}此例仅为了说明lambda表达式本质是一个匿名函数底层由仿函数实现所有函数的内部都能定义不同类型的变量也允许有返回值。
4. 捕捉方式
基本方式
Lambda表达式的捕捉方式是指它如何访问外部变量也就是定义在lambda表达式之外的变量。Lambda表达式的最基本的两种捕获方式是按值捕获Capture by Value和按引用捕获Capture by Reference。 按值捕获是指lambda表达式内部保存一份外部变量的副本对这个副本进行操作不会影响原来的变量。 按引用捕获是指lambda表达式内部直接使用外部变量的引用对这个引用进行操作会影响原来的变量。
隐式和混合
除此之外还有两种方式隐式捕获和混合方式。它们是两种更简便的捕获方式它们可以让编译器自动推断需要捕获的外部变量。
隐式捕获
[]表示以值捕获的方式捕获所有外部变量默认成员函数包括this指针[]表示以引用捕获的方式捕获所有外部变量成员函数包括this指针。
混合方式是指在隐式捕获的基础上显式地指定某些变量的不同捕获方式。
混合使用时要求捕获列表中第一个元素必须是隐式捕获或然后后面可以跟上显式指定的变量名和符号。
混合使用时若隐式捕获采用引用捕获则显式指定的变量必须采用值捕获的方式若隐式捕获采用值捕获则显式指定的变量必须采用引用捕获的方式即变量名前加。
一个例子
int main()
{int a 123;int b 456;auto f [, b]() { // 隐式值捕获a显式引用捕获bcout a endl; // 输出123cout b endl; // 输出456b 789; // 修改b的值};f();cout b endl; // 输出789return 0;
}输出
123
456
789补充 []以值捕获是默认状态一般不写。 不允许以相同方式重复捕捉例如[, a]前者已经表示以值捕获所有外部变量后者就重复了。但是[, a]是被允许的因为它们的捕获方式不同。 Lambda表达式的父作用域是指定义lambda表达式的那个作用域通常是一个函数或者一个类。Lambda表达式的块作用域是指lambda表达式本身的作用域它是一个匿名函数可以在其他地方调用。 Lambda表达式可以捕获父作用域中的局部变量但是不能捕获其他作用域或者非局部变量。Lambda表达式捕获父作用域中的局部变量时要求这个变量必须是声明为final的或者实际上是final的即不会被修改。Lambda表达式不能在自己的块作用域中声明和父作用域中同名的参数或者局部变量。 Lambda表达式之间不能相互赋值因为它们的本质是函数对象。即使从文本层面看它们的名字相同但是底层编译器给它们取的名字是不同的。
5. 传递lambda表达式
C传递lambda表达式的方法有以下几种
将lambda表达式转换为相应的函数指针如果没有捕获任何变量。使用std::function作为参数类型它可以接受任何可调用对象包括捕获变量的lambda表达式。例如
void foo(std::functionbool(int) f)
{// 函数动作
}foo([] (int x) // 传递lambda表达式{ return x 0; }
); 将lambda表达式赋值给一个变量然后将这个变量传递给函数。例如
auto add [] (int a, int b) // 将lambda表达式赋值给add变量
{ return a b;
}; int result add(1, 2); // 调用lambda表达式
bar(add); // 传递lambda表达式给变量bar使用decltype来推导出lambda表达式的类型然后将这个类型作为模板参数传递给函数。例如
templatetypename F
void baz(F f)
{// 函数动作
}auto mul [] (int a, int b)
{return a * b;
}; // 定义lambda表达式bazdecltype(mul)(mul); // 传递lambda表达式和它的类型给变量baz示例
下面以第三种方式为例
int main()
{auto cmp [](int a, int b){return a b;};setint, decltype(cmp) s(cmp);return 0;
}这段代码中使用了decltype是为了推断出lambda表达式的类型。decltype是一个C11引入的关键字它可以根据表达式的类型返回一个类型。在这个例子中decltype(cmp)就是lambda表达式的类型它被用作set容器的第二个模板参数指定了set中元素的比较方式。set的第三个模板参数需要一个比较函数类型而lambda表达式本身没有一个固定的类型decltype可以根据lambda表达式的operator()来推导出它的函数类型从而满足set的要求。
如果不使用decltype就需要显式地写出lambda表达式的类型但这很麻烦因为lambda表达式的类型是编译器生成的并没有一个具体的名字。你就需要自己定义一个比较类或者函数并将其作为模板参数传递给std::set。 在这里暂不讨论泛型lambda表达式。 6. 原理
在上文已经提到lambda表达式的本质就是一个函数对象即仿函数下面分别是两个功能相同的lambda表达式和函数对象
class Add
{
public:Add(int base):_base(base){}int operator()(int num){return _base num;}
private:int _base;
};
int main()
{int base 1;// 函数对象Add add1(base);add1(1);// lambda表达式auto add2 [base](int num){return base num;};add2(1);return 0;
}网站链接https://godbolt.org/
通过汇编代码可以看到编译器处理lambda表达式的方式和处理函数对象相同。原因是C的lambda表达式底层实现是一个类它重载了operator()运算符使得它可以像函数一样被调用。这个类还包含了lambda表达式捕获的变量它们可以是值捕获或引用捕获。
一个lambda表达式产生一个临时对象它可以用来初始化一个栈上的变量。这个对象有构造函数和析构函数并且遵循所有C规则。
这就是lambda表达式即使在上层看来名字相同也不能相互赋值的原因编译器在底层给它们各自的函数对象的类起了不同的名字。例如在VS编译器中具体类名为lambda_uuid。 类名的 uuid Universally Unique Identifier是一个用于标识类的唯一标识符。 lambda_uuid即lambda表达式的类型。所以lambda表达式的“匿名”属性是对于使用者而言的对于编译器来说是有名的。
7. 内联属性
C的lambda表达式是一种方便的定义匿名函数对象Go语言中的闭包的方法它可以在调用或作为参数传递给函数的地方直接定义。通常lambda表达式用于封装一些传递给算法或异步函数的代码。
C11中引入了λ表达式它可以用来定义一个**内联 (inline)**的函数作为一个本地的对象或者一个参数。内联函数是一种编译器优化技术它可以避免函数调用的开销提高执行效率。λ表达式可以被编译器自动内联展开从而减少函数调用的次数。但是并不是所有的λ表达式都会被内联这取决于编译器的实现和优化策略。
一般来说lambda表达式会被内联的条件是
lambda表达式作为参数传递给一个内联函数lambda表达式没有被保存或者传递到其他地方lambda表达式没有从更外层的函数返回
如果不满足这些条件可以使用noinline关键字来标记lambda参数表示不要求内联。另外编译器也会根据实现和优化策略来决定是否内联lambda表达式。
如果一个函数被调用的频率很低甚至只有一次那么内联属性使得它在被调用的位置展开能减少调用函数创建栈帧的开销。lambda表达式的内联属性是默认的只要编译器认为它可以在某处展开那么它就像一个内嵌语句一样。