迭代器..为什么要使用它们?

22

在STL库中,一些容器具有迭代器,并且通常认为它们是遍历这些容器的 superior way,而不是简单的for循环。

for ( int i=0; i < vecVector.size(); i++ )
{

..

}

有谁能告诉我为什么以及在什么情况下我应该使用迭代器,以及在什么情况下使用上面的代码片段吗?


哪些 STL 容器 <i>没有</i> 迭代器? - Chris Charabaruk
1
根据STL的定义,如果一个类型有关联的迭代器类型,则它是一个容器。因此,按照定义,没有这样的类型。 - Dave Van den Eynde
6个回答

28

注意,通常的向量实现不会使用“int”作为索引/大小的类型。因此,您的代码至少会引发编译器警告。

泛型

迭代器增加了代码的泛化性。

例如:

typedef std::vector<int> Container ;

void doSomething(Container & p_aC)
{
    for(Container::iterator it = p_aC.begin(), itEnd = p_aC.end(); it != itEnd; ++it)
    {
       int & i = *it ; // i is now a reference to the value iterated
       // do something with "i"
    }
}

现在,假设你将向量更改为列表(因为在您的情况下,列表更好)。 您只需要更改typedef声明并重新编译代码。

如果您使用基于索引的代码,那么它需要被重新编写。

访问

迭代器应该被视为一种超级指针。它“指向”值(或在映射的情况下指向键/值对)。

但它有方法可以移动到容器中的下一项或上一项。 一些容器甚至提供随机访问(例如向量和deque)。

算法

大多数STL算法适用于迭代器或迭代器范围(再次,这是由于通用性)。 在这里,您不能使用索引。


注意:这段代码在使用“range”库时特别强大。可以使用迭代器对容器的子集进行算法处理,除了流和其他值生成器。请参见http://boost.org,“Range”和“Iterators”库。 - Aaron

17
使用迭代器使得您的代码不受容器实现的影响。如果您的容器具有高效的随机访问能力,那么在性能上几乎没有什么差别。
但在许多情况下,您不知道是否存在这种情况。例如,如果您尝试在链表上使用下标操作符,容器将需要在每次迭代中遍历列表以查找您的元素。
因此,除非您确切地知道对容器进行随机访问是便宜的,否则请使用迭代器。

我认为 std::list 没有订阅运算符。 - Dave Van den Eynde
对的,这个集合甚至可能不支持随机访问——所以下标访问可能无法使用。迭代器则总是有效的。 - JohnMcG

3
如果您将迭代器用作函数的参数,则可以将其与使用的“容器”类型解耦。例如,您可能会将函数的结果指向控制台输出,而不是一个向量(请参见下面的示例)。这个技巧可以极大地减少类之间的耦合。松散耦合的类更容易测试。
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

template <typename InputIterator, typename OutputIterator>
void AddOne(InputIterator begin, InputIterator end, OutputIterator dest)
{
    while (begin != end)
    {
        *dest = *begin + 1;
        ++dest;
        ++begin;
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    vector<int> data;
    data.push_back(1);
    data.push_back(2);
    data.push_back(3);

    // Compute intermediate results vector and dump to console
    vector<int> results;
    AddOne(data.begin(), data.end(), back_inserter(results));
    copy(results.begin(), results.end(), ostream_iterator<int>(cout, " "));
    cout << endl;

    // Compute results and send directly to console, no intermediate vector required
    AddOne(data.begin(), data.end(), ostream_iterator<int>(cout, " "));
    cout << endl;

    return 0;
}

2
在你的例子中,调用vecVector.size()比使用迭代器效率低。迭代器本质上封装了容器大小的问题,使你不必担心正在迭代的容器的大小。此外,迭代器不一定要按顺序进行。它只需要以任何它认为合适的方式响应.next调用即可。

1
我认为这并不会降低效率。编译器会通过将对size()的调用放在循环外来进行优化。此外,size是向量的属性,始终已知且永远不需要计算,就像end()一样。 - Dave Van den Eynde
如果在循环内部向向量添加了一个项目,会发生什么? - Ben Hoffstein
1
当然,它不会将其从循环中优化出来,但它也不会在循环外使用end()进行优化。因此,仍然没有区别。 - Dave Van den Eynde

1

迭代器主要是更高层次的抽象。

您的代码片段假设容器可以索引。对于std::vector<>和一些其他容器,例如原始数组,这是正确的。

但是std::set<>完全没有索引,而std::map<>的索引运算符会将任何参数插入到映射中 - 这不是在您的for循环中预期的行为。

此外,性能问题只有在经过测量并证明后才会成为问题。


1

首先,如果你将那个向量转换成列表,上述代码将不再起作用。

迭代器允许你创建函数模板,而这些模板不需要知道它们所操作的容器的类型。你甚至可以这样做:

#include <algorithm>

void printvalue(double s)
{
    // Do something with s
}

int _tmain(int argc, _TCHAR* argv[])
{
    double s[20] = {0};

    std::for_each(s, s+20, printvalue);

    return 0;
}

这是因为标准指针也是 for_each 的有效迭代器。

Dave


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