10.3.1 向算法传递函数

作为一个例子,假定希望在调用elimDups(参见10.2.3节)后打印vector的内容。

此外还假定希望单词按其长度排序,大小相同的再按字典序排列。

为了按长度重排vector,我们将使用sort 的第二个版本,此版本是重载过的,它接受第三个参数,此参数是一个谓词(predicate)

谓词

  • 谓词接受一个参数【一元谓词】或两个参数【二元谓词】,在所有的可能值上定义了一个一致的序
  • sort函数会用谓词函数代替“<”来对元素排序
//比较函数,按长度排序
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size();
}
//由短到长排序words
sort(words.begin(), words.end(), isShorter);

排序算法

在我们将 words按大小重排的同时,还希望具有相同长度的元素按字典序排列。

为了保持相同长度的单词按字典序排列,可以使用stable_sort算法。这种稳定排序算法维持相等元素的原有顺序。

elimDups(words); //将words按字典序重排,消除重复单词
//按长度重排,长度相同的单词维持字典序
stable_sort(words.begin(), words.end(), isShorter);
//源程序
#include<iostream>
#include<vector>
#include<algorithm>
#include<iterator>
using namespace std;
void elimDups(vector<string> &words){
//按字典排序words,以便查找重复单词
sort(words.begin(), words.end());
//unique重排输入范围,使得每个单词只出现一次
//排列在范围前部,返回指向不重复区域之后一个位置的迭代器
auto end_unique = unique(words.begin(), words.end());
//使用earase删除重复单词
words.erase(end_unique, words.end());
}
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size();
}
int main(){
vector<string> words = {"slow","the","fox","jumps","over","quick",
"red","the","turtle"};
elimDups(words);
stable_sort(words.begin(), words.end(), isShorter);
for(string s : words)
cout<<s<<" ";
cout<<endl;
return 0;
}
//输出:fox red the over slow jumps quick turtle

10.3.2 lambda表达式

引入

求words中大于一个给定长度的单词有多少个,并打印满足条件的单词

将次函数命名为“biggies”,框架如下:

现在的新问题是在vector中寻找第一个大于等于给定长度的元素

我们可以使用标准库find_if 算法来查找第一个具有特定大小的元素。

类似find(参见10.1节,第336页), find_if 算法接受一对迭代器,表示一个范围

但与 find不同的是,find_if 的第三个参数是一个一元谓词

find_if算法对输入序列中的每个元素调用给定的这个谓词。它返回第一个使谓词返回非О值的元素,如果不存在这样的元素,则返回尾迭代器。

编写一个函数,令其接受一个string 和一个长度,并返回一个bool值表示该string的长度是否大于给定长度,是一件很容易的事情。

但是,find_if接受一元谓词——我们传递给find_if 的任何函数都必须严格接受一个参数,以便能用来自输入序列的一个元素调用它。但我们又不得不给它传入第二个参数来表示长度。

为了解决此问题,需要使用另外一些语言特性。

介绍lambda

lambda表达式是一个可调用对象

可调用对象(callable object):

对于任何一个对象或者表达式,只要可以对它使用调用运算符“()”,我们就称该对象或表达式是“可调用的”

常见的可调用对象

  • 函数和函数指针
  • 重载了函数调用运算符的类(14.8节介绍)
  • lambda表达式

lambda表达式可以理解为一个未命名的内联函数,可以定义在函数中

构成:

  • capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);

  • return type、parameter list和 function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。

  • 但是,与普通函数不同,lambda必须使用尾置返回(参见6.3.3节,第206页)来指定返回类型

可以忽略参数列表和返回类型,但必须包含捕获列表和函数体

//定义了一个lambda:不接受任何参数,返回42
auto f = []{return 42;};
//调用方式和函数相同,用调用运算符
cout<<f()<<endl; //打印42

当函数体只有一个return语句时,lambda表达式可以推断出返回类型

如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void。

向lambda传递参数

lambda不接受默认实参

传递参数举例:

[](const string &a, const string &b)
{return a.size() < b.size();}
  • “[]”:不需要所在函数中的任何局部变量
  • “(…)”:参数
  • 只有return语句,忽略返回类型
  • “{…}”:函数体
