12

这对你们中的一些人来说可能看起来很无聊,但是以下两种在 STL 容器上的迭代方法中哪一种更好?为什么

class Elem;
typedef vector<Elem> ElemVec;
ElemVec elemVec;

// Method 0
for (ElemVec::iterator i = elemVec.begin(); i != elemVec.end(); ++i)
{
    Elem& e = *i;
    // Do something
}

// Method 1
for (int i = 0; i < elemVec.size(); ++i)
{
    Elem& e = elemVec.at(i);
    // Do something
}

方法 0 看起来像更干净的 STL,但方法 1 用更少的代码实现了相同的效果。对容器的简单迭代在任何源代码中随处可见。所以,我倾向于选择方法 1,它似乎可以减少视觉混乱和代码大小。

PS:我知道迭代器可以做的不仅仅是一个简单的索引。但是,请让回复/讨论集中在容器上的简单迭代上,如上所示。

4

9 回答 9

16

第一个版本适用于任何容器,因此在将任何容器作为参数的模板函数中更有用。即使对于向量,它也可能稍微更有效。

第二个版本仅适用于向量和其他整数索引容器。对于那些容器来说,它会更惯用一些,C++ 新手很容易理解,如果你需要对索引做其他事情,这很有用,这并不罕见。

像往常一样,恐怕没有简单的“这个更好”的答案。

于 2009-04-04T08:30:21.293 回答
8

如果您不介意(非常?)小的效率损失,我建议使用Boost.Foreach

BOOST_FOREACH( Elem& e, elemVec )
{
    // Your code
}
于 2009-04-04T08:38:30.383 回答
6

方法 0 更快,因此推荐。

方法 1 使用允许为 O(1) 的 size(),具体取决于容器和 stl 实现。

于 2009-04-04T08:59:00.567 回答
5

以下在标准库容器上的迭代方法是最好的。

(及更高版本)的基于范围的for循环与说明auto一起使用:

// Method 2
for (auto& e: elemVec)
{
    // Do something with e...
}

这与您的类似,但需要更少的输入,更少的维护,并且适用于任何与andMethod 0兼容的容器,包括普通的旧数组。std::begin()std::end()

于 2012-04-29T01:09:10.033 回答
4

方法0的更多优点:

  • 如果您从向量移动到另一个容器,则循环保持不变,
  • 如果需要,很容易从迭代器移动到 const_iterator,
  • 当 c++0x 到来时,自动输入将减少一些代码混乱。

主要缺点是在许多情况下您扫描两个容器,在这种情况下,索引比保留两个迭代器更干净。

于 2009-04-04T08:35:15.103 回答
2

巧合的是,我最近做了一个速度测试(用 rand() 填充 10 * 1024 * 1024 个整数)。
这些是 3 次运行,时间以纳秒为单位

vect[i] time      : 373611869  
vec.at(i) time    : 473297793  
*it = time        : 446818590  
arr[i] time       : 390357294  
*ptr time         : 356895778  

更新:添加了 stl 算法 std::generate,由于特殊的迭代器优化 (VC++2008),它似乎运行得最快。时间以微秒为单位。

vect[i] time      : 393951
vec.at(i) time    : 551387
*it = time        : 596080
generate = time   : 346591
arr[i] time       : 375432
*ptr time         : 334612

结论:使用标准算法,它们可能比显式循环更快!(也是很好的做法)

更新:上述时间处于 I/O-bound 情况,我对 CPU-bound 进行了相同的测试(迭代一个相对较短的向量,它应该重复适合缓存,将每个元素乘以 2 并写回向量)

//Visual Studio 2008 Express Edition
vect[i] time      : 1356811
vec.at(i) time    : 7760148
*it = time        : 4913112
for_each = time   : 455713
arr[i] time       : 446280
*ptr time         : 429595

//GCC
vect[i] time      : 431039
vec.at(i) time    : 2421283
*it = time        : 381400
for_each = time   : 380972
arr[i] time       : 363563
*ptr time         : 365971  

有趣的是,与 for_each 相比,VC++ 中的迭代器和运算符 [] 速度要慢得多(这似乎通过一些模板魔术将迭代器降级为指针以提高性能)。
在 GCC 中,at() 的访问时间更短,这是正常的,因为它是测试的唯一范围检查函数。

于 2009-04-04T09:25:50.287 回答
2

方法0,有几个原因。

  • 它更好地表达了您的意图,这有助于编译器优化和可读性
  • 一个错误的可能性较小
  • 即使您将向量替换为没有运算符 [] 的列表,它也可以工作

当然,最好的解决方案通常是解决方案 2:std 算法之一。std::for_each、std::transform、std::copy 或您需要的任何其他内容。这有一些进一步的优点:

  • 它更好地表达了您的意图,并允许进行一些重要的编译器优化(MS 的安全 SCL 对您的方法 0 和 1 执行边界检查,但会在 std 算法上跳过它)
  • 它的代码更少(至少在调用站点。当然,您必须编写一个仿函数或其他东西来替换循环体,但是在使用站点,代码会被清理很多,这可能是最重要的地方.

一般来说,避免过度指定您的代码。准确指定您想要完成的操作,仅此而已。std 算法通常是去那里的方式,但即使没有它们,如果你不需要 index i,为什么要有它?在这种情况下,请改用迭代器。

于 2009-04-04T11:11:58.503 回答
1

这取决于哪种类型的容器。对于 a vector,您使用哪个可能并不重要。方法 0 变得更加地道,但正如大家所说,它们并没有太大的区别。

如果您决定使用 a list,则方法 1 原则上将是O(N),但实际上没有 listat()方法,因此您甚至不能那样做。(所以在某种程度上,你的问题堆积了甲板。)

但这本身就是方法 0 的另一个论点:它对不同的容器使用相同的语法。

于 2009-04-04T09:20:47.053 回答
0

上面没有考虑的一种可能性:根据“做某事”的细节,可以同时具有方法0和方法1,您不必选择:

for (auto i = elemVec.begin(), ii = 0; ii < elemVec.size(); ++i, ++ii)
{
    // Do something with either the iterator i or the index ii
}

这样,无论是查找索引还是访问相应的成员,都非常简单。

于 2017-02-14T15:51:49.433 回答