首页 > 编程语言 >C++Lambda表达式

C++Lambda表达式

时间:2024-03-31 15:12:27浏览次数:35  
标签:return 函数 int 捕获 C++ 表达式 Lambda

Lambda表达式

0、介绍

c++11 新引入了 lambda 表达式,一般用于定义匿名函数,使得代码更加灵活简洁。lambda 表达式与普通函数类似,也有参数列表、返回值类型和函数体,只是它的定义方式更加简介,并且可以在函数内部定义。

通常,lambda 用于封装传递给算法或异步方法的几行代码。

1、Lambda 表达式定义

语法如下:

[capture list] (parameter list) -> return type { function body }

参数释义:

  • capture list捕获列表,用于指定 Lambda 表达式可以访问的外部变量,以及是按值还是按引用的方式访问。捕获列表可以为空,表示不访问任何外部变量,也可以使用默认捕获模式&=来表示按引用或按值捕获所有外部变量,还可以混合使用具体的变量名和默认捕获模式来指定不同的捕获方式。实际上,[]是 Lambda 引出符。编译器根据该引出符判断接下来的代码是否是 Lambda 函数。
  • parameter list参数列表,用于表示 Lambda 表达式的参数,可以为空,表示没有参数,也可以和普通函数一样指定参数的类型和名称,还可以在 c++14 中使用 auto 关键字来实现泛型参数。
  • return type返回值类型,用于指定 Lambda 表达式的返回值类型,可以省略,表示由编译器根据函数体推导,也可以使用->符号显示指定,还可以在 c++14 中使用auto关键字来实现泛型返回值。
  • function body函数体,用于表示 Lambda 表达式的具体逻辑,可以是一条语句,也可以是多条语句,还可以在 c++14 中使用 constexpr 来实现编译期计算。

2、Lambda 表达式捕获方式

值捕获

capture by value,在捕获列表中使用变量名,表示将该变量的值拷贝到 Lambda 表达式中,作为一个数据成员。值捕获的变量在 Lambda 表达式定义时就已经确定,不会随着外部变量的变化而变化。值捕获的变量默认不能在 Lambda 表达式中修改,除非使用 mutable 关键字。例如:

int x = 10;
auto f = [x] (int y) -> int { return x + y;};    // 值捕获x
x = 20; // 修改外部的 x
cout << f(5) << endl;   // 输出15,不受外部 x 的影响

引用捕获

capture by reference,在捕获列表中使用&加变量名,表示将该变量的引用传递到 Lambda 表达式中,作为一个数据成员。引用捕获的变量在 Lambda 表达式调用时才确定,会随着外部变量的变化而变化。引用捕获的变量可以在 Lambda 表达式中修改,但要注意生命周期的问题,避免悬空引用的出现。例如:

int x = 10;
auto f = [&x] (int y) -> int { return x + y; }; // 引用捕获x
x = 20; // 修改外部的 x
cout << f(5) << endl;   // 输出25,受外部 x 的影响

隐式捕获

implicit capture,在捕获列表中使用=&,表示按值或按引用捕获 Lambda 表达式中使用的所有外部变量。这种方式可以简化捕获列表的书写,避免过长或遗嘱。隐式捕获可以和显示捕获混合使用,但不能和同类型的显示捕获一起使用。例如:

int x = 10;
int y = 20;
auto f = [=, &y] (int z) -> int {return x + y + z;}; // 隐式按值捕获x,显示按引用捕获y,混合使用,这是允许的。
x = 30; // 修改外部的x
y = 40; // 修改外部的y
cout << f(5) << endl;   // 输出55,不受外部x的影响,受外部y的影响。

// 错误演示
auto f = [=, &x, &y] (int z) -> int {return x + y + z;};    // 显示按引用捕获了x和y,此时引用捕获无论捕获到x 还是 y,都是一起使用,这是不被允许的。

初始化捕获

init capture,c++14 引入的一种新的捕获方式,它允许在捕获列表使用初始化表达式。从而在捕获列表中创建并初始化一个新的变量,而不是捕获一个已存在的变量。这种方式可以使用 auto 关键字来推导类型,也可以显示指定类型。这种方式可以用来捕获只移动的变量,或者捕获this指针的值。例如:

