迭代器循环与索引循环

时间:2013-01-17 07:14:08

标签: c++ loops c++11 indexing iterator

  

可能重复:
  Why use iterators instead of array indices?

我正在回顾我对C ++的了解,并且我偶然发现了迭代器。我想知道的一件事是什么使他们如此特别,我想知道为什么:

using namespace std;

vector<int> myIntVector;
vector<int>::iterator myIntVectorIterator;

// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);

for(myIntVectorIterator = myIntVector.begin(); 
        myIntVectorIterator != myIntVector.end();
        myIntVectorIterator++)
{
    cout<<*myIntVectorIterator<<" ";
    //Should output 1 4 8
}

比这更好:

using namespace std;

vector<int> myIntVector;
// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);

for(int y=0; y<myIntVector.size(); y++)
{
    cout<<myIntVector[y]<<" ";
    //Should output 1 4 8
}

是的,我知道我不应该使用std命名空间。我刚把这个例子从cprogramming网站上删除了。那么请你告诉我为什么后者更糟?有什么大不同?

8 个答案:

答案 0 :(得分:158)

关于迭代器的特殊之处在于它们提供了algorithms and containers之间的粘合剂。对于通用代码,建议使用STL算法的组合(例如findsortremovecopy)等来执行您的计算请记住您的数据结构(vectorlistmap等),并将该算法与迭代器一起提供到您的容器中。

您的特定示例可以写成for_each算法和vector容器(参见下面的选项3)的组合,但它只是四种不同的方法来迭代std ::矢量:

1)基于索引的迭代

for (std::size_t i = 0; i != v.size(); ++i) {
    // access element as v[i]

    // any code including continue, break, return
}

优点:熟悉C风格代码的人都熟悉,可以使用不同的步幅循环(例如i += 2)。

缺点:仅适用于顺序随机访问容器(vectorarraydeque),不适用于list,{ {1}}或关联容器。循环控制也有点冗长(init,check,increment)。人们需要了解C ++中基于0的索引。

2)基于迭代器的迭代

forward_list