//按长度排序,长度相同维持字典序
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b)
{return a.size()<b.size();});

使用捕获列表

lambda表达式可以通过捕获列表,使用函数中定义的局部变量

biggies函数中有一个局部变量sz,可以定义如下lambda表达式来把参数的大小与sz比较

[sz](const string &a) {return a.sise() >= sz;}

一个 lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在lambda的函数体中使用该变量。【有点绕,具体看例子】

调用find_if

现在可以写出find_if【在biggies函数内使用,biggies内定义了sz】了

//获取迭代器,指向第一个size>=sz的元素
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
{return a.size() >= sz;});

使用for_each

for_each(迭代器范围, 可调用对象):对迭代器范围内的每个元素调用传入的可调用对象

//打印长度>=给定值的单词,每个单词后面接一个空格
for_each(wc, wc.words.end(),
[](string &s){cout<<s<<" ";});

完整的biggies

void biggies(vector<string> &words, int sz){
elimDups(words); //words按字典排序,删除重复单词
//按长度排序,长度相同的维持字典序
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b)
{return a.size()<b.size();});
//获取迭代器,指向第一个满足size()>=sz的元素
auto wc = find_if(words.begin(), words.end(),
[sz](const string &s)
{return s.size()>=sz;});
//计算满足size>=sz的元素个数
auto count = words.end() - wc;
cout<<"count = "<<count<<endl;
//打印长度大于给定值的单词
for_each(wc, words.end(),
[](string &s)
{cout<<s<<" ";});
cout<<endl;
}

10.3.3 lambda的捕获和返回

当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。我们将在14.8.1节(第507页)介绍这种类是如何生成的。

目前,可以这样理解,当向一个函数传递一个lambda 时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。

类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。

默认情况下,从 lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。

值捕获

类似于参数传递,捕获的方式可以是值捕获也可以是引用捕获

采用值捕获的前提是可以拷贝

与函数参数传递不同,lambda表达式捕获的变量的值是在创建时拷贝,而不是调用时拷贝

void fcn1(){
int v1 = 42;
auto f = [v1]{return v1;};
v1 = 0;
cout<<f()<<endl;
//输出为42
}

引用捕获

void fcn2(){
int v1 = 42;
auto f = [&v1]{return v1;};
v1 = 0;
cout<<f()<<endl;
//输出为0
}

引用捕获是必要的,毕竟有些对象如ostream对象,是无法拷贝的,只能采用引用捕获

采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的

lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了。如果 lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失。

我们也可以从一个函数返回 lambda。函数可以直接返回一个可调用对象,或者返回一个类对象,该类含有可调用对象的数据成员。如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获,因为捕获的对象是局部对象,在函数结束时已经消失。

建议:尽量保持lambda的变量捕获简单化

一个 lambda捕获从 lambda被创建(即,定义 lambda的代码执行时)到lambda自身执行(可能有多次执行)这段时间内保存的相关信息。确保 lambda每次执行的时候这些信息都有预期的意义,是程序员的责任。

  • 捕获一个普通变量,如int.string或其他非指针类型,通常可以采用简单的值捕获方式。在此情况下,只需关注变量在捕获时是否有我们所需的值就可以了。

  • 如果我们捕获一个指针或迭代器,或采用引用捕获方式,就必须确保在 lambda执行时,绑定到迭代器、指针或引用的对象仍然存在。而且,需要保证对象具有预期的值在 lambda从创建到它执行的这段时间内,可能有代码改变绑定的对象的值。也就是说,在指针(或引用)被捕获的时刻,绑定的对象的值是我们所期望的,但在 lambda执行时,该对象的值可能已经完全不同了。

一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且.如果可能的话,应该避免捕获指针或引用。

隐式捕获

除了显式地指定捕获的变量外,可以让编译器根据代码自动推断我们要使用哪些变量,此时应在捕获列表写一个&=

  • &:引用捕获

  • =:值捕获

我们可以重写find_if

//sz为隐式捕获,值捕获
wc = find_if(words.begin(), words.end(),
[=](const string &s)
{return s.size()>=sz;});

可以混合使用显示捕获和隐式捕获

