赞
踩
- template<class T>
- struct ListNode
- {
- ListNode<T>* _next;//指向后一个节点
- ListNode<T>* _prev;//指向前一个节点
- T _data; // 存数据
- // 这里用匿名对象当缺省值
- ListNode(const T& data = T())
- :_next(nullptr)
- , _prev(nullptr)
- , _data(data)
- {}
- };
因为要频繁的调用ListNode里面的数据选择了struct ,struct具有公有性。
- template<class T>
- class list
- {
- typedef ListNode<T> Node;
- private:
- Node* _head;
- };
在class list类中添加了构造以及插入函数
- list()
- {
- _head = new Node();
- _head->_next = _head;
- _head->_prev = _head;
- }
- void push_back(const T& x)
- {
- Node* newnode = new Node(x);
- Node* tail = _head->_prev;
-
- tail->_next = newnode;
- newnode->_prev = tail;
- newnode->_next = _head;
- _head->_prev = newnode;
- }

当创建一个list对象首先发生构造,创建头结点,之后就是插入数据
在main函数中我们要这样使用迭代器,进行循环
- list<int> lit;
- lit.push_back(1);
- lit.push_back(2);
- lit.push_back(3);
- lit.push_back(4);
- lit.push_back(5);
- list<int>::iterator it = lit.begin();
- // it.operator!=(lit.end());
- while (it != lit.end())
- {
- cout << *it << " ";
- ++it;
- }
我们先创建一个对象lit,在这个对象中,我们在各个节点中push_back数据,在push_back内部我们使其各个分散的节点连接一起,最后定义了迭代器进行循环
在循环的过程中,我们使用下面的迭代器可以吗?
typedef Node* iterator;
不能,因为每个节点是独立的空间地址,每个节点是不连续的,我们无法保证++来移动指针,使其下个地址就是下个节点的位置。
那应该怎么办?
因为,我们要求这个迭代器++就能指向下一个节点位置,我们只能创建个类
在这里我们创建一个类,来模拟自定义类型Node的指针,为其添加类似于内置类型的功能(++,--)最后封装成iterator
- template<class T>
- class ListIterator
- {
- typedef ListNode<T> Node;
- typedef ListIterator<T> Self;
- public:
- ListIterator(Node* node)
- :_node(node)
- {}
- // ++it
- Self& operator++()
- {
- _node = _node->_next;
- return *this;
- }
- bool operator!=(const Self& it)
- {
- return _node != it._node;
- }
- T& operator*()
- {
- return _node->_data;
- }
- T* operator->()
- {
- return &_node->_data;
- }
- private:
- Node* _node;
- };

循环遍历首先要++,移动指针的位置,使其指向下一个节点
在上面的main函数中
list<int>::iterator it = lit.begin();
it接受第一个节点的地址后
之后进行++it<------>it.operator(第一个节点地址)
- Self& operator++()
- {
- _node = _node->_next;
- return *this;
- }
把一个节点地址修改为下一个节点的地址进行返回
循环遍历的循环条件是
it != lit.end()
迭代器的区间一般又是左闭右开
所以iterator end返回的是头节点
指向的位置
什么意思呢?就是指向的位置是左闭右开
之后就是访问里面的数据
如果_data里面存储的是内置类型可以直接解引用使用operator*
如果_data里面存储的是自定义类型这样就要使用operator->去访问里面的数据(若是自定义类型不是一定要用operator->去访问)
- struct Pos
- {
- int _row;
- int _col;
-
- Pos(int row = 0,int col = 0)
- :_row(row)
- ,_col(col)
- {}
- };
例如我们自定义一个这样的类型
我们push_back存储数据,该如何遍历里面的数据呢?
- list<Pos> lt1;
- lt1.push_back(Pos(100, 100));
- lt1.push_back(Pos(200, 200));
- lt1.push_back(Pos(300, 300));
cout << it.operator->()->_col << ":" << it.operator->()->_row << endl;
我们可以向上面一样访问
operator->返回的是匿名对象存数据的地址,之后再访问Pos类里面的内容(只是调用了一次operator->)
系统默认优化这样写也行
cout << it->_col << ":" << it->_row << endl;
上面的代码可以这么理解,迭代器就是一个指针,指针访问数据
也可以这么写
cout << (*it)._col << ":" << (*it)._row << endl;
我们这里operator*采用的是引用返回,方便修改数据
operator->采用的是T*返回,也能修改数据。
如果不想修改数据,又该如何操作
假如是一个const list我们又该如何遍历链表
- void Func(const list<int>& lt)
- {
- list<int>::iterator it = lt.begin();
- while (it != lt.end())
- {
- // *it += 10;
- cout << *it << " ";
- ++it;
- }
- cout << endl;
- }
首先const的list需要一个const迭代器
这个const迭代器的功能是 *it内容的数据不能修改,it可以++
直接在iterator前面+const是不可以的,因为it不能进行++了
如何操作?再设计一个const迭代器的类
首先,要明白如何控制*it的内容不被修改------->就是迭代器中的返回的数据不能修改
也就是控制T&与 T*的返回类型,在他们前面都加上const
- T& operator*()
- {
- return _node->_data;
- }
- T* operator->()
- {
- return &_node->_data;
- }
之后再list类中补充一下const list有关迭代器返回函数
- typedef ListConstIterator<T> const_iterator;//const迭代器类
- const_iterator begin() const
- {
- return const_iterator(_head->_next);
- }
- const_iterator end() const
- {
- return const_iterator(_head);
- }
我们发现,虽然我们实现了const list的遍历,但是代码过于繁琐
我们通过模板让编译器控制返回的类型这样可以减少代码的繁琐,
于是分别定义了两个模板在list类中,通过调用不同的名称来控制某个模板
- typedef ListIterator<T, T&, T*> iterator;
- typedef ListIterator<T, const T&, const T*> const_iterator;
迭代器的模板,接受的类型一个是T一个是Ref一个是Ptr,若是使用的iterator,迭代器模板所接受的就是T,T&,T*,若是使用的是const_iterator,迭代器模板所接受的就是T,const T&,const T*
- template<class T,class Ref,class Ptr>
- struct ListIterator
- {
- typedef ListNode<T> Node;
- typedef ListIterator<T, Ref, Ptr> Self;
- Node* _node;
- ListIterator(Node* node)
- :_node(node)
- {}
- Ref operator*()
- {
- return _node->_data;
- }
- Ptr operator->()
- {
- return &_node->_data;
- }
- bool operator!=(const Self& it)
- {
- return _node != it._node;
- }
- Self& operator++()
- {
- _node = _node->_next;
- return *this;
- }
- };

