C++ Lambda语法详解

在C++11及更新的版本中引入Lambda表达式的支持。在C++中可以这么理解Lambda:它是用于在C++中定义匿名函数方式。它的功能类似于C++的函数对象(仿函数),但是相较于函数对象,Lambda定义起来语法更简单。本文将对其语法以及在不同版本的C++中的支持情况做介绍。

语法结构

lambda_exp_syntax
  1. 参数捕获列表(必选):用于设置Lambda表达式用到的外部需要以何种方式被捕捉。
  2. 参数列表(可选):此处是Lambda自身的参数列表,语法与函数的参数列表定义方式一样。
  3. mutable规范(可选):用于修改按值捕获的变量的方式(默认捕获为 const)。
  4. throw()异常类型(可选):用于表明是否会抛异常以及抛异常的类型。
  5. -> return type(可选): 用于设置Lambda返回的数据类型(类似函数的返回值类型定义)。
  6. Lambda主体(必选):等价于函数体的实现。

参数捕捕获列表([])

Lambda定义是以“参数捕获列表”子句([])开头的,它用于定义Lambda以何种方式捕获其所在作用域内被Lambda体用到的变量。

捕获变量的方式总体可以分以下两种(值或引用):

  • [&] : 表示通过引用的方式捕获当前作用域下被Lambda体使用到的所有变量。
  • [=] : 表示通过值的方式捕获当前作用域下被Lambda体使用到的所有变量。

参数捕获列表归纳有以下几种用法

  1. 空捕获

    • 语法:[]
    • 含义:不捕获任何外部变量,Lambda体内部无法访问外部作用域的变量。
    • 示例:
    
     auto func = [] { cout << "不捕获任何变量"; };
    
  2. 按值捕获

捕获变量的副本,lambda 内部修改不会影响外部原变量。

  • 按值捕获部分变量

    • 语法:[var, var1](var, var1为外部变量名,只写需要被捕获的变量名)
    • 含义:只有在捕获列表内的变量才会被Lambda捕获,且只有被捕获的变量才可以在Lambda体内访问。
    • 示例:
    
              int x = 10;
              int y = 0;
              int z = 0;
    
              // 仅捕获x和y的副本,z不可以在Lambda体内被访问。
              auto func = [x, y] { cout << x << y; };
    
    • 按值捕获所有变量

      • 语法:[=]
      • 含义:按值捕获 lambda 内部使用的所有外部变量(隐含捕获,无需显式列出)。
      • 示例:

        
            int a = 1;
            int b = 2;
        
            // 按值捕获 a 和 b
            auto sum = [=] { return a + b; }; 
        
  1. 按引用捕获

    捕获变量的引用,lambda 内部修改会直接影响外部原变量(需确保变量生命周期长于 lambda)。

    • 按引用捕获部分变量

      • 语法:[&var1, &var2](var1,var2 为外部变量名,只写需要被捕获的变量名)
      • 含义:只有在捕获列表内的变量才会被Lambda捕获,且只有被捕获的变量才可以在Lambda体内访问。
      • 示例:
      
        int x = 10;
      
        // 捕获 x 的引用,修改会影响外部
        auto func = [&x] { x++; }; 
      
        // 外部 x 变为 11
        func();
      
    • 按引用捕获所有变量

      • 语法:[&]
      • 含义:按引用捕获Lambda内部使用的所有外部变量(隐含捕获)。
      • 示例:
      
            int a = 1;
            int b = 2;
      
            // 按引用捕获 a 和 b
            auto increment = [&] { a++; b++; };
      
  2. 混合捕获(按值 + 按引用)

    结合 = 或 & 作为默认捕获方式,再显式指定个别变量的捕获方式(覆盖默认)。

    • 默认按值捕获,个别变量按引用捕获。

      • 语法:[=, &var1, &var2]
      • 含义:默认按值捕获所有变量,但 var1、var2 等按引用捕获。
      • 示例:
      
            int a = 1;
            int b = 2;
            int c = 3;
      
            // a、c 按值,b 按引用
            auto func = [=, &b] { a++; b++; c++; };
      
    • 默认按引用捕获,个别变量按值捕获.

      • 语法:[&, var1, var2]
      • 含义:默认按引用捕获所有变量,但 var1、var2 等按值捕获。
      • 示例:
      
            int a = 1;
            int b = 2;
            int c = 3;
      
            // a、c 按引用,b 按值
            auto func = [&, b] { a++; b++; c++; }; 
      
  3. 类成员函数中的捕获(this 指针)

    在类的非静态成员函数中,Lambda 可捕获当前对象的 this 指针,从而访问类的成员变量和成员函数。

    • 捕获 this 指针(按引用访问对象)

      • 语法:[this]
      • 含义:通过 this 指针访问当前对象的成员(本质是按引用捕获对象,对象生命周期需注意)。
      • 示例:
      
            class MyClass 
            {
            public:
                void func() 
                {
                    // 通过 this 访问 x
                    auto lambda = [this] { x++; }; 
      
                    // 类成员 x 变为 11
                    lambda(); 
                }
      
            private:
                int x = 10;
            };
      
    • 按值捕获当前对象(C++17 起)

      • 语法:[*this]
      • 含义:复制当前对象的副本(而非引用),Lambda 内部操作的是副本,不影响原对象。
      • 示例:
      
            class MyClass 
            {
      
            public:
                void func() 
                {
                    // 捕获对象副本,x 为 10
                    auto lambda = [*this] { cout << x; }; 
      
                    x = 20; // 修改原对象
      
                    // 输出 10(副本的值未变)
                    lambda(); 
                }
      
            private:
                int x = 10;
            };
      
  4. 捕获表达式(C++14 起)

    允许捕获时对变量进行初始化或表达式计算,生成一个新的变量供 Lambda 内部使用(类似 “捕获并重命名”)。

    • 语法:[identifier = expression]
    • 含义:expression 可以是外部变量、表达式或函数调用,结果存储在 identifier 中(按值捕获)。
    • 示例:
    
         int x = 10;
    
         // 捕获 x+5 的结果,存储为 y(值为 15)
         auto func = [y = x + 5] { cout << y; }; 
    
    • 示例:也可结合引用:
    
         int x = 10;
    
         // 引用捕获 x,并重命名为 y
         auto func = [&y = x] { y++; }; 
    

