泛型算法结构

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

迭代器类别

输入迭代器        只读,不写;单遍扫描,只能递增

输出迭代器        只写,不读;单遍扫描,只能递增

前向迭代器        可读写;多遍扫描,只能递增

双向迭代器        可读写;多遍扫描,可递增递减

随机访问迭代器      可读写;多遍扫描,支持全部迭代器运算

1 5类迭代器

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

迭代器是按它们所提供的操作来分类的,而这种分类形成了一种层次。除了输出迭代器之外,一个高层类别的迭代器支持底层类别迭代器的所有操作。

迭代器类别

输入迭代器

输入迭代器:可以读取序列中的元素。一个输入迭代器必须支持

  • 用于比较两个迭代器的相等和不相等运算符(==、!=)
  • 用于推进迭代器的前置和后置递增运算(++)
  • 用于读取元素的解引用运算符(*);解引用只会出现在赋值运算符的右侧
  • 箭头运算符(->),等价于(*it).member,即,解引用迭代器,并提取对象的成员

输入迭代器只用于顺序访问。对于一个输入迭代器,*it++保证是有效的,但递增它可能导致所有其他指向流的迭代器失效。其结果就是,不能保证输入迭代器的状态可以保存下来并用来访问元素。因此,输入迭代器只能用于单遍扫描算法。算法find和accumulate要求输入迭代器;而istream_iterator是一种输入迭代器。

输出迭代器

输出迭代器:可以看做输入迭代器功能上的补集——只写而不读元素。输出迭代器必须支持

  • 用于推进迭代器的前置和后置递增运算(++)
  • 解引用运算符(*),只能出现在赋值运算符的左侧(向一个已经解引用的输出迭代器赋值,就是将值写入它所指向的元素)

我们只能向一个输出迭代器赋值一次。类似输入迭代器,输出迭代器只能用于单遍扫描算法。用作目的位置的迭代器通常都是输出迭代器。例如,copy函数的第三个参数就是输出迭代器。ostream_iterator类型也是输出迭代器。

前向迭代器

前向迭代器:可以读元素。这类迭代器只能在序列中沿一个方向移动。前向迭代器支持所有输入和输出迭代器的操作,而且可以多次读写同一个元素。因此,我们可以保存前向迭代器的状态,使用前向迭代器的算法可以对序列进行多遍扫描。算法replace要求前向迭代器,forward_list上的迭代器就是前向迭代器。

双向迭代器

双向迭代器:可以正向/反向读写序列中的元素。除了支持所有前向迭代器的操作之外,双向迭代器还支持前置和后置递减运算符(--)。算法reverse要求双向迭代器,除了forward_list之外,其他标准库都提供符合双向迭代器要求的迭代器。

随机迭代器