int x = 10;
auto f = [z = x + 5] (int y) -> int {return z + y;};    // 初始化捕获z,相当于值捕获 x + 5
x = 20;     // 修改外部的 x
cout << f(5) << endl;   // 输出20,不受外部 x 的影响

3、Lambda 表达式优点

Lambda 表达式相比于普通函数和普通类,有以下几个优点:

  • 简洁:Lambda 表达式可以省略函数名和类名,直接定义和使用,使得代码更加简洁和清晰。
  • 灵活:Lambda 表达式可以捕获外部变量,可以作为函数参数,也可以作为函数返回值,使得代码更加灵活和方便。
  • 安全:Lambda 表达式可以控制外部变量的访问方式,可以避免全局变量的定义,可以避免悬空指针和无效引用的产生,使得代码更加安全和稳定。

4、Lambda 表达式示例

下面我们通过一些示例来展示 Lambda 表达式的用法和效果。

使用 Lambda 表达式 定义简单的匿名函数

我们可以使用 Lambda 表达式来定义一些简单的匿名函数,例如计算两个数的和、判断一个数是否为奇数等。例如:

#include <iostream>
using namespace std;

int main()
{
    // 定义一个 Lambda表达式,计算两个数的和
    auto plus = [] (int a, int b) -> int { return a + b; };
    // 调用 Lambda表达式
    cout << plus(3, 4) << endl; // 输出 7

    // 定义一个 Lambda表达式,判断一个数是否为奇数
    auto is_odd = [] (int n) { return n % 2 == 1; };
    // 调用 Lambda表达式
    cout << is_odd(5) << endl; // 输出 1
    cout << is_odd(6) << endl; // 输出 0

    return 0;
}

使用 Lambda 表达式捕获外部变量

我们可以使用 Lambda 表达式的捕获列表来指定 Lambda 表达式可以访问的外部变量,以及是按值还是按引用的方式访问。例如:

#include <iostream>
using namespace std;

int main()
{
    int x = 10;
    int y = 20;

    // 定义一个 Lambda表达式,按值捕获 x 和 y
    auto add = [x, y] () -> int { return x + y; };
    // 调用 Lambda表达式
    cout << add() << endl; // 输出 30

    // 修改 x 和 y 的值
    x = 100;
    y = 200;

    // 再次调用 Lambda表达式
    cout << add() << endl; // 输出 30,捕获的是 x 和 y 的副本,不受外部变化的影响

    // 定义一个 Lambda表达式,按引用捕获 x 和 y
    auto mul = [&x, &y] () -> int { return x * y; };
    // 调用 Lambda表达式
    cout << mul() << endl; // 输出 20000

    // 修改 x 和 y 的值
    x = 1000;
    y = 2000;

    // 再次调用 Lambda表达式
    cout << mul() << endl; // 输出 2000000,捕获的是 x 和 y 的引用,会反映外部变化的影响

    return 0;
}

使用 Lambda表达式作为函数参数

我们可以使用 Lambda 表达式作为函数的参数,这样可以方便地定义和传递一些简单的函数对象,例如自定义排序规则、自定义比较函数等。例如:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 定义一个结构体
struct Item
{
    Item(int aa, int bb) : a(aa), b(bb) {}
    int a;
    int b;
};

int main()
{
    vector<Item> vec;
    vec.push_back(Item(1, 19));
    vec.push_back(Item(10, 3));
    vec.push_back(Item(3, 7));
    vec.push_back(Item(8, 12));
    vec.push_back(Item(2, 1));

    // 使用 Lambda表达式,根据 Item 中的成员 a 升序排序
    sort(vec.begin(), vec.end(), [] (const Item& v1, const Item& v2) { return v1.a < v2.a; });

    // 使用 Lambda表达式,打印 vec 中的 Item 成员
    for_each(vec.begin(), vec.end(), [] (const Item& item) { cout << item.a << " " << item.b << endl; });

    return 0;
}

使用 Lambda 表达式作为函数返回值

我们可以使用 Lambda 表达式作为函数的返回值,这样可以方便地定义和返回一些简单的函数对象,例如工厂函数、闭包函数等。例如:

