10.3lambda表达式

定制操作

很多算法都会比较输入序列中的元素。默认情况下,这类算法使用元素类型的<或==运算符进行比较。

sort算法默认使用元素类型的<运算符进行升序排列。但是我们希望的排序顺序与<所定义的顺序不同,或者我们的序列可能保存的是未定义的<运算符的元素类型(如Sales_data)。这两种情况下都需要重载sort的默认行为,使用这些算法的额外版本,允许我们提供自己定义的比较运算符来代替默认运算符

有的算法接收谓词作为其参数,有的算法接收可调用对象作为其参数。

1.向算法传递函数

想要使用重载版本的算法来代替算法的默认形式,则需要向算法传递一个叫谓词的参数。

谓词是一个可调用的表达式,其返回结果是bool类型

①标准库算法所用的谓词只能接收一个参数或者两个参数,因此可以分为两类:一元谓词和二元谓词:

  • 一元谓词:只接受一个参数的谓词。
  • 二元谓词:只接受两个参数的谓词。

②算法的谓词模式包括以下五种:

  • 函数谓词:通过传递函数名,匹配谓词。

  • 函数指针谓词:建立一个函数指针,使用指针代替函数名,类似函数谓词。

  • lambda表达式谓词:可匹配一元、二元谓词,如果希望的操作需要更多的参数,超出算法对谓词的限制,可以通过捕获列表达到效果。

  • 函数对象(重载函数调用运算符()的自定义类型的对象)谓词函数对象使用函数调用运算符()时看起来很像一个函数调用,因此又叫仿函数返回bool类型的仿函数叫函数对象谓词

  • 库定义的函数对象谓词:标准库定义的函数对象,充当算法的谓词。

如:find_if函数接收一元谓词,查找第一个长度大于5的元素。如果有匹配的元素,则返回这个元素的迭代器,否则,返回尾后迭代器。

bool greaterFive(string &s) {
    return s.size() > 5 ? true : false; 
}
int main() {
    vector<string> vec{ "abc", "bcde", "aaaaaa" };
    auto iter = find_if(vec.begin(), vec.end(), greaterFive);
    cout << *iter << endl;
    return 0;
}
//输出结果为:"aaaaa"

2.lambda表达式

算法只接受一元谓词和二元谓词,我们传递给算法的谓词必须严格接收一个或两个参数。但是,有时我们希望进行的操作需要更多参数超出了算法对谓词的限制,这时候就可以用lambda表达式来解决这个问题。

如:对给定一个字符序列,对其进行字典降序,并返回第一个大于等于给定长度sz的元素的迭代器。

很显然,如果用函数作为谓词的话,需要3个参数,超出了算法对谓词的限制。

2.1介绍lambda

①可调用对象

可以向算法传递任何类别的可调用对象。对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。即,如e是一个可调用的表达式,则可以写成e(args)的形式,args表示一个参数列表。

②lambda的定义

一个lambda表达式表示一个可调用的代码单元,相当于一个未命名的内联函数。与任何函数类似,lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部(函数不能定义在函数内)。

lambuda表达式具有如下形式:

[capature list](parameter list)->return type {function body}

其中,capture表示捕获列表、prarmeter list表示参数列表、return type表示尾置返回类型、function body表示函数体。参数列表和返回类型可以省略,但必须永远包含捕获列表和函数体。

如:定义一个可调用对象f,不接受任何参数,返回42。将lambda表达式赋值给一个对象,这个对象就变成了一个可调用对象。

auto f = [] {return 42;}
cout << f() << endl;

③lambda省略参数列表和返回类型的情况

在lambda中忽略参数列表等价于一个空参数列表

如果忽略返回类型,lambda根据函数体中的代码推断返回类型:

  • 如果函数体只是一个return语句,则返回类型从返回的表达式类型推断出来。
  • 否则,返回类型为void。如果lambuda的函数体中包含任何单一return语句之外的内容,且未指定返回类型,则返回void。
  • 只有lambda返回bool类型的时候才能用作谓词。

④向lambda传递参数(调用lambda)

调用一个lambda与函数调用类似,但不同的是lambda不能有默认参数,因此,调用的实参数目永远与形参相等。调用lambda的方法一般有两种:

  • 将lambda赋值给一个可调用对象

