一、list底层实现机制刨析
前面在讲 STL list 容器时提到,该容器的底层是用双向链表实现的,甚至一些 STL 版本中(比如 SGI STL),list 容器的底层实现使用的是双向循环链表
图 1 双向链表( a) )和双向循环链表( b) )
图 1 中,node 表示链表的头指针。
如图 1 所示,使用链表存储数据,并不会将它们存储到一整块连续的内存空间中。恰恰相反,各元素占用的存储空间(又称为节点)是独立的、分散的,它们之间的线性关系通过指针(图 1 以箭头表示)来维持。
通过图 1 可以看到,双向链表的各个节点中存储的不仅仅是元素的值,还应包含 2 个指针,分别指向前一个元素和后一个元素。
通过查看 list 容器的源码实现,其对节点的定义如下:
template<class T>
struct list_node {
T data;
struct list_node<T>* next;//节点的下一个节点的地址
struct list_node<T>* prev;//节点的上一个节点的地址
list_node(const T date)
:
data(date),
next(nullptr)
, prev(nullptr)
{}
};
注意,为了方便读者理解,此代码以及本节后续代码,都省略了和本节核心内容不相关的内容,如果读者对此部分感兴趣,可查看 list 容器实现源码。
可以看到,list 容器定义的每个节点中,都包含 *prev、*next 和 data。其中,prev 指针用于指向前一个节点;next 指针用于指向后一个节点;data 用于存储当前元素的值
一、list的核心框架接口的模拟实现
1.list迭代器
STL容器迭代器有两种实现方式,具体应根据容器底层数据结构实现:
1)原生指针,比如:vector string
2). 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下
方法:
- 指针可以解引用,迭代器的类中必须重载operator*()
- 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
- 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
至于operator–()/operator–(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前 移动,所以需要重载,如果是forward_list就不需要重载– - 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=():
//迭代器结构体
template<class T, class Ref, class Ptr>
struct list_Iterator {
typedef list_node<T> Node;
typedef list_Iterator<T, Ref, Ptr> Self;
Node* node;
public:
bool operator!=(const Self& iter) {
return node != iter.node;
}
/* list_Iterator<T>& operator++(){
node = node->next ;
return *this;
}*/
//前置++
Self& operator ++() {
node = node->next;
return *this;
}
//后置++和前置++构成重载
Self& operator ++(int)
{
node = node->next;
return *this;
}
list_Iterator(Node* _node)
:node(_node)
{
}
Self& operator--() {
node = node->prev;
return *this;
}
Self& operator--(int) {
node = node->prev;
return *this;
}
Ptr operator*()const {
return node->data;
}
Ptr operator->() {
return &node->data;
}
bool operator !=(const Self& it) const {
return node != it->node;
}
bool operator ==( const Self& it) const {
return node == it.node;
}
};
list迭代器分为ierator和const_iterator迭代器,本来分别实现他的iterator迭代器和const_iterator迭代器。但是c++中有模板作为支撑。对list类采用三个模板来实现迭代器。
typedef list_Iterator<T, T*, T&> iterator;
typedef list_Iterator<T, const T*, const T&> const_iterator;
list类中iterator和const_iterator模板定义分别对应迭代器中模板定义的:
template<class T, class Ref, class Ptr>
采用向上传值的方式,传入不同值来采用不同迭代器。iterator传入的<T,T&,T*>对应的是<T,Ref,Ptr>.
const_iterator传入的是<T,const T&,const T*>对应的是<T,Ref,Ptr>,
迭代器根据传入不同值来判断采用的是那种迭代器。
首先是迭代器的++,分为前置++和后置++。两种运算符重载根据参数来构成运算符重载。
//前置++
Self& operator ++() {
node = node->next;
return *this;
}
//后置++和前置++构成重载
Self& operator ++(int)
{
node = node->next;
return *this;
迭代器前置++和后置++都是将node->nex来赋给node来达到迭代器的移动。
迭代器的operator*()和operator->()都是获取迭代器所对应的值。
但是operator*()返回的是模板的&可以都也可以写。
而operator->()返回的是模板对应指针。
Ptr operator*()const {
return node->data;
}
Ptr operator->() {
return &node->data;
}
list其他接口的实现:
list接口实现其实和链表基本差不了多少。唯一的差别就是list就是用到了迭代器。
2.1 list的insert()
数是一个迭代器和要插入的值X
iterator insert(iterator pos, const T& x) {
Node* cue = pos.node;
Node* prev = cue->prev;
Node* newnode = new Node(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = cue;
cue->prev = newnode;
return iterator(newnode);
}
先保存pos对应的模板值接着参照链表的在pos的前一个节点和pos节点链接起来,返回pos’节点前一个节点的迭代器。
2.2 list的push_back()
void push_back(const T& t) {
/* Node* l = head->prev;
Node* newnode = new Node(t);
newnode->prev = l;
l->next = newnode;
head->prev = newnode;
newnode->next = head;*/
insert(end(), t);
}
list的push_back()只需调用insert()但是参数迭代器是end().
2.3 list的erase()
iterator erase(iterator iter) {
assert(iter != end());
Node* cur = iter.node;
Node* prev = cur->prev;
Node* Next = cur->next;
prev->next = Next;
Next->prev = prev;
delete cur;
return iterator(Next);
}
list的erase()传入要删所对应的迭代器。然后保存迭代器所对应的模板值找到前一个节点和后一个节点将钱一个节点后一个节点连接起来。释放pos对应的节点。返回pos下一个节点对应的指针。
2.4 list构造
在list无参构造将head的next和prev都指向自己,早就循环链表。
list() {
head = new Node(T());
head->next = head;
head->prev = head;
}
list(IteraterFirst first,IteraterFirstEnd en) {
head = new Node(T());
head->next = head;
head->prev = head;
while (first!=en) {
push_back(*first);
first++;
}
}
传统写法
list(const list<T>& x) {
head = new Node(T());
head->next = head;
head->prev = head;
const_iterator i = x.begin();
while (i != x.end()) {
push_back(*i);
i++;
}
}
现代写法
list(const list<T>& t) {
head = new Node(T());
head->next = head;
head->prev = head;
list<T> temp(t.begin(),t.end());
swap(head, temp.head);
}
list<T> operator=(const list<T> lt) {
swap(head, lt.head);
return *this;
}
2.5 其他接口大多都是参照inser()和erase()实现的