当前位置: 首页 > article >正文

C++学习指南(六)----list

        欢迎来到繁星的CSDN。本期内容主要包括,list的介绍、使用以及与vector的优缺点。        

一、什么是list

        在先前的C语言学习中,我们接触到了顺序表和链表,而在C++中,这正好对应了vector(动态增长顺序表)和list(链表)。所以本期的内容实质上仍然是与链表相关的封装、重载函数等。

           list和之前说的string、vector一样,属于container(容器),作为STL库里的常客,list的出场为我们的链表提供的简易的使用方法。但是很可惜的是,我们的二叉树等链表的变形却不能直接使用。原因是list实质上是带头双向链表。(HEAD节点和其余节点结构一致,但是在list内为空时,该节点不销毁,只是为空)

二、list的接口

        list的接口比之list而言更有序,但数量仍然十分众多。

        具体查看网址如下:

        cplusplus.com/reference/list/list/

      构造constructor

explicit list ();//默认构造

explicit list (size_type n);//传值构造
list (size_type n, const value_type& val);//传值构造

template <class InputIterator>  list (InputIterator first, InputIterator last);//区间构造

list (const list& x);//拷贝构造

   文档中还有其他重载函数,以及以上展示的构造函数中,我已将有关空间配置器的内容省去(因为大部分情况下我们只需要使用STL原生的配置器就可以了)。

   实际上我们可以发现,list的构造函数和vector的构造函数差别不大。

      销毁destructor

~list();

    和我们熟悉的销毁方式没有什么区别。实际上就是将链表中的动态申请部分释放,并将相关成员变量置空。而且由于这是STL库内的部分而非自定义类型需要我们自己实现,当程序结束的时候,将会自动调用销毁函数,所以无需担心。

       赋值重载operator=

list& operator= (const list& x);

     

   经过实践检验得知,当我们使用=赋值另一个list的时候,两者的地址不同,所以这是深拷贝,无需担心先前的list销毁导致复制出来的list同时销毁。 

      迭代器iterator

        list的迭代器我们可以暂时理解为指针。

#include<iostream>
#include<list>
using namespace std;

int main() {
	int array[] = { 1,2,3,4,0,5,6,7,8,9 };
	int n = sizeof(array) / sizeof(int);
	list<int> mylist(array, array + n);
	auto it = mylist.begin();
	*it = 0;
	for (int a : mylist) {
		cout << a << endl;
	}
	return 0;
}

        (这里偷偷使用了auto来偷懒,实际写可以写为list::iterator)

        之前提过,迭代器的出现就是为了封装,减少记忆成本。

        所以这里的it仍然可以实现,++,--,*这几个方式。

        ++就是到下一个节点,--就是返回上一个节点,*就是解引用。

        与迭代器相关的接口如下:
        

   begin(),end()                          //正向迭代器

   rbegin(),rend()                       //反向迭代器

   cbegin(),cend()                      //const正向迭代器

   crbegin(),crend()                   //const反向迭代器

        但是有一点比较麻烦,list不再支持[ ],也就是随机访问。

        原因在于,底层的链表实现随机访问的代价太大,即使list的底层是带头双向链表,由于物理储存空间的不连续性,最坏情况也可以达到O(n/2)。

      容器大小capacity

    empty();

    size();

    max_size();

        相比vector,list的capacity变成了max_size,这是因为链表某一结点的空间都是单独申请,所以不存在可容纳空间这一概念,取代capacity的是max_size,代表可供使用的空间大小,这取决于系统,而非自己申请。

      访问方式access

   front();

   back();

        front与back分别是整个list的头与尾,可以通过这两个直接得到。

      成员函数member function

void push_front (const value_type& val);
void push_back (const value_type& val);
void pop_front();
void pop_back();
//对单个元素进行操作


iterator erase (const_iterator position);//删除单个元素
iterator erase (const_iterator first, const_iterator last);//删除区间内的元素

iterator insert (const_iterator position, const value_type& val);
iterator insert (const_iterator position, size_type n, const value_type& val);
//在某一位置插入一个或多个同一元素
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
//在某一位置插入另一容器的部分元素

       对list的相关函数操作,实际上和vector差别不大,当然C++11还支持了emplace_back,emplace_front,emplace,这分别对应push_back,push_front,insert。不过对于左值引用而言,效率一样,对于右值引用(这里未书写,仅仅是将参数类型变成右值),emplace的效率更高。

        当然list的相关函数还有很多,感兴趣的可以看:

        cplusplus.com/reference/list/list/

