接下來我會依照前面所說的一些接口以及list的結構來進行講解。
1. list_node的結構
1.1 list_node結構體
list由于其結構為雙向循環鏈表,所以我們在這里要這么初始化
_next
:指向鏈表中下一個節點的指針_prev
:指向鏈表中上一個節點的指針_val
:存儲節點的值
list_node(const T& val = T())
?用于初始化節點,將?_next
?和?_prev
?指針默認設置為?nullptr
,并將?_val
?初始化為傳入的值
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_node<T> Node;
- 將
list_node<T>
(鏈表節點類型)簡化定義為Node
- 在類內部可以直接使用
Node
來表示鏈表節點類型,無需重復寫list_node<T>
- 將
typedef _list_iterator<T, T&, T*> iterator;
- 定義了普通迭代器類型
iterator
- 這里的
_list_iterator
應該是一個迭代器模板類,三個模板參數分別表示:- 存儲的數據類型
T
- 迭代器解引用后返回的引用類型
T&
- 迭代器解引用后返回的指針類型
T*
- 存儲的數據類型
- 定義了普通迭代器類型
typedef _list_iterator<T, const T&, const T*> const_iterator;
- 定義了常量迭代器類型
const_iterator
- 與普通迭代器的區別是,解引用后返回的是
const
類型的引用和指針 - 用于對
const list
對象進行遍歷,保證不能通過迭代器修改元素值
- 定義了常量迭代器類型
class list
{public:typedef list_node<T> Node;typedef _list_iterator<T, T&, T*> iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;
};
1.2?list和list_node分離設計原因?
將list_node
結構體和list
類分離設計,是一種典型的 "數據存儲" 與 "邏輯管理" 分離的思想。
1.?職責分離,符合單一職責原則
list_node
結構體:僅負責存儲單個節點的數據和連接關系(前驅_prev
、后繼_next
指針),它的職責非常單一 —— 就是作為鏈表的 "最小數據單元"。list
類:負責管理整個鏈表的邏輯,包括節點的創建 / 銷毀、插入 / 刪除、遍歷、容量管理等操作。它不直接存儲數據,而是通過操作list_node
對象來實現鏈表的整體功能。這種分離讓代碼更清晰:節點只關心自身數據和連接,鏈表類只關心整體邏輯,便于維護和擴展。
2.?封裝性更好,隱藏實現細節
list_node
通常是list
類的內部私有類型(或通過typedef
限定在類內部使用),外部用戶不需要知道節點的具體結構(比如_prev
、_next
指針的存在)。- 用戶只需通過
list
類提供的接口(如push_back
、erase
、迭代器等)操作鏈表,無需直接接觸節點,避免了誤操作節點指針導致的內存問題(如野指針、內存泄漏)。3.?便于迭代器設計和容器適配
- 鏈表的迭代器需要通過節點的
_next
/_prev
指針實現遍歷,將節點封裝為list_node
后,迭代器可以統一通過節點指針操作,無需關心具體數據類型。- 符合 STL 容器的設計規范:STL 中所有容器都需要將 "元素存儲" 和 "容器管理" 分離,這樣才能統一迭代器接口。
list_node
可以被視為一個通用的 "雙向節點模板",如果未來需要實現其他基于雙向節點的結構(如循環鏈表、隊列等),可以復用list_node
的設計。- 當需要擴展鏈表功能時,只需修改
list
類的管理邏輯,無需改動list_node
的結構。
2. list迭代器
2.1 三個可變參數的原因
我們在這里之所以要設置三個,是因為在list的迭代器里面需要不同的類型。同時一個可變參數在一個類里面只會推導一次,所以我們在這里是需要是三個。
T
:鏈表中存儲的元素類型(如?int
、string
?等)。Ref
:迭代器解引用后返回的引用類型(普通迭代器用?T&
,常量迭代器用?const T&
)。Ptr
:迭代器通過?->
?運算符返回的指針類型(普通迭代器用?T*
,常量迭代器用?const T*
)。
template<class T,class Ref,class Ptr>
struct _list_iterator{typedef list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self;Node* _node;};
2.2 構造函數
我們在這里又會有構造函數,是因為我們在list中實際上寫了三個類,一個list_iterator,一個list_node和一個list,這三者構成了完整的list。
這個就是在給list的迭代器進行初始化。因為是通過節點去訪問所以怎么寫。
_list_iterator(Node* node):_node(node)
{}
2.3?operator*()
返回對元素的引用(Ref
),模擬指針的解引用操作(如?*p
?獲取指針指向的對象)。所以在這里要使用Ref。
Ref operator*()
{return _node->_val;
}
2.4?operator->()?
這邊的Ptr更多是為了結構體這種設計的,簡單來說這樣設計就是為了先進入結構體然后再訪問其內部成員。
那為什么要加&呢?這是因為operator要返回一個指針,然后我們通過這個指針來訪問結構體的內部成員。
Ptr operator->()
{return &_node->_val;
}
2.5 前置++
因為是前置的,所以是先++再return。
self
?是迭代器類型本身(_list_iterator<T, Ref, Ptr>
),它是一個 “指向元素的迭代器”,負責管理節點指針、提供遍歷能力。
self& operator++()
{_node = _node->_next;return *this;
}
2.6 后置++
這邊()里面的int是特殊處理,是為了和前置++構成函數重載。
又因為是后置++,所以我們讓迭代器向后走并返回走之前的值。
self operator++(int)
{self tmp(*this);_node = _node->next;return tmp;
}
2.7 前置--
因為是前置--,所以先返回上一個節點在return。
self& operator--()
{_node = _node->_prev;return *this;
}
2.8 后置--
因為是后置--,所以先保存節點在--。
int是為了和前置--構成函數重載。
self operator--(int)
{self tmp(*this);_node = _node->_prev;return tmp;
}
2.9 operator!=
這個加const是防止修改指向和被指向的內容。
因為是bool類型的,所以直接比較兩個指針的地址值是否一樣即可。
bool operator!=(const self& it) const
{return _node != it._node;
}
2.10 operator==
這個加const是防止修改指向和被指向的內容。
因為是bool類型的,所以直接比較兩個指針的地址值是否一樣即可。
bool operator==(const self& it) const
{return _node == it._node;
}