C++中list的用法实例讲解

目录
  • 前言
  • 一、list的节点
  • 二、list的迭代器
    • 2.1、模板参数为什么是三个
    • 2.3 修改方法
  • 二、美中不足
  • 三、迭代器的分类
    • 3.x std::find的一个报错
  • 总结

前言

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 /* __SGI_STL_NO_ARROW_OPERATOR */

  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、模板参数为什么是三个

2.2 const 迭代器

有这样一种情况,我们需要const对象去遍历,假如我们有个函数叫做print_list(const list< int >& lt);

传参: 其中传参中const是因为不会对对象进行修改,加引用是因为不用深拷贝,提高效率。

功能: 这个函数就是去打印链表里面的内容的。但是按照我们上面的实现,会出现什么问题呢。

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

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

2.3 修改方法

最简单的方法当然就是再写多一个迭代器,把__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;

总结

到此这篇关于C++中list用法的文章就介绍到这了,更多相关C++ list用法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++初阶之list的模拟实现过程详解

    list的介绍 list的优点: list头部.中间插入不再需要挪动数据,O(1)效率高 list插入数据是新增节点,不需要增容 list的缺点: 不支持随机访问,访问某个元素效率O(N) 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低. 今天来模拟实现list 我们先来看看官方文档中对于list的描述 我们先大致了解一下list的遍历 迭代器 对于迭代器我们可以用while循环+begin()end().同时还可以用迭代器区间. 当然迭代器区间的方式只适用于内存连续的结构

  • C++ 模拟实现list(迭代器)实现代码

    C++ 模拟实现list(迭代器) 实现代码: #pragma once; #include <assert.h> #include<iostream> #include <assert.h> using namespace std; template<class T> struct __ListNode { T _data; __ListNode<T>* _next; __ListNode<T>* _prev; __ListNode

  • C++实现双向链表(List)

    list是C++容器类中的"顺序存储结构"所包含的一种结构.list是非连续存储结构,具有双链表结构,支持前向/后向遍历,且支持高效的随机删除/插入. 实现代码如下: **list.h** #pragma once #include<stdio.h> #include<assert.h> #include<iostream> using namespace std; typedef int DataType; struct ListNode { Li

  • C++入门之list的使用详解

    目录 前言 构造的使用 1 构造空list 2 构造含n个值为val的元素 3 拷贝构造 4 用迭代区间 迭代器接口 1 正常迭代接口 2 逆向迭代接口 容量接口 元素访问 数据修改 头插 头删 尾插 尾删 pos位置插入 erase擦除pos位置 交换两个链表元素 总结 前言 今天我们终于来到了C++的list章节,在讲解之前,先回顾一下前面的vector和string吧. vector和string的底层都是用的顺序表,因此其空间在物理结构上连续的.而今天的list却不一样,它在物理上是散乱

  • C++ list的实例详解

     C++ list的实例详解 Source: #include <iostream> #include <list> #include <numeric> #include <algorithm> using namespace std; typedef list<int> LISTINT; //创建一个list容器的实例LISTINT typedef list<int> LISTCHAR; //创建一个list容器的实例LISTCH

  • C++数据结构之list详解

    目录 前言 一.list的节点 二.list的迭代器 2.1 const 迭代器 2.2 修改方法 二.美中不足 三.迭代器的分类 3.x std::find的一个报错 总结 前言 list相较于vector来说会显得复杂,它的好处是在任意位置插入,删除都是一个O(1)的时间复杂度. 一.list的节点 template <class T> struct __list_node { typedef void* void_pointer; void_pointer next; void_poin

  • C++ 使用模板实现一个List的实例

    C ++使用模板写的一个List template<class T> class List { private: struct Node { T data; Node *next; }; //head Node *head; //size int length; //process Node *p; //temp Node *q; public: List() { head = NULL; length = 0; p = NULL; } void add(T t) { if(head == N

  • C++中list的使用方法及常用list操作总结

    C++中list的使用方法及常用list操作总结 一.List定义: List是stl实现的双向链表,与向量(vectors)相比, 它允许快速的插入和删除,但是随机访问却比较慢.使用时需要添加头文件 #include <list> 二.List定义和初始化: list<int>lst1;          //创建空list     list<int> lst2(5);       //创建含有5个元素的list     list<int>lst3(3,2

  • 基于多线程中join()的用法实例讲解

    Thread中,join()方法的作用是调用线程等待该线程完成后,才能继续用下运行. public class TestThread5 { public static void main(String[] args) throws InterruptedException { Runner0 run5 = new Runner0(); Thread th5 = new Thread(run5); th5.start(); th5.join();//join()方法用在此处是为了等待主线程结束后运

  • C++中list的用法实例讲解

    目录 前言 一.list的节点 二.list的迭代器 2.1.模板参数为什么是三个 2.3 修改方法 二.美中不足 三.迭代器的分类 3.x std::find的一个报错 总结 前言 list相较于vector来说会显得复杂,它的好处是在任意位置插入,删除都是一个O(1)的时间复杂度. 一.list的节点 template <class T> struct __list_node { typedef void* void_pointer; void_pointer next; void_poi

  • js中arguments的用法(实例讲解)

    如下所示: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8&qu

  • Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)

    在这之前,我已经分享过组件与组件的通信机制以及父子组件之间的通信机制,而我们的vuex就是为了解决组件通信问题的 vuex是什么东东呢? 组件通信的本质其实就是在组件之间传递数据或组件的状态(这里将数据和状态统称为状态),但可以看到如果我们通过最基本的方式来进行通信,一旦需要管理的状态多了,代码就会变得十分臃肿和庞大.对所有状态的管理便会显得力不从心,因此,vuex出现了,他就是帮助我们把公用的状态全抽出来放在vuex的容器中,然后根据一定的规则来进行管理,我们赶紧来用一下吧,想要掌握vuex的

  • java中调用super的实例讲解

    在java中类之间也是有着继承关系的,就我们之前有提到不少父类与子类的一些问题. 讲的以子类的调用为主,那么有小伙伴知道父类的调用方法吗?这里我们需要借助关键字super来实现.下面我们就来讲讲super的概念.调用方法.应用范围,帮助大家找到使用supei调用父类的方法. 1.概念 super关键字用于引用使用该关键字的类的超类. 作为独立语句出现的 super 表示调用超类的构造方法. 2.调用超类方法 super.<methodName>() 只有在如下情况中才需要采用这种用法:要调用在

  • python中numpy.empty()函数实例讲解

    在使用python编程的过程中,想要快速的创建ndarray数组,可以使用numpy.empty()函数.numpy.empty()函数所创建的数组内所有元素均为空,没有实际意义,所以它也是创建数组最快的方法.本文介绍python中numpy.empty()函数的使用方法. 1.numpy.empty()函数 这个函数可以创建一个没有任何具体值的ndarray数组,是创建数组最快的方法. 根据给定的维度和数值类型返回一个新的数组,其元素不进行初始化. 2.用法 import numpy as n

  • requests在python中发送请求的实例讲解

    当我们想给服务器发送一些请求时,可以选择requests库来实现.相较于其它库而言,这种库的使用还是非常适合新手使用的.本篇要讲的是requests.get请求方法,这里需要先对get请求时的一些参数进行学习,在掌握了基本的用法后,可以就下面的requests.get请求实例进一步的探究. 1.get请求的部分参数 (1) url(请求的url地址,必需 ) import requests url="http://www.baidu.com" resp=requests.get(url

  • PHP中使用ElasticSearch最新实例讲解

    网上很多关于ES的例子都过时了,版本很老,这篇文章的测试环境是ES6.5 通过composer安装 composer require 'elasticsearch/elasticsearch' 在代码中引入 require 'vendor/autoload.php'; use Elasticsearch\ClientBuilder; $client = ClientBuilder::create()->setHosts(['172.16.55.53'])->build(); 下面循序渐进完成一

  • python ChainMap管理用法实例讲解

    说明 1.ChainMap的主要用例是提供一种有效的方法来管理多个范围或上下文,并处理重复键的访问优先级. 2.当有多个存储重复键的字典访问它们的顺序时,这个功能非常有用. 在ChainMap文档中找到一个经典的例子,它模拟Python如何分析不同命名空间中的变量名称. 当Python搜索名称时,它会依次搜索当地.全局和内置的功能域,直到找到目标名称.Python作用域是将名称映射到对象的字典. 为了模拟Python的内部搜索链,可以使用链映射. 实例 >>> import builti

  • MySQL数据类型中DECIMAL的用法实例详解

    MySQL数据类型中DECIMAL的用法实例详解 在MySQL数据类型中,例如INT,FLOAT,DOUBLE,CHAR,DECIMAL等,它们都有各自的作用,下面我们就主要来介绍一下MySQL数据类型中的DECIMAL类型的作用和用法. 一般赋予浮点列的值被四舍五入到这个列所指定的十进制数.如果在一个FLOAT(8, 1)的列中存储1. 2 3 4 5 6,则结果为1. 2.如果将相同的值存入FLOAT(8, 4) 的列中,则结果为1. 2 3 4 6. 这表示应该定义具有足够位数的浮点列以便

随机推荐