【C++】 Lambda表达式详解

🛫 问题

描述

记得去年立了一个重学C++新特性的flag,可是真的太忙了,大部分精力都花在全栈上了,今年开始看一些开源源码,发现各种奇怪的语法,根本看不懂,不学不行了。而且接触了很多语言后,发现新特性的确能提高开发效率,所以还是重新学习下C++吧。

环境

版本号描述
文章日期2023-06-09
操作系统Win11 - 21H2 - 22000.1335
C++在线工具https://c.runoob.com/compile/12/

1️⃣ 什么是Lambda表达式

Lambda表达式是C++11中新增的一种函数对象,它可以方便地定义一个匿名函数,从而简化代码的编写。
Lambda表达式的本质是一个可调用对象,可以像函数一样被调用,也可以作为函数参数或返回值。

Lambda 表达式的各个部分

下面是作为第三个参数 std::sort() 传递给函数的简单 lambda:

#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        // 下面是一个简单的 `Lambda 表达式`
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
        } 
    ); 
}

下图显示了 lambda 语法的各个部分:

  1. 捕获列表:(capture list)(在 C++ 规范中也称为 Lambda 引导。)
  2. 参数列表:(parameters list)(可选)。 (也称为 Lambda 声明符)
  3. mutable 规范:(可选)。
  4. 异常说明:exception-specification(可选)。
  5. 返回类型:trailing-return-type(可选)。
  6. Lambda 体:也就是函数体。
    标识 lambda 表达式的各个部分的示意图。

引用大佬的一张图:
在这里插入图片描述

2️⃣ 优缺点

优点

  1. 简化代码:Lambda表达式可以将一些冗长的代码简化为一行代码,使代码更加简洁。
  2. 提高可读性:Lambda表达式可以使代码更加易读,减少了一些冗余的代码,使代码更加简洁明了。
  3. 提高可维护性:Lambda表达式可以使代码更加易于维护,因为它可以将一些复杂的逻辑封装在一个方法中,使代码更加模块化。

缺点

  1. 学习成本高:Lambda表达式需要一定的学习成本,需要理解函数式编程的概念和Lambda表达式的语法。
  2. 可读性降低:有时候Lambda表达式可能会使代码变得更加难以理解,特别是当Lambda表达式嵌套时。
  3. 性能问题:Lambda表达式可能会影响程序的性能,因为它需要创建一个新的对象来表示Lambda表达式。但是,这种影响通常是微不足道的,只有在极端情况下才会有明显的性能问题。

3️⃣ 使用场景

Lambda表达式可以用于任何需要函数对象的场景,例如:

  • STL算法中需要传递函数对象的地方,如std::sortstd::for_each等;
  • STL容器中需要传递比较函数的地方,如std::setstd::map等;
  • 多线程编程中需要传递回调函数的地方,如std::threadstd::async等。

在线C++工具

为了方便演示,找了个在线C++工具 https://c.runoob.com/compile/12/ ,可以直接在网页中运行C++代码。
效果图如下:
在这里插入图片描述

STL算法库

find_if应用实例

#include <iostream>
#include <deque>
#include <algorithm>

using namespace std;

int main()
{
	int x = 5;
	int y = 10;
	deque<int> coll = { 1, 3, 19, 5, 13, 7, 11, 2, 17 };
	auto pos = find_if(coll.cbegin(), coll.cend(), [=](int i) {                 
		return i > x && i < y;
	});
	
	cout << "find " << (pos != coll.end() ? "success" : "failed");

   return 0;
}

sort实例,用于对一个整数数组进行排序:
以上代码中,Lambda表达式[](int a, int b) { return a < b; }用于指定排序规则,即按照升序排列。

#include <iostream>
#include <algorithm>
#include <vector>

int main()
{
    std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};

    // 使用Lambda表达式对vec进行排序
    std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });

    // 输出排序后的结果
    // 1 1 2 3 3 4 5 5 5 6 9 
    for (auto x : vec)
    {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}

STL容器中需要传递比较函数

在线工具不能正常运行:

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <string>
#include <map>

int LambdaContainer()
{
	auto fc = [](const std::string& a, const std::string& b) {
		return a.length() > b.length();
	};
	std::map<std::string, int, decltype(fc)> myMap = { {"apple", 5}, {"banana0", 10}, {"orange", 15} };

	// 使用迭代器遍历map
	std::cout << "使用迭代器遍历map:" << std::endl;
	for (auto it = myMap.begin(); it != myMap.end(); ++it) {
		std::cout << it->first << " : " << it->second << std::endl;
	}

	// 需要C++20支持
	std::map < std::string, int, decltype([](const std::string& a, const std::string& b) {
		return a.length() < b.length();
	}) > myMap2 = { {"apple", 5}, {"banana0", 10}, {"orange", 15} };
	// 使用范围for循环遍历map
	std::cout << "\n\n使用范围for循环遍历map:" << std::endl;
	for (const auto& [key, value] : myMap2) {
	    std::cout << key << " : " << value << std::endl;
	}

	return 0;
}

针对myMap,需要C++17支持
针对myMap2decltype中使用lambda需要C++20支持
在这里插入图片描述

多线程示例

在线工具不能正常运行:
在这里插入图片描述

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>

int main()
{
    // vector 容器存储线程
    std::vector<std::thread> workers;
    for (int i = 0; i < 5; i++) 
    {
        workers.push_back(std::thread([]() 
        {
            std::cout << "thread function\n";
        }));
    }
    std::cout << "main thread\n";

    // 通过 for_each 循环每一个线程
    // 第三个参数赋值一个task任务
    // 符号'[]'会告诉编译器我们正在用一个匿名函数
    // lambda函数将它的参数作为线程的引用t
    // 然后一个一个的join
    std::for_each(workers.begin(), workers.end(), [](std::thread &t) 
    {
        t.join();
    });

    return 0;
}

