什么是C++:auto = []语法
C++中的Lambda表达式通常用于封装算法,以便将其传递给另一个函数。然而,Lambda表达式也可以在定义时立即执行。这使得Lambda表达式成为重构复杂函数的强大工具。你可以将代码块包装在Lambda函数中,然后逐步进行参数化的过程,并在每个步骤之后进行中间测试。一旦你将代码块完全参数化(即删除了符号“&”),你可以将代码移动到外部位置并将其变为普通函数。
此外,你还可以使用Lambda表达式根据算法的结果初始化变量。Lambda表达式还可以作为参数传递给另一个Lambda表达式,用作分割程序逻辑的一种方式。
Lambda表达式还允许创建具有名称的嵌套函数,这是一种避免重复逻辑的便捷方式。使用命名Lambda表达式在将非平凡函数作为参数传递给另一个函数时,也比使用匿名内联Lambda表达式更容易阅读。
如果后续的性能分析发现函数对象的初始化开销很大,可以选择将其重写为普通函数。
Lambda表达式在一些场景中也被称为立即调用函数表达式(Immediately Invoked Function Expression,IIFE)。这种模式可以用于创建词法作用域。
Lambda表达式可以通过将其赋值给一个对象来命名。这个对象可以是std::function类型,你可以在其中存储任何可调用对象,并在以后调用它。
,Lambda表达式在C++中提供了一种灵活且强大的方式来封装算法、参数化代码块、初始化变量以及创建具有名称的嵌套函数。它们为重构复杂函数和分割程序逻辑提供了一个有用的工具。
【原文中包含的代码已经包括在文章中】
C++中的lambda函数是从lambda演算和函数式编程中衍生出来的概念。lambda是一个无名函数,在实际编程中非常有用,用于无法重用且不值得命名的短代码片段。
在C++中,lambda函数的定义如下:
[]() { } // 最简单的lambda
或者完整写法为:
[]() mutable -> T { } // T是返回类型,还缺少throw()
其中,[]
是捕获列表,()
是参数列表,{}
是函数体。
捕获列表定义了在lambda函数体内部应该访问外部的内容以及如何访问。
它可以是以下几种形式之一:
- 一个值:[x]
- 一个引用 [&x]
- 通过引用捕获当前范围内的任何变量 [&]
- 与3相同,但通过值捕获 [=]
你可以在逗号分隔的列表中混合使用以上任意形式的捕获方式,例如[x, &y]
。
参数列表与其他任何C++函数的参数列表相同。
函数体是在实际调用lambda时执行的代码。
如果一个lambda只有一个返回语句,返回类型可以省略,并且隐式类型为decltype(return_statement)
。
如果一个lambda被标记为mutable(例如[]() mutable { }
),它被允许修改以值方式捕获的变量的值。
标准定义的库从lambda中受益匪浅,大大提高了可用性,因为现在用户不再需要在某个可访问的作用域中使用小型函数对象来减少代码的复杂性。
在C++14中,lambda通过各种提案得到了扩展。
初始化的lambda捕获允许使用=
对捕获列表中的元素进行初始化。这样可以对变量进行重命名,并通过移动进行捕获。以下是从标准中摘取的一个示例:
int x = 4; auto y = [&r = x, x = x+1]()->int { r += 2; return x+2; }(); // 将::x更新为6,并将y初始化为7。
还有一个来自维基百科的示例,展示了如何使用std::move
进行捕获:
auto ptr = std::make_unique<int>(10); // std::make_unique请参考下文 auto lambda = [ptr = std::move(ptr)] {return *ptr;};
现在lambda可以是泛型的(auto
相当于在周围作用域中的类型模板参数T
):
auto lambda = [](auto x, auto y) {return x + y;};
C++14允许对每个函数进行推断返回类型,不再限制于return expression;
形式的函数。这个特性也适用于lambda函数。
在你上面提到的初始化的lambda捕获的示例中,为什么在lambda函数的末尾加上了();?这看起来像是[](){}();
而不是[](){};
。此外,x的值不应该是5吗?
1)()的作用是在定义lambda后立即调用它,并给y赋值它的返回值。变量y是一个整数,而不是lambda本身。2)不,x=5是局部于lambda的(通过值方式捕获,刚好与外部作用域的x同名),然后x+2 = 5+2被返回。外部变量x的重新赋值是通过引用r进行的:r = &x; r += 2;
,但这是针对原始值4进行的。
你好,通过说“任何当前作用域内的变量”,是什么意思?它是否意味着全局范围内的所有全局变量以及此函数中的任何局部变量都会被捕获?
我在文档中看到已经添加了Throw:learn.microsoft.com/en-us/cpp/cpp/…
C++中包含了一些非常有用的泛型函数,比如std::for_each和std::transform,它们非常方便。然而,它们在使用上也可能非常麻烦,尤其是当你想要应用的函数对象是特定于特定函数的时候。
如果你只在特定的地方使用了一个函数对象,并且它似乎不值得为了一个微不足道的、一次性的事情而编写一个完整的类,那么在C++03中,你可能会尝试写出类似下面的代码,以保持函数对象的局部性:
void func2(std::vector& v) { struct { void operator()(int) { // do something } } f; std::for_each(v.begin(), v.end(), f); }
然而,这是不允许的,f无法在C++03中传递给一个模板函数。
C++11引入了lambda表达式,允许你编写一个内联的、匿名的函数对象来替代struct f。对于简单的例子来说,这样做可以使代码更清晰(将所有内容放在一起)并且更易于维护,例如:
void func3(std::vector& v) { std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ }); }
Lambda函数只是匿名函数对象的语法糖。
在简单的情况下,lambda的返回类型由编译器推导,例如:
void func4(std::vector& v) { std::transform(v.begin(), v.end(), v.begin(), [](double d) { return d < 0.00001 ? 0 : d; } ); }
然而,当你开始编写更复杂的lambda函数时,你很快就会遇到编译器无法推导返回类型的情况,例如:
void func4(std::vector& v) { std::transform(v.begin(), v.end(), v.begin(), [](double d) { if (d < 0.0001) { return 0; } else { return d; } }); }
为了解决这个问题,你可以显式地为lambda函数指定返回类型,使用-> T语法:
void func4(std::vector& v) { std::transform(v.begin(), v.end(), v.begin(), [](double d) -> double { if (d < 0.0001) { return 0; } else { return d; } }); }
除了在lambda内部使用传递给它的参数之外,我们还可以使用其他变量。如果你想访问其他变量,你可以使用捕获子句(lambda表达式中的[]),例如:
void func5(std::vector& v, const double& epsilon) { std::transform(v.begin(), v.end(), v.begin(), [epsilon](double d) -> double { if (d < epsilon) { return 0; } else { return d; } }); }
你可以通过引用和值两种方式来捕获变量,分别使用&和=来指定:
- [&epsilon, zeta]:通过引用捕获epsilon,通过值捕获zeta
- [&]:通过引用捕获lambda中使用的所有变量
- [=]:通过值捕获lambda中使用的所有变量
- [&,epsilon]:通过引用捕获lambda中使用的所有变量,但通过值捕获epsilon
- [=,&epsilon]:通过值捕获lambda中使用的所有变量,但通过引用捕获epsilon
lambda生成的operator()默认是const的,这意味着默认情况下捕获的变量是const的。这意味着每次相同的输入调用时都会产生相同的结果。但是,你可以使用mutable关键字将lambda标记为mutable,以请求生成的operator()不是const。
在使用lambda作为变量时,你可以使用std::function
需要注意的是,在将lambda存储在可能超出引用范围的变量中时,使用基于引用的捕获需要谨慎使用。这在引用中始终是一个问题。
最后,需要注意的是在三元运算符中的隐式提升规则。例如,return d < 0.00001 ? 0 : d;中,由于?:运算符的隐式提升规则,保证了返回类型是double,即使一个操作数是整数常量。将0替换为0.0可能会使示例更容易理解。
在C++03中,这是不允许的,f无法在C++03中传递给一个模板函数。