手把手教你实现C++的list容器

目录

一、list 的结构剖析

二、迭代器的实现

1.迭代器类的设计与实现

 1.1模板参数:

1.2成员变量与函数:

1.3 运算符重载:

2.const 迭代器的实现

三、list 类的实现

1.基本框架与成员变量

2.构造函数

3.析构函数和clear函数

4.插入与删除

1.插入函数

2.头插与尾插

3.删除函数

4.头删和尾删

5.访问函数

6.其他函数

四、完整代码


一、list 的结构剖析

      list 作为 C++ STL 中的一员,其底层结构是带头双向循环链表。这种结构的设计,赋予了 list 独特的性能优势。与普通的双向链表不同,带头双向循环链表有一个头节点,它不存储实际的数据,主要作用是简化链表的操作,使得链表在进行插入、删除等操作时,无需对空链表等特殊情况进行额外的处理。同时,链表的双向特性,使得我们可以从任意一个节点出发,向前或向后遍历链表;而循环特性,则保证了链表的最后一个节点的后继指针指向头节点,头节点的前驱指针指向最后一个节点,形成一个完整的环。

       在带头双向循环链表中,每个节点由数据域和指针域组成。数据域用于存储实际的数据,它可以是任意类型,这使得 list 具有很强的通用性;指针域则包含两个指针,一个指向前驱节点(prev),另一个指向后继节点(next)。通过这两个指针,节点之间形成了双向的链接关系,使得链表的遍历和操作变得更加灵活。下面是节点实现代码:

template <class T>
class ListNode {
	ListNode*  _prev;// 前驱指针,指向上一个节点
	ListNode*  _next;// 后继指针,指向下一个节点
	T _val;// 数据域,存储数据

	ListNode(const T& val=T())
		: _prev(nullptr)
		, _next(nullptr)
	    , _val(val)
	{ }
};

二、迭代器的实现

1.迭代器类的设计与实现

 1.1模板参数:

在设计 list 迭代器类的时候,通常会用到三个模板参数,分别是数据类型 T、引用类型 Ref 和指针类型 Ptr ,这么做是为了区分普通迭代器和 const 迭代器。在普通迭代器里,Ref 是 T &,Ptr 是 T* ,可以对元素进行读写操作在 const 迭代器里,Ref 是 const T &,Ptr 是 const T *,只能读取元素。通过这种方式,就能在同一个迭代器类模板里实现两种迭代器的功能,提高代码的复用性,让代码能被更高效地使用。

1.2成员变量与函数:

迭代器类最关键的成员变量是指向 list 节点的指针,凭借它迭代器可在链表节点中自由访问和操作节点。构造函数则根据传入的节点指针初始化迭代器,使其能准确指向正确节点。

1.3 运算符重载:

为了使迭代器能够像指针一样使用,我们需要对一些常用的运算符进行重载,包括解引用运算*、箭头运算符->、自增运算符++、自减运算符--、比较运算符!=和==等。

template<class T,class Ref,class Ptr>
struct List_iterator {
	typedef ListNode<T> Node;
	typedef List_iterator<T, Ref, Ptr> self;

	Node* _node;

	List_iterator(Node* node)
		:_node(node)
	{ }
	Ref operator*()
	{
		return _node->_val;
	}
	Ptr operator->()
	{
		return &(_node->val);
	}
	// 前置自增
	self operator++()
	{
		_node = _node->_next;
		return *this;
	}
	self operator++(int)
	{
		self tmp(*this);
		tmp._node =tmp._node->_next;
		return tmp;
	}
	self operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	self operator++(int)
	{
		self tmp(*this);
		tmp._node = tmp._node->_prev;
		return tmp;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}

	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

2.const 迭代器的实现

const 迭代器和普通迭代器最大的不同就是,const 迭代器只能读取容器里的元素,没办法修改元素的值。在实现 const 迭代器的时候,我们可以用模板参数来区分普通迭代器和 const 迭代器。当使用 const 迭代器时,把模板参数 Ref 和 Ptr 分别设为 const T & 和 const T* ,这样在解引用和访问成员的时候,返回的就是 const 类型的引用和指针,也就保证了元素是常量,不能被修改。

在 list 类中,通常会提供两个版本的begin和end函数,一个返回普通迭代器,另一个返回 const 迭代器。这样,当我们使用 const 对象时,就会调用返回 const 迭代器的函数,确保在遍历过程中不会意外修改容器中的元素。代码如下:

template<class T>
class list {
public:
	typedef ListNode<T> Node;
	typedef List_iterator<T, T&, T*> iterator;
	typedef List_iterator<T, cosnt T&, const T*>  const_iterator;
	iterator begin()​
	{
	  return iterator(_head->_next); ​
	}​
	iterator end()
	{
	  return iterator(_head->_prev);
	}
	  const_iterator begin() const
	{
	   return const_iterator(_head->_next); ​
	}
	  const_iterator end() const
    {
	   return const_iterator(_head->_prev);
	}
private:
	Node* _head;
};

三、list 类的实现

1.基本框架与成员变量

#pragma once
#include<iostream>
using namespace std;
namespace my_list{
	template <class T>
	struct ListNode {
		ListNode*  _prev;
		ListNode*  _next;
		T _val;
		ListNode(const T& val=T())
			: _prev(nullptr)
			, _next(nullptr)
		    , _val(val)
		{ }
	};
	template<class T,class Ref,class Ptr>
	struct List_iterator {
		typedef ListNode<T> Node;
		typedef List_iterator<T, Ref, Ptr> self;
		Node* _node;

		List_iterator(Node* node)
			:_node(node)
		{ }
		Ref operator*()
		{
			return _node->_val;
		}
		Ptr operator->()
		{
			return &(_node->_val);
		}
		// 前置自增
		self operator++()
		{
			_node = _node->_next;
			return *this;
		}
		self operator++(int)
		{
			self tmp(*this);
			tmp._node =tmp._node->_next;
			return tmp;
		}
		self operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			tmp._node = tmp._node->_prev;
			return tmp;
		}
		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		bool operator==(const self& s)
		{
			return _node == s._node;
		}
	};
	template<class T>
	struct  list {
		typedef ListNode<T> Node;
		typedef List_iterator<T, T&, T*>  iterator;
		typedef List_iterator<T, const T&, const T*> const_iterator;
	private:
		Node* _head;
	public:
		
	};
}

我们将双向链表的实现放在一个自定义命名空间my_list中,以避免命名冲突。整个代码主要包含两个模板结构体:ListNode和List_iterator,以及一个模板类list。ListNode和List_iterator在上面已经实现好了,接下来我们将实现各个成员函数。

2.构造函数

1.无参构造

 list()
 {
     _head = new Node;
     _head->_next = _head;
     _head->_prev = _head;
 }

2.拷贝构造


list(const list<T>& l)
{
    _head = new Node;
    _head->_next = _head;
    _head->_prev = _head;
    for (const_iterator it = l.begin(); it != l.end(); ++it)
    {
        push_back(*it);
    }
}

拷贝构造函数,用于创建一个新对象,该对象是另一个同类型对象的副本。拷贝构造函数的参数通常是一个对同类对象的常量引用。里面用到的push_back()函数后面我们会实现。

3.析构函数和clear函数

  ~list()
 {
     clear();
     delete _head;
     _head = nullptr;
 }

 void clear()
 {
     Node* cur = _head->_next;
     while (cur != _head)
     {
         Node* tmp = cur->_next;
         delete cur;
         cur = tmp;
     }
     cur = nullptr;
     _head->_next = _head;
     _head->_prev = _head;
 }

这里我们通过调用 clear() 函数清空链表中的所有节点,然后释放头节点的内存,并将头指针置为 nullptr,实现析构函数。而clear()主要用于清空链表中的所有节点,只保留头节点。

4.插入与删除

1.插入函数

iterator insert(iterator pos, const T& val)
{
    Node* cur = new Node(val);
    Node* temp = pos._node;
    Node* next = pos._node->_next;
    temp->_next = cur;
    cur->_prev = temp;
    cur->_next = next;
    next->_prev = cur;
    return iterator(cur);
}

我们在这里用temp存取要插入位置的节点和用next存取它下一个节点,用cur动态分配一个空间存val,让temp后驱指针指向cur,cur的前驱指针指向temp,cur的后驱指针指向next,next的前驱节点指向cur,这样我们就完成了在任意位置的插入。

2.头插与尾插

  void push_back(const T& val)
  {
      iterator it = --end();
      insert(it, val);
  }

  void push_front(const T& val)
  {
      iterator it = begin();
      insert(it, val);
  }

头插函数,我们先获取指向链表第一个节点的迭代器,然后调用 insert() 函数在该位置插入新节点。尾插也一样,我们先获取最最后一个节点的指针,然后插入。

3.删除函数

iterator erase(iterator pos)
{
    Node* cur = pos._node->_prev;
    Node* next = pos._node->_next;
    cur->_next = next;
    next->_prev = cur;
    delete pos._node;
    return iterator(next);
}

