7.3 lambda函数

一、语法

1.基础语法

[capture](paramLists) mutable ->retunType{statement}
  • capture。捕获列表,用于捕获前文的变量供lambda函数中使用,可省略。
  • (paramLists)。参数列表,可省略。
  • mutable。lambda表达式默认具有常量性,可以通过mutable取消常量性,可省略。
  • returnType。函数返回类型,可省略。
  • statement。函数体,可省略。

结合上述可省略的内容,C++11中最简单的lambda表达式可以是(虽然没有实际意义):

[]{}

2.捕获列表

lambda函数的与普通函数最大的区别在于可以捕获前文的局部变量(仅仅对于局部而言,如果是全局lambda函数则不支持)。而捕获的方式有:

  • [var]表示值传递方式捕获变量var
  • [=]表示值传递方式捕获父作用域所有变量(包括this)
  • [&var]表示引用传递方式捕获变量var
  • [&]表示引用传递方式捕获父作用域所有变量(包括this)
  • [this]表示值传递方式捕获变量this

而由于捕获列表支持多个值(用,分隔),因此可以进行组合:

  • [=,&a,&b]表示引用传递捕获a,b,值传递捕获其他内容。
  • [&,a,this]表示值传递捕获a,this,引用传递捕获其他内容。

需要注意的是,捕获列表不能重复,如

[=,a,b]或者[&,&a,&b]等都是重复捕获(以相同的传递方式捕获同一个变量)。

3.基础使用

lambda函数通常用于局部作用域作为局部[匿名]函数。

        extern int z;
        extern float c;
        void Calc(int& , int, float &, float);
        void TestCalc() {
            int x, y = 3;
            float a, b = 4.0;
            int success = 0;
            auto validate = [&]() -> bool
            {
                if ((x == y + z) && (a == b + c))
                    return 1;
                else
                    return 0;
            };
            Calc(x, y, a, b);
            success += validate();
            y = 1024;
            b = 1e13;
            Calc(x, y, a, b);
            success += validate();
        }
        // 编译选项:g++ -c -std=c++11 7-3-7.cpp

而在有时会通过auto为lambda函数命名,使其获得自说明性。

与普通函数相比lambda有如下优势:

  • 支持直接在函数内创建,作用域外释放,而不用额外创建一个函数。
  • 能够直接捕获所有局部变量,而普通函数则需要额外传递。
  • lambda函数默认内联,在较多次调用时性能比普通函数好。
  • lambda函数的设计更简单,不需要考虑参数传递等问题

二、关于lambda的一些实验与讨论

1.捕获参数的传递方式

lambda函数中不同的捕获传递方式会造成不同的结果,对于值传递,则在传递的值在编译期就确定了,无法被修改,而对于引用传递则可以同步lambda函数外的修改。

        #include <iostream>
        using namespace std;
        int main() {
            int j = 12;
            auto by_val_lambda = [=] { return j + 1;};
            auto by_ref_lambda = [&] { return j + 1;};
            cout << "by_val_lambda: " << by_val_lambda() << endl;
            cout << "by_ref_lambda: " << by_ref_lambda() << endl;
            j++;
            cout << "by_val_lambda: " << by_val_lambda() << endl;
            cout << "by_ref_lambda: " << by_ref_lambda() << endl;
        }

运行结果:

        by_val_lambda: 13
        by_ref_lambda: 13
        by_val_lambda: 13
        by_ref_lambda: 14

2.与函数指针的关系

lambda函数与函数指针看起来很相似,但是实际上却不是函数指针,它是一种称为"闭包"(closure)的类。

这种类型支持向函数指针转换,前提是:

  • lambda函数不捕获任何变量
  • 函数指针的原型与lambda一致(参数,返回值都完全一致)
        int main() {
            int girls = 3, boys = 4;
            auto totalChild = [](int x, int y)->int{ return x + y; };
            typedef int (*allChild)(int x, int y);
            typedef int (*oneChild)(int x);
            allChild p;
              p = totalChild;
              oneChild q;
              q = totalChild;      // 编译失败,参数必须一致
              decltype(totalChild) allPeople = totalChild;   // 需通过decltype获得lambda的类型
              decltype(totalChild) totalPeople = p;       // 编译失败,指针无法转换为lambda
              return 0;
          }
          // 编译选项:g++ -std=c++11 7-3-10.cpp

