【C++】lambda 表达式 / 包装器 / bind 绑定

一. lambda 表达式

Lambda 表达式是 C++11 标准引入的一种新特性, 它提供了一种方便的方式来定义匿名函数.

lambda 表达式实际上是一个匿名的仿函数; 不同于仿函数的是, 该类由编译器自动创建. 而调用lambda 表达式就相当于调用类的 operator() 函数.

1. lambda 表达式的语法

//    捕捉列表           参数列表					  返回值类型	       函数体
[/*capture-list*/] (/*parameters*/) mutable -> /*return_type*/ {/*statement*/};
  • [ ]: capture-list 捕捉列表, 该列表总是出现在 lambda 函数的开始位置, 编译器根据 [] 来判断接下来的代码是否为 lambda 函数, 捕捉列表能够捕捉上下文中的变量供 lambda 函数使用; 捕捉列表不可省略, 但可以为空.

  • ( ): parameters 参数列表, 与普通函数的参数列表一致, 若不需要参数传递, 则可以连同 () 一起省略, 但通常不建议省略.

  • mutable: 在默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量性; (使用该修饰符时, 参数列表不可省略, 即使参数为空).

  • ->: 后面接 return_type 返回值类型, 若没有返回值或返回值类型明确的情况下可以省略, 由编译器对返回类型自动推导.

  • { }: statement 函数体, 和普通函数的函数体一样, 不过在该函数体内, 除了可以使用其参数外, 还可以使用被 [ ] 所捕获的变量.

  • : 捕捉列表 [ ] 和 函数体 { } 不可省略; 若使用了 mutable 关键字 或 显示声明 -> return 返回值, 就不可以省略 ( ) 参数列表, 即使为空.

1. lambda 表达式的使用

lambda 表达式的类型是由编译器自动生成的, 并且带有随机值, 是无法具体的写出 lambda 表达式的类型, 并且 lambda 表达式是一个匿名的函数对象(匿名对象生命周期只有一行), 若不立即使用 或 使用 auto 推导赋值给对象, 则会销毁.

int main()
{
	// 						  显示声明返回值
    int ret = [](int i, int j)->int { return i + j; }(1, 2);
    cout << ret << endl;
    
	// 自动推导返回值
    auto add = [](int i, int j) { return i + j; };
    cout << add(1, 2) << endl;

 	return 0;	
}

在这里插入图片描述

2. lambda 表达式的捕捉列表

lambda 表达式的使用相较于普通函数和仿函数, 增加了捕捉列表;
捕捉列表可以使 lambda 表达式捕捉父作用域中 lambda 表达式之前的所有变量.

  • 传值捕捉 [var]
    捕捉变量 var 的拷贝, 传值捕捉到的参数默认是被 const 修饰的, 若希望修改, 需要使用 mutable 修饰.
int main()
{
    int i = 1;

    auto add = [i](int j)mutable
    { 
        i = j;
        return i;
    };

    cout << add(2) << endl;
	return 0;	
}

在这里插入图片描述

在这里插入图片描述

  • 传引用捕捉 [&var]
    捕捉变量 var 的引用
    在这里插入图片描述
  • 传值捕捉所有 [=]
    捕捉所有变量的引用
    在这里插入图片描述
  • 传值捕捉所有 [&]
    捕捉所有变量的拷贝
    在这里插入图片描述
  • 混合捕捉
    捕捉所有(或部分)变量的拷贝(或引用)
    在这里插入图片描述

注:

  • 父作用域是指包含 lambda 函数的语句块, 捕捉列表可以捕捉父作用域中位于 lambda 函数之前定义的所有变量;
  • lambda 表达式的捕捉列表不允许变量重复传递, 否则就会导致编译错误;
  • 在块作用域中的lambda函数仅能捕捉父作用域中的局部变量, 捕捉任何非此作用域或者非局部变量都会导致编译报错;
  • lambda 表达式之间不能相互赋值.

二. 包装器

包装器是一种特殊的类模板, 是适配器的一种, 可以将相同或类似使用方式, 但类型不同的对象, 包装为同一类型, 使其可以达到类似回调函数的效果.

