c++11之lambda表达式

参考:c++primer

主要介绍泛型算法中的谓词


向算法传递函数

有些算法接受一个叫做谓词的参数,所谓谓词,就是一个可调用的表达式,其返回结果是一个能用作条件的值。

标准库算法使用的谓词分为两类:一元谓词(unary predict)二元谓词(binary predict),几元就表示该谓词接受几个参数。

举个例子:

vector<string> names;
//要求按string长度对其进行排序
bool cmp(const string &s1, const string &s2){  //一个二元谓词
    return s1.size() < s2.size();
}

sort(names.begin(), names.end(), cmp); 

有时候上面这种做法是没问题的,不过…

再看这个例子:

find_if算法第三个参数是一个一元谓词,它对每个元素调用这个谓词,返回第一个使这个谓词为真的元素迭代器。
如果我们要查找长度大于某一定值的name,就得传入两个参数,但一元谓词不允许!

bool cmp(const string &s, int sz){
    return s.size() > sz;
}

find_if(names.begin(), names.end(), cmp); //报错,find_if只接受一元谓词

当然解决方式可以把cmp函数中的sz硬编码为某一数值,只传一个参数就可以了。

不过我们有更好的办法,即lambda表达式

lambda表达式

先介绍一下lambda的写法:

[捕获列表](参数列表) -> 返回类型 { 函数体 }  //使用了尾置返回类型,捕获列表和函数体是必须的,其它都可以省略

所谓捕获列表,就是lambda所在函数中定义的局部变量的列表。

还是上面那个例子,用lambda表达式可以这么写:

int sz = 5;
find_if(names.begin(), names.end(), [sz](const string &s){
        return s.size() > sz;
});

还是很好理解的吧~

再举个例子:for_each算法

for_each(names.begin(), names.end(), [](const string &s){
    cout<<s<<" ";
});

当定义一个lambda表达式时,编译器生成了一个与lambda对应的新的未命名的类类型。

比如上面find_if中的lambda对应的类类型是这样的:

class cmp{
public:
    cmp(int sz): sz(sz){ }
    bool operator()(const string &s)const{  //注意这里必须是const,因此不能修改sz
        return s.size() > sz;   
    }
private:
    int sz; //数据成员对应捕获列表
};

//调用时这样
find_if(names.begin(), names.end(), cmp(sz));

于是根据上面生成的类类型我们就可以理解可变lambda是怎么回事:
如果想要修改捕获列表的值,就要用mutable修饰

int sz = 5;
find_if(names.begin(), names.end(), [sz](const string &s) mutable{
        sz++;
        sz--;  //这里只是为了说明问题
        return s.size() > sz;
});
//对应的类如下:
class cmp{
public:
    cmp(int sz): sz(sz){ }
    bool operator()(const string &s)const{  //虽然是const,但是sz用mutable修饰了,因此可以修改sz
        sz++;
        sz--;
        return s.size() > sz;   
    }
private:
    mutable int sz; //数据成员对应捕获列表
};

当然也可以用引用捕获,不过这样以来捕获列表就是一个别名,能否被修改就取决于该值定义是否是const。
再看一个例子:

int a = 3, b = 3;
auto f = [&a]{
    a++;
    return a;   
};
auto g = [b]()mutable{  //注意这里必须用mutable修饰,而且参数列表那个括号不可以省略
    b++;
    return b;
};
int ans1 = f(); //ans1 = 4;
int ans2 = g(); //ans2 = 4;
cout<<"ans1 =: "<<ans1<<endl;
cout<<"ans2 =: "<<ans2<<endl;
a = 0;  //会影响到f中的a值,二者是同一个对象
b = 0;  //g中的b是它的副本,没有直接关系
ans1 = f(); //ans1 = 1;
ans2 = g(); //ans2 = 5;
cout<<"ans1 =: "<<ans1<<endl;
cout<<"ans2 =: "<<ans2<<endl;

看完上面这个例子应该彻底理解了吧
下面说明捕获列表的几种情况:

  • 值捕获
  • 引用捕获
  • 隐式捕获

值捕获引用捕获对应函数中的参数传值和传引用,这里就不多说了,写法上引用捕获前面加个&符号。

至于隐式捕获,编译器可以根据函数体中用到的变量推断出捕获列表,自动捕获,不过我们需要告诉编译器是值捕获([=])还是引用捕获([&]),也可混用。

值捕获引用捕获默认引用捕获,指定值捕获默认值捕获,指定引用捕获
[=][&][&, list][=, list]

其中list是逗号分隔的参数列表。

下面再说返回类型:

如果lambda表达式中只有一条返回语句,那么不需要写返回类型,编译器会自动推断,我们上面写的就是这种。

如果除了返回语句之外的其他语句,编译器则默认返回void。

然而实测之后编译器可自动推断返回类型。。。
比如:

vector<int> v{1, 4, -5};
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;   //没有像书上说的报错
});

参数绑定

lambda是匿名对象,只用一次的地方和方便,如果要多次使用,那么通常应该定义一个函数,不过对于有捕获列表的lambda怎么用函数来替换呢?用bind

bind可以看做通用的函数适配器,可接收一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

还是之前找大于某个长度的name的那个例子,让我们看一下用bind怎么实现:

using namespace std::placeholders;  //注意_1在这个命名空间中
bool cmp(const string &s, int sz){
    return s.size() > sz;
}
int sz = 5;
auto f = bind(cmp, _1, sz);  //_1叫做占位符,对应谓词中第一个参数,以此类推;
find_if(names.begin(), names.end(), f);

需要注意bind中不是占位符的参数是拷贝方式传递的,那么如何才能引用的方式传参呢?这对于不能拷贝的对象(比如ostream)来说至关重要。

方法是用refcref

ref返回一个对象,包含给定的引用,cref生成一个保存const引用的类。

void print(ostream &os, const string &s, string op){
    os<<s<<op;
}
for_each(names.begin(), names.end(), bind(print, ref(cout), _1, " - ")); 

算法命名规范

算法形参模式

alg(beg, end, other args);
alg(beg, end, dest, other args);
alg(beg, end, beg2, other args);
alg(beg, end, beg2, end2, other args);

一些算法使用重载形式传递一个谓词

  • unique(beg, end); //使用==比较元素
  • unique(beg, end, comp); //使用comp比较元素

_if版本的算法

  • find(beg, end, val);//查找val第一次出现的位置
  • find_if(beg, end, pred);//查找第一个另pred为真的元素的位置

区分是否拷贝元素

  • reverse(beg, end); //反转自身
  • reverse(beg, end, dest);//将反转后的拷贝到dest

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值