C++lambda函数
什么是lambda函数
文章目录
- 什么是lambda函数
- 为什么需要lambda
- lambda的完整语法结构
- lambda的实现原理
- C++14/17/20 中的新特性
简单来说, Lambda 函数是一个“就地”定义、“当场”使用的匿名函数。
想象一下,你只是需要一个简单的函数来作为另一个函数的参数(例如,给
std::sort
提供一个排序规则),但你又不想在别处郑重其事地定义一个完整的、有名字的函数。这时,Lambda 就是你的最佳选择。它让你可以在需要的地方直接写出函数体,非常方便和简洁。
为什么需要lambda
我们通过一个例子来看看 Lambda 的威力。假设你有一个 std::vector<int>
,想对它进行自定义排序,规则是:偶数在前,奇数在后。
在不使用lambda的时候
你可能需要定义一个独立的函数或者一个函数对象。
// 方法一:定义一个普通函数
bool compareForSort(int a, int b) {bool a_is_even = (a % 2 == 0);bool b_is_even = (b % 2 == 0);if (a_is_even != b_is_even) {return a_is_even; // 如果一个偶一个奇,偶数排前面}return a < b; // 如果同为偶数或奇数,按升序排
}// ... 在 main 函数中 ...
std::vector<int> v = {1, 2, 3, 4, 5, 6};
std::sort(v.begin(), v.end(), compareForSort);
这个 compareForSort
函数可能只在这里用一次,却需要定义在全局或类作用域中,显得有些“笨重”。
使用lambda的时候
#include <iostream>
#include <vector>
#include <algorithm>
int main() {std::vector<int> v = {1, 2, 3, 4, 5, 6};// 直接在 std::sort 的第三个参数位置定义 Lambda 函数std::sort(v.begin(), v.end(), [](int a, int b) {bool a_is_even = (a % 2 == 0);bool b_is_even = (b % 2 == 0);if (a_is_even != b_is_even) {return a_is_even;}return a < b;});for (int n : v) {std::cout << n << " "; // 输出: 2 4 6 1 3 5}std::cout << std::endl;return 0;
}
优点显而易见:
- 代码更紧凑: 函数逻辑直接写在了调用的地方,可读性更高。
- 上下文关联性强: 排序的规则和排序的动作紧密地放在一起,一目了然。
- 避免命名污染: 无需为只用一次的简单功能函数想一个名字。
lambda的完整语法结构
lambda 的语法可能初看起来有些奇怪,但一旦掌握,就会觉得非常强大。
[capture](parameters) specifiers -> return_type { function_body }
我们来逐一分解:
[]
- 捕获列表
这是 Lambda 最核心、最强大的部分。它决定了 Lambda 函数可以访问其定义时所在作用域中的哪些变量。[]
:不捕获任何外部变量[=]
:按值捕获所有外部变量。在 Lambda 函数体内,这些变量是只读的副本(除非使用mutable
)。[&]
:按引用捕获所有外部变量。在 Lambda 函数体内,可以修改这些外部变量。[x, &y]
:指定捕获列表。x
按值捕获,y
按引用捕获。[=, &y]
:除了y
按引用捕获,其他所有变量都按值捕获。[&, x]
:除了x
按值捕获,其他所有变量都按引用捕获[this]
:捕获当前类对象的this
指针,这样就可以在 Lambda 内部访问类的成员变量和成员函数。
示例:
int x = 10;
int y = 20;// 按值捕获 x
auto lambda1 = [x]() { std::cout << "x = " << x << std::endl; };
lambda1(); // 输出 x = 10// 按引用捕获 y
auto lambda2 = [&y]() { y = 30; };
lambda2();
std::cout << "y = " << y << std::endl; // 输出 y = 30,y 的值被改变了// 混合捕获
auto lambda3 = [x, &y]() {// x++; // 错误!按值捕获的变量是 const 的y++;
};
lambda3();
std::cout << "y after lambda3 = " << y << std::endl; // 输出 y after lambda3 = 31
()
- 参数列表
和普通函数的参数列表一样。如果你的 Lambda 不需要参数,可以省略括号()
。specifiers
- 可选说明符
这里可以放一些关键字,最常用的是mutable
。mutable
: 允许你在 Lambda 函数内部修改按值捕获的变量。这个修改只在 Lambda 内部有效,不会影响外部原始变量。
int a = 0;
auto lambda_mutable = [a]() mutable {a = 5; // 如果没有 mutable,这行会编译错误std::cout << "Inside lambda, a = " << a << std::endl;
};
lambda_mutable(); // 输出: Inside lambda, a = 5
std::cout << "Outside lambda, a = " << a << std::endl; // 输出: Outside lambda, a = 0
-> return_type
- 返回类型
指定 Lambda 函数的返回类型。在大多数情况下,编译器可以根据return
语句自动推断出返回类型,所以这个部分通常可以省略。
只有在少数复杂情况下(例如函数体中有多个返回语句,且返回类型不同)才需要显式指定。{}
- 函数体
和普通函数一样,这里是 Lambda 函数的具体实现逻辑。
lambda的实现原理
在底层,编译器会为每个 Lambda 表达式生成一个唯一的、匿名的类。这个类重载了 operator()
(函数调用运算符),使得其实例可以像函数一样被调用。
这个生成的类被称为闭包。
- 你定义的 Lambda 函数体,成为了这个类
operator()
方法的函数体。 - 你捕获的变量,成为了这个类的成员变量。
- 按值捕获的变量被拷贝到成员变量中。
- 按引用捕获的变量则作为引用成员
这就是为什么 Lambda 可以“记住”其创建时环境的原因
C++14/17/20 中的新特性
lambda 也在不断进化,变得越来越强大:
- 泛型 lambda (C++14): 可以使用
auto
关键字作为参数类型,让 lambda 像模板一样工作。
auto generic_add = [](auto a, auto b) {return a + b;
};
int sum_int = generic_add(3, 4); // 7
double sum_double = generic_add(3.5, 4.5); // 8.0
std::string s1 = "hello", s2 = " world";
std::string s_cat = generic_add(s1, s2); // "hello world"
- 捕获初始化 (C++14): 可以在捕获列表中直接创建和初始化新的变量,这个变量只在 Lambda 内部可见。这对于移动(move)一个只能移动的对象(如
std::unique_ptr
)到 lambda 中特别有用。
std::unique_ptr<int> ptr = std::make_unique<int>(10);// 将 ptr 的所有权移动到 Lambda 内部的 p 变量中
auto lambda_move = [p = std::move(ptr)]() {std::cout << "Value inside lambda: " << *p << std::endl;
};
lambda_move();
//此时 ptr 已经是 nullptr 了
[*this]
(C++17): 按值捕获this
所指向的对象,即创建当前对象的一个副本。这在异步编程中非常有用,可以防止原始对象被销毁后,lambda 内部的this
指针变成悬空指针。