优点:更通用,适用于所有容器(即使是新的无序关联容器,也可以使用不同的步幅(例如for (auto it = v.begin(); it != v.end(); ++it) { // if the current index is needed: auto i = std::distance(v.begin(), it); // access element as *it // any code including continue, break, return } );

缺点:需要额外的工作来获取当前元素的索引(可以是列表或forward_list的O(N))。同样,循环控制有点冗长(init,check,increment)。

3)STL for_each算法+ lambda

std::advance(it, 2)

优点:与2相同,加上环路控制的小幅减少(无检查和增量),这可以大大降低您的错误率(错误的初始化,检查或增加,逐个错误) )。

缺点:与显式迭代器循环相同,加上循环中流控制的限制可能性(不能使用continue,break或return),并且没有不同步幅的选项(除非你使用迭代器适配器,重载std::for_each(v.begin(), v.end(), [](T const& elem) { // if the current index is needed: auto i = &elem - &v[0]; // cannot continue, break or return out of the loop }); )。

4)range-for循环

operator++

优点:非常紧凑的循环控制,可直接访问当前元素。

缺点:获取索引的额外语句。不能使用不同的步伐。

使用什么?

对于迭代for (auto& elem: v) { // if the current index is needed: auto i = &elem - &v[0]; // any code including continue, break, return } 的特定示例:如果您确实需要索引(例如访问上一个或下一个元素,在循环内打印/记录索引等)或者您需要一个不同于1的步幅,然后我会去显式索引循环,否则我会去range-for循环。

对于泛型容器的通用算法,我会选择显式迭代器循环,除非代码在循环中不包含流控制并且需要步幅1,在这种情况下我会选择STL std::vector + a拉姆达。

答案 1 :(得分:9)

迭代器使您的代码更通用。
每个标准库容器都提供一个迭代器,因此如果您将来更改容器类,则循环不会受到影响。

答案 2 :(得分:7)

迭代器是operator[]的首选。 C ++ 11提供std::begin()std::end()函数。

由于您的代码仅使用std::vector,我不能说这两个代码存在很大差异,但operator []可能无法按您的意图运行。例如,如果您使用map,operator[]将在未找到的情况下插入元素。

此外,通过使用iterator,您的代码在容器之间变得更加便携。如果您使用迭代器,则可以自由地将容器从std::vector切换到std::list或其他容器而不会发生太大变化,这样的规则不适用于operator[]

答案 3 :(得分:5)

使用向量迭代器不提供任何真正的优势。语法更加丑陋,键入的时间更长,更难以阅读。

使用迭代器迭代向量并不快,并且不安全(实际上,如果使用迭代器在迭代期间可能调整大小,则会给您带来很大的麻烦)。

在以后更改容器类型时使用通用循环的想法在实际情况下也大多是无意义的。不幸的是,没有严格打字推断的严格类型语言的黑暗面(现在使用C ++ 11稍微好一点)就是你需要说明每一步的所有内容类型。如果你以后改变主意,你仍然需要四处走动并改变一切。此外,不同的容器具有非常不同的权衡,并且不经常发生更改容器类型。

在编写模板代码时,如果可能的泛型应该保留迭代的唯一情况,但是(我希望你)并不是最常见的情况。

显式索引循环中唯一存在的问题是size返回无符号值(C ++的设计错误),有符号和无符号之间的比较是危险和令人惊讶的,因此最好避免。如果你使用一个合适的编译器并启用了警告,那么就应该对它进行诊断。

请注意,解决方案不是使用unsiged作为索引,因为无符号值之间的算术也显然不合逻辑(它是模运算,x-1可能大于x)。相反,您应该在使用之前将大小转换为整数。 只有当您正在使用16位C ++实现(16 bit was the reason for having unsigned values in sizes)时,可能才能使用无符号大小和索引(对您编写的每个表达式给予很多关注)。

作为一个典型的错误,无符号大小可能会引入考虑:

void drawPolyline(const std::vector<P2d>& points)
{
    for (int i=0; i<points.size()-1; i++)
        drawLine(points[i], points[i+1]);
}

此处存在错误,因为如果您传递空的points向量,则值points.size()-1将是一个巨大的正数,使您循环进入段错误。 工作解决方案可能是

for (int i=1; i<points.size(); i++)
    drawLine(points[i - 1], points[i]);

但我个人更喜欢始终使用unsinged删除int(v.size()) -

在这种情况下使用迭代器的丑陋留给了读者。

答案 4 :(得分:4)

它总取决于你需要什么。

当您需要直接访问向量中的元素时(当您需要索引向量中的特定元素时),您应该使用operator[]。在迭代器上使用它没有任何问题。但是,您必须自己决定哪个(operator[]或迭代器)最适合您的需求。

使用迭代器可以切换到其他容器类型,而无需对代码进行太多更改。换句话说,使用迭代器会使您的代码更通用,并且不依赖于特定类型的容器。

答案 5 :(得分:1)

通过根据迭代器编写客户端代码,您可以完全抽象出容器。

考虑以下代码:

class ExpressionParser // some generic arbitrary expression parser
{
public:
    template<typename It>
    void parse(It begin, const It end)
    {
        using namespace std;
        using namespace std::placeholders;
        for_each(begin, end, 
            bind(&ExpressionParser::process_next, this, _1);
    }
    // process next char in a stream (defined elsewhere)
    void process_next(char c);
};

客户代码:

ExpressionParser p;

std::string expression("SUM(A) FOR A in [1, 2, 3, 4]");
p.parse(expression.begin(), expression.end());

std::istringstream file("expression.txt");
p.parse(std::istringstream<char>(file), std::istringstream<char>());

char expr[] = "[12a^2 + 13a - 5] with a=108";
p.parse(std::begin(expr), std::end(expr));

编辑:考虑您的原始代码示例,使用以下方法实现:

using namespace std;

vector<int> myIntVector;
// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);

copy(myIntVector.begin(), myIntVector.end(), 
    std::ostream_iterator<int>(cout, " "));

答案 6 :(得分:0)

关于迭代器的好处是稍后如果你想将矢量切换到另一个STD容器。然后forloop仍然可以工作。

答案 7 :(得分:-1)

速度问题。使用迭代器可以更快地访问元素。这里回答了类似的问题:

What's faster, iterating an STL vector with vector::iterator or with at()?

编辑: 访问速度因每个cpu和编译器而异[/ p>