
目录
前言:
我们今天讲一下list的内置函数以及list的模拟实现。在数据结构中我们学习了链表,之后我们还会根据我们所学的内容来实现stack和queue和priority_queue不过那都是后话了。今天我来给大家完整的讲一下list的内置函数以及一些常用的内置函数的实现。
一.list的介绍
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
- list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
- 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
- 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
二.list的模拟实现以及实现中所需要的细节
在简单介绍完之后list的大致内容后我们先来实现一下我们自己list的一个基本框架。list主要有3部分组成。一个是list每一个元素中的详细内容,还有一个就是存储链表的。以及一个迭代器的设置。
1.实现每个元素的类
cpp
template<class T>
struct ListNode {
T _date;
ListNode<int>* _prev;
ListNode<int>* _next;
ListNode(const T node)
:_date(node)
,_prev(nullptr)
, _next ( nullptr)
{; }
};
有人可能会好奇为什么我们使用struct而不是class呢?在我们的基础语法中我们提到了struct和class的区别。他们两个都能进行对类的定义,而有不同的是struct的基本类型是public而class的基本类型是private。所以这边我们可以用struct定义或者是在class中进行public。
这个类里面我们分别定义了_data,_prev_next我们用这三个变量实现对数据的存储。
2.主类
第二个类主要的就是实现我们对我们list的控制,基本上所有的操作都是在list中进行。
cpp
template<class T>
class list
{
public:
typedef ListNode<T> Node;
private:
Node* _ahead;
};
在没有实现迭代器之前我们主要进行的工作就只有这些了。
3.构造函数
cpp
void empty_init()
{
_head = new Node(T());
_head->_next = _head;
_head->_prev = _head;
}
List()
{
empty_init();
}
将这两部分分离的目的主要是empty_init函数后面会多次进行使用。
4.push_back
cpp
void push_back(const T& x)
{
Node* newnode = new Node(x);
Node* tail = _head->_prev;
tail->_next = newnode;
newnode->_next = _head;
newnode->_prev = tail;
_head->_prev = newnode;
}
基本操作与我们在数据结构上学的链表几乎相同。
5.迭代器函数以及迭代器类的实现
cpp
template<class T,class Tqu,class Con>
class ListIterator {
typedef ListIterator<T, Tqu,Con> self;
typedef ListNode<T> Node;
public:
Node* _node;
ListIterator(Node* node)
:_node (node)
{}
self& operator ++()
{
_node = _node->_next;
return *this;
}
self& operator --()
{
_node = _node->prev;
return *this;
}
self& operator ++(int)
{
self tmp = _node;
_node = _node->_next;
return tmp;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
Tqu operator*()
{
return _node->_date;
}
};
在list中由于不是一块连续的空间导致我们不可以使用[]去访问,所以我们就不可以把迭代器单纯的看作是一个指针了。那么问题又来了那么我们要怎么去实现指针才能实现的内容呢?这时就需要用到我们的operator了。如上述代码所展示。
模板后面两个参数是T*和T&主要的作用是在增加const变量时不需要手动再写一个函数。
cpp
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&,const T*> const_iterator;
iterator begin()
{
iterator tmp(_head->_next);
return tmp;
}
iterator end()
{
iterator tmp(_head);
return tmp;
}
const_iterator begin() const
{
iterator tmp(_head->_next);
return tmp;
}
const_iterator end()
{
iterator tmp(_head);
return tmp;
}
这些就是我们迭代器所需要的内置类型函数了。
6.insert和erase
cpp
iterator insert(iterator pos,const T&x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* prev = pos._node->_prev;
//prev newnode cur
newnode->_next = cur;
newnode->_prev = prev;
prev->_next = newnode;
cur->_prev = newnode;
return iterator(newnode);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = pos._node->_prev;
Node* next = cur->_next;
//prev cur next
prev->_next = next;
next->_prev = prev;
delete cur;
cur = nullptr;
return iterator(next);
}
这两个函数的作用主要是在指定位置插入数据,以及在指定位置删除数据。
7.拷贝构造和operator=
cpp
List(List<T>& lt)
{
empty_init();
for (auto& e : lt)
{
push_back(e);
}
}
list<T>& operator=(list<T> lt)
{
swap(_head, lt._head);
return *this;
}
基本上也是很简单,需要注意的时候我们在进行拷贝构造的时候需要定义一个新的开年表所以我们需要对它进行初始化。
结言:
list与vetcor的实现思路基本上相似的。不过他们在内存上有着本质上的区别。他们两个各有各的好处。好了今天的内容就到此为止了。