小工具知识网

当前位置: 首页 > 行业知识 > 【C++】list基本接口+手撕 list(详解迭代器)

行业知识

【C++】list基本接口+手撕 list(详解迭代器)

2026-10-29 07:05:17 来源:互联网转载

���母就像迭代器,封装了他们的脆弱...... 

手撕list目录:

一、list的常用接口及其使用

1.1list 构造函数与增删查改

1.2list 特殊接口

1.3list 排序性能分析

二、list 迭代器实现(重点+难点)

关于迭代器的引入知识:

2.1迭代器的分类

2.2 list 迭代器失效问题(和vector有差异)

2.3list 迭代器源码模板

2.4list 整体基本框架

三、手撕list迭代器

3.1重载operator*()

3.2重载++、–、!=

3.3 利用类模板优化

四、增删查改

4.1 insert(参数必须加引用,担心非内置类型)和erase

4.2 push_back和push_front

4.3  pop_back和pop_front

五、list 构造+赋值重载

5.1默认构造+迭代器区间构造+拷贝构造

5.2 赋值重载现代写法

5.3 类名和类型的问题(C++的一个坑)

六、list和vector的对比(重点)

七、源码合集


一、list的常用接口及其使用

1.1list 构造函数与增删查改

list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,其底层是带头双向循环链表;list 常用接口的使用和 string、vector 系列容器的接口使用一样,这里我不详细介绍,请看我们的老朋友:cplusplus.com - The C++ Resources Network

构造函数:

构造函数(constructor)接口说明
list (size_type n, const value_type& val = value_type())构造的 list 中包含n个值为val的元素
list()构造空的 list
构造空的 list拷贝构造函数
list (InputIterator first, InputIterator last)用 [first, last) 区间中的元素构造 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中的有效元素

注意:

1、由于 list 的物理结构是非连续的 – 前一个节点地址和后一个节点地址的位置关系是随机的,所以 list 不支持随机访问,自然也就不支持 [ ] 操作;

2、list 不支持reserve操作,因为 list 的节点是使用时开辟,使用完销毁,不能预留空间;

(从这个特点也容易看出来,如果需要一直插入删除元素,利用list更好)

1.2list 特殊接口

除了上述 STL 容器基本都有的一般接口外,list 还提供一些独有的特殊操作接口,如下:

函数声明接口说明
splice将 list1 中的元素转移到 list2 中
remove移除 list 中的指定元素
unique链表去重(sort之后才可以用)
merge合并两个链表
sort链表排序(探究为什么list自己写sort)
reverse链表逆置

题外话: 为什么list需要自己实现sort接口??难道说库中的封装性不好?效率不高?

 我们先使用库中自己的sort函数:

我们使用算法库中的sort函数:

void test_sort(){     list l1{ 5,6,4,8,9,2,7 };//C++ 11写法     sort(l1.begin(),l1.end());     for(auto l : l1)     {        cout _data;}

 3.2重载++、–、!=

   __list_iterator& operator++(){_pnode = _pnode->_next;return *this;}__list_iterator& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const __list_iterator& it){return _pnode != it._pnode;}

 如果按照上面的做法,我们在来看看此时普通迭代器和const迭代器的区别:

//typedef __list_iterator iterator;//typedef __list_const_iterator const_iterator;    templatestruct __list_iterator{typedef list_node node;node* _pnode;__list_iterator(node* p):_pnode(p){}T& operator*(){return _pnode->_data;}__list_iterator& operator++(){_pnode = _pnode->_next;return *this;}__list_iterator& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const __list_iterator& it){return _pnode != it._pnode;}};//跟普通迭代器的区别:遍历,不能用*it修改数据templatestruct __list_const_iterator{typedef list_node node;node* _pnode;__list_const_iterator(node* p):_pnode(p){}const T& operator*(){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;}};

代码冗余!!!代码冗余!!!代码冗余!!!

如果是这样子去实现的话,我们就会发现,这两个迭代器的实现并没有多大的区别,唯一的区别就在于operator*的不同。const迭代器和普通迭代器的唯一区别就是普通迭代器返回T&,可读可写,const迭代器返回const T&,可读不可写。我们可以参考源码的实现:类模板参数解决这个问题,这也是迭代器的强大之处

3.3 利用类模板优化

template //typedef __list_iterator iterator;//typedef __list_iterator const_iterator;

