【C++】lambda表达式

1.基本概念

\quad Lambda函数,亦称为Lambda表达式、匿名函数等,是一种函数对象,Lambda函数可以让函数像普通变量一样进行赋值、传递、函数返回等操作。C++中的Lambda函数经常用来解决如下问题:

  • 使得程序更加简洁,尤其对于一次性使用的函数
  • 使得函数可以自由流动,就像变量一样,这给函数式编程模式奠定了语法基础

2.语法逻辑

2.1 语义规定

[capture list](params list)mutable exception -> return type{functionbody}

解析:

  • captruelist: 外部变量列表
  • params list: 形参列表
  • mutable:是否可以修改外部变量
    • 默认情况下,Lambda函数总是一个const函数,mulable可以取消其常量性
    • 在使用mutable时,形参列表不可省略
  • exceptilon:异常设定
  • return type:返回类型
    • 可以不需要声明返回值,此时返回类型相当于使用decltyp根据返回值推断得到
  • function body: 函数体

2.2 基本语法

下面用一些简明的例子,说明如何编写并使用Lambda表达式:

 #include <iostream>
using namespace std;

int main(int argc, char const *argv[])
{
    int a = 1;
    int b = 2;
    int sum =[a,b]()->int{return a+b;}();
    cout <<"sum:"<< sum<< endl;
    return 0;
}

在上述代码中,从左到右逐个解释:

  • Lambda函数是:a,b->int{return a+b;}
  • a和b是从外部可见作用域中捕获的变量,默认它们在Lambda函数内部是只读的
  • ()圆括号内是Lambda函数的形参列表,此处为空,但不可省略
    • Lambda函数的形参列表一般用于跟容器类结合的时候
  • ->int 是Lambda函数的返回值类型,可以省略
  • {}内部就是Lambda函数的具体代码实现
  • 最后面的()表示调用(执行)该表达式

\quad 注意,最右边的圆括号()是对Lambda函数调用的符号,不是Lamdba函数本身,我们可以将此 Lambda 函数当做一个对象,赋值给另一个具名的函数对象,再去调用它来更清楚地看Lambda函数的本体:

// 将匿名的Lambda函数,赋值给一个名叫f的函数对象
auto f = [a,b]()->int{return a+b;};

// 两种等价的调用函数对象的形式:
int sum = [a,b]()->int{return atb;}();
int sum = f();

类比于C语言函数指针:

int func(int a, char b)
{
    
}
int (*ptr)(int, char) = func;
ptr();

2.2.1 以引用方式捕获外部参数

\quad 上述例子捕获的a和b都是只读的,在Lambda函数内部不可修改,但如果有需要修改这些外部参数,则需要将捕获列表改为引用模式:

[&a, b]()->int{a=100;return a+b;}();

上述代码中:

  • &a代表以引用模式捕获变量a,此时可以在Lambda函数体内修改变量a

示例代码:

#include <iostream>