最后把迭代器补充完整
后置++
- // it++
- Self& operator++(int)
- {
- Self tmp(*this);
- _node = _node->_next;
- return tmp;
- }
注意:这里实现的是浅拷贝,为什么这里不实现深拷贝?
我们这是需要指针,而不是所指向的内容,使用深拷贝太浪费空间
前置--,与后置--
- // --it
- Self& operator--()
- {
- _node = _node->_prev;
- return *this;
- }
- // it--
- Self& operator--(int)
- {
- Self tmp(*this);
- _node = _node->_prev;
- return _node;
- }
operator==
- bool operator==(const Self& it)
- {
- return _node == it._node;
- }
operator->
- T* operator->()
- {
- return &_node->_data;
- }
insert
- iterator insert(iterator pos, const T& x)
- {
- Node* cur = pos._node;
- Node* newnode = new Node(x);
- Node* prev = cur->_prev;
-
- prev->_next = newnode;
- newnode->_prev = prev;
- newnode->_next = cur;
- cur->_prev = newnode;
-
- return iterator(newnode);
- }
insert这里没有迭代器失效,因为pos指向的位置没有改变
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;
-
- return iterator(next);
- }
这里迭代器失效了,因为pos的位置已经delete了,pos是野指针
尾删,头插,尾删
- void pop_back()
- {
- erase(--end());
- }
-
- void push_front(const T& x)
- {
- insert(begin(), x);
- }
-
- void pop_front()
- {
- erase(begin());
- }
析构
- ~list()
- {
- clear();
- delete _head;
- _head = nullptr;
- }
- void clear()
- {
- auto it = begin();
- while (it != end())
- {
- it = erase(it);
- }
- }
这里我们采用一个clear函数来清除节点的数据,因为清除每个节点后,it失效,erase指向下一个节点的位置,所以要重新赋值一下it,防止野指针
拷贝构造
- void empty_init()
- {
- _head = new Node();
- _head->_next = _head;
- _head->_prev = _head;
- }
- list()
- {
- empty_init();
- }
- //lt2(lt1)
- list(const list<T>& lt)
- {
- empty_init();
- for (const auto& e : lt)
- {
- push_back(e);
- }
- }

我们这里创建一个empty_init用来初始化头结点,因为拷贝与拷贝构造都需要使用所以创建了一个函数,使其方便调用。在拷贝构造过程中,我们使用了for (const auto& e : lt)循环,为啥使用&?如果这里的T是string等其他自定义类型,会先构造生成一个临时变量,使其效率低下,所以要添加&
赋值拷贝
- list<T>& operator=(list<T> lt)
- {
- swap(_head, lt._head);
- return *this;
- }
这里采用的是是现代写法。
要是我想使用C++11中的这种初始化方式应该如何模拟?
list<int> lit = {1,2,3,4};
initializer_list<value_type> il
这个底层其实就是把{ }中的内容变成常量数组
底层有2个指针,一个指向开始的位置,一个指向结束的地方
- list(initializer_list<T> il)
- {
- empty_init();
- for (const auto& e : il)
- {
- push_back(e);
- }
- }
在mian函数中 初始化lit为1234之后传给形参il经过initializer_list类的实现,已经成为一个il为1234的常量数组了,之后把il中的数据插入到list链表中
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。