#include <iostream>
using namespace std;

// 定义一个函数,返回一个 Lambda表达式,实现两个数的加法
auto make_adder(int x)
{
    return [x] (int y) -> int { return x + y; };
}

int main()
{
    // 调用函数,得到一个 Lambda表达式
    auto add5 = make_adder(5);
    // 调用 Lambda表达式
    cout << add5(10) << endl; // 输出 15

    return 0;
}

Lamdba 表达式应用于 STL 算法库

// for_each应用实例
int a[4] = {11, 2, 33, 4};
sort(a, a+4, [=](int x, int y) -> bool { return x%10 < y%10; } );
for_each(a, a+4, [=](int x) { cout << x << " ";} );
// find_if应用实例
int x = 5;
int y = 10;
deque<int> coll = { 1, 3, 19, 5, 13, 7, 11, 2, 17 };
auto pos = find_if(coll.cbegin(), coll.cend(), [=](int i) {                 
    return i > x && i < y;
});
// remove_if应用实例
std::vector<int> vec_data = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int x = 5;
vec_data.erase(std::remove_if(vec.date.begin(), vec_data.end(), [](int i) { 
    return n < x;}), vec_data.end());

std::for_each(vec.date.begin(), vec_data.end(), [](int i) { 
    std::cout << i << std::endl;});

Lamdba 表达式应用于多线程场景

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>

int main()
{
    // vector 容器存储线程
    std::vector<std::thread> workers;
    for (int i = 0; i < 5; i++) 
    {
        workers.push_back(std::thread([]() 
        {
            std::cout << "thread function\n";
        }));
    }
    std::cout << "main thread\n";

    // 通过 for_each 循环每一个线程
    // 第三个参数赋值一个task任务
    // 符号'[]'会告诉编译器我们正在用一个匿名函数
    // lambda函数将它的参数作为线程的引用t
    // 然后一个一个的join
    std::for_each(workers.begin(), workers.end(), [](std::thread &t;) 
    {
        t.join();
    });

    return 0;
}

Lamdba 表达式在 QT 中的应用

QTimer *timer=new QTimer;
timer->start(1000);
QObject::connect(timer,&QTimer::timeout,[&](){
        qDebug() << "Lambda表达式";
});
int a = 10;
QString str1 = "汉字博大精深";
connect(pBtn4, &QPushButton::clicked, [=](bool checked){
	qDebug() << a <<str1;
	qDebug() << checked;
	qDebug() << "Hua Windows Lambda Button";
});

5、Lambda 表达式与普通函数和普通类的关系

Lambda 表达式虽然是一种语法糖,但它本质上也是一种函数对象,也就是重载了 operator() 的类的对象。每一个 Lambda 表达式都对应一个唯一的匿名类,这个类的名称由编译器自动生成,因此我们无法直接获取或使用。Lambda 表达式的捕获列表实际上是匿名类的数据成员,Lambda 表达式的参数列表和返回值类型实际上是匿名类的 operator() 的参数列表和返回值类型,Lambda表达式的函数体实际上是匿名类的 operator() 的函数体。例如,下面的 Lambda 表达式:

int x = 10;
auto f = [x] (int y) -> int { return x + y; };

相当于定义了一个匿名类,类似于:

int x = 10;
class __lambda_1
{
public:
    __lambda_1(int x) : __x(x) {} // 构造函数,用于初始化捕获的变量
    int operator() (int y) const // 重载的 operator(),用于调用 Lambda表达式
    {
        return __x + y; // 函数体,与 Lambda表达式的函数体相同
    }
private:
    int __x; // 数据成员,用于存储捕获的变量
};
auto f = __lambda_1(x); // 创建一个匿名类的对象,相当于 Lambda表达式

由于 Lambda 表达式是一种函数对象,因此它可以赋值给一个合适的函数指针或函数引用,也可以作为模板参数传递给一个泛型函数或类。例如:

#include <iostream>
using namespace std;

// 定义一个函数指针类型
typedef int (*func_ptr) (int, int);

// 定义一个函数,接受一个函数指针作为参数
void apply(func_ptr f, int a, int b)
{
    cout << f(a, b) << endl;
}

