第10章:泛型算法

顺序容器只定义了很少的操作,在多数情况下,我们可以添加和删除元素,访问首尾元素,确定容器是否为空以及获得只想首元素或尾元素之后位置的迭代器。
如果用户还希望做其他更多有用的操作呢:比如查找特定元素,替换或删除一个特定值,重排元素顺序等。
标注好难看并未给每个容器都定义成员函数来实现这些操作,而是定义了一组泛型算法,称它们为算法是因为他们实现了一些经典算法的公共接口,如排序和搜索,称它们是泛型的,是因为它们可以用于不同类型的元素和多种容器类型,不仅包括标准库类型,如vector或list,还包括内置数组类型。

10.1 概述

大多数算法都定义在头文件algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法。
一般情况下,这些算法不直接操作容器,而是遍历由两个迭代器指定的一个元素范围来进行操作。通常情况下,算法遍历范围,对其中每个元素进行一些处理。
在这里插入图片描述
算法如何工作
在这里插入图片描述
这些步骤都不依赖于容器所保存的元素的类型,因此,只要有一个迭代器可用来访问元素,find就完全不依赖于容器类型。
迭代器令算法不依赖于容器,…
在这里插入图片描述
…,但算法依赖于元素类型的操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.2 初识泛型算法

标准库提供了超过100个算法。但与容器类似,这些算法都有一致的结构。理解算法的最基本的方法就是了解他们是否读取元素,改变元素或是重排元素顺序。

10.2.1 只读算法

如find,accumulate
算法和元素类型
accumulate要求序列与第三个参数类型匹配。
在这里插入图片描述
在这里插入图片描述
操作两个序列的算法
另一个只读算法是equal
在这里插入图片描述
在这里插入图片描述

10.2.2 写容器元素的算法

一些算法将新值赋予序列中的元素,当我们使用这类算法时,必须注意确保序列原大小至少不小于我们要求算法写入的元素数目。
如fill
在这里插入图片描述
算法不检查写操作
一些算法接受一个迭代器来指出一个单独的目的位置。这些算法将新值赋予一个序列中的元素,该序列从目的的位置迭代器指向的元素开始。
在这里插入图片描述
介绍back_inserter
一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器。插入迭代器是一种向容器中添加元素的迭代器。通常情况下,当我们通过一个迭代器向容器元素赋值时,值被赋予迭代器指向的元素,而当我们通过一个插入迭代器赋值时,一个与赋值号右侧值相等的元素被添加到容器中。
在这里插入图片描述
拷贝算法
拷贝算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。此算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。此算法将输入范围中的元素拷贝到目的序列中。传递给 copy的目的序列至少要包含与输入序列一样多的元素。
在这里插入图片描述

10.2.3 重排容器元素的算法

某些算法会重排容器中元素的顺序,一个明显的例子是sort,调用sort会重排输入序列中的元素,使之有序,它是利用元素类型的<运算符来实现排序的。
在这里插入图片描述
消除重复单词
为了消除重复单词,首先将vector排序,使得重复的单词都相邻出现,一旦vector排序完毕,我们就可以使用另一个成为unique的标准库算法来重排vector,使得不重复的元素 出现在vector的开始部分。由于算法不能执行容器的操作,我们将使用vector的erase成员来完成真正的删除操作:
在这里插入图片描述
使用unique
words排序完毕后,我们希望将每个单词都只保存一次,unique算法重排输入序列,将相邻的重复项消除,并返回一个指向不重复值末尾的迭代器。
在这里插入图片描述
在这里插入图片描述
使用容器操作删除元素
为了真正地删除无用元素,我们必须使用容器操作,如erase。

10.3 定制操作

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

10.3.1 向算法传递函数

作为一个例子,假定希望在调用elimDups后打印vector的内容,此外还假定希望单词按其长度排序,大小相同的再按字典排序。为了按长度重排vector,我们将使用sort的第二个版本,此版本是重载过的,它接受第三个参数,此参数是一个谓词。
谓词
谓词是一个可调用的表达式,其返回的结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词和二元谓词。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。
接受一个二元谓词参数的sort版本用这个谓词代替<(小于)来比较元素。
在这里插入图片描述
在这里插入图片描述
排序算法
我们将words按大小重排的同时,还希望具有相同长度的元素按字典序排列。为了保持相同长度的单词按字典序排列,可以使用stable_sort算法。这种稳定排序算法谓词相等元素的原有顺序。

10.3.2 lambda表达式

