闭包->函数对象包装器function和bind->lambda表达式

目录

1. 什么是闭包?

1.1 闭包的实现方式有哪几种?

2. 函数对象包装器function和bind有啥用?

2.1 function支持对四种函数的封装:

2.2 bind机制:

3. lambda表达式

3.1 lambda表达式的构成

3.2 lambda与仿函数的关系

3.3 lambda的类型

3.4 lambda的优势

4. function、bind和lambda表达式的配合使用


1. 什么是闭包?

        一个函数,带上了状态,就变成了闭包了。那什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量,这些个变量的值是创建闭包的时候设置的,并在调用闭包的时候,可以访问这些变量。函数是代码,状态是一组变量,将代码和一组变量捆绑 (bind) ,就形成了闭包。

1.1 闭包的实现方式有哪几种?

        1.仿函数

        2.std::function()

        3.std::bind()

        4.lambda表达式

2. 函数对象包装器function和bind有啥用?

        所谓函数对象包装器,其实和vector和list等容器类似,只不过函数对象包装器是对函数的包装,容器是对各种数据类型的包装,简单说function和bind为函数提供了一种容器;用于需要使用回调函数的情况下,将参数从函数指针类型,转变为被函数容器封装的对象,通过仿函数的形式来调用被绑定的可调用对象,便于操作;

例如:

#include <functional>

using namespace std;

int test (int n) {
    cout<< n << endl;
    return n;
}

int main () {
    test(123);
    std::function<int (int)> f = test;
    f(123);
    return 0;

}

通过function函数,绑定到test函数上,将test函数包装成一个对象f,后续便可以通过该对象f对test函数进行调用;效果一样;有了function,

2.1 function支持对四种函数的封装:

        1.普通函数

        2.匿名函数 (lambda表达式)

        3.类的成员函数

        4.仿函数(重载了operator()运算符的函数  )

#include <functional>

using namespace std;


int test (int n) {
    cout<< n << endl;
    return n;
}

class A {

    public:
        A(){};
    
        int MyTest(int n) {
            cout<<n<<endl;
            return n;
        }
        
        int operator() (int n) {

            cout<<n<<endl;
            return n;
        }

};

int main () {
    test(123);
    //普通函数
    std::function<int (int)> f1 = test;
    f1(123);
    
    //匿名函数
    std::function<int (int)> f2 = [](int n)->int {
        cout<<n<<endl; 
        return n;
    };

    //类的成员函数,因为类的成员函数默认会传递该对象的this指针,所以需要写出&A
    std::function<int (&A,int)>  f3 = &A::MyTest;
    A a; //需要先定义对象
    f3(&a,123);

    //仿函数
    std::function<int (&A, int)> f4 = &A::operator();
    A b;
    f4(&b,123);

    return 0;

}

无论是什么类型的函数,使用std::function进行绑定之后,对该可调用对象的使用操作都是一致的,绑定后通过仿函数来调用被绑定的可调用对象;

std::function对象最大的用处就是在实现函数回调

2.2 bind机制:

        bind与function作用一致,都是将函数包装成一个对象,使用对象进行函数调用,将可调用对象的操作统一化。但bind又有其特殊点,bind机制把一个函数的函数体和它的参数一起进行绑定封装,绑定后形成一个新的可操作的对象,适用于函数参数较多的函数,根据不同的情况,可以对参数的值进行随意指定;这种机制在回调函数的使用过程中也颇为有用

#include<functional>

using namespace std;

void test(int a, int b, int c){
        cout<<a<<b<<c<<endl;
}

int main(){

    auto a = std::bind(test, 1, 2, 3);//将test函数和它的三个给定参数打包封装成一个新的对象a
    a();通过函数对象调用test函数
    
    auto b = std::bind(test, std::placeholders::_1, 2, std::placeholders::_2)//将函数test和它的三个参数全都绑定在一起,封装成一个新的对象b,函数的第2参数已经给定,第1,3个参数使用占位符placeholders占位,等运行时根据传入的实际参数再确定参数的值
    b(1, 3);//1是传入的第一个参数,即placeholders_1, 3是第二个参数,即placeholders::_2的值
}

3. lambda表达式

C++11中的lambda表达式用于定义并创建匿名的函数对象,以简化编程工作

3.1 lambda表达式的构成

 

函数对象参数

[],标识一个lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义lambda为止时lambda所在作用范围内可见的局部变量(包括lambda所在类的this)。函数对象参数有以下形式:

    1. 空。没有使用任何函数对象参数。
    2. =。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
    3. &。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
    4. this。函数体内可以使用lambda所在类中的成员变量。
    5. a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
    6. &a。将a按引用进行传递。
    7. a, &b。将a按值进行传递,b按引用进行传递。
    8. =,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
    9. &, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。

