首页 > 编程语言 >大话C++:第27篇 Lambda表达式

大话C++:第27篇 Lambda表达式

时间:2024-11-05 13:47:04浏览次数:4  
标签:27 变量 int 捕获 C++ 按值 表达式 Lambda

1 Lambda表达式介绍

在C++中,lambda表达式(也称为闭包)是一种可以定义匿名函数对象的便捷方式。它们能够捕获所在作用域中的局部变量,并且可以在需要函数对象的地方使用。Lambda表达式为C++提供了更简洁、更灵活的函数式编程方式。

Lambda表达式的基本语法如下:

[capture](parameters) mutable-> return_type { body_of_lambda }

其中,

  • capture:捕获子句,用于指定lambda可以访问的外部变量的列表。捕获可以是按值(=)或按引用(&),或者显式列出要捕获的变量。

  • parameters:lambda函数的参数列表,与普通函数的参数列表类似。

  • mutable可选的。如果指定了mutable,那么lambda体内部可以修改通过值捕获的外部变量的值。

  • return_type:返回类型。如果省略,编译器会根据lambda体中的返回语句自动推断返回类型。

  • body_of_lambda:lambda函数的主体,即包含函数要执行的代码块。

例如,计算两个整数之和

#include <iostream>
#include <functional>


int main() 
{
    int num1 = 5;
    int num2 = 10;

    // 使用lambda表达式计算两数之和,并显式指定参数和返回类型
  	// std::function<int(int, int)>也可以直接采用auto进行声明定义sum
    std::function<int(int, int)> sum = [](int x, int y) -> int
    {
        return x + y;
    };

    // 调用lambda表达式并输出结果,显式声明变量类型
    int res = sum(num1, num2);
    std::cout << "两数之和:" << res << std::endl;

    return 0;
}

其中,std::function来存储Lambda表达式,因为它是一个通用、多态的函数包装器,可以存储、复制和调用任何可调用的目标,包括Lambda表达式、函数指针和其他函数对象。

2 Lambda表达式捕获列表

Lambda捕获列表决定了Lambda表达式可以访问哪些外围作用域的变量以及以何种方式访问它们。捕获列表决定了Lambda表达式内部对外部变量的访问权限和生命周期。

捕获列表的可以是以下几种形式之一:

  • 空捕获列表 []:不捕获任何外部变量。Lambda表达式内部不能访问任何外部作用域的变量(除了全局变量和静态变量)。

  • 按值捕获 [=]:以值的方式捕获所有外部变量。Lambda表达式内部可以访问并修改这些变量的副本,但修改不会影响原始变量。

  • 按引用捕获 [&]:以引用的方式捕获所有外部变量。Lambda表达式内部可以访问并修改这些变量的原始值,对变量的修改会影响到原始变量。

  • 显式捕获:可以显式指定要捕获的变量及其捕获方式。例如,[x, &y] 表示按值捕获变量x,按引用捕获变量y

  • 混合捕获:可以在同一个捕获列表中混合使用按值和按引用捕获。例如,[=, &z] 表示按值捕获所有外部变量,但z是按引用捕获的。

除此之外,捕获列表还可以包含mutable关键字,它允许在const环境中修改Lambda捕获的按值捕获的变量。

2.1 空捕获列表

当使用空捕获列表[]时,Lambda表达式不能访问任何外部作用域的变量(除了全局变量和静态变量)。这意味着Lambda表达式内部只能使用其参数和内部定义的局部变量。

#include <iostream>

int main() 
{
    int x = 10;
  
    auto lambda = []() 
    {
        // 不能访问x,因为使用了空捕获列表
        // std::cout << x << std::endl; // 错误
        std::cout << "Lambda表达式空捕获列表" << std::endl;
    };
  
    lambda();
    return 0;
}

2.2 按值捕获

按值捕获[=]是Lambda表达式将捕获所有外部作用域的变量,并以副本的形式在Lambda内部使用。这些副本是在Lambda创建时创建的,因此Lambda内部对副本的修改不会影响原始变量。

#include <iostream>

int main() 
{
    int x = 10;
    int y = 20;
    auto lambda = [=]() 
    {
        // 访问的是x和y的副本
        std::cout << "Lambda表达式按值捕获 x: " << x << std::endl;
        std::cout << "Lambda表达式按值捕获 y: " << y << std::endl;
    };
    
    // 修改x和y的值
    x = 30;
    y = 40;
    
    // 输出x和y的原始值,而不是修改后的值
    lambda(); 
    
    return 0;
}

2.3 按引用捕获

按引用捕获[&]是Lambda表达式将捕获所有外部作用域的变量的引用。Lambda内部可以直接访问和修改这些变量的原始值,修改将反映到原始变量上。

#include <iostream>

