再学C++:高级特性:Lambda表达式

C++ Lambda 表达式

Lambda 表达式是C++11引入的一种内嵌匿名函数

核心与理解

我个人的理解是,可以在函数体内,写的一种特殊的函数。它的作用域只有它自己的函数块,其可以捕获外部变量,如果捕获方式是引用,就和平常指针一样,“拥有这些变量”。如果捕获方式是值,就通过副本保存下来这个值。也就是,Lambda是一个可以认为独立于原函数的一个匿名函数。
可以认为在实际使用中,Lambda表达式,可以通过传参,保存此时的“状态”,并且持久化这个状态(如果是引用捕获,会存在被修改的可能,如果是值传递,则永久保持这个状态),进行数据封装,同时用于处理一些逻辑。这也是我对“闭包”这个概念的理解。
如果不容易理解,可以先去看后面的语法,在回来看这块内容。

//大家无需看懂这个表达式,毕竟语法还没开始学习。这儿就理解一下,
int main()
{
    int x = 10;
    // Lambda 表达式捕获 x
    auto lambda = [x]() mutable
    {
        x += 5; // 修改内部变量 x
        std::cout << "x = " << x << std::endl;
    };
    lambda(); // 输出 x = 15 部x = 10 + 5;
    x = x + 20; // 这里修改的是外部;
    lambda(); // 输出 x = 20  内部第二次调用 x = 15 + 5;

    return 0;
}

引用:

//大家无需看懂这个表达式,毕竟语法还没开始学习。这儿就理解一下,
int main() {
    int x = 10;

    // Lambda 表达式引用捕获 x 
    auto lambda = [&x]() {
        x += 5;  // 修改外部变量 x,如同指针,这里的x是外部的
        std::cout << "x = " << x << std::endl;
    };

    lambda(); // 输出 x = 15 , x = 10 + 5;
    x = x + 20;  // 这里修改的是外部; x = 15 + 20 = 30;
    lambda(); // 输出 x = 40  内部第二次调用 x = 35 + 5;

    return 0;
}

如果能理解这个核心思想,Lambda就差不多完全掌握了,高级特性对于熟练编程的人来说,难的不是如何用,而是为什么用,为什么要去用。
好,以下便是基础学习部分。

基础知识

知识点:语法

完整语法
[capture list] (params list) mutable(optional) constexpr(optional)(C++17) exception attribute -> return type { function body };
//绝大多数使用为:[捕捉](形参)->int {函数体}
  • capture list:捕获外部变量列表,不能省略;(可认为是“传入值”);
  • params list:形参列表可以省略(但是后面必须紧跟函数体);
  • mutable指示符: 可选,将lambda表达式标记为mutable后,函数体就可以修改传值方式捕获的变量
  • constexpr:可选,C++17,可以指定lambda表达式是一个常量函数
  • exception:异常设定, 可选,指定lambda表达式可以抛出的异常
  • attribute:可选,指定lambda表达式的特性
  • return type:返回类型
  • function body:函数体
简化语法

在实际使用中,Lambda 表达式的语法可以简化为以下几种形式:

  1. 指定返回类型

    [capture list] (params list) -> return type { function body };
    
    std::transform(vi.begin(), vi.end(), vi.begin(), 
         [](int i) -> int { 
             if (i < 0) 
                 return -i;
             else 
                 return i;
         }
     );
    
  2. 省略返回类型

    [capture list] (params list) { function body };
     
     std::transform(vi.begin(), vi.end(), vi.begin(), 
         [](int i) { 
             return i < 0 ? -i : i; 
         }
     );
    
  3. 省略参数列表和返回类型

    [capture list] { function body };
    
    auto printMessage = [a] { 
         std::cout << "Value of a is: " << a << std::endl; 
     };
    

知识点:捕获外部变量

Lambda 表达式可以通过捕获外部变量来访问其周围的上下文。捕获方式有以下几种:

  1. 值捕获

    int a = 123;
    auto f = [a] { 
        std::cout << a << std::endl; 
    };
    f(); // 输出:123
    a = 321;
    f(); // 输出:123
    
  2. 引用捕获

    int a = 123;
    auto f = [&a] { 
        std::cout << a << std::endl; 
    }; 
    a = 321;
    f(); // 输出:321
    
  3. 隐式捕获

    • 值捕获
      int a = 123, b = 321;                     
      auto df = [=] { 
          std::cout << a << b << std::endl; 
      }; 
      
    • 引用捕获
      int a = 123, b = 321;                     
      auto rf = [&] { 
          std::cout << a << b << std::endl; 
      }; 
      

知识点:参数列表

Lambda表达式的参数定义和C++函数方法存在一些不同。
主要为:

  • 参数列表中不能有默认参数。