此外,不支持函数指针向lambda转换

3.常量性与mutable

前面提到对于值传递的捕获参数具有常量性无法被修改,而想要打破这一限制,可以加上mutable关键字。(注意虽然可以修改,但仍然不影响父作用域变量)

#include <iostream>
int main() {
	int val=0;
	// 编译失败, 在const的lambda中修改常量
	//auto const_val_lambda = [=]() { val = 3; };
	// 非const的lambda,可以修改常量数据
	auto mutable_val_lambda = [=]() mutable { val = 3; };
	mutable_val_lambda();
	std::cout << val << std::endl;
	// 依然是const的lambda,不过没有改动引用本身
	auto const_ref_lambda = [&] { val = 4; };
	const_ref_lambda();
	std::cout << val << std::endl;
	// 依然是const的lambda,通过参数传递val
	auto const_param_lambda = [&](int v) { v = 5; };
	const_param_lambda(val);
	std::cout << val << std::endl;
	return 0;
}

而对于引用传递方式,则表示lambda捕获的参数引用了父作用域的变量,一边修改都会同步到另一边。

三、lambda与STL

前面说到,lambda对C++11最大的贡献,或者说是改变,应该在STL库中。这主要体现于STL算法更加容易,也更加容易学习了(可读性更高)。

下面将以for_each为例,讲述lambda带来的便捷。

#include <vector>
#include <algorithm>
using namespace std;
vector<int> nums;
vector<int> largeNums;
const int ubound = 10;
inline void LargeNumsFunc(int i){
    if (i > ubound)
        largeNums.push_back(i);
}
void Above() {
    // 传统的for循环
    for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
        if (*itr >= ubound)
            largeNums.push_back(*itr);
    }
    // 使用函数指针
    for_each(nums.begin(), nums.end(), LargeNumsFunc);
    // 使用lambda函数和算法for_each
    for_each(nums.begin(), nums.end(), [=](int i){
        if (i > ubound)
            largeNums.push_back(i);
    });
}
编译选项: g++ 7-3-13.cpp -c -std=c++11

这是通过基础for循环、for_each和lambda实现查找大于某个值的功能。相比for循环而言,for_each只需要关心数据起始点,并将每个元素作用到指定的操作上即可,在效率、正确性、可维护性上都具有一定优势。

而lambda较for_each而言,首先其函数内容会直接放在调用处,可阅读性更高(当然,有时也会被分离出来并命名,但通常不会太远);其次使用函数指针很可能导致编译器不对其进行inline优化(inline对编译器而言并非强制),在循环次数较多的时候,内联的lambda和没有能够内联的函数指针可能存在着巨大的性能差别。

此外相较于仿函数(不论是自己实现还是内置仿函数),lambda也依旧存在着不小的优势。

#include <vector>
#include <algorithm>
using namespace std;
vector<int> nums;
vector<int> largeNums;
class LNums{
public:
LNums(int u): ubound(u){}
void operator () (int i) const
{
    if (i > ubound)
        largeNums.push_back(i);
}
private:
int ubound;
};
void Above(int ubound) {
    // 传统的for循环
    for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
        if (*itr >= ubound)
            largeNums.push_back(*itr);
    }
    // 使用仿函数
    for_each(nums.begin(), nums.end(), LNums(ubound));
    // 使用lambda函数和算法for_each
    for_each(nums.begin(), nums.end(), [=](int i){
        if (i > ubound)
            largeNums.push_back(i);
    });
}

对于自己实现的仿函数,很直观的,lambda更加简洁。

而当面对更加复杂的场景时,lambda显得更加有优势:

