C++也能像Python一样玩转lambda

C++也能像Python一样玩转lambda

0.导语

C++2.0新特性之一lambda,本节学习激动人心的lambda.

1.lambda表达式

[capture list] (params list) mutable exception-> return type { function body }
  • capture list:捕获外部变量列表
  • params list:形参列表
  • mutable指示符:用来说用是否可以修改捕获的变量
  • exception:异常设定
  • return type:返回类型
  • function body:函数体

简言之 [ 捕获 ] ( 形参 ) 可选参数 -> 返回类型 { 函数体 }

2.从简单到复杂

匿名无参函数

例如:

[] {
    cout << "hello" << endl;
}();

同样我们可以使用auto关键字来获得lambda的返回类型:

auto I = [] {
    cout << "hello" << endl;
};
I();

对于lambda的返回,一般人不知道其返回类型是什么,所以常常使用auto,这也是auto使用比较多的一点,像这个比较简单,我们可以直接写出返回类型:

funciton<void()> I = [] {
    cout << "hello" << endl;
};
I();

捕获列表

引入上述例子看一下[]可以做哪些事: 常常也被称为引入符号(lambda-introduct).

  • 声明当前是个lambda表达式
  • 捕获列表

我们来演示捕获作用:

  • [=] =表示默认以by value传递外部所有变量
  • [&] &表示默认以by reference传递外部所有变量
  • [=,&id,...] 一部分传引用,其余传值
  • [&,id,...] 一部分传值,其余传引用

传值

int id=2,id1=3;
auto f = [=]() {       // 这里加不加()都可以
    cout << "id=" << id << endl;
    cout << "id1=" << id1 << endl;
};
id=3;
f();

输出:

id=2
id1=3

这里主要来说明前面id被修改为3,为何输出没变;另外lambda可以访问到外部所有变量;里面的值如何进行修改呢?

第一个问题:输出不变?

因为是传值,外部修改对f函数没影响,所以id仍旧是2,要想修改看后面引用传入.

第二个问题:访问到所有变量?

因为使用了=,=表示默认以by value传递外部所有变量,也就是前面的捕获列表为=.

第三个问题:如何修改外部变量? 可以通过添加mutable,但是当lambad函数结束后,变量仍旧恢复. 例如:

int id=2,id1=3;
auto f = [=]()mutable{
    id--;
    cout << "id=" << id << endl;    // 1
    cout << "id1=" << id1 << endl;  // 3
};
id=3;
f();

内部可以对外部变量修改,但是跳出lambda,并不会影响外部,如果要影响,必须传引用.

另外,如果lambda内部修改了变量,但是并没有使用mutable关键字,报错:error: increment of read-only variable ‘id’.且使用mutable后,()必须添加()

传引用

int id=2,id1=3;
auto f = [&](){
    id++;
    cout << "id=" << id << endl;    // 4
    cout << "id1=" << id1 << endl;  // 3
};
id=3;
f();
cout << "id=" << id << endl;    // 4

看上面输出,我们知道id外部修改,对内部发生了影响,且内部修改也对外部进行了修改,这就是传引用!传递引用可以不加mutable关键字.

其余的一部分传引用,其余传值或者一部分传值,其余传引用,只需要记着,传值必须用mutable关键字进行修改,且对外部变量没有影响.传引用会影响即可.

参数列表

前面都是对lambda函数不传递任何参数,传递参数就比较简单了,调用的时候:

f(xxx);

即可. 那,我们写一个简单的:

function<int(int x)> ll = [](int x) -> int {    // 由于这个返回值比较简单,可以不用显示写->int 返回类型,例如当想转为float类型等操作的时候,可以显示写出->float.
    return x + 10;
};

对传递进来的值加10操作.

返回类型

