第13课lambda表达式

浅墨浓香 浅墨浓香     2022-09-23     642

关键词:

1. lambda语法形式[capture](params) opt -> ret {body;};

(1)capture为捕获列表

  ①[]、[&]和[=]分别表示不捕获、按引用捕获、按值捕获所有父作用域中内的局部变量。(父作用域指包含lambda表达式的语句块,如main函数)。

    ◆lambda函数只能捕获父作用域中的局部变量,而捕获非父作用域static变量都会报错(不是C++11的标准,其行为可能因编译器而不同)。(注意全局变量或static变量不能被捕获即不能被写入捕获列表中,但可在lambda的函数体内直接访问

    ◆默认下无法修改按值捕获的外部变量因为lambda表达式的operator()默认是const函数,但捕获this指针后可修改成员变量的值,因为是通过this指针来修改的)。

    ◆在类中如果使用&或=捕获,会同时默认捕获this指针

  ②[=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。注意,捕获列表不允许变量重复传递,如[=,var],var被按值捕获了两次,这是不允许的。

  ③[bar]按值捕获bar变量(注意只捕获bar,其他变量不被捕获)

  ④[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样访问权限,可以使用当前类的成员函数和成员变量(注意也可修改non-const成员变量的值)。

(2)params lambda 表达式的参数列表

  ①如果省略,则类似于无参函数func()

  ②参数列表中不能有默认参数所有的参数必须有参数名

  ③不支持可变参数。

(3)opt选项

  ①mutable修饰符:默认下,lambda表达式的operator()是const函数mutable可以取消其常量性,让body内的代码可以修改被捕获的变量,并可以访问被捕获对象的non-const函数。在使用该修饰符时,参数列表不可省略(即使参数为空)

  ②exception:说明lambda表达式是否抛出异常(noexcept),以及抛出何种异常。如抛出整数类型的异常,可以使用throw(int)。

  ③attribute用来声明属性

(4)ret为返回值类型

  ①如果被省略了由return语句的返回类型确定

  ②如果没有return语句,则类似于void func(…)函数

(5)body:函数体

【编程实验】lambda表达式初体验

#include <iostream>

using namespace std;

int g = 0;

class Test
{
private:
    int i = 0;
public:
    
    void func(int x, int y)
    {
        //auto x1 = []{return i;};   //error,没有捕获任何变量。当然也无法捕获父作用域(func)以外的变量,因此i是不能访问到的!
        //auto x1 = [&i]{return i;}; //error,无法捕获父作用域(func)以外的变量
        auto x1 = [=]{return i++;}; //ok,因为“=”或“&”默认会捕获this指针,也就可以访问到成员变量。根据按值捕获的特点,此时
                                    //在lambda的函数体内不能修改this指针本身。但这不影响我们通过this指针修改成员变量i的值!
        
        auto x2 = []{return g++;};  //ok,g不在lambda的父作用域,不能被捕获。
                                    //如auto x2 = [&g]{return g++;}。但由于g是全局变量
                                    //所以在lambda的body内仍是可见的!
                                    
        auto x3 = [=]{return i++, i + x + y;};//ok,按值捕获所有外部变量,由于&或=捕获时会默认地同时传入this,所以可改变i的值
        auto x4 = [&]{return i + x + y;}; //ok,按引用捕获所有变量:x, y以及this指针
                                          //但注意,i没有被捕获,也不可捕获到。它是通过this指针来访问到的。
                                          
        auto x5 = [this]{return i++;};    //ok,捕获了this指针,也就可以修改成员的值
        //auto x6 = [this, x]{return i + x + y;}; //error,没捕获y
    }
};

int main()
{
    []{}; //最简单的lambda表达式
    
    //使用返回值后置语法
    auto f1 = [](int a) ->int {return a + 1;}; 
    cout << f1(1) << endl; //输出2
    
    //省略了返回值类型,由return语法推断
    auto f2 = [](int i){return i + 1;};  //注意:初始化列表不能用于返回值的自动
                                         //推导:如auto x = [](){return {1, 2};};
    cout << f2(1) << endl; //输出2
    
    //参数列表为空时可以省略
    auto f3 = []{return 1;}; //等价于 auto f3 = [](){return 1;};
    cout << f3 << endl;      //等价于 cout << f3() << endl;
    
    int a = 0, b = 1;
    //auto f4 = []{return a;}; //error,没有捕获外部变量
    auto f5 = [&]{return a++;}; //ok,a=1;
    //auto f6 = [=]{return a++;}; //error,按值捕获的
    auto f7 =[a, &b]{return a + (b++);};//ok,按值捕获a,按引用捕获b++
    
    return 0;
}

2. lambda与仿函数

(1)仿函数是编译器实现lambda表达式的一种方式。在现阶段,通常编译器会把lambda表达式转化成一个仿函数对象。因此在C++11中,lambda可以视为仿函数的一种等价形式。

 

(2)注意事项

  ①lambda表达式按值捕获了所有外部变量。在捕获的一瞬间,变量x的值就已经被复制了。如果希望lambda表达式在调用时能即时访问外部变量,应当使用引用方式捕获。(如变量y)

  ②默认情况下,按值捕获的变量是不可以被修改的(如mx++会报错)因为operator()是const函数除非在lambda表达式加关键字mutable,此时重载的operator()就不会被加上const。但应注意,由于按值捕获的变量是外部变量的副本,修改他们并不会真正影响到外部变量。

  ③lambda表达式在C++11中被称为“闭包类型(Closure Type)”,可以认为它是一个带有operator()的类(即仿函数),它的捕获列表捕获的任何外部变量最终均会变为闭合类型的成员函数。没有捕获变量的lambda表达式可以直接转换为函数指针捕获变量的lambda表达式则不能转换为函数指针。

【编程实验】深入分析lambda表达式

#include <iostream>

using namespace std;

int main()
{
    //1. 按值和按引用捕获的比较
    int a = 12;
    auto f_val = [=] {return a + 1;}; //按值捕获:表达式中a的值是外部变量a的副本,
                                      //在捕获一瞬间己确定下来。
    auto f_ref = [&] {return a + 1;}; //按引用捕获
    
    cout << "f_val: " << f_val() << endl; //13
    cout << "f_ref: " << f_ref() << endl; //13
    
    a++;  //修改a值,a==13
    
    cout << "f_val: " << f_val() << endl;//13,注意这里输出没变!
    cout << "f_ref: " << f_ref() << endl;//14
    
    //2. 修改按值捕获的变量(只能影响lambda表达式,不影响外部变量)
    //auto f1 = [=]{erturn a++;}; //error,operator()是const函数!
    auto f1 = [=]()mutable{cout << "++a: " << ++a << endl; return a;}; //必须写参数列表,即使为空!
    cout << f1() << endl; //14
    cout << a << endl;    //13
    
    //3. lambda与函数指针的转换
    using func_t = int (*)(int, int);
    func_t f2 = [](int a, int b){return a + b;}; //ok,捕获列表必须为空!
    cout << f2(10, 20) << endl; //30
    
    //4. mutable关键字
    int val = 0;
    //auto f3 = [=](){val = 3;}; //编译失败:const函数,不能修改按值捕获的变量
    auto f3 = [=]()mutable{ val = 3;};//ok,加了mutable关键字
    
    auto f4 =[&](){val = 3;};  //ok,依然是const函数,但可以修改按引用捕获的变
                               //量(不过没改动引用本身)
    auto f5 = [=](int v) { v = 3;}; //传参方式将val传
    f5(val); //传参方式将val传入,与普通函数的传参方式等效!
        
    return 0;
}

3. 使用lambda表达式简化代码

(1)lambda被设计出来的主要目的之一就是简化仿函数的使用,使得在调用标准库算法的调用时,可以不必定义函数对象,从而大大简化标准库的使用。

(2)lambda是就地封装的短小功能闭包,将其作为局部函数可以轻松地在函数内重用代码。使得代码更简洁,逻辑更清晰

【编程实验】使用lambda表达式,使代码更简洁、更灵活

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

using namespace std;
using namespace std::placeholders;


const int g_ubound = 10;

vector<int> nums={8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,20};
vector<int> largeNums;

//显示vector元素
void show(vector<int>& vec)
{
    for(auto& elem : vec){
        cout << elem << " ";
    }
    
    cout << endl;
}

//函数指针
inline void LargeNumsFunc(int i)
{
    if(i > g_ubound)
    {
        largeNums.push_back(i);
    }
}

//仿函数
class LargeNums
{
private:
    int ubound;
public:
    LargeNums(int u) : ubound(u){};
    
    void operator() (int i) const
    {
        if(i > ubound){
            largeNums.push_back(i);
        }
    }
};

//找出vector中大于ubound的元素
void test(int ubound)
{
    //1. 使用传统的for
    //缺点: 需要直接使用全局变量ubound
    for(auto iter = nums.begin(); iter != nums.end(); ++iter){
        if(*iter > ubound)
            largeNums.push_back(*iter);
    }
    show(largeNums);
    largeNums.clear();
    
    //2. 使用函数指针:
    //缺点:函数定义在别的地方,代码阅读不方便
    //      inline并非强制,可能导致效率问题,特别是循环次数较多的时候
    //      LargeNumsFunc是个有状态的函数,直接使用了全局变量(g_ubound),
    //      函数的可重用性不高!
    for_each(nums.begin(), nums.end(), LargeNumsFunc);
    show(largeNums);
    largeNums.clear();    
    
    //3. 使用仿函数
    //优点: 仿函数可以拥有状态,由于for_each第3个参数的只能传递一个可调用对象,而不能
    //       传递额外的参数很有利,因为可以将ubound作为仿函数的参数传入。
    //缺点: 需要单独定义一个仿函数类 
    for_each(nums.begin(), nums.end(), LargeNums(ubound));
    show(largeNums);
    largeNums.clear();
    
    //4. 使用lambda表达式
    //优点: 比仿函数书写上更简便,函数的功能(找出大于ubound的元素)更清晰
    for_each(nums.begin(), nums.end(), [=](int i){ //“=”会捕获test(int ubound)中的ubound变量
                  if(i>ubound)
                  largeNums.push_back(i);      
            });
    show(largeNums);
    largeNums.clear();    
}

int main()
{
    //1. 简化标准库的调用(统计(50,73]之间元素的个数)
    vector<int> v{15, 37, 94, 50, 73, 58, 28, 98};
    
    //组合使用bind
    auto f1 = bind(logical_and<bool>(), bind(greater<int>(), _1, 50), 
                                        bind(less_equal<int>(), _1, 73));
    cout << count_if(v.begin(), v.end(), f1) << endl; //2
    
    //使用lambda表达式
    auto f2 = [](int x)->bool {return (50<x)&&(x<=73);};
    cout << count_if(v.begin(), v.end(), f2) << endl; //2
    //cout << count_if(v.begin(), v.end(), [](int x)->bool {return (50<x)&&(x<=73);}) << endl;
    
    //2. lambda表达式与其他callable object的对比
    test(g_ubound);
    
    return 0;
}
/*输出结果
e:\Study\C++11\13>g++ -std=c++11 test3.cpp
e:\Study\C++11\13>a.exe
2
2
11 12 13 14 15 16 17 18 19 20
11 12 13 14 15 16 17 18 19 20
11 12 13 14 15 16 17 18 19 20
11 12 13 14 15 16 17 18 19 20
*/

使用 Swift 开发应用程序 - 第 13 课 - 练习:“isBelow13”

】使用Swift开发应用程序-第13课-练习:“isBelow13”【英文标题】:AppDevelopmentwithSwift-Lesson13-Exercise:"isBelow13"【发布时间】:2016-11-1121:23:23【问题描述】:我对Swift很陌生,我通过使用“使用Swift开发应用程序”-Book来学习。... 查看详情

《英雄编程体验课》第13课|双指针

文章目录零、写在前面一、最长不重复子串1、初步分析2、朴素算法3、优化算法二、双指针1、算法定义2、算法描述3、条件1)单调性2)时效性三、双指针的应用1、前缀和问题2、哈希问题3、K大数问题零、写在前面  该章节节... 查看详情