#include <vector>
#include <algorithm>
using namespace std;
extern vector<int> nums;
void TwoCond(int low, int high) {
    // 传统的for循环
    for (auto i = nums.begin(); i != nums.end(); i++)
        if (*i >= low && *i < high) break;
    // 利用了3个内置的仿函数,以及非标准的compose2
    find_if(nums.begin(), nums.end(),
        compose2(logical_and<bool>(),
        bind2nd(less<int>(), high),
        bind2nd(greater_equal<int>(), low)));
    // 使用lambda函数
    find_if(nums.begin(), nums.end(), [=](int i) {
        return i >= low && i < high;
    });
}

这里我们需找到vector nums中第一个值介于[low, high)间的元素,可以看到内置仿函数变得异常复杂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/254546.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python识别增强静脉清晰度 opencv-python图像处理案例

一.任务说明 用python实现静脉清晰度提升。 二.代码实现 import cv2 import numpy as npdef enhance_blood_vessels(image):# 调整图像对比度和亮度enhanced_image cv2.convertScaleAbs(image, alpha0.5, beta40)# 应用CLAHE&#xff08;对比度受限的自适应直方图均衡化&…

Future CompleteFuture

前言 Java8 中的 completeFuture 是对 Future 的扩展实现&#xff0c;主要是为了弥补 Future 没有相应的回调机制的缺陷。 Callable、Runnable、Future、CompletableFuture 之间的关系&#xff1a; Callable&#xff0c;有结果的同步行为&#xff0c;比如做蛋糕&#xff0c;…

python程序打包成exe全流程纪实(windows)

目录 前言准备工作安装python&#xff08;必须&#xff09;安装vs平台或conda&#xff08;非必须&#xff09; 详细步骤Step1.创建python虚拟环境方法一、裸装(windows下)方法二、借助工具(windows下) Step2.安装打包必须的python包Step3.准备好程序logo&#xff08;非必须&…

51单片机定时器

51单片机有两个16位定时器&#xff0c;今天复习了一下使用方法&#xff0c;发现当初刚开始学习51单片机时并没有记录&#xff0c;特此今天补上这篇博客。 下面是定时器的总览示意图&#xff0c;看到这个图就能想到定时器怎么设置&#xff0c;怎么开始工作。 第一步&#xff1a…

刷完这个笔记,18K不能再少了....

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备年后面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿到…

EmbedAI:一个可以上传文件训练自己ChatGPT的AI工具,妈妈再也不用担心我的GPT不会回答问题

功能介绍&#xff1a; 个性化定制&#xff1a;提供灵活的训练选项&#xff0c;用户能够通过文件、网站、Notion文档甚至YouTube等多种数据源对ChatGPT进行训练&#xff0c;以满足不同领域和需求的个性化定制。广泛应用场景&#xff1a;ChatGPT支持多种用例&#xff0c;包括智能…

Jmeter吞吐量控制器使用小结

吞吐量控制器(Throughput Controller)场景: 在同一个线程组里, 有10个并发, 7个做A业务, 3个做B业务,要模拟这种场景,可以通过吞吐量模拟器来实现.。 添加吞吐量控制器 用法1: Percent Executions 在一个线程组内分别建立两个吞吐量控制器, 分别放业务A和业务B 吞吐量控制器采…

【算法系列篇】递归、搜索和回溯(三)

文章目录 前言什么是决策树1. 全排列1.1 题目要求1.2 做题思路1.3 代码实现 2. 子集2.1 题目要求2.2 做题思路2.3 代码实现 3. 找出所有子集的异或总和再求和3.1 题目要求3.2 做题思路3.3 代码实现 4. 全排列II4.1 题目要求4.2 做题思路4.3 代码实现 前言 前面我们通过几个题目…

蚂蚁集团5大开源项目获开放原子 “2023快速成长开源项目”

12月16日&#xff0c;在开放原子开源基金会主办的“2023开放原子开发者大会”上&#xff0c;蚂蚁集团主导开源的图数据库TuGraph、时序数据库CeresDB、隐私计算框架隐语SecretFlow、前端框架OpenSumi、数据域大模型开源框架DB-GPT入选“2023快速成长开源项目”。 &#xff08;图…

Kafka中Ack应答级别和数据去重

