前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C/C++开发基础——lambda表达式与std::bind闭包

C/C++开发基础——lambda表达式与std::bind闭包

作者头像
Coder-Z
发布2023-03-08 16:47:08
8470
发布2023-03-08 16:47:08
举报

本章主要内容:

一,lambda表达式

1.基本概念

2.关于捕获子句

3.常见的捕获方式

二,闭包与std::bind模板

1.什么是闭包

2.std::bind的简介

3.std::bind的用法

三,参考阅读

一,lambda表达式

1.基本概念

lambda表达式是从C++11开始引入的,主要用来定义匿名函数和闭包。lambda表达式可以被当作一个值赋给另一个变量,也可以作为实参传递给其他函数,或者作为其他函数的返回结果,用法类似于前面提到的函数对象和函数指针。如果只是把单个函数拿来传参,lambda表达式的使用方式比函数指针和函数对象更简洁。

lambda表达式可以不指定函数的返回类型,编译器将自动推导该类型。

lambda表达式的完整公式:

代码语言:javascript
复制
[capture_list](parameter_list) mutable -> return_type{ process code };

具体含义:

[]: lambda表达式的引出符,编译器根据该符号判断接下来的代码是否为lambda匿名函数。

parameter_list: 参数列表,与普通函数的参数列表一致。如果不需要传递参数,则可以省略该部分以及小括号()。

mutable: 使用了mutable修饰符的lambda表达式,不可以省略参数列表。

return_type: 函数返回值类型。该部分可以连同"->"一起省略。

process code: 函数体,它除了可以使用参数之外,还可以使用捕获到的变量。

lambda表达式样例:

代码语言:javascript
复制
[](int x, int y){return x<y;}        //[]用来标记lambda表达式的开始
[](int x=0, int y=0){return x<y;}    //传默认实参x=0,C++14标准开始支持
[]{return true;}                     //没有参数时,可以省略圆括号()
[](int x, int y)->bool{return x<y;}  //显式指定返回值类型,让代码更清晰

注意,lambda表达式中的"[ ]"不一定是空的,里面可以包含捕获子句,捕获子句用来捕获上下文中的变量来提供给lambda表达式使用。

C++代码样例:

Demo1:

代码语言:javascript
复制
#include<iostream>
using namespace std;
int main() {
    auto operation = [](int a, int b, string op) -> double {
        if (op == "sum") {
            return a + b;
        }
        else {
            return (a + b) / 2.0;
        }
    };
    int num1 = 1;
    int num2 = 2;
    auto sum = operation(num1, num2, "sum");
    cout << "Sum = " << sum << endl;
    auto avg = operation(num1, num2, "avg");
    cout << "Average = " << avg;
    return 0;
}

运行结果:

代码语言:javascript
复制
Sum = 3
Average = 1.5

Demo2: lambda与std::for_each结合使用

代码语言:javascript
复制
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
int main()
{
       vector<int> vec{ 1, 2, 3, 4, 5 };
       for_each(vec.begin(),
                   vec.end(),
                   [](int& a) { a *= 2; }
               );
       for_each(vec.begin(),
                   vec.end(),
                   [](int a) { cout << a << " " ; }
                );
       cout << endl;
       return 0;
}

运行结果:

代码语言:javascript
复制
2 4 6 8 10

2.关于捕获子句

捕获子句定义了lambda表达式访问(捕获)表达式以外的参数和变量的方式。

默认的捕获子句有两种即"="(按值捕获)和"&"(按引用捕获)。

为什么要有捕获子句:

当[ ]中为空时,lambda表达式只能访问lambda表达式中定义的局部实参和局部变量。当[ ]中不为空时,lambda表达式可以访问代码指定作用域中的所有参数和变量。因此,捕获子句的使用扩大了lambda表达式捕获变量的范围。

3.常见的捕获方式

方式一,按值捕获

方括号中包含"=",指定作用域中变量的值可以传递到lambda表达式,lambda表达式可以使用变量的值,但是不能修改变量的值。

方式二,按引用捕获

方括号中包含"&",指定作用域中变量的引用可以传递到lambda表达式,lambda表达式既可以使用变量的值,也可以修改变量的值。

方式三,捕获指定的变量

捕获变量和默认捕获子句的操作有些区别:

按值捕获变量:[ ]中直接传变量名,不带"="。

按引用捕获变量:[ ]中传的是 "&"后面加变量名。

捕获多个变量时可以用逗号分隔,例如:

代码语言:javascript
复制
[=, &counter]  //按引用捕获counter,按值捕获其他变量
[&, counter]    //按值捕获counter,按引用捕获其他变量