auto lambda = [](int a, int b = 10) { // 错误:Lambda 表达式中不允许默认参数
    std::cout << "a = " << a << ", b = " << b << std::endl;
};
  • 不支持可变类型参数。 (C++14引入,C++17扩展,现在已经允许,见后文)
auto lambda = [](auto... args) { // 错误:Lambda 表达式不支持可变参数
    (std::cout << ... << args) << std::endl;
};
  • 所有参数必须有参数名。
auto lambda = [](int a, int) { // 错误:Lambda 表达式中的参数必须有名称
    std::cout << "a = " << a << std::endl;
};

知识点:返回类型

  • 单一返回语句:Lambda 表达式只有一个return语句时,可以自动推断:

    std::transform(vi.begin(), vi.end(), vi.begin(), 
        [](int i) { 
            return i < 0 ? -i : i; 
        }
    );
    
  • 多语句:当 Lambda 表达式包含多个return语句时,必须显式指定返回类型:

    std::transform(vi.begin(), vi.end(), vi.begin(), 
        [](int i) -> int { 
            if (i < 0) 
                return -i;
            else 
                return i;
        }
    );
    

知识点:mutable 关键字

允许函数对传入的进行修改,如果不进行mutable修饰,那么无法修改传入的,引用不受限制;

  • 使用示例
    int x = 10;
    auto f = [x]() mutable { 
        x++;  // 修改的是捕获的副本,不是外部变量 x
        std::cout << x << std::endl; 
    }; 
    f(); // 输出:11
    std::cout << x << std::endl; // 输出:10
    

新特性

泛型lambda表达式

在 C++14 中,Lambda 表达式得到了增强,支持泛型编程。泛型 Lambda 表达式允许使用 auto 关键字来表示参数类型,这样可以在调用时根据传入参数的类型自动推断。这种功能类似于模板函数,可以提高代码的灵活性和复用性。

// 泛型 Lambda 表达式示例
auto add = [](auto x, auto y) { return x + y; };

int main() {
    int intResult = add(2, 3);           // 5
    double doubleResult = add(2.5, 3.5); // 6.0
    std::string strResult = add(std::string("Hello, "), std::string("world!")); // Hello, world!

    std::cout << "intResult: " << intResult << std::endl;
    std::cout << "doubleResult: " << doubleResult << std::endl;
    std::cout << "strResult: " << strResult << std::endl;

    return 0;
}

Lambda 捕捉表达式

C++14 引入了 Lambda 捕捉表达式,允许 Lambda 表达式捕捉任意类型的表达式结果。捕捉表达式可以按复制或引用方式捕捉作用域内的变量,甚至可以捕捉右值。这使得 Lambda 表达式更加灵活,特别是在处理复杂对象和右值引用时。


int main() {
    // 捕捉外部变量并进行初始化
    int x = 4;
    auto y = [&r = x, x = x + 1] { r += 2; return x * x; }();
    std::cout << "x: " << x << ", y: " << y << std::endl; // 输出:x: 6, y: 25

    // 直接用字面值初始化捕捉变量
    auto z = [str = "string"] { return str; }();
    std::cout << "z: " << z << std::endl; // 输出:z: string

    // 捕捉不能复制的对象
    auto myPi = std::make_unique<double>(3.1415);
    auto circle_area = [pi = std::move(myPi)](double r) { return *pi * r * r; };
    std::cout << "Circle area with radius 1: " << circle_area(1.0) << std::endl; // 输出:3.1415

    return 0;
}

在这个示例中:

  • y 捕捉了外部变量 x 并进行了初始化,同时修改了 r 的值。
  • z 捕捉了一个字符串字面值 “string”。
  • circle_area捕捉了一个 std::unique_ptr 对象 myPi,并通过 std::move 将其移动到 Lambda 表达式中。

其他特性

constexprexception 关键字

  • constexpr:指定 Lambda 表达式为常量表达式:

    constexpr auto f = [](int x) -> int { return x * 2; };
    static_assert(f(3) == 6, "f(3) should be 6");
    
  • exception:指定 Lambda 表达式可以抛出的异常:

    auto f = [](int x) noexcept -> int { 
        return x * 2; 
    }; 
    

nodiscard和maybe_unused关键字

  • [[nodiscard]]:防止忽略返回值:
auto f = [](int x) -> int [[nodiscard]] { 
    return x * 2; 
};

int main() {
    f(10); // 如果忽略返回值,编译器可能会给出警告
    return 0;
}
  • [[maybe_unused]]:标记可能未使用的参数:
auto f = [](int x [[maybe_unused]]) { 
    // 可能不使用x 
};

int main() {
    f(10); // 不会有警告,即使x未被使用
    return 0;
}

好了,以上就是所有内容了,欢迎点赞与收藏,如果错误,恳请指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云璃丶夢紡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值