list常识
- list迭代器不支持【】,所以不支持随机访问。也不支持>、<,没有意义,因为iterator是地址,地址并不连续。
重要的说三遍: list不支持随机访问,因为没有重写[]。 list不支持随机访问。 list不支持随机访问。 - list底层是双向循环链表,头插尾插都方便,但是vector只有尾插方便(不扩容时)。
list模拟实现
首先,list的底层是每个节点,所以需要:Node类,后才是list类。因为涉及利用迭代器去构造类,所以还需要迭代器类。
0.准备工作:需要的三个类解析
Node、ListIterator、list
- Node:节点类,list是循环链表,需要双指针,所以给需要前后指针,而存值类型需要是泛型,所以类外需要模板。
- 缺省构造函数:省写全缺省型。
??这里初始化值为默认值,且两个指针指向空。 多用初始化参数列表,参数列表这只初始化,不赋值。
初始化参数列表好处: 比如有const类型数据和引用类型数据,const类型成员变量,只能初始化不能赋值。 引用类型只能初始化,不能改变,且不能在默认构造函数里,需要专门的构造函数去做初始化,但是也只能在初始化列表中初始化。 效率高
struct ListNode
{
ListNode(const T& val = T())
:_val(val)
, _prev(nullptr)
, _next(nullptr)
{}
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
- Iterator:迭代器类,它的本质是指向Node的地址,所以私有成员是Node节点指针,后续解引用或++、–都需要通过该指针指向成员的prev或next来获取地址。此外,迭代器需要实现基本功能++、–、!=、=、等等。list不能随机访问,所以用迭代器方便访问且非常必要。让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。
- 普通迭代器构造函数:
??接收一个节点指针,缺省型构造函数。 - const类型迭代器构造函数:
??这也是为什么类外需要三个模板参数,因为只是返回值类型不同,因为比如Operator*,operator++、operator–这些没有参数,迭代器部分重载函数没有参数,C++重载需要参数类型不同,而这里只有返回值类型不同。所以为了减少代码冗余,我们把cosnt类型和普通类型的类、指针、引用都用模板,这样就可以重载了,不必为了const类型重写相似度极大的迭代器代码了,在list上,调用时候,会决定这些的类型。
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
public:
ListIterator(PNode pNode = nullptr);
ListIterator(const Self& l);
T& operator*();
T* operator->();
Self& operator++();
Self operator++(int);
Self& operator--();
Self& operator--(int);
bool operator!=(const Self& l);
bool operator==(const Self& l);
private:
PNode _pNode;
};
ListNode和Iterator必须是struct或public类型,不然过程中你在list通过节点类和迭代器类不能调用里面的private成员变量。
1. 迭代器的构造函数和拷贝构造函数
??迭代器的构造函数有两种。
- 普通构造函数:成员变量是指向节点的指针,所以我们利用节点的指针,可以初始化一个迭代器对象。
直接利用参数的节点指针,初始化自己的成员变量节点的指针值。
- 拷贝构造函数,我们利用迭代器对象,也可以拷贝构造迭代器。
ListIterator(PNode pNode = nullptr)
:_pNode(pNode)
{}
ListIterator(const Self& l)
: _pNode(l._pNode)
{}
2. 迭代器类的重载函数
和T或T&相关的返回值类型的函数,建议先看完list的const迭代器,这里的写法没有考虑const迭代器类型。 0. 回忆:几个遗忘概念:
- this的值是对象的地址,*this得到迭代器这个类的对象。
- ++、–我们要的是迭代器对象本身,所以返回*this或后置时返回迭代器对象temp即可。
- **typedef ListIterator<T, Ref, Ptr> Self; **这样写是说list将来内部会用到Ref、Ptr,而我们的ListIterator中存的值可能是这三种,这里改为Self,就理解成是一个迭代器类型即可。
- return & 和 return 普通,return &不是返回引用类型,而返回引用类型只能从函数类型上做声明。
- 重载解引用:operator*
??对迭代器解引用,我们一般的用法像:*it = 2,所以需要能改变节点的值,所以用引用接收,而返回的是this->_pNode->val。 函数类型是引用,使得直接能修改。 函数类型是引用,使得直接能修改。 函数类型是引用,使得直接能修改。
T& operator*()
{
return _pNode->_val;
}
- 重载->:
??当节点T中存的是对象,当使用it->,即:迭代器->,需要能拿到节点中存的对象,其实就是val。所以需要:先解引用,得到节点,节点是个对象,我们访问对象的成员,需要用对象的地址,所以再取地址&。
T* operator->()
{
return &*this;
}
- 重载前置++、后置++,区别是括号中有没有int,后置有int。
不论是前置还是后置,都需要返回迭代器,所以前置是*this,迭代器对象本身。Self表示这个迭代器类。此外,用&类型接收效率高。 ??前置++:
Self& operator++(int)
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
??后置++:利用拷贝构造,以迭代器对象*this拷贝tmp即可。 后置++我们返回的是tmp,不能用&,因为tmp生命周期只在函数体内,而使用普通类型Self则使得函数再返回一个临时拷贝,对return中的tmp再拷贝一次,简言之,return tmp搭配引用,是错误的。
Self operator++(int)
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
- 重载–:道理如同++,且注意后置–不可以用引用接收。
Self& operator--(int)
{
Self tmp(*this);
_pNode = _pNode->_pPre;
return tmp;
}
Self& operator--()
{
_pNode = _pNode->_pNext;
return *this;
}
- 重载!=和==:
迭代器相不相等,不看迭代器本身,而是内部包含的节点是否相同,而看内部节点相同否,需要看:它的成员变量,节点指针指向的地址是不是相同,相同说明记录的是同一个节点。
bool operator!=(const Self& l)
{
return _pNode != l._pNode;
}
bool operator==(const Self& l)
{
return _pNode == l._pNode;
}
list类部分:
1. 构造函数:
- 普通构造函数:造个头节点即可。
??
list()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
- 拷贝构造函数:
list(const list<T>& l)
{
_pHead = new Node;
_pHead->_pPre = _pHead;
_pHead->_pNext = _pHead;
list<T> tmp(l.begin(), l.end());
swap(tmp);
}
??用迭代器拷贝构造函数拷贝即可。现代写法做交换。 3. 迭代器的拷贝构造函数 先要一个头节点,再从头到尾,插值。
template <class Iterator>
list(Iterator first, Iterator last)
{
_pHead = new Node;
_pHead->_pPre = _pHead;
_pHead->_pNext = _pHead;
while (first != last)
{
push_back(*first);
++first;
}
}
4. Swap:
void swap(list<T>& l)
{
PNode tmp = _pHead;
_pHead = l._pHead;
l._pHead = tmp;
}
这个函数非常重要,在现代写法中灵活利用,省写很多,通过参数(不要用引用类型),参数会调用拷贝构造函数,此swap在list中要交换两个节点,而这里面因为参数list类型的L是局部变量,最终会调用L的析构函数,而不会调用*this的析构函数,所以这两个指向的节点只要交换就行。
CRUD:
注意CRUD时的所有插入操作,都不需要考虑扩容,因为list的底层是链表节点。
- erase():通过迭代器拿节点,然后删除链表节点。
- insert():利用insert()可以实现各种插入操作。list和vector的insert都是对迭代器操作。
iterator insert(iterator pos, const T& val)
{
PNode pNewNode = new Node(val);
PNode pCur = pos._pNode;
pNewNode->_pPre = pCur->_pPre;
pNewNode->_pNext = pCur;
pNewNode->_pPre->_pNext = pNewNode;
pCur->_pPre = pNewNode;
return iterator(pNewNode);
}
5. 普通迭代器
??关于迭代器类函数begin和end的返回值,也再次提醒规律,只有全局对象,或者说是成员变量,才配用引用返回(函数类型是引用,确实我们也看到了在迭代器类中才用了返回,且是*this)。 ??返回迭代器即可,迭代器初始化用节点地址。因为return的值是临时变量,所以我们不能用引用类型。
iterator begin()
{
return iterator(_pHead->_pNext);
}
iterator end()
{
return iterator(_pHead);
}
6. const迭代器相关
??list中通过实例化来使用迭代器类,迭代器类根据不同实例化给的值去区分现在内部的一些模板参数值是啥。所以迭代器类使用三个模板参数,后面两个是引用Ref和指针Ptr。使得const迭代器不能修改,只能读。list中用const_iterator,则iterator中的参数Ref和Ptr会实例化为const T&和const T*。注意:我们迭代器中定了参数,函数返回值类型就用参数即可。 自从,我们改了iterator的模板参数,增加了两个,就也得修改原来的一些返回值类型: T& 变成Ref,T*变为Ptr。这样就使得list那边调用过来时候自动实例化使得能区分。 此外,别迷糊:内部重命名的Self是迭代器类本身,只有++、–时才使用,做这些返回,而模板参数只涉及内部存储值,和解引用、->相关。 此外,回忆operator*的返回值类型是T&,因为在iterator内部,还要对存储值本身要做修改,所以我们换T&为Ref后,Ref有两种情况,当是普通引用类型时候可读可写,而为const 引用类型时只可读。
7. 遇到的错误:
==ListNode和Iterator必须是struct或public类型,不然过程中你在list通过节点类和迭代器类不能调用里面的private成员变量。 ====ListNode和Iterator必须是struct或public类型,不然过程中你在list通过节点类和迭代器类不能调用里面的private成员变量。 ==ListNode和Iterator必须是struct或public类型,不然过程中你在list通过节点类和迭代器类不能调用里面的private成员变量。
完整代码
list2.h
#include<iostream>
#include<assert.h>
using namespace std;
namespace lz
{
template<class T>
struct ListNode
{
ListNode(const T& val = T())
:_val(val)
, _pPre(nullptr)
, _pNext(nullptr)
{
}
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
public:
ListIterator(PNode pNode = nullptr)
:_pNode(pNode)
{}
ListIterator(const Self& l)
: _pNode(l._pNode)
{
}
Ref operator*()
{
return _pNode->_val;
}
Ptr operator->()
{
return &*this;
}
Self& operator++()
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
Self& operator--()
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_pNode = _pNode->_pPre;
return tmp;
}
bool operator!=(const Self& l)
{
return _pNode != l._pNode;
}
bool operator==(const Self& l)
{
return _pNode == l._pNode;
}
PNode _pNode;
};
template<class T>
class list
{
typedef ListNode<T> Node;
typedef Node* PNode;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T&> const_iterator;
public:
list()
{
_pHead = new Node;
_pHead->_pPre = _pHead;
_pHead->_pNext = _pHead;
}
list(int n, const T& value = T())
{
_pHead = new Node;
_pHead->_pPre = _pHead;
_pHead->_pNext = _pHead;
for (int i = 0; i < n; ++i)
push_back(value);
}
template <class Iterator>
list(Iterator first, Iterator last)
{
_pHead = new Node;
_pHead->_pPre = _pHead;
_pHead->_pNext = _pHead;
while (first != last)
{
push_back(*first);
++first;
}
}
list(const list<T>& l)
{
_pHead = new Node;
_pHead->_pPre = _pHead;
_pHead->_pNext = _pHead;
list<T> tmp(l.begin(), l.end());
swap(tmp);
}
list<T>& operator=(list<T> l)
{
swap(l);
return *this;
}
~list()
{
clear();
delete _pHead;
_pHead = nullptr;
}
iterator begin()
{
return iterator(_pHead->_pNext);
}
iterator end()
{
return iterator(_pHead);
}
const_iterator begin()const
{
return const_iterator(_pHead->_pNext);
}
const_iterator end()const
{
return const_iterator(_pHead->_pNext);
}
size_t size()const
{
size_t sz = 0;
const_iterator it = begin();
while (it != end())
{
sz++;
it++;
}
return sz;
}
bool empty()const
{
return begin()==end();
}
T& front()
{
return *begin();
}
const T& front()const
{
return *begin();
}
T& back()
{
return *(--end());
}
const T& back()const
{
return *(--end());
}
void push_back(const T& val) { insert(begin(), val); }
void pop_back() { erase(--end()); }
void push_front(const T& val) { insert(begin(), val); }
void pop_front() { erase(begin()); }
iterator insert(iterator pos, const T& val)
{
PNode pNewNode = new Node(val);
PNode pCur = pos._pNode;
pNewNode->_pPre = pCur->_pPre;
pNewNode->_pNext = pCur;
pNewNode->_pPre->_pNext = pNewNode;
pCur->_pPre = pNewNode;
return iterator(pNewNode);
}
iterator erase(iterator pos)
{
assert(pos._pNode);
assert(pos != end());
PNode pDel = pos._pNode;
PNode pRet = pDel->_pNext;
pDel->_pPre->_pNext = pDel->_pNext;
pDel->_pNext->_pPre = pDel->_pPre;
delete pDel;
return iterator(pRet);
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void swap(list<T>& l)
{
PNode tmp = _pHead;
_pHead = l._pHead;
l._pHead = tmp;
}
private:
PNode _pHead;
};
};
测试文件 list.cpp
#include"l2list.h"
void testl1()
{
lz::list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lz::list<int>::iterator it = lt.begin();
cout << "构造函数测试、迭代器测试、解引用修改值、push测试" << endl;
while (it != lt.end())
{
cout << *it << "改之后 : ";
*it *= 2;
cout << *it << endl;
++it;
}
}
int main()
{
testl1();
return 0;
}
|