随机访问迭代器:提供在常量时间内访问序列中的任意元素的能力。此类迭代器支持双向迭代器的所有功能,此外还支持如下的操作:

  • 用于比较两个迭代器相对位置的关系运算符(<、<=、>和>=)
  • 迭代器和一个整数值的加减运算(+、+=、-和-=),计算结果是迭代器在序列中前进(或后退)给定整数个元素后的位置
  • 用于两个迭代器上的减法运算符(-)得到两个迭代器的距离
  • 下标运算符(iter[n],与*(iter[n])等价

算法sort要求随机访问迭代器,array、deque、string和vector的迭代器都是随机访问迭代器,用于访问内置数组元素的指针也是。

 

2 算法形参模式

在任何其他算法分类之上,还有一组参数规范。大多数算法具有如下4种形式之一:

alg(beg,end,other args);

alg(beg,end,dest,other args);

alg(beg,end,beg2,other args);

alg(beg,end,beg2,end2,other args);

其中alg是算法的名字,beg和end表示算法所操作的输入范围。几乎所有算法都接受一个输入范围,是否有其他参数依赖于要执行的操作。这里列出了常见的一种——dest、beg2和end2,都是迭代器参数。顾名思义,如果用到了这些迭代器参数,它们分别承担指定目的位置和第二个范围的角色。除了这些迭代器参数,一些算法还接受额外的、非迭代器的特定参数。

 

接受单个目标迭代器的算法

dest参数是一个表示算法可以写入的目的位置的迭代器。算法假定:按其需要写入数据,不管写入多少个元素都是安全的。

如果dest是一个直接指向容器的迭代器,那么算法将输出数据写到容器中已存在的元素内。更常见的情况是,dest被绑定到一个插入迭代器或是一个ostream_iterator。插入迭代器会将新元素添加到容器中,因而保证空间是足够的。ostream_iterator会将数据写入一个输出流,同样不管要写多少个元素都没有问题。

 

接受第二个输入序列的算法

接受单独的beg2或是接受beg2和end2的算法用这些迭代器表示第二个输入范围。这些算法通常使用第二个范围中的元素与第一个输入范围结合来进行一些运算。

 

3 算法命名规范

除了参数规范,算法还遵循一套命名和重载规范。这些规范处理诸如:如何提供一个操作代替默认的<或=运算符以及算法是将输出数据写入输入序列还是一个分离的目的位置等为题。

 

一些算法使用重载形式传递一个谓词

接受谓词参数来代替<或==运算符的算法,以及哪些不接受额外参数的算法,通常都是重载的函数。函数的一个版本用元素类型的运算符来比较元素;另一个版本接受一个额外谓词参数,来代替<或==:

unique(beg,end);  //使用==运算符比较元素

unique(beg,end,comp);   //使用comp比较运元素

两个调用都重新整理给定序列,将相邻的重复元素删除。第一个调用使用元素类型的==运算符来检查重复元素;第二个则调用comp来确定两个元素是否相等。由于两个版本的函数在参数个数上不相等,因此具体应该调用那个不会产生歧义。

 

_if版本的算法

接受一个元素值的算法通常有另一个不同名的版本,该版本接受一个谓词代替元素值。接受谓词参数的算法都有附加_if前缀:

find(beg,end,val); //查找输入范围中val第一次出现的位置

find_if(beg,end,pred);  //查找第一个令pred为真的元素

这两个算法都在输入范围中查找特定的元素第一次出现的位置。算法find查找一个指定值;算法find_if查找使得pred返回非零值的元素。

这两个算法提供了命名上的差异的版本,而非重载版本,因为两个版本的算法都接受相同数目的参数。因此可能产生重载歧义,虽然很罕见,但为了避免任何可能的歧义,标准库提供不同名字的版本而不是重载。

 

区分拷贝元素的版本和不拷贝的版本

默认情况下,重排元素的算法将重排后的元素写回固定的输入序列中。这些算法还提供另一个版本,将元素写到一个指定的输出目的位置。如我们所见,写到额外目的空间的算法都在名字后面附加一个_copy:

reverse(beg,end); //反转输入范围中元素的顺序

reverse_copy(beg,end,dest); //将元素按逆序拷贝到dest

一些算法同时提供_copy和_if版本。这些版本接受一个目的位置迭代器和一个谓词:

//从v1中删除奇数元素

remove_if(v1.begin(),v1.end(),

            [](int i) {return i%2;});

//将偶数元素从v1拷贝到v2;v1不变

remove_copy_if(v1.begin(),v1.end(),back_inserter(v2),

            [](int i) {return i%2;});

两个算法都调用了lambda来确定元素是否为奇数。在第一个调用中,我们从输入序列中将奇数元素删除。在第二个调用中,我们将非奇数(即偶数)元素从输入范围拷贝到v2中。

时间: 2024-08-03 19:48:09

泛型算法结构的相关文章

C++泛型算法的一些总结_C 语言

泛型算法的一些总结1.每个泛型算法的实现都独立于单独的容器,并且不依赖于容器存储的元素类型. 2.泛型算法从不直接添加或删除元素. 3.与容器的类型无关,只在一点上隐式地依赖元素类型:必须能够对元素做比较运算. A.需要某种遍历集合的方式:能够从一个元素向前移到下一个元素. B.必须能够知道是否到达了集合的末尾. C.必须能够对容器中的每一个元素与被查找的元素进行比较. D.需要一个类型来指示元素在容器中的位置,或者表示找不到该元素. 4.迭代器将算法和容器绑定起来.算法基于迭代器及其操作实现,

c++-泛型算法中find和binary_sreach有什么区别

问题描述 泛型算法中find和binary_sreach有什么区别 有序集合是不是只有升序和降序? binary_sreach使用的是什么搜索方式? 还有其他的搜索方式吗? 解决方案 一般C++有序集合就是升序或者降序的 binary_sreach就是二分法检索,经过一次比较就缩小一半的检索区间,所以效率比find高 解决方案二: 二分查找,每次都是取中间值比较,这样就可以确定查找值的范围.这个使用的前提就是已经排序过了.

[C++ 面试基础知识总结] 泛型算法

[C++ 面试基础知识总结] 泛型算法 参考书籍:<C++ Primer> 目录 C 面试基础知识总结 泛型算法 目录 基础泛型算法 只读算法 写容器算法 重排容器元素算法 定制操作 向算法传递函数 lambda表达式 参数绑定 特殊迭代器 插入迭代器 iostream迭代器 反向迭代器 5类迭代器 链表的特定容器算法 基础泛型算法 泛型算法本身运行于迭代器之上,不会执行容器的操作,可以改变容器中保存元素的值,也可以在容器内移动元素,但永远不会直接添加或删除元素.插入迭代器是一种特殊的迭代器,

泛型算法概述

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

初识泛型算法

除了少数例外,标准库算法都对一个范围内的元素进行操作.我们将此元素范围称为"输入范围".接受输入范围的算法总是使用前两个参数来表示此范围,两个参数分别是指想要处理的第一个元素和尾元素之后位置的迭代器. 虽然大多数算法遍历输入范围的方式相似,但它们使用范围中元素的方式不同.理解算法的最基本的方法就是了解它们是否读取元素.改变元素.或是重排元素顺序.   1 只读算法 一些算法只会读取其输入范围内的元素,而从不改变元素.find就是这样一种算法. 另一个只读算法是accumulate,它定

C# 2.0新特性探究之模拟泛型和内置算法

算法 在C#2.0中,匿名方法.IEnumerable接口和匿名方法的合作,使很多的编程任务变得非常的简单,而且写出来的程序非常的优美. 比如,我们可以写出如下的代码: List<Book> thelib = Library.getbooks(); List<Book> found = thelib.FindAll(delegate(Book curbook) { if (curbook.isbn.StartsWith("...")) return true;

C# 2.0的模拟泛型和内置算法

在C#2.0中,匿名方法.IEnumerable接口和匿名方法的合作,使很多的编程任务变得非常的简单,而且写出来的程序非常的优美. 比如,我们可以写出如下的代码: List<Book> thelib = Library.getbooks(); List<Book> found = thelib.FindAll(delegate(Book curbook) { if (curbook.isbn.StartsWith("...")) return true; ret

ACM STL容器和算法

1.4      STL 的组成 STL有三大核心部分:容器(Container).算法(Algorithms).迭代器(Iterator),容器适配器(container adaptor),函数对象(functor),除此之外还有STL其他标准组件.通俗的讲: 容器:装东西的东西,装水的杯子,装咸水的大海,装人的教室--STL里的容器是可容纳一些数据的模板类. 算法:就是往杯子里倒水,往大海里排污,从教室里撵人--STL里的算法,就是处理容器里面数据的方法.操作. 迭代器:往杯子里倒水的水壶,

[CLR via C#]12. 泛型

原文:[CLR via C#]12. 泛型 泛型(generic)是CLR和编程语言提供一种特殊机制,它支持另一种形式的代码重用,即"算法重用". 简单地说,开发人员先定义好一个算法,比如排序.搜索.交换等.但是定义算法的开发人员并不设定该算法要操作什么数据类型:该算法可广泛地应用于不同类型的对象.然后,另一个开发人员只要指定了算法要操作的具体数据类型,就可以使用这个现成的算法了. 泛型有两种表现形式:泛型类型和泛型方法. 泛型类型:大多数算法都封装在一个类型中,CLR允许创建泛型引用