简述泛型算法之_二lambda表达式

简述泛型算法之 二lambda表达式

背景:
我们传递的算法必须严格接受一个或两个参数,但是有时我们希望进行的操作需要更多的参数,超出了算法对谓词(predicate)的限制。
在上篇博客简述泛型算法之 一认识算法末尾提到,要分割的长度大于n,普通方法无能为力。这个时候,就需要lambda表达式”登场了”。

lambda表达式

我们可以为任何一个算法传递任何类别的可调用对象。
可调用对象:对于一个对象或者表达式,如果能够对其使用调用运算符,则称它为可调用的;如果e可调哟个,则可以写代码e(args) arges为参数列表
之前学过的可调用对象:函数,函数指针;

lambda表达式:表示一个可调用的代码单元。可以理解为一个未命名的内敛函数,与任何函数类似,一个lambda具有返回类型,参数列表,函数体。
但与函数不同的是,lambda可以定义在函数内部。一个lambda表达式具有如下形式:
[capture list](parameter list) -> return type {funtion body}
capture list(捕获列表)是一个lambda对象所在函数中定义的局部变量的列表;
return type, parameter list, function body 与普通函数一样
但与普通函数不同的是,lambda必须使用尾置返回(->)来指定类型
我们可以忽略函数参数列表,但是必须永远包含捕获列表和函数体。

lambda基本用法

//定义一个可调用对象f,不接受参数;
auto f1 = [] { return 34; };
//调用方式与普通函数一样,使用函数调用运算符
cout << f1() << endl;

//传递参数:与普通函数不同,lambda不能有默认实参
//编写一个与isShorter功能完全一样的lambda
auto f2 = [](const string& a, const string& b) { return a.size() < b.size(); };

vector<string> vss{"the", "quick", "red", "fox", "jumps", "over", "the","slow", "red", "turtle"};
//使用lambda调用stable_sort
stable_sort(vss.begin(), vss.end(), f2);
stable_sort(vss.begin(), vss.end(),
        [](const string& a, const string& b) {
            return a.size() < b.size();
        });

lambda捕获变量

虽然lambda可以定义在函数内,使用其局部变量,但是它只能使用那些明确指明的变量。
lambda通过将局部变量包含在其捕获列表[]中来指出将会使用这些变量

//值捕获
{
    size_t sz = 43;
    //值捕获即值的拷贝,且值拷贝发生在lambda定义时,而非调用时,所以sz不会被改变,且lambda内部不能改变值捕获的变量
    auto f = [sz] {return sz;};
    sz = 0;
    cout << f() << endl;    //43  因为是拷贝

    //lambda内部默认不能改变值捕获的变量,改变的方法:加上mutable (有没有觉得很熟?哪里见过?见后文)
    sz = 43;
    auto g = [sz]()mutable { return ++sz; };
    cout << g() << endl;
}

//引用捕获
//引用捕获相当于定义了一个引用,因此必须遵循引用相关的规则:即必须引用到一个已经存在的变量
{
    size_t sz = 43;
    //引用捕获可以改变捕获的变量,而且内部也可以改变;
    //引用捕获有时是必要的,因为有些类型无法拷贝:iostream, unique_ptr
    auto f = [&sz] { return ++sz; };
    sz = 0;
    cout << f() << endl;    //0  因为是引用
}
//显示捕获,隐式捕获
{
    size_t sz = 0;
    //= 标识隐式值捕获
    auto f = [=] { return sz; };

    //& 标识隐式引用捕获
    auto g = [&] { return sz; };

    cout << f() << g() << endl;
}

隐式,显式,值,引用捕获混合使用
当我们混合使用隐式和显式捕获时,捕获列表中第一个元素必须 是& 或者 =,此符号指定了默认捕获方式
而且,显式捕获的变量和隐式捕获的变量必须采用不同的方式(值和引用)
即不能出现此种情况 [&, &c] or [=, =c];
只能出现此种情况: [&, =c] or [=, &c];

{
    int c = 0;
    //cout:隐式引用捕获; c:显式值捕获
    auto f = [&, c] { cout << c << endl; };

    //c:隐式值捕获 cout:显式引用捕获
    ostream& os(cout);
    auto g = [=, &os] { os << c << endl; };
}

综上所述:尽可能保持lambda的变量捕获简单化,尽量减少捕获的数据量;如果可能,尽量避免捕获引用或指针