第13课进阶面向对象(上)

日常生活中,我们都习惯于对事物进行分类,那么这种分类思想是否可以引入程序设计中呢?面向对象的基本概念:     类和对象的意义:  一些有趣的问题;小结:  查看详情

第13课字典(代码片段)

一、字典1、字典的定义:  <class‘dict‘>>>>dict1=>>>type(dict1)<class‘dict‘>2、dict1=键1:值,键2:值dict2=‘name‘:‘Tom‘,‘age‘:18,‘weight‘:1303、键值成对出现,不然会报错。>>>dict2 查看详情

行为参数化与lambda表达式-读《java8实战》

零、概述第一部分:1~3章主要讲了行为参数化和Lambda表达式第二部分:4~7章主要讲了流的应用,包括流与集合差异,流的操作,收集器,注的并行执行第三部分:8~12章主要讲了怎样用Java8引入的特性改善老代码,Optional类和Complet... 查看详情

第13课:常用dos命令

网络命令ping进行网络连接测试、名称解析ftp文件传输net网络命令集及用户管理telnet远程登陆ipconfig显示、修改TCP/IP设置msg给用户发送消息arp显示、修改局域网的IP地址-物理地址映射列表 ping 1.Ping命令的语法格式  ... 查看详情

python学习第六篇:lambda表达式(代码片段)

