前言
list類是STL中封裝的鏈表模板類,並且底層實現是以帶頭雙向鏈表作爲基礎進行封裝的,甚至一些 STL 版本中(比如 SGI STL),list容器的底層實現使用的是帶頭雙向循環鏈表
list 容器中各個元素的前後順序是靠指針來維繫的,每個元素都配備了兩個指針,分別指向它的前一個元素和後一個元素,list容器中的元素還可以分散存儲在內存空間裏(邏輯結構連續,物理結構分散)。
- list的優點:
1.在任意位置插入刪除數據的效率高
2.不存在擴容的問題(拷貝數據、釋放空間的代價)
2.list支持前後雙向迭代
- list的缺點:
1.不支持下標的隨機訪問
2.list需要額外的空間保存每個結點的相關聯信息
3.底層空間不連續可能會導致空間利用率低
- 應用場景:
應用於有大量插入和刪除操作,且不用關心隨機訪問時間複雜度不友好的問題的場景。
一:標準庫中的list類
list接口的使用與vector接口的使用非常的類似,這裏我們就不做過多的介紹。
vector類接口的使用請參照我的另一篇博客:STL容器_vector類
1.1 list類的迭代器失效問題
迭代器失效即迭代器所指向的結點無效,即該節點被刪除了。
因爲list的底層結構爲帶頭結點的雙向循環鏈表,因此在list中進行插入數據時是不會導致list的迭代器失效的(不需要挪動數據和擴容),只有在刪除時纔會失效,並且失效的只是指向被刪除節點的迭代器,其他迭代器不會受到影響。
舉個例子:刪除list中的所有偶數
#include<iostream>
#include<list>
using namespace std;
int main(){
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
list<int>::iterator it = lt.begin();
while (it != lt.end()){
if (*it % 2 == 0){
// lt.erase(it):會造成迭代器失效
it = lt.erase(it);
}
else{
it++;
}
}
for (const auto& e : lt){
cout << e << " ";
}
cout << endl;
}
輸出結果:1 3 5
二:list類的模擬實現
在我們瞭解了list類的接口使用和底層結構的相關知識之後,下面我們來模擬實現一個list類。
2.1 模擬實現list
#include<assert.h>
namespace WJL{
// 1.List的結點類
template<class T>
struct _List_node{
_List_node<T>* _next;
_List_node<T>* _prev;
T _data;
_List_node(const T& x = T())
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{}
};
// 2.List類的迭代器
template<class T, class Ref, class Ptr>
struct _List_iterator{
typedef _List_node<T> Node;
typedef _List_iterator<T, Ref, Ptr> Self;
Node* _node;
_List_iterator(Node* node)
:_node(node)
{}
// 解引用
Ref operator*(){
return _node->_data;
}
// operator++(++it)
Self& operator++(){
_node = _node->_next;
return *this;
}
// operator++(it++)
Self operator++(int){
Self tmp(*this);
//_node = _node->_next;
++(*this);
return tmp;
}
// operator--(--it)
Self& operator--(){
_node = _node->_prev;
return *this;
}
// operator--(it--)
Self operator--(int){
Self tmp(*this);
//_node = _node->_prev;
--(*this);
return tmp;
}
// !=
bool operator!=(const Self& it){
return _node != it._node;
}
// ==
bool operator==(const Self& it){
return _node == it._node;
}
// ->
Ptr operator->(){
return &_node->_data;
}
};
// 3.List類
template<class T>
class List{
typedef _List_node<T> Node;
public:
typedef _List_iterator<T, T&, T*> iterator;
typedef _List_iterator<T, const T&, const T*> const_iterator;
iterator begin(){
return iterator(_head->_next);
}
iterator end(){
return iterator(_head);
}
// 返回值是const迭代器(不可修改)
const_iterator begin()const{
return const_iterator(_head->_next);
}
const_iterator end()const{
return const_iterator(_head);
}
// 帶頭雙向循環鏈表
List(){
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
// 深拷貝
List(const List<T>& lt){
// 頭結點
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
/*const_iterator it = lt.begin();
while (it != lt.end()){
push_back(*it);
++it;
}*/
for (const auto& e : lt){
push_back(e);
}
}
/*List<T>& operator=(const List<T>& lt){
if (this != <){
clear();
for (const auto& e : lt){
push_back(e);
}
}
return *this;
}*/
// operator現代寫法
List<T>& operator=(List<T> lt){
// lt是傳值拷貝構造出來的
swap(_head, lt._head);
// 交換之後 lt除了作用域釋放掉被賦值的空間
return *this;
}
~List(){
clear();
delete _head;
_head = nullptr;
}
void clear(){
iterator it = begin();
while (it != end()){
erase(it++);
}
}
// 尾插
void push_back(const T& x){
//Node* newnode = new Node(x);
//Node* tail = _head->_prev; // 原list中的最後一個結點
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
// 頭插
void push_front(const T& x){
insert(begin(), x);
}
// 尾刪
void pop_back(){
//erase(iterator(_head->_prev));
erase(--end());
}
// 頭刪
void pop_front(){
erase(begin());
}
// pos位置插入
void insert(iterator pos, const T& x){
// 取出pos位置結點的指針
Node* cur = pos._node;
Node* newnode = new Node(x);
Node* prev = cur->_prev;
// prev cur newnode
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
}
// pos位置刪除
void erase(iterator pos){
// 頭結點不能刪
assert(pos != end());
// 取出pos位置結點的指針
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
}
private:
Node* _head;
};
void test_list1(){
List<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
for (const auto& e : lt){
cout << e << " ";
}
cout << endl;
lt.pop_back();
List<int> lt2(lt);
List<int>::iterator it = lt2.begin();
while (it != lt2.end()){
cout << *it << " ";
++it;
}
cout << endl;
List<int> lt3;
lt3 = lt;
for (const auto& e : lt3){
cout << e << " ";
}
cout << endl;
}
}
2.2 vector和list的對比
- vector是一個可動態增長的數組
支持隨機訪問(很好的支持排序、二分查找等算法),但在頭部或中間插入和刪除數據的效率低,並且存在擴容問題。
- list是一個帶頭結點的雙向循環鏈表
不支持隨機訪問,但在任意位置插入和刪除數據的效率高,並且不存在擴容的問題。
- 小結
vector和list是兩個相輔相成、互補的容器,在使用時根據具體場景再選擇使用哪種容器。