指定lambda返回值
ex:算法对输入序列中每个元素调用可调用对象,并将结果写回目的位置。

transform(v.begin(), v.end(), v.begin(),
          [](int i) {
              return i < 0 ? -i : i;
          });

但如果写成这样,会产生编译错误

transform(v.begin(), v.end(), v.begin(),
          [](int i) {
              if (i < 0) {
                  return -i;
              } else {
                  return i;
              }
          });

(C++11会产生错误,但C++14不会;笔者的xcode支持C++14,所以这样写也不报错)
改成如下形式,指定返回值则不产生错误

transform(v.begin(), v.end(), v.begin(),
          [](int i) -> int {
              if (i < 0) {
                  return -i;
              } else {
                  return i;
              }
          });

小例子:求大于等于一个给定长度的单词有多少,使程序只打印大于等于给定长度的单词

//消除重复单词
void elimDups(vector<string> &words) {
    sort(words.begin(), words.end());
    auto end_unique = unique(words.begin(), words.end());
    words.erase(end_unique, words.end());
}

//只打印大于等于给定长度(sz)的单词
void biggies(vector<string>& words, vector<string>::size_type sz) {
    elimDups(words);
    stable_sort(words.begin(), words.end(),
                [](const string &a, const string &b) {
                    return a.size() < b.size();
                });    //lambda表达式代替上篇博客的函数指针
    auto wc = find_if(words.begin(), words.end(),
                      [sz](const string& a) {
                          return a.size() >= sz;
                      });    //lambda表达式
    auto count = words.end() - wc;
    cout << count << " ";
    for_each(wc, words.end(),
             [](const string &a) {
                 cout << a << " ";
             });
    cout << endl;
}

上述代码仅仅用于应用lambda测试,也有更简单的办法

void biggies_2(vector<string>& words, vector<string>::size_type sz) {
    vector<string>::size_type count = 0;
    for (auto iter = words.begin(); iter != words.end(); ++iter) {
        if (iter->size() >= sz) {
            ++count;
            cout << *iter << " ";
        }
    }
    cout << count << endl;
}

笔者的代码没有修改原vector,而书中代码修改了vector,所以视情况而选择方法。(此处仅仅是学习lambda)

lambda实现原理

当编写一个lambda时,编译器生成一个与lambda对应的未命名的类的未命名对象。在lambda产生的类中,含有一个重载的函数调用运算符。

ex1:没有捕获变量

stable_sort(vss.begin(), vss.end(),
            [](const string& a, const string& b) {
                return a.size() < b.size();
            });

其行为类似于下面这个类的未命名对象

class ShorterString {
public:
    bool operator()(const string& s1, const string& s2) const {
        return s1.size() < s2.size();
    }
};

产生的类只有一个函数调用运算符成员。为何默认情况下不能修改它捕获的变量?因为该函数默认是const。
此时,通过该类替代lambda后,可以重写函数如下:
stable_sort(vss.begin(), vss.end(), ShorterString());
注意: ShorterString()为类构造的临时对象;

ex2:捕获变量
当通过引用捕获时,将由程序负责确保引用所引的对象确实存在。因此编译器可以直接使用该引用而无需在类中存储数据成员。
相反,当通过引用捕获时,lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其捕获的变量初始化数据成员。

auto wc = find_if(v.begin(), v.end(), 
                [sz](const string &a) {
                    return a.size() >= sz;
                });

该lambda表达式产生类形如:

class SizeTmp {
public:
    SizeTmp(size_t n):sz(n) {}
    bool operator()(const string& s) const {
        return s.size() >= sz;
    }
private:
    size_t sz;
};

到这里,就差最后一个困惑了?
值捕获不能修改捕获的变量,如果要修改则必须加上mutable?why?
当我问完问题,答案也出来了。因为有一种成员变量即使是const成员函数,也能修改。在定义的时添加mutable。

private:
    mutable size_t sz;
};

所以,在之前提到过,要想修改捕获的变量,也只需要加上mutable.

lambda表达式产生的类不含默认构造函数,赋值运算符以及默认析构函数;它是否有默认拷贝/移动函数通常要视捕获的数据成员类型而定。

bind

虽然本文中谈到lambda简洁且强大,但在某些时候仍然有其局限性,最后就得使用”终极武器”bind。详情见下篇简述泛型算法之 三bind 参数绑定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值