根据算法接受一元谓词还是二元谓词,我么能传递给算法的谓词必须严格接受一个或两个参数。但是有时我们希望进行的操作需要更多参数,超出了算法对谓词的限制。例如,为上一节最后一个联系所编写的程序中,就必须将大小5硬编码到划分序列的谓词中。如果再编写划分序列的谓词时,可以不必为每个可能的大小都编写一个独立的谓词,显然更有实际价值。
介绍lambda
我们可以向一个算法传递任何类别的可调用对象。对于一个对象或一个表达式,如果可以对其使用调用运算符,则称他为可调用的。即,如果e是一个可调用的表达式,则我们可以编写代码e(args),其中args是一个逗号分隔的一个或多个参数的列表。
到目前为止,我们使用过的仅有两种可调用对象是函数和函数指针。还有其它两种可调用对象:重载了函数调用运算符的类,以及lambda表达式。
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型,一个参数列表和一个函数体。但与函数不同,lambda可能定义再函数内部。一个lambda表达式具有如下形式。
在这里插入图片描述
其中capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空),return type、parameter list和function body与任何普通函数一样,分别表示返回类型,参数列表和函数体,但是与普通函数不同,lambda必须使用尾巴置返回来指定返回类型。
我们可以忽略参数列表和返回类型,但是必须永远包含捕获列表和函数体。
在这里插入图片描述
此例定义了一个可调用对象f,它不接受参数,返回42.
lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符。
在这里插入图片描述
再lambda中忽略括号和参数列表等价于指定一个空参数列表。在此例中,当调用f时,参数列表是空的。如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来,否则返回类型为void。
向lambda传递参数
与普通函数不同,lambda不能有默认参数。因此,一个lambda调用的实参数目永远与形参数目相等。
在这里插入图片描述
在这里插入图片描述
使用捕获列表
一个lambda表达式可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。一个lambda通过将局部变量包含在其捕获列表中来之处将会是哦那个这些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
调用find_if
使用此lambda,我们就可以查找第一个长度大于等于sz的元素。
在这里插入图片描述
在这里插入图片描述

通过对find_if的调用返回一个迭代器,指向第一个长度不小于给定参数sz的元素。

for_each算法
问题的最后一部分是打印words中长度大于等于sz的元素。为了达到这一目的,我们可以使用for_each算法。此算法接受一个可调用对象,并对输入序列中每个元素调用此对象。
在这里插入图片描述
*完整的biggies
在这里插入图片描述

10.3.3 lambda捕获和返回

当定义一个lambda时,编译器生成一个与lambda对应的新的类类型。当一个函数传递一个lambda时,同时定义了一个新的类型和该类型的一个对象;传递的参数就是此编译器生成的类类型的未命名对象。类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lamba生成的类型的对象。
值捕获
类似参数传递,变量的捕获方式也可以是值或引用。被捕获的变量的值是在lambda创建时拷贝的,不是调用时拷贝的。
在这里插入图片描述
引用捕获
在这里插入图片描述
我们也可以从函数返回lambda,函数可以直接返回一个可调用对象,或者返回一个类对象,该类含有可调用对象的数据成员。
在这里插入图片描述
隐式捕获
在这里插入图片描述
=表示值捕获,&表示引用捕获,混合捕获
在这里插入图片描述
可变lambda
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们虚妄能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表。
在这里插入图片描述
一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型。
在这里插入图片描述
指定labmda返回类型
到目前为止我们所编写的lambda都只包含一个单一的return语句,因此,我们还未遇到必须指定返回类型的情况。默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。与其它返回void的函数类似,被推断返回void的lambda不能返回值。
在这里插入图片描述
在这里插入图片描述

当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型。
在这里插入图片描述

10.3.4 参数绑定

对于只在一两个地方使用的简单操作,lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数,而不是多次编写相同的lambda表达式,类似的,如果一个操作需要很多语句才能完成,通常使用函数更好。
在这里插入图片描述
标准库bind函数
我们解决向check_size传递一个长度参数的问题,方法是使用一个新的名为bind的标准库函数,它定义在头文件functional中。可以将bingd函数看做一个通用的函数适配器,它接受一个可调用的对象,生成一个新的可调用对象来适应原对象的参数列表。
在这里插入图片描述
绑定check_size的sz参数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用placeholder名字
名字_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。为了使用这些名字,两个命名空间都要写上。
在这里插入图片描述
为了简单:
在这里插入图片描述
bind的参数
我们可以用bind修正参数的值。更一般的,可以用bind绑定给定可调用对象中的参数或重新安排其顺序。
在这里插入图片描述
生成一个新的可调用对象,它有两个参数,分别是占位符_2和_1表示。这个新的可调用对象会将自己的参数作为第三个和第五个参数传递给f。
在这里插入图片描述
用bind重排参数顺序
在这里插入图片描述
绑定引用参数
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数我们希望以引用方式传递,或是要绑定参数的类型无法拷贝。
在这里插入图片描述
在这里插入图片描述

