一、list的介紹及使用
1、list 的介紹
- list 是可以在常數範圍內在任意位置進行插入和刪除的序列式容器,並且該容器可以前後雙向迭代;
- list 底層時雙向鏈表結構,雙向鏈表中每個元素存儲在互不想關的獨立節點中,在節點中通過指針指向其前一個元素和後一個元素;
- list 和 forward_list 非常相似,主要不同在於 forward_list 是單鏈表,只能朝前迭代,已讓其簡單高效;
- 與其他的序列式容器相比較,list 通常在任意位置進行插入,移除元素的執行效率更好;
- 與其他的序列式容器相比,list 和 forward_list 最大缺陷就是不支持在任意位置的隨機訪問;
2、list的使用
構造函數 | 接口說明 |
list() |
構造空的list |
list(size_type n,const value_type* val = value_type()) | 構造的list中包含n個值爲val的元素 |
list(const list&x) | 拷貝構造函數 |
list(Inputlterator firs,Inputlterator last) | 用[list,last)區間中的元素構造 |
void MakeListTest() {
list<int> l1; //空
list<int> l2(4,100); //l2中放置4個值爲100的元素
list<int> l3(l2.begin(),l2.end()); //用l2中的[begin,end)區間構造l3
list<int> l4(l3); //用l3拷貝構l4
//以數組爲迭代器區間構造l5
int array[] = {9, 5, 2, 7};
list<int> l5(array,array+sizeof(array)/sizeof(int));
//使用迭代器的當時打印l5
for (list<int>::iterator it = l5.begin(); it != l5.end();++it) {
cout << *it << " ";
}
cout << endl;
for (auto x : l5)
cout << x << " ";
cout << endl;
3、list iterator的使用
此處,可以暫時把迭代器理解爲一個指針,該指針指向 list 中的某個節點;
函數聲明 | 接口說明 |
begin() |
返回第一個元素的迭代器 |
end() | 返回最後一個元素下一個位置的迭代器 |
rbegin() | 返回第一個元素的reverse_iterator,及end位置 |
rend() | 返回最後一個元素下一個位置reverse_iterator,即begin位置 |
cbegin() |
返回第一個元素的const_iterator(C++11) |
cend() |
但回最後一個元素下一個位置的const_iterator(C++11) |
crbegin() | 即credn()的位置(C++11) |
crend() | 即crbegin()的位置(C++11) |
void IteratorListTest() {
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
list<int> ll(array, array + sizeof(array) / sizeof(int));
//正向迭代器
for (list<int>::iterator it = ll.begin(); it != ll.end();++it) {
cout << *it << " ";
}
cout << endl;
//反向迭代器
for (list<int>::reverse_iterator it = ll.rbegin(); it != ll.rend();++it) {
cout << *it << " ";
}
cout << endl;
//const 正向迭代器(C++11)
auto cit = ll.cbegin();
cout << typeid(cit).name() << endl;
//const 反向迭代器(C++11)
auto crit = ll.crbegin();
cout << typeid(crit).name() << endl;
}
總結:
- begin 與 end 爲正向迭代器,對迭代器執行 ++ 操作,迭代器向後移動;
- rbegin 與 rend 爲反相迭代器,對迭代器執行 ++ 操作,迭代器向前移動;
- cbegin 與 cend 爲 const 的正向迭代器,與 begin 和 end 不同的是:該迭代器指向節點中的元素值不能修改;
- crbegin 與 crend 爲 const 的反向迭代器,與 rbegin 和 rend 不同的是:該迭代器指向節點中的元素值不能修改;
4、list capacity
函數聲明 | 接口說明 |
bool empty() const | 檢測list是否爲空,是返回true,否則返回false |
size_t size() const | 返回list中有效節點的個數 |
void ListCapacityTest() {
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> ll(array, array + sizeof(array) / sizeof(int));
//打印list 中有效節點的個數
cout << ll.size() << endl;
//檢測list 是否爲空
if (ll.empty())
cout << "empty()" << endl;
else
for (auto a : ll)
cout << a << " ";
cout << endl;
}
5、list element access
函數聲明 | 接口說明 |
reference front() | 返回list的第一個節點中值的引用 |
const_reference front() const | 返回list的第一個節點中值的const引用 |
reference back() |
返回list的最後一個節點中值的引用 |
const_reference back() const | 返回list的最後一個節點中值的const引用 |
void ListElementAccessTest() {
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> ll(array, array + sizeof(array) / sizeof(int));
for (auto a : ll)
cout << a << " ";
cout << endl;
//將list 中的第一個節點的值 和 最後一個節點的值 改爲 20;
ll.front() = 20;
ll.back() = 20;
for (auto a : ll)
cout << a << " ";
cout << endl;
const list<int> ll2(array,array+sizeof(array)/sizeof(int));
const int& ca = ll2.front();
}
6、list modifiers
函數聲明 | 接口說明 |
void push_front(const value_type& val) | 頭插 |
void pop_front() | 頭刪 |
void push_back(const value_type& val) | 尾插 |
void pop_back() | 尾刪 |
template<calss.. Args> void empalce_front(Args&&.. args)(C++11) |
在list的第一個元素前根據參數直接構造元素 |
tmplate<class.. Args> void emplace_back(Args&&.. args) (C++11) |
在list的最後一元素後位置根據參數直接構造元素 |
iterator insert(iterator pos,const value_type& val) | 在pos位置插入值爲 val 的元素 |
void insert(iterator pos,size_type n,const value_type& val) | 在pos爲插入 n 個值爲val 的元素 |
void insert(iterator pos,Inputlterator firsr,inputlterator last) | 在pos爲插入[first,last)區間中的元素 |
iterator erase(iterator pos) | 刪除pos位置的元素 |
iterator erase(iterator first,iterator last) | 刪除[first,list)中的元素 |
void swap(list& x) | 交換兩個list中的元素 |
void resize(size_type n,value_type val = value_type()) | 將list 中有效元素個數改變爲n,多出的元素用val 填充 |
void clear() | 清空list 所有元素 |
測試==》》
1、push_front / pop_front / push_back / pop_back
void ListModifiersTest() {
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> L(array, array + sizeof(array) / sizeof(int));
// push_front / pop_front / push_back / pop_back
L.push_front(6);
L.push_back(6);
Print(L);
L.pop_front();
L.pop_back();
Print(L);
}
2、empalce_back / emplace_front / emplace
void ListModifiersTest() {
//emplace_back / emplace_front / empalce
list<Date> l;
Date d(2018, 1, 1);
l.push_back(d);
l.emplace_back(2012, 12, 12);
l.emplace_front(2019, 12, 12);
}
3、insert / erase
void ListModifiersTest() {
//insert / erase
int array[] = { 1, 2, 3};
list<int> L1(array, array + sizeof(array) / sizeof(int));
//獲取鏈表的第二個結點
auto pos = ++L1.begin();
cout << *pos << endl;
//在pos的前面插入4
L1.insert(pos, 4);
Print(L1);
//在pos的前插入4個5
L1.insert(pos, 4, 5);
Print(L1);
//在pos前面插入[v.begin(),v.end())區間中的的元素
vector<int> v{9,5,2,7};
L1.insert(pos, v.begin(), v.end());
Print(L1);
//刪除pos位置的元素
L1.erase(pos);
Print(L1);
//刪除list 中[begin,end)區間的中的所有元素
L1.erase(L1.begin(), L1.end());
Print(L1);
}
4、resize / swap / clear
void Test() {
int array[] = { 1, 2, 3 };
list<int> L(array, array + sizeof(array) / sizeof(int));
Print(L);
//L的元素增加爲10個,多餘的默認值填充
L.resize(10);
Print(L);
//L的元素增加爲20個,多餘的用1填充
L.resize(20, 1);
Print(L);
//L的元素減少到5個
L.resize(5);
Print(L);
//用vector中的元素來構造list
vector<int> v{ 9,5,2,7 };
list<int> ll(v.begin(), v.end());
Print(ll);
//交換L和ll
L.swap(ll);
Print(L);
Print(ll);
//清空ll
Print(ll);
}
7、list 迭代器失效情況
迭代器失效即迭代器所指向的節點的無效,即該節點被刪除。因爲list 的底層結構均爲帶頭節點的雙向鏈表結構,因此在list 中插入時是不會導致list 的迭代器失效的,只有刪除的時候纔會失效,並且失效的只是指向被刪除節點的迭代器,其他迭代器不會受到影響;
void ListIteratorError() {
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list<int> ll(array, array + sizeof(array) / sizeof(int));
auto it = ll.begin();
while (it != ll.end()) {
ll.erase(it);
++it;
}
}
注:erase()執行後,it 所指向的節點已經刪除,因此 it 無效,在下一次使用 it 時,必須先給其賦值;
二、list 與 vector 的對比
vector 和 list 都時 STL 中非常重要的序列式容器,由於兩個容器的底層結構不同,導致其特性以及應用場景不同;
1、底層結構:
- vector:動態順序表,一段連續的空間;
- list:帶頭結點的雙向循環鏈表;
2、隨機訪問:
- vector:支持隨機訪問,訪問某個元素效率O(1);
- list:不支持隨機訪問,訪問某個元素的效率爲O(N);
3、插入和刪除:
- vector:任意位置插入和刪除效率低,需要搬移元素,時間複雜度爲O(N),插入時有可能需要擴容(開闢新空間,拷貝元素,釋放舊空間,導致效率更低);
- list:任意位置的插入和刪除效率極高,不需要搬移元素,時間複雜度爲O(1);
4、空間利用率:
- vector:底層爲連續空間,不容易造成內存碎片,空間利用率高,緩存利用率高;
- list:底層節點動態開闢,小節點容易造成內存碎片,空間利用率低,緩存利用率低;
5、迭代器:
- vector:原生態指針;
- list:對原生態指針(節點指針)進行封裝;
6、迭代器失效:
- vector:在插入元素的時候,要給所有的迭代器重新賦值,因爲插入元素有可能會導致重新擴容,致使原來的迭代器失效,刪除時,當前迭代器需要重新賦值否則會失效;
- list:插入元素不會導致迭代器的失效,刪除元素的時候,也只會導致當前的迭代器失效,其他迭代器不會受影響;
7、使用場景:
- vector:需要高效存儲,支持隨機訪問,不關新插入、刪除的效率;
- list:大量插入和刪除操作,不關心隨機訪問;
三、list 的模擬實現
有點長。。。
#include<iostream>
using namespace std;
namespace My {
template<class T>
struct ListNode{
ListNode(const T& val = T())
:_pPre(nullptr)
, _pNext(nullptr)
, _val(nullptr)
{}
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
//正向迭代器
template<class T, class Ref, class Ptr>
class ListIterator {
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
public:
ListIterator(PNode pNode = nullptr)
:_pNode(pNode)
{}
ListIterator(const Self& l)
:_pNode(l._pNode)
{}
T* operator*() {
return _pNode->_val;
}
T* operator->() {
return &(operator*());
}
Self& operator++() {
_pNode = _pNode->_pNext;
return *this;
}
Self& operator++(int) {
Self temp(*this);
_pNode = _pNode->_pNext;
return temp;
}
bool operator==(const Self& l) {
return _pNode == l._pNode;
}
bool operator!=(const Self& l) {
return _pNode != l._pNode;
}
PNode _pNode;
};
//反向迭代器
template<class T,class Ref,class Ptr,class Iterator>
class ListReverseIterator {
typedef ListReverseIterator<T, Ref, Ptr, iterator> Self;
public:
ListReverseIterator(const Iterator& it)
:_it(it)
{}
ListReverseIterator(const Self& l)
:_it(l._it)
{}
Ref operator*() {
Iterator temp = _it;
return *(--temp);
}
Ref operator->() {
return &operator*();
}
Self& operator++() {
--_it;
return *this;
}
Self operator++() {
Iterator temp(_it);
--_it;
return temp;
}
Self& operator--() {
++_it;
return *this;
}
Self operator--() {
iterator temp(_it);
++_it;
return temp;
}
bool operator!=(const Self& s) {
return _it != s._it;
}
bool operator==(const Self& s) {
return _it == s._it;
}
private:
Iterator _it;
};
template<class T>
class List {
typedef ListIterator<T, T&, T*> Iterator;
typedef ListIterator<T, const T&, const T*> ConstIterator;
typedef ListReverseIterator<T, T&, T*, Iterator> ReverseIterator;
typedef ListReverseIterator<T, const T&, const T*, const Iterator> ConstReveresIterator;
public:
//構造
List() {
CreateHead();
}
List(int n ,const T& value = T()) {
CreateHead();
for (int i = 0; i < n;++i) {
PushBack(value);
}
}
template<class Iterator>
List(Iterator first,Iterator last) {
CreateHead();
while (first!=last) {
PushBack(*first);
++first;
}
}
List(const List<T>& l) {
CreateHead();
List<T> temp(l.CBegin(), l.CEnd());
this->Swap(temp);
}
List<T>& operator=(const List<T>& l) {
if (this != &l) {
List<T> temp(l);
this->Swap(temp);
}
return *this;
}
~List() {
Clear();
delete _PHead;
_PHead = nullptr;
}
/* List Itreator */
Iterator Begin() {
return Iterator(_PHead->_pNext);
}
Iterator End() {
return Iterator(_PHead);
}
ReverseIterator RBegin() {
return ReverseIterator(End());
}
ReverseIterator REnd() {
return ReverseIterator(Begin());
}
ConstIterator CBegin() {
return ConstIterator(_PHead->_pNext);
}
ConstIterator CEnd() {
return ConstIterator(_PHead);
}
ConstReveresIterator CRBegin() {
return ConstReveresIterator(CEnd());
}
ConstReveresIterator CREnd() {
return ConstReveresIterator(CBegin());
}
/* List Capacity */
size_t Size()const {
size_t count = 0;
PNode pCur = _PHead->_pNext;
while (pCur != _PHead) {
++count;
pCur = pCur->_pNext;
}
return count;
}
bool Empty() {
return _PHead->_pNext == _PHead;
}
void Resize(size_t newSize,const T& val = T()) {
size_t oldSize = Size();
if (oldSize <= newSize) {
for (size_t i = oldSize; i < newSize; ++i)
PushBack(val);
}
else {
for (size_t i = newSize; i < oldSize; ++i)
PopBack(val);
}
}
/* List Access */
T& Front() {
return _PHead->_pNext->_val;
}
const T& Front()const {
return _PHead->_pNext->_val;
}
T& Back() {
return _PHead->_pPre->_val;
}
const T& Back() const {
return _PHead->_pPre->_val;
}
/* List Modify */
void PushBack(const T& val) {
PNode pNewNdoe = new Node(val);
pNewNdoe->_pNext = _PHead;
pNewNdoe->_pPre = _PHead->_pPre;
_PHead->_pPre = pNewNdoe;
pNewNdoe->_pPre->_pNext = pNewNdoe;
}
void PopBack() {
PNode pDel = _PHead->_pPre;
if (pDel != _PHead) {
_PHead->_pPre = pDel->_pPre;
pDel->_pPre->_pNext = _PHead;
delete pDel;
}
}
void PushFront(const T& val) {
PNode pNewNode = new Node(val);
pNewNode->_pNext = _PHead->_pNext;
pNewNode->_pPre = _PHead;
_PHead->_pNext = pNewNode;
pNewNode->_pNext->_pPre = pNewNode;
}
void PopFront() {
PNode PDel = _PHead->_pNext;
if (PDel != _PHead) {
_PHead->_pNext = PDel->_pNext;
PDel->_pNext->_pPre = _PHead;
delete PDel;
}
}
Iterator Inerst(Iterator pos,const T& val) {
PNode pNewNode = new Node(val);
PNode pCur = pos._pNode;
pNewNode->_pPre = pCur->_pPre;
pNewNode->_pNext = pCur;
pNewNode->_pPre->_pNext = pNewNode;
pCur->_pPre = pNewNode;
return iterator(pNewNode);
}
Iterator Erase(Iterator pos) {
PNode pDel = pos._pNode;
PNode pRet = pDel->_pNext;
pDel->_pPre->_pNext = pDel->_pNext;
pDel->_pNext->_pPre = pDel->_pPre;
delete pDel;
return Iterator(pRet);
}
void Clear() {
PNode pCur = _PHead->_pNext;
while (pCur != _PHead) {
_PHead->_pNext = pCur->_pNext;
delete pCur;
pCur = _PHead->_pNext;
}
_PHead->_pNext = _PHead;
_PHead->_pPre = _PHead;
}
void Swap(List<T>& l) {
swap(_PHead,l._PHead);
}
private:
void CreateHead() {
_PHead = new Node;
_PHead->_pPre = _PHead;
_PHead->_pNext = _PHead;
}
private:
PNode _PHead;
};
}