C++系列(二)lambda表达式

C++的这个系列接着续上,这次聊聊lambda表达式。

C++11中首次引入了lambda表达式。通俗的来说,就是可以方便的定义和创建一个内嵌的匿名函数。我们都知道在C++中,一个普通的函数可以被这样定义:

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

这样的标准定义方式在对于查找一段逻辑的调用栈,或者不同层级的逻辑相互解耦的时候是很好的写法。但当我们需要将这个函数的值传递给另外一个算法或者异步函数,特别是函数中的代码量很少的时候,lambda表达式也是一种不错的选择。

例如此时,我们需要自定义std::sort的比较函数时,调用普通函数的做法会是这样:
std::sort(arr.begin(), arr.end(), compare);
而在使用了lambda表达式之后:

std::sort(arr.begin(), arr.end(), 
[](int a, int b) {
    return (std::abs(a) < std::abs(b));
});

可以看到,这里我们直接用lambda表达式创建的匿名函数替换了原先独立声明的compare函数,语法上简洁了许多。

基本语法

知道了我们会在什么情况下选择lambda表达式,下面我们来看一下它具体的语法。

首先来看一下最简单的例子

auto helloWorld = [] { std::cout << "Hello, world!" << std::endl; };
helloWorld();

这个例子中的lambda表达式没有任何的参数和返回值,只是执行了函数体中的内容。那么如果我们想要加上参数和返回值,就需要跟普通的函数一样,在括号中包括我们想要的参数,并在->之后加上返回值类型。

int cnt = wordCount("User");  // return 4

capture子句的原理和使用

每当定义一个lambda表达式后,编译器会自动生成一个重载了 () 运算符的匿名类,称为闭包类型(closure type)。在runtime,表达式会返回一个匿名的闭包实例。所以我们可以说,之前的例子中我们定义的每一个lambda表达式都是一个闭包。

闭包的一个特性就是,可以通过传递值或者引用的方式捕捉到作用域中的变量。具体到语法上,就是lambda表达式前面的方括号,也叫capture子句。例子如下:

int base_length = 10;
auto wordCount = [base_length](std::string username) -> int { return username.length() + base_length; };
int cnt = wordCount("User");  // return 14

总体来说,lambda的capture字句大体有一下几类:

  • [&]:通过默认引用的方式捕获所有的变量
  • [=]:通过默认值的方式捕获所有的变量
  • [var]:通过值的方式捕获变量var
  • [&var]:通过引用的方式捕获变量var
  • [=, &var]:对于除var以外的变量,默认通过值捕获,var用引用捕获
  • [&, var]:对于除了var以外的变量,默认通过引用捕获,var用值捕获
  • [this]:捕获当前对象(本质是复制了指针)

[&]或[=]使用的问题

  • [&] Dangling Reference
    对于默认引用捕获来说,当该引用对应的变量被销毁之后,其指向的值不再是原先我们预期的值,因此我们的lambda函数就会出现不可预知的行为。例如

    std::function<int(int)> test(int x){
        lambdafunc = [&](int y) -> int { return x + y; };
    }
    

在这个例子中,lambdafunc的调用中,x已经出了它的作用域,因此调用它会产生没有意义的结果。

  • [=]
    使用传值的捕获可以避免以上的问题,但是这样依然有问题。例如在一个类中,使用[=]会将this指针一并捕获,相当于就是以引用的方式捕获了这个类的对象。那么同样的,当这个类被析构之后,再次调用这个表达式依然会出现dangling reference的问题。因此[=]的不安全点主要是在于,其将指针进行复制,本质上还是传递了一个引用。

[var]和mutable的使用

经过上文的叙述,我们知道一个变量可以通过传值和传引用的方式被捕获。理所应当的,当这个值是以传引用的方式被捕获的时候,我们可以修改它的内容,这一点和函数参数的引用是类似的。那么假如我们使用的是以传值的方式进行捕获,还可以进行修改吗?

答案是可以的,但是需要搭配上mutable关键字。例如:

auto func = [x](int y) mutable -> int { x = x * 2; return x + y; };
通过mutable关键字,使得其在生成的闭包类中的operator()的重载不再是const类型,自然就可以进行赋值了。

这次简单介绍了一下lambda表达式的使用,下一篇聊聊右值引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dabtwice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值