返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++数据结构之list详解
  • 431
分享到

C++数据结构之list详解

2024-04-02 19:04:59 431人浏览 薄情痞子
摘要

目录前言一、list的节点二、list的迭代器2.1 const 迭代器2.2 修改方法二、美中不足三、迭代器的分类3.x std::find的一个报错总结前言 list相较于vec

前言

list相较于vector来说会显得复杂,它的好处是在任意位置插入,删除都是一个O(1)的时间复杂度。

一、list的节点


template <class T>
struct __list_node {
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  T data;
};

这个是在stl3.0版本下的list的节点的定义,节点里面有一个前指针,一个后指针,有一个数据data。这里只能知道他是一个双向链表,我们可以再稍微看一下list关于它的构造函数


class list  --> list() { empty_initialize(); 

  void empty_initialize() { 
    node = get_node();
    node->next = node;
    node->prev = node;
  }

再看一下它的list(),可以看出他调用的empty_initialize(),是创建了一个头结点,并且是一个循环的结构。

综上:list的总体结构是一个带头循环双向链表

二、list的迭代器

迭代器通常是怎么使用的,看一下下面这段代码。


int main()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	l.push_back(6);

	list<int>::iterator it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}
  • 我们从list< int >当中定义一个iterator对象,然后让他去访问我们的节点
  • 并且他所支持的操作有++,解引用,当然还有 --等等

stl3.0当中的迭代器实现:


template<class T, class Ref, class Ptr>
struct __list_iterator {
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;

  typedef bidirectional_iterator_tag iterator_cateGory;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  link_type node;

  __list_iterator(link_type x) : node(x) {}
  __list_iterator() {}
  __list_iterator(const iterator& x) : node(x.node) {}

  bool operator==(const self& x) const { return node == x.node; }
  bool operator!=(const self& x) const { return node != x.node; }
  reference operator*() const { return (*node).data; }

#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif 

  self& operator++() { 
    node = (link_type)((*node).next);
    return *this;
  }
  self operator++(int) { 
    self tmp = *this;
    ++*this;
    return tmp;
  }
  self& operator--() { 
    node = (link_type)((*node).prev);
    return *this;
  }
  self operator--(int) { 
    self tmp = *this;
    --*this;
    return tmp;
  }

大家感兴趣可以先看看上面的,我们先用一个简述版的来带大家简要实现一下


	template<class T>
	class __list_node
	{
	public:
		__list_node(const T& val = T())//用一个全缺省比较好
			:_next(nullptr)
			,_pre(nullptr)
			,node(val)
		{}
	public:
		__list_node<T>* _next;
		__list_node<T>* _pre;
		T node;
	};

	template<class T>
	class __list_itertaor//这里是迭代器
	{
	public:
		typedef __list_node<T>  Node;
		__list_itertaor(Node* node)
		{
			_node = node;
		}

		bool operator!=(const __list_itertaor<T>& it)
		{
			return _node != it._node;
		}
		__list_itertaor<T>& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		T& operator*()
		{
			return _node->node;
		}
	private:
		Node* _node;
	};

这里的实现是不完整的,但是很适合说明问题。通过我们去重载opertaor++,和重载opertaor*,可以让我们像指针一样去访问一个节点,让我们可以跟vector和string一样用同样的接口就能实现对数据的访问,这是非常厉害的一个技术。

注意点:

