一、概述
除了少数例外。标准库算法都对一个范围内的元素进行操作。我们将此元素范围称为“输入范围”。接受输入范围的算法总是使用前两个参数来表示此范围,两个参数分别是指向要处理的第一个元素和尾元素之后位置的迭代器。
二、初识泛型算法
1. 只读算法
只读算法只会读取其输入范围内的蒜素,而不改变元素。例如:find算法、count算法、accumulate算法。
序列中元素的类型必须与第三个参数匹配,或者能够转换为第三个参数的类型。
由于string定义了+运算符,所以我们可以通过调用accumulate来将vector中所有string元素连接起来。
2. 写容器元素的算法
算法不会执行容器操作。所以此类算法要传递有效的输入范围。
算法不检查写操作
介绍back_inserter
一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器。插入迭代器是一种向容器中添加元素的迭代器。通常情况下,当我们通过一个迭代器向容器元素赋值时,值被赋予迭代器指向的元素。而当我们通过一个插入迭代器赋值时,一个与赋值号右侧相等的元素被添加到容器中。
back_insert是定义在头文件iterator中的一个函数。
拷贝算法
拷贝(copy)算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。此算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。此算法将输入范围中的元素拷贝到目的序列中。传递给copy的目的序列至少要包含与输入序列一样多的元素。
copy返回的是其目的位置迭代器(递增后)的值。
3. 重排容器元素的算法
举例:sort算法。调用sort会重排输入序列中的元素,使之有序,它是利用元素类型的<运算符来实现排序的。
其中words的大小并未改变。
标准库算法对迭代器而不是容器进行操作。因此,算法不能(直接)添加或删除元素。
三、 定制操作
谓词
谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词(意味着它们只接受单一参数)和二元谓词(意味着它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。
isShorter是提供给sort的谓词。
stable_sort算法维持相等元素的原有顺序。
2. lambda表达式
一个lambda表达式表示一个可调用的代码单元,我们可将其理解为一个未命名的内联函数。与函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type、parameter list和function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。lambda使用尾置返回来指定返回类型。
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
在此例中,我们定义了一个可调用对象f,它不接受参数,返回42;
lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符:
如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来。否则,返回类型为void。
向lambda传递参数
与普通函数不同,lambda不能有默认参数。
空捕获列表表明此lambda不使用它所在函数中的任何局部变量。
使用捕获列表
一个lambda通过将局部变量包含在其捕获列表中来指出将会使用这些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。
举例:
for_each算法
3. lambda捕获和返回
变量的捕获方式可以是引用或值。
值捕获
与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝:
引用捕获
隐式捕获
在捕获列表中写一个&或=,指示编译器推断捕获列表。&告诉编译器采用引用捕获方式,=则表示采用值捕获方式。
可变lambda
4. 参数绑定
标准库bind函数
bind函数定义在头文件functional中。可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
使用placeholders名字
名字_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在std命名空间。
bind的参数
可以用bind绑定给定可调用对象中的参数或重新安排其顺序。
绑定引用参数
如果我们希望传递给bind一个对象而又不拷贝它,就必须使用标准库ref函数:
推荐使用lambda。
四、再探迭代器
除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器。这些迭代器包括以下几种。
插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素。
流迭代器:这些迭代器被绑定到输入或输出流上,可用来遍历所关联的IO流。
反向迭代器:这些迭代器向后而不是向前移动。除了forward_list之外的标准库容器都有反向迭代器。
移动迭代器:这些专用的迭代器移动其中的元素。
1. 插入迭代器
插入器是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。当我们通过一个插入迭代器进行赋值时,该迭代器调用容器操作来向给定容器的指定位置插入一个元素。
插入器有三种类型,差异在于元素插入的位置:
back_inserter创建一个使用push_back的迭代器。
front_inserter创建一个使用push_front的迭代器。
inserter创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。
当调用inserter(c, iter)时,我们得到一个迭代器,接下来使用它时,会将元素插入到iter原来所指向的元素之前的位置(iter仍指向原来所指的元素)。当我们调用front_inserter时,元素总是插入到容器第一个元素之前。
2. iostream迭代器
istream_iterator读取输入流,ostream_iterator向一个输出流写数据。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。通过使用流迭代器,我们可以用泛型算法从流对象读取数据以及向其写入数据。
istream_iterator操作
当创建一个流迭代器时,必须指定迭代器将要读写的对象类型。一个istream_iterator使用>>来读取流。
对于一个绑定到流的迭代器,一旦其关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相等。
使用算法操作流迭代器
istream_iterator允许使用懒惰求值
当我们将一个istream_iterator绑定到一个流时,标准库并不保证迭代器立即从流读取数据。具体实现可以推迟从流中读取数据,直到我们使用迭代器时才真正读取。标准库中的实现所保证的时,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成了。
ostream_iterator操作
3. 反向迭代器
反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。递增一个反向迭代器(++it)会移动到前一个元素;递减一个迭代器(--it)会移动到下一个元素。
我们可以通过调用rbegin、rend、crbegin和crend成员函数来获得反向迭代器。
不能从一个forward_list或一个流迭代器创建反向迭代器。
反向迭代器和其他迭代器之间的关系
假定有一个名为line的string,保存着一个逗号分隔的单词列表,我们希望打印line中的第一个单词。
如果希望打印最后一个单词,可以改用反向迭代器:
我们希望按正常顺序打印从rcomma开始到line末尾间的字符,需要将rcomma转换回一个普通迭代器,能在line中正向移动。我们通过调用reverse_iterator的base成员函数来完成这一转换,此成员函数会返回其对应的普通迭代器:
普通迭代器与反向迭代器的关系反映了左闭合区间的特性。
五、 泛型算法结构
1. 5类迭代器
算法所要求的迭代器操作可以分为5个迭代器类别。
2. 算法形参模式
大多数算法具有以下4种形式之一: