C++第二十四弹---从零开始模拟STL中的list(上)

2024-06-08 1034阅读

C++第二十四弹---从零开始模拟STL中的list(上)

✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1、基本结构

2、基本函数实现

2.1、默认构造函数

2.2、尾插数据

3、迭代器的封装

3.1、迭代器的基本结构

3.2、迭代器重载函数的实现

4、迭代器与list进行关联

4.1、使用迭代器打印链表数据

4.2、其他相关函数

总结


1、基本结构

namespace lin
{
	template
	struct ListNode//双向循环链表的基本结构
	{
		ListNode* _prev;//前驱指针
		ListNode* _next;//后继指针
		T _data;//数据值
        
        //不传值时使用T()默认值构造,传值则传值构造
		ListNode(const T& val = T())//默认构造 + 传值构造
			:_prev(nullptr)
			,_next(nullptr)
			,_data(val)
		{}
	};
    template
    struct ListIterator//迭代器封装类,成员都会被调用,因此使用struct
    {
	    typedef ListNode Node;
        Node* _node;//结点指针
    }
    template
    class list//链表模板类,成员变量定义及函数封装
    {
	    typedef ListNode Node;//将链表结构取别名,简化代码
    public:
        typedef ListIterator iterator;//迭代器重命名
    private:
	    Node* _head;//链表头指针
        size_t size;//链表长度
    }
}

上述代码实现了双向循环链表的基本结构,其中包含了四个部分:

1.namespace lin,命令空间 lin 是用于封装代码,避免同名类型和函数冲突。

2.在命名空间中,定义了模板类ListNode(双向循环链表基本结构),该类包含三个成员变量:

  • _prev : 存储指向前一个结点的指针
  • _next : 存储指向后一个结点的指针
  • _data : 存储数据

    ListNode类还实现一个有缺省值(T())的构造函数,如果构造函数没有提供参数,则使用T类型的默认构造来初始化_data,如果传值则使用该值来初始化_data,该构造函数也会将_prev和_next指针指向nullptr。

    3.模板类ListIterator(迭代器封装),该类包含一个成员变量,即链表的结点指针:

    为什么链表需要封装一个迭代器的类呢???

    1. 链表的物理空间是不连续的,是通过结点的指针依次链接。
    2. 不能像string和vector一样直接解引用去访问其数据。
    3. 结点的指针解引用还是结点,结点指针++还是结点指针。
    4. 在string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。

    4.模板类list(链表的基本成员变量及其函数接口),该类包含两个成员变量:

    • _head : 链表的头结点指针
    • _size : 链表的长度

      2、基本函数实现

      注意:我们实现的是带头双向循环链表。

      2.1、默认构造函数

      list()

      默认构造的函数功能是构造一个没有元素的空容器。

      思路:我们实现的是带头双向循环链表,因此默认构造时我们需要创建(new)一个头结点,并将链表长度初始化为0。

      //构造头结点函数
      void empty_init()
      {
      	_head = new Node;//创建新结点
      	_head->_next = _head;
      	_head->_prev = _head;
          _size = 0;
      }
      //默认构造 构造一个头结点
      list()
      {
      	empty_init();
      }

      为了后序使用方便,我们将构造头结点封装成了一个函数。 

       2.2、尾插数据

      为什么在第二个函数就写尾插呢?因为后面的函数会大量用到尾插函数。

      push_back()

      思想:

      • 先找到尾结点,即头结点的前一个结点。
      • 然后将尾结点,新结点以及头结点进行链接。
        void push_back(const T& val)
        {
        	//tail _head->_prev
        	Node* tail = _head->_prev;
        	Node* newnode = new Node(val);//创建一个值为val的新结点
        	//tail newnode _head //链接关系的顺序
        	tail->_next = newnode;
        	newnode->_prev = tail;
        	newnode->_next = _head;
        	_head->_prev = newnode;
        	_size++;//尾插之后长度要++
        }

        C++第二十四弹---从零开始模拟STL中的list(上)

        我们尾插完数据之后,想要遍历整个链表怎么遍历呢???

        我们在使用链表的时候是通过迭代器进行遍历,如下代码:

        void test_list1()
        {
        	list lt;
        	lt.push_back(1);
        	lt.push_back(2);
        	lt.push_back(3);
        	lt.push_back(4);
        	list::iterator it = lt.begin();
        	while (it != lt.end())
        	{
        		cout _data;//访问链表的data数据
        }

         operator!=

        重载两个迭代器不相等,指针不相等则返回true,相等则返回false。

        bool operator!=(const Self& lt)
        {
        	return _node != lt._node;//两个迭代器不相等即指针不相等
        }	

        operator==

        bool operator==(const Self& lt)
        {
        	return _node == lt._node;
        }

         注意:比较迭代器是否相等比较的是的地址。

        4、迭代器与list进行关联

        4.1、使用迭代器打印链表数据

        void test_list1()
        {
        	list lt;
        	lt.push_back(1);
        	lt.push_back(2);
        	lt.push_back(3);
        	lt.push_back(4);
        	list::iterator it = lt.begin();
        	while (it != lt.end())
        	{
        		cout _prev;
        	Node* newnode = new Node(val);
        	//prev newnode cur
        	prev->_next = newnode;
        	newnode->_prev = prev;
        	newnode->_next = cur;
        	cur->_prev = newnode;
        	_size++;
        }

        C++第二十四弹---从零开始模拟STL中的list(上)

        erase()

        删除pos位置的值,并返回删除前的下一个结点的地址。

        思路:

        • 先获取当前结点的地址
        • 然后通过前驱指针找到前一个结点地址,通过后继指针找到后一个结点的地址
        • 将prev 前驱指针与后继指针建立链接关系
        • 释放当前结点
        • 返回next结点
          iterator erase(iterator pos)//删除pos位置值,迭代器失效问题
          {
          	Node* cur = pos._node;
          	Node* prev = cur->_prev;
          	Node* next = cur->_next;
          	//prev next
          	prev->_next = next;
          	next->_prev = prev;
          	delete cur;
          	_size--;
          	return iterator(next);//返回迭代器中结点指针
          }

          头插尾插头删尾删

          复用insert()函数和erase()函数实现。

          C++第二十四弹---从零开始模拟STL中的list(上)

          void push_back(const T& val)
          {
          	insert(end(), val);//end()之前插入
          }
          void push_front(const T& val)
          {
              insert(begin(),val);//begin()之前插入
          }
          void pop_back()
          {
          	erase(--end());//删除end前面一个结点
          }
          void pop_front()
          {
              erase(begin());//删除begin位置结点
          }

          总结

          本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]