C++ Lambda 表达式

Lambda表达式概述

C++11引入了Lambda表达式,用于定义并创建匿名的函数对象,主要用于方便编程,避免全局变量的定义,并且变量安全。

Lambda 表达式可以理解为一个匿名的内联函数。一个Lambda表达式表示一个可调用的代码单元。

Lambda 的基本语法:

[函数对象参数](函数参数)修饰符->返回值类型{函数体};

可以忽略函数参数和返回类型,但必须永远包含函数对象参数和函数体;忽略参数列表等价于指定一个空函数列表,忽略返回类型,Lambda 会根据函数体中的代码推断出来(如果函数体直接return,则是void类型)。

和普通函数一样,Lambda 表达式的调用方式与普通函数的调用方式相同,也都具有一个返回类型、一个参数列表和一个函数体。

与普通函数的几点不同在于:

(1)Lambda必须使用尾置返回类型
(2)lambda表达式不能有默认参数。因此,一个Lambda表达式调用的实参数目永远与形参数目相等。
(3)所有参数必须有参数名
(4)不支持可变参数

Lambda 的使用

1、函数对象参数(捕获列表)

[函数对象参数] (函数参数) 修饰符 -> 返回值类型 {函数体};

[ ]标识一个Lambda表达式的开始,这一部分是不可以忽略的。

函数对象参数表示捕获列表,捕获就是明确的指明 Lambda 能使用的局部变量。(在捕获全局变量时会提示:无法在 lambda 中捕获带有静态存储持续时间的变量)

捕获列表只用于非静态局部变量,Lambda可以直接使用静态局部变量和在函数之外声明的名字(全局变量)。

void func()
{
	static int i = 10;
	int  j = 20;
	auto f1 = [ ] () { return j; };		//编译出错,没有进行捕获,函数体内不能使用
	auto f2 = [ ] () { return i; };		//静态变量无需捕获
	auto f3 = [j] () { return j; };		//进行了捕获,函数体内可以使用了
}

函数参数有以下几种形式:

捕获列表意义
[ ]不捕获Lambda表达式外的变量。
[=]以值传递的方式捕获 Lambda 所在范围内所有可见的局部变量,即以const引用的方式传值 。
[&]以引用传递的方式使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this)。
[this]函数体内可以使用 Lambda 所在类中的成员变量。
[x][=x]以值传递的方式捕获变量x,即const int x。
[&x]将变量x按引用进行传递,在函数体内可以改变 x 的值。
[x,&x1]将变量x按值传递,变量x1按引用进行传递。
[=,&x,&x1]除变量x和变量x1按引用进行传递外,其他参数都按值进行传递。
[&,x,x1]除变量x和变量x1按值进行传递外,其他参数都按引用进行传递。

(1)值捕获

与传递参数类似,采用值捕获的前提是变量可以拷贝。
与参数不同,被捕获的变量的值是在 Lambda 创建时拷贝,而不是调用时拷贝。例如:

void func()
{
	int v1 = 42;
	auto f = [ v1 ]  { return v1; };	// 创建时拷贝,f() = 42
    v1 = 24;
	auto j = f();						// 调用f()
    cout << j << endl;                  // 42,f保存了我们创建它时v1的拷贝
}

由于被捕获的值在 Lambda 创建时拷贝,因此在随后对其修改不会影响到 Lambda 内部对应的值。

默认情况下,如果以传值方式捕获外部变量,则在 Lambda 表达式函数体中不能修改该外部变量的值

改变值捕获的局部变量,就必须在参数列表后加上关键字mutable

语法变为:[capture list] (parameter list) mutable -> return type {function body}

void func()
{
	int v1 = 42;
    int v2 = 24;
	auto f1 = [ = ]() mutable { v1 = v2; return v1 + v2 ; };  // 创建时拷贝 f1() = 48
    cout << v1 << endl;             // 42
    cout << f1() << endl;           // 48
    cout << v1 << endl;             // 42
	v2 = 42;                                
    cout << f1() << endl;           // 48
}

可以看出,被 mutable 修饰的函数参数,该函数参数可以在 Lambda 函数体内改变,但不会改变函数体外该变量的值,我们也可以理解为在函数体内拷贝了这个变量的同名变量。

(2)引用捕获

和函数引用参数一样,一个引用类型的变量在函数体内改变时,实际上使用的是引用所绑定的对象。

void func()
{
	int v1 = 42;
	auto f = [ &v1 ]  { return v1; };	// 引用捕获,将v1拷贝到名为f的可调用对象
	cout << f() << endl;                // 42
    v1 = 24;
	auto j = f();
    cout << j << endl;                  // 24
}
void func()
{
	int v1 = 42;
	auto f = [ &v1 ] { v1 = 24; return v1; };	// 引用捕获,将v1拷贝到名为f的可调用对象
	auto j = f();
    cout << j << endl;      // 24
    cout << v1 << endl;     // 24  
}

如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在 Lambda 执行的时候是存在的
Lambda 捕获的都是局部变量,这些变量在函数结束后就不复存在了。如果 Lambda 可能在函数结束后执行,这里就会出现问题。