int main() 
{
    int x = 10;
    int y = 20;
  
  	// 访问的是x和y的引用
    auto lambda = [&]() 
    {
        // 修改x和y的值
        x = 30; 
      	y = 40;
        std::cout << "Lambda表达式按引用捕获x: " << x << std::endl;
        std::cout << "Lambda表达式按引用捕获y: " << y << std::endl;
    };
  
  	// 输出修改后的x和y的值
    lambda(); 
  
    std::cout << "Lambda表达是修改后的x: " << x << std::endl;
  	std::cout << "Lambda表达是修改后的y: " << y << std::endl;
  
    return 0;
}

2.4 显式捕获

显式捕获允许你选择性地捕获特定的外部变量,并可以为每个变量指定捕获方式(按值或按引用)。

#include <iostream>

int main() 
{
    int x = 10;
    int y = 20;
    int z = 30;
    auto lambda = [x, &y](int a) 
    {
        // x是按值捕获的
        // y是按引用捕获的
        // z没有被捕获,因此不可访问
        std::cout << "Lambda表达式显示捕获x : " << x << std::endl;
        std::cout << "Lambda表达式显示捕获y: " << y << std::endl;
        // std::cout << "z: " << z << std::endl; // 错误,z没有被捕获
    };
  
    y = 40; // 修改y的值
    lambda(5); // 输出x的原始值和修改后的y的值
  
    return 0;
}

2.5 混合捕获

混合捕获允许你在同一个捕获列表中同时使用按值和按引用捕获。这提供了一种灵活的方式来决定哪些变量应该被按值捕获,哪些应该被按引用捕获。

#include <iostream>
#include <vector>

int main() 
{
    int a = 10; // 一个不会被修改的变量,可以按值捕获
    int b = 20; // 一个可能会被Lambda内部修改的变量,应该按引用捕获
    std::vector<int> vec = {1, 2, 3, 4, 5}; // 一个容器,我们可能想要修改它的内容,应该按引用捕获

    // 使用混合捕获:a按值捕获,b和vec按引用捕获
    auto lambda = [a, &b, &vec]() 
    {
        std::cout << "Lambda表达式混合捕获a (按值方式): " << a << std::endl;
        std::cout << "Lambda表达式显示捕获b (按引用方式),修改前值: " << b << std::endl;
        std::cout << "Lambda表达式显示捕获vec(按照引用方式),原始数组长度: " << vec.size() << std::endl;

        // 修改按引用捕获的变量和容器
        b = 30;
        vec.push_back(6);

        std::cout << "Lambda表达式显示捕获b (按引用方式),修改后值: " << b << std::endl;
        std::cout << "Lambda表达式显示捕获vec(按照引用方式),修改后数组长度: " << vec.size() << std::endl;
    };

    // 调用Lambda
    lambda();

    // 输出Lambda外部修改后的b的值和vec的大小,以验证Lambda内部对它们的修改是有效的
    std::cout << "调用Lambda表达式后,b的值是: " << b << std::endl;
    std::cout << "调用Lambda表达式后,数组长度是: " << vec.size() << std::endl;

    return 0;
}

3 Lambda表达式作为返回值

Lambda表达式可以像其他任何对象一样被返回。我们需要定义一个返回函数对象(比如闭包)的函数时特别有用。Lambda表达式可以捕获其所在作用域的变量,并且返回后可以在其他地方调用。

#include <iostream>
#include <functional>

// 一个函数,返回一个Lambda表达式
std::function<int(int)> GetMultiplier(int factor) 
{
    return [factor](int x) 
    {
        return factor * x;
    };
}

int main() 
{
    // 获取一个Lambda表达式,该表达式表示乘以3的操作
    std::function<int(int)> multiplier = GetMultiplier(3);

    // 使用返回的Lambda表达式
    std::cout << "乘以3的结果: " << multiplier(5) << std::endl;

    // 获取另一个Lambda表达式,这次表示乘以5的操作
    multiplier = GetMultiplier(5);

    // 使用新的Lambda表达式
    std::cout << "乘以5的结果: " << multiplier(5) << std::endl; // 输出 25

    return 0;
}

代码示例中,GetMultiplier函数接受一个整数factor,并返回一个Lambda表达式,该表达式接受另一个整数x并返回factorx的乘积。Lambda表达式捕获了factor变量,并可以在调用时用它来计算乘积。

4 Lambda表达式注意事项

