【C++】-- STL之list模拟实现(一)

简介: 【C++】-- STL之list模拟实现

一、list模拟实现思路

list链表的模拟实现,与前面的string和vector相比,略复杂一点:

(1)由于链表节点本身就是一个结构体,包含数据域、指针域。 将节点的相关操作放入节点类进行处理,逻辑更清晰

(2)string和vector的元素在空间上是连续分布的,迭代器++就能指向下一个元素,但list的迭代器不行,它的每个元素在空间上都不连续,要访问下一个节点必须找到当前节点的next,因此list的迭代器必须重写

链表主要模拟实现以下功能:

为了和库里面的list区分开,使用命名空间delia将 list类和库里list隔离开

1. namespace delia
2. {
3. }

二、节点类的实现

节点类的成员变量有3个:

(1)节点值_val

(2)指向前一个节点的指针_prev

(3)指向后一个节点的指针_next

节点无需拷贝构造、赋值运算符重载,由于没有额外申请空间,因此也不需要析构

1.  template<class T>
2.  struct _list_node
3.  {
4. //3个成员变量
5.    T _val;
6.    _list_node<T>* _next;
7.    _list_node<T>* _prev;
8. 
9. //构造函数
10.     _list_node(const T& val = T())
11.       :_val(val) 
12.       , _prev(nullptr)
13.       , _next(nullptr)
14.     {}; 
15.   };

三、list迭代器的实现

由于节点的指针原生行为不满足迭代器定义,在这里,迭代器通过类来封装节点的指针重载运算符,如operator*、operator->、operator++等。用迭代器封装节点指针的思路不关心容器底层结构是数组、链表还是树等数据结构,都封装隐藏了底层细节,可以用一种统一的方式去访问、修改容器。

迭代器分为普通迭代器和const迭代器,对于_list_iterator类要实现两个版本,一个是普通的_list_iterator,另一个是const版本的_list_const_iterator,区别在于:对于两个类中的部分函数有普通函数和const函数之分(如begin( )和end( )),其他并无区别。因为这两个类的大部分代码相似,会造成代码冗余,如何解决呢?

对于T&,类模板实例化出两个类,一个是T&类,一个是const T&类,同理,T*也一样。使用

template<class T,class Ref,class Ptr>

类模板就会实例化出来两个类,一个是普通的不带const的T,T&, T*,另一个是带const的T,const T&, const T*,其中Ref是引用,Ptr是指针,该类模板实例化了以下这两个类模板:

1. template _list_iterator<T, T&, T*> _iterator;
2. template _list_iterator<T, const T&, const T*> const_iterator;

这就解决了需要写两个类的问题。

1._list_iterator类

迭代链表的节点,只需要一个成员变量即节点

1. template<class T,class Ref,class Ptr>
2.  struct _list_iterator//使用_list_iterator类来封装node*
3.  {
4.    typedef _list_node<T> node;
5.    typedef _list_iterator<T,Ref,Ptr> self;
6. 
7.    node* _pnode;//成员变量
8.     }

2.构造函数

只需要初始化节点即可

1.    //构造函数
2.    _list_iterator(node* pnode)
3.      :_pnode(pnode)
4.    {}

拷贝构造、赋值运算符重载、析构函数,编译器默认生成即可

3.operator* 运算符重载

1.    //解引用,返回左值,是拷贝,因此要用传引用返回
2.    Ref operator*()
3.    {
4.      return _pnode->_val;
5.    }

4.operator-> 运算符重载

1.    //结构体指针解引用,返回节点值的指针
2.    Ptr operator->()
3.    {
4.      return &_pnode->_val;
5.    }

5.operator!= 运算符重载

1.    //!=重载
2.    bool operator!=(const self& s) const
3.    {
4.      return _pnode != s._pnode;
5.    }

6.operator==运算符重载

1.    //==重载
2.    bool operator==(const self& s) const
3.    {
4.      return _pnode == s._pnode;
5.    }

7.前置++

让迭代器走到下一个节点

1.    //前置++  it.operator(&it)
2.    self& operator++()
3.    {
4.      _pnode = _pnode->next;
5.      return *this;
6.    }

8.后置++

1.    //后置++ 返回++之前的值  it.operator(&it,0)
2.    self operator++(int)//需构造临时对象,临时对象不能用引用返回,所以self没有加&
3.    {
4.      self tmp(*this);
5.      _pnode = _pnode->_next;
6.      return tmp;
7.    }

9.前置--

让迭代器走到前一个节点

1.    //前置--  it.operator(&it)
2.    self& operator--()
3.    {
4.      _pnode = _pnode->prev;
5.      return *this;
6.    }

10.后置--

1.    //后置-- 返回--之前的值  it.operator(&it,0)
2.    self operator--(int)//需构造临时对象,临时对象不能用引用返回,所以self没有加&
3.    {
4.      self tmp(*this);
5.      _pnode = _pnode->_prev;
6.      return tmp;
7.    }


