这篇文章主要讨论在使用list方法后迭代器的活动和一些注意点。
先说结论:assign(),resize(),erase()和remove()方法可能会对iterator造成影响,其他方法均不改变iterator原本指向的空间。
详细见下文
如果想在C++中使用list容器,需要有头文件
迭代器的类型声明是list
#include#include using namespace std; list
a {1, 3, 5}; list b {2, 4};//构造函数 list ::iterator ita = a.begin(); auto itb = b.begin();//声明一下我们要讨论的迭代器ita和itb,分别作用于链表a和链表b
这里auto会自动判断变量类型,所以上面代码中声明迭代器的两种方法其实是等价的。
下面我们列举几个list方法,看看这个期间iterator的变化。
1. begin()和end()ita = a.begin(); cout << *ita << endl; //输出1 ita = a.end(); cout << *ita << endl; //报错
a.begin()会使ita指向第一个元素,而a.end()会使ita指向最后一个元素的下一个位置。所以第二个 cout会报错。
如果想输出最后一个元素,可以进行如下操作
ita = a.end(); ita--; cout << *ita << endl; // 输出5
list的迭代器是支持++和--操作的(前后缀都支持)
但是不支持+和-操作,因为链表不能进行随机访问操作,只能O(n)查询。
所以想输出第5个元素,这么做是不行的:
ita = a.begin(); ita = ita + 4; // 报错 cout << *ita << endl;
得这么整:
ita = a.begin(); for (int i = 0; i < 4; ++i) ++ita; cout << *ita << endl;
想输出最后一个元素,还有下面的一种方法。
2. front()和back()cout << a.front() << endl; // 输出1 cout << a.back() << endl; // 输出5
这里a.front()和a.back()直接返回第一个元素和最后一个元素。这个例子与迭代器无关。
3. push_back()和push_front()push_back()的作用是在链表末尾加一个元素
push_front()的作用是在链表开头加一个元素
ita = a.begin(); a.push_front(999); cout << *ita << endl; // 输出1
ita原来指向存有1的内存空间,在push_front()后,ita仍然指向原来的内存块,所以输出*ita仍会输出1。
但是如果执行以下代码:
cout << ita == a.begin() << endl; // false
会得到false。因为此时的开头不是原来的1了,而是999。
for (ita = a.begin(); ita != a.end(); ++ita) cout << *ita << endl; // 输出结果为: // 999 // 1 // 3 // 5
itb = b.end(); b.pushback(itb, 888); cout << *itb << endl; // 会报错 for (itb = b.begin(); itb != b.end(); ++itb) cout << *itb << endl; // 输出结果为: // 2 // 4 // 888
即使在push_back()后itb仍然指向一个不存有元素的空间(没变),所以那一行cout会报错。
4. resize()resize()的作用是把链表长度改成指定的大小
resize()有两种使用方法,一个是扩大,还有一个是缩小
//扩大 ita = a.end(); a.resize(5, 2); // 把链表长度改为 5 ,额外多出来的部分都置为 2 // 可以省略第二个参数,那么多出来的默认置为 0 cout << *ita << endl; // 会报错
如果在resize()之前输出*ita,那么肯定会报错,但是为什么扩大之后也报错呢?
因为扩大后新申请的空间不是原来*ita指向的空间,想想链表的最后一项的next不是NULL嘛!
缩小就更不用说了,就看缩到什么程度了。如果缩小后iterator仍然在链表长度范围内,那么没事儿;但是如果超出范围了,那就报错了。
所以resize()后一定要检查一下。避免出现错误。
5. pop_back()和pop_front()pop_back()的作用是删除最后一个元素
pop_front()的作用是删除第一个元素
ita = a.begin(); a.pop_front(); cout << *ita << endl; // 报错
这个应该很明显了。但是我想输出第一个元素,我能不能这么做呢?
ita = a.begin(); a.pop_front(); ita++; cout << *ita << endl;
运行结果:在这一步会报错。
ita++; // Assertion Failed!
因为在pop_front()之后,第一个元素的内存被free(或delete)掉了。所以ita++,也就是p=p->next是没法实现的。所以会报错。
所以想实现pop_front()后输出第一个元素:
方法一:可以先移动一位
ita = a.begin(); ++ita; a.pop_front(); cout << *ita << endl;
方法二:用begin()定位
a.pop_front(); ita = a.begin(); cout<< *ita << endl;
方法三:用front()直接输出
a.pop_front(); cout << a.front() << endl;
pop_back()也同理啦!同样没法实现以下操作:
ita = a.end(); ita--; a.pop_back(); ita--; // 报错 cout << *ita << endl;6. assign()
assign()的可以认为是resize()的升级版,可以改变长度的同时重置所有元素。
用法一:assign(n, val)ita = a.end(); ita--; // 指向最后一个元素 a.assign(2, 10); // 长度变短 cout << *ita << endl; // 报错
长度变短之后,原来指向的空间没了,所以报错
ita = a.end(); a.assign(5, 10); // 变长 cout << *ita << endl; // 报错
与上面几个例子同理,这也同样行不通。
ita = a.begin(); a.assign(5, 10); cout << *ita << endl; // 输出10
扩大的过程中如果原来的内存空间存在,那么就不会新分配空间,只会改变里面的值,所以输出的不是1而是10。缩小同理。
用法二:assign(l.begin(), l.end())这里两个参数是两个迭代器,不用非得从头到尾,但是要合法。
itb = b.begin(); itb++; b.assign(a.begin(), a.end()); cout << *itb << endl; // 输出 3
可以看出这里输出的是更改后的值
itb = b.begin(); itb++; b.assign(a.begin(), a.end()); itb++; cout << *itb << endl; // 输出 5
变长之后itb++也是没问题的。但是不能在assign()之前itb++,那样会越界报错。
这里猜测assign()的原理是值赋值+如果扩大则开辟新空间。
swap()的作用是交换两个链表
ita = a.begin(); a.swap(b); cout << *ita << endl; // 输出 1
可以看出ita指向的内存空间还是原来的1,猜测swap()做的事情是交换两个头指针。
itb = b.end(); b.swap(a); cout << *itb << endl; // 会报错
这个问题这篇文章里提到过很多次了,不赘述了。
想输出修改后的值,那就重新定位就可以了。比如
ita = a.begin(); a.swap(b); ita = a.begin(); // 重新定位 cout << *ita << endl; // 输出 28. reverse()
reverse()的作用是翻转链表,原来的1,2,3变成3,2,1。
ita = a.begin(); a.reverse(); cout << *ita << endl; // 输出 1
猜测reverse()的作用是翻转next和prev指针指向的空间。
9. merge()merge()的作用是合并两个链表。不会去重。
使用merge()的前提是两个链表已经按同样的顺序排好序(升序or降序)
不会去重的意思是,比如
链表a = { 1, 2, 3, 5, 5}
链表b = { 4, 5, 5}
merge()后的链表就是 { 1, 2, 3, 4, 5, 5, 5}
下面的例子还用{1, 3, 5}和{2, 4}
ita = a.begin(); a.merge(b); cout << *ita << endl; // 输出 1
itb = b.begin(); b.merge(a); cout << *itb << endl; // 输出 2
由上面两个例子可知合并后不会重新分配空间。ita和itb仍然指向原来的内存空间。只是改变了内存空间的指向关系。
注意:a.merge(b)后b链表会变为空,所以指向b的指针会失效。
10. insert()a.insert(ita, val)后,会在ita的位置之前插入val。
ita = a.begin(); a.insert(ita, 10); cout << *ita << endl; // 输出 1 for (ita = a.begin(); ita != a.end(); ++ita) cout << *ita << endl; // 输出: // 10 // 1 // 3 // 5
这个感觉还是比较好想的。ita指向的内存空间不变。
11. erase()erase(ita)的作用是删除ita指向的内存空间的元素。
ita = a.begin(); ++ita; // 指向第二个元素 a.erase(ita); cout << *ita << endl; // 报错 for (ita = a.begin(); ita != a.end(); ++ita) cout << *ita << endl; // 输出: // 1 // 5
由此可见当erase()后,ita既不会指向前一个元素,也不会指向下一个元素。
同样,下面的操作也不合法。
ita = a.begin(); ++ita; a.erase(ita); ita++; cout << *ita << endl; //试图输出最后一个元素
当然会报错,会在倒数第二行的ita++处报错。理由在上面也阐述过。
12. remove()remove(val)的作用是删除所有值为val的结点。
如果链表a为{1,1,2,3},那么执行remove(1)后,链表a变为{2,3}
下面例子中a仍为{1,3,5}
ita = a.begin(); ita++; a.remove(1); cout << *ita << endl; // 输出 1 a.remove(3); cout << *ita << endl; // 报错
报错理由与erase()相同。
结尾先写这么些吧。第一次写这么长的文章(可能在全网来看这一点也不长),花了两天时间T^T。希望能帮助到各位。如果有错误请批评指正,感激不尽!