在使用Lambda表达式时,需要注意以下事项:

  • 捕获列表的使用:

    • 按值捕获:如果Lambda表达式捕获了按值传递的局部变量,并且这些变量在Lambda被调用时不再有效(例如,它们位于Lambda定义的作用域之外,并且已经被销毁),则会出现错误。通常,最好捕获值类型的常量或不可变数据。

    • 按引用捕获:按引用捕获的变量必须确保在Lambda表达式被调用时仍然有效。如果Lambda的生命周期超过了捕获的变量的生命周期,这将会导致悬挂引用,是非常危险的。

    • 避免捕获大对象:按值捕获大对象或容器可能会导致不必要的性能开销,因为会创建这些对象的副本。如果可能,最好通过引用捕获或使用指针。

  • Lambda表达式的生命周期:

    • Lambda表达式具有其自己的生命周期,并与它被定义的位置和作用域无关。它们可以被存储在变量中、作为参数传递或返回。

    • 需要确保Lambda表达式使用的所有资源(如动态分配的内存)在Lambda不再需要时得到正确释放。

  • 闭包行为:Lambda表达式可以形成闭包,这意味着它们可以访问并操作其定义时所在的词法作用域中的变量。必须谨慎处理这些变量的修改,以避免意外的副作用。

  • 可调用性:Lambda表达式是可调用的对象,因此它们可以作为函数参数传递,也可以赋值给函数指针或std::function对象。但是,传递给它们的参数类型和返回类型必须与Lambda表达式的签名匹配。

  • 类型推断:Lambda表达式的返回类型通常是自动推断的。如果Lambda体中的代码路径不一致(例如,有些路径返回int,有些路径返回float),则可能会导致编译错误。

  • 异常安全性:如果Lambda表达式可能抛出异常,需要确保调用它的代码能够妥善处理这些异常。避免在可能抛出异常的代码块中使用Lambda,除非已经正确实现了异常处理。

  • 可读性和可维护性:Lambda表达式可以提高代码的简洁性和灵活性,但过度使用可能会降低代码的可读性和可维护性。在适当的地方使用Lambda表达式,并在必要时添加注释来解释其行为。

  • 线程安全性:如果Lambda表达式将在多线程环境中使用,必须确保它是线程安全的。这包括避免共享可变状态、使用线程安全的容器和算法,以及正确处理同步问题。


欢迎您同步关注我们的微信公众号!!!

标签:27,变量,int,捕获,C++,按值,表达式,Lambda
From: https://blog.csdn.net/whccf/article/details/143506932

相关文章

  • Python学习的自我理解和想法(27)
    学的是b站的课程(千锋教育),跟老师写程序,不是自创的代码!今天是学Python的第27天,学的内容是python操作pptx和pdf,但是这节博客只会介绍如何新建pptx和加密pdf。开学了,时间不多,写得不多,见谅。目录1.安装必要的库2.创建PPTX(1).创建幻灯片对象(2).选择母版添加一页(3).获取标题......
  • C++ OpenCv二值化找圆心坐标
    思路:图像转为灰度图,然后二值化得到只有0或255的点坐标,此处圆是黑点,所以添加所有像素值为0的坐标。在这些坐标中找到圆上最左边、最顶端、最右边、最底端的四个点,这时可求出圆心坐标。 .cpp文件#include<opencv2/opencv.hpp>#include<iostream>#include<cstdlib>#include......
  • (3)---【C语言】【GL库】【计算机图形学】DEV C++ 平台openGL库 下的画线图案设计 房
    声明:        由于本人是一名学生,现阶段还要完成学业,所以我们每周假期再回!谢谢大家理解和支持!上篇上手实践  运行结果 实现代码#include<windows.h>#defineGLUT_DISABLE_ATEXIT_HACK//处理不同系统的配置问题的宏#include<GL/glut.h>#include<std......
  • 每日OJ题_牛客_相差不超过k的最多数_滑动窗口_C++_Java
    目录牛客_相差不超过k的最多数_滑动窗口题目解析C++代码Java代码牛客_相差不超过k的最多数_滑动窗口相差不超过k的最多数_牛客题霸_牛客网(nowcoder.com)描述:        给定一个数组,选择一些数,要求选择的数中任意两数差的绝对值不超过 k 。问最多能选择多少......
  • 【c++篇】:深入剖析vector--模拟实现属于自己的c++动态数组
    ✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨✨个人主页:余辉zmh–CSDN博客✨文章所属专栏:c++篇–CSDN博客文章目录前言一.`vector`类的默认成员函数整体框架构造函数析构函数拷贝构造函数赋值运算符重载函数测试二.`vector`......
  • JSP毕业设计1927鞋城网站设计与实现源码//潮鞋网站/潮鞋商城
    项目包含:源码、参考论文、讲解视频、说明文档请查看博主个人简介运行环境:推荐jdk1.8开发工具:Eclipse、MyEclipe以及idea(推荐)操作系统:windows108G内存以上(其他windows)浏览器:GoogleChrome(推荐)、Edge、360浏览器;数据库:MySQL5.7;数据库可视化工具:NavicatPremium推......
  • C++ 逆序乘积式
     题目描述【问题描述】若两个正整数的乘积,等于两正整数各自逆序后的乘积,则称其为逆序乘积式。编写程序读入两个正整数,然后判断这两个正整数能否构成逆序乘积式。假设两个正整数的乘积不会超过int数据类型的表示范围。【输入形式】从控制台输入以一个空格分隔的两个正整数......
  • HarmonyOS 开发实践——基于HAR的跨模块C++头文件引用
    ......
  • 【热门主题】000027 React:前端框架的强大力量
    前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦......