迭代器循环与索引循环

Cod*_*asy 100 c++ indexing iterator loops c++11

可能重复:
为什么使用迭代器而不是数组索引?

我正在回顾我对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
}
Run Code Online (Sandbox Code Playgroud)

比这更好:

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
}
Run Code Online (Sandbox Code Playgroud)

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

Tem*_*Rex 171

迭代器的特殊之处在于它们提供了算法和容器之间的粘合剂.对于通用代码,该建议是使用(例如STL算法的组合find,sort,remove,copy)等,其执行的是你心里有你的数据结构(计算vector,list,map等),并提供与算法迭代器进入容器.

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

1)基于索引的迭代

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

    // any code including continue, break, return
}
Run Code Online (Sandbox Code Playgroud)

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

缺点:只为顺序随机访问容器(vector,array,deque),不适合工作list,forward_list或关联容器.循环控制也有点冗长(init,check,increment).人们需要了解C++中基于0的索引.

2)基于迭代器的迭代

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
}
Run Code Online (Sandbox Code Playgroud)

优点:更通用,适用于所有容器(即使是新的无序关联容器,也可以使用不同的步幅(例如std::advance(it, 2));

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

3)STL for_each算法+ lambda

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
});
Run Code Online (Sandbox Code Playgroud)

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

缺点:与显式迭代器循环相同,加上循环中流控制的限制可能性(不能使用continue,break或return),并且没有不同步幅的选项(除非使用重载的迭代器适配器operator++).

4)range-for循环

for (auto& elem: v) {
     // if the current index is needed:
     auto i = &elem - &v[0];

    // any code including continue, break, return
}
Run Code Online (Sandbox Code Playgroud)

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

缺点:获取索引的额外声明.不能使用不同的步伐.

用什么?

对于你迭代的特定例子std::vector:如果你真的需要索引(例如访问上一个或下一个元素,在循环内打印/记录索引等)或者你需要一个不同于1的步幅,那么我会明确地去indexed-loop,否则我会选择range-for循环.

对于通用容器上的通用算法,我会选择显式迭代器循环,除非代码在循环中不包含流控制并且需要步长1,在这种情况下我会选择STL for_each+ lambda.

  • 这是一个非常有用的答案!感谢您提出这四种不同方法的利弊。一个问题:基于索引的迭代使用`i!= v.size()`进行测试。这里是否有理由使用`!=`而不是`&lt;`?我的C本能告诉我使用`i &lt;v.size()`代替。我希望任何一个都可以工作,只是我更习惯在数字`for`循环中看到`&lt;`。 (2认同)

Alo*_*ave 9

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

  • @CodingMadeEasy但并非所有容器都提供随机访问.也就是说,`std :: list`没有(也不能)有`operator []`(至少没有以任何有效的方式). (4认同)

bil*_*llz 7

迭代器是首选operator[].C++ 11提供std::begin(),std::end()功能.

由于您的代码只使用std::vector,我不能说两个代码有很大差异,但是,operator []可能无法按照您的意图运行.例如,如果使用map,operator[]则会在未找到时插入元素.

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


650*_*502 5

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

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

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

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

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

请注意,解决方案不是使用unsiged作为索引,因为无符号值之间的算术也显然是不合逻辑的(它是模运算,并且x-1可能大于x).相反,您应该在使用之前将大小转换为整数.它可以做一些有意义的使用无符号的大小和索引(支付的关注很多你写的每一个表情),只有当你在一个16位的C++实现的工作(16位是在大小为无符号值的原因).

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

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

这里存在错误,因为如果你传递一个空points向量,那么该值points.size()-1将是一个巨大的正数,使你循环进入段错误.一个有效的解决方案

for (int i=1; i<points.size(); i++)
    drawLine(points[i - 1], points[i]);
Run Code Online (Sandbox Code Playgroud)

但我个人更喜欢永远删除unsinged-ness with int(v.size()).

在这种情况下使用迭代器的丑陋留给读者练习.

  • 虚拟-1:"丑陋,更长时间打字,更难读" - > a)这是POV,b)`for(auto x:container)`?? (4认同)
  • 你会详细说明为什么`size()`是unsigned是一个设计错误?我看不出一个单一的原因`for(int i = 0; ...)`可能比`for(size_t i; ...)`更可取.我在64位系统上遇到了32位索引的问题. (3认同)
  • @ 6502:关于size_t的无符号性:不,它只是意味着我还没有听说过它.谷歌对于不同的搜索主题是相对沉默的,指向我(像你一样)到Alf的答案之一,这听起来似乎有道理,但没有被引用本身支持.我不确定为什么"从未听说过"与"我不同意"是一样的; 这是一个很大的猜测.不,纯粹的推理和深刻的C++知识是不够的; C++标准不包含这样的轶事,也不包含逻辑. (2认同)
  • 我大多同意无符号类型是不幸的,但由于它们已经被标准化库中所覆盖,我也看不到避免它们的好方法.一个"值永远不会超过`INT_MAX`的无符号类型"在我看来并不比另一方提出的更可靠,"一个有效值永远不会小于0的有符号类型".如果容器的大小大于`INT_MAX`,那么显然你不能将它转换为`int`并且代码失败.`long long`会更安全(特别是因为它最终是标准的).我永远不会创建一个含有2 ^ 63个元素的向量,但我可能会用2 ^ 31. (2认同)
  • @ 6502:对我而言,这只是意味着处理它的一种方法(使用无符号类型和0时的风险回绕)有一个更明显的问题,而另一种(将大小转换为`int`)有一个更微妙的问题.我实际上更喜欢在常见情况下出现的错误,以及逃避测试的错误.将大小转换为int的问题并不是我认为数字2 ^ 31-1"不够".如果我正在编写一些操作向量的代码,那么我想接受调用者可以创建的所有类型的值,我不想对我的API引入额外的混淆限制. (2认同)