迭代器循环与索引循环
可能重复:
为什么使用迭代器而不是数组索引?
我正在评论我在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网站上拿下了这个例子。 那么你能告诉我为什么后者更糟? 最大的区别是什么?
迭代器的特别之处在于它们提供了algorithm和容器之间的粘合。 对于generics代码,build议将使用STLalgorithm(如find
, sort
, remove
, copy
等)的组合,执行您的数据结构( vector
, list
, map
等)的计算, ,并将迭代器提供给你的容器。
你特别的例子可以写成for_each
algorithm和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 }
优点 :熟悉C风格代码的人可以使用不同的步幅循环(例如i += 2
)。
缺点 :仅适用于顺序随机访问容器( vector
, array
, deque
),不适用于list
, forward_list
或关联容器。 此外,循环控制有点冗长(初始化,检查,增量)。 人们需要了解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 }
优点 :更通用,适用于所有容器(即使是新的无序关联容器,也可以使用不同的步幅(例如std::advance(it, 2)
);
缺点 :需要额外的工作来获取当前元素的索引(对于list或forward_list可能是O(N))。 同样,循环控制有点冗长(初始化,检查,增量)。
3)STL for_eachalgorithm+ 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 });
优点 :与2)相同,再加上循环控制的小减less(不检查和增加),这可以大大降低错误率(错误的初始化,检查或增量,逐个错误)。
缺点 :与显式迭代器循环相同,加上循环中stream控制受限的可能性(不能使用continue,break或return),对于不同的步长没有select(除非使用重载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 }
优点 :非常紧凑的循环控制,直接访问当前元素。
缺点 :额外的语句来获取索引。 不能使用不同的步幅。
使用什么?
对于迭代std::vector
特定示例,如果您确实需要索引(例如,访问上一个或下一个元素,打印/logging循环内的索引等),或者需要一个不同于1的跨度,那么我会去显式索引循环,否则我会去范围for循环。
对于generics容器上的genericsalgorithm,我会去显式迭代器循环,除非代码中包含循环内没有stream控制,并需要步幅1,在这种情况下,我会去的STL for_each
+ lambda。
迭代器使你的代码更通用。
每个标准库容器都提供了一个迭代器,因此如果将来更改容器类,循环将不会受到影响。
迭代器是operator[]
首选。 C ++ 11提供了std::begin()
, std::end()
函数。
由于你的代码只使用std::vector
,所以我不能说在两个代码中都有很大的区别,但是operator []
可能无法按照你的意图操作。 例如,如果你使用map, operator[]
会插入一个元素,如果没有find。
另外,通过使用iterator
您的代码在容器之间变得更加便携。 你可以自由地将容器从std::vector
切换到std::list
或其他容器,如果你使用迭代器,这个规则不适用于operator[]
而不会有太大的变化。
它总是取决于你需要什么。
当需要直接访问向量中的元素时(当需要索引向量中的特定元素时),应该使用operator[]
。 在迭代器上使用它没有任何问题。 但是,您必须自己决定哪个( operator[]
或迭代器)最适合您的需求。
使用迭代器将使您能够切换到其他容器types,而不会在代码中发生太多变化。 换句话说,使用迭代器会使你的代码更通用,并且不依赖于特定types的容器。
使用向量迭代器不提供任何真正的优势。 语法较丑,打字时间较长,难以阅读。
使用迭代器遍历一个向量不会更快,也不会更安全(实际上,如果向量在迭代过程中可能resize,使用迭代器会让你陷入困境)。
当你稍后改变容器types时,有一个通用循环的思想在实际情况下也是大部分无意义的。 不幸的是,严格键入的语言的黑暗面没有严肃的键入推理(但是现在用C ++ 11更好一点)就是你需要说明每一步中所有事物的types。 如果你稍后改变主意,你仍然需要四处走动,改变一切。 而且不同的容器有不同的权衡,容器types的改变不是经常发生的事情。
唯一可能的情况是应该保留迭代,如果可能的话generics是在编写模板代码的时候,但是(我希望对你来说)并不是最常见的情况。
在你的显式索引循环中唯一的问题是size
返回一个无符号值(C ++的devise错误),并且签名和未签名之间的比较是危险和令人惊讶的,所以最好避免。 如果你使用了一个体面的编译器并且启用了警告,那么应该有一个诊断。
请注意,解决scheme是不要使用unsiged作为索引,因为无符号值之间的算术显然也是不合逻辑的(这是模运算,并且x-1
可能比x
)。 在使用之前,您应该将大小转换为整数。 只有在使用16位C ++实现( 16位是无符号值大小的原因 )的情况下,使用无符号大小和索引(对所写的每个expression式都要十分注意) 可能是有意义的。
作为无符号大小可能引入的典型错误考虑:
void drawPolyline(const std::vector<P2d>& points) { for (int i=0; i<points.size()-1; i++) drawLine(points[i], points[i+1]); }
这里存在的错误是因为如果你通过一个空的points
vector的值points.size()-1
将是一个巨大的正数,使你循环成一个段错误。 一个可行的解决scheme可以
for (int i=1; i<points.size(); i++) drawLine(points[i - 1], points[i]);
但我个人更喜欢用int(v.size())
去除unsinged
。
在这种情况下使用迭代器的丑陋留给读者。
通过用迭代器编写客户端代码,可以完全抽象出容器。
考虑这个代码:
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, " "));
迭代器的好处在于,如果你想把你的向量切换到另外一个STD容器。 然后forloop仍然会工作。
它的速度问题。 使用迭代器更快地访问元素。 在这里回答了类似的问题:
用vector :: iterator或at()迭代一个STL向量会更快吗?
编辑:访问速度因每个CPU和编译器而异