void print(vector<string> &words, ostream &os, char c = ' '){
//os隐式捕获,引用捕获;c显示捕获,值捕获
for_each(words.begin(), words.end(),
[&, c](const string s){os<<s<<c;});
//os显示捕获,引用捕获;c隐式捕获,值捕获
for_each(words.begin(), words.end(),
[=, &os](const string s){os<<s<<c;});
}
  1. 混合使用显示捕获和隐式捕获时,第一个参数必须是&=
  2. 显示捕获和隐式捕获的变量必须采取不同的捕获方式

可变lambda

默认情况下

  • 引用捕获时,可以改变捕获变量的值

  • 值捕获时,无法改变捕获变量的值,但可以通过添加mutable关键字实现这点

void fcn3(){
int v1 = 42;
//此时参数列表不可省略
auto f = [v1] () mutable {return ++v1;};
vl = 0;
cout<<f()<<endl;
//输出为43
}

指定lambda的返回类型

  • 当lambda函数体中只包含return语句,所以lambda可以自动推断返回类型,返回类型可以省略

  • 当lambda中包含其他语句(循环,判断…),就必须指定返回类型,否则默认返回void

下面给出了一个简单的例子

我们可以使用标准库 transform算法和一个lambda来将一个序列中的每个负数替换为其绝对值:

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

函数transform接受三个迭代器一个可调用对象

前两个迭代器表示输入序列,第三个迭代器表示目的位置算法对输入序列中每个元素调用可调用对象,并将结果写到目的位置。

如本例所示,目的位置迭代器与表示输入序列开始位置的迭代器可以是相同的。当输入迭代器和目的迭代器相同时,transform将输入序列中每个元素替换为可调用对象操作该元素得到的结果。

本例子中,lambda中只有return语句,所以无需指定返回类型,如果改写为:

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

会产生编译错误,应该指定返回类型,写为:

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

ps.不知道为什么我没有指定返回类型在vscode上也通过编译了,不过vscode编译检查并不严格,为了保证程序的可移植性,最好加上返回类型

int main(){
fcn3();
vector<int> vi = {1,-2,4,-3};
transform(vi.begin(), vi.end(), vi.begin(),
[](int i)->int{if(i<0) return -i; else return i;});
for_each(vi.begin(), vi.end(), [](int i){cout<<i<<" ";});
return 0;
//输出1,2,4,3
}

算法的if版本

在10.2.2 已经讲过,算法的拷贝版本xxx_copy(指定容器的迭代器,...)可以将函数的运行结果拷贝到指定的容器中

而算法的if版本xxx_if(...,谓词)则是对迭代器范围内的元素调用可调用对象

  • find_if(iter1, iter2, 一元谓词):返回第一个使得谓词为真的元素的迭代器

  • cout_if(iter1, iter2, 谓词):返回使得谓词为真的元素的个数

    int main(){
    vector<int> vi = {1,-2,4,-3,5};
    cout<<count_if(vi.begin(), vi.end(), [](int i){return i>0;})<<endl;
    return 0;
    //输出为3
    }
  • for_each(iter1, iter2, 可调用对象):对范围内的每个元素调用可调用对象

    //遍历的新写法
    for_each(vi.begin(), vi.end(), [](int i){cout<<i<<" ";});
  • transform(iter1, iter2, iter3, 可调用对象):见上文