操作符重载函数参数

标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。

可修改标示符

mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。

错误抛出标示符

exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)

函数返回值

->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

是函数体

       {},标识函数的实现,这部分不能省略,但函数体可以为空。

3.2 lambda与仿函数的关系

class MyFunctor
{
public:
    MyFunctor(int tmp) : round(tmp) {}
    int operator()(int tmp) { return tmp + round; }

private:
    int round;
};

int main()
{
    //仿函数
    int round = 2;
    MyFunctor f1(round);//调用构造函数
    cout << "result1 = " << f1(1) << endl; //operator()(int tmp)

    //lambda表达式
    auto f2 = [=](int tmp) -> int { return tmp + round; } ;
    cout << "result2 = " << f2(1) << endl;

    return 0;
}

除去在语法层面上的不同,lambda和仿函数有着相同的内涵——都可以捕获一些变量作为初始化状态,并接受参数进行运行。

而事实上,在C++11中,lambda可以视为仿函数的一种等价形式,编译器内部依然将lamda表达式转化为仿函数,只是lamda的使用要更方便,代码更易读,使用仿函数还要先在类中声明和定义,lambda则可以直接使用。

仿函数是类中重载了()运算符,使得类的对象在使用operator()时表达式形式像函数一样,例如:obj(i),而lamda表达式作为仿函数的等价形式,在使用的时候也是lmd_obj(i), i为接收的参数,

即[](){}中()内的参数;

3.3 lambda的类型

lambda表达式的类型在C++11中被称为“闭包类型”,每一个lambda表达式会产生一个临时对象(右值)。因此,严格地将,lambda函数并非函数指针。

不过C++11标准却允许lambda表达式向函数指针的转换,但提前是lambda函数没有捕获任何变量,且函数指针所示的函数原型,必须跟lambda函数有着相同的调用方式(参数的个数和类型要一致)。

int main()
{
    //使用std::function和std::bind来存储和操作lambda表达式
    function<int(int)> f1 = [](int a) { return a; };
    function<int()> f2 = bind([](int a){ return a; }, 123);
    cout << "f1 = " << f1(123) << endl;
    cout << "f2 = " << f2() << endl;

    auto f3 = [](int x, int y)->int{ return x + y; }; //lambda表达式,没有捕获任何外部变量
    typedef int (*PF1)(int x, int y);   //函数指针类型
    typedef int (*PF2)(int x);

    PF1 p1;     //函数指针变量
    p1 = f3;    //ok, lambda表达式向函数指针的转换
    cout << "p1 = " << p1(3, 4) << endl;

    PF2 p2;
    p2 = f3;     //err, 编译失败,参数必须一致

    decltype(f3) p3 = f3;   // 需通过decltype获得lambda的类型
    decltype(f3) p4 = p1;   // err 编译失败,函数指针无法转换为lambda

    return 0;
}

3.4 lambda的优势

        lambda表达式的价值在于,就地封装短小的功能闭包,可以及其方便地表达出我们希望执行的具体操作,并让上下文结合更加紧密。

4. function、bind和lambda表达式的配合使用


    //使用std::function和std::bind来存储和操作lambda表达式
    function<int(int)> f1 = [](int a) { return a; };
    function<int()> f2 = bind([](int a){ return a; }, 123);
    cout << "f1 = " << f1(123) << endl;
    cout << "f2 = " << f2() << endl;

    auto f3 = [](int x, int y)->int{ return x + y; }; //lambda表达式,没有捕获任何外部变量

