list模拟实现
本文中我们将来模拟实现一下STL中的list,在STL中使用的是带头节点的双向链表结构。
list的节点结构体和构造函数
template <class T>
struct list_node {
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& x = T()) // 用来插入数据的时候进行构造初始化,这里初始化需要使用的是T()匿名对象,不能使用0这种内置类型
:_next(nullptr)
,_prev(nullptr)
,_data(x)
{}
};
list的构造函数
list() // 创建一个带有头结点的链表,表头表位都指向自己
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
尾插、头插、尾删、头删这几个方法都与我们之前数据结构中编写的双向带头循环链表相类似,这里就不过多赘述。
我们这里主要介绍的是迭代器相关内容。
迭代器
在之前的string与vector这两个类中我们迭代器使用的都是指针,因为在这两个结构中所占有的空间都是连续的,通过指针的++就可以获取到下一个元素的地址。但是在这里由于list所占有的空间是分开的那么就会有一个问题:加入我们将跟之前一样将指针++的话,得到的不知道是哪里的地址,无法获得我们想要的下一个数据的地址。这里就需要将节点的指针进行封装,然后就可以在这个封住的类中重载++方法。
迭代器的构造函数
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* n)
:_node(n)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
self& operator++()
{
_node = _node->_next;
return *this;
}
bool operator==(const self& s)
{
return _node == s._node;
}
通过将节点的指针进行封装后,重新给这个类重载++让迭代器的++能够指向下一个数据,通过这种方式我们可以控制各种操作。
然后就可以在list类中进行迭代器的处理:
// 迭代器浅拷贝不会失效的原因是没有编写析构函数,不需要释放节点
iterator begin()
{
//iterator it(_head->_next);
//return it;
return iterator(_head->_next); // 这里返回时可以直接使用匿名对象进行返回,这里使用的是默认生成的浅拷贝,可以传给使用者begin的数据且不会让使用这对数据进行修改。
}
iterator end()
{
//iterator it(_head);
//return it;
return iterator(_head);
}
接下来就是const迭代器的问题,有了上述基本的迭代器,我们就试想将const迭代器直接仿照普通迭代器,写一份const迭代器,这样可以通过编译且能够运行。但是可以发现,这样操作之后代码的迭代器部分有大量重复的代码,非常的不好。因此我们想要通过在模板里放入新的模板参数来解决控制const迭代器的返回值的问题。通过模板参数使T&和const T&进行复用。
template <class T>
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;
typedef const __list_iterator<T> const_iterator; // 这里我们不能写成这个样子,因为这样就会让迭代器本身无法改变,而我们需要让迭代器进行++操作
}
可以发现在模板参数中还有一个T*参数这个是为了解决重载 -> 的问题:
class AA
{
public:
AA(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{}
int _a1;
int _a2;
};
void test_list2()
{
list<AA> l1;
l1.push_back(AA(1, 1));
l1.push_back(AA(2, 2));
l1.push_back(AA(3, 3));
// AA* ptr
list<AA>::iterator it = l1.begin();
while (it != l1.end())
{
//cout << (*it)._a1 << ":" << (*it)._a2 << endl;
// 会发现这里调用感觉缺少了一个->
cout << it->_a1 << ":" << it->_a2 << endl; // 本来应该是->->但是编译器优化了,为了增加可读性,省略了一个->
cout << it.operator->()->_a1 << ":" << it.operator->()->_a1 << endl;
++it;
}
cout << endl;
list迭代器失效的问题
迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
list的拷贝构造和赋值重载
template <class Iterator>
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
void swap(list<T>& tmp)
{
empty_init(); // 这里需要注意头地址是否有意义的问题
std::swap(_head, tmp._head);
}
list(const list<T>& lt)
{
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
// 这里不能使用&,会对原数据进行修改,这是本不需要的
list<T>& operator=(list<T> lt) // 这里的swap需要对内部的头指针进行修改所以不能使用const修饰
// 这里借用了拷贝构造在lt这里发生了拷贝构造,然后让lt与需要被赋值的进行交换,得到我们想要的结果
{
swap(lt);
return *this;
}