目录
前言:
模拟实现list,本篇的重点就是由于list是一个双向循环链表结构,所以我们对迭代器的实现不能是简单的指针的++,--了,因为我们知道,链表的存储不一定是连续的,所以直接++,--是链接不起来节点的,所以我们要对迭代器也就是对节点的指针进行封装。结尾会附上完整的代码。
1.创建节点
template<class T>
struct list_node
{
list_node<T>* _prev;
list_node<T>* _next;
T _data;
list_node(const T& x= T())//这里不给缺省值可能会因为没有默认构造函数而编不过
:_prev(nullptr)
,_next(nullptr)
,_data(x)
{}
};
注意给缺省值,这样全缺省就会被当做默认构造了,不会因为没有默认构造而报错。
我们实现的list是带哨兵位的,它同时是迭代器的end()(因为是双向循环的list)。
2.普通迭代器的封装
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迭代器是不能直接++的
_list_iterator(node* n)
:_node(n)
{}
Ref operator*()//返回的必须是引用,不然改变不了外面的对象的成员,要支持对自己解引用改变值就要用应用
{
return _node->_data;
}
Ptr operator->()
{
return &(_node->_data);//返回地址,再解引用直接访问数据
}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);//默认的拷贝构造可以,因为没有深拷贝
_node = _node->_next;
return tmp;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
注意list是双向迭代器,可以++,--,不能+,-
这里对迭代器的实现就如我们开始所说的, 迭代器的实现就是使用节点的指针实现的,而我们不能直接对list创建出的节点进行++,--,所以要进行一层封装;然后再对节点指针初始化。
重载解引用时要注意返回的是引用,不然对自己解引用的时候,返回值如果是临时的,是改变不了内部的data的。
对于箭头的解引用,是为了支持这样的场景:
struct AA
{
int _a1;
int _a2;
AA(int a1=0,int a2=0)
:_a1(a1)
,_a2(a2)
{}
};
void test_list2()
{
list<AA> lt;
lt.push_back(AA(1,1));
lt.push_back(AA(2, 2));
lt.push_back(AA(3, 3));
list<AA>::iterator it = lt.begin();
while (it != lt.end())
{
//cout << (*it)._a1 << " "<<(*it)._a2<<endl;
cout << it->_a1 << " " << it->_a2 << endl;//面对这样的类型,需要重载->,.也可以访问,但是有点别扭
++it;
}
cout << endl;
}
迭代器遇到箭头,返回对象的地址也就是节点数据的地址,再解引用找到成员。或者说node中的data就是存放的是对象(也就是用来初始化的数据),然后重载的->拿到对象的地址,再->去访问里面的成员变量_a1。
对于前置后置++与--,前置就返回对象的引用,是传引用返回;后置需要进行拷贝给一个临时的对象,再对调用对象++--,返回的是tmp也就是没有改变的对象,是传值返回。注意区分前置后置,后置要加上参数int。