重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
目录🌈感谢阅读East-sunrise学习分享——list的介绍及模拟实现
成都创新互联公司是少有的成都网站设计、网站建设、营销型企业网站、重庆小程序开发、手机APP,开发、制作、设计、卖友情链接、推广优化一站式服务网络公司,从2013年成立,坚持透明化,价格低,无套路经营理念。让网页惊喜每一位访客多年来深受用户好评博主水平有限,如有差错,欢迎斧正🙏感谢有你
码字不易,若有收获,期待你的点赞关注💙我们一起进步
今天想分享介绍一下STL的容器之一list,以及进行模拟实现📌
有了之前对数据结构——链表的知识基础,其实list的底层结构也并不神秘了,而list相较于前面的string、vector容器特别的地方就在于其迭代器,今天我们重点放在list的模拟实现及迭代器的介绍
list底层结构是带头双向链表
templatestruct list_node
{list_node* _next;//指向下一个节点
list_node* _prev;//指向前一个节点
T _data;//节点数据
//构造函数
list_node(const T& x)
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{}
};
定义完list的节点,接下来即是list的主要结构
namespace qdy
{templatestruct list_node
{list_node* _next;
list_node* _prev;
T _data;
list_node(const T& x)
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{}
};
templateclass list
{typedef list_nodenode;
public:
list()
{ _head = new node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
//尾插
void push_back(const T& x)
{ node* newnode = new node(x);
node* tail = _head->_prev;
// _head tail newnode
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
private:
node* _head;//哨兵位的头节点
size_t _size;//记录节点个数
};
以上便是list的结构框架(简易版实现),目前仅有尾插的接口,用于对list容器中添加数据✏️
添加数据后我们想要遍历打印怎么办呢?那就需要用到STL六大组件之一的迭代器🧮
iterator的模式定义:“提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式。”
——《STL源码剖析》
🎈通俗理解:容器用于存放数据,存放数据便有访问数据读写数据的需求,STL六大组件之一的迭代器,便是给每个容器提供一个便于读取数据的方法
2.迭代器的功能分类迭代器是内嵌类型,通常定义为内部类或者直接定义在类中
3.迭代器失效对于list,迭代器在进行insert操作后不失效,在进行erase操作后失效❌
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
void TestListIterator1()
{int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
listl(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
l.erase(it);
++it;
}
}
// 改正
void TestListIterator()
{int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
listl(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{it = l.erase(it);
//为了避免迭代器失效的问题,erase接口提供了返回值可接收,该返回值为删除后的下一节点
}
}
4.list迭代器的模拟实现
1.普通迭代器在对迭代器模拟实现之前,我们要搞清楚list迭代器要有什么功能?
回顾之前string和vector迭代器的模拟实现,我们是直接将指针typedef为迭代器使用,因为string和vector的底层结构是顺序表,是一段连续的物理空间,所以通过使用原生指针便能符合其迭代器的需求了✔️
💥但是list的底层结构是链表,链表是按需开辟空间,并不是一段连续的物理空间,每个节点的物理地址并不连续,我们无法直接使用原生指针去+±-来遍历来访问节点。我们回顾刚开始接触C++学习的日期类,日期类中的“日期”是我们自己定义的一种自定义类型,无法使用内置操作符直接对日期进行运算操作(编译器又不认识那么多)所以我们是通过自己再去重定义日期的操作类,去重载操作符来满足需求。
🚩类和对象说:该我上场表演了
既然原生指针已经无法满足list迭代器的需求,那么我们可以自己定义一个迭代器,然后将节点指针封装起来,然后再根据我们具体的需求情况去重载各种运算符实现迭代器功能。
//用类封装迭代器
templatestruct __list_iterator
{typedef list_nodenode;
node* _pnode;//封装一个节点的指针
//用节点指针进行构造
__list_iterator(node* p)
:_pnode(p)
{}
//迭代器运算符的重载
T& operator*()
{return _pnode->_data;
}
__list_iterator& operator++()
{_pnode = _pnode->_next;
return *this;//返回的是迭代器
}
bool operator!=(const __list_iterator& it)
{return _pnode != it._pnode;
}
};
定义完迭代器后便能对我们添加了数据的list进行遍历打印了
1.迭代器遍历 2.范围for遍历(底层也是调用了迭代器)
void test_list1()
{listlt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
list::iterator it = lt.begin();
while (it != lt.end())
{cout<< *it<< " ";
++it;
}
cout<< endl;
for (auto e : lt)
{cout<< e<< " ";
}
cout<< endl;
}
定义完迭代器后,通过迭代器对容器数据进行访问,实际上是一种函数调用
2.const迭代器❌const迭代器的错误写法
typedef __list_iteratoriterator;
const list::iterator cit = lt.begin();
const之前我们修饰指针时有两种方法
const T* p1;
T* const p2;
正确的const迭代器应该是像p1的行为,保护指向的对象不被修改,但是迭代器本身是可以修改的
但是上述的const迭代器写法是保护了迭代器本身不能被修改,那么我们就不能++迭代器了
✔️正确写法:想实现const迭代器,无法对普通迭代器直接加const,需要再写一个const版本迭代器的类
templatestruct __list_const_iterator
{typedef list_nodenode;
node* _pnode;
__list_const_iterator(node* p)
:_pnode(p)
{}
const T& operator*()const
{return _pnode->_data;
}
__list_const_iterator& operator++()
{_pnode = _pnode->_next;
return *this;
}
__list_const_iterator& operator--()
{_pnode = _pnode->_prev;
return *this;
}
bool operator!=(const __list_const_iterator& it)
{return _pnode != it._pnode;
}
};
typedef __list_iteratorconst_iterator;
但是如果像上述一样,写一个普通迭代器再写一个const迭代器,代码看起来就十分的冗长。那么我们可以利用好类模板,类模板即是能根据模板和调用时的参数,根据实参类型推演产生函数的特定类型版本。这样,我们根据传入参数的不同,可以使得一份类模板生成两种类型的迭代器🧮
templatestruct __list_iterator
{typedef list_nodenode;
typedef __list_iteratorSelf;
node* _pnode;
__list_iterator(node* p)
:_pnode(p)
{}
Ref operator*()
{return _pnode->_data;
}
Self& operator++()
{_pnode = _pnode->_next;
return *this;
}
Self& operator--()
{_pnode = _pnode->_prev;
return *this;
}
bool operator!=(const Self& it)
{return _pnode != it._pnode;
}
};
typedef __list_iteratoriterator;
typedef __list_iteratorconst_iterator;
5.迭代器operator->的重载我们调用对象的成员变量成员函数是用.
实现,对指针解引用取其值是用*
实现,而当结构体要解引用是使用->
,再用->
取其成员变量。而假如现在list中就存放的是结构体类型的数据✏️
所以我们有必要对->
也进行重载
T* operator->()
{return &_pnode->_data;
}
重载完之后我们要怎么使用呢❓一共有3种方法
//坐标类
struct Pos
{int _row;
int _col;
Pos(int row = 0, int col = 0)
:_row(row)
,_col(col)
{}
};
void test()
{listlt;
lt.push_back(Pos(1,1));
lt.push_back(Pos(2,2));
lt.push_back(Pos(3,3));
// int* p --->*p
// Pos* p --->p->list::iterator it = lt.begin();
while (it != lt.end())
{cout<< (&(*it))->_row;
//*it取出容器数据(POS类) -- 再取地址访问解引用得到_row
cout<< it.operator->()->_row;
//it.operator->()是显式调用,然后再->解引用得到_row
cout<< it->_row;
//同第二种写法,编译器为了可读性,省略了一个->
++it;
}
}
💥但是当实际使用时,会发现有问题,那就是->的重载返回值为T*,这样一来无论是普通迭代器或const迭代器都能对其值进行修改,所以我们需要将operator->
的返回值改为泛型,然后针对不同的迭代器给不同的返回参数以示区分,如此一来,我们的迭代器模板又得再多一个参数啦📈
templatePtr operator->()
{return &_pnode->_data;
}
typedef __list_iteratoriterator;
typedef __list_iteratorconst_iterator;
6.迭代器的价值vector和list就像左右手一样,是互补配合的关系
vector的优点即是list的缺点,list的优点也是vector的缺点,实际使用时可按照需求择优选用或者结合使用
1.vectorvector的优点
🎈综上所述vector的优点都得益于其结构优势
vector的缺点
🎈迭代器失效
insert和erase均会导致迭代器失效
2.listlist的优点
list的缺点
🎈迭代器失效
insert不失效,erase失效
namespace qdy
{templatestruct list_node
{list_node* _next;
list_node* _prev;
T _data;
list_node(const T& x)
:_next(nullptr)
,_prev(nullptr)
,_data(x)
{}
};
templatestruct __list_iterator
{typedef list_nodenode;
typedef __list_iteratorSelf;
node* _pnode;
__list_iterator(node* p)
:_pnode(p)
{}
Ptr operator->()
{ return &_pnode->_data;
}
Ref operator*()
{ return _pnode->_data;
}
Self& operator++()
{ _pnode = _pnode->_next;
return *this;
}
Self& operator--()
{ _pnode = _pnode->_prev;
return *this;
}
Self operator--(int)
{ Self tmp(*this);
_pnode = _pnode->_prev;
return tmp;
}
bool operator!=(const Self& it)
{ return _pnode != it._pnode;
}
bool operator==(const Self& it) const
{ return _pnode == it._pnode;
}
};
templateclass list
{typedef list_nodenode;
public:
typedef __list_iteratoriterator;
//typedef __list_const_iteratorconst_iterator;
typedef __list_iteratorconst_iterator;
//构造函数
list()
{ empty_initialize();
}
~list()
{ clear();
delete _head;
_head = nullptr;
}
void clear()
{ iterator it = begin();
while (it != end())
{ it = erase(it);
}
}
const_iterator begin() const
{ return const_iterator(_head->_next);
}
const_iterator end() const
{ return const_iterator(_head);
}
iterator begin()
{ return iterator(_head->_next);
//iterator it(_head->_next);
//return it;
}
iterator end()
{ return iterator(_head);
}
void empty_initialize()
{ _head = new node(T());
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
templatelist(InputIterator first, InputIterator last)
{ empty_initialize();
while (first != last)
{ push_back(*first);
++first;
}
}
void swap(list& lt)
{ std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
// 现代写法
// lt2(lt1)
list(const list& lt)
//list(const list& lt) // 不建议
{ empty_initialize();
listtmp(lt.begin(), lt.end());
swap(tmp);
}
// lt3 = lt1
list& operator=(listlt)
//list& operator=(list lt) // 不建议
{ swap(lt);
return *this;
}
//尾插
void push_back(const T& x)
{ //node* newnode = new node(x);
//node* tail = _head->_prev;
_head tail newnode
//newnode->_prev = tail;
//tail->_next = newnode;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
void push_front(const T& x)
{ insert(begin(), x);
}
void pop_back()
{ erase(--end());
}
void pop_front()
{ erase(begin());
}
iterator insert(iterator pos, const T& x)
{ node* newnode = new node(x);
node* cur = pos._pnode;
node* prev = cur->_prev;
//prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
return iterator(newnode);
}
iterator erase(iterator pos)
{ assert(pos != _head);
node* prev = pos._pnode->_prev;
node* next = pos._pnode->_next;
prev->_next = next;
next->_prev = prev;
delete pos._pnode;
--_size;
return iterator(next);
}
size_t size()const
{ return _size;
}
bool empty()const
{ return _size == 0;
}
private:
node* _head;
size_t _size;
};
}
🌈🌈写在最后
我们今天对list的分享就到此结束了
对于这篇博客最精华的部分便是迭代器的实现,迭代器在各种容器中是不可或缺的一部分🚩
🎈感谢能耐心地阅读到此
🎈码字不易,感谢三连
🎈关注博主,我们一起学习、一起进步
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