前言
数据结构中,我们了解到了链表,但是我们使用时需要自己去实现链表才能用,但是C++出现了list将这一切皆变为现。list可以看作是一个带头双向循环的链表结构,并且可以在任意的正确范围内进行增删查改数据的容器。list容器一样也是不支持下标访问的,主要也是物理空间并不连续。
list的部分重要接口实现
一样的,在实现之前,我们要对list的结构有一个大概的框架
在此基础就很容易理解list的内部是一个个节点,该节点的类型也都是自定义类型,而模版参数是节点中val的类型。代码框架如下:
namespace cr
{
template<class T>
struct ListNode
{
ListNode(const T& x = T())//T()匿名对象
:val(x)
,pre(nullptr)
,next(nullptr)
{}
T val;
struct ListNode<T>* pre;
struct ListNode<T>* next;
};
template<class T>
class list
{
typedef ListNode<T> Node;//对类进行重命名
public:
private:
Node* _head;//自定义类型成员的指针
};
}
1.迭代器实现
2.const迭代的实现
首先我们要了解list中const迭代器与非const迭代器的区别,const迭代器中的成员变量指向的数据是一个节点的指针,并不是说const修饰了以后这个节点不能改变,实际上是节点中的val成员变量不能改变。假如说是节点不能改变的话,那么就连pre和next这两个成员变量也不可以改变,如果连这两个都不能改变的话,那么还怎么实现operator++,还怎么遍历链表中的数据呢。
所以凭这一点,const迭代器就不可能是直接在iterator前面加上const限定符修饰那么容易。
而想要val的值不能改变的话,实际上就是operator*和operator->这两个函数的返回值要加const引用来修饰
方法一:再写一个_List_const_iterator的类
对于_List_terator类而言_List_const_iterator这个类只需要改一下类型而已,像operator*和operator->的返回值加上const修饰而其他需要返回迭代器的地方改成_List_const_iterator<T>就行,最后在list类中typedef _List_const_iterator<T> const_iterator就完成了
方法二:通过模版实现
方法一是需要再实现一个_List_const_iterator的类,但是本质上其实这个类相较于_List_iterator没什么区别,也就是返回值的类型有区别而已,所以此时对迭代器模版的引入就在合适不过了。而此时的模版参数肯定不能只有一个参数,而应该有三个参数:
template<class T,class ref,class ptr>
模版中第二个参数的实参其实就是const T&用于operator*运算符函数的返回值,而第三个参数的实参其实就是const T* 用于operator->函数的返回值。此时你可能会问为什么是需要这两个参数呢?其实判断方法很简单,就是通过实质上的比较const迭代器和非const迭代器实现时各函数返回值有哪些发生了改变。表面上你是只写了一个迭代器的类加一个模版就实现了两个迭代器干的活,实际上在编译的时候编译器通过模版参数自动生成了两个类,在使用时会优先找最匹配的调用。
所以这里提一点:模版中类名相同模版参数不同并不代表是同一个类型,类型=类名+模版参数
迭代器实现代码
template<class T,class ref,class ptr>
struct _List_iterator
{
typedef ListNode<T> Node;
_List_iterator(Node* node)
:_pnode(node)
{}
ref operator*()
{
return _pnode->val;
}
ptr operator->()
{
return &_pnode->val;
}
_List_iterator<T,ref,ptr> operator++()
{
_pnode = _pnode->next;
return *this;
}
_List_iterator<T,ref,ptr> operator++(int)
{
_List_iterator tmp = *this;
_pnode = _pnode->next;
return tmp;
}
_List_iterator<T,ref,ptr> operator--()
{
_pnode = _pnode->pre;
return *this;
}
_List_iterator<T,ref,ptr> operator--(int)
{
_List_iterator tmp = *this;
_pnode = _pnode->pre;
return tmp;
}
bool operator!=(const _List_iterator<T,ref,ptr>& cmpnode)const
{
return _pnode != cmpnode._pnode;
}
Node* _pnode;
};
其实上面的代码本质上就是将operator*和operator->的返回值虚拟化,然后在调用迭代器时才进行实例化的。
template<class T>
class list
{
typedef ListNode<T> Node;//对类进行重命名
public:
typedef _List_iterator<T,T&,T*> iterator;//实例化
typedef _List_iterator<T, const T&, const T*> const_iterator;
const_iterator begin()const
{
//return iterator(_head->next);//匿名对象
return _head->next;
}
const_iterator end()const
{
//return iterator(_head);//匿名对象
return _head;
}
......
}
在list类中分别将两种类型迭代器重命名,所以在外面调用哪种迭代器时就会在内部模版实例化成具体的迭代器
3.list的插入与删除
iterator insert(iterator pos,const T& x)
{
Node* l1 = pos._pnode->pre;
Node* l2 = pos._pnode;
Node* tmp = new Node(x);
l1->next = tmp;
tmp->pre = l1;
tmp->next = l2;
l2->pre = tmp;
return tmp;
}
iterator erase(iterator pos)//调用之后pos节点失效
{
Node* l1 = pos._pnode->pre;
Node* l2 = pos._pnode->next;
l1->next = l2;
l2->pre = l1;
delete pos._pnode;
return l2;
}
void push_back(const T& x)
{
insert(this->end(), x);
//Node* tmp = new Node(x);
//Node* tail = _head->pre;//找尾
//tail->next = tmp;
//tmp->pre = tail;
//tmp->next = _head;
//_head->pre = tmp;
}
这里的插入删除同链表一样,没什么好说的,唯一要注意的就是迭代器失效的问题,list的insert不存在扩容换址,所以不会造成迭代器失效,但是list的erase就不同了,earase会释放当前的节点,所以调用之后就会失效。所以给erase一个返回值,返回需要删除的节点的下一个节点。
4.list的构造加赋值
list()
:_head(nullptr)
{
_head = new Node;//该节点也是一个类,创建时调用构造函数
_head->next = _head;
_head->pre = _head;
}
list(const list<T>& copy)
:_head(nullptr)
{
_head = new Node;//该节点也是一个类,创建时调用构造函数
_head->next = _head;
_head->pre = _head;
for (auto it : copy)//内部实际是用迭代器
{
push_back(it);
}
}
list<T>& operator=(list<T> copy)//调用拷贝构造
{
std::swap(_head, copy._head);
return *this;
}
因为list容器类似带头双向循环链表,所以构造时要new一个带头节点,其次要注意的是new的这个节点的类型是我们自定义的类型,所以在new时就会自动调用该节点的构造函数将其空间内容初始化。赋值函数就实现的比较巧妙,通过传参调用拷贝构造函数的深拷贝,然后再换值就行。
5.空间释放析构函数
void clear()
{
list::iterator del = this->begin();
while (del != this->end())
{
del = erase(del);
}
_size = 0;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
若有不恰当的描述欢迎留言指正!