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

时间:2008-09-25 02:58:09

标签: c++ stl iterator

采用以下两行代码:

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
}

我被告知第二种方式是首选。这究竟是为什么?

27 个答案:

答案 0 :(得分:196)

仅当vector.size()是快速操作时,第一种形式才有效。例如,对于向量而言,这是正确的,但对于列表则不然。另外,你打算在循环体内做些什么?如果您打算按

中的方式访问元素
T elem = some_vector[i];

然后您假设容器已定义operator[](std::size_t)。同样,这适用于矢量,但不适用于其他容器。

迭代器的使用使您更接近容器独立性。您不是对随机访问能力或快速size()操作做出假设,只是假设容器具有迭代器功能。

您可以使用标准算法进一步增强代码。根据您尝试实现的目标,您可以选择使用std::for_each()std::transform()等。通过使用标准算法而不是显式循环,您可以避免重新发明轮子。您的代码可能更有效(假设选择了正确的算法),正确且可重复使用。

答案 1 :(得分:51)

因为您没有将代码绑定到some_vector列表的特定实现。如果你使用数组索引,它必须是某种形式的数组;如果使用迭代器,则可以在任何列表实现上使用该代码。

答案 2 :(得分:48)

这是现代C ++灌输过程的一部分。迭代器是迭代大多数容器的唯一方法,因此您甚至可以使用向量来使自己进入正确的思维模式。说真的,这是我这样做的唯一原因 - 我不认为我曾经用一种不同类型的容器替换了一个载体。

<小时/> 哇,三周后这仍然是低估了。我想,做一点点傻瓜并不值得付出代价。

我认为数组索引更具可读性。它匹配其他语言中使用的语法,以及用于老式C数组的语法。它也不那么冗长。如果您的编译器有任何优点,效率应该是一种清洗,并且几乎没有任何重要的情况。

即便如此,我仍然发现自己经常使用迭代器和向量。我相信迭代器是一个重要的概念,所以我会尽可能地推广它。

答案 3 :(得分:33)

想象一下some_vector是用链表实现的。然后请求第i个位置的项目需要进行i操作以遍历节点列表。现在,如果你使用迭代器,一般来说,它将尽最大努力尽可能高效(在链表的情况下,它将维护一个指向当前节点的指针并在每次迭代中推进它,只需要一个单一操作)。

所以它提供了两件事:

  • 使用抽象:你只想迭代一些元素,你不关心如何做到这一点
  • 性能

答案 4 :(得分:26)

我将成为这里的恶魔倡导者,不推荐使用迭代器。主要原因是,我从桌面应用程序开发到游戏开发所使用的所有源代码都有我也不需要使用迭代器。它们一直没有被要求,其次是使用迭代器得到的隐藏的假设和代码混乱以及调试噩梦使它们成为不在任何需要速度的应用程序中使用它的主要例子。

即使从维护的角度来看,他们也是一团糟。它不是因为它们,而是因为场景背后发生的所有混叠。我怎么知道你没有实现自己的虚拟向量或数组列表,它们与标准完全不同。我是否知道运行时当前的类型?您是否超载了操作员我没有时间检查所有源代码。地狱,我甚至知道你使用的STL版本是什么?

迭代器的下一个问题是漏洞抽象,尽管有很多网站都会详细讨论这个问题。

对不起,我还没有看到迭代器中的任何一点。如果他们将列表或向量从你身上抽象出来,实际上你应该知道你处理的是什么向量或列表,如果你没有,那么你将来会为自己设置一些很好的调试会话。

答案 5 :(得分:23)

如果要在迭代过程中向向量添加/删除项目,可能需要使用迭代器。

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;
    }
}

如果您使用索引,则必须在数组中上/下移动项目以处理插入和删除。

答案 6 :(得分:16)

关注点分离

将迭代代码与循环的“核心”关注分开是非常好的。这几乎是一个设计决定。

实际上,通过索引迭代会将您与容器的实现联系起来。向容器请求开始和结束迭代器,启用循环代码以与其他容器类型一起使用。

此外,在std::for_each方式中,您TELL the collection what to do, instead of ASKing了解其内部内容

0x标准将引入闭包,这将使这种方法更容易使用 - 看看例如表达能力。 Ruby的[1..6].each { |i| print i; } ...

效果

但也许一个受到监督的问题是,使用for_each方法可以让迭代并行化 - intel threading blocks可以将代码块分配给系统中的处理器数量! / p>

注意:在发现algorithms库,特别是foreach之后,我经历了两三个月的小写'帮助'操作符结构,这会让你的开发人员疯狂。在这段时间之后,我回到了务实的方法 - 小循环体不再需要foreach:)

关于迭代器的必读参考是书"Extended STL"

GoF在Iterator模式的末尾有一个小小的段落,它讨论了这个迭代的品牌;它被称为“内部迭代器”。看看here也是如此。

答案 7 :(得分:14)

因为它更面向对象。如果你正在迭代你假设的索引:

a)订购这些物品
b)这些对象可以通过索引获得 c)指数增量将击中每个项目
d)该指数从零开始

使用迭代器,你会说“给我一切,所以我可以使用它”,而不知道底层实现是什么。 (在Java中,有些集合无法通过索引访问)

此外,使用迭代器,无需担心超出数组的范围。

答案 8 :(得分:14)

除了所有其他优秀的答案之外...... 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
}

答案 9 :(得分:14)

迭代器的另一个好处是它们更好地允许你表达(并强制执行)你的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
}

答案 10 :(得分:11)

我可能应该指出你也可以打电话

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);

答案 11 :(得分:6)

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
}
'

答案 12 :(得分:5)

我不认为它对矢量有很大的不同。我更喜欢自己使用索引,因为我认为它更具可读性,您可以随机访问,例如跳转6项或在需要时跳回来。

我也喜欢像这样在循环中引用项目,所以这个地方周围没有​​很多方括号:

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

    // Do stuff to "item".
}

使用迭代器可能会很好,如果你认为你可能需要在将来的某个时候用一个列表替换向量,它对STL怪物看起来也更时尚,但我想不出任何其他原因。 / p>

答案 13 :(得分:3)

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

答案 14 :(得分:3)

在对这个答案的主题进行了一些学习之后,我意识到这有点过于简单了。这个循环之间的区别:

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算法库结合使用时,你可以做一些超出数组索引administrivia范围的非常酷的东西。

答案 15 :(得分:3)

第二种形式代表你正在做的更准确。在你的例子中,你不关心i的值,真的 - 你想要的只是迭代器中的下一个元素。

答案 16 :(得分:2)

没有人提到索引的一个优点是当你追加到像std::vector这样的连续容器时它们不会变得无效,所以你可以在迭代期间将项添加到容器中。

迭代器也可以这样做,但你必须调用reserve(),因此需要知道你要添加多少项。

答案 17 :(得分:2)

在迭代过程中,您不需要知道要处理的项目数。你只需要这个项目,迭代器就可以做得非常好。