int main()
{
    // 定义一个 Lambda表达式,计算两个数的乘积
    auto mul = [] (int x, int y) -> int { return x * y; };
    // 将 Lambda 表达式赋值给一个函数指针
    func_ptr fp = mul;
    // 调用函数,传递函数指针
    apply(fp, 3, 4); // 输出 12

    return 0;
}

6、C++14 和 C++17 对 Lambda 表达式的扩展和改进

C++14 和 C++17 对 Lambda 表达式进行了一些扩展和改进,使得 Lambda 表达式更加强大和灵活。主要有以下几个方面:

泛型 Lambda

C++14 允许在 Lambda 表达式的参数列表和返回值类型中使用 auto 关键字,从而实现泛型 Lambda,即可以接受任意类型的参数和返回任意类型的值的 Lambda 表达式。例如:

#include <iostream>
using namespace std;

int main()
{
    // 定义一个泛型 Lambda,根据参数的类型返回不同的值
    auto f = [] (auto x) -> auto
    {
        if (is_integral<decltype(x)>::value) // 如果 x 是整数类型
        {
            return x * 2; // 返回 x 的两倍
        }
        else if (is_floating_point<decltype(x)>::value) // 如果 x 是浮点类型
        {
            return x / 2; // 返回 x 的一半
        }
        else // 其他类型
        {
            return x; // 返回 x 本身
        }
    };
    // 调用泛型 Lambda
    cout << f(10) << endl; // 输出 20
    cout << f(3.14) << endl; // 输出 1.57
    cout << f("hello") << endl; // 输出 hello

    return 0;
}

初始化捕获

C++14 允许在 Lambda 表达式的捕获列表中使用初始化表达式,从而实现初始化捕获,即可以在捕获列表中创建和初始化一个新的变量,而不是捕获一个已存在的变量。例如:

#include <iostream>
using namespace std;

int main()
{
    // 定义一个 Lambda表达式,使用初始化捕获,创建一个新的变量 z
    auto f = [z = 10] (int x, int y) -> int { return x + y + z; };
    // 调用 Lambda表达式
    cout << f(3, 4) << endl; // 输出 17

    return 0;
}

捕获 this 指针

C++17 允许在 Lambda 表达式的捕获列表中使用 *this,从而实现捕获 this 指针,即可以在 Lambda 表达式中访问当前对象的成员变量和成员函数。例如:

#include <iostream>
using namespace std;

// 定义一个类
class Test
{
public:
    Test(int n) : num(n) {} // 构造函数,初始化 num
    void show() // 成员函数,显示 num
    {
        cout << num << endl;
    }
    void add(int x) // 成员函数,增加 num
    {
        // 定义一个 Lambda表达式,捕获 this 指针
        auto f = [*this] () { return num + x; };
        // 调用 Lambda表达式
        cout << f() << endl;
    }
private:
    int num; // 成员变量,存储一个整数
};

int main()
{
    Test t(10); // 创建一个 Test 对象
    t.show(); // 调用成员函数,输出 10
    t.add(5); // 调用成员函数,输出 15

    return 0;
}

7、无捕获的lambda可以转换为同类型的函数指针

只有无捕获的lambda可以转换为同类型的函数指针,如下所示:

#include <iostream>

// 声明一个compute函数指针,函数参数为两个int型,返回值为int型
int (*compute)(int, int);

int main(void) 
{
    int x = 2; 
	int y = 5;

    // 无捕获的lambda可以转换为同类型的函数指针
	auto sum = [](int x, int y)->int{ return x + y; };
	std::cout << "sum: " << compute_x_y(x, y, sum) << std::endl; // sum: 7

    getchar();
    return 0;
}

如上所示,在定义匿名函数(lambda)时,一般使用auto作为匿名函数类型。

8、总结