四、list类的实现

1.list类

list的成员只需要一个头节点,通过迭代器访问其他节点元素

1.  template<class T>
2.  class list
3.  {
4.    typedef _list_node<T> node;
5.  public:
6.    typedef _list_iterator<T,T&,T*> iterator;//重命名迭代器
7.    typedef _list_iterator<T, const T&, const T*> const_iterator;//重命名const迭代器
8.  private:
9.    node* _head;
10.   };

2.构造函数

1.    list()
2.    {
3.      _head = new node;//会调_list_node的构造函数
4.      _head->_next = _head;//整个链表只有头节点,先构造一个没有实际节点的链表
5.      _head->_prev = _head;//整个链表只有头节点,先构造一个没有实际节点的链表
6.    }

3.拷贝构造

创建一个空链表,再把参数链表节点的值一个一个尾插进去

1.    //拷贝构造 lt1(lt)
2.    list(const list<T>& lt)
3.    {
4.      //1.先构造一个只有头节点的空list:创建头节点,头节点的前一个是自己,头节点的后一个是自己
5.      _head = new node;
6.      _head->_next = _head;
7.      _head->_prev = _head;
8. 
9.      //2.将lt的元素全部尾插到新链表
10.       for (const auto& e : lt)
11.       {
12.         push_back(e);
13.       }
14.     }

4.赋值运算符重载

(1)传统的赋值运算符重载

1. //赋值运算符重载  lt1 = lt  传统写法
2.    list<T> operator=(const list<T>& lt)
3.    {
4. //链表已存在,只需将节点尾插进去即可
5.      if(this != lt)
6.      {
7.        for (auto& e : lt)
8.        {
9.          push_back(e);
10.         }
11.       }
12.     }

(2)现代的赋值运算符重载

1.    list<T> operator=(list<T>& lt)
2.    {
3.      swap(_head, lt->_head);
4.      return *this;
5.    }

其中,swap( )函数的执行过程如下:  

1. template <class T> void swap ( T& a, T& b )
2. {
3. T c(a); a=b; b=c;
4. }

在执行赋值运算符重载时,会调用拷贝构造函数执行深拷贝,然后再交换两个链表头节点的指针,把this要释放的资源交给临时对象,临时对象出了swap的函数作用域就被this要释放的资源也会被同时释放。

5.析构

(1)清除所有节点内容

(2)删除头节点,链表就销毁了

1.    //析构
2.    ~list()
3.    {
4.      clear();//先清除所有节点内容
5.      delete _head;//再删除头节点
6.      _head = nullptr;
7.    }

6.迭代器

(1)普通迭代器

1.    iterator begin()
2.    {
3.      return iterator(_head->_next);//头节点不存数据
4.    }
5. 
6.    iterator end()
7.    {
8.      return iterator(_head);//尾节点的下一个节点位置即头节点
9.    }

(2)const迭代器

1.    const_iterator begin() const
2.    {
3.      return const_iterator(_head->_next);//头节点不存数据
4.    }
5. 
6.    const_iterator end() const
7.    {
8.      return const_iterator(_head);//尾节点的下一个节点位置即头节点
9.    }

7.insert( )

(1)构造节点

(2)双向带头循环链表插入节点

1. //插入节点   
2. void insert(iterator pos, const T& x)
3.    {
4.      assert(pos._pnode);
5.      node* newnode = new node(x);//构造节点
6. 
7.      node* prev = pos._pnode->_prev;//为方便后续操作,给插入位置的前一个节点起了个名字,pos是迭代器对象
8. 
9. //插入节点
10.       newnode->_next = pos._pnode;
11.       pos._pnode->_prev = newnode;
12.       prev->_next = newnode;
13.       newnode->_prev = prev;
14. 
15.     }

8.erase( )

(1)删除节点

(2)更新指针指向

1.    //删除节点
2. iterator erase(iterator pos)
3.    {
4.      assert(pos._pnode);//判断该位置节点是否存在
5.      assert(pos != end());//end()是最后一个节点的下一个节点位置,也就是头节点,头节点不能删,需要断言
6. 
7.      node* prev = pos._pnode->_prev;//pos位置节点的前一个节点
8.      node* next = pos._pnode->_next;//pos位置节点的后一个节点
9. 
10. //删除节点
11.       delete pos._pnode;
12.       prev->_next = next;
13.       next->_prev = prev;
14. 
15.       return iterator(next);//删除之后pos失效,把下一个位置的迭代器给它
16.     }
相关文章
|
1月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
8月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
238 2
|
8月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
465 73
|
9月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
8月前
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
369 3
|
9月前
|
算法 C++ 容器
模拟实现c++中的list模版
模拟实现c++中的list模版
|
9月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
535 1
|
10月前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
267 21
|
9月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
11月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
251 1
下一篇
oss云网关配置