【发布时间】:2017-06-05 13:42:46
【问题描述】:
Johannes Schaub 声称 here
总是对定义的迭代器使用前缀递增形式 你不知道。这将确保您的代码运行与 可能。
for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
/* std::cout << *it; ... */
}
为什么不先迭代它,然后开始循环(在 v.begin() + 1 处)?
【问题讨论】:
Johannes Schaub 声称 here
总是对定义的迭代器使用前缀递增形式 你不知道。这将确保您的代码运行与 可能。
for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
/* std::cout << *it; ... */
}
为什么不先迭代它,然后开始循环(在 v.begin() + 1 处)?
【问题讨论】:
为什么不先迭代它,然后开始循环(在 v.begin() + 1 处)?
迭代语句总是在每次迭代结束时执行。这与您使用的增量运算符的类型无关,或者您是否使用增量运算符。
不使用迭代语句表达式的结果,因此它对循环的行为没有影响。声明:
++it;
在功能上等同于语句:
it++;
后缀和前缀自增表达式只有在使用表达式的结果时才会有不同的行为。
为什么对迭代器使用前缀递增形式?
因为后缀操作意味着复制。复制迭代器通常至少与不复制迭代器一样慢,但可能比不复制迭代器慢。
后缀增量的典型实现:
iterator tmp(*this); // copy
++(*this); // prefix increment
return tmp; // return copy of the temporary
// (this copy can be elided by NRVO)
当不使用结果时,即使是第一个副本也可以被优化掉,但前提是该操作是内联扩展的。但这不能保证。
我不会盲目地使用“始终使用带有迭代器的前缀增量”的规则。有些算法用后缀表达起来更清楚,尽管这只是我的看法。一个适合后缀递增的算法示例:
template<class InIter, class OutIter>
OutIter copy(InIter first, InIter last, OutIter out) {
while(first != last)
*out++ = *first++;
return out;
}
【讨论】:
请注意,您的代码等效于
for(std::vector<T>::iterator it = v.begin(); it != v.end(); ) {
/* std::cout << *it; ... */
++it;
}
很明显,写++it 并不重要;或it++;。 (这也解决了你的最后一点。)
但概念上it++ 需要在其实现中存储未递增值的副本,因为这就是表达式的计算结果。
it 可能是一个大而重的对象,其中获取值副本的计算成本很高,并且您的编译器可能无法优化掉 it++ 获取的隐式值副本。
如今,对于大多数容器,如果不使用表达式的值,编译器会将可以说更清晰的it++优化为++it;即生成的代码将是相同的。
我遵循作者的建议并始终尽可能使用预增量,但我 (i) 过时并且 (ii) 知道很多专家程序员不这样做,所以它主要是个人选择。
【讨论】:
为什么不先迭代它,然后开始循环(在 v.begin() + 1 处)?
因为for loop会被解析为:
{
init_statement
while ( condition ) {
statement
iteration_expression ;
}
}
所以
for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
/* std::cout << *it; ... */
}
等价于
{
std::vector<T>::iterator it = v.begin();
while ( it != v.end() ) {
/* std::cout << *it; ... */
++it ;
}
}
这意味着它首先会在v.begin() 进行循环,然后向前推进it。前缀增量表示增加值,然后返回增加对象的引用;如您所见,在这种情况下根本没有使用返回的对象,那么++it 和it++ 将导致相同的结果。
【讨论】: