为什么要使用迭代器而不是数组索引?
采取以下两行代码:
for (int i = 0; i < some_vector.size(); i++) { //do stuff }
还有这个:
for (some_iterator = some_vector.begin(); some_iterator != some_vector.end(); some_iterator++) { //do stuff }
有人告诉我第二种方法是首选。为什么会这样呢?
解决方案
因为我们没有将代码与some_vector列表的特定实现联系在一起。如果使用数组索引,则它必须是某种形式的数组;如果使用迭代器,则可以在任何列表实现中使用该代码。
我认为矢量对它没有太大影响。我更喜欢自己使用索引,因为我认为它更具可读性,并且我们可以进行随机访问,例如向前跳6个项目或者在需要时向后跳。
我也喜欢像这样在循环中引用该项目,因此该地方周围没有很多方括号:
for(size_t i = 0; i < myvector.size(); i++) { MyClass &item = myvector[i]; // Do stuff to "item". }
如果我们认为将来可能需要用列表替换向量,并且使用STL怪胎看起来更时尚,那么使用迭代器可能会很好,但是我无法想到任何其他原因。
因为它更加面向对象。如果要遍历索引,则假定:
a)这些对象是有序的
b)可以通过索引获得那些对象
c)索引增量将击中每个项目
d)该索引从零开始
使用迭代器时,我们在说"给我一切,以便我可以使用它",而无需知道底层实现是什么。 (在Java中,有些集合无法通过索引访问)
同样,使用迭代器,无需担心超出数组范围。
想象some_vector是用链表实现的。然后,在第i个位置请求一个项目需要执行i个操作来遍历节点列表。现在,如果使用迭代器,通常来说,它将尽最大努力提高效率(对于链表,它将维护指向当前节点的指针并在每次迭代中进行推进,只需要一个单次操作)。
因此,它提供了两件事:
- 使用抽象:我们只想迭代一些元素,而不关心如何做
- 表现
仅当vector.size()是快速操作时,第一种形式才有效。例如,向量是正确的,但列表则不是。另外,我们打算在循环体内做什么?如果我们打算像这样访问元素
T elem = some_vector[i];
那么我们就假设容器已定义了" operator [](std :: size_t)"。同样,对于矢量而言,这是正确的,但对于其他容器,则不是。
迭代器的使用使我们更接近容器独立性。我们并没有假设随机访问功能或者快速的size()操作,只是假设容器具有迭代器功能。
我们可以使用标准算法来进一步增强代码。根据我们要实现的目标,我们可以选择使用" std :: for_each()"," std :: transform()"等等。通过使用标准算法而不是显式循环,我们可以避免重新发明轮子。代码可能更有效(如果选择了正确的算法),正确且可重用。
除了所有其他出色的答案外," int"对于向量来说可能还不够大。相反,如果要使用索引,请对容器使用size_type
:
for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i) { Foo& this_foo = myvector[i]; // Do stuff with this_foo }
第二种形式表示我们正在做的事更准确。在示例中,我们无需关心i的值,实际上,我们想要的只是迭代器中的下一个元素。
如果要在向量上进行迭代时向向量添加/删除项目,则可能需要使用迭代器。
some_iterator = some_vector.begin(); while (some_iterator != some_vector.end()) { if (/* some condition */) { some_iterator = some_vector.erase(some_iterator); // some_iterator now positioned at the element after the deleted element } else { if (/* some other condition */) { some_iterator = some_vector.insert(some_iterator, some_new_value); // some_iterator now positioned at new element } ++some_iterator; } }
如果使用索引,则必须在数组中上下移动项目以处理插入和删除操作。
关于迭代器的另一个好处是,它们更好地允许我们表达(并强制执行)const-preference。此示例确保我们不会在循环中间更改矢量:
for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos) { // Foo & foo = *pos; // this won't compile const Foo & foo = *pos; // this will compile }
它是现代C ++灌输过程的一部分。迭代器是迭代大多数容器的唯一方法,因此即使将向量用于向量,也可以使自己陷入正确的思维定式。认真地讲,这就是我这样做的唯一原因,我认为我从未用其他类型的容器替换过矢量。
哇,三个星期后,它仍然被低估了。我想这不是白费力气的事。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
我认为数组索引更具可读性。它与其他语言中使用的语法以及老式C数组中使用的语法相匹配。它也不太冗长。如果编译器是好的,效率应该是洗钱,而且几乎在任何情况下它都至关重要。
即使这样,我仍然发现自己经常将向量与迭代器结合使用。我相信迭代器是一个重要的概念,因此我会尽可能推广它。
在迭代过程中,我们不需要知道要处理的项目数。我们只需要该项目,迭代器就可以很好地完成这些事情。
我将在这里成为恶魔的拥护者,而不推荐迭代器。这样做的主要原因是,从台式机应用程序开发到游戏开发,我从事的所有源代码都不需要使用迭代器。一直以来都不需要它们,其次,使用迭代器获得的隐藏假设,代码混乱和调试噩梦使它们成为一个很好的例子,不将其用于任何需要速度的应用程序中。
即使从维护的角度来看,它们也是一团糟。这不是因为它们,而是因为幕后发生的所有混叠。我怎么知道我们还没有实现与标准完全不同的虚拟矢量或者数组列表。我是否知道运行时当前是哪种类型?我们是否使运算符超载了,我没有时间检查所有源代码。我甚至不知道我们使用的是哪个版本的STL?
迭代器遇到的下一个问题是泄漏抽象,尽管有许多网站与它们进行了详细讨论。
抱歉,我还没有看到迭代器的任何意义。如果他们从我们那里抽象出列表或者向量,则实际上我们应该已经知道要处理的向量或者列表(如果我们不这样做),那么将来我们将需要准备一些很棒的调试会话。
甚至比"告诉CPU做什么"(命令)更好的是"告诉库想要什么"(功能)。
因此,代替使用循环,我们应该学习stl中存在的算法。
我一直使用数组索引,因为我的许多应用程序都需要"显示缩略图"之类的东西。所以我写了这样的东西:
some_vector[0].left=0; some_vector[0].top =0;<br> for (int i = 1; i < some_vector.size(); i++) { some_vector[i].left = some_vector[i-1].width + some_vector[i-1].left; if(i % 6 ==0) { some_vector[i].top = some_vector[i].top.height + some_vector[i].top; some_vector[i].left = 0; } }
已经有几个好处。我还有一些其他评论:
- 假设我们正在谈论C ++标准库,"向量"表示一个随机访问容器,该容器具有C数组的保证(随机访问,连续内存布局等)。如果我们说过" some_container",那么上面的许多答案都将更为准确(容器独立性等)。
为了消除对编译器优化的任何依赖性,我们可以将some_vector.size()从索引代码中移出循环,如下所示:
const size_t numElems = some_vector.size(); for (size_t i = 0; i Always pre-increment iterators and treat post-increments as exceptional cases. for (some_iterator = some_vector.begin(); some_iterator != some_vector.end(); ++some_iterator){ //do stuff }
因此,假设和可索引的std :: vector <>
类似容器,没有充分的理由优先选择一个容器,顺序地通过容器。如果必须经常引用旧的或者较新的元素索引,那么索引版本会更合适。
通常,首选使用迭代器,因为算法会使用它们,并且可以通过更改迭代器的类型来控制(并隐式记录)行为。可以使用数组位置代替迭代器,但是语法上的差异会很明显。
为了容器独立
STL迭代器大部分都在那儿,因此诸如sort之类的STL算法可以与容器无关。
如果只想遍历向量中的所有条目,请使用索引循环样式。
对于大多数人来说,它的键入次数更少并且更易于解析。如果C ++有一个简单的foreach循环而不用模板魔术过度,那就太好了。
for( size_t i = 0; i < some_vector.size(); ++i ) { T& rT = some_vector[i]; // now do something with rT } '
我可能应该指出,我们也可以致电
std :: for_each(some_vector.begin(),some_vector.end(),&do_stuff);`
关注点分离
将迭代代码与循环的"核心"关注点分开是非常好的。这几乎是一个设计决定。
确实,通过索引进行迭代将我们与容器的实现联系在一起。向容器询问开始和结束迭代器,可使循环代码与其他容器类型一起使用。
同样,以std :: for_each
的方式,告诉集合该怎么做,而不是向其内部进行询问
0x标准将引入闭包,这将使这种方法更易于使用,请看一下诸如Ruby的[1..6] .each {| i |打印我; }
...
表现
但是,也许一个可以忽略的问题是,使用for_each
方法可以使迭代并行化,从而使intel线程块可以在系统中的处理器数量上分配代码块!
注意:发现了"算法"库,尤其是" foreach"库之后,我花了两三个月的时间来编写可笑的小型" helper"运算符结构,这会使开发人员发疯。在这段时间之后,我回到了务实的方法,小循环体不再需要" foreach"了:
关于迭代器的必读参考是" Extended STL"一书。
GoF在Iterator模式的末尾有一个很小的段落,它讨论了这种迭代品牌。它被称为"内部迭代器"。也在这里看看。
在了解了有关此答案的内容后,我意识到这有点过分简化了。此循环之间的区别:
for (some_iterator = some_vector.begin(); some_iterator != some_vector.end(); some_iterator++) { //do stuff }
而这个循环:
for (int i = 0; i < some_vector.size(); i++) { //do stuff }
相当少。实际上,以这种方式执行循环的语法似乎在我身上越来越普遍:
while (it != end){ //do stuff ++it; }
迭代器确实解锁了一些相当强大的声明性功能,当与STL算法库结合使用时,我们可以做一些很酷的事情,这些事情超出了数组索引管理的范围。
索引需要额外的" mul"操作。例如,对于" vector <int> v",编译器将" v [i]"转换为"&v + sizeof(int)* i"。
我不使用迭代器的原因与我不喜欢foreach语句的原因相同。当有多个内部循环时,很难在不记住所有局部值和迭代器名称的情况下跟踪全局/成员变量。我发现有用的是在不同情况下使用两组索引:
for(int i=0;i<anims.size();i++) for(int j=0;j<bones.size();j++) { int animIndex = i; int boneIndex = j; // in relatively short code I use indices i and j ... animation_matrices[i][j] ... // in long and complicated code I use indices animIndex and boneIndex ... animation_matrices[animIndex][boneIndex] ... }
例如,我什至不想将诸如" animation_matrices [i]"之类的缩写缩写为某些以" anim_matrix"命名的迭代器,因为那样我们就无法清楚地看到此值源自哪个数组。