C++的匿名函数(lambda表达式)

简介: C++的匿名函数(lambda表达式)

总述

C++11提供了对匿名函数的支持,称为Lambda函数(也叫Lambda表达式). 它是定义和使用匿名函数对象的一种简便的方式。匿名函数是我们需要用到的一个函数,但是又不想去费力命名一个函数的场景。我们无需为每个值或者每种类型)单独编写函数,更不必把值保存在让人厌倦的全局变量中 。 利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读。

   工作的时候这个是比较常用的,通过匿名函数进行函数内部变量的捕获,继而进行操作变量等。那么现在就由我来给大家分享一下,我对Lambda表达式的认知,仅作为一个基础的介绍哈,毕竟C++博大精深,吾辈还需要深究。

作者:良知犹存

转载授权以及围观:欢迎添加微信公众号:Conscience_Remains


1 它的结构

一条lambda表达式一般会有以下部分:

1.一个可能为空的捕获列表,指明定义环境中的那些名字能被用在lambda表达式内,以及这些名字的访问形式拷贝还是引用,捕获列表位于 [] 内。

2.一个可选的参数列表,指明lambda表达式所需的参数,参数列表位于 () 内。

3.一个可以选的mutable修饰符,指明该lambda表达式可能会修改它自身的状态(即,改变通过值捕获的变量的副本)

4.一个可选的 -> 形式的返回类型声明  

5.一个表达式体,指明要执行的代码,表达式位于 {} 内。

[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
}

上面的语法规则除了 [捕获列表] 内的东西外,其他部分都很好理解,只是一般函数的函数名被略去, 返回值使用了一个 -> 的形式进行。

所谓捕获列表,其实可以理解为参数的一种类型,lambda 表达式内部函数体在默认情况下是不能够使用函数体外部的变量的, 这时候捕获列表可以起到传递外部数据的作用。

在lambda中,传参、返回结果以及定义表达式体和普通的函数都是一致的,区别就在于普通函数没有提供局部变量“捕获”功能,而局部捕获的功能,就意味着lambda可以做局部函数使用,而普通函数不能。

展示一个小例子证明lambda表达式的简洁性:

Greater than 是一个函数对象,保存了要比较的值:

struct Greater_than {
  int val;
  Greater_than(lnt v) : val{v} { };
  bool operatorO(const pair<string.int>& r) { return r.second>val;}
};

我们也可以使用 lambda 表达式 :

auto p =find_if(m.beginO, m.endO,
 [](const pair<string, int>& r) { return r.second>42; }); 

每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。


2 基本的参数分析

C++11中的Lambda表达式捕获外部变量主要有以下形式:

[]:默认不捕获任何变量;

[=]:默认以值捕获所有变量;

[&]:默认以引用捕获所有变量;

[x]:仅以值捕获x,其它变量不捕获;

[&x]:仅以引用捕获x,其它变量不捕获;

[=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;

[&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;

[this]:通过引用捕获当前对象(其实是复制指针);

[*this]:通过传值方式捕获当前对象;

   在上面的捕获方式中,注意最好不要使用[=]和[&]默认捕获所有变量。首先说默认引用捕获所有变量,你有很大可能会出现悬挂引用(Dangling references),因为引用捕获不会延长引用的变量的声明周期,例如一个形参传进来我们进行捕获并作为一个返回值执行。因为函数传参进来之后,本函数不会保存该变量,函数执行完就会自动释放,那么这个时候返回值就可能产生一个没有意义的结果。

auto evt_set_status_x = [&](EventType x)
{
  status[x] = true;/*通过引用捕获的变量 我们可以进行修改变量的数据*/
};

   [&]是一个捕获列表(  capture l ist ), 它指明所用的局部名字(如 x) 将通过引用访问 。如果我们希望只"捕获 "x ,则可以写成 [&x] ;如果希望给生成的函数对象传递一个  的拷贝, 则写成[ x] 。什么也不捕获是[],捕获所有通过引用访问的局部名字是[&],捕获所有以值访问的局部名字是[=]  。    

   并且lambda表达式也可以赋值给相对应的函数指针,这也使得你完全可以把lambda表达式看成对应函数类型的指针。

当我们需要访问它的局部变量的时候,我们需要特别定义捕获列表中的类型

下面是一个没有使用局部变量的lambda表达式,所以它的[]里面为空

void part(vector<int>& v)
{
    sort(v.begin,v.end);//排列值
    sort(v.begin,v.end,
            [](int x,int y){return abs(x) < abs(y);});//排列绝对值
}

下面是一个使用局部变量的lambda表达式,所以它的[]里面为空就会出错

void part(vector<int>& v)
{
    bool value = true;
  sort(v.begin,v.end,
            [](int x,int y){return  value ? x<y:abs(x) < abs(y);});
}

这时候就错误了,因为我们用到了value这个局部变量,而没有进行捕获列表的设置。


3 捕获使用分析

使用 lambda 虽然简单便捷,但也有可能显得晦涩难懂 。  

值捕获

与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 lambda 表达式被创建时拷贝, 而非调用时才拷贝:

#include <iostream>
void value_capture() {
    int value = 1;
    auto copy_value = [value] {
        return value;
    };
    value = 100;
    auto stored_value = copy_value();
    std::cout << "stored_value = " << stored_value << std::endl;
}
int main(int argc,char ** argv)
{
  value_capture();
}
// 这时, stored_value == 1, 而 value == 100.
// 因为 copy_value 在创建时就保存了一份 value 的拷贝

4edc953e2c684bbe819ffa954c899c08.png记得编译的时候加 -std=c++11

引用捕获

与引用传参类似,引用捕获保存的是引用,值会发生变化

void reference_capture() {
    int value = 1;
    auto copy_value = [&value] {
        return value;
    };
    value = 100;
    auto stored_value = copy_value();
    std::cout << "stored_value = " << stored_value << std::endl;
    // 这时, stored_value == 100, value == 100.
    // 因为 copy_value 保存的是引用
}

4edc953e2c684bbe819ffa954c899c08.png

泛型lambda表达式

C++14开始,lambda表达式支持泛型:其参数可以使用自动推断类型的功能,而不需要显示地声明具体类型。这就如同函数模板一样,参数要使用类型自动推断功能,只需要将其类型指定为auto,类型推断规则与函数模板一样。就用我最早给出的那个例子好了。

auto evt_set_status_x = [&](EventType x)
{
  status[x] = true;
};

lambda表达式返回值

在使用lambda表达式时,有时候可以不用指定放回类型,但有时必须指定返回类型,否者将会出错。

这是是由我们写的代码决定的,一般情况下,编译器会自动推断出lambda的返回类型。但是如果函数体里面有多个返回语句,甚至有一些常量return返回时候,编译器无法自动推断其返回类型,这个时候我们需要指定其返回类型。

1.不指定返回类型的情况。

编译器会自动推断出lambda的返回类型。例如:

auto func_1=[] (int I) {return I;};   //编译器推断返回类型为int型
auto func_2=[] (int I) {cout<<I<<endl;};   //编译器推断返回类型为void型,也就是lambda不返回任

2.必须指定返回类型的情况

   当lambda函数体内包含不止一个return语句时,编译器就不能推断其返回类型。例如:

auto func_3=[] (int I) { if(I<0) return 0;else return 1;};   //虽然两个return语句都返回int型,但编译器不能推断出其返回类型,所以该语句会产生编译错误。
auto func_3=[] (int I) -> int {if (I<0) return 0; else return 1;};  //指定返回类型必须采用尾置返回类型(即-> type的形式) 

这就是我分享的c++中的lambda表达式,以后有机会再往深入去分析一哈,其次如果大家有什么更好的思路,欢迎分享交流哈。

目录
相关文章
|
4月前
|
算法 编译器 C++
【C++11】lambda表达式
C++11 引入了 Lambda 表达式,这是一种定义匿名函数的方式,极大提升了代码的简洁性和可维护性。本文详细介绍了 Lambda 表达式的语法、捕获机制及应用场景,包括在标准算法、排序和事件回调中的使用,以及高级特性如捕获 `this` 指针和可变 Lambda 表达式。通过这些内容,读者可以全面掌握 Lambda 表达式,提升 C++ 编程技能。
189 3
|
6月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
38 0
|
8月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
98 1
|
9月前
|
算法 编译器 C++
C++一分钟之—Lambda表达式初探
【6月更文挑战第22天】C++的Lambda表达式是匿名函数的快捷方式,增强函数式编程能力。基本语法:`[capture](params) -&gt; ret_type { body }`。例如,简单的加法lambda:`[](int a, int b) { return a + b; }`。Lambda可用于捕获外部变量(值/引用),作为函数参数,如在`std::sort`中定制比较。注意点包括正确使用捕获列表、`mutable`关键字和返回类型推导。通过实践和理解这些概念,可以写出更简洁高效的C++代码。
84 13
|
9月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
25天前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4天前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
34 16
|
8天前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
51 6
|
1月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
25天前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。