答案 18 :(得分: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”-named-iterator,因为那时你无法清楚地看到这个值来自哪个数组。

答案 19 :(得分:1)

  • 如果您喜欢接近金属/不信任其实施细节,不要使用迭代器。
  • 如果您在开发过程中经常为另一个收集类型切换,使用迭代器。
  • 如果您发现难以记住如何迭代不同类型的集合(可能您使用了几种不同的外部源中的几种类型),使用迭代器来统一您走过的方式元素。这适用于使用数组列表切换链表。

真的,这就是它的全部。这并不是说你平均会以任何方式获得更多的简洁,如果简洁确实是你的目标,你总是可以依靠宏。

答案 20 :(得分:1)

如果您有权使用C++11功能,则还可以使用range-based for loop遍历向量(或任何其他容器),如下所示:

for (auto &item : some_vector)
{
     //do stuff
}

此循环的好处是,您可以直接通过item变量访问向量的元素,而不必担心弄乱索引或在取消引用迭代器时犯错误。此外,占位符auto可以防止您重复容器元素的类型, 这使您更接近于独立于容器的解决方案。

注意:

  • 如果您需要在循环中使用元素索引,并且operator[]存在于您的容器中(并且对您而言足够快),那么最好采用第一种方法。
  • 基于范围的for循环不能用于向容器中添加元素或从容器中删除元素。如果您想这样做,那么最好坚持使用Brian Matthews给出的solution
  • 如果您不想更改容器中的元素,则应使用关键字const,如下所示:for (auto const &item : some_vector) { ... }

答案 21 :(得分:1)

已有好几点。我还有一些意见:

  1. 假设我们正在谈论C ++标准库,“vector”意味着一个随机访问容器,它具有C阵列的保证(随机访问,连续内存布局等)。如果您说'some_container',上面的许多答案会更准确(容器独立等)。

  2. 要消除对编译器优化的任何依赖,可以将some_vector.size()移出索引代码中的循环,如下所示:

    const size_t numElems = some_vector.size();
    for (size_t i = 0; i 
  3. 始终预先增加迭代器并将后增量视为例外情况。

  4. for(some_iterator = some_vector.begin(); some_iterator!= some_vector.end(); ++ some_iterator){// do stuff}

    所以假设和可索引std::vector<>像容器一样,没有充分的理由偏爱其他人,顺序通过容器。如果您必须经常引用较旧或较新的elemnent索引,则索引版本更为适用。

    通常,首选使用迭代器是因为算法使用它们,并且可以通过更改迭代器的类型来控制(并隐式记录)行为。可以使用数组位置代替迭代器,但语法差异将会突然出现。

答案 22 :(得分:0)

对于容器独立性

答案 23 :(得分:0)

两种实现都是正确的,但我更喜欢'for'循环。由于我们决定使用Vector而不是任何其他容器,因此使用索引将是最佳选择。使用带有向量的迭代器将失去将对象放在连续内存块中的好处,这有助于简化对象的访问。

答案 24 :(得分:0)

我总是使用数组索引,因为我的许多应用程序需要类似“显示缩略图”的内容。所以我写了这样的话:

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;
    }

}

答案 25 :(得分:0)

我觉得这里没有任何答案可以解释为什么我喜欢迭代器作为索引而不是索引到容器的一般概念。请注意,我使用迭代器的大部分经验实际上并非来自C ++,而是来自诸如Python之类的高级编程语言。

迭代器接口对您的函数的使用者提出了较少的要求,这使使用者可以对它进行更多的操作。

如果您只需要能够转发就可以了,开发人员不仅限于使用可索引的容器-他们可以使用实现operator++(T&)operator*(T)operator!=(const &T, const &T)的任何类

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

您的算法可以在您需要的情况下工作-遍历向量-但对于您不一定期望的应用程序也很有用:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

尝试实现一个与该迭代器类似的操作的方括号运算符,而迭代器的实现相对简单。方括号运算符还影响类的功能-您可以将其索引到任意点-实现起来可能很困难或效率低下。

迭代器还适合decoration。人们可以编写迭代器,该迭代器在其构造函数中使用迭代器并扩展其功能:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

虽然这些玩具看似平凡,但不难想象使用迭代器和迭代器装饰器通过一个简单的界面即可完成强大的功能-用一个迭代器来装饰数据库结果的前向迭代器,该迭代器可从单个结果构造一个模型对象, 例如。这些模式可以实现无限集的内存高效迭代,并且使用像我上面所写的那样的过滤器,可能对结果进行延迟评估。

C ++模板的部分功能是迭代器接口,当将其应用于固定长度C数组decays to simple and efficient pointer arithmetic之类的时候,使其真正成为零成本的抽象。

答案 26 :(得分:0)

甚至比“告诉CPU要做什么”(命令性)更好的是“告诉图书馆你想要什么”(功能性)。

因此,您应该学习stl。

中存在的算法,而不是使用循环