利用类模板参数修正之后的代码:

    //  typedef __list_iterator iterator;//  typedef __list_iterator const_iterator;templatestruct __list_iterator{typedef list_node node;typedef __list_iterator Self;node* _pnode;__list_iterator(node*p):_pnode(p){}//返回数据的指针Ptr operator->(){return &_pnode->_data;}//模板参数做返回值Ref operator *(){return _pnode->_data;}//++itSelf& operator ++(){_pnode = _pnode->_next;return *this;}//it++Self operator ++(int){Self tmp(*this);_pnode = _pnode->_next;return tmp;}Self& operator--(){_pnode = _pnode->_prev;return *this;}Self operator--(int){Self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator !=(const Self& it)const{return _pnode != it._pnode;}bool operator ==(const Self& it)const{return _pnode == it._pnode;}};

同一个类模板,此时我们传递不同的参数实例化成不同的迭代器了!!!这解决了我们刚刚所说的代码冗余问题


四、增删查改

4.1 insert(参数必须加引用,担心非内置类型)和erase

insert:在pos位置上一个插入,返回插入位置的迭代器,对于list的insert迭代器不会失效,vector失效是因为扩容导致pos位置造成野指针问题

iterator insert(iterator pos,const T& x){node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}

 erase:这里的带头(哨兵位)头结点不可删除,返回值是删除位置的下一个,对于list的erase迭代器是失效的

iterator erase(iterator pos){assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;--_size;return iterator(next);}

4.2 push_back和push_front

        void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}

注意!list的begin和end的位置

同时这个问题还可以延伸出另一个问题:为什么迭代器访问元素的时候要这样写?

 在vector中,物理地址是连续的,这么写还情有可原,分析过list的begin和end之后,你还敢这么写吗??

 直接就报错了,所以正确的应该是!=,而不是

pre void test3(){ vectorint vv={1,5,7,8,9,3,4}; listint l={1,5,6,7}; vectorint::iterator it1=vv.begin(); listint::iterator it2=l.begin(); while(it1 _data;}Self& operator++() //前置++{_pnode = _pnode->_next;return *this;}Self& operator++(int) //后置++{Self it(*this);_pnode = _pnode->_next;return it;}Self& operator--() //前置--{_pnode = _pnode->_prev;return *this;}Self& operator--(int) //后置--{Self it(*this);_pnode = _pnode->_prev;return it;}bool operator!=(const Self& it) const //!={return _pnode != it._pnode;}bool operator==(const Self& it) const //=={return _pnode == it._pnode;}};//list 类templateclass list{typedef list_node node; //list 的节点public:typedef __list_iterator iterator; //迭代器typedef __list_iterator const_iterator; //const 迭代器//迭代器iterator begin() {return iterator(_head->_next);}iterator end() {//iterator it(_head);//return it;//直接利用匿名对象更为便捷return iterator(_head);}const_iterator begin() const {return const_iterator(_head->_next);}const_iterator end() const {return const_iterator(_head);}void empty_initialize() { //初始化 -- 哨兵位头结点_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0; //空间换时间,用于标记节点个数}list() { //构造,不是list的原因:构造函数函数名和类名相同,而list是类型empty_initialize();}//迭代器区间构造template list(InputIterator first, InputIterator last) {empty_initialize();while (first != last){push_back(*first);++first;//first++;}}//拷贝构造传统写法//list(const list& lt) {//empty_initialize();//for (const auto& e : lt)//{//push_back(e);//}//}// 拷贝构造的现代写法//list(const list& lt) 官方库是这样写的,这是由于在类内类名等价于类型,但不建议自己这样写list(const list& lt) {empty_initialize(); //初始化头结点,防止交换后tmp野指针不能正常的调用析构list tmp(lt.begin(), lt.end());swap(tmp);}//赋值重载传统写法//list& operator=(const list& lt) {//if (this != &lt)//{//clear();//for (const auto& e : lt)//{//push_back(e);//}//}//return *this;//}//赋值重载现代写法//list& operator=(list lt)list& operator=(list lt) { //不能加引用,lt是调用拷贝构造生成的swap(lt);return *this;}~list() { //析构clear();delete _head;_head = nullptr;}void swap(list& lt) { //交换两个链表,本质上是交换两个链表的头结点std::swap(_head, lt._head);std::swap(_size, lt._size);}size_t size() const { //增加一个计数的成员,以空间换时间return _size;}bool empty() { //判空return _size == 0;}void clear() {iterator it = begin();while (it != end()) {it = erase(it);}_size = 0;}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;insert(end(), x); //复用}void push_front(const T& x) {insert(begin(), x); //复用}void pop_front() {erase(begin());}void pop_back() {erase(--end());}iterator insert(iterator pos, const T& x) {node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;cur->_prev = newnode;newnode->_next = cur;++_size;return iterator(pos);}iterator erase(iterator pos) {assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;--_size;return iterator(next);}private:node* _head;size_t _size;};}

 

完结撒花~

上一篇:小红书下载教程,轻松掌握持续下载技巧

下一篇:实用教程,轻松关闭相机软件驱动!