🛫 问题

描述

记得去年立了一个重学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
在这里插入图片描述

📖 参考资料

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