有一些不可拷贝对象,只能使用引用捕获的方式,比如 ostream 对象。

(3)隐式捕获

除了显式指定我们希望使用的来自所在函数的局部变量之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。

隐式捕获有两种方式,分别是[=][&],分别表示以值传递引用传递的方式捕获所在函数的局部变量。

void func()
{	// 引用传递
	int v1 = 42;
    int v2 = 24;
	auto f1 = [ & ]  { return v1 + v2 ; };  // 引用传递所有可见局部变量
    cout << f1() << endl;                   // 66
	v1 = 24;                                
    cout << f1() << endl;                   // 48
}

(4)混合方式捕捉

混合捕获时,捕获列表中的 第一个元素必须是 =& ,此符号 指定了默认捕获的方式 是值捕获或引用捕获 。

此外,显式捕获的变量必须使用和默认捕获不同的方式捕获

void func()
{
	int i = 10;
	int j = 20;
	auto f1 = [ =, &i  ] () { return j + i; };		//正确,默认值捕获,显示是引用捕获
	auto f2 = [ =, i ] () { return i + j; };		//编译出错,默认值捕获,显示值捕获,冲突了
	auto f3 = [ &, &i ] () { return i +j; };		//编译出错,默认引用捕获,显示引用捕获,冲突了
}

2、函数参数

[函数对象参数] (函数参数) 修饰符 -> 返回值类型 {函数体};

这一部分可以被省略(如果函数无参数),我们可以使用下面方式定义Lambda表达式:

void func()
{
	auto f1 = [] {
	    cout << "Hello" << '\n';
    };
    f1();

    auto f2 = [] (string s) {
        cout << "Hello " << s << '\n';
    };
    f2("world");
}

3、修饰符

[函数对象参数] (函数参数) 修饰符 -> 返回值类型 {函数体};

这一部分是可以省略的,常见的修饰符有两个,一个是mutable,另一个是exception

  • mutable 用来在 Lambda 函数体内改变通过值传递捕获的变量,但不会改变在函数体外该变量的值。
  • exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。

4、返回值类型

[函数对象参数] (函数参数) 修饰符 -> 返回值类型 {函数体};

这一部分也是可以省略的,Lambda 表达式会自动推断返回值类型,但是返回类型不统一会报错。

5、函数体

[函数对象参数] (函数参数) 修饰符 -> 返回值类型 {函数体};

标识函数的实现,这一部分可以为空,但是不能省略

Lambda 的调用

1. 创建时调用

函数体后面加括号(如果有函数参数则需要参数),则代表在创建时调用表达式:

void func()
{
    int v1 = 42;
	auto f = [ &v1 ] { return v1; }();	// 将括号放到函数体后面,相当于调用,然后给 f 赋值
	cout << f << endl;                  // 42
    v1 = 24;
	auto j = f;
    cout << j << endl;                  // 42,与上述引用捕获的例子形成对比
}
2. 创建后,像普通函数一样调用(匿名函数实例化)
void func()
{
    int v1 = 42;
	auto f = [ &v1 ] { return v1; };	// 创建
	cout << f() << endl;                // 调用,42
    v1 = 24;
	auto j = f();
    cout << j << endl;                  // 调用,24
}
auto f = [ &v1 ] { return v1; };

等价于

#include <functional>
function<int()> f = [ &v1 ] { return v1; };	
3. 使用函数指针

使用函数指针指向 Lambda 表达式,但是不能有变量捕获

void func()
{
	auto f = [](int x) { cout << x << endl; return x*x; };	// 创建
    int (*func_ptr)(int) = f;           // 函数指针
	cout << func_ptr(3) << endl;        // 9
}

举例

class Increase
{
	public:
	    void operator()(int& val){++val;}
};

int main()
{
    int i = 1, j = 1;
    auto f = [](int& j){ ++j; };	// 等价于Increase
    f(j);	// (1)
    Increase Inc;
    Inc(i);	// (2)
}	// (1)与(2)等价
class Bigger//1
{
	public:
	    bool operator()(int a, int b){ return a > b; }
};

bool bigger(int a, int b){ return a > b; }

int main()
{
    vector<int> vec{ 2, 0, 1, 3, 3, 0, 1, 9, 7, 7 };
    sort(vec.begin(), vec.end(), [](int a, int b)->bool{return a > b; });	// lambda 
    sort(vec.begin(), vec.end(), bigger);		// 函数指针
    sort(vec.begin(), vec.end(), Bigger());		// 函数对象
}
class F
{
	public:
	    F(int n) :num(n){}
	    int operator()(){ return num; }
	private:
	    int num;
};

int main(){
    int num = 100;
    auto f = [num](){return num; };
    cout << f() << endl;    // 100
    F fclass(num);
    auto j = fclass();
    cout << j << endl;      // 100
    return 0;
}

参考:
C++之Lambda表达式详细解释
C++ lambda表达式 C++
lambda表达式及其原理

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值