C++顺序容器删除元素时的一个小陷阱(C++ primer第四版习题9.26

C++ 中删除顺序容器的操作有以下几种:

c.erase(p) 删除迭代器p所指向的元素,返回一个迭代器,它指向被删除元素后面的元素

c.erase(b,e) 删除迭代器b和e所标记的范围内的所有元素,返回一个迭代器,指向被删除元素段后面的元素。

c.clear() 删除容器c内的所有元素,返回void

c.pop_back() 删除容器c的最后一个元素,返回void

c.pop_front() 删除容器c的第一个元素,返回void,该操作只适用于list和deque容器。

需要注意的是,erase操作不会检查它的参数,所以必须确保用作参数的迭代器是有效的。

下面看题:

假设有如下ia的定义,将ia复制到一个vector容器和一个list容器中。使用单个迭代器参数版本的erase函数将list容器中的奇数值元素删除掉,然后将vector容器中的偶数值元素删除掉。

int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };

我觉得很容易,代码如下:

#include <iostream>#include <vector>#include <list>using namespace std;int main(){int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };vector<int> ivec(11);list<int> ilist(11);//复制过程vector<int>::iterator av = ivec.begin();list<int>::iterator al = ilist.begin();for (int i = 0;i < 11;i++) {*av = ia[i];*al = ia[i];av++;al++;}//删除部分vector<int>::iterator av1 = ivec.begin(); for (int i = 0;i < 11;i++) {if (i % 2 != 0)ivec.erase(av1);av1++;}list<int>::iterator al1 = ilist.begin();for (int i = 0;i < 11;i++) {if (i % 2 == 0)ilist.erase(al1);al1++;}//显示部分 cout << "vector: " << endl;for (vector<int>::iterator a = ivec.begin();a != ivec.end();a++)cout << *a << " ";cout << endl;cout << "list: " << endl;for (list<int>::iterator a = ilist.begin();a != ilist.end();a++)cout << *a << " ";cout << endl;return 0;}运行,结果不对。在linux下出现的问题是:Segmentation fualt (core dumped)

后来我想,肯定是在删除元素的时候,在迭代器的有效范围之外。

我们注意到删除部分:

//删除部分vector<int>::iterator av1 = ivec.begin(); for (int i = 0;i < 11;i++) {if (i % 2 != 0)ivec.erase(av1);av1++;}当删除一个元素时,也就是执行一次ivec.erase(av1);此时迭代器会指向哪儿呢?该操作的返回值是指向被删除元素后一个元素的迭代器,那么我想,删除一个元素之后,当前迭代器指向的也是下一个元素,这时再执行av1++;就使得在一个循环之内迭代器往后移了两位,所以最后会超出迭代器有效范围。

修改代码如下:

#include <iostream>#include <vector>#include <list>using namespace std;int main(){int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };vector<int> ivec(11);list<int> ilist(11);//复制过程vector<int>::iterator av = ivec.begin();list<int>::iterator al = ilist.begin();for (int i = 0;i < 11;i++) {*av = ia[i];*al = ia[i];av++;al++;}//删除部分vector<int>::iterator av1 = ivec.begin(); for (int i = 0;i < 11;i++) {if (i % 2 != 0)ivec.erase(av1);elseav1++;}list<int>::iterator al1 = ilist.begin();for (int i = 0;i < 11;i++) {if (i % 2 != 0)ilist.erase(al1);elseal1++;}//显示部分 cout << "vector: " << endl;for (vector<int>::iterator a = ivec.begin();a != ivec.end();a++)cout << *a << " ";cout << endl;cout << "list: " << endl;for (list<int>::iterator a = ilist.begin();a != ilist.end();a++)cout << *a << " ";cout << endl;return 0;}如我所料,vector部分显示正常,但是list又有问题。在网上找了一下,因为vector容器的元素是连续排列的,就像数组那样,所以可以用下标访问,而且迭代器可以使用类似p+n这样的运算,p是迭代器,n是常数,因为连续排列意味着知道其中一个元素的地址,就知道了所有元素的地址,每两个相邻的地址之间总是相差一个元素的长度大小。而list却不一样,它类似于链表,两个相邻元素之间地址相差多少根本不知道,因为它不是连续排列的,也因此我们没办法使用p+n这类的运算。在使用erase操作的时候,list每删除一个元素,迭代器就失效了,所以在删除时必须记录下一个元素的地址,这样才能保证程序的正常运行,而vector则不需要。

修改之后,代码如下:

#include <iostream>#include <vector>#include <list>using namespace std;int main(){int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };vector<int> ivec(11);list<int> ilist(11);//复制过程vector<int>::iterator av = ivec.begin();list<int>::iterator al = ilist.begin();for (int i = 0;i < 11;i++) {*av = ia[i];*al = ia[i];av++;al++;}//删除部分vector<int>::iterator av1 = ivec.begin(); for (int i = 0;i < 11;i++) {if (i % 2 != 0)ivec.erase(av1);elseav1++;}list<int>::iterator al1 = ilist.begin();for (int i = 0;i < 11;i++) {if (i % 2 == 0)al1 = ilist.erase(al1);elseal1++;}//显示部分 cout << "vector: " << endl;for (vector<int>::iterator a = ivec.begin();a != ivec.end();a++)cout << *a << " ";cout << endl;cout << "list: " << endl;for (list<int>::iterator a = ilist.begin();a != ilist.end();a++)cout << *a << " ";cout << endl;return 0;}总结:

1.要理解不同容器的内部构造到底是什么样子的,,而不是单纯的记忆个容器有哪些操作。

2.时刻记得迭代器是可能失效的,并且可能超出有效范围。

3.光想是没用的,一定要落实到每一行代码,哪怕觉得很简单。

思念是一种幸福的忧伤,是一种甜蜜的惆怅,是一种温馨的痛苦;

C++顺序容器删除元素时的一个小陷阱(C++ primer第四版习题9.26

相关文章:

你感兴趣的文章:

标签云: