C++11 lambda(匿名函数)表达式详细介绍

 更新时间:2022年07月11日 10:57:50   作者:luoyayun361  
lambda 表达式(lambda expression)是一个匿名函数,C++11中的lambda表达式用于定义并创建匿名的函数对象,以简化编程工作,下面这篇文章主要给大家介绍了关于C++11 lambda(匿名函数)表达式的相关资料,需要的朋友可以参考下

前言

Lambda(匿名函数)表达式是C++11最重要的特性之一,lambda来源于函数式编程的概念,也是现代编程语言的一个特点。

优点如下:

  • 声明式编程风格:就地匿名定义目标函数或函数对象,有更好的可读性和可维护性。
  • 简洁:不需要额外写一个命名函数或函数对象,,避免了代码膨胀和功能分散。
  • 更加灵活:在需要的时间和地点实现功能闭包。

概念及基本用法

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。语法形式如下:

[ capture ] ( params ) opt -> ret { body; };

  • capture:捕获列表
  • params:参数列表
  • opt:函数选项
  • ret:返回值类型
  • body:函数体

一个完整的lambda表达式是这样:

auto f = [](int a) -> int {return a + 1;};
cout << f(3) << endl;  //输出4

以上定义了一个完整的lambda,但是在实际的使用中,可以省略其返回值的定义,编译器会根据return语句进行自动推导返回值类型。

省略过后如下:

auto f = [](int a) {return a + 1;};

需要注意的是,初始化列表不能用于返回值的自动推导:

如:auto f = [](){return {1,2};}; //error:无法推导返回值类型

另外,如果表达式没有参数列表时,也可以省略,如:

auto f = []{return 1;};

捕获变量

lambda表达式可以通过捕获列表捕获一定范围内的变量,主要有以下几种情况:

  • [] 不捕获任何变量
  • [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)
  • [=]捕获外部作用域中所有变量,并作为副本在函数体重使用(按值捕获)
  • [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量
  • [bar] 按值捕获bar变量,同时不捕获其他变量
  • [this] 捕获当前类中的this指针,让表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员变量和成员函数。

通过示例来看具体用法:

class A 
{
public:
    int i_ = 0;
    
    void func(int x,int y)
    {
        auto x1 = []{return i_;};  // error,没有捕获外部变量
        auto x2 = [=]{return i_ + x + y;}; //ok,按值捕获所有外部变量
        auto x3 = [&]{return i_ + x + y;}; //ok,按引用捕获所有外部变量
        auto x4 = [this]{return i_;}; //ok,捕获this指针
        auto x5 = [this]{return i_ + x + y;}; //error,没有捕获x和y变量
        auto x6 = [this,x,y]{return i_ + x + y;}; //ok,捕获了this指针和x、y变量
        auto x7 = [this]{return i_++;}; //ok,捕获了this指针,修改成员变量的值
    }
};
int a = 0 , b = 0 ;
auto f1 = []{return a;}; // error,没有捕获外部变量
auto f2 = [&]{return a++;}; //ok,捕获所有外部变量,并对a变量自加
auto f3 = [=]{return a;}; //ok,捕获所有外部变量,并返回a
auto f4 = [=]{return a++;}; //error,a变量是以复制方式捕获的,不能修改
auto f5 = [a]{return a+b;}; //error,没有捕获b变量
auto f6 = [a,&b]{return a+ (b++);}; //ok,捕获a以及b的引用,对b进行自加
auto f7 = [=,&b]{return a+ (b++);}; //ok, 捕获所有外部变量和b的引用,对b进行自加

需要注意的是,lambda无法修改按值捕获的外部变量,如果需要修改外部变量,可以通过引用方式捕获。

关于lambda表达式的延迟调用很容易出错,如下:

int a = 0;
auto f = [=]{return a;};
a += 1;
cout << f() << endl;

以上示例中,lambda按值捕获了所有外部变量,在捕获的时候 a的值就已经被复制到 f 中了,之后a被修改,但是f里面存储的a仍然是捕获时的值,所以最终输出的是 0.

如果希望lambda表达式在调用的时候能够访问外部变量,需要使用引用方式捕获。

所以简单来说,按值捕获,外部变量会被复制一份存储在lambda表达式变量中。

如果是按值捕获并且又想修改外部变量,可以显示指明lambda表达式为mutable:

int a = 0;
auto f1 = [=]{return a++;};  //error,修改按值捕获的外部变量
auto f2 = [=]() mutable {return a++;}; //ok

被mutable修饰的lambda表达式就算没有参数也要写明参数列表。

lambda表达式类型

lambda表达式的类型在C++11中被称为“闭包类型”,它是一个特殊的,匿名的非nunion的类型。

可以认为它是带有一个operator()的类,即仿函数。
我们可以通过std::function和std::bind来存储和操作lambda表达式:

std::function<int(int)> f1 = [](int a){return a;};
std::function<int(void)> f2 = std::bind([](int a){return a;},123);

另外,对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针:

using func_t = int(*)(int);
func_t f = [](int a){return a;};
f(123);

lambda可以说是就地定义仿函数闭包的“语法 糖”。它的捕获列表捕获住任何外部变量,最终都会变为闭包类型的成员变量。而一个成员变量的类的operator(),如果能直接被转换为普通的函数指针,那么lambda表达式本身的this指针就丢掉了。而没有捕获任何外部变量的lambda表达式则不存在这个问题。

需要注意的是,没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。如下:

typedef void(*Ptr)(int*);
Ptr p = [](int *p){delete p;};  //ok
Ptr p1 = [&](int *p){delete p;}; //error

前面说到的按值捕获无法修改捕获的外部变量,因为按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量的值,而mutable的作用,就是取消operator()的const限制。

声明式的编程风格

通过示例来看一下lambda的使用,在C++11之前,如果要用for_each函数将数组中的偶数数量打印出来,代码如下:

#include <vector>
#include <algorithm>
class Count
{
public:
    Count(int &val):num(val){}
    void operator()(int val){
        if(!(val & 1)){
            ++num;
        }
    }
private:
    int &num;
};

int main()
{
    std::vector<int> v = {1,2,3,4,5,6,7};
    int count = 0;
    for_each(v.begin(),v.end(),Count(count));
    std::cout << count << endl;
    return 0;
}

如果使用lambda表达式,就可以简化一下,真正使用闭包概念来替换这里的仿函数。

#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> v = {1,2,3,4,5,6,7};
    int count = 0;
    for_each(v.begin(),v.end(),[&count](int val){
        if(!(val & 1)){
            ++count;
        }
    });

    std::cout << count << endl;
    return 0;
}

lambda表达式的价值在于,就地封装短小的功能闭包,方便地表达出我们希望执行的具体操作,并让上下文结合的更加紧,代码更加简洁,更灵活,也提高了开发效率及可维护性。

参考:《深入应用C++11》

总结

到此这篇关于C++11 lambda(匿名函数)表达式详细介绍的文章就介绍到这了,更多相关C++11 lambda表达式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言深入探究冒泡排序与堆排序使用案例讲解

    C语言深入探究冒泡排序与堆排序使用案例讲解

    算法中排序是十分重要的,而每一个学习计算机的都会在初期的时候接触到这种排序,下面这篇文章主要给大家介绍了关于c语言冒泡排序与堆排序使用的相关资料,需要的朋友可以参考下
    2022-05-05
  • C++计算每个字符出现的次数

    C++计算每个字符出现的次数

    这篇文章主要介绍了C++计算每个字符出现的次数的相关资料,需要的朋友可以参考下
    2016-05-05
  • C语言中单链表(不带头结点)基本操作的实现详解

    C语言中单链表(不带头结点)基本操作的实现详解

    链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。本文主要和大家聊聊C语言中单链表(不带头结点)的基本操作,感兴趣的小伙伴可以了解一下
    2022-11-11
  • C语言实现手写字符串处理工具的示例代码

    C语言实现手写字符串处理工具的示例代码

    这篇文章主要为大家详细介绍了利用C语言实现手写字符串处理工具的相关资料,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
    2022-09-09
  • 基于C语言实现的TCP服务器的流程分析

    基于C语言实现的TCP服务器的流程分析

    本文详细介绍了如何使用C语言编写一个简单的TCP服务器,包括创建套接字、绑定IP和端口、监听连接请求、接受客户端连接、数据接收与发送以及关闭套接字等步骤,最后通过一个简单的示例展示了TCP服务器的基本实现过程
    2024-10-10
  • C语言 数据结构之数组模拟实现顺序表流程详解

    C语言 数据结构之数组模拟实现顺序表流程详解

    顺序表,全名顺序存储结构,是线性表的一种,线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外,不仅如此,顺序表对数据的物理存储结构也有要求,跟随下文来具体了解吧
    2021-11-11
  • 简单谈谈C语言中的= 和==、!=

    简单谈谈C语言中的= 和==、!=

    这篇文章主要给大家介绍了关于C语言中= 和==、!=的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • c++初级并查集知识点总结

    c++初级并查集知识点总结

    在本篇文章里小编给各位分享的是关于c++初级并查集知识点以及实例代码内容,有需要的朋友们学习下。
    2019-07-07
  • C语言线性代数算法实现矩阵示例代码

    C语言线性代数算法实现矩阵示例代码

    这篇文章主要为大家介绍了使用C语言线性代数的算法来实现矩阵示例代码,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-10-10
  • C++中volatile关键字及常见的误解总结

    C++中volatile关键字及常见的误解总结

    这篇文章主要给大家介绍了关于C++中volatile关键字及常见的误解的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-05-05

最新评论