目录
在学习STL中的list链表的过程中,对于其中主要功能的实现,我遇到了一些困难,因此在此总结一下问题所在。
1.list的节点结构体
在STL中,list的是带头双向循环链表的实现 ,其节点类型如下
既有上一节点的指针,也有下一节点的指针以及本节点的值。
而在list类中存储一个头节点为开始,也是结尾,因此头节点的下一节点还是头节点是判断链表是否为空的条件。
2.list的构造函数
这里我主要介绍前两个常用构造函数, 如果想要理解其余的构造函数,可以点击
https://cplusplus.com/reference/list/list/list/
首先,第一个构造函数就是传入一个任何类型的引用变量,将其赋值给list链表,可以是数字,数组甚至字符串,如下:
list<int> L1(1);//数字
list<int> L2(1,2,3,4,5);//数组
list<string> L3("abc");//字符串
第二个构造函数也比较常用,其功能是传入一个整数和一个任何类型的数据,将整数个数据添加入链表中,如下:
list<int> L1(10,1);//添加十个数字1
list<int> L2(10,'a');//添加十个字符a
list<string> L3(10,"abc");//添加十个字符串abc
拷贝构造,赋值重载以及析构函数这里不再赘述。
3.list的增删查改
与vector不同的是,list的插入操作更加简单,头插头删,尾插尾删,任意位置插入与删除,list都能够实现。
首先是插入操作,list的插入操作不用考虑扩容,比较简单,我们大可不必全部写,而是只写一个insert (任意位置插入),而尾插与头插直接复用就行了,代码如下:
//我的代码
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node; //cur指向插入位置原来节点
Node* prev = cur->_prev; //prev指向插入位置的下一节点
Node* newnode = new Node(x); //创建新节点,值为x
//处理指向
prev->_next = newnode;
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
//返回插入后的新pos位置
return newnode;
}
//STL源码
iterator insert(iterator position, const T& x) {
link_type tmp = create_node(x);
tmp->next = position.node;
tmp->prev = position.node->prev;
(link_type(position.node->prev))->next = tmp;
position.node->prev = tmp;
return tmp;
}
其实源码与我写的原理相同,就是源码看起来更加高大上。。。
其次,在链表中的删除操作是很简单的,这里我们也是只要写一个任意位置删除的erase,其他删除操作进行复用就可以,学过数据结构的朋友都知道,删除操作很简单,改变指向加释放节点就能够实现,代码如下:
//我的代码
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;
//返回更新后的pos位置
return next;
}
//STL源码
iterator erase(iterator position) {
link_type next_node = link_type(position.node->next);
link_type prev_node = link_type(position.node->prev);
prev_node->next = next_node;
next_node->prev = prev_node;
destroy_node(position.node);
return iterator(next_node);
}
4.list的迭代器问题
在学习STL的过程中,我们学习了vector的迭代器,但是vector的迭代器其实可以直接用指针进行替代,正是由于vector是一块连续的空间,但是在list之中,空间不是连续的,我们无法直接通过自增自减实现节点之间的跳跃,因此在list中,对于其迭代器的行为我们需要进行一些特殊处理。
这里提取list源码中的迭代器定义如下:
//同一个类模板实例化出的两个类型,便于实现const版本和非const版本
//STL源代码中的写法,通过利用多个模板参数来避免副本造成的代码冗余问题
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator<T, Ref, Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
link_type node;
__list_iterator(link_type x) : node(x) {}//使用x构造list迭代器
__list_iterator() {}
__list_iterator(const iterator& x) : node(x.node) {}
bool operator==(const self& x) const { return node == x.node; }
bool operator!=(const self& x) const { return node != x.node; }
reference operator*() const { return (*node).data; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
//为了区分前置和后置,统一在后置中加一个int参数
self& operator++() {
node = (link_type)((*node).next);
return *this;
}
self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
self& operator--() {
node = (link_type)((*node).prev);
return *this;
}
self operator--(int) {
self tmp = *this;
--*this;
return tmp;
}
};
5.list的排序问题
在list中,一般不在成员函数中实现排序函数,因为,在list中直接进行排序是得不偿失的,相较而言,将其赋值到vector中进行排序后,在进行数据覆盖是更加高效的做法。
在vector和list中,也提供了通过迭代器始末进行构造的构造函数,如果一定要在成员函数中实现排序,使用堆排序是比较好的一种做法,眼见为实,我们进行五百万个数据的排序进行比较
如下,其实我们也知道,sort是使用快速排序,比堆排序快是理所当然的,并且随着数据量的增加,差异会越来越大,而拷贝数据的效率消耗可以忽略不计。
如果在list中自己实现排序,对于少量数据还是有一定作用的,但是大量数据就会事半功倍。
以上是我个人学习list主要功能过程碰到的一些问题,总结以便复习。