10.1概述
大多数算法定义在头文件algorithm中.另外头文件numeric中定义了一组数值泛型算法.
一般情况下算法不直接操作容器,而是通过迭代器来对元素进行处理,因此迭代器令算法不依赖容器,但算法依赖于元素类型的操作.
泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上.算法永远不会改变底层容器的大小,算法可能改变容器中保存的元素的值,但是不会直接添加或删除元素.
10.2初识泛型算法
标准库提供超过100个算法.要全部记住不太现实,本章会通过介绍一些算法来大体上介绍算法的分类.在需要用到算法时可以直接搜索,不用记住这一百多个算法.
10.2.1只读算法
只读算法只会读取范围内的元素,从不改变元素.
#include <numeric>
accumulate(范围(迭代器),范围(迭代器),初始值)
accumulate返回给出的迭代器范围内的元素总和加上初始值,返回的类型和给定的初始值类型有关.
然而对于只读不改变元素的算法,最好使用cbegin和cend(我上面的写法没啥大问题,但是不严谨,尽管底层算法基本不会出错).但是如果算法返回的是迭代器,并且要通过返回的迭代器修改元素,那么还是使用begin和end.
#include <numeric>
equal(序列1迭代器,序列1迭代器,序列2迭代器)
equal返回bool值(打印出来是0(false)/1(true)),用于判断两个序列是否一致.我们可以看到序列1要给出两个迭代器来表示范围,而序列2只给出一个迭代器,这是因为算法假定第二个序列至少和第一个序列一样长,只比较序列1的范围的长度,因此使用这类算法时应当注意要确保序列2的长度要大于等于序列1的范围,编译器不会做检测,如果序列2的长度比序列1小,那么会有可能导致严重错误.
用一个单一迭代器表示第二个序列的算法都假定第二个序列至少和第一个序列一样长.
10.2.2写容器元素的算法
#include<algorithm>
fill_n(迭代器,n,val)
fill_n从迭代器开始,将n个元素的值变成val.
算法不检查写操作,因此要确保迭代器(包括)之后至少有n个位置来写入val.
back_inserter(容器)
back_inserter返回一个插入迭代器,通过插入迭代器赋值时,相当于对绑定的容器进行push_back操作.
copy(序列1迭代器,序列1迭代器,序列2迭代器)
copy将序列1 的元素值拷贝给序列2,序列2至少要包含和序列1一样多的元素.然后copy返回其目的位置迭代器的值,即指向序列2的尾元素之后的位置.
10.2.3重排容器元素的算法
sort(范围(迭代器),范围(迭代器),谓词(可以暂时忽略))
sort会默认将范围内的元素从小到大(升序)进行排序,可以通过谓词来修改排序规则(后面说).
unique(范围(迭代器),范围(迭代器))
unique将范围内相邻的重复元素消除,仅留下一样,返回一个迭代器指向最后一个不重复元素之后的位置.由于算法不对容器进行操作,因为unique实际上并没有消除元素,而是将重复的元素挪到范围的最后面,因此要彻底去重则需要手动删除.
10.3定制操作
10.3.1向算法传递函数
前面提过的谓词包括一元谓词和二元谓词,它们的区别在于一个接收一个参数,一个接收两个参数.谓词可以是函数,可以是仿函数,可以是lambda表达式.
10.3.2 lambda表达式
[捕获列表] (参数列表)->返回值类型{ 函数体}
其中参数列表(如果没有参数的话)和返回值类型可以忽略,但必须包含捕获列表和函数体,以下是最简单的lambda表达式,不需要参数,直接返回一个字符串,甚至返回值类型都不用写:
[]{
return "hello world";
}
和普通函数不同的是,lambda不能拥有默认参数.
find_if(范围(迭代器),范围(迭代器),谓词(可以为lambda))
返回迭代器,或满足谓词的条件则指向第一个满足条件的元素,否则返回容器的end的拷贝.
for_each(范围(迭代器),范围(迭代器),谓词(可以为lambda))
将范围内的元素送入谓词中.这个很实用.
10.3.3 lambda捕获和返回
当以引用方式(&)捕获一个变量时,必须保证在lambda执行时变量是存在的.
尽可能保持lambda的变量捕获简单化,并且应当尽量避免捕获指针或引用.
如果lambda非常复杂的话,不如直接写一个函数.
10.3.4参数绑定
bind函数可以看作是一个通用的函数适配器.
auto newCallable = bind(callable,arg_list)
bind后,调用newCallable时,会同时调用callable,并传递给它arg_list中的参数.
arg_list(逗号分隔的参数列表)可能包含形如_n的名字,其中n是一个整数,这类参数是占位符,占据着传递给newCallable的位置,例如_1为第一个参数,_2为第二个参数……
对于_n名字,需要对每个这种名字都单独进行using声明:
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
或者直接:
using namespace std::placeholder;
10.4再探迭代器
除了iterator之外,标准库还定义了额外的几种迭代器:
插入迭代器: insert iterator
流迭代器: streamiterator
反向迭代器: reserve iterator
移动迭代器: move iterator
10.4.1插入迭代器
当我们通过插入迭代器进行赋值时,迭代器会调用容器的操作来向给定的容器进行插入元素.
10.4.2 iostream迭代器
iostream类型不是容器,但是标准库定义了可以用于这些IO类型对象的迭代器.
10.5泛型算法结构
10.5.1 五类迭代器
对于向一个算法传递错误类别的迭代器的问题,很多编译器不会给出任何警告或提示.
10.5.2算法形参模式
向输出迭代器写入数据的算法都假定目标空间足够容纳写入的数据.
10.5.3算法命名规范
接受谓词参数的算法基本上都有附加的_if的后缀.
拷贝版本的算法在名字后面都有附加一个_copy.