指定的默认子句("="或"&")必须是捕获列表中的第一项。

如果捕获列表的前面已经加了"="捕获子句,则后面不能再按值捕获特定变量。同理,如果捕获列表的前面已经加了"&"捕获子句,则后面不能再按引用捕获特定变量。

所以下面这两个捕获子句会产生编译错误:

代码语言:javascript
复制
[&, &counter]
[=, &counter, number]

方式四,捕获this指针

如果一个对象的成员函数中有lambda表达式,那么这个lambda表达式不能通过按值捕获或按引用捕获这个对象的成员变量。为了让lambda表达式能够访问当前对象的成员变量,应该在捕获子句中使用this关键字。

有了this指针,lambda表达式可以访问当前对象的所有成员函数和成员变量,无论它们的访问权限被声明为protected还是private。

总结下来,常见的捕获语法有:

[=]: 按值捕获所有变量。

[&]: 按引用捕获所有变量。

[=,&x,&y]: 按引用捕获变量x和y,按值捕获其他变量。

[&,x,y]: 按值捕获变量x和y,按引用捕获其他变量。

[this]: 捕获当前的对象。

[*this]: 捕获当前的对象的副本。

C++代码样例

Demo1:

代码语言:javascript
复制
#include<iostream>
using namespace std;
int main() {
    int initial_sum = 100;
    auto add_to_sum = [initial_sum](int num) {
        return initial_sum + num;
    };
    int final_sum = add_to_sum(78);
    cout << "100 + 78 = " << final_sum;
    return 0;
}

运行结果:

代码语言:javascript
复制
100 + 78 = 178

Demo2:

代码语言:javascript
复制
#include <functional>
#include <iostream>
using namespace std;
int main()
{
    int i = 3;
    int j = 5;
    function<int(void)> f1 = [i, j] { return i + j; };
    i = 22;
    j = 44;
    function<int(void)> f2 = [&i, &j] { return i + j; };
    cout << f1() << endl;
    cout << f2() << endl;
}

运行结果:

代码语言:javascript
复制
8
66

Demo3:

代码语言:javascript
复制
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
class Scale
{
public:
    explicit Scale(int scale) : _scale(scale) {}
    void ApplyScale(const vector<int>& v) const
    {
        for_each(v.begin(), v.end(), [this](int n) { cout << n * _scale <<" "; });
        cout << endl;
    }
private:
    int _scale;
};
int main()
{
    vector<int> values;
    values.push_back(1);
    values.push_back(2);
    values.push_back(3);
    values.push_back(4);
    Scale s(3);
    s.ApplyScale(values);
}

运行结果:

代码语言:javascript
复制
3 6 9 12

二,闭包与std::bind模板

1.什么是闭包

闭包( Closure)这个概念起源于函数式编程,是指外部变量与函数之间的绑定,可以这样理解,捕获了外部变量的lambda表达式是一种闭包。

2.std::bind的简介

std::bind是C++11标准引入的函数模板,用于取代bind1st和bind2nd等旧式语法。std::bind常用来实现闭包,

它用于包装和调用特征相同的函数指针、函数对象或lambda表达式。

std::bind可以充当函数适配器,即它接受一个原函数作为输入并返回一个新的函数对象作为输出,返回的函数对象包含一个或多个与原函数绑定的参数。std::bind可以预先指定函数的所有参数,也可以将函数的部分参数预先指定好,剩下的参数等真正调用的时候再指定。

3.std::bind的用法

假如有一个计算两个数字相加的函数。

代码语言:javascript
复制
int add(int first, int second)
{
    return first + second;
}

std::bind将函数名作为其第一个参数,后面的参数用"_1,_2"这样的占位符来预留,得到一个函数对象add_func。add_func可以像函数一样直接被调用。

代码语言:javascript
复制
auto add_func = std::bind(&add, _1, _2);
add_func(4,5); //4+5, 返回9

假设遇到了特殊场景,需要将函数的第一个参数传12,第二个参数作为预留,使用方式如下。

代码语言:javascript
复制
auto new_add_func = std::bind(&add, 12, _1);
new_add_func(5); //12+5, 返回17

完整C++代码实现:

代码语言:javascript
复制
#include <iostream>
#include <functional>
using namespace std;
int add(int first, int second)
{
    return first + second;
}
int main()
{
    auto new_add_func = std::bind(&add, 12, placeholders::_1);
    int x = new_add_func(5);
    cout << x << endl;
    return 0;
}

运行结果:

代码语言:javascript
复制
17
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档