? ? ? ?////// ? ? 歡迎來到 aramae 的博客,愿 Bug 遠離,好運常伴! ?//////
博主的Gitee地址:阿拉美 (aramae) - Gitee.com
![]()
時代不會辜負長期主義者,愿每一個努力的人都能達到理想的彼岸。
- 1. list的介紹及使用
- 2. list的深度剖析及模擬實現
- 3. list與vector的對比? ?
引言:?本章學習STL中的List容器,包括 list 的介紹及使用,深度剖析及模擬實現并補充和 vector的差異。
1. list的介紹及使用
1.1 list的介紹
- 1. list是可以在常數范圍內在任意位置進行插入和刪除的序列式容器,并且該容器可以前后雙向迭代。
- 2. list的底層是雙向鏈表結構,雙向鏈表中每個元素存儲在互不相關的獨立節點中,在節點中通過指針指向 其前一個元素和后一個元素。
- 3. list與forward_list非常相似:最主要的不同在于forward_list是單鏈表,只能朝前迭代,已讓其更簡單高效。
- 4. 與其他的序列式容器相比(array,vector,deque),list通常在任意位置進行插入、移除元素的執行效率 更好。
- 5. 與其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的隨機訪問,比如:要訪問list 的第6個元素,必須從已知的位置(比如頭部或者尾部)迭代到該位置,在這段位置上迭代需要線性的時間 開銷;list還需要一些額外的空間,以保存每個節點的相關聯信息(對于存儲類型較小元素的大list來說這 可能是一個重要的因素)
https://cplusplus.com/reference/list/list/?kw=listhttps://cplusplus.com/reference/list/list/?kw=list
1.2 list的使用?
list中的接口比較多,此處類似,只需要掌握如何正確的使用,然后再去深入研究背后的原理,已達到可擴展 的能力。以下為list中一些常見的重要接口。
1.2.1 list的構造
?list的構造使用代碼演示:
#include <iostream>
#include <list>
#include <vector>
using namespace std;// 打印 list 內容的函數
template <typename T>
void printList(const list<T>& lst, const string& desc) {cout << desc << ": ";for (const auto& elem : lst) {cout << elem << " ";}cout << endl;
}int main() {// 1. 默認構造函數 list()list<int> lst1;lst1.push_back(1);lst1.push_back(2);printList(lst1, "1. 默認構造后添加元素");// 2. 構造包含 n 個 val 的 list:list (size_type n, const value_type& val)list<int> lst2(5, 10); // 構造包含 5 個 10 的 listprintList(lst2, "2. 構造 5 個 10 的 list");// 3. 拷貝構造函數 list (const list& x)list<int> lst3(lst2); // 用 lst2 拷貝構造 lst3printList(lst3, "3. 拷貝 lst2 構造 lst3");// 4. 范圍構造函數 list (InputIterator first, InputIterator last)vector<int> vec = {100, 200, 300};list<int> lst4(vec.begin(), vec.end()); // 用 vector 的 [begin, end) 范圍構造printList(lst4, "4. 用 vector 范圍構造 lst4");// 也可以用 list 自身的迭代器范圍構造list<int> lst5(lst4.begin(), lst4.end());printList(lst5, "4. 用 lst4 范圍構造 lst5");return 0;
}
1.2.2 list iterator的使用
?將迭代器理解成一個指針,該指針指向list中的某個節點。
?list的迭代器使用代碼演示:
#include <iostream>
#include <list>
#include <string>
using namespace std;// 打印 list 內容(普通迭代器遍歷)
template <typename T>
void printListNormal(const list<T>& lst, const string& desc) {cout << desc << ": ";// begin 返回指向第一個元素的迭代器,end 返回指向最后一個元素下一個位置的迭代器for (auto it = lst.begin(); it != lst.end(); ++it) {cout << *it << " ";}cout << endl;
}// 打印 list 內容(反向迭代器遍歷)
template <typename T>
void printListReverse(const list<T>& lst, const string& desc) {cout << desc << ": ";// rbegin 返回指向最后一個元素的反向迭代器(對應普通迭代器的 end 前一個位置)// rend 返回指向第一個元素前一個位置的反向迭代器(對應普通迭代器的 begin 位置)for (auto it = lst.rbegin(); it != lst.rend(); ++it) {cout << *it << " ";}cout << endl;
}// 演示普通迭代器修改元素(需要非 const list)
template <typename T>
void modifyWithIterator(list<T>& lst) {cout << "嘗試用普通迭代器修改元素:" << endl;for (auto it = lst.begin(); it != lst.end(); ++it) {// 通過解引用迭代器修改元素值(需確保 list 非 const)*it *= 2; }
}int main() {list<int> myList = {1, 2, 3, 4, 5};// 1. 普通迭代器遍歷(begin + end)printListNormal(myList, "普通迭代器遍歷初始 list");// 2. 反向迭代器遍歷(rbegin + rend)printListReverse(myList, "反向迭代器遍歷初始 list");// 3. 用普通迭代器修改元素modifyWithIterator(myList);printListNormal(myList, "修改后的 list(普通迭代器遍歷)");printListReverse(myList, "修改后的 list(反向迭代器遍歷)");// 4. 結合 const list 演示(只能讀,不能通過迭代器修改)const list<int> constList = {10, 20, 30};cout << "const list 遍歷(普通迭代器):";for (auto it = constList.begin(); it != constList.end(); ++it) {cout << *it << " ";}cout << endl;return 0;
}
1.2.3 list capacity
empty
:判斷?list
?是否為空,返回?bool
?類型結果(空為?true
,非空為?false
?)。size
:獲取?list
?中有效元素(節點)的數量,返回?size_type
?類型值 。
代碼演示:
#include <iostream>
#include <list>int main() {// 創建一個空的 liststd::list<int> myList;// 使用 empty() 檢查是否為空std::cout << "myList 是否為空? " << (myList.empty() ? "是" : "否") << std::endl;// 使用 size() 獲取元素數量std::cout << "myList 的元素數量: " << myList.size() << std::endl;// 添加元素myList.push_back(10);myList.push_back(20);myList.push_back(30);// 再次檢查狀態std::cout << "\n添加元素后:" << std::endl;std::cout << "myList 是否為空? " << (myList.empty() ? "是" : "否") << std::endl;std::cout << "myList 的元素數量: " << myList.size() << std::endl;// 清空 listmyList.clear();// 最后一次檢查std::cout << "\n清空后:" << std::endl;std::cout << "myList 是否為空? " << (myList.empty() ? "是" : "否") << std::endl;std::cout << "myList 的元素數量: " << myList.size() << std::endl;return 0;
}
?1.2.4 list element access
front()
:返回第一個節點的值的引用,可直接讀取或修改(需確保?list
?非空,否則行為未定義)。back()
:返回最后一個節點的值的引用,同理需確保?list
?非空。
?代碼演示:
#include <iostream>
#include <list>
using namespace std;int main() {// 1. 初始化一個非空 listlist<int> myList = {10, 20, 30};// 2. front:訪問并輸出第一個節點的值cout << "第一個節點的值(front):" << myList.front() << endl;// 3. back:訪問并輸出最后一個節點的值cout << "最后一個節點的值(back):" << myList.back() << endl;// 4. 通過 front/back 修改首尾元素(利用引用特性)myList.front() = 100; // 修改第一個節點為 100myList.back() = 300; // 修改最后一個節點為 300cout << "修改后 list 的元素:";for (int num : myList) {cout << num << " ";}cout << endl;// 5. 測試空 list(危險操作,實際開發需避免)list<int> emptyList;// 以下兩行代碼會導致未定義行為(空容器訪問 front/back)// cout << emptyList.front(); // cout << emptyList.back(); // 正確做法:先判斷 empty()if (!emptyList.empty()) {cout << emptyList.front();} else {cout << "list 為空,無法訪問 front/back" << endl;}return 0;
}
1.2.5 list modifiers
1.push_front
?/?pop_front
:
push_front
?在鏈表頭部快速插入元素(雙向鏈表結構保證?O(1)?時間復雜度)。pop_front
?刪除頭部元素,同樣是?O(1)?復雜度。2.?
push_back
?/?pop_back
:
push_back
?在鏈表尾部插入元素,O(1)?時間。pop_back
?刪除尾部元素,O(1)?時間。
3.insert
:
- 需要傳入迭代器指定位置,在該位置前插入新元素。
- 由于是鏈表結構,插入操作僅需調整指針,O(1)?時間(找到位置的遍歷是?O(n),但插入本身是?O(1))。
4. erase
:
- 傳入迭代器指定要刪除的位置,刪除該元素并返回下一個位置的迭代器(示例中簡化處理,直接移動迭代器)。
- 同樣利用鏈表結構,刪除操作?O(1)?時間(遍歷找位置是?O(n),刪除本身?O(1))。
5. swap
:
- 交換兩個?
list
?的內部數據,時間復雜度?O(1)(僅交換鏈表頭指針等少量數據)。
6. clear
:
- 清空鏈表所有元素,釋放內存,所有迭代器失效。
list的插入和刪除使用代碼演示 :
#include <iostream>
#include <list>
using namespace std;// 打印 list 內容的函數
template <typename T>
void printList(const list<T>& lst, const string& desc) {cout << desc << ": ";for (const auto& elem : lst) {cout << elem << " ";}cout << endl;
}int main() {list<int> myList = {10, 20, 30};// 1. push_front:在首元素前插入myList.push_front(5);printList(myList, "push_front(5) 后"); // 輸出: 5 10 20 30 // 2. pop_front:刪除第一個元素myList.pop_front();printList(myList, "pop_front() 后"); // 輸出: 10 20 30 // 3. push_back:在尾部插入myList.push_back(40);printList(myList, "push_back(40) 后"); // 輸出: 10 20 30 40 // 4. pop_back:刪除最后一個元素myList.pop_back();printList(myList, "pop_back() 后"); // 輸出: 10 20 30 // 5. insert:在指定位置插入auto it = myList.begin();advance(it, 1); // 迭代器移動到第 2 個元素位置(值為 20)myList.insert(it, 15); printList(myList, "insert 后"); // 輸出: 10 15 20 30 // 6. erase:刪除指定位置元素it = myList.begin();advance(it, 2); // 迭代器移動到第 3 個元素位置(值為 20)myList.erase(it); printList(myList, "erase 后"); // 輸出: 10 15 30 // 7. swap:交換兩個 list 的元素list<int> anotherList = {100, 200};myList.swap(anotherList);printList(myList, "swap 后 myList"); // 輸出: 100 200 printList(anotherList, "swap 后 anotherList"); // 輸出: 10 15 30 // 8. clear:清空 listmyList.clear();printList(myList, "clear 后 myList"); // 輸出: (空)return 0;
}
1.2.6 list的迭代器失效
前面說過,此處大家可將迭代器暫時理解成類似于指針,迭代器失效即迭代器所指向的節點的無效,即該節點被刪除了。因為list的底層結構為帶頭結點的雙向循環鏈表,因此在list中進行插入時是不會導致list的迭代器失效的,只有在刪除時才會失效,并且失效的只是指向被刪除節點的迭代器,其他迭代器不會受到影響。
下面是幾個?
std::list
?迭代器失效的典型例子及分析:例子 1:刪除操作導致迭代器失效
#include <iostream> #include <list>int main() {std::list<int> myList = {1, 2, 3, 4, 5};auto it = myList.begin();std::advance(it, 2); // 讓 it 指向值為 3 的元素// 錯誤做法:刪除元素后繼續使用原迭代器myList.erase(it); // 下面這行代碼會導致未定義行為,因為 it 已經失效// std::cout << *it << std::endl; // 正確做法:使用 erase 的返回值更新迭代器auto itCorrect = myList.begin();std::advance(itCorrect, 2); itCorrect = myList.erase(itCorrect);if (itCorrect != myList.end()) {std::cout << "正確處理后下一個元素的值: " << *itCorrect << std::endl;}return 0; }
解釋:在?
std::list
?中調用?erase
?刪除元素時,指向被刪除元素的迭代器會失效。正確的做法是使用?erase
?函數返回的迭代器(指向下一個有效元素)來更新原來的迭代器。例子 2:清空容器導致迭代器失效
#include <iostream> #include <list>int main() {std::list<int> myList = {1, 2, 3};auto it = myList.begin();myList.clear();// 以下操作會導致未定義行為,因為 myList 已經清空,所有迭代器都失效了// std::cout << *it << std::endl; return 0; }
解釋:當調用?
clear
?函數清空?std::list
?時,所有指向該容器元素的迭代器都會失效,此時再使用這些迭代器訪問元素就會產生未定義行為。例子 3:交換容器導致迭代器失效(相對原容器)
#include <iostream> #include <list>int main() {std::list<int> list1 = {1, 2, 3};std::list<int> list2 = {4, 5, 6};auto it = list1.begin();list1.swap(list2);// 此時 it 仍然指向原來 list1 中的某個元素,但該元素現在屬于 list2 了// 以下操作雖然不會崩潰,但不符合預期,因為 it 不再是 list1 的有效迭代器// std::cout << *it << std::endl; return 0; }
解釋:調用?
swap
?函數交換兩個?std::list
?時,迭代器所指向的元素雖然沒有被銷毀,但所屬的容器發生了變化,對于原容器來說,原來的迭代器就失效了。通過這些例子可以看出,在對?
std::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
要模擬實現list,必須要熟悉list的底層結構以及其接口的含義,通過上面的學習,這些內容已基本掌握,現在我們來模擬實現list。
list.h
#include<assert.h>namespace aramae
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;list_node(const T& val = T()):_next(nullptr), _prev(nullptr), _val(val){}};// typedef __list_iterator<T, T&, T*> iterator;// typedef __list_iterator<T, const T&, const T*> const_iterator;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->_val;}Ptr operator->(){return &_node->_val;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const self& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}};/*template<class T>struct __list_const_iterator{typedef list_node<T> Node;Node* _node;__list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_val;}__list_const_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_const_iterator<T> operator++(int){__list_const_iterator<T> tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const __list_const_iterator<T>& it){return _node != it._node;}bool operator==(const __list_const_iterator<T>& it){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;// 如何設計const迭代器?iterator begin(){//return _head->_next;return iterator(_head->_next);}iterator end(){return _head;//return iterator(_head);}const_iterator begin() const{//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{return _head;//return const_iterator(_head);}void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}list(){empty_init();}// lt2(lt1)list(const list<T>& lt)//list(const list& lt){empty_init();for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt)//list& operator=(list lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}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());}// pos位置之前插入iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;}size_t size(){/*size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;*/return _size;}private:Node* _head;size_t _size;};void Print(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){// (*it) += 1;cout << *it << " ";++it;}cout << endl;}void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){(*it) += 1;cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;Print(lt);}struct A{A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;};void test_list2(){list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;++it;}cout << endl;}void test_list3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_front(5);lt.push_front(6);lt.push_front(7);lt.push_front(8);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt){cout << e << " ";}cout << endl;lt.clear();lt.push_back(10);lt.push_back(20);lt.push_back(30);lt.push_back(40);for (auto e : lt){cout << e << " ";}cout << endl;cout << lt.size() << endl;}void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt1(lt);for (auto e : lt1){cout << e << " ";}cout << endl;list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);lt2.push_back(40);for (auto e : lt2){cout << e << " ";}cout << endl;lt1 = lt2;for (auto e : lt1){cout << e << " ";}cout << endl;}
}
2.2 list的反向迭代器
通過前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭代器的實現可以借助正向迭代器,即:反向迭代器內部可以包含一個正向迭代器,對正向迭代器的接口進行包裝即可。
template<class Iterator>
class ReverseListIterator
{// 注意:此處typename的作用是明確告訴編譯器,Ref是Iterator類中的類型,而不是靜態成員變量// 否則編譯器編譯時就不知道Ref是Iterator中的類型還是靜態成員變量// 因為靜態成員變量也是按照 類名::靜態成員變量名 的方式訪問的
public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;
public://////////////////////////////////////////////// 構造ReverseListIterator(Iterator it): _it(it){}//////////////////////////////////////////////// 具有指針類似行為Ref operator*(){Iterator temp(_it);--temp;return *temp;}Ptr operator->(){ return &(operator*());}//////////////////////////////////////////////// 迭代器支持移動Self& operator++(){--_it;return *this;}Self operator++(int){Self temp(*this);--_it;return temp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self temp(*this);++_it;return temp;}//////////////////////////////////////////////// 迭代器支持比較bool operator!=(const Self& l)const{ return _it != l._it;}bool operator==(const Self& l)const{ return _it != l._it;}Iterator _it;
};
3. list與vector的對比
vector與list都是STL中非常重要的序列式容器,由于兩個容器的底層結構不同,導致其特性以及應用場景不 同,其主要不同如下:
#include <iostream>
#include <list>
#include <vector>int main() {// list 示例std::list<int> myList;myList.push_back(1); // 在尾部插入元素myList.push_front(0); // 在頭部插入元素// 遍歷 liststd::cout << "List elements: ";for (const auto& element : myList) {std::cout << element << " ";}std::cout << std::endl;// vector 示例std::vector<int> myVector;myVector.push_back(10); // 在尾部插入元素myVector.push_back(20);// 通過下標訪問 vector 元素std::cout << "Vector element at index 0: " << myVector[0] << std::endl;// 遍歷 vectorstd::cout << "Vector elements: ";for (const auto& element : myVector) {std::cout << element << " ";}std::cout << std::endl;return 0;
}
結語:感謝相遇
/// 高山仰止,景行行止。雖不能至,心向往之 ///