还是上述例子,通过-> return type`来使用.

this指针

对于[=]与[&],可以直接使用this指针,而对于[]则需要显示[this].

例如:

class X {
private:
    int __x, __y;
public:
    X(int x, int y) : __x(x), __y(y) {}

    int operator()(int a) { return a; }

    int f() {
        // 下列 lambda 的语境是成员函数 X::f
        // 对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针
        return [&]() -> int {
            return operator()(this->__x + __y); // X::operator()(this->x + (*this).y)
            // 拥有类型 X*
        }();
    }


    int ff() {
        return [this]() {
            return this->__x;
        }();
    }
};

调用:

// this 指针
X x_(1, 2);
cout << "x_.f()=" << x_.f() << endl;   // 1+2=3
cout << "x_.ff()=" << x_.ff() << endl; // 1

lambda等价操作

我们可以把lambda的使用等价于下面这个类,通过这两个来做对比:

class UnNamedLocalFunction {
private:
    int localVar;
public:
    UnNamedLocalFunction(int var) : localVar(var) {}

    bool operator()(int val) {
        return val == localVar;
    }
};

调用:

UnNamedLocalFunction lambda2(tobefound);
bool b2 = lambda2(5);

lambda使用:

int tobefound = 5;
auto lambda1 = [tobefound](int val) {
    return val == tobefound;
};
bool b1 = lambda1(5);

decltype+lambda

在使用STL容器的时候,通常需要比大小,例如set,需要传递一个比较器,针对这个我们可以使用lambda来传递.

例如:我们有个Person类,我们比大小是根据这个人的lastname来比较

struct Person {
    string firstname;
    string lastname;
};

写比较器: 由于lambda的返回类型很少友人知道,书写比较困难,因此我们可以直接用auto.

auto cmp = [](const Person &p1, const Person &p2) {  // 如果对lambda比较熟悉,可以直接写返回类型:function<bool(const Person&p1,const Person&p2)>
    return p1.lastname < p2.lastname;
};

我们使用set来去重当前容器中的Person是否重复,这里就需要传递一个比较器:

set<Person, decltype(cmp)> col(cmp);

这里使用decltype(cmp)来获取返回类型,如果功力深厚,可以直接写:

set<Person, function<bool(const Person&p1,const Person&p2)> > col(cmp);

我们通常使用decltype推断类型+lambda来书写这种比较复杂的例子.

算法+lambda

在容器的算法中常常使用lambda,例如remove_if,for_each等等.

例如:我们实现一个移除vector中30到100之间的所有数字,只需要后面三行:

vector<int> vec{5, 28, 50, 83, 70, 590, 245, 59, 24};
int x = 30, y = 100;

vec.erase(remove_if(vec.begin(), vec.end(), [x, y](int n) { return x < n && n < y; }), vec.end());
for_each(vec.begin(), vec.end(), [](int i) { cout << i << " "; });
cout << endl;

而如果不用lambda,则需要写这么多:

public:
    LambdaFunctor(int a, int b) : m_a(a), m_b(b) {}

    bool operator()(int n) const {
        return m_a < n && n < m_b;
    }

private:
    int m_a;
    int m_b;
};
vec.erase(remove_if(vec.begin(), vec.end(), LambdaFunctor(x, y)), vec.end());
for(auto i:vec) cout<<i<<" ";
cout<<endl;

最后,c++14,17,20都对lambda做了一些修改,本节只是比较简单的学习了c++2.0的新特性,期待大家共同交流!

本文分享自微信公众号 - 光城(guangcity)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码客

Flex 布局教程:语法篇

布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易...

8810
来自专栏运维猫

Varnish为网站加速

Varnish是一款高性能的开源HTTP加速器,挪威最大的在线报纸Verdens Gang (http://www.vg.no)使用3台Varnish代替了原来...

28430
来自专栏微信公众号【Java技术江湖】

Java 8 终于支持 Docker!

注意:我在本文中使用采用GNU GPL v2许可证的OpenJDK官方docker映像。在Oracle Java SE中,这里描述的docker支持功能在更新1...

11520
来自专栏光城(guangcity)

发布一个STL源码剖析专栏及序列式容器deque

大家好,我是光城,最近一直在研究STL源码剖析,据此,开一个知乎专栏:《C++ STL 源码剖析》,地址戳下面或点击阅读原文,欢迎大家关注!

8330
来自专栏BI8ESZ

docker 命令

docker build -t 镜像名 . (注意最后有个点用来表示当前目录,初次构建速度会比较慢,需要多等一会。)

10330
来自专栏SH的全栈笔记

在Java中使用redisTemplate操作缓存

在最近的项目中,有一个需求是对一个很大的数据库进行查询,数据量大概在几千万条。但同时对查询速度的要求也比较高。

8120
来自专栏光城(guangcity)

​C++ STL源码剖析之容器配接器stack与queue、priority_queue

对于stack来说,底层容器可以是vector、deque、list,但不可以是map、set。由于编译器不会做全面性检查,当调用函数不存在的时候,就编译不通过...

8140
来自专栏大内老A

[ASP.NET Core 3框架揭秘] 依赖注入:依赖注入模式

IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照“好莱坞法则”实现应用程序的代码与框架之间的交互。我们...

8820
来自专栏Java架构沉思录

这份3万字的Spring Boot知识清单,请查收!

在过去两三年的Spring生态圈,最让人兴奋的莫过于Spring Boot框架。或许从命名上就能看出这个框架的设计初衷:快速的启动Spring应用。因而Spri...

9320
来自专栏微信公众号【Java技术江湖】

走进JavaWeb技术世界2:JSP与Servlet的曾经与现在

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看

7700

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励