10-3 定制操作lambda的更多相关文章

  1. lambda 表达式定制操作

    泛型算法中的定制操作 许多算法都会比较输入序列中的元素以达到排序的效果,通过定制比较操作,可以控制算法按照编程者的意图工作. 普通排序算法: template<class RandomItera ...

  2. 【足迹C++primer】32、定制操作_2

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/cutter_point/article/details/32301839 定制操作_2 完整的big ...

  3. 【足迹C++primer】32、定制操作_1

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/cutter_point/article/details/32066151 定制操作 向算法传递函数 ...

  4. Django从Models 10分钟定制一个Admin后台

    目录 Django从Models 10分钟建立一套RestfulApi Django从Models 10分钟定制一个Admin后台 简介 Django自带一个Admin后台, 支持用户创建,权限配置和 ...

  5. 背水一战 Windows 10 (88) - 文件系统: 操作文件夹和文件

    [源码下载] 背水一战 Windows 10 (88) - 文件系统: 操作文件夹和文件 作者:webabcd 介绍背水一战 Windows 10 之 文件系统 创建文件夹,重命名文件夹,删除文件夹, ...

  6. Android:日常学习笔记(10)———使用LitePal操作数据库

    Android:日常学习笔记(10)———使用LitePal操作数据库 引入LitePal 什么是LitePal LitePal是一款开源的Android数据库框架,采用了对象关系映射(ORM)的模式 ...

  7. 定制操作(传递函数或lambda表达式)

    很多算法都会比较输入序列中的元素.默认情况下,这类算法使用元素类型的<或==运算符完成比较.标准库还为这些算法定义了额外的版本,允许我们提供自己定义的操作来代替默认运算符. 例如,sort算法默 ...

  8. Java基础学习总结(44)——10个Java 8 Lambda表达式经典示例

    Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Ja ...

  9. python基础(内置函数+文件操作+lambda)

    一.内置函数 注:查看详细猛击这里 常用内置函数代码说明: # abs绝对值 # i = abs(-123) # print(i) #返回123,绝对值 # #all,循环参数,如果每个元素为真,那么 ...

  10. IntelliJ IDEA中我最爱的10个快捷操作

    前言 欢迎关注微信公众号「JAVA旭阳」交流和学习 IntelliJ IDEA提供了一些Java的快捷键,同样也可以帮助我们提高日常的开发效率.关于这些快捷操作,你知道那几个呢? 1. psvm/ma ...

随机推荐

  1. 2. 从0开始学ARM-CPU原理,基于ARM的SOC讲解

    关于ARM的一些基本概念,大家可以参考我之前的文章: <到底什么是Cortex.ARMv8.arm架构.ARM指令集.soc?一文帮你梳理基础概念[科普]> 关于ARM指令用到的IDE开发 ...

  2. 推荐7款美观且功能强大的WPF UI库

    前言 经常看到有小伙伴在DotNetGuide技术社区交流群里提问:WPF有什么好用或者好看的UI组件库推荐的?,今天大姚给大家分享7款开源.美观.功能强大.简单易用的WPF UI组件库. WPF介绍 ...

  3. java游戏服务器2023年7月22日

    name 卡牌军团 放置卡牌游戏 开发语言: java mysql 通信http 账号服务器 提供验证等功能 中心服务器 跨服功能 排行榜 公会

  4. thymeleaf学习问题整理

    使用配置 <properties> <java.version>1.8</java.version> <thymeleaf.version>3.0.9. ...

  5. 【Mac + Appium + Java1.8(一)】之Android自动化环境安装配置以及IDEA配置(附录扩展Selenium+Java自动化)

    配置环境: MacOS:10.13.6 java:1.8 IntelliJ IDEA:2018.3 Android SDK:25 Appium:1.9.1 Appium-desktop:1.7.1 j ...

  6. Postman Code Java-Unirest 代码的依赖

    本来是Postman的Code直接使用的,结果根据这个名字 Unirest,搜出来了很多依赖,使用了排名第一的, https://search.maven.org/search?q=Unirest 结 ...

  7. 【YashanDB知识库】数据库审计shutdown immediate操作导致数据库异常退出

    [问题分类]功能使用 [关键字]数据库审计,shutdown immediate [问题描述]审计shutdown immediate 操作,数据库作主从切换时会导致数据库异常退出. [问题原因分析] ...

  8. Identity – Options

    前言 上一篇已经有写到一些配置了, 但不完整, 这里专门写一篇吧. 防暴力登入 services.Configure<IdentityOptions>(options => { // ...

  9. SpringMVC —— SpringMVC简介

    SpringMVC SpringMVC技术 与 Servlet技术功能等同,均属于web层开发技术 是一种基于java实现MVC模型的轻量级Web框架 SpringMVC 入门案例          ...

  10. redisson内存泄漏问题排查

    问题描述 最近生产有个服务突然出现频繁告警,接口P99响应时间变长,运维同学观察到相应的pod cpu飙升,内存占用很高. cpu升高问题排查是老生常谈的话题了,一般可以使用top -p pid -H ...