在Kafka中&#xff0c;保证数据安全可靠的条件是&#xff1a; 数据完全可靠条件 ACK级别设置为-1 分区副本大于等于2 ISR里应答的最小副本数量大于等于2&#xff1b; Ack应答级别 可靠性总结&#xff1a; acks0&#xff0c;生产者发送过来数据就不管了&#xff0c;可靠性差…

2023年国赛高教杯数学建模D题圈养湖羊的空间利用率解题全过程文档及程序

2023年国赛高教杯数学建模 D题 圈养湖羊的空间利用率 原题再现 规模化的圈养养殖场通常根据牲畜的性别和生长阶段分群饲养&#xff0c;适应不同种类、不同阶段的牲畜对空间的不同要求&#xff0c;以保障牲畜安全和健康&#xff1b;与此同时&#xff0c;也要尽量减少空间闲置所…

人工智能深度学习:探索智能的深邃奥秘

导言 人工智能深度学习作为当今科技领域的明星&#xff0c;正引领着智能时代的浪潮。深度学习和机器学习作为人工智能领域的两大支柱&#xff0c;它们之间的关系既有协同合作&#xff0c;又存在着显著的区别。本文将深入研究深度学习在人工智能领域的角色&#xff0c;以及其在各…

Android Termux安装MySQL数据库并通过内网穿透实现公网远程访问

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…

鸿蒙端H5容器化建设——JSB通信机制建设

1. 背景 2023年鸿蒙开发者大会上&#xff0c;华为宣布为了应对国外技术封锁的潜在风险&#xff0c;2024年的HarmonyOS NEXT版本中将不再兼容Android&#xff0c;并推出鸿蒙系统以及其自研的开发框架&#xff0c;形成开发生态闭环。同时&#xff0c;在更高维度上华为希望将鸿蒙…

GPT-4V被超越?SEED-Bench多模态大模型测评基准更新

&#x1f4d6; 技术报告 SEED-Bench-1&#xff1a;https://arxiv.org/abs/2307.16125 SEED-Bench-2&#xff1a;https://arxiv.org/abs/2311.17092 &#x1f917; 测评数据 SEED-Bench-1&#xff1a;https://huggingface.co/datasets/AILab-CVC/SEED-Bench SEED-Bench-2&…

基于主动安全的AIGC数据安全建设

面对AIGC带来的数据安全新问题&#xff0c;是不是就应该一刀切禁止AIGC的研究利用呢&#xff1f;答案是否定的。要发展AIGC&#xff0c;也要主动积极地对AIGC的数据安全进行建设。让AIGC更加安全、可靠的为用户服务。为达到此目的&#xff0c;应该从三个方面来开展AIGC的数据安…

C++中的并发多线程网络通讯

C中的并发多线程网络通讯 一、引言 C作为一种高效且功能强大的编程语言&#xff0c;为开发者提供了多种工具来处理多线程和网络通信。多线程编程允许多个任务同时执行&#xff0c;而网络通信则是现代应用程序的基石。本文将深入探讨如何使用C实现并发多线程网络通信&#xff…

【Netty】Netty核心概念

目录 NIO编程NIO介绍NIO和BIO的比较缓冲区(Buffer)基本介绍常用API缓冲区对象创建添加数据读取数据 通道(Channel)基本介绍Channel常用类ServerSocketChannelSocketChannel Selector (选择器)基本介绍常用API介绍示例代码 NIO 三大核心原理 Netty核心概念Netty 介绍原生 NIO 存…

verilog基础语法-计数器

概述&#xff1a; 计数器是FPGA开发中最常用的电路&#xff0c;列如通讯中记录时钟个数&#xff0c;跑马灯中时间记录&#xff0c;存储器中地址的控制等等。本节给出向上计数器&#xff0c;上下计数器以及双向计数器案例。 内容 1. 向上计数器 2.向下计数器 3.向上向下计数…

Minio文件服务器(上传文件)

官网&#xff1a;https://www.minio.org.cn/ 开源的分布式对象存储服务器 Window安装 用户名和密码相同 创建bucket&#xff0c;并且将策略改成public 一、添加依赖 二、代码 public class FileUploadTest{public static void main(String[] args) throws Exception{//…