在向量之后执行“删除迭代”模式之一时,我不明白为什么这段代码有效,或者它是否正在使用未定义的行为:
守则:
#include <vector>
#include <iostream>
int main(int argc, char* argv[], char* envz[])
{
std::vector<std::string> myVec;
myVec.push_back("1");
myVec.push_back("2");
myVec.push_back("3");
for (std::vector<std::string>::iterator i = myVec.begin();
i != myVec.end();
++i)
{
if ("1" == *i)
{
std::cout << "Erasing " << *i << std::endl;
i = myVec.erase(i);
--i;
continue;
}
std::cout << *i << std::endl;
}
return 0;
}
输出:
>g++ -g main.cpp
>./a.out
Erasing 1
2
3
问题:
考虑for循环的第一次迭代:
i
是 myVec.begin(),“指向”1
。1
被删除,i
设置为擦除元素之后的一个,即2
,现在myVec.begin()i
,所以现在它指向... myVec.begin之前的一个()??? 我很困惑为什么这似乎有效,正如输出所证明的那样,但是在减少迭代器方面感觉很可疑。如果条件为if ("2" == *i)
,则此代码很容易合理化,因为迭代器递减仍将其置于向量中的有效条目。即如果我们有条件地删除2
,i
将被设置为指向3
,但随后手动递减并因此指向1
,然后是for循环增量,设置它再次指向3
。有条理地删除最后一个元素也很容易理解。
我还有什么尝试:
这个观察结果让我假设在vector :: begin()之前递减是幂等的,所以我尝试了额外的减量,如下:
#include <vector>
#include <iostream>
int main(int argc, char* argv[], char* envz[])
{
std::vector<std::string> myVec;
myVec.push_back("1");
myVec.push_back("2");
myVec.push_back("3");
for (std::vector<std::string>::iterator i = myVec.begin();
i != myVec.end();
++i)
{
if ("1" == *i)
{
std::cout << "Erasing " << *i << std::endl;
i = myVec.erase(i);
--i;
--i; /*** I thought this would be idempotent ***/
continue;
}
std::cout << *i << std::endl;
}
return 0;
}
但这导致了一个段错误:
Erasing 1
Segmentation fault (core dumped)
有人可以解释为什么第一个代码块工作,特别是为什么删除第一个元素后的单个减量有效?
答案 0 :(得分:6)
不,您的代码有未定义的行为:如果i == myVec.begin()
,那么i = myVec.erase(i);
会再次导致i
(新值)myVec.begin()
和--i
具有未定义的行为,因为它超出了迭代器的有效范围。
如果您不想使用擦除删除习惯用法(即myVec.erase(std::remove(myVec.begin(), myVec.end(), "1"), myVec.end())
),那么手动循环变换看起来像这样:
for (auto it = myVec.begin(); it != myVec.end(); /* no increment! */) {
if (*it == "1") {
it = myVec.erase(it);
} else {
++it;
}
}
无论如何,此处和原始代码中的关键点是erase
使迭代器无效,因此在擦除后必须使用有效值重新分配迭代器。我们实现这一点归功于erase
的返回值,这正是我们需要的新的有效迭代器。
答案 1 :(得分:1)
这可能在某些编译器中有效,但在其他编译器中可能会失败(例如,编译器可能实际上在运行时检查你没有在begin()下递减并在这种情况下抛出异常 - 我相信至少有一个编译器会这样做但是不记得是哪一个。)
在这种情况下,一般模式不会在for
中增加,而是在循环中增加:
for (std::vector<std::string>::iterator i = myVec.begin();
i != myVec.end();
/* no increment here */)
{
if ("1" == *i)
{
std::cout << "Erasing " << *i << std::endl;
i = myVec.erase(i);
continue;
}
std::cout << *i << std::endl;
++i;
}
使用向量时,错误的迭代实际上可能在更多情况下起作用,但如果您尝试这样做,则会遇到非常糟糕的时间。使用std::map
或std::set
。
答案 2 :(得分:-1)
此处的关键是递减后的continue
。
通过调用它,++i
将在解除引用i
之前由循环迭代触发。