  • 我们通过对节点的操作,重载了operator++等接口实现了对一个节点的访问,访问的时候实际上也就是创建迭代器对象,对我们的数据进行访问,所以我们封装的时候是将节点的指针进行封装。
  • list相比vector,正因为他们的底层结构不相同,list的迭代器在插入操作和接合操作(splice)都不会造成迭代器失效,只有删除的时候,只有那个被删除元素的迭代器失效,而不影响后面的,而vector就统统失效了。

模板参数为什么是三个

2.1 const 迭代器

有这样一种情况,我们需要const对象去遍历,假如我们有个函数叫做print_list(const list< int >& lt);
传参: 其中传参中const是因为不会对对象进行修改,加引用是因为不用深拷贝,提高效率。
功能: 这个函数就是去打印链表里面的内容的。但是按照我们上面的实现,会出现什么问题呢。

在这里插入图片描述

这很正常,在const迭代器就去生成const迭代器对象,在vector,string这些迭代器就是原生指针的时候我们只需要typedef const T* const_iterator,那如果我们在我们生成的list也做类似的操作,来看看结果。

在这里插入图片描述

结果我们发现,好像没多大问题,但是我们尝试修改const迭代器里面的内容时,却发现能修改成功。const迭代器怎么能修改里面的数据呢?这就有问题了!!!说明我们的有一个巨大的隐患在里面。

在这里插入图片描述

2.2 修改方法

最简单的方法当然就是再写多一个迭代器,把__list_iterator换成__list_const_iterator 之类的,但是我们认真观察的话,实际上这两个类很多东西是重复的,只有在operator*,operator->时所需要的返回值,我们需要找到一种方法去让const对象的返回值也是const对象,答案就是添加多两个个模板参数。
以下以添加一个模板参数为例,实现一个Ref operator*();


template<class T>
	class __list_node
	{
	public:
		__list_node(const T& val = T())//用一个全缺省比较好
			:_next(nullptr)
			,_pre(nullptr)
			,node(val)
		{}
	public:
		__list_node<T>* _next;
		__list_node<T>* _pre;
		T node;
	};

	template<class T,class Ref>
	class __list_itertaor
	{
	public:
		typedef __list_node<T>  Node;
		__list_itertaor(Node* node)
		{
			_node = node;
		}

		bool operator!=(const __list_itertaor<T,Ref>& it)
		{
			return _node != it._node;
		}
		__list_itertaor<T,Ref>& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		Ref operator*()//返回Ref,返回值就有区别啦
		{
			return _node->node;
		}
	private:
		Node* _node;
	};

	template<class T>
	class list
	{
		typedef __list_node<T>  Node;
	public:
		typedef __list_itertaor<T,T&> iterator;
		typedef __list_itertaor<T, const T&> const_iterator;//修改
		iterator begin()
		{
			return iterator(_node->_next);
		}
		iterator end()
		{
			return iterator(_node);
		}
		const_iterator begin()const
		{
			return const_iterator(_node->_next);
		}
		const_iterator end()const
		{
			return const_iterator(_node);
		}
		list()
		{
			_node = new Node;
			_node->_next = _node;
			_node->_pre = _node;
		}
		void push_back(const T& val)
		{
			Node* newnode = new Node(val);
			Node* tail = _node->_pre;
			tail->_next = newnode;
			newnode->_pre = tail;
			newnode->_next = _node;
			_node->_pre = newnode;
		}
	private:
		Node* _node;
	};

一图了解:也就是我们的测试端test函数中定义list< int >::const_iterator cit= l.begin();的时候迭代器对象就会识别到定义的const迭代器,它的第二个模板参数放的就是const T&,这样子我们operator*()返回的时候只需要返回第二个模板参数就可以了

在这里插入图片描述

同理,我们要用到的接口operator->当中也会有const对象和普通对象调用的情况。我们这里把实现的代码放出来,有需要的自取。

–》码云链接《–

二、美中不足

list上面说的仿佛都是优点

任意位置的O(1)时间的插入删除,迭代器失效的问题变少了。但他又有哪些不足呢

  • 不支持随机访问
  • 排序的效率慢,库中的sort用的是归并排序–>快排需要三数取中,对于链表来说实现出来效率也低,所以当链表的元素需要进行排序的时候,我们通常也都会拷贝到vector当中,再用vector当中的排序。
  • 同理链表的逆置效率也不高!

三、迭代器的分类

迭代器从功能角度来看的话分为:const迭代器/普通迭代器 + 正反向。

容器底层结构角度分为:单向,双向,随机。

