1. list的介紹及使用
1.1list介紹
List文檔介紹
1.2 list的使用
list中的接口比較多,此處類似,只需要掌握如何正確的使用,然后再去深入研究背后的原理,已 達到可擴展的能力。以下為list中一些常見的重要接口。
1.2.1 list的構造
list<int>lt1(5); 構造空的list
list<int>lt2(3, 2); 構造的list中包含n個值為val的元素
list<int>lt3(lt2); 拷貝構造函數
int arr[] = { 1,2,3,4,5 };
list<int>lt4(arr, arr + 5);用[?rst, last)區間中的元素構造list
1.2.2 list iterator的使用
lt1.begin(); 返回第一個元素的迭代器 lt1.end(); 返回最后一個元素下一個位置的迭代器lt1.rbegin(); 返回第一個元素的reverse_iterator, 即end位置lt1.rend(); 返回最后一個元素下一個位置的reverse_iterator, 即begin位置
1.2.3 list capacity
lt1.size(); 返回list中有效節點的個數lt1.empty(); 檢測list是否為空,是返回true,否則返回false
1.2.4 list element access
lt1.front(); 返回list的第一個節點中值的引用lt1.back(); 返回list的最后一個節點中值的引用
1.2.5 list modi?ers
lt1.push_back(1); 尾插lt1.push_front(1); 頭插lt1.pop_back(); 尾刪lt1.pop_front(); 頭刪lt1.insert(lt1.begin(), 1); 在list position 位置中插入值為val的元素lt1.erase(lt1.begin()); 刪除list position位置的元素lt1.swap(lt2); 交換兩個list中的元素lt1.clear(); 清空list中的有效元素
1.2.6 list的迭代器失效
迭代器失效即迭代器所指向的節點的無效,即該節點被刪除了。因為list的底層結構為帶頭結點的雙向循環鏈表,因此在list中進行插入時是不會導致list的迭代器失效的,只有在刪除時才會失效,并且失效的只是指向被刪除節點的迭代器,其他迭代器不會受到影響。
void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){// erase()函數執行后,it所指向的節點已被刪除,因此it無效,在下一次使用it時,必須先給其賦值l.erase(it);++it;}
}
// 改正
void TestListIterator()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++);//it = l.erase(it);}
}
2. list的模擬實現
2.1 模擬實現list
#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace XiaoHai
{template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){ }};template<class T, class ref, class Ptr>struct Reverse_iterator{Iterator _it;typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}Ref operator*(){iterator tmp(_it);--tmp;return *tmp;}Ptr operator->(){return &(operator*());}self operator++(int){self tmp(_it);--_it;return *tmp;}self operator--(int){self tmp(_it);++_it;return *tmp;}self operator++(){--_it;return *tmp;}self operator--(){++_it;return *tmp;}bool operator!=(const Self& s){return _it != s._it;}};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;}T* operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self& operator++(int){__list_iterator<T>tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self& operator--(int){__list_iterator<T>tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const __list_iterator<T>& it)const{return _node != it._node;}bool operator ==(const __list_iterator<T>& it)const{return !(_node != it._node);}};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_iterator begin() const{return iterator(_head->_next);}const_iterator end() const{return iterator(_head);}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}list(){empty_init();}//lt2(lt1)list(const list<T><){empty_init();for (const auto& e:lt){push_back(e);}}list(initializer_list<int>il){empty_init();for (const auto& e : il){push_back(e);}}void swap(list<T><){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt1 = lt2list<T>& operator=(const list<T>* lt){swap(lt);return *this;}/*list<T>& operator=(const list<T>* lt){if (this != <){clear();for (const auto& e : il){push_back(e);}}return *this;}*/~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;}iterator& erase(iterator pos){Node* cur = pos._node;Node* next = cur->_next;Node* prev = cur->_prev;//prev (cur) nextprev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}size_t size(){return _size;}private:Node* _head;size_t _size = 0;};
}
2.2 list的反向迭代器
通過前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++, 因此反向迭代器的實現可以借助正向迭代器,即:反向迭代器內部可以包含一個正向迭代器,對正向迭代器的接口進行包裝即可。
template<class T, class ref, class Ptr>struct Reverse_iterator{Iterator _it;typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}Ref operator*(){iterator tmp(_it);--tmp;return *tmp;}Ptr operator->(){return &(operator*());}self operator++(int){self tmp(_it);--_it;return *tmp;}self operator--(int){self tmp(_it);++_it;return *tmp;}self operator++(){--_it;return *tmp;}self operator--(){++_it;return *tmp;}bool operator!=(const Self& s){return _it != s._it;}};
3. list與vector的對比
vector與list都是STL中非常重要的序列式容器,由于兩個容器的底層結構不同,導致其特性以及 應用場景不同
vector | list | |
底層結構 | 動態順序表,一段連續空間 | 帶頭結點的雙向循環鏈表 |
隨機訪問 | 支持隨機訪問,訪問某個元素效率O(1) | 不支持隨機訪問,訪問某個元素效率O(n) |
插入和刪除 | 尾插效率高,任意位置插入和刪除效率低,需要搬移元素,時間復雜度為O(N),插入時有可能需要增容,增容: 開辟新空間,拷貝元素,釋放舊空間,導致效率更低 | 任意位置插入和刪除效率高, 不需要搬移元素,時間復雜度 為O(1) |
空間利用率 | 底層為連續空間,不容易造成內存碎片,空間利用率高,緩存利用率高 | 底層節點動態開辟,小節點容易造成內存碎片,空間利用率 低,緩存利用率低 |
迭代器 | 原生態指針 | 對原生態指針(節點指針)進行封裝 |
迭代器失效 | 插入操作如果導致底層空間重新開辟,則迭代器就會失效,刪除操作不光會導致指向被刪除元素的迭代器失效,刪除元素后面的迭代器也會失效 | 插入元素不會導致迭代器失效,刪除元素時,只會導致當前迭代器失效,其他迭代器不受影響 |
使用場景 | 需要高效存儲,支持隨機訪問,不關心插入刪除效率 | 大量插入和刪除操作,不關心隨機訪問 |