《C++primer》 第十章_泛型算法 读书笔记
《C++Primer 第五版》
——读书随笔集
第十章
顺序容器只定义了很少的操作,在多数情况下,我们可以添加和删除元素,访问首尾元素,确定容器是否为空以及获得只想首元素或尾元素之后位置的迭代器。
如果用户还希望做其他更多有用的操作呢:比如查找特定元素,替换或删除一个特定值,重排元素顺序等。
标注好难看并未给每个容器都定义成员函数来实现这些操作,而是定义了一组泛型算法,称它们为算法是因为他们实现了一些经典算法的公共接口,如排序和搜索,称它们是泛型的,是因为它们可以用于不同类型的元素和多种容器类型,不仅包括标准库类型,如vector或list,还包括内置数组类型。
10.1 概述
大多数算法都定义在头文件algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法。一般情况下,这些算法不直接操作容器,而是遍历由两个迭代器指定的一个元素范围来进行操作。通常情况下,算法遍历范围,对其中每个元素进行一些处理。
- 迭代器算法不依赖于容器,通过迭代器操作来实现。
- 但算法依赖于元素类型的操作,大多数算法提供了一种方法,允许我们使用自定义操作来代替默认的运算符。
算法永远不会执行容器的操作。泛型算法本身永远不会执行容器的操作,他们只会运行在迭代器之上,执行迭代器的操作。泛型算法运行在迭代器之上而不会执行容器操作的特性带来了一个令人惊讶但非常必要的编程假定:算法永远不会改变底层容器的大小。算法可能改变容器中保存的值,也可能在容器中移动元素,但永远不会直接添加或者删除元素。
后面我们将见识到一种特殊的迭代器,叫做插入器。与普通迭代器只能遍历所绑定的容器相比,插入器能做更多的事。当给这类迭代器赋值时,他们会在底层容器上执行插入操作。因此算法可以操控这类迭代器,从而完成向容器中添加元素的效果,但算法自身不会执行这样的操纵。
10.2 初识泛型算法
除少数例外,标准库算法都对一个范围内的元素进行操作。我们将此类元素范围称为“输入范围”。接受输入范围的算法总是使用前两个参数来表示此范围,两个参数分别是指向要处理的第一个元素和尾元素之后位置的迭代器。
虽然大多数算法遍历输入范围的方式相似,但它们使用范围中元素的方式不同。理解算法的最基本的方法就是了解它们是否读取元素,改变元素或是重排元素。
10.2.1 只读算法
一些算法只会读取其输入范围的元素,而从不改变元素。find就是这样一种算法,我们之前使用的count函数也是如此。另一个只读算法是accumulate,它定义在头文件numeric中。accumulate函数接受三个参数,前两个指出需要求和的范围,第三个参数就是和的初值。假定vec是一个整数序列,则:
int sum = accumulate(vec.cbegin(), vec.cend(), 0);
accumulate
的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型。
-
算法和元素类型
accumulate作为第三个参数作为求和起点,这里蕴含一个假定:将元素类型加到和的类型上的操作必须是可行的。即序列中的元素类型必须与第三个参数匹配,或者能够转化为第三个参数的类型。
//将v中的每个元素连接到一起 string num = accumulate(v.cbegin(), v.cend(), string("")); //错误,const char* 上没有定义+运算符 string num = accumulate(v.cbegin(), v.cend(), "");
对于只读算法,最好只使用cbegin()和cend()。但是,如果你要通过使用算法返回的迭代器来修改元素的值,则就需要使用begin()和end()的结果作为参数。
-
操作两个序列的算法
另外一个只读算法是equal(),用于确定两个序列是否保存相同的值。将第一个序列中的每个元素与第二个序列中的每个元素进行比较。如果每个对应元素都相等,则返回true,否则返回false。此算法接受三个迭代器:前两个为第一个序列的元素范围,第三个是第二个序列的首元素。
//roster2中的元素数目应该至少与roster1一样多 equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());
由于equal利用迭代器完成操作,所以我们可以调用equal来比较两个不同类型的容器中的元素。而且,元素类型也不必一样,只要我们能用==来比较两个元素类型即可。
但是,equal有一个非常重要的假设:它假定第二序列至少与第一个序列一样长。
那些只接受单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。