目录
一、前言
Lambda表达式:是C++11引入的一种函数对象,可以方便地创建匿名函数。与传统的函数不同,Lambda表达式可以在定义时直接嵌入代码,无需单独定义函数名称、参数和返回类型等信息。Lambda表达式通常用于需要定义一些简单的回调函数或者函数对象。优点:简洁、效率高、更加灵活。本文主要介绍Lambda的工作原理以及使用方法。
二、lambda 表达式是什么?
lambda表达式 源于数学中的λ演算,λ演算是一种 基于函数的形式化系统,它由数学家 阿隆佐邱奇 提出,用于研究抽象计算和函数定义。对于编程领域来说,可以使用lambda表达式 快速 构建函数对象,作为函数中的参数

三、lambda 表达式的引入 --- 仿函数
仿函数 是
C++中的概念,指借助 【类】 + 【operator()】重载 创建的 函数对象,仿函数 的使用场景如下
- 创建一个
vector,通过sort函数进行排序,至于结果为升序还是降序,可以通过 仿函数 控制
#include <iostream>
#include <unordered_map>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct cmpLess
{
bool operator()(int n1, int n2)
{
return n1 < n2;
}
};
struct cmpGreater
{
bool operator()(int n1, int n2)
{
return n1 > n2;
}
};
int main()
{
vector<int> arr = { 8,5,6,7,3,1,1,3 };
sort(arr.begin(), arr.end(), cmpLess()); // 升序
cout << "升序: ";
for (auto e : arr)
cout << e << " ";
cout << endl;
sort(arr.begin(), arr.end(), cmpGreater()); // 降序
cout << "降序: ";
for (auto e : arr)
cout << e << " ";
cout << endl;
return 0;
}
注:sort 如果不传递函数对象,默认排序结果为升序

结果为正确排序,但这种先创建一个仿函数对象,再调用的传统写法有点麻烦了,如果是直接使用
lambda表达式 创建函数对象,整体逻辑会清楚很多
- 使用
lambda表达式 修改后的代码如下,最大的改变就是 可以直接在传参时直接编写函数对象的代码逻辑
#include <iostream>
#include <unordered_map>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> arr = { 8,5,6,7,3,1,1,3 };
sort(arr.begin(), arr.end(), [](int n1, int n2) { return n1 < n2; }); // 升序
cout << "升序: ";
for (auto e : arr)
cout << e << " ";
cout << endl;
sort(arr.begin(), arr.end(), [](int n1, int n2) { return n1 > n2; }); // 降序
cout << "降序: ";
for (auto e : arr)
cout << e << " ";
cout << endl;
return 0;
}
- 最终结果也是正常的

有了
lambda表达式 之后,程序员不必再通过 仿函数 构建 函数对象,并且可以在一定程度上提高代码的可阅读性,比如一眼就可以看出回调函数是在干什么
- 接下来看看如何理解
lambda表达式 语法
四、lambda表达式的语法
lambda表达式 分为以下几部分:
[ ]捕捉列表( )参数列表mutable关键字->returntype返回值类型{ }函数体
lambda表达式:[ ]( ) mutable ->returntype { }

其中,( ) 参数列表、mutable、->returntype 都可以省略
- 省略
( )参数列表 表示当前是一个无参函数对象 - 省略
mutable关键字 表示保持捕捉列表中参数的常量属性 - 省略
->returntype返回值类型 表示具体的返回值类型由函数体决定,编译器会自动推导出返回值类型
注意:
- 捕捉列表 和 函数体 不可省略
- 如果使用了
mutable关键字 或者->returntype返回值,就不能省略( )参数列表,即使为空 - 虽然返回值类型编译器可以推导,但最好还是注明返回值类型
也就是说,最基本的 lambda表达式 只需书写 [ ]{ } 即可表示,比如这样
int main()
{
// 最简单的 lambda表达式
[]{};
return 0;
}
- 此时的
lambda表达式 相当于一个 参数为空、返回值为空、函数体为空 的匿名函数对象
void func()
{}
和 func 的主要区别在于
lambda表达式 构建出来的是一个 匿名函数对象,而func是一个 有名函数对象,可以直接调用
五、lambda表达式的使用
🔥 lambda 的经典用法🔥
lambda表达式 构建出的是一个 匿名函数对象,匿名函数对象也可以调用,不过需要在创建后立即调用,否则就会因为越出作用域而被销毁(匿名对象生命周期只有一行)
- 下面通过
lambda表达式 构建一个简单的 两整数相加 函数对象并调用
int main()
{
int ret = [](int x, int y)->int { return x + y; }(1, 2);
cout << ret << endl;
return 0;
}

- 直接使用
lambda表达式 构建出的 匿名函数对象 比较抽象,一般都是将此 匿名函数对象 作为参数传递(比如sort),如果需要显式调用,最好是将创建出来的 匿名函数对象 赋给一个 有名函数对象,调用时逻辑会清晰很多 - 使用
auto推导 匿名函数对象 的类型,然后创建add函数对象
int main()
{
auto add = [](int x, int y)->int { return x + y; };
int ret = add(1, 2);
cout << ret << endl;
return 0;
}

lambda表达式 还有很多玩法,接下来逐一介绍,顺便学习其他组成部分
🔥 lambda 中捕捉列表的使用🔥
利用
lambda表达式 构建一个交换两个元素的 函数对象
- 最经典的写法是 函数参数设为引用类型,传入两个元素,在函数体内完成交换
int main()
{
int x = 1;
int y = 2;
cout << "交换前" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
auto swap = [](int& rx, int& ry)->void
{
auto tmp = rx;
rx = ry;
ry = tmp;
};
swap(x, y);
cout << "交换后" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
return 0;
}
- 这种经典写法毋庸置疑,肯定能完成两数交换的任务

- 除此之外,还可以借助
lambda表达式 中的 捕捉列表 捕获外部变量进行交换
int main()
{
int x = 1;
int y = 2;
cout << "交换前" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
auto swap = [x, y]() ->void
{
auto tmp = x;
x = y;
y = tmp;
};
swap();
cout << "交换后" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
return 0;
}
- 因为现在 函数对象 是直接捕获外部变量进行操作,调用函数对象时,无需传参
- 代码写完,编译器立马给出了报错:
x、y不可修改

- 这是因为 捕捉列表 中的参数是一个值类型(传值捕捉),此时的捕获的是外部变量的内容,然后赋值到 “
x、y” 中,捕捉列表 中的参数默认具有 常量属性,不能直接修改,但可以添加mutable关键字 取消常性
int main()
{
int x = 1;
int y = 2;
cout << "交换前" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
auto swap = [x, y]()mutable ->void
{
auto tmp = x;
x = y;
y = tmp;
};
swap();
cout << "交换后" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
return 0;
}
- 但是程序运行结果不尽人意,外部的
x、y并没有被交换,证明此时 捕捉列表 中的参数x、y是独立的值(类似函数中的值传递)

- 想让外部的
x、y被真正捕获,需要使用 引用捕捉
int main()
{
int x = 1;
int y = 2;
cout << "交换前" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
auto swap = [&x, &y]() ->void
{
auto tmp = x;
x = y;
y = tmp;
};
swap();
cout << "交换后" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
return 0;
}
- 现在
x、y被成功交换了

注意: 捕捉列表中的 &x 表示引用捕捉外部的 x 变量,并非取地址(特例)
所以说
mutable关键字不常用,因为它取消的是值类型的常性,即使修改了,对外部也没有什么意义,如果想修改,直接使用 引用捕捉 就好了
捕捉列表 支持 混合捕捉,同时使用 引用捕捉 + 传值捕捉
int main()
{
int x = 1;
int y = 2;
cout << "调用前" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
// 混合捕捉
auto func = [&x, y]()mutable ->void
{
x = 100;
y = 200;
};
func();
cout << "调用后" << endl;
cout << "\tx: " << x << endl << "\ty: " << y << endl;
return 0;
}
x被修改了,而y没有

除了 混合捕捉 外,捕捉列表 还支持 全部引用捕捉 和 全部传值捕捉
全部引用捕捉
int main()
{
int x, y, z, a, b, c;
x = y = z = 0;
a = b = c = 1;
string str = "Hello lambda!";
cout << "&str: " << &str << endl << endl;
auto func = [&]()->void
{
cout << x << " " << y << " " << z << " " << endl;
cout << a << " " << b << " " << c << " " << endl;
cout << str << endl;
cout << "&str: " << &str << endl << endl;
};
func();
return 0;
}
- 无需指定 捕捉列表 中的参数,
&可以一键 引用捕捉 外部所有变量

注:只能捕捉已经定义或声明的变量
全部传值捕捉
int main()
{
int x, y, z, a, b, c;
x = y = z = 0;
a = b = c = 1;
string str = "Hello lambda!";
cout << "&str: " << &str << endl << endl;
auto func = [=]()->void
{
cout << x << " " << y << " " << z << " " << endl;
cout << a << " " << b << " " << c << " " << endl;
cout << str << endl;
cout << "&str: " << &str << endl << endl;
};
func();
return 0;
}
- 全部传值捕捉 也能一键捕捉外部变量,不过此时捕获的是外部变量的值,并非变量本身,无法对其进行修改(可以通过
mutable关键字 取消常性)