  • 单向: 单链表迭代器(forward_list)/哈希表迭代器;这些只支持单向++;
  • 双向: 双链表迭代器/map迭代器;这些支持的++/- -操作;
  • 随机迭代器: string/vector/deque;这些是支持++/- -/+/-操作的,类似原生指针一般。

我们来看一下部分函数的,比如sort当中的模板参数写成RandoMaccessIterator,就是想要明示使用者他这里需要的是一个随机的迭代器,在它的底层会调用到迭代器的+操作,所以这个时候如果你传的是一个双向或者单向的迭代器就不行了!!


//sort的函数声明
template <class RandomAccessIterator>
  void sort (RandomAccessIterator first, RandomAccessIterator last);
custom (2)	
template <class RandomAccessIterator, class Compare>
  void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

比如说reverse函数声明,它的模板参数是BidirectionalIterator,也就是需要一个支持双向的迭代器,这个时候其实我们就可以传随机迭代器和双向迭代器,从上面的迭代器支持的操作可以看到,随机迭代器是支持双向迭代器的所有操作的
同理,如果是一个需要单向迭代器的地方,我们就可以传一个双向,随机,单向迭代器了!!


std::reverse
template <class BidirectionalIterator>
  void reverse (BidirectionalIterator first, BidirectionalIterator last);

从stl3.0当中的stl_iterator.h,我们可以看出当中的继承关系。这个我们之后再讲。

在这里插入图片描述

注意:difference_type为两个迭代器之间的距离。类型ptrdiff_t为无符号整形。

3.x std::find的一个报错

当我们实现了自己的数据结构,如list,我们如果用库里的std:find查找我们实现的数据结构当中的数据会报错。博主的测试版本为vs2013,在其他版本可能不做检查,不会报错。


void test_list()
	{

		list<int> l;
		l.push_back(5);
		list<int>::iterator it = std::find(l.begin(), l.end(), 5);
	}

报错:这里的报错说的是iterator_category不在我们的迭代器当中,这个是对我们迭代器类型的一个检查。

在这里插入图片描述

stl_list.h当中为迭代器添加了如下声明来解决这个问题。

在这里插入图片描述

解决方案: 我们可以用stl3.0版本下stl_list.h当中的迭代器的声明。也可以用release版本下,都是可以跑过的。


