读书频道 > 网站 > 网页设计 > 大规模C++程序设计
1.5 迭代器
15-04-27    下载编辑
收藏    我要投稿   

本文所属图书 > 大规模C++程序设计

《大规模C++程序设计》由世界级软件开发大师John Lakos亲笔撰写,是C++程序设计领域最有影响力的著作之一。作者结合自己多年从事大规模C++项目的开发经验,详细介绍了大规模C++程序设计涉及的一系列概念、理论、立即去当当网订购

在面向对象设计中迭代器可能是最常见的模式。一个迭代器就是一个对象。迭代器由某种原始对象提供并与之密切耦合,用途是允许客户端程序顺序地访问原始对象的部件、属性和子对象。

对象常常会表现为其他对象的集合,这种对象通常被称为容器。集合、表、栈、堆、队列、哈希表等均是典型的容器对象。要注意的是在相关的地方,我们通常会用一段前导注释标识程序体的源文件。例如:


 

例如图1-8所示的实现一个整数集合的简单类,我们可以从其头文件中看到,使用对象IntSetLink实现IntSet,事实上这是该类的封装好的实现细节。在这个最小的实现中,我们已经作了这样的选择:通过让这些在其他方面自动产生的函数成为私有函数,防止用户构造一个IntSet拷贝或赋值给一个IntSet对象(注释NOT IMPLEMENTED表明此功能不存在,即使是私有的也一样)。IntSet的用户只允许创建一个空的集合、添加整数、检查成员关系和销毁它。


 

使用这个有限功能的一个很小的测试驱动程序的练习如图1-9所示。值得注意的是,本书中的驱动程序用文件名后缀.t.c表示。


 

假设我们希望找到存在于集合中的成员,以便将它们打印出来。理论上,我们可以编写我们自己的输出函数(如图1-10所示),但是这种实现的性能可能会略显不足。


 

一种显而易见的解决方案是使“operator<<”函数成为类IntSet的一个友元,以便利用它的内部表达法。我们可以做到这一点,但是如果一个客户端对这个运算符的实现所提供的格式表示不满意将会怎么样?如果稍后我们发现需要访问内部成员,比方说,要比较两个IntSet对象,会发生什么?

我们可以一直添加新成员和友元,可是每次这样做的时候,我们都会把客户端和我们自己置于增加类复杂性的危险之中。反复重新访问并扩充一个对象的功能是在软件中引入缺陷的公认方式。而且,除非你打算支持多个版本,其他客户端不会关心这个强加给它们的新的功能。


 

我们通过提供一个通用的、有效的方式立刻访问集合的单个成员,可以一次性地解决大多数缺陷,而不是每次处理一个缺陷。假设我们决定将这种能力直接添加到IntSet类中,如图1-11所述。现在就允许客户端程序通过类IntSet的一个实例进行迭代,并以所希望的任何格式打印出对象的所有内容。图1-12说明了迭代器的一些功能。不管集合的实现如何改变,客户端的代码都不会受到影响。


 

可惜,图1-12仍然存在设计问题。对于一个特定对象,在任何时候它们最多只能有一个迭代器在运行。假设我们正在尝试为IntSet实现一个比较函数,出于调试的目的,决定通过比较迭代,在中途打印出集合的内容。打印例行程序可能会破坏比较函数迭代状态,产生副作用。问题在于,IntSet要分配到足够的空间以便为每一次迭代保留状态信息。无论一个迭代是否活跃,被分配的空间都要保留。如果由于某些原因,想有一对嵌套的for循环在同一个集合的元素上迭代,我们就得必须复制整个集合。

这个问题可以这样得到解决:让客户端程序保持内部状态或保留占位符的某些其他形式。如果客户端程序动态地分配状态,用户必须记得删除状态以免内存泄漏。

如果占位符是整数索引的形式,那么可能在集合的底层实现上会有某些附加的实际约束。例如,若集合实现为一个链表(而不是一个数组),那么在迭代期间的行为所需要的时间复杂度为二次方(即:O (N2)),因为for循环的每次迭代都必须要遍历这个表。

标准的方法是连同每个容器类一起提供一个迭代器类(在同一个头文件中)。迭代器被声明为容器的一个友元,因此可以访问它的内部组织。把迭代器类和容器类定义在同一个头文件中,以避免与“远距离”友元相关的问题(将在3.6节中论述)。对于诸如IntSet之类的具体的容器,通常在程序栈中创建迭代器;因此当迭代器超出作用域范围时自动地删除其状态。迭代器的对象增加了空间的有效性,因为每次迭代所需的空间只存在于迭代过程中。同样,在一个给定的容器中,任意数量的迭代器在任何时间都可以独立地活动,不会相互干扰。

实际情况是,在迭代的过程中不能修改或撤销在迭代器上所操作的对象,这对于迭代器而言是很常见的一种假设。迭代期间对象所呈现出来的顺序依赖于实现,更改对象的顺序无需另行通知,这也是很常见的。理想情况下,迭代器的开发者应该明确地规定是否要定义迭代器的顺序。为安全起见,迭代器的客户端不应该假定一个顺序,除非有指定顺序。

图1-13说明在本书中所使用的标准迭代器模式的设计。这个迭代器对象适用于以for循环的形式使用。该迭代器的语法相当简洁。运算符在这里的使用并非显而易见,如果你从来没有见过它们以这种方式使用,那就更是如此。很容易得出的结论是:这种风格是运算符重载的滥用,降低了可读性。然而,这样做还有其他理由。

考虑到大型设计中迭代器出现的频率,开发者面对的首要问题是一致性。如果我们避免使用运算符重载而使用函数重载,那么每次使用相同的函数名是很重要的;否则我们将发现自己会不经意地给这些函数取错名,并总是不得不回到头文件去查看语法细节。具有代表性的一些可能等价的函数名如图1-14所示。

经验表明,每个标准迭代方法都宜采用图1-14左栏显示的运算符,为具体类型之上的迭代产生一致、易用、易于熟悉并且易于识别的习语。无论你决定使用哪些名字,都要确保整个生产线一致。IntSet输出运算符的最终实现如图1-15所示,简明扼要的迭代器符号提供了一个简洁的实现。



在图1-15中,经过慎重考虑,我们决定使用前置递增(++it)而不使用后置递增(it++);后置递增版本需要另一个哑变元,并且该哑变元不是普遍适用的。此外,在应用到基本类型时,一个迭代器的增量语义更接近于前置递增的模式(见9.1.1节)。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3 功能
下一篇:1.5 小结
相关文章
图文推荐
JavaScript网页动画设
1.9 响应式
1.8 登陆页式
1.7 主题式
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站