​Python学习第六篇:lambda表达式——活动地址:CSDN21天学习挑战赛lambda函数Python中使用lambda表达式表示匿名函数,允许用lambda关键字创造"匿名函数",可以不给函数分配名称。匿名是不需要以标准的方式来声... 查看详情

《java高级语法》第12节:lambda表达式

​Lambda表达式是JDK8的一个新特性,它可以定义大部分的匿名内部类,从而让程序员能写出更优雅的Java代码,尤其在集合的各种操作中可以极大地优化代码结构。8.12.1认识Lambda表达式一个接口的实现类可以被定义为匿名类。经过... 查看详情

scala实战高手****第13课scala模式匹配实战和spark源码鉴赏

packagecom.dt.spark.scala.bascisclassDataframeworkcaseclassComputerframework(name:String,popular:Boolean)extendsDataframeworkcaseclassStorgeframework(name:String,popular:Boolean)extendsDataframeworkob 查看详情

第12课std::bind和std::function_std::function可调用对象包装器

...针之外的所有可调用对象。(2)可以将普通函数,lambda表达式和函数对象类统一起来。尽管它们并不是相同的类型,但通过function类模板,可以转化为相同类型的对象(function对象),这样就可以用统一的方式来保存或传递可调... 查看详情

重学java8新特性|第3讲——我们为什么要使用lambda表达式?

