标准库并未给每个容器都定义成员函数来实现这些操作,而是定义了一组泛型算法,是因为他们实现了一些经典算法的公共接口,如排序和搜素:称它们是“泛型的”,是因为它们可以用于不同类型的元素和多种容器类型。
大多数的算法都定义在头文件algorithm中。标准库还在头文件numeric中定义了一组数值泛型算法。
算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。
除了少数例外,标准库算法都对一个范围内的元素进行操作。我们将此元素范围称为“输入范围”。接受输入范围的算法总是使前两个参数来表示此范围,两参数分别是指想要处理的第一个元素和尾元素之后的迭代器。
一些算法只会读取其输入范围内的元素,而从不改变元素。
1.对于只读取而不改变元素的算法,通常最好使用cbegin()和cend()。
2.那些只接受一个单一迭代器来表示第二个序列的算法,都假第二个序列至少与第一个序列一样长。
一些算法将新值赋予序列中的元素。当我们使用这类算法时,必须注意确保序列原大小至少不小于我们要求算法写入的元素数目。
记住,算法不会执行容器操作,因此它们自身不可能改变容器的大小。
向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。
例子:消除重复单词
void elimDups(vector<string> &worda)
{
// 按字典序排序words,以便查找重复单词
sort(words.begin(),words.end());
// unique 重排输入范围,使得每个单词只出现一次
// 排列在范围的前部,返回指向不重复区域之后一个位置的迭代器
auto end_unque = unique(words.begin(),words.end());
//使用向量操作erase删除重复单词
words.erase(end_unique,words.end());
}
例如,sort算法默认使用元素类型的<运算符。但可能我们希望的排列顺序与<所定义的顺序不同,这就需要重载sort的默认行为。
谓词:一个可调用的表达式,其返回结果是一个能用作条件的值。
标准库算法所使用的谓词分为两类:一元谓词(只接受单一参数)和二元谓词(只接受两个参数)
谓词只能接受一个或两个参数,受到参数的限制。
我们可以向一个算法传递任何类别的可调用对象。对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。
可调用对象有:函数和函数指针,重载了函数调用运算符的类,以及lambda表达式。
一个lambda表达式表示一个可调用的代码单元。一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。
一个lambda表达式具有如下形式:
如果lambda函数体包含任何单一 return 语句之外的内容,且未指定返回类型,则返回 void。
一个 lambda 通过将局部变量包含在其捕获列表中来将会使用这些变量。捕获列表指引 lambda 在其内部包含访问局部变量所需的信息。
捕获列表只用于局部非 static 变量,lambda 可以直接使用局部 static 变量和在它所在函数之外声明的名字。
类似参数传递,变量的捕获方式也可以是值或引用。
1.值捕获
采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda 创建时拷贝,而不是调用时拷贝。
由于被捕获变量的值是在lambda 创建时拷贝,因此随后对其修改不会影响到lambda内对应的值。
2.引用捕获
采用引用方式捕获一个变量,就必须确保被引用的对象在lambda 执行的时候是存在的。
一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。也应该避免捕获指针或引用。
3.隐式捕获
除了显示捕获,,还可以让编译器根据lambda 体中的代码来推断我们要使用哪些变量。在捕获列表中写一个 & 或 = 。& 告诉编译器采用捕获引用方式,=表示值捕获方式。
混合使用隐式捕获和显示捕获时:
如果lambda函数体包含任何单一 return 语句之外的内容,且未指定返回类型,则返回 void。
当我们需要为一个lambda 定义返回类型时,必须使用返回类型。
transform(vi.begin(),vi.end(),vi.begin(),
[](int i) -> int
{if (i < 0) return -i; else return i; });
对于那种只在一两个地方使用的简单操作,lambda 表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数。
如果 lambda 的捕获列表为空,通常可以用函数来代替它。
bind的标准库函数,它定义在**头文件 functional **中。可以将 bind 函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
名字_n 都定义在一个名为 placeholders 的命名空间中,而这个命名空间本身定义在 std 命名空间中。为了 使用这些名字,两个命名空间都要写上。
用 bind 重排参数顺序
一个例子:
//按单词长度由短至长排序
sort(woeds.begin(),words.end(),isShorter);
//按单词长度由长至短排序
sort(woeds.begin(),words.end(),bind(isShorter,_2,_1));
头文件 iterator中定义了几种迭代器:
插入器是一种迭代适配器,它接受一个容器,生成一个迭代器,能实现给定容器添加元素。
一个例子:
list<int> lst = {1,2,3,4};
list<int> lst2,lst3;
//拷贝完成之后,lst2包含4 3 2 1
copy(lst.cbegin(),lst.cend(),front_inserter(lst2));
//拷贝完成之后,lst3包含1 2 3 4
copy(lst.cbegin(),lst.cend(),inserter(lst3,lst3.beginn()));
istream_iterator 读取输入流,ostream_iterator 向一个输出流写数据。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。
一个例子:
istream_iterator<int> in_iter(cin); //从cin读取int
istream_iterator<int> eof; //istream尾后迭代器
while (in_iter != eof) //当有数据可供读取时
//后置递增运算读取流,返回迭代器的旧值
//解引用迭代器,获得从流读取的前一个值
v.push_back(*in_iter++);
可以从重写为如下形式,这体现了 istream_iterator 更有用的地方:
istream_iterator<int> in_iter(cin),eof;
vector<int> vec(int_iter,eof);
istream_iterator<int> in(cin),eof;
cout << accumlate(in, eof, 0) <<endl;
此调用会计算出标准输入读取的和。如果输入为:
23 109 45 89 6 34 12 90 34 23 56 23 8 89 23
则输出为664。
反向迭代器就是 在容器中从尾元素向首元素反向移动的迭代器。对于反向跌倒器,递增(以及递减)操作的含义会颠倒过来。递增一个反向迭代器(++it)会移动到前一个元素。
除了forward_list之外,其他容器都支持反向迭代器。可以通过调用 rbegin、rend、crbegin 和 crend成员函数来获得反向迭代器。
任何算法的最基本的特性是他要求其迭代器提供哪些操作。算法所要求的迭代器操作可以分为5个迭代器类别。
1.输入迭代器
输入迭代器只用于顺序访问。
2.输出迭代器
我们只能向一个输出迭代器赋值一次。输出迭代器只能用于单遍扫描算法。
3.前向迭代器
可以读写元素。forward_list 上的迭代器是前向迭代器。
4.双向迭代器
可以正向/反向读写序列中的元素。除了forward_list外,其他标准库都提供符合双向迭代器要求的迭代器。
5.随机迭代器
提供在常量时间内访问序列中任意元素的能力。array、deque、string和vector 的迭代器都是随机访问迭代器,用于访问内置数组元素的指针也是。
大多数算法具有如下4种形式之一:
1.接受单个目标迭代器的算法
dest 参数是一个表示算法可以写入的目的位置的迭代器。算法假定:按其需要写入数据,不管写入多少个元素都是安全的。
2.接受第二个输入序列的算法
接受单独 beg2 的算法假定从 beg2 开始的序列与 beg 和 end 所表示的范围至少一样大。
除了参数规范,算法还遵循一套命名和重载 规范。
1.一些算法使用重载形式传递一个谓词
接受谓词参数来代替< 或 == 运算符的算法,以及那些不接受额外参数的算法,通常都是重载函数。
2._if 版本的算法
接受一个元素值的算法通常有另一个不同名的(不是重载的)版本,该版本接受一个谓词代替元素值。该算法都有附加的 _if 前缀,如 find_if 函数。
3.区分拷贝元素的版本和不拷贝的版本
默认情况下,重排元素的算法将重排后的元素写回给定的输入序列中。这些算法还提供另一个版本,将元素写到一个指定的输出目的位置。。写到额外目的空间的算法都在名字后面附件一个_copy。
list 和 forward_list 类分别提供双向迭代器和前向迭代器。
对与list 和 forward_list,应优先使用成员函数版本的算法而不是通用算法。
链表类型定义了splice 算法。此算法是链表数据结构所特有的。
链表特有版本算法与通用版本间的一个至关重要的区别是链表版本会改变底层的容器。