【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.     }
相关文章
|
2天前
|
设计模式 算法 C++
【C++】STL之迭代器介绍、原理、失效
【C++】STL之迭代器介绍、原理、失效
13 2
|
2天前
|
存储 C++ 容器
C++:STL - set & map
C++:STL - set & map
15 4
|
2天前
|
调度 C++ 容器
【C++】手搓 list 容器
本文我们实现了STL库中重要的list 的模拟实现,其中最重要莫过于迭代器的封装类的书写,这是前所未有的操作(对于我来说,我是第一次使用这种结构)。通过list 的模拟实现也帮我们巩固了类与对象的知识,也强化了指针操作的思路。欢迎大家讨论分析。
13 1
|
2天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽
16 0
|
2天前
|
存储 Serverless C++
【C++入门到精通】哈希 (STL) _ unordered_map _ unordered_set [ C++入门 ]
【C++入门到精通】哈希 (STL) _ unordered_map _ unordered_set [ C++入门 ]
10 1
|
2天前
|
存储 设计模式 算法
【C++/STL】stack和queue(容器适配器、优先队列、双端队列)
【C++/STL】stack和queue(容器适配器、优先队列、双端队列)
14 1
|
2天前
|
存储 编译器 C++
【C++/STL】list(常见接口、模拟实现、反向迭代器、)
【C++/STL】list(常见接口、模拟实现、反向迭代器、)
5 0
|
2天前
|
算法 C++ 容器
【C++/STL】vector(常见接口、模拟实现、迭代器失效)
【C++/STL】vector(常见接口、模拟实现、迭代器失效)
11 0
|
2天前
|
存储 算法 C++
详解C++中的STL(标准模板库)容器
【4月更文挑战第30天】C++ STL容器包括序列容器(如`vector`、`list`、`deque`、`forward_list`、`array`和`string`)、关联容器(如`set`、`multiset`、`map`和`multimap`)和容器适配器(如`stack`、`queue`和`priority_queue`)。它们为动态数组、链表、栈、队列、集合和映射等数据结构提供了高效实现。选择合适的容器类型可优化性能,满足不同编程需求。
|
2天前
|
设计模式 安全 算法
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
18 0