list的使用和模拟实现

博主个人主页:不是笨小孩
⏩专栏分类:数据结构与算法 C++ 刷题专栏 C语言
代码仓库:笨小孩的代码库
⏩社区:不是笨小孩
欢迎大家三连关注,一起学习,一起进步!!

在这里插入图片描述

list

  • list的使用
    • 构造函数
    • 迭代器
    • 插入删除数据
    • reverse和sort
    • list模拟实现

list的使用

list的底层是我们所说的双向带头循环链表,因此它的空间分配和vector不一样,它在内存中不是连续存放的。我们只讲常用的。

构造函数

list的使用和模拟实现_第1张图片

我们现阶段可以不用管那个alloc,我们可以看到,可以用n个val来初始化list,也可以用一段迭代器区间来初始化,也可以用一个list来拷贝构造。

void test()
{
	list<int> ls(10, 1);
	list<int> ls1(ls);
	list<int> ls2(ls1.begin(),ls1.end());
}

迭代器

list的使用和模拟实现_第2张图片
我们可以看到list也是支持迭代器的,所以我们可以用迭代器进行访问。

void test()
{
	list<int> ls(10, 1);
	list<int>::iterator it = ls.begin();

	while (it != ls.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

插入删除数据

1. push_back
在这里插入图片描述
尾插一个数据。
2. pop_back

在这里插入图片描述
尾删一个数据。

3. push_front
在这里插入图片描述
头插一个数据。

4. pop_front

在这里插入图片描述

头删一个数据。

5. insert
list的使用和模拟实现_第3张图片

在某个迭代器的位置的前面插入一个数据,这个有个返回值,返回的是插入后的那个数据的迭代器。在某个迭代器的位置前面插入n个val,在某个迭代器的位置的前面插入一段迭代器区间。insert不会导致迭代器失效。

6. erase
在这里插入图片描述

删除某个迭代器位置的值,删除一段迭代器区间,erase会导致迭代器失效,因此删除后需要返回删除后,那个位置的数据的迭代器。

void test()
{
	list<int> ls;
	//尾插
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	//头插
	ls.push_front(2);
	ls.push_front(3);
	//头删
	ls.pop_front();
	//尾删
	ls.pop_back();

	ls.insert(ls.begin(), 5);

	ls.erase(--ls.end());
	list<int>::iterator it = ls.begin();

	while (it != ls.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

reverse和sort

1. reverse
在这里插入图片描述
逆置链表。

2. sort
在这里插入图片描述

因为库里面的sort只能用来排序一段连续的空间的数据,所以链表就不能用,所以针对链表有一个专门的sort,但是比较慢,不推荐使用。

void test()
{
	list<int> ls;
	//尾插
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	
	ls.reverse();
	ls.sort();
	list<int>::iterator it = ls.begin();
	while (it != ls.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

list模拟实现

我们说list是双向带头循环链表,所以它的结构接很清晰了。

结构

template <class T>
	struct ListNode
	{
		T _date;
		ListNode<T>* _next;
		ListNode<T>* _prev;

		ListNode(const T& x = T())
			: _date(x)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};

因为我们在申请节点的时候会用一个初始值来初始化,所以我们可以给ListNode来一个构造函数。

list构造函数

为了修改方便我们会把结构typedef一下。
typedef ListNode Node;

我们开始需要申请一个节点,让他的前后指针都指向它自己。这里我们为了记录数量,我们添加了一个_size来记录容器中的数量。

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

迭代器

我们说迭代器是一种类似指针的东西,对于vector来说,它的底层是数组,可以++和–,但是对于我们链表来说,他是不支持++和–的,所以,我们的迭代器可以说是一个全新的类,我们要对这个类加入运算符重载,然后让他实现我们想要的操作。

但是我们迭代器分为迭代器和const迭代器,所以我们可以加两个模板参数,这样可以减少代码的冗余。
在这里插入图片描述
然后我们在list中定义的时候可以直接传T&和const T&就可以了。
在这里插入图片描述
这样我们就可以减少代码的冗余。

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 = nullptr)
           :_node(node)
       {}
       bool operator!=(const self& s )
       {
           return _node != s._node;
       }
       bool operator==(const self& s)
       {
           return _node == s._node;
       }
       //解引用
       Ref operator*()
       {
           return _node->_date;
       }
       //为了以后对自定义类型使用
       Ptr operator->()
       {
           return &(_node->_date);
       }
       //前置++
       self operator++()
       {
           _node = _node->_next;
           return *this;
       }
       //前置--
       self operator--()
       {
           _node = _node->_prev;
           return *this;
       }
       //后置++
       self operator++(int)
       {
           self tmp(*this);
           _node = _node->_next;
           return tmp;
       }
       //后置--
       self operator--(int)
       {
           self tmp(*this);
           _node = _node->_prev;
           return tmp;
       }
};

有了这个以后我们就可以在list里面开始完成迭代器相关的接口了。

  iterator begin()
  {
      return _head->_next;
  }

  iterator end()
  {
      return _head;
  }

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

  const_iterator end() const
  {
      return _head;
  }

插入和删除数据

插入和删除我们又6个接口,但是我们实现一个insert和erase后上下来直接来复用这两个接口就可以了。

  1. insert

我们需要申请一个新节点,从迭代器中拿到需要插入的节点,然后记录一下前一个节点,然后改变链接关系,最后返回插入后的那个节点就可以了。

iterator insert(iterator pos, const T& x)
{
    Node* cur = pos._node;
    Node* newnode = new Node(x);

    Node* prev = cur->_prev;

    prev->_next = newnode;
    newnode->_prev = prev;

    newnode->_next = cur;
    cur->_prev = newnode;
    _size++;

    return newnode;
}
  1. erase

我们拿到迭代器中的当前节点,然后拿到前一个节点和后一个节点,释放当前节点,改变链接关系,返回下一个节点就可以了。

iterator erase(iterator pos)
 {
     Node* cur = pos._node;

     Node* prev = cur->_prev;
     Node* next = cur->_next;

     delete cur;
     _size--;
     prev->_next = next;
     next->_prev = prev;

     return next;
 }

头插头删尾插尾删

头插就是在_head节点前插入,头删就是删除_head的下一个节点,尾删就是删除_head的前一个节点,尾插就是在_head的前面插入。

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

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

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

clear

clser可以用迭代器来清,遍历的同时删除。

void clear()
{
    iterator it = begin();
    while (it != end())
    {
        it = erase(it);
    }
}

拷贝构造析构和赋值重载

拷贝构造先初始化一下,然后遍历需要拷贝的list,依次尾插。

list(const list& ls)
{
    _head = new Node;
    _head->_next = _head;
    _head->_prev = _head;
    _size = 0;
    for (auto e : ls)
    {
        push_back(e);
    }
}

析构可以先clear一下,然后把哨兵位的头结点释放了就可以了。

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

赋值拷贝

我们可以直接让原数据拷贝给tmp,然后和this交换,就可以了,等这个函数结束,编译器会帮我们调用析构。

 void swap(list& s)
 {
     std::swap(_head, s._head);
     std::swap(_size, s._size);
 }
 list& operator=(list ls)
 {
     swap(ls);

     return *this;
 }

今天的分享就到这里,感谢大家的关注和支持。

你可能感兴趣的:(C++,list,windows,数据结构)