int main(int argc, char const *argv[])
{
    int a = 10;
    int b = 20;

    int sum = [&a, b]()->int {
        a++;    // a是引用参数,因此在表达式式中可以被修改
        // b++; // 错误,b是值参数,在表达式中默认是只读的
        return a + b;
    }();

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

2.2.2 捕获作用域内所有变量

\quad 如果作用域内的变量较多,一个个填写到Lambda外部参数列表显然比较麻烦,此时可以用如下代码来一次性全部导入:

// 以传值模式(只读)捕获所有外部变量
[=]()->int{...};

// 以引用模式(读写)捕获所有外部变量
[&]()->int{...};

2.2.3 给Lambda函数传参

\quad 就像普通函数一样,可以给Lambda函数传参,比如:

#include <iostream>
using namespace std;

int main(int argc, char const *argv[])
{
    int a = 1;
    int b = 2;
    // 定义一个具有两个int形参的Lambda函数
    // 并将Lambda函数赋值给函数对象sum(相当于起了个名字以便于后续易式调用
    auto sum = [](int x,int y){return x+y;};
    
    // 调用函数对象
    cout<< sum(a, b)<< endl;
    return 0;  
}

\quad 请注意,上述代码仅仅是为了讲解Lambda函数的形参列表的语法逻辑,而并不是Lambda函数的一般典型用法,因为上述例子已经违背了匿名函数的初衷—将Lambda函数赋值给了一个具名函数对象sum,这么做毫无意义,如果非要定义一个具名函数的话,那么完全可以直接编写一个普通的函数 sum 来达到此功能。
\quad 上述代码之所以这么写的原因是,是需要举一个例子来说明Lambda函数的形参的传递过程而在没有涉及STL容器及其配套算法库函数之前,我们没有用过能被自动调用的函数,因此我们需要一个语句来显式地调用函数,因此就需要一个具名的函数对象sum来承接Lambda函数的功能。

示例代码:

#include <iostream>

int main(int argc, char const *argv[])
{
    int a = 10;
    int b = 20;

    // 末尾有圆括号表示该表达式会直核被执行
    int Max =[=]()->int{ return a>b?a:b; }();
    std::cout<<"Max="<<Max<< std::endl ;
    
    //末尾没有圆括号,表示该表达式不会被直核执行,而是作为函数指针赋值给 ptr
    auto ptr=[](int x,int y)->int{ return x>y? x:y; };
    // ptr(a,b)通过指针进行调用表达式
    std::cout<<ptr(a,b)<<std::endl;
    
    return 0;
}

\quad 那么,Lambda函数形参列表的典型场景是怎样的呢?这里可以举STL算法库中的最简单的一例加以说明,如果需要对STL容器及其算法库有进一步的了解,请查相关知识。

实际使用:示例代码1

#include <iostream>

bool compare(auto x, auto y, auto func)
{
    return func(x, y);
}

int main(int argc, char const *argv[])
{
    if(compare(10, 20, [](auto x, auto y){return x < y;}))
    {
        std::cout << "a < b" << std::endl;
    }
    else
    {
        std::cout << "a >= b" << std::endl;
    }
    return 0;
}

示例代码2:

#include <array>
#include <algorithm>

using namespace std;

int main(int argc, char const *argv[])
{
    // 定义一个静态数组arr(这是一种STL容器,简单理解为一维数组即可)
    array<int,5>arr = {1,2,3,4,5};
    // 使用算法库函数 count_if()计算 arr 中的偶数数目
    int num = count_if(arr.begin(), arr.end(), [](int m){return m%2==0;});
    cout<<"偶数数目是:"<<num<< endl;
    return 0;  
}

说明:

  • 上述代码中,Lambda函数是[](int m){return m%2==0;},基本含义是:
    • 不捕获任何外部参数
    • 接受一个 int 型参数,注意此时形参将由算法库函数 count_if()自动传递给Lambda函数
    • 当形参 m 为偶数时,结果为真0
  • count_if()是C++算法库函数
    • 其基本功能是将容器指定范围内的元素,逐个地传递给Lambda函数进行检测
    • 累计所有检测结果为真的元素,并返回这个累计数目
    • 指定的范围由半开半闭区间(arr.begin(),arr.end())规定,其中的 arr.begin()和 arr.end()是分别指向容器的迭代器(可看做广义指针),用来界定范围

\quad 这样一来就很清楚了,我们利用Lambda匿名函数逐个接收count_if传递过来的参数(即int m,因为容器arr中的元素类型都是int型整数),并判断其是否为偶数,以期达到计算容器中偶数数目的最终目的。
\quad 当然,算法库的函数所能接收的函数符(functor)不一定是Lambda匿名函数,Lambda匿名函数只是函数符的其中一种形式,它们还可以是普通的函数指针,还有更常见的类函数对象,这些知识点将在STL容器及算法库中说明的。

2.3 传递Lambda函数

\quad Lambda匿名函数被设计为一种对象,这意味着可以将它们作为函数的参数、返回值,有时,那些使用了Lambda函数作为参数、返回值的函数,被称为高阶函数。

2.3.1 Lambda表达式参数

将Lambda表达式作为函数参数,在上述 count_if() 求偶数数目的例子中已有体现:

count_if(arr.begin(), arr.end(),[](int m){return m%2==0;});

此处,Lambda表达式 [](int m){retum m%2==0;}就做为第三个参数传递给了 count_if。

2.3.2 lambda表达式返回值

对于Lambda表达式,作为函数返回值与其他普通的对象语法上没有太大区别,例如:

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

int main()
{
    f(1)(3); 
    // [=](int b){cout<< a+b << endl;}(3);
    // [=](int b){cout<< 1+3 << endl;}(3);
    return 0;
}

3.浅谈函数式编程

\quad 注意,掌握了以上语法细节,并不意味若掌握了Lamba匿名函数,也不意味着掌握了函数式编程思维,Lambda函数最重要的设计目的之一,是给一种程范式、一种思考角度提供语法方案,这种范式和思维方式就是函数式编程
\quad 函数式编程是一种继过程性编程、面向对象编程之后的新的程范式,涉及的概念很多比如上述Lambda函数中的 f(1)(2)实际可理解为函数的柯里化,还有诸如纯函数、只读数据、高阶函数、函子、单子等概念,是独立的一个编程领域,详情可查阅函数式编程相关知识。
\quad 要弄清楚 函数式编程 的基本思想,首先要弄清楚与之相对应的 命令式编程 ,前者强调的是关联数据的映射,而后者关注的是解决问题的步骤。
举例子
给定一个字符串"abcdefg",要求将其翻转为"gfedcba",这并不是什么难题,有各种不同的解法,来看下命令式编程思维是怎么思考的?

void revert(char s[], int len)
{
    if(len <= 1)
        return;
    //首尾元素交换
    swap(s[0],s[len-1]);
    
    // 中间整体翻转
    revert(s+1, len-2);
}

上述代码是一段典型的递归算法,其思路是分解了解决翻转问题的各个步骤,即:

  • 首先,如果字符串长度太短,则无需翻转,算法结束
  • 否则,就对调首尾两个字符
  • 最后,将中间的若干个字符戳转

这种思路是命令式的,也就是过程性的,将注意力放在了如何构建一个通达目标的解题路径,一步两步三步,最终完成任务。
那函数式编程思维会怎么解这道题呢?函数式思维的注意力主要放在数据的映射上,即设计出一个从 旧数据向 新数据变化的映射关系(即函数):

char *revert(char sl,int len)
{
    //数据量太少,无法映射
    if(len <= 0)
        return;
    char *rs = new char[len],
    for(int i; len)
       rs[i]= s[len-i];
       
    return rs; 
}

### C++ Lambda 表达式简介 C++11 引入了 lambda 表达式这一特性,使得开发者可以在需要函数的地方直接定义匿名函数对象,而无需单独声明函数。这种机制不仅简化了代码结构,还增强了程序的灵活性[^4]。 #### 基本语法 Lambda 表达式的通用形式如下: ```cpp [capture](parameters) -> return_type { body } ``` - **`capture`**: 定义如何捕获外部作用域中的变量(按值、按引用或其他方式)。 - **`parameters`**: 函数参数列表,类似于普通函数。 - **`return_type`**: 可选部分,默认情况下编译器会推导返回类型。 - **`body`**: 函数体,包含具体的逻辑实现。 --- ### 示例解析 以下是几个典型的 lambda 表达式使用场景及其对应的代码示例。 #### 1. 打印容器中的元素 通过 `std::for_each` 配合 lambda 表达式来遍历并打印向量中的数据。 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> vec = {1, 2, 3, 4, 5}; // 使用 Lambda 表达式打印每个元素 for_each(vec.begin(), vec.end(), [](int x) { cout << x << " "; }); cout << endl; return 0; } ``` 此代码展示了如何利用 lambda 表达式作为回调函数传递给标准库算法 `std::for_each`[^1]。 --- #### 2. 捕获局部变量 Lambda 表达式可以通过 `[ ]` 来捕获上下文中的变量。支持的方式有多种:按值 (`=`),按引用 (`&`) 或显式指定单个变量。 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int factor = 2; vector<int> vec = {1, 2, 3, 4, 5}; // 按值捕获变量 'factor' transform(vec.begin(), vec.end(), vec.begin(), [factor](int x) { return x * factor; }); for (auto num : vec) { cout << num << " "; } cout << endl; return 0; } ``` 在此示例中,lambda 表达式捕获了名为 `factor` 的局部变量,并将其用于计算新数组的值[^2]。 --- #### 3. 返回复杂类型的 lambda 表达式lambda 表达式的返回值较为复杂时,可以显式指定其返回类型。 ```cpp #include <functional> #include <iostream> using namespace std; int main() { auto adder = [] (int a, int b) -> int { return a + b; }; cout << adder(3, 4) << endl; // 输出 7 return 0; } ``` 上述代码显示了一个简单的加法操作,其中指定了返回类型为整数型。 --- ### 总结 Lambda 表达式极大地提高了 C++ 编程的便利性和可读性,尤其是在处理 STL 算法或者 GUI 应用开发时尤为有用。它们能够减少不必要的辅助函数定义,使代码更加紧凑高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值