lambda表达式详解

发布于:2025-05-23 ⋅ 阅读:(16) ⋅ 点赞:(0)

lambda表达式

参考C++11标准中的lambda表达式。

lambda表达式是C++11标准中加入的。其具备很高的便捷性和自由度。

lambda表达式实际上是一个匿名类函数,在编译时编译器会将表达式转换成匿名类函数。

类函数,又称函数对象或仿函数,c++中由重载`()`运算符实现

C++Primer真本书中,lambda表达式初次出现位于泛型算法这一章节里,可见lambda表达式很大程度上是为了泛型而诞生的。


语法

 [capture list] (parameter list) -> return type {function body};
 [捕获列表](参数列表) -> 返回值类型 {函数体};
  • [capture list]:捕获列表,该列表总是会出现在lambda的开头位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕获列表能够捕捉上下文中的变量供lambda函数使用。

  • parameter list:参数列表,与普通的函数和成员函数的使用大致相同,不过不同的是在lambda表达式的参数列表中不可以声明参数默认值。另外:若函数不需要参数传递,可以连同()一起省略。

  • -> return type:以后置返回值类型的方式来追踪函数的返回值类型,如果函数体没有返回值可以省略。另外:若函数体只是单一return语句,返回值类型可由编译器推导出,-> return type也可以省略。

  • {function body}:函数体,函数体除了可以使用其参数外,还可以使用所有捕获到的变量。

由上面的规则可以得出C++中最简单的lambda表达式:

 [] {};

接下来是对一些用法的一一演示:

代码块内的注释多属于扩展知识

  1. 无参数传递省略参数列表

     auto x = [] {cout << "test01\n";};
     x(); // 从这里也可以看出lambda表达式是一种可调用对象,其余的可调用对象还有函数,仿函数,函数指针
  2. 有参数传递无返回值

     auto x = [](int x) {cout << x;};
     x(10);
  3. 单一return语句省略返回值类型

     auto x = [] {return 100;};
     cout << x();
     ​
     // 这里的单一return语句并非函数体只有一条return语句,而是多种return语句的返回值类型相同  
     auto x = [](int x) {
         if (x > 0) return 1;
         else return 0;
         };
     // 多种return语句编译失败
     auto x = [](int x) {
         if (x > 0) return 1.0;
         else return 0;
         };
     // lambda表达式中返回值类型的自动推导被广泛认为过于苛刻,因此在c++14的标准中放宽了限制,允许返回值进行隐式转换,隐式转换的规则与赋值时隐式转换的规则相同,当函数体内同时存在double和int两种return类型时int类型的返回值会被隐式转换为double类型。
  4. 函数体使用捕获列表内声明的变量

     int a = 0;
     auto x = [a] {cout << a;};
     x();

此外,lambda表达式还支持=赋值,()拷贝,(move())转移,{}列表初始化等通用语法。

捕获列表中还可用,一下语句来对捕获变量进行限定,对此不做过多演示。

  1. [a, &b]:其中a以复制捕获而b以引用捕获。

  2. [this]:以引用捕获当前对象(this)

  3. [&]:以引用捕获所有用于lambda体内的自动变量,并以引用捕获当前对象。

  4. [=]:以复制捕获所有用于lambda体内的自动变量,并以引用捕获当前对象。

  5. []:不捕获。

2,3,4常用于类的内部。


lambda的用处

首先要声明,lambda表达式中的引用捕获使用起来并不是很安全,尤其是2,3,4部分,在使用时可能会导致指针悬空,声明周期管理混乱等一系列问题。

另外lambda表达式在使用时相较于普通的函数也有一些限制。似乎lambda表达式除了写起来方便以外一无是处?

那么,lambda表达式的优势到底在哪里?这个问题其实在一开始就给出了答案——泛型编程。

首先,我们知道代码复用性对于泛型编程来说是很非常重要的,我们可以模拟这样一个场景:

现在我们手上有一个函数,其通过泛型实现了对于不同类型的数据都达到相似目的功能,比如algorithm中的sortfor_each,但是我们现在的函数实际上是以"硬编码"的形式实现的。若想要实现灵活的编码以传统的手段显然是很难做到的,比如:sort自定义排序方式,for_each自定义操作语句。

这时候可能有人会想到使用函数指针将函数作为参数传递以实现软编码的函数。但是如果存在多条规则的时候呢?还要重载各种参数个数的函数,还是说使用函数可变参数?

重载许多版本这显然是不可能的,而且在泛型编程中这种有这种诉求的函数也不在少数,使用函数可变参数可能会影响效率不说,传统的函数可变参数也是很危险的,可能会引起一系列的问题。

于是C++11引入了lambda表达式,已解决传统方法难以实现软编码的问题。

模拟这样一个问题,现在给你一个数组,比如:

 int arr[] = {1, 2, 3, 4, 5};

让你使用泛型编程实现仅用三个参数实现对于给定的任意整数x,只输出>=x的内容。

这个函数其实algorithm库中已经提供了,那就是for_each,我们观察其函数原型:

 _EXPORT_STD template <class _InIt, class _Fn>
 _CONSTEXPR20 _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
     _STD _Adl_verify_range(_First, _Last);
     auto _UFirst      = _STD _Get_unwrapped(_First);
     const auto _ULast = _STD _Get_unwrapped(_Last);
     for (; _UFirst != _ULast; ++_UFirst) {
         _Func(*_UFirst);
     }
 ​
     return _Func;
 }

将视角聚焦到for循环内,可以发现其要求的_Func函数要求是只有一个参数的函数。并且这个参数是数组中每一位的值。似乎使用传统方法只能这样写,无法判断数字是否输出:

 int arr[] = { 1, 2, 3, 4, 5 };
 for_each(arr, arr + 5, [](int x) {cout << x;});

这时候我们就可以使用捕获列表将外部的限制捕获到函数体内部。

 int minL = 2;
 int arr[] = { 1, 2, 3, 4, 5 };
 for_each(arr, arr + 5, [minL](int x) {if(x >= minL)cout << x;});

这样就可以很方便的实现有额外限制的问题了。


总结

lambda表达式是主要是为泛型编程而生,本质上是匿名仿函数。

其除了使用起来方便以外,最大的优点源自于其独特的捕获列表,使其可以很轻松的进行自由定值的软编码操作。

此外,其可以捕获作用域内"所有"变量的性质也使得其在处理大量数据时非常自由。

但滥用捕获机制可能在并发程序中引起一系列的问题,比如:指针悬挂,对象生命周期异常等一系列问题。


网站公告

今日签到

点亮在社区的每一天
去签到