Lambda 表达式是 c++11 引入的一个语法糖,它可以用来定义并创建匿名的函数对象,主要用于方便编程,避免全局变量的定义,并且变量安全。Lambda表达式的语法类似于一个函数定义,但它不需要函数名,可以直接定义并使用。Lambda表达式相比于普通函数和普通类,有以下几个优点:简洁、灵活和安全。Lambda 表达式本质上是一个匿名类的对象,因此它可以赋值给一个函数指针或函数引用,也可以作为模板参数传递给一个泛型函数或类。C++14 和 C++17 对 Lambda 表达式进行了一些扩展和改进,使得 Lambda 表达式更加强大和灵活,主要有以下几个方面:泛型 Lambda、初始化捕获和捕获 this 指针。

9、原文链接

该文章参考自以下链接:

原文链接:https://blog.csdn.net/m0_60134435/article/details/136151698

原文链接:https://blog.csdn.net/A1138474382/article/details/111149792

标签:return,函数,int,捕获,C++,表达式,Lambda
From: https://www.cnblogs.com/-h47/p/18106746

相关文章

  • 老鹰捉小鸡 c++编程参考程序(一本通51.
    #include<bits/stdc++.h>usingnamespacestd;intmain(){   inti,j,a[6],n;//定义整形变量   for(i=1;i<6;i++)//循环      a[i]=i;   i=1;   cout<<i<<":"<<"";//输出i   for(j=1;j<6;j++)//再循环      cout<......
  • C++String类
    前言大家好,我是jiantaoyab,本篇文章将给大家介绍String类的常用法,和模拟实现String类。String介绍在cplusplus中,对String有着下面的介绍。Thestandardstringclassprovidessupportforsuchobjectswithaninterfacesimilartothatofastandardcontainerofb......
  • C++原子操作与内存序 1
    问题#include<iostream>#include<thread>intmain(){ intsum=0; autof=[&sum](){ for(inti=0;i<10000;i++) sum+=1; }; std::threadt1(f); std::threadt2(f); t1.join(); t2.join(); std::cout<<"thesum......
  • 【c++】类和对象(六)深入了解隐式类型转换
    ......
  • C++: 虚函数,一些可能被忽视的细节
    C++:虚函数,一些可能被忽视的细节引言:关于C++虚函数,对某些细节的理解不深入,可能导致我们的程序无法按预期结果运行,或是表明我们对其基本原理理解不够透彻。本文详细解答以下几个问题:实现多态,忘记写virtual会怎么样?虚函数的默认参数可以重载吗?纯虚函数真的不能有实现吗?析构函数可......
  • C++ 中的 volatile 和 atomic
    C++中的volatile和atomic0.TL;DRstd::atomic用于多线程并发场景,有两个典型使用场景:原子操作:对atomic变量的操作(读/写/自增/自减)仿佛受互斥量保护。一般通过特殊的机器指令实现,比使用互斥量更高效限制编译器/硬件对赋值操作的重新排序volatile和多线程并发没有......
  • C++—vector的介绍及使用 && vector的模拟实现
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档目录文章目录前言一、vector的介绍及使用1.1vector的介绍1.2vector的使用1.2.1vector的定义1.2.2vectoriterator的使用1.2.3vector空间增长问题1.2.4 vector增删查改1.2.5 vector迭代器......
  • 波兰表达式代码
    importjava.util.ArrayList;importjava.util.List;importjava.util.Stack;publicclassPolan{publicstaticvoidmain(String[]args){//先定义波兰表达式//(3+2)*5-4=》32+5*4—StringExpression="32+5*4—";//将后......
  • 矩阵匹配【华为OD机试JAVA&Python&C++&JS题解】
    一.题目-矩阵匹配从一个NM(N<=M)的矩阵中选出N个数,任意两个数字不能在同一行或同一列,求选出来的N个数中第K大的数字的最小值是多少。输入描述:输入矩阵要求:1<=K<=N<=M<=150输入格式:NMKNM矩阵输出描述:N*M的矩阵中可以选出M!/N!种组合数组,每个组合数组中第K大的数中的......
  • 文本统计分析【华为OD机试JAVA&Python&C++&JS题解】
    一.题目-文本统计分析有一个文件,包含以一定规则写作的文本,请统计文件中包含的文本数量规则如下文本以";“分隔,最后一条可以没有”;",但空文本不能算语句,比如"COMMANDA;;"只能算一条语句.注意,无字符/空白字符/制表符都算作"空"文本文本可以跨行,比如下面,......