10.4 再探迭代器

除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器包括:
插入迭代器这些迭代器被绑定到一个容器上,可以用来向容器插入元素
流迭代器这些迭代器被绑定在输入或输出流上,可用来遍历所关联的IO流
反向迭代器这些迭代器向后而不是向前移动,除了forward_list之外的标准库容器都有反向迭代器。
移动迭代器这些专用的迭代器不是拷贝其中的元素,而是移动它们。

10.4.1 插入迭代器

插入器是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。当我们通过一个插入迭代器进行赋值时,该迭代器调用容器操作来向给定容器的指定为止插入一个元素。
在这里插入图片描述
插入器有三种类型,差异在于元素插入的位置。

  • back_inserter创建一个使用push_back的迭代器。
  • front_inserter创建一个使用push_front的迭代器。
  • inserter创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

10.4.2 iostream迭代器

虽然iostream类型不是容器,但标准库定义了可以用于这些IO类型对象的迭代器。
istream_iterator读取输入流,ostream_iterator向一个输出流写数据。这些迭代器将它们对应的流当做一个特定类型的元素序列来处理。通过使用流迭代器,我们可以用泛型算法从流对象读取数据以及向其写入数据。

istream_iterator操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用算法操作流迭代器
由于算法使用迭代器操作来处理数据,而流迭代器又至少支持某些迭代器操作,因此,我们至少可以用某些算法来操作流迭代器。
在这里插入图片描述
istream_iterator允许使用懒惰求值
当我们将一个istream_iterator绑定到一个流时,标准库并不保证迭代器立即从流读取数据。具体实现可以推迟从流中读取数据,制导我们使用迭代器时才真正读取。标准库中的实现所保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成了。
ostream_iterator操作
我们可以对任何具有输出运算符(<<运算符)的类型定义ostream_iterator。当创建一个ostream_iterator时,我们可以提供第二个参数,它是一个字符串,在输出每个元素后都会打印此字符串。此字符串必须是一个c风格字符串。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
使用流迭代器处理类类型
我们可以为任何定义了输入运算符的类型创建lstream_iterator对象。类似的,只要类型又输出运算符(<<),我们就可以为其定义ostream_iterator。
在这里插入图片描述

10.4.3 反向迭代器

反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。对于反向迭代器,递增操作的含义会颠倒过来。递增一个反向迭代器会移动到前一个元素;递减一个迭代器会移动到下一个元素。
除了forward_list之外,其它容器都支持反向迭代器。我们可以通过调用rbegin,rend,crbegin和crend成员函数来获得反向迭代器。
在这里插入图片描述
反向迭代器需要递减运算符
在这里插入图片描述
反向迭代器和其它迭代器之间的关系
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.5 泛型算法结构

在任何算法的最基本的特性是它要求其迭代器提供哪些操作。某些算法,如find,只要求通过迭代器访问元素,递增迭代器以及比较两个迭代器是否相等这些能力。其它一些算法,如sort,还要求读,写和随机访问元素的能力。算法所要求的迭代器操作可以分为5个迭代器类别,如表10.5所示。每个算法都会对他的每个迭代器参数指明需要提供哪类迭代器。

在这里插入图片描述
第二种算法分类的方式是按照是否读,写,或是重新排列序列中的元素来分类。

10.5.1 5类迭代器

类似容器,迭代器也定义了一组公共操作。一些操作所有迭代器都支持,另外一些只有特定类别的迭代器才支持。例如,ostream_iteratro只支持递增,解引用和赋值。vector,string和deque的迭代器除了这些操作外,还支持递减,关系运算符和算术 操作。
在这里插入图片描述
迭代器类别

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.5.0 算法形参模式

在这里插入图片描述

10.5.2 算法命名规范

一些算法使用重载形式传递一个谓词
在这里插入图片描述
** _if版本的算法**
在这里插入图片描述
区分拷贝元素的版本和不拷贝元素的版本
在这里插入图片描述
在这里插入图片描述

10.6 特定容器算法

与其它容器不同,链表类型list和forward_list定义了几个 成员函数形式的算法。特别是,它们定义了独有的sort,merge,remove,reverse和unique。
链表类型定义的其它算法的通用版本可以用于链表,但代价太高,这些算法需要交换输入序列中的元素。一个链表可以通过改变元素间的链接而不是真的交换它们的值来快速交换元素。因此,这些链表版本的算法性能比对应的通用版本好得多。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
splice成员
链表还定义了splice算法,此算法是链表数据结构所特有的,因此不需要通用版本。
在这里插入图片描述
链表特有的操作会改变容器
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值