例:
下列的函数, 仿函数, lambda 表达式的使用方式相同, 但却会实例化出不同类型的 useF() 函数.

void func(int i) 
{
    i++;
}

struct Functor 
{
    void operator()(int i)
    {
        i++;
    }
};

template<class F>
void useF(F f, int i) 
{
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    f(i);
}

int main()
{
    int i = 0;
    // 函数指针
    useF(func, i);
    // 仿函数
    useF(Functor(), i);
    // lambda 表达式
    useF([](int i) { i++; }, i);

    return 0;
}

在这里插入图片描述
C++11 中增加了 function 包装器解决了这类问题, function 包装器是使用可变参数模板实现的.

#include <functional>
// 类模板原型
template <class Ret, class... Args>	//Ret 被调用函数的返回类型, Args… 被调用函数的形参
class function<Ret(Args...)>;		
int main()
{
    int i = 0;
    // void 返回值, int 参数类型
    function<void(int)> f;   

    // 函数指针
    f = func;
    useF(f, i);

    // 仿函数
    f = Functor();
    useF(f, i);

    // lambda 表达式
    f = [](int i) { i++; };
    useF(f, i);


    return 0;
}

在这里插入图片描述
function 包装器也可以包装静态成员函数, 只需指明类型.

class Func
{
public:
    static void func()
    {}
};

int main()
{
    int i = 0;
    // void 返回值, 无参数类型
    function<void()> f;

    f = Func::func;

    return 0;
}

若绑定非静态成员函数, 非静态成员函数中有隐藏的 this 指针, 需要将类的对象一起传入.

class Add
{
public:
    int add(int i, int j)
    {
        return i + j;
    }
};

int main()
{
    int i = 0;
    // int 返回值, Add, int, int 参数类型
    function<int(Add, int, int)> f;

    Add a;
    f = &Add::add;
    cout << f(Add(), 1, 1) << endl;

    return 0;
}

在这里插入图片描述
也可以传对象的指针, 但必须是左值的对象, 右值是没有地址的.

class Add
{
public:
    int add(int i, int j)
    {
        return i + j;
    }
};

int main()
{
    int i = 0;
    // int 返回值, Add*, int, int 参数类型
    function<int(Add*, int, int)> f;

    Add a;
    f = &Add::add;
    cout << f(&a, 1, 1) << endl;

    return 0;
}

三. bind 绑定

bind 是一个函数模板, 包含在 <functional> 头文件中, bind 可以接受一个可调用对象(callable object), 然后生成一个新的可调用对象来 “适应” 原对象的参数列表.

bind 可以调整可调用对象的参数 – 包括调整参数顺序和调整参数个数.

template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);
  • 参数:
    fn: 需要绑定的函数或函数对象的指针;
    args: 可变参数包, 是函数或函数对象需要调整的参数, 也可以使用占位符(placeholders)对参数进行占位, 表示该位置的参数需要在调用时再传递进来.

placeholders 是 C++11 引入的一个命名空间, 其中的 _1, _2, …, _N 称为占位符,分别表示函数中的第 1, 2, …, N 位参数, 可以直接使用
例: 调整参数顺序

int func(int i, int j)
{
    return i - j;
}

int main()
{
    int i = 0;

    //                  _2 代表实参的第二个参数, _1 代表实参的第一个参数
    auto f = bind(func, placeholders::_2, placeholders::_1);

    // 1 传参给 _1, _1 传参给 j;  2 传参给 _2, _2 传参给 i;
    cout << f(1, 2) << endl;

    return 0;
}

可以和包装器配合使用, 调整参数的个数

class Add
{
public:
    int add(int i, int j)
    {
        return i + j;
    }
};

int main()
{
	   Add a;
    int i = 0;
    
    // int 返回值, Add, int, int 参数类型
    function<int(int, int)> f;
    f = bind(&Add::add, Add(), placeholders::_1, placeholders::_2);
    cout << f(1, 1) << endl;

    return 0;
}

在这里插入图片描述

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值