为什么要使用迭代器而不是数组索引?

281

考虑以下两行代码:

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
}

我被告知第二种方式更受欢迎。为什么呢?


77
若你将 some_iterator++ 修改为 ++some_iterator,则更倾向于使用第二种方式。因为后置自增会创建一个不必要的临时迭代器。 - jason
6
你还应该将 end() 加入声明子句。 - Lightness Races in Orbit
5
使用效率低下的vector::end(向量的结尾) 的C++实现,可能存在比它是否被提取出循环更值得担心的问题。就我个人而言,我更喜欢清晰易懂的代码——如果终止条件中涉及到find操作,那么我会感到有点担忧。 - Steve Jessop
13
@Tomalak: 这段代码并不邋遢(除了后置递增可能有点),就C++迭代器的允许范围而言,它是简洁明了的。增加更多的变量只是为了过早优化而增加认知负担,这才是邋遢的做法。 - Steve Jessop
8
如果它不是瓶颈,那么现在就进行还为时过早。你的第二点对我来说似乎很荒谬,因为正确的比较不是 it != vec.end()it != end,而是 (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)(vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)。我不需要计算字符数。当然可以更喜欢其中一个,但别人对你偏好的异议并不是“粗心”,而是更喜欢变量更少、阅读时更易考虑的简化代码。 - Steve Jessop
显示剩余5条评论
27个回答

12
我可能需要指出,你也可以调用以下代码:
``` std::for_each(some_vector.begin(), some_vector.end(), &do_stuff); ```

8

STL迭代器的主要作用是使STL算法(如sort)与容器无关。

如果您只想循环遍历向量中的所有条目,请使用索引循环样式。

对于大多数人来说,这样做的输入更少且更易于解析。如果C++有一个简单的foreach循环而不会过度依赖模板魔法,那就太好了。

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

我认为对于向量来说,使用索引和不使用索引并没有太大的区别。个人更喜欢使用索引,因为这样更易读,而且可以随意跳转到向前或向后的第6个元素。

我还喜欢在循环中引用项,就像这样,这样就不会出现很多方括号:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

如果您认为将来可能需要使用列表替换向量,并且还想让STL爱好者看起来更加时尚,那么使用迭代器可能是个不错的选择,但我想不出其他理由。


大多数算法按顺序一次在容器的每个元素上操作。当然,有一些例外情况,你可能想以特定的顺序或方式遍历集合,但在这种情况下,我会尽力编写一个与STL集成并使用迭代器的算法。 - wilhelmtell
这将鼓励重复使用并避免后续的偏移错误。然后,我会像任何其他标准算法一样使用迭代器来调用该算法。 - wilhelmtell
1
不需要使用 advance()。迭代器具有与索引相同的 += 和 -= 运算符(适用于向量和类似向量的容器)。 - MSalters
在某些情况下,我更喜欢使用索引,因为我认为它更易读。但在其他情况下,索引很快会变得非常混乱,而且您可以进行随机访问,这并不是索引的独特功能:请参见http://en.cppreference.com/w/cpp/concept/RandomAccessIterator。 - underscore_d

4

在了解了这个答案的主题后,我意识到它有点过于简化了。这个循环与以下循环之间的区别:

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算法库结合使用时,您可以做一些非常酷的事情,这些事情超出了数组索引管理的范围。


事实上,如果所有的迭代器都像你最后的例子一样紧凑,我对它们就没有什么问题了。当然,这实际上等同于 for (Iter it = {0}; it != end; ++it) {...} - 你只是省略了声明 - 所以简洁性与你的第二个例子并没有太大的区别。不过还是要点赞。 - Engineer

3
第二种形式更准确地表示了您正在做什么。在您的示例中,您实际上并不关心 i 的值 - 您想要的只是迭代器中的下一个元素。

3

索引需要额外的mul操作。例如,对于vector<int> v,编译器将v[i]转换为&v + sizeof(int) * i


在大多数情况下,与迭代器相比可能并不会有显着的劣势,但意识到这一点是一件好事。 - Brent Bradburn
3
对于单个独立元素访问,可能是这样。但如果我们谈论循环 - 像OP所说的那样 - 那么我相信这个答案基于一个想象中的非优化编译器。任何半过得去的编译器都有充足的机会和可能缓存sizeof,并且只需在每次迭代中添加一次,而不是每次都重新进行整个偏移量计算。 - underscore_d

2
如果您可以使用 C++11 功能,那么您也可以使用 基于范围的 for 循环 对您的向量(或任何其他容器)进行迭代,如下所示:
for (auto &item : some_vector)
{
     //do stuff
}

这个循环的好处是你可以通过变量item直接访问向量的元素,而不必担心弄乱索引或在解除引用迭代器时犯错误。此外,占位符auto避免了重复容器元素类型的问题,使您更接近一个与容器无关的解决方案。
注:
  • 如果您需要循环中的元素索引并且您的容器已经存在operator[](并且足够快),那么最好使用第一种方法。
  • 基于范围的for循环不能用于向容器添加/删除元素。如果您要这样做,最好坚持Brian Matthews所提供的solution
  • 如果您不想改变容器中的元素,则应如下使用关键字constfor (auto const &item : some_vector) { ... }

2

在迭代过程中,您不需要知道要处理的项目数量。您只需要这个项目,而迭代器可以非常好地完成这些操作。


2
没有人提到指数的一个优点是,当您附加到连续容器(如std::vector)时,它们不会变得无效,因此您可以在迭代期间向容器添加项目。
使用迭代器也可以实现这一点,但必须调用reserve(),因此需要知道要附加多少项。

1

我不使用迭代器的原因与我不喜欢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"的迭代器,因为这样你就无法清楚地看出这个值来自哪个数组。


我不认为索引在这方面有任何优势。你可以轻松地使用迭代器,并选择它们的命名约定:itjtkt等等,甚至可以继续使用ijk等等。如果您需要确切知道迭代器表示什么,则对我来说,像for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()这样的写法比不断索引animation_matrices[animIndex][boneIndex]更具描述性。 - underscore_d
哇,感觉好久以前我写下那个意见了。现在使用foreach和C++迭代器时不会太紧张。我想多年来与错误代码一起工作可以增强一个人的耐受性,所以接受所有语法和约定更容易...只要它能正常工作,并且只要一个人能回家,你懂的 ;) - AareP
哈哈,确实,在此之前我没有仔细看过这个的年代!上次我不知道为什么没想到的是,现在我们也有基于范围的 for 循环,这使得基于迭代器的方法更加简洁。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接