目录
list的模拟实现总览:
1.节点类的模拟实现
构造函数
2.迭代器类的模拟实现
迭代器类的模板参数说明
构造函数
++运算符的重载
--运算符的重载
==运算符的重载
!=运算符的重载
*运算符的重载
->运算符的重载
list的模拟实现
默认成员函数
构造函数
拷贝构造函数(迭代器版本)
拷贝构造函数(现代写法)
赋值运算符重载函数(现代写法)
其他构造函数
析构函数
迭代器相关函数
访问容器相关函数
插入、删除函数
insert(前插)
erase
push_back和pop_back
push_front和pop_front
#include
using namespace std;
namespace lxy
{
//模拟实现list当中的结点类
template
struct ListNode
{
//构造函数
ListNode(const T& data = T());
//成员变量
T _data; //数据域
ListNode* _next; //后继指针
ListNode* _prev; //前驱指针
};
//模拟实现list迭代器
template
struct __list_iterator
{
typedef ListNode Node;
typedef __list_iterator self;
//构造函数
__list_iterator(Node* x);
//各种运算符重载函数
self& operator++();
self& operator--();
self operator++(int);
self operator--(int);
bool operator==(const self& it) const;
bool operator!=(const self& it) const;
Ref operator*();
Ptr operator->();
//成员变量
Node* _node;
};
//模拟实现list
template
class list
{
public:
typedef ListNode Node;
typedef __list_iterator iterator;
typedef __list_iterator const_iterator;
//默认成员函数
list();
list(const list& lt);
list& operator=(const list& lt);
~list();
//迭代器相关函数
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
//访问容器相关函数
T& front();
T& back();
const T& front() const;
const T& back() const;
//插入、删除函数
iterator insert(iterator pos, const T& x);
iterator erase(iterator pos);
void push_back(const T& x);
void pop_back();
void push_front(const T& x);
void pop_front();
//其他函数
size_t size() const;
void resize(size_t n, const T& val = T());
void clear();
bool empty() const;
void swap(list& lt);
private:
//指向链表头结点的指针
Node* _head;
};
}
template
struct ListNode
{
//构造函数
ListNode(const T& data = T());
//成员变量
T _data; //数据域
ListNode* _next; //后继指针
ListNode* _prev; //前驱指针
};
list是一个带头双向循环链表,故先实现一个结点类。
ListNode(const T& data = T())
:_next(nullptr)
, _prev(nullptr)
, _data(data)
{ }
注意: 若构造结点时未传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据。
//模拟实现list迭代器
template
struct __list_iterator
{
typedef ListNode Node;
typedef __list_iterator self;
//构造函数
__list_iterator(Node* x);
//各种运算符重载函数
self& operator++();
self& operator--();
self operator++(int);
self operator--(int);
bool operator==(const self& it) const;
bool operator!=(const self& it) const;
Ref operator*();
Ptr operator->();
//成员变量
Node* _node;
};
对于list来说,其各个结点在内存当中的位置是随机的,并不是连续的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作。迭代器让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。
list迭代器类,实际上就是对结点指针进行了封装,对其各种运算符进行了重载,使得结点指针的各种行为看起来和普通指针一样。
template
在list的模拟实现当中,typedef了两个迭代器类型,普通迭代器和const迭代器。
typedef __list_iterator iterator;
typedef __list_iterator const_iterator;
可知:Ref代表引用类型,Ptr代表指针类型。
若该迭代器类不设计三个模板参数,那么就不能很好的区分普通迭代器和const迭代器。
对结点指针进行了封装,封装成迭代器。
__list_iterator(Node* x)
:_node(x)
{}
前置++,后置++
迭代器++,访问下一个节点
// ++it
self& operator++()
{
_node = _node->_next;
return *this;
}
// it++
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
说明: self是当前迭代器对象的类型:
typedef __list_iterator self;
前置--,后置--
迭代器--,访问上一个节点
// --it
self& operator--()
{
_node = _node->_prev;
return *this;
}
// it--
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
判断两个迭代器当中的结点指针的指向是否相同,注意加上const
bool operator!=(const self& it) const
{
return _node != it._node;
}
判断两个迭代器当中的结点指针的指向是否不同,注意加上const
bool operator==(const self& it) const
{
return _node != it._node;
}
*运算符得到该位置的数据内容,并且返回引用
Ref operator*()
{
return _node->_data;
}
某些情景下,我们使用迭代器的时候可能会用到->运算符。
当list容器当中的每个结点存储的不是内置类型,而是自定义类型,例如日期类,那么当我们拿到一个位置的迭代器时,我们可能会使用->运算符访问Date的成员:
void test()
{
list lt;
lt.push_back(Date(2022, 3, 12));
lt.push_back(Date(2022, 3, 13));
lt.push_back(Date(2022, 3, 14));
list::iterator it = lt.begin();
while (it != lt.end())
{
//cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
++it;
}
cout << endl;
}
注意: 使用pos->_year这种访问方式时,需要将日期类的成员变量设置为公有。
对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可。
Ptr operator->()
{
return &_node->_data;
}
这里本来是应该有两个->的,第一个箭头是pos ->去调用重载的operator->返回Date* 的指针,第二个箭头是Date* 的指针去访问对象当中的成员变量_year。
但是一个地方出现两个箭头,程序的可读性太差了,所以编译器做了特殊识别处理,为了增加程序的可读性,省略了一个箭头。
list是一个带头双向循环链表,在构造一个list对象时,申请一个头结点,并让其前驱指针和后继指针都指向自己即可。
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
先申请一个头节点,再遍历一个范围,push进去
说明:push_back函数稍后讲解。
//list lst2(lst1.begin(),lst1.end());
template
list(InputIterator first, InputIterator last)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
while (first != last)
{
push_back(*first);
++first;
}
}
先用迭代器版本构造函数构造tmp,再交换头指针指向位置。
// lt2(lt1)
list(const list& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
list tmp(lt.begin(), lt.end());
std::swap(_head, tmp._head);
}
利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换即可。
// lt2 = lt1
list& operator=(list lt)
{
std::swap(_head, lt._head);
return *this;
}
使用案例:
构造一个list:1,1,1,1,1
构造一个list含5个Date类型。
注意:
class InputIterator应该为迭代器类型,但是如果
list
则会识别为int类型,所以写出带int的构造函数,编译器会优先选择。
// list lt1(5, Date(2022, 3, 15));
// list lt2(5, 1);
list(int n, const T& val = T())
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
for (int i = 0; i < n; ++i)
{
push_back(val);
}
}
list(size_t n, const T& val = T())
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
首先说明:clear 函数(2钟写法),erase函数稍后解析。
void clear()
{
iterator it = begin();
while (it != end())
{
iterator del = it++; //迭代器前进
delete del._node; //删除当前位置数据
}
_head->_next = _head;
_head->_prev = _head;
}
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
析构函数:首先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空即可。
~list()
{
clear();
delete _head;
_head = nullptr;
}
begin和end:begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。即begin指向首元素,end指向尾后。
对于list这个带头双向循环链表来说:
说明:
typedef __list_iterator iterator;
typedef __list_iterator const_iterator;
并且迭代器就是对节点指针的封装。
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
front和back函数分别用于获取第一个有效数据和最后一个有效数据,并且返回引用。
T& front()
{
return *begin(); //返回第一个有效数据的引用
}
T& back()
{
return *(--end()); //返回最后一个有效数据的引用
}
const版本不再赘述。
核心函数:insert函数 erase函数,其他函数复用insert和erase得到
插入后返回指向newNode的迭代器。
// 这里insert以后,pos不失效
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
// 这里erase以后,pos失效
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
delete pos._node;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
删除后返回下一个位置的迭代器
复用insert、erase
//尾插
void push_back(const T& x)
{
insert(end(), x); //在头结点前插入结点
}
//尾删
void pop_back()
{
erase(--end()); //删除头结点的前一个结点
}
复用insert、erase
//头插
void push_front(const T& x)
{
insert(begin(), x); //在第一个有效结点前插入结点
}
//头删
void pop_front()
{
erase(begin()); //删除第一个有效结点
}
size_t size() const
{
size_t sz = 0; //统计有效数据个数
const_iterator it = begin(); //获取第一个有效数据的迭代器
while (it != end()) //通过遍历统计有效数据个数
{
sz++;
it++;
}
return sz; //返回有效数据个数
}