List属于带头双链表结构,这种结构比较简单,很多接口都是复用,但他的迭代器是一个重点
构造函数( constructor) | 接口说明 |
---|---|
list() | 构造空的list |
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list (const list& x) | 用[first, last)区间中的元素构造list |
函数声明 | 接口说明 |
---|---|
begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置 |
函数声明 | 接口说明 |
---|---|
empty | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
函数声明 | 接口说明 |
---|---|
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
函数声明 | 接口说明 |
---|---|
push_front | 在list首元素前插入值为val的元素 |
pop_front | 删除list中第一个元素 |
push_back | 在list尾部插入值为val的元素 |
pop_back | 删除list中最后一个元素 |
insert | 在list position 位置中插入值为val的元素 |
erase | 删除list position位置的元素 |
swap | 交换两个list中的元素 |
clear | 清空list中的有效元素 |
namespace dd
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
}
};
类似C语言中声明双链表的结构体及其内部成员组成,区别是这里可以在构造函数中初始化列表
namespace dd
{
template<class T>
class My_list
{
typedef ListNode<T> Node;
public:
My_list()
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
}
private:
Node* _head;
};
}
类似于C语言时,在外部(如main中等)创建一个节点类型的结构体指针并调用一个初始化接口函数
而C++中struct是默认公有的(public),class默认私有的(private),所以可以直接访问ListNode中的成员
void push_back(const T& val)
{
Node* newnode = new Node(val);
Node* tail = _head->_prev;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
tail->_next = newnode;
}
注意:Node* newnode是不会调用构造函数的,这里他只是个存,申请1个动态内存Node类型地址的指针(占4\8字节),只有实例化对象才会调用构造函数
void pop_back()
{
assert(_head->_prev != _head);
Node* earse = _head->_prev;
Node* tail = earse->_prev;
tail->_next = _head;
_head->_prev= tail;
delete earse;
//earse = nullptr;
}
注意:不可以删除头节点,记得要释放删除的内存避免内存泄漏
头删头插尾删尾插这4个接口功能基本类似,而且通过insert和erase可以复用实现,这里就不写头操作的删除增加了
迭代器是存储头尾数据地址的指针,在vector和string中因为是数组型式所以++、- - 很方便,那链表怎么实现呢?
方法:
想实现链表的++、- -需要对++、- -运算符重载下手;在++中的操作可以理解为 _head = _head->_next,每次++就让他指向下一个地址并返回这个地址
具体做法:
1.对节点封装出一个类
namespace dd
{
template<class T>
struct list_iterator
{
typedef ListNode<T> Node;
Node* _pnode;
list_iterator(Node* pnode)
:_pnode(pnode)
{}
};
}
2.对++ 、- -、* 、!= 重载
前置++
list_iterator<T>& operator++()
{
_pnode = _pnode->_next;
return *this;
}
后置++
list_iterator<T> operator++(int)
{
list_iterator<T> temp(*this);
_pnode = _pnode->_next;
return temp;
}
前置--
list_iterator<T>& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
后置--
list_iterator<T> operator--(int)
{
list_iterator<T> temp(*this);
_pnode = _pnode->_prev;
return temp;
}
解引用
T& operator*()
{
return _pnode->_data;
}
判断不相等
bool operator!=(const list_iterator<T>& L)
{
return L._pnode != _pnode;
}
3.在My_list中设置迭代器的接口
list_iterator<T> begin()
{
return list_iterator<T>(_head->_next);
}
list_iterator<T> end()
{
return list_iterator<T>(_head);
}
下面来测试一下并查看执行顺序都调用了哪些函数
void print(My_list<int>& L)
{
L.push_back(1);
L.push_back(2);
L.push_back(3);
L.push_back(4);
list_iterator<int>iterator = L.begin();
while(iterator != L.end())
{
//*it = 10;
cout << *iterator <<" ";
++iterator;
}
cout << endl;
}
先调用begin函数,begin中的返回值是构建匿名 list_iterator 的对象,构建完后返回给外部即将实例化的对象
但注意,编译器对匿名对象进行了优化:匿名对象只会调用普通构造函数,将匿名对象直接变为构造的对象
正常逻辑是先调用拷贝构造,将匿名对象拷贝给临时对象,再将临时对象拷贝给外部即将实例化的对象。而优化后为将返回值构造的匿名对象(原本声明周期只存在一行)之接变为外部的对象,不调用任何拷贝,只是匿名对象构造时调用一次构造函数。
之后的++、!= * 都可以看成
iterator.operator++() 使当前节点指针指向next
iterator.operator!=(L.end()) 判断节点地址是否相等
iterator.operator*() 解引用是 节点指针指向的data
搞懂了它们之间的逻辑,下面来把上面的代码优化一下
template<class T>
struct list_iterator
{
typedef ListNode<T> Node;
typedef list_iterator<T> self; 优化1
Node* _pnode;
list_iterator(Node* pnode)
:_pnode(pnode)
{}
//前置++
self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
//后置++
self operator++(int)
{
self temp(*this);
_pnode = _pnode->_next;
return temp;
}
//前置--
self& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
//后置--
self operator--(int)
{
self temp(*this);
_pnode = _pnode->_prev;
return temp;
}
//解引用
T& operator*()
{
return _pnode->_data;
}
//判断不相等
bool operator!=(const self& L)
{
return L._pnode != _pnode;
}
};
template<class T>
class My_list
{
typedef ListNode<T> Node;
public:
typedef list_iterator<T> iterator; 优化2:告诉编译器这里对自定义类型定义个别名,在这里使用
........
........
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
........
........
};
外部使用就变为:
My_list<int>::lierator it = L.begin();
iterator算是通过My_list调用的,My_list显示实例化后,确定模板是int,模板参数class T就是int
同时确定了typedef list_iterator 是 list_iterator,list_iterator的模板参数就也确定为int
假如外部传参是 const修饰过的,那么*解引用符号在这里就不对了
void print(const My_list<int>& L)
{
My_list<int>::iterator it = L.begin();
while(it != L.end())
{
*it = 10;
cout << *it <<" ";
++it;
}
cout << endl;
}
void test4()
{
My_list<int> L1;
L1.push_back(1);
L1.push_back(2);
L1.push_back(3);
L1.push_back(4);
print(L1);
}
解决办法一:修饰为const 类型的对象
const My_list
但这样做,需要把所有的重载运算符都 实现一遍const修饰的,代码冗余,并且修饰完++也不可以使用了,所以这个方法不可用
既然要确保L、it 的权限只读的,也就是 it 只能读解引返回的data别名不能对data本身进行写入的操作
解决办法二:对迭代器接口、解引用符重载一个const的版本
但通过测试用例发现, *it 会自动匹配到可更改那个,这个办法也不可以用
所以真正的问题是 iterator对象本身,这个对象就代表可更改的,就算内部重载出 const修饰的函数、运算符,外部发生赋值时也会自动匹配到没有const的那个
解决办法三:再创建一个类,只存创建来类中成员函数const版本
这个办法是行的通的,但是当项目很大,成员函数很多时,这样的代码就很冗余,所以这个方法可行但不推荐
解决办法四:通过增加模板参数对指定成员函数修改
因为迭代器类的别名定义在My_list中,也是通过My_list调用并同时确定参数模板的实例是什么,所以可以对模板增加参数
具体步骤如下:
1.对迭代器类模板增加参数
template<class T,class Ref>
struct list_iterator
{
typedef ListNode<T> Node;
typedef list_iterator<T,Ref> self;
Node* _pnode;
.....
.....
.....
.....
}
2.指定My_list类中 迭代器的模板参数
template<class T>
class My_list
{
typedef ListNode<T> Node;
public:
typedef list_iterator<T,T&> iterator;
typedef list_iterator<T,const T&> const_iterator;
3.改变解引用符的函数返回值,和增加迭代器函数const版本
const_iterator begin()
{
return const_iterator(_head->_next);
}
const_iterator end()
{
return const_iterator(_head);
}
Ref operator*()
{
return _pnode->_data;
}
这时外部形参因为是const修饰的,所以自动匹配到 const_iterator begin()const版本,同时确定迭代器类模板参数是int,第二个参数是const int&,而解引用运算符是Ref,正是第二个模板参数,根据传入的参数确定是const int& ,所以也就达到了只读不可写的权限
按照上述,可以再增加一个 T*(使用场景例如 ->、&看具体需求重载相应的运算符,返回的是地址)
typedef list_iterator<T,T&,T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
-------------------------------------------------------------------------------
template<class T,class Ref,class Ptr>
struct list_iterator
{
typedef ListNode<T> Node;
typedef list_iterator<T,Ref,Ptr> self;
Node* _pnode;
Ptr operator->()
{
return &_pnode->_data;
}
#pragma once
#include
//#include //list ——双向带头循环列表
using namespace std;
#include
#include "reverse_iterator.h"
namespace dd
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
};
template<class T,class Ref,class Ptr>
struct list_iterator
{
typedef ListNode<T> Node;
typedef list_iterator<T,Ref,Ptr> self; //优化1
Node* _pnode;
list_iterator(Node* pnode)
:_pnode(pnode)
{}
//前置++
self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
//后置++
self operator++(int)
{
self temp(*this);
_pnode = _pnode->_next;
return temp;
}
//前置--
self& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
//后置--
self operator--(int)
{
self temp(*this);
_pnode = _pnode->_prev;
return temp;
}
//解引用
Ref operator*()
{
return _pnode->_data;
}
Ptr operator->()
{
return &_pnode->_data;
}
//判断不相等
bool operator!=(const self& L)
{
return L._pnode != _pnode;
}
};
template<class T>
class My_list
{
typedef ListNode<T> Node;
public:
typedef list_iterator<T,T&,T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
My_list()
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
}
void push_back(const T& val)
{
Node* newnode = new Node(val);
Node* tail = _head->_prev;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
tail->_next = newnode;
}
void pop_back()
{
assert(_head->_prev != _head);
Node* earse = _head->_prev;
Node* tail = earse->_prev;
tail->_next = _head;
_head->_prev = tail;
delete earse;
//earse = nullptr;
}
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);
}
private:
Node* _head;
};
}
在指定位置的前面插入
iterator insert(iterator pos,const T& val)
{
Node* newnode = new Node(val);
Node* cur = pos._pnode;
Node* front = cur->_prev;
newnode->_next = cur;
newnode->_prev = front;
cur->_prev = newnode;
front->_next = newnode;
return iterator(newnode);
}
删除指定位置
iterator earse(iterator pos)
{
assert(pos._pnode != _head);
Node* front = pos._pnode->_prev;
Node* tail = pos._pnode->_next;
front->_next = tail;
tail->_prev = front;
delete pos._pnode;
return iterator(tail);
}
void push_fornt(const T& val)
{
insert(_head->_next,val);
}
void pop_front()
{
earse(_head->_next);
}
void push_back(const T& val)
{
insert(_head,val);
}
void pop_back()
{
earse(_head->_prev);
}
void clear()
{
iterator it = begin();
while(it != end())
{
iterator temp = it;
++it;
delete temp._pnode;
}
_head->_next = _head;
_head->_prev = _head;
}
或
void clear()
{
iterator it = begin();
while(it != end())
earse(it++);
}
~My_list()
{
clear();
delete _head;
_head = nullptr;
}
template<class InputIterator>
My_list(InputIterator first,InputIterator last)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
while(first != last)
{
push_back(*first++);
}
}
传统写法
My_list(const My_list<T>& cpy)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
const_iterator it = cpy.begin();
while(it != cpy.end())
{
push_back(*it++);
}
}
现代写法
My_list(const My_list<T>& it)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
My_list<T> temp(it.begin(),it.end());
swap(temp._head,_head);
}
传统写法
My_list<T> operator=(My_list<T>& cpy)
{
if(this != &cpy)
{
clear();
iterator it = cpy.begin();
while(it != cpy.end())
{
push_back(*it++);
}
}
return *this;
}
现代写法
My_list<T> operator=(My_list<T> it)
{
std::swap(_head,it._head);
return *this;
}
通过查看STL原码发现,反向迭代器的 rbegin 实际是 end()、rend() 实际是 begin(),那怎么实现?
1.和正向迭代器类似,把正向迭代器封装成一个类
template <typename Iterator>
class reverse_iterator
{
public:
typedef reverse_iterator<Iterator> self;
reverse_iterator(Iterator it)
:_cur(it)
{}
private:
Iterator _cur;
self& operator++()
{
--_cur;
return *this;
}
self operator++(int)
{
Iterator temp = _cur;
--_cur;
return temp;
}
self& operator--()
{
++_cur;
return *this;
}
self operator--(int)
{
Iterator temp = _cur;
++_cur;
return temp;
}
bool operator!=(const self& it)
{
return _cur != it._cur.;
}
bool operator==(const self& it)
{
return _cur == it._cur.;
}
这里重点说明一下解引用符重载:
按正常理解应该是,rbegin在最后一个有效数据的位置,rend在第一个有效数据的前一个位置
但在源码中查看,发现反向迭代器begin封装的是正向迭代器end,反向迭代器end封装的是正向迭代器begin
这么设计可能会让很多人疑惑,自己实现的时候也可以按正常理解去定义,但从复用的角度来看,数组和链表都可以复用
所以在解引用时,就需要调用正向迭代器- -之后的值
注意:这里也需要增加两个模板参数
template <typename Iterator,class Ref,class Ptr>
class reverse_iterator
{
public:
typedef <typename Iterator,class Ref,class Ptr> self;
..........
..........
..........
Ref operator*()
{
Iterator temp = _cur;
return *--temp;
}
先调用正向迭代器--操作符函数,在调用正向迭代器*解引用操作符函数
Ptr operator->()
{
return &*this->operator*();
}
调用反向迭代器的解引用,反向迭代器解引用的底层是正向迭代器的解引用
在取其地址就取到了_data的地址
}
还需要在My_list中增加反向迭代器接口函数
typedef reverse_iterator<iterator,const T&,const T*> const_reverse_iterator;
typedef reverse_iterator<iterator,T&,T*> reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rbegin()
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend()
{
return const_reverse_iterator(begin());
}
注意:若在不同的文件中,需要My_list包含 反迭代器的头文件,若在不同的域中还需要表明属于哪个域
测试结果如下:
void print(My_list<int>& L)
{
My_list<int>::iterator it = L.begin();
while(it != L.end())
{
cout << *it <<" ";
++it;
}
cout << endl;
}
void Rprint(const My_list<int>& L)
{
My_list<int>::const_reverse_iterator it = L.rbegin();
while(it != L.rend())
{
//*it = 10;
cout << *it <<" ";
++it;
}
cout << endl;
}
void test5()
{
My_list<int> L1;
L1.push_back(1);
L1.push_back(2);
L1.push_back(3);
L1.push_back(4);
My_list<int> L4(L1.begin(),L1.end());
print(L4);
Rprint(L4);
}
反向迭代器可以理解为,在内部构造一个正向迭代器类的对象,反向迭代器的各种运算符操作其实就是对正向迭代器函数接口的再封装
实例化对象过程如下:
解引用过程如下:
反向迭代器和正向迭代器的模板参数:
在My_list里调用函数接口时就确定了,会从迭代器函数接口向上找到重定义的地方,之后就会给到迭代器类模板参数里
反向迭代器和正向迭代器是不需要深拷贝的,因为它们的作用是访问地址中的数据,所以系统默认生成的就够用了(赋值重载和拷贝构造系统默认生成的按字节序拷贝),只需要浅拷贝把地址复制一份存起来在外部去访问,并且也不需要析构,析构是My_list去完成的,迭代器析构会把原本的链接断掉,造成内存泄漏野指针等问题
My_list .h
#pragma once
#include
//#include //list ——双向带头循环列表
using namespace std;
#include
#include "reverse_iterator.h"
using namespace Hx;
namespace dd
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
};
template<class T,class Ref,class Ptr>
struct list_iterator
{
typedef ListNode<T> Node;
typedef list_iterator<T,Ref,Ptr> self; //优化1
Node* _pnode;
list_iterator(Node* pnode)
:_pnode(pnode)
{}
//前置++
self& operator++()
{
_pnode = _pnode->_next;
return *this;
}
//后置++
self operator++(int)
{
self temp(*this);
_pnode = _pnode->_next;
return temp;
}
//前置--
self& operator--()
{
_pnode = _pnode->_prev;
return *this;
}
//后置--
self operator--(int)
{
self temp(*this);
_pnode = _pnode->_prev;
return temp;
}
//解引用
Ref operator*()
{
return _pnode->_data;
}
Ptr operator->()
{
return &_pnode->_data;
}
//判断不相等
bool operator!=(const self& L)
{
return L._pnode != _pnode;
}
bool operator==(const self& L)
{
return !(*this != L);
}
};
template<class T>
class My_list
{
typedef ListNode<T> Node;
public:
typedef list_iterator<T,T&,T*> iterator;
typedef list_iterator<T,const T&,const T*> const_iterator;
typedef Hx::reverse_iterator<const_iterator,const T&,const T*> const_reverse_iterator;
typedef Hx::reverse_iterator<iterator,T&,T*> reverse_iterator;
My_list()
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
}
//清除
/*void clear()
{
iterator it = begin();
while(it != end())
{
iterator temp = it;
++it;
delete temp._pnode;
}
_head->_next = _head;
_head->_prev = _head;
}*/
void clear()
{
iterator it = begin();
while(it != end())
earse(it++);
}
//析构
~My_list()
{
clear();
delete _head;
_head = nullptr;
}
//迭代器构造
template<class InputIterator>
My_list(InputIterator first,InputIterator last)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
while(first != last)
{
push_back(*first++);
}
}
//拷贝构造
/*My_list(const My_list& cpy)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
const_iterator it = cpy.begin();
while(it != cpy.end())
{
push_back(*it++);
}
}*/
My_list(const My_list<T>& it)
{
_head = new Node();
_head->_prev = _head;
_head->_next = _head;
My_list<T> temp(it.begin(),it.end());
swap(temp._head,_head);
}
//赋值重载
/*My_list operator=(My_list& cpy)
{
if(this != &cpy)
{
clear();
iterator it = cpy.begin();
while(it != cpy.end())
{
push_back(*it++);
}
}
return *this;
}*/
My_list<T> operator=(My_list<T> it)
{
std::swap(_head,it._head);
return *this;
}
//尾插
/*void push_back(const T& val)
{
Node* newnode = new Node(val);
Node* tail = _head->_prev;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
tail->_next = newnode;
}
//尾删
void pop_back()
{
assert(_head->_prev != _head);
Node* earse = _head->_prev;
Node* tail = earse->_prev;
tail->_next = _head;
_head->_prev = tail;
delete earse;
//earse = nullptr;
}*/
//正向迭代器
//可读可写
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);
}
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rbegin()const
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend()const
{
return const_reverse_iterator(begin());
}
//随机插(前)
iterator insert(iterator pos,const T& val)
{
Node* newnode = new Node(val);
Node* cur = pos._pnode;
Node* front = cur->_prev;
newnode->_next = cur;
newnode->_prev = front;
cur->_prev = newnode;
front->_next = newnode;
return iterator(newnode);
}
//随机删
iterator earse(iterator pos)
{
assert(pos._pnode != _head);
Node* front = pos._pnode->_prev;
Node* tail = pos._pnode->_next;
front->_next = tail;
tail->_prev = front;
delete pos._pnode;
return iterator(tail);
}
//头删头插尾删尾插复用
void push_fornt(const T& val)
{
insert(_head->_next,val);
}
void pop_front()
{
earse(_head->_next);
}
void push_back(const T& val)
{
insert(_head,val);
}
void pop_back()
{
earse(_head->_prev);
}
private:
Node* _head;
};
}
反向迭代器.h
#pragma once
using namespace std;
namespace Hx
{
template <typename Iterator,typename Ref,typename Ptr>
class reverse_iterator
{
public:
typedef reverse_iterator<typename Iterator,typename Ref,typename Ptr> self;
reverse_iterator(Iterator it)
:_cur(it)
{}
Ref operator*()
{
//self prev = _cur;//error自己调自己
Iterator temp = _cur;
return *--temp;
}
Ptr operator->()
{
return &*this->operator*();
}
self& operator++()
{
--_cur;
return *this;
}
self operator++(int)
{
Iterator temp = _cur;
--_cur;
return temp;
}
self& operator--()
{
++_cur;
return *this;
}
self operator--(int)
{
Iterator temp = _cur;
++_cur;
return temp;
}
bool operator!=(const self& it)
{
return _cur != it._cur;
}
bool operator==(const self& it)
{
return _cur == it._cur.;
}
private:
Iterator _cur;
};
}
vector优势:
可以支持随机访问(下标访问)
底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高
vector缺陷:
1.空间不够需要增容,增容代价比较大,要深拷贝数据等等
2.按需申请空间,会导致频繁申请,所以一般扩为2或1.5倍,但存在一定的空间浪费
3.头删头插或中部等距离尾端有一定距离的增删,需要挪动数据,效率低下
适用高校访问,对插入删除效率要求不高的场景
list的优势:
增删效率高,不存在空间浪费
list缺陷:
不能随机访问,节点与节点之间不是连续的地址,缓存命中率低
适用于大量插入删除不需要随机访问的场景
vector和list本质上是互补的,vector的迭代器是原生的,list是类封装结点指针,实际应中vector相对比list多一些