		typedef bidirectional_iterator_tag iterator_category;
		typedef T value_type;
		typedef Ptr pointer;
		typedef Ref reference;
		typedef ptrdiff_t difference_type;

在这里插入图片描述

总结

list的讲解就到这里啦,看到这里不妨一键三连。

到此这篇关于c++数据结构之list详解的文章就介绍到这了,更多相关C++ list 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++数据结构之list详解

本文链接: https://lsjlt.com/news/157951.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++数据结构之list详解
    目录前言一、list的节点二、list的迭代器2.1 const 迭代器2.2 修改方法二、美中不足三、迭代器的分类3.x std::find的一个报错总结前言 list相较于vec...
    99+
    2024-04-02
  • C++数据结构之堆详解
    目录堆的概念提示:完全二叉树堆的性质最大堆最小堆代码定义有限数组形式动态数组形式操作向下调整结点建立堆初始化打印堆测试main函数结果完整代码堆的概念 堆(heap)是计算机科学中一...
    99+
    2024-04-02
  • C++数据结构之链表详解
    目录前言一、删除链表中给定值为key的节点二、反转链表三、返回链表的中间节点四、删除链表的倒数第K个节点五、分割链表六、合并两个有序链表七、删除有序链表中重复节点八、环形链表九、相交...
    99+
    2024-04-02
  • 详解C语言数据结构之栈
    目录栈的链式实现主要内容代码实现:总结栈的链式实现 主要内容 (1) 栈包含7个元素,依次是67,3,88,6,1,7,0,采用尾插入法创建 栈,为该栈设置两个指针,一个bottom...
    99+
    2024-04-02
  • JS数据结构之队列结构详解
    目录一.认识队列二.队列的应用三.队列类的创建四.队列的常见操作五.击鼓传花六.优先级队列七.优先级队列的实现一.认识队列 受限的线性结构: 我们已经学习了一种受限的线性结构:栈结构...
    99+
    2022-11-13
    JS队列结构 JS队列 JS 数据结构
  • Java数据结构之List怎么用
    小编给大家分享一下Java数据结构之List怎么用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!泛型什么是泛型泛型:即通过参数化类型来实现在同一份代码上操作多种数据类型。泛型是在C#2.0引入的。泛型(Genericity...
    99+
    2023-06-21
  • C语言数据结构之堆排序详解
    目录1.堆的概念及结构2.堆的实现2.1 堆的向下调整算法2.2 堆的向上调整算法2.3 建堆(数组)2.4 堆排序2.5 堆排序的时间复杂度1.堆的概念及结构 如果有一个关键码的集...
    99+
    2024-04-02
  • C语言数据结构之二叉树详解
    目录1. 树概念及结构1.1树概念1.2树的表示2. 二叉树概念及结构2.1概念2.2数据结构中的二叉树2.3特殊的二叉树2.4二叉树的存储结构2.5二叉树的性质3. 二叉树顺序结构...
    99+
    2024-04-02
  • Java数据结构之List的使用总结
    目录泛型什么是泛型泛型的分类泛型的定义简单演示泛型背后作用时期和背后的简单原理泛型类的使用泛型总结包装类基本数据类型和包装类直接的对应关系包装类的使用,装箱(boxing)和拆箱(u...
    99+
    2024-04-02
  • 详解redis数据结构之sds
    详解redis数据结构之sds 字符串在redis中使用非常广泛,在redis中,所有的数据都保存在字典(Map)中,而字典的键就是字符串类型,并且对于很大一部分字典值数据也是又字符串组成的。以下是sd...
    99+
    2022-06-04
    数据结构 详解 redis
  • Python数据结构之栈详解
    目录0. 学习目标1. 栈的基本概念1.1 栈的基本概念1.2 栈抽象数据类型1.3 栈的应用场景2. 栈的实现2.1 顺序栈的实现2.1.1 栈的初始化2.2 链栈的实现2.3 栈...
    99+
    2024-04-02
  • C语言数据结构之队列算法详解
    目录一、前言二、基本概念三、顺序队列四、链队列五、循环队列六、总结与提高一、前言 队列在程序设计中经常出现,如:操作系统中的排队问题。 这篇文章主要介绍了队列的...
    99+
    2024-04-02
  • Python数据结构之图的存储结构详解
    一、图的定义 图是一种比树更复杂的一种数据结构,在图结构中,结点之间的关系是任意的,任意两个元素之间都可能相关,因此,它的应用极广。图中的数据元素通常被称为顶点 ( V e r t ...
    99+
    2024-04-02
  • Java数据结构之栈的线性结构详解
    目录一:栈二:栈的实现三:栈的测试四:栈的应用(回文序列的判断)总结一:栈 栈是限制插入和删除只能在一个位置上进行的表,此位置就是表的末端,叫作栈顶。 栈的基本操作分为push(入...
    99+
    2024-04-02
  • 详解python数据结构之栈stack
    前言 栈(Stack)是一种运算受限的线性表。 按照先进后出(FILO,First In Last Out)的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶。栈只能在一端进行插入和删除操作。 文章内容包含: ...
    99+
    2022-06-02
    python 栈stack python数据结构
  • Python基础之数据结构详解
    目录一、列表1.1 列表更新元素1.2 列表增加元素1.3 列表删除元素1.4 列表的其他操作二、元组2.1 删除元组2.2 元组的其他操作三、字典3.1 字典删除元素3.2 字典的...
    99+
    2024-04-02
  • Java数据结构之链表详解
    目录一、链表的介绍二、单链表的实现三、双向链表的实现四、循环链表的实现五,链表相关的面试题一、链表的介绍 什么是链表 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑...
    99+
    2024-04-02
  • java数据结构之栈的详解
    目录一、栈1.栈的应用1.1括号匹配1.2后缀表达式1.3用栈实现队列1.4最小栈1.5栈的压入和弹出序列总结一、栈 栈的特性就是先进后出,常用方法是入栈(push()),出栈(po...
    99+
    2024-04-02
  • Python数据结构之队列详解
    目录0. 学习目标1. 队列的基本概念1.1 队列的基本概念1.2 队列抽象数据类型1.3 队列的应用场景2. 队列的实现2.1 顺序队列的实现2.2 链队列的实现2.3 队列的不同...
    99+
    2024-04-02
  • Redis数据结构之链表详解
    目录1 链表和链表节点的结构2 链表相关的API1 链表和链表节点的结构 1.1 节点结构 节点的结构大概长下边这个样子: 那么,把这些节点就连起来就成了这个样子: 1.2 链表...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作