C++基础整理(5)之lambda表达式/匿名函数
注:整理一些突然学到的C++知识,随时mark一下
例如:忘记的关键字用法,新关键字,新数据结构
C++ 的匿名函数/lambda表达式
提示:本文为 C++ 中 匿名函数 的用法和举例
一、lambda表达式(匿名函数)
C++想要定义函数一般需要函数声明和函数定义,并且一般放置在h文件和cpp文件中,且必须放在main函数外再定义,那如果我们只想突然用一个小功能的函数,不想再在当前函数外面写定义这么麻烦,那怎么办呢,于是C++就引入了匿名函数这一功能:直接将lambda表达式整体当成一个有完备功能的函数的名字来用,即如果这个表达式被赋给一个变量a ,那么a就可以当成一个函数名来用,这就是lambda表达式的初衷。
1、lambda表达式基本语法
lambda表达式的基本语法格式:
[capturedvariables](parameters) -> return_type { body_of_lambdaFunction }
上面写法如果省略掉返回类型就是[](){}
,仔细看其实就是把函数定义时候那个写法中函数名字的部分换成了[]
,所以这个表达式也叫做匿名函数。
(1)capture:捕获列表,全称应该是想要捕获当前作用域内的变量的列表,它决定了哪些外部变量可以在lambda体中被访问。(这里的外部变量指lambda函数的外部:即你想在哪个函数或作用域,比如说我在main的内部创建lambda表达式,这时候就是main函数的内部的变量可以在lambda表达式的内部被调用,然后我就把想调用的变量一个个列在这个[]
中)。捕获列表也可以是空,也可以包含&(按引用捕获) 或 =(按值捕获)或者具体的变量名。
(2)parameters:参数列表,与普通函数参数列表类似。
(3)return_type:返回类型,可以省略,由编译器自动推断。
(4)body_of_lambda:lambda的主体,即函数体。
综上,Lambda表达式是一种在某个作用域(或者一个函数)的内部定义一个函数功能并且可以捕获当前作用域中的变量的简洁方式。
Lambda表达式通常用于需要一个函数对象,但又不想显式定义之的场合。以下是一些常见用法:
2、lambda表达式的基本使用——定义简单的匿名函数
定义简单的匿名函数:
auto add = [](int a, int b) { return a + b; };
//现在add就可以当一个函数来用了
int sum = add(3, 4); // sum现在是7
定义简单的匿名函数,当需要捕获外部变量:
//捕获可以是按值捕获([=])或按引用捕获([&]),
//[v1]表示按值捕获变量v1、[=]表示按值捕获当前作用域的全部变量
//[&v2]表示按引用捕获变量v2、[&]表示按引用捕获当前作用域的全部变量
//也可以显式指定捕获哪些变量(例如[a, &b])。
int a = 10;
auto printA = [&]() { std::cout << a << std::endl; };
printA(); // 输出10
3、lambda表达式的基本使用——作为函数对象传递
因为lambda表达式本身是个函数对象的名字,所以可以作为函数方法里面的(比如STL里面的algorithm)参数来传:
C++的STL相关标准库中的许多算法接受函数对象作为参数,Lambda表达式可以方便地、直接地作为这些算法的参数。
例如,在std::for_each或std::find_if等算法中:
std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int n) { std::cout << n << ' '; });
// 输出: 1 2 3 4 5
又例如,特别是在std::sort或std::remove_if等算法中,用于定义排序规则或过滤条件:
std::vector<int> vec = {5, 3, 1, 4, 2};
std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });
// vec现在是{1, 2, 3, 4, 5}
在异步编程中,当使用C++的异步编程功能(如std::async或std::future)时,Lambda表达式可以用来定义异步任务:
std::future<int> result = std::async(std::launch::async, []() {
return some_expensive_computation();
});
// ... 在其他地方使用result.get()获取结果
4、lambda表达式的基本使用——作为函数返回值
Lambda表达式可以作为函数的返回值,特别是当需要返回一个可调用的对象时:
auto getLambda() {
return [](int a) { return a * 2; };
}
auto doubleIt = getLambda();
int doubledValue = doubleIt(5); // doubledValue现在是10
5、关于捕获列表【】
lambda表达式中,捕获列表(capture clause)用于指定哪些外部变量(相较于lambda的外部)可以在lambda函数体中被访问。捕获可以是按值(by value)或按引用(by reference)。按值捕获时,捕获的是变量的一个副本,会发生拷贝;按引用捕获时,捕获的是对变量的一个引用。理解可以类比函数的值传递和引用传递。
捕获放在lambda表达式的开头,用方括号[]表示。捕获可以是空的,也可以包含以下符号:
[]:不捕获任何外部变量。
[=]:按值捕获所有外部变量。
[&]:按引用捕获所有外部变量。
[a, &b]:按值捕获变量a,按引用捕获变量b。
[=,&b]:按引用捕获变量b,其他变量均按值捕获
[this]捕获所在类的this指针
注意同时的[=, &]是错误的。
下面是一些使用捕获子句的示例:
示例 1:不捕获任何变量
int main() {
int a = 10;
auto lambda = []() {
std::cout << "a is not captured: " << a << std::endl; // 错误:a未捕获
};
lambda();
return 0;
}
在这个例子中,lambda表达式没有捕获任何变量,因此尝试在lambda体内部访问变量a会导致编译错误。
示例 2:按值捕获所有变量
int main() {
int a = 10;
auto lambda = [=]() {
std::cout << "Value of a is: " << a << std::endl; // 输出:Value of a is: 10
};
lambda();
return 0;
}
在这个例子中,[=]表示按值捕获所有外部变量。因此,lambda体内部可以访问变量a,并且使用的是a的一个副本。
示例 3:按引用捕获所有变量
int main() {
int a = 10;
auto lambda = [&]() {
a = 20; // 修改a的值
std::cout << "Value of a is: " << a << std::endl; // 输出:Value of a is: 20
};
lambda();
std::cout << "Value of a outside lambda: " << a << std::endl; // 输出:Value of a outside lambda: 20
return 0;
}
在这个例子中,[&]表示按引用捕获所有外部变量。因此,lambda体内部不仅可以访问变量a,还可以修改它的值。修改后的值在lambda外部也是可见的。
示例 4:混合捕获
int main() {
int a = 10;
int& b = a; // 引用b指向a
auto lambda = [a, &b]() {
a = 20; // 修改a的副本,不影响外部a的值
b = 30; // 修改b引用的值,即外部a的值
std::cout << "Inside lambda: a = " << a << ", b = " << b << std::endl; // 输出:Inside lambda: a = 20, b = 30
};
lambda();
std::cout << "Outside lambda: a = " << a << ", b = " << b << std::endl; // 输出:Outside lambda: a = 10, b = 30
return 0;
}
在这个例子中,lambda表达式按值捕获了变量a的副本,并按引用捕获了变量b(它本身是对a的引用)。因此,在lambda体内部修改a的值不会影响外部变量a,但修改b的值会影响外部变量a的值。
选择按值还是按引用捕获取决于你的具体需求。按值捕获可以确保lambda函数不会意外地修改外部变量的状态,而按引用捕获则允许lambda函数访问和修改外部变量的当前状态。
6、关于捕获类的this指针
lambda表达式可以使用[this]捕获子句来捕获所在类的this指针。这允许lambda内部访问调用它的对象的成员变量和成员函数。
以下是一个使用[this]捕获子句的例子:
class MyClass {
public:
int value;
MyClass(int v) : value(v) {}
// 一个成员函数,它接受一个lambda并调用它
void processWithLambda(const std::function<void(int)>& func) {
func(value);
}
// 一个成员函数,它创建一个lambda并立即调用它
void useLambdaDirectly() {
// 创建一个lambda,它捕获this指针并打印value
auto lambda = [this]() {
std::cout << "Value from lambda: " << this->value << std::endl;
};
// 调用lambda
lambda();
}
};
int main() {
MyClass obj(42);
// 使用成员函数processWithLambda传递一个lambda
obj.processWithLambda([&](int a) {
std::cout << "Value from lambda in processWithLambda: " << a << std::endl;
});
// 直接在成员函数中使用lambda
obj.useLambdaDirectly();
return 0;
}
在这个例子中,MyClass有一个整数成员变量value和两个成员函数:processWithLambda和useLambdaDirectly。
processWithLambda接受一个std::function<void(int)>类型的参数,并在函数内部调用它。在main函数中,我们创建了一个捕获this指针的lambda,并将其作为参数传递给processWithLambda。这个lambda打印出value的值。
useLambdaDirectly在函数体内直接创建了一个lambda,该lambda捕获this指针,并打印出value的值。这里使用了[this]捕获子句来确保lambda可以访问MyClass的成员。
请注意,当使用[this]捕获时,你只能在lambda内部访问类的非静态成员变量和成员函数。此外,由于捕获了this指针,你必须确保在lambda被调用时,this指针是有效的(即对象没有被销毁)。
需要注意的是,Lambda表达式在C++11及以后的版本中可用,并且随着C++标准的演进,Lambda表达式的功能也在不断增强。例如,C++14引入了基于初始化列表的捕获以及泛型Lambda(使用auto作为参数类型),而C++17则允许在Lambda的捕获子句中使用结构化绑定。(这里不深讲)