如上代码所示,函数包装器将lambda表达式包装成一个对象,对象以仿函数的形式来调用lambda表达式;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
首先,我们需要定义文法符号的编号,如下: ``` 0: D' 1: D 2: T 3: L 4: i 5: f 6: , 7: x 8: $ ``` 其中,`0` 表示起始符号,`8` 表示结束符号。 然后,我们可以按照以下步骤构造 LR 项目集族: 1. 初始化第 0 个项目集为 {`D' -> .D`}。 2. 对于每个项目集 `I` 和文法符号 `X`,进行如下操作: a. 遍历 `I` 中的每个项目 `A -> α.Xβ`。 b. 对于 `X` 的每个产生式 `X -> γ`,构造新的项目 `X -> .γ` 并将其加入集合 `J` 中。 c. 对于 `J` 中的每个项目 `X -> .γ`,计算其闭包 `closure(X -> .γ)` 并将其加入集合 `I` 中。 d. 若集合 `I` 为新的项目集,则重复以上操作,直到不再出现新的项目集。 e. 记录 `I` 中 `X -> .γ` 对应的项目编号,并将其加入 LR 分析表的对应位置。 下面是构造过程的详细过程: | I0: {D' -> .D} | | --- | 1. 初始项目集为 `{D' -> .D}`。 | I0: {D' -> .D} | | --- | | D -> .TL | 2. 对于 `I0` 中的项目 `D -> .TL`,有两个产生式 `T -> i` 和 `T -> f`。因此,构造两个新的项目: ``` T -> .i T -> .f ``` 并将它们加入集合 `J0` 中: ``` J0: {T -> .i, T -> .f} ``` 接下来,计算 `J0` 中每个项目的闭包: ``` closure(T -> .i): { T -> .i } closure(T -> .f): { T -> .f } ``` 并将它们加入项目集 `I0` 中: ``` I0: { D' -> .D, T -> .i, T -> .f } ``` 最后,记录 `T -> .i` 和 `T -> .f` 对应的项目编号,并将其加入 LR 分析表的对应位置: ``` ACTION[0,i] = shift(1) ACTION[0,f] = shift(2) ``` | I0: {D' -> .D, T -> .i, T -> .f} | | --- | | T -> .i | | T -> .f | 3. 对于 `I0` 中的项目 `T -> .i`,没有新的产生式可以扩展,因此不需要进行操作。 4. 对于 `I0` 中的项目 `T -> .f`,有两个产生式 `L -> L,x` 和 `L -> x`。因此,构造两个新的项目: ``` L -> .L,x L -> .x ``` 并将它们加入集合 `J1` 中: ``` J1: {L -> .L,x, L -> .x} ``` 接下来,计算 `J1` 中每个项目的闭包: ``` closure(L -> .L,x): { L -> .L,x, L -> .x } closure(L -> .x): { L -> .x } ``` 并将它们加入项目集 `I1` 中: ``` I1: { T -> .f, L -> .L,x, L -> .x } ``` 最后,记录 `L -> .L,x` 和 `L -> .x` 对应的项目编号,并将其加入 LR 分析表的对应位置: ``` ACTION[1,x] = shift(3) ACTION[1,$] = reduce(L -> x) ``` | I1: {T -> .f, L -> .L,x, L -> .x} | | --- | | L -> .L,x | | L -> .x | 5. 对于 `I1` 中的项目 `L -> .L,x`,只有一个产生式 `L -> L,x` 可以扩展,因此构造新的项目: ``` L -> L.,x ``` 并将其加入集合 `J2` 中: ``` J2: {L -> L.,x} ``` 接下来,计算 `J2` 中的项目闭包: ``` closure(L -> L.,x): { L -> L.,x, L -> .L,x, L -> .x } ``` 并将其加入项目集 `I2` 中: ``` I2: { L -> L.,x, L -> .L,x, L -> .x } ``` 最后,记录 `L -> L.,x` 对应的项目编号,并将其加入 LR 分析表的对应位置: ``` ACTION[2,x] = shift(4) ``` | I2: {L -> L.,x, L -> .L,x, L -> .x} | | --- | | L -> L.,x | | L -> .L,x | | L -> .x | 6. 对于 `I2` 中的项目 `L -> .L,x`,只有一个产生式 `L -> L.,x` 可以扩展,因此构造新的项目: ``` L -> L.,x ``` 并将其加入集合 `J3` 中: ``` J3: {L -> L.,x} ``` 接下来,计算 `J3` 中的项目闭包: ``` closure(L -> L.,x): { L -> L.,x, L -> .L,x, L -> .x } ``` 并将其加入项目集 `I3` 中: ``` I3: { L -> L.,x, L -> .L,x, L -> .x } ``` 最后,记录 `L -> L.,x` 对应的项目编号,并将其加入 LR 分析表的对应位置: ``` ACTION[3,x] = reduce(L -> L) ACTION[3,$] = reduce(T -> fTL) ``` 最终得到的 LR 项目集族如下: | I0: {D' -> .D, T -> .i, T -> .f} | | --- | | T -> .i | | T -> .f | | I1: {T -> .f, L -> .L,x, L -> .x} | | --- | | L -> .L,x | | L -> .x | | I2: {L -> L.,x, L -> .L,x, L -> .x} | | --- | | L -> L.,x | | L -> .L,x | | L -> .x | | I3: {L -> L.,x, L -> .L,x, L -> .x} | | --- | | (reduce) L -> L | | (reduce) T -> fTL | LR 分析表如下: | | i | f | , | x | $ | | -- | -------- | -------- | -------- | -------- | -------- | | 0 | shift(1) | shift(2) | | | | | 1 | | | shift(3) | reduce | reduce | | 2 | | | | shift(4) | reduce | | 3 | | | | reduce | reduce | | 4 | shift(1) | shift(2) | | | |
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值