...c;我就已经讲过了,Java8新特性中最为核心的便是Lambda表达式与StreamAPI,只不过这一讲我首先会为大家讲解Lambda表达式。其实,Lambda表达式就是Java8提出的一种新的语法格式。在讲这个语法格式之前,我们首先得了... 查看详情

第13课:sparkstreaming源码解读之driver容错安全性

本期内容:ReceivedBlockTracker容错安全性DStream和JobGenerator容错安全性Driver的容错有两个层面:1.Receiver接收数据的元数据2.Driver管理的各组件信息(调度和驱动层面)元数据采用了WAL的容错机制case AddBlock(receivedBlockInfo) =>&nb... 查看详情

第0课-makefile引言

Makefile引言第1课-make和makefile第2课-初识makefile的结构第3课-makefile伪目标的引入第4课-变量和不同的赋值方式第5课-预定义变量的使用第6课-变量的高级主题(上)第7课-变量的高级主题(下)第8课-条件判断语句第9课-函数定义及调... 查看详情

第39课逗号操作符的分析(代码片段)

1. 逗号操作符(,)(1)逗号表达式用于将多个子表达式连接为一个表达式(2)逗号表达式的值为最后一个子表达式的值(3)逗号表达式的前N-1个子表达式可以没有返回值,最后一个要有返回值(4)逗号表达式按照从左向... 查看详情

重学java8新特性|第4讲——lambda表达式详解(代码片段)

文章目录Lambda表达式是什么?Lambda表达式的基础语法语法格式一:无参数,且无返回值语法格式二:有一个参数,并且无返回值语法格式三:若只有一个参数,参数的小括号可以省略不写语法格式四... 查看详情

重学java8新特性|第3讲——我们为什么要使用lambda表达式?(代码片段)

...c;我就已经讲过了,Java8新特性中最为核心的便是Lambda表达式与StreamAPI,只不过这一讲我首先会为大家讲解Lambda表达式。其实,Lambda表达式就是Java8提出的一种新的语法格式。在讲这个语法格式之前,我们首先得了... 查看详情

jdk8新特性之lambda表达式(代码片段)

目录Lambda表达式1.需求分析2.Lambda表达式初体验3.Lambda的语法规则3.1Lambda练习13.2Lambda练习24.@FunctionalInterface注解5.Lambda表达式的原理6.Lambda表达式的省略写法7.Lambda表达式的使用前提8.Lambda和匿名内部类的对比Lambda表达式Lambda表达... 查看详情

《jdk8新特性专题》-01lambda表达式(代码片段)

文章目录1.Lambda表达式简介1.1.需求分析1.2.Lambda表达式初体验1.3.Lambda的语法规则2.Lambda练习13.Lambda练习25.@FunctionalInterface注解6.Lambda表达式的原理7.Lambda表达式的省略写法8.Lambda表达式的使用前提9.Lambda和匿名内部类的对比1.Lambda... 查看详情