现代C++语言的核心特征之一:lambda表达式。虽然其它编程语言早已具备了这种特性,但直到C++11标准发布,C++11才具备了lambda表达式。本节主要讲解lambda表达式的语法和使用方法。具体包括:捕获列表、可选参数列表、可选异常说明符、可选返回值类型等。此外,还将介绍lambda表达式在STL算法中的应用和泛型lambda表达式的使用方法。
函数对象:
不过在此之前我们先来了解一下函数对象(仿函数)。函数对象其实就是在类内对函数调用运算符:()进行重载。因为使用方法与函数类似,我们又将其称为仿函数。
struct MyAdd{
public:
int operator()(int a,int b){
return a+b;
}
};
class MyCompare{
public:
bool operator()(int a,int b){
return a<b;
}
};
写两端的目的是为了告诉大家,C++中的struct和class都可以重载运算符来进行运算,一般我们又将返回值为bool的函数对象称为谓词,参数为一个就是一元谓词,参数为两个就是二元谓词。
那么我们知道函数对象又称仿函数,那么使用方法我们就可以猜出来了:
void test01(){
MyAdd add;
cout<<add(1,3)<<endl;//输出4
}
void test02(){
MyCompare compare;
cout<<compare(2,5)<<endl;//true--实际输出1
}
仿函数是C++中用于实现功能对象的两种重要机制之一,另一个就是lambda表达式。C++中的lambda表达式的底层与仿函数类似。lambda表达式可以定义匿名函数。优点在于简洁、可以捕获作用域中的变量,使得可以在函数内部使用外部变量。
基本语法:
[capture](parameters) -> return_type { body }
基本的lambda表达式的语法如上。其中:
capture:捕获外部变量的方式。
parameters:匿名函数的参数列表。
return_type:返回类型,通常可以省略,编译器会进行推导。
body:lambda表达式的函数体。
捕获列表:
捕获列表指定了lambda表达式可访问的变量,并包括以下几种方式:
值捕获:复制外部变量的值到lambda中。
int x=10;
auto lambda = [x](){return x+5;}; //用值传递
引用捕获:捕获外部变量的引用,可以在lambda中修改变量。
int x = 10;
auto lambda = [&x]() { x += 5; }; // x 通过引用捕获
默认捕获:[=]:值捕获所有外部变量,[&]:引用捕获所有外部变量。
int a = 5, b = 10;
auto lambda = [=]() { return a + b; }; // 所有变量以值捕获
auto lambda = [&]() { return a + b; }; // 所有变量以引用捕获
混合捕获:可以混合使用值和引用捕获。
int x = 1, y = 2;
auto lambda = [x, &y]() { return x + y++; }; // x 值捕获,y 引用捕获
在这里大家肯定有点疑惑,捕获列表的用途是什么, 这有什么用,还没有看明白。这里我举个例子:
int main(){
int a=10,b=20,c=30;
char ch1='a',ch2=’B‘;
//然后我想让ch1=a+b+c;但是过了本行不影响ch1的值
return 0;
}
我想通过函数的方式调用,有很多方式,但大都是外部定义一个函数,但这时候,我们需要传入参数,变量多的时候,我们还要传很多的参数,定义普通的函数显然有点不合适。那么lambda表达式就很合适了,直接捕获所有变量,然后想使用啥使用啥,我眼中的lambda表达式其实是一种函数内部的匿名函数。
参数和返回值:
参数:参数可以为空,也可以包含一个或多个参数,就像普通函数一样。
auto lambda = [](int a, int b) { return a + b; };
返回类型:如果返回类型明确、显式,可以调用->来指明返回类型,如果省略,编译器会自行推导。
底层探究:
但凡是个表达式,都有其类型,那么lambda表达式是个什么类型呢?我们使用typeid().name()来输出一下看看。
这时候我们可以看出来lambda表达式的类型本身是一个类类型。每创立一个表达式,都会创建一个类,而且是在函数内部的作用域下创建的类,我们也可以看出来,每个lambda的类型都是不一样的。这时候我们想到了仿函数,仿函数是在类内重载了()运算符,那这个lambda表达式到底与仿函数有什么渊源呢?
通过观察上面的反汇编语言。我们可以看出来,lambda的底层机制还是仿函数。只不过编译器会自动为你的lambda表达式生成一个类,将lambda表达式当作函数使用时其实就是调用类内的()重载函数。
STL算法应用:
Lambda 表达式经常与标准模板库(STL)的算法结合使用:
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int &n) { n *= 2; });
//这里我们可以使用仿函数,也可以使用函数指针,也可以像本例一样使用lambda表达式
for (const auto &n : vec) {
std::cout << n << " "; // 输出: 2 4 6 8 10
}
return 0;
}
- 简洁性: 不需要定义完整的函数,提高了代码的可读性。
- 访问外部作用域: 可以直接使用外部的变量,而无需将其作为参数传递。
- 灵活性: 可以在需要时快速定义函数,特别适用于 STL 算法。
注意事项:
- Lambda 表达式在多线程环境下使用时要注意捕获对象的生命周期。
- 当捕获的对象超出了作用域或被销毁后,如果 Lambda 仍然存在并使用这些对象,会导致未定义行为。
感谢大家!