如:返回两个字符串中较小的一个

auto f = [](const string &s1, const string &s2) {
    return s1.size() < s2.size() ? s1 : s2;
}
string s = f("abc", "abcd");
cout << s << endl;
//结果为:abc
  • 作为谓词直接作为算法的参数,算法运行时会调用。

如:用sort算法按字符串长度进行升序。

vector<string> s{"kbc", "abdee", "akzz", "op"};
sort(s.begin(), s.end(), 
     [](const string &s1, const string &s2)
     {return s1.size() > s2.size();});
for (auto s : s) {
    cout << s << " ";
}
//结果为:abdee akzz kbc op

⑤使用捕获列表

一个函数不能出现在另一个函数中,而lambda可以出现在一个函数中,使用其局部变量,但它只能使用捕获列表中的局部变量

如:我们希望lambda表达式能将输入序列中每个string的长度与函数中的sz进行比较。查找第一个长度不小于sz的迭代器。

vector<string> s{"abc", "abcd", "abcde"};
int sz = 3;
auto it = find_if(s.begin(), s.end(), 
                 [sz](const string &s){return s.size() > sz;});
cout << *it << endl;
//结果为:abcd

又如:打印所有长度大于sz的字符串。

vector<string> s{"abc", "abcd", "abcde"};
int sz = 3;
for_each(s.begin(), s.end(), [sz](const string &s)
         {if (s.size() > sz) cout << s << endl; });
//结果为:abcd

3.lambda捕获

定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。即:当向一个算法函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是编译器生成的类类型的未命名的对象。

类似函数的参数传递,变量的捕获方式也可以是值或者引用

①值捕获

采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda被创建时拷贝,而不是调用时拷贝:

如:

void func() {
    int v = 42;
    auto f = [v]{return v;};
    v = 0;
    int j = f();
    //j为42,如果lambda被调用时再拷贝捕获的对象,那j就应该时0.
}

②引用捕获

定义lambda时也可以采用引用捕获的方式。如:

void func2() {
    int v1 = 42;
    auto f2 = [&v1]{return v1;};
    v1 = 0;
    auto j = f2();
    cout << j << endl;
    //j为0,因为f2捕获的是一个引用,因此,当v1变得时候,捕获得值也会改变。
}

注意:当以引用方式捕获一个变量时,必须保证在lambda执行时变量时存在得。

③隐式捕获

根据函数体中代码推断我们要使用哪些变量。捕获列表用=或&来说明采用哪种捕获方式。

④可变lambda

对于一个值被拷贝的变量lambda不会改变其值。如果我们希望改变捕获的变量的值,就必须在参数列表后面加上关键字mutable,因此可变lambda不能省略参数列表。

如:

void func3() {
    int v = 42;
    auto f = [v] () mutable {return ++v;};
    auto j = f();
    cout << j << endl;
}
//结果为:43

对于一个引用捕获的变量是否可以修改依赖此引用指向的是一个const还是一个非const类型。

如:

void func4() {
    int v = 42;
    //如果是const int v = 42;则不可改变v的值
    auto f = [&v] {return ++v;};
    int j = f();
    cout << j << endl;
}
//结果为:43

4.指定lambda返回类型

如果一个lambda函数体包含return之外的任何语句,则lambda默认返回void,而被推断返回void的lambda不能返回值。

如:

vector<int> vec{ 1, -1, -5, -10, 5 };
vector<int> vec2;
transform(vec.begin(), vec.end(), back_inserter(vec2), [](int a) 
          {if (a < 0) return -a; else return a;);     
//错误:lambda推断返回void,不能返回值,应写成
transform(vec.begin(), vec.end(), back_inserter(vec2), [](int a) 
          { return a < 0 ? -a : a;);   

当我们需要为一个lambda自定义返回类型时,必须使用尾置返回类型

如:对上述进行改进

vector<int> vec{ 1, -1, -5, -10, 5 };
vector<int> vec2;
transform(vec.begin(), vec.end(), back_inserter(vec2), [](int a)->int 
          {if (a < 0) return -a; else return a;});    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值