4️⃣ Lambda表达式与函数指针的比较

Lambda表达式与函数指针类似,都可以用于定义函数对象。但是,Lambda表达式相比函数指针具有以下优点:

  • Lambda表达式可以捕获外部变量,从而方便地访问外部环境;
  • Lambda表达式可以定义在函数内部,从而避免了命名冲突的问题;
  • Lambda表达式可以使用auto关键字自动推导返回值类型,从而简化代码。

5️⃣ 捕获列表

Lambda表达式的捕获列表用于指定Lambda表达式中使用的外部变量。捕获列表可以为空,也可以包含以下内容:

  • []:不捕获任何外部变量;
  • [&]:以引用方式捕获所有外部变量;
  • [=]:以值方式捕获所有外部变量;
  • [var1, var2, ...]:指定捕获特定的外部变量;
  • [&, var1, var2, ...]:以引用方式捕获所有外部变量,并指定捕获特定的外部变量;
  • [=, &var1, &var2, ...]:以值方式捕获所有外部变量,并以引用方式捕获特定的外部变量。

6️⃣ 返回值类型

Lambda表达式的返回值类型可以显式指定,也可以使用auto关键字自动推导。如果Lambda表达式的函数体只有一条语句,且该语句的返回值类型可以自动推导,则可以省略返回值类型和return关键字。

7️⃣ 工作原理

编译器会把一个Lambda表达式生成一个匿名类的匿名对象,并在类中重载函数调用运算符,实现了一个operator()方法。
auto print = []{cout << "Hello World!" << endl; };为例,编译器会把上面这一句翻译为下面的代码:

class print_class
{
public:
	void operator()(void) const
	{
		cout << "Hello World!" << endl;
	}
};
// 用构造的类创建对象,print此时就是一个函数对象
auto print = print_class();

ps: 仿函数(functor)又称为函数对象(function object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符,仿函数与Lamdba表达式的作用是一致的。
stl中含有大量类似的对象,如std::less
在这里插入图片描述

📖 参考资料

### C++ Lambda 表达式的使用方法 C++11 引入了 lambda 表达式,这是一种用于创建匿名函数的便捷方式。Lambda 表达式可以在需要函数对象的地方直接定义和使用,从而简化代码结构。 #### 基本语法 Lambda 表达式的一般形式如下: ```cpp [capture](parameters) -> return_type { body } ``` - **`capture`**: 定义如何捕获外部作用域中的变量。可以通过值 (`=` 或 `&`) 捕获单个变量或者整个上下文。 - **`parameters`**: 函数参数列表,类似于普通函数。 - **`return_type`**: 可选部分,默认情况下编译器会推导返回类型。 - **`body`**: 函数体,包含具体的逻辑实现。 以下是几个具体示例: --- #### 示例 1: 不带捕获的简单 Lambda 表达式 下面是一个简单的例子,展示如何通过 Lambda 表达式遍历容器并打印其内容[^1]。 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> vec = {1, 2, 3, 4, 5}; // 使用 Lambda 表达式打印每个元素 for_each(vec.begin(), vec.end(), [](int x) { cout << x << " "; }); cout << endl; return 0; } ``` 此程序利用标准库算法 `std::for_each` 和一个不带捕获的 Lambda 表达式来逐一遍历向量 `vec` 并输出其中的内容。 --- #### 示例 2: 捕获局部变量 当需要访问 Lambda 外部的作用域时,可以使用捕获功能。以下示例展示了如何按引用捕获变量[^2]: ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int factor = 2; vector<int> numbers = {1, 2, 3, 4, 5}; // 使用 Lambda 表达式计算每个数乘以 factor 的结果 transform(numbers.begin(), numbers.end(), numbers.begin(), [factor](int n) { return n * factor; }); for (const auto& num : numbers) { cout << num << " "; } cout << endl; return 0; } ``` 在此代码片段中,`factor` 是通过值被捕获到 Lambda 中,并被用来修改输入数据。 --- #### 示例 3: 按引用捕获 如果希望在 Lambda 内部更改外部变量,则应采用按引用的方式捕获该变量: ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int sum = 0; vector<int> nums = {1, 2, 3, 4, 5}; // 计算总和 for_each(nums.begin(), nums.end(), [&sum](int n) { sum += n; }); cout << "Sum of elements: " << sum << endl; return 0; } ``` 这里 `[&sum]` 表明我们希望通过引用捕获变量 `sum`,以便能够在 Lambda 内更新它的值。 --- #### 示例 4: 默认捕获模式 还可以指定默认捕获模式为值(`=')或引用(&),之后仅需显式处理例外情况: ```cpp #include <iostream> #include <string> using namespace std; void example() { string message = "Hello"; int count = 5; // 默认按值捕获所有变量,但对 'message' 进行特殊处理(改为按引用) auto greet = [=,&message]() mutable { message += ", World!"; for(int i = 0; i < count; ++i){ cout << message << endl; } }; greet(); } int main(){ example(); return 0; } ``` 上述代码演示了一个复杂的捕获场景,其中大部分变量按照值捕获,而特定变量则依据需求调整捕获方式[^3]。 --- ### 总结 Lambda 表达式极大地增强了 C++ 编程语言的能力,使得编写简洁高效的代码变得更加容易。无论是基本操作还是复杂逻辑都可以借助它们轻松完成。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

夜猫逐梦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值