三、list和vector的优劣与不可替代性

        说起list,就不能不提到与vector的优劣势,以及是否可以被替代。

        list和vector的优劣取决于他们的储存方式。vector是由一段连续的物理空间储存,而list是不必使用连续空间储存,却不支持随机访问。

vectorlist
底层结构动态顺序表、连续物理空间带头双向链表
随机访问支持不支持
插入与删除尾端效率高,其余位置需要挪动元素,效率不高任意位置效率均高,O(1)
空间利用率空间利用率高、缓存利用率高空间利用率低、缓存利用率低
迭代器原生指针对原生指针进行封装
迭代器失效插入时如发生扩容,迭代器失效,删除时当前迭代器失效插入不会导致失效,删除时仅当前迭代器失效
使用场景需要高效储存、需要随机访问,不需要大量插入删除操作大量插入删除操作

四、list的模拟实现

namespace show
{
    // List的节点类
    template<class T>
    struct ListNode
    {
        T _val;
        ListNode<T>* _pPre;
        ListNode<T>* _pNext;
        ListNode(const T& val = T())
            :_val(val)
            ,_pNext(nullptr)
            ,_pNext(nullptr)
        {}
            ;
    };


    //List的迭代器类
    template<class T, class Ref, class Ptr>
    struct list_iterator
    {
        typedef list_node<T> Node;
        typedef list_iterator<T, Ref, Ptr> Self;
        Node* _node;

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

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

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

        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;
        }

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

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


    //list类
	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;
		iterator begin()
		{
			return _head->_next;
		}

		iterator end()
		{
			return _head;
		}

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

		const_iterator end() const
		{
			return _head;
		}

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

		list()
		{
			empty_init();
		}

		list(initializer_list<T> il)
		{
			empty_init();
			for (auto& e : il)
			{
				push_back(e);
			}
		}

		list(const list<T>& lt)
		{
			empty_init();

			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

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

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
		
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

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

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

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

			++_size;

			return newnode;
		}

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

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

		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;

			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			--_size;

			return next;
		}

		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size;
	};
};

        

        本篇内容到此结束,谢谢大家的观看!

        觉得写的还不错的可以点点关注,收藏和赞,一键三连。

        我们下期再见~

        往期栏目:

        C++学习指南(一)——C++入门基础_c++ 学习指南-CSDN博客

        C++学习指南(二)——类和对象-CSDN博客

        C++学习指南(三)——模板_模板类-CSDN博客

        C++学习指南(四)------string-CSDN博客

        C++学习指南(五)——vector_erwei vector bianliangchushihua-CSDN博客


http://www.kler.cn/a/312572.html

相关文章:

  • 文件上传漏洞--理论
  • 力扣.15 三数之和 three-sum
  • 信息安全工程师(83)Windows操作系统安全分析与防护
  • 【GVN】AWZ算法
  • ubuntu下安装 git 及部署cosyvoice(2)
  • 萤石设备视频接入平台EasyCVR海康私有化视频平台监控硬盘和普通硬盘有何区别?
  • 【docker】阿里云使用docker,2024各种采坑
  • 【笔记】扩散模型(八):DALL-E 2 (unCLIP) 论文解读与代码实现
  • C++设计模式——Interpreter解释器模式
  • 科技修复记忆:轻松几步,旧照变清晰
  • Android mmap分析
  • Linux进阶命令-scp
  • k8s快速搭建+prometheus部署及使用(纯干货!!!)
  • 基于正点原子Linux开发板的智能监控与家电控制系统设计:深度解析Video4Linux和TCP/IP技术栈
  • android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
  • Web开发:Thymeleaf模板引擎
  • Redis系列之底层数据结构SDS
  • 编程技巧:SQL 处理超大查询
  • 对商品分类系统的若干问题的思考
  • 【Linux】程序地址空间
  • 数据库函数
  • C++_CH18_构造函数与析构函数
  • Java优先级队列PriorityQueue
  • 大数据Flink(一百二十二):阿里云Flink MySQL连接器介绍
  • 将阮一峰老师的《ES6入门教程》的源码拷贝本地运行和发布
  • 【深度学习】注意力机制介绍,了解什么是注意力计算规则以及常见的计算规则,知道注意力机制的工作流程