注意: [=] 表示全部传值捕捉,[] 表示不进行捕捉,两者不等价
捕捉列表 的使用非常灵活,比如
[&, x]表示x使用 传值捕捉,其他变量使用 引用捕捉;[=, &str]表示str使用 引用捕捉,其他变量使用 传值捕捉
- 捕捉列表 就像一个 “大师球”,可以直接捕捉到外部的变量,在需要大量使用外部变量的场景中很实用,有效避免了繁琐的参数传递与接收

有没有 全部引用捕捉 + 全部传值捕捉 ?
- 当然没有,这是相互矛盾的,一个变量不可能同时进行 引用传递 和 值传递,即便传递成功了,编译器在使用时也不知道使用哪一个,存在二义性,所以不被允许

注意: 关于 捕获列表 有以下几点注意事项
- 捕捉列表不允许变量重复传递,否则就会导致编译错误
- 在块作用域以外的
lambda函数捕捉列表必须为空 - 在块作用域中的
lambda函数不仅能捕捉父作用域中局部变量,也能捕捉到爷爷作用域中的局部变量

lambda表达式 还可以完美用作 线程回调函数,比如接下来使用C++11中的thread线程类,创建一个线程,并使用lambda表达式 创建一个线程回调函数对象
int main()
{
// 创建线程,并打印线程id
thread t([] { cout << "thread running... " << this_thread::get_id() << endl; });
t.join();
return 0;
}

总之
lambda表达式 在实际开发中非常好用,关于thread类的相关知识放到后面讲解,接下来先看看lambda表达式 的实现原理
🔥lambda表达式的原理🔥
lambda表达式 生成的函数对象有多大呢?
- 是像 普通的函数对象指针 一样占
4/8字节,还是像 仿函数 一样占1字节,通过sizeof计算大小就可以一探究竟
// 普通函数
int add(int x, int y)
{
return x + y;
}
// 仿函数
class addFunc
{
public:
int operator()(int x, int y)
{
return x + y;
}
};
int main()
{
auto typeA = add;
addFunc typeB;
auto typeC = [](int x, int y)->int { return x + y; };
cout << "普通函数: " << sizeof(typeA) << endl;
cout << "仿函数: " << sizeof(typeB) << endl;
cout << "lambda表达式: " << sizeof(typeC) << endl;
return 0;
}
- 结果显示,
lambda表达式 生成的函数对象与 仿函数 生成的函数对象大小是一样的,都是1字节

- 仿函数 生成的函数对象大小为
1字节是因为其生成了一个空类,实际调用时是通过operator()重载实现的,比如上面的addFunc类,空类因为没有成员变量,所以大小只为1字节
由此可以推断
lambda表达式 本质上也是生成了一个空类,分别查看使用 仿函数 和lambda表达式 时的汇编代码

- 可以看到,这两段汇编代码的内容是一模一样的,都是先 call 一个函数(operator() 重载函数),然后再执行主体逻辑(两数相加),只不过使用 仿函数 需要自己编写一个 空类,而 使用 lambda 表达式 时由编译器生成一个 空类,为了避免这个自动生成的 空类 引发冲突,会将这个 空类 命名为 lambda_uuid
uuid是 通用唯一标识码,可以生成一个重复率极低的辨识信息,避免类名冲突,这也意味着即便是两个功能完全一样的lambda表达式,也无法进行赋值,因为lambda_uuid肯定不一样
所以在编译器看来,lambda 表达式 本质上就是一个 仿函数
六、lambda表达式的优点及适用场景
lambda表达式 作为一种轻量级的匿名函数表示方式,具备以下优点:
- 简洁性: 对于简单的函数操作,无需再手动创建函数、调用,只需要编写一个 lambda 表达式生成函数对象
- 方便些: lambda 表达式具有 捕捉列表,可以轻松捕获外部的变量,避免繁琐的参数传递与接收
- 函数编程支持: lambda 表达式可以作为函数的参数、返回值或存储在数据结构中
- 内联定义: lambda 表达式 lambda表达式可以作为函数的参数、返回值或存储在数据结构中
- 简化代码: 对于一些简单的操作,使用 lambda 表达式可以减少代码的行数,提高代码的可读性
总的来说,
lambda表达式 可以替代一些代码量少的函数,使用起来十分方便,如果lambda表达式 编写出来的代码过于复杂时,可以考虑转为普通函数,确保代码的清晰性和可读性
七、共勉
以下就是我对 【C++11】lambda表达式 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 【C++11】 的理解,请持续关注我哦!!!

1019

被折叠的 条评论
为什么被折叠?