这里,我们用cur存取要删位置的前一个节点,next存取要删的节点的下一个节点。然后让cur的后驱指针指向next,next的前驱指针指向cur,最后释放要删节点的空间,这样就完成了删除。

4.头删和尾删

 void pop_back()
 {
     erase(--end());
 }

 void pop_front()
 {
     erase(begin());
 }

这里我们直接调用删除函数,分别传入最后一个节点的位置,和第一个节点的位置即可完成尾删和头删。

5.访问函数

 T& front()
 {
     return _head->_next->_val;
 }

 T& back()
 {
     return _head->_prev->_val;
 }

我们直接通过头节点便可访问最后一个元素和第一个元素。

6.其他函数

1.empty():判断链表是否为空。

bool empty()
{
    return (_head->_next == _head);
}

2.size():返回链表的元素个数。

  size_t size()
  {
      size_t count = 0;
      for (const_iterator it = begin(); it != end(); ++it)
      {
          count++;
      }
      return count;
  }

四、完整代码

#pragma once
#include<iostream>
using namespace std;

namespace my_list {
    template <class T>
    struct ListNode {
        ListNode* _prev;
        ListNode* _next;
        T _val;
        ListNode(const T& val = T())
            : _prev(nullptr)
            , _next(nullptr)
            , _val(val)
        {
        }
    };

    template<class T, class Ref, class Ptr>
    struct List_iterator {
        typedef ListNode<T> Node;
        typedef List_iterator<T, Ref, Ptr> self;
        Node* _node;

        List_iterator(Node* node)
            :_node(node)
        {
        }

        Ref operator*()
        {
            return _node->_val;
        }

        Ptr operator->()
        {
            return &(_node->_val);
        }

        // 前置自增
        self operator++()
        {
            _node = _node->_next;
            return *this;
        }

        self operator++(int)
        {
            self tmp(*this);
            tmp._node = tmp._node->_next;
            return tmp;
        }

        self operator--()
        {
            _node = _node->_prev;
            return *this;
        }

        self operator--(int)
        {
            self tmp(*this);
            tmp._node = tmp._node->_prev;
            return tmp;
        }

        bool operator!=(const self& s)
        {
            return _node != s._node;
        }

        bool operator==(const self& s)
        {
            return _node == s._node;
        }
    };

    template<class T>
    struct  list {
        typedef ListNode<T> Node;
        typedef List_iterator<T, T&, T*>  iterator;
        typedef List_iterator<T, const T&, const T*> const_iterator;
    private:
        Node* _head;
    public:
        iterator begin()
        {
            return iterator(_head->_next);
        }

        iterator end()
        {
            return iterator(_head);
        }

        const_iterator begin()const
        {
            return const_iterator(_head->_next);
        }

        const_iterator end()const
        {
            return const_iterator(_head);
        }

        list()
        {
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }

        list(const list<T>& l)
        {
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
            for (const_iterator it = l.begin(); it != l.end(); ++it)
            {
                push_back(*it);
            }
        }

        ~list()
        {
            clear();
            delete _head;
            _head = nullptr;
        }

        void clear()
        {
            Node* cur = _head->_next;
            while (cur != _head)
            {
                Node* tmp = cur->_next;
                delete cur;
                cur = tmp;
            }
            cur = nullptr;
            _head->_next = _head;
            _head->_prev = _head;
        }

        iterator insert(iterator pos, const T& val)
        {
            Node* cur = new Node(val);
            Node* temp = pos._node;
            Node* next = pos._node->_next;
            temp->_next = cur;
            cur->_prev = temp;
            cur->_next = next;
            next->_prev = cur;
            return iterator(cur);
        }

        void push_back(const T& val)
        {
            iterator it = --end();
            insert(it, val);
        }

        void push_front(const T& val)
        {
            iterator it = begin();
            insert(it, val);
        }

        iterator erase(iterator pos)
        {
            Node* cur = pos._node->_prev;
            Node* next = pos._node->_next;
            cur->_next = next;
            next->_prev = cur;
            delete pos._node;
            return iterator(next);
        }

        void pop_back()
        {
            erase(--end());
        }

        void pop_front()
        {
            erase(begin());
        }

        T& front()
        {
            return _head->_next->_val;
        }

        T& back()
        {
            return _head->_prev->_val;
        }

        bool empty()
        {
            return (_head->_next == _head);
        }

        size_t size()
        {
            size_t count = 0;
            for (const_iterator it = begin(); it != end(); ++it)
            {
                count++;
            }
            return count;
        }
    };
}

好了,今天的分享就到这里了,我们下次再见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值