捕获变量时需要注意以下几点

  1. 捕获列表不能重复捕获同一变量(如 [x, &x] 是错误的)。
  2. 默认捕获(= 或 &)与显式捕获的变量不能冲突(如 [=, x] 错误,因 = 已隐含按值捕获 x)。
  3. 局部变量不能被隐式捕获(需显式列出或用默认捕获),全局变量无需捕获即可直接访问。
  4. 捕获的变量生命周期需与 lambda 匹配(尤其按引用捕获时,避免变量销毁后 lambda 仍使用其引用)。

参数列表

Lambda 既可以捕获变量,也可以接受输入参数。 参数列表(在标准语法中称为 Lambda 声明符)是可选的,它在大多数方面类似于函数的参数列表。

  • 示例:

auto y = [] (int first, int second)
{
    return first + second;
};

在 C++14 中,如果参数类型是泛型,则可以使用 auto 关键字作为类型说明符。 此关键字将告知编译器将函数调用运算符创建为模板。 参数列表中的每个 auto 实例等效于一个不同的类型参数。

  • 示例:

auto y = [] (auto first, auto second)
{
    return first + second;
};

mutable 规范

通常,Lambda 的函数调用运算符是 const-by-value,但对 mutable 关键字的使用可将其取消。它不产生 mutable 数据成员。

利用 mutable 规范,Lambda 表达式的主体可以修改通过值捕获的变量。

这个特性知道有就好了,不推荐使用(后文有用法示例)。

异常规范

你可以使用 noexcept 异常规范来指示 Lambda 表达式不会引发任何异常。 与普通函数一样,如果 Lambda 表达式声明 noexcept 异常规范且 Lambda 体引发异常。

  • 示例:

int main() // C4297 expected
{
   []() noexcept { throw 5; }();
}

返回类型

将自动推导 Lambda 表达式的返回类型。 无需使用 auto 关键字,除非指定了 trailing-return-type。 trailing-return-type 类似于普通函数或成员函数的 return-type 部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->。

如果 Lambda 体仅包含一个返回语句,则可以省略 Lambda 表达式的 return-type 部分. 或者,在表达式未返回值的情况下。 如果 lambda 体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会将返回类型推导为 void。

  • 示例:

auto x1 = [](int i){ return i; }; // OK: return int
auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing
                                  // return type from braced-init-list isn't valid

Lambda 体

Lambda表达式的Lambda体是一个复合语句;它可以包含普通函数或成员函数体中允许的任何内容。

总结

到此关于C++中Lambda语法及用法的内容就介绍完毕了,最后以一个非常典型的例子来结束本文的内容。


#include 
<iostream>
using namespace std;

int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);

   // 可以猜测以下这里会输出什么?
   cout << m << endl << n << endl;
}
此条目发表在C/C++分类目录。将固定链接加入收藏夹。