资讯专栏INFORMATION COLUMN

[C/C++ -STL]list模拟实现及list迭代器底层刨析

不知名网友 / 1549人阅读

摘要:对类采用三个模板来实现迭代器。楷体类中和模板定义分别对应迭代器中模板定义的楷体采用向上传值的方式,传入不同值来采用不同迭代器。首先是迭代器的,分为前置和后置。迭代器的和都是获取迭代器所对应的值。唯一的差别就是就是用到了迭代器。

一、list底层实现机制刨析
前面在讲 STL list 容器时提到,该容器的底层是用双向链表实现的,甚至一些 STL 版本中(比如 SGI STL),list 容器的底层实现使用的是双向循环链表

图 1 双向链表( a) )和双向循环链表( b) )
图 1 中,node 表示链表的头指针。
如图 1 所示,使用链表存储数据,并不会将它们存储到一整块连续的内存空间中。恰恰相反,各元素占用的存储空间(又称为节点)是独立的、分散的,它们之间的线性关系通过指针(图 1 以箭头表示)来维持。
通过图 1 可以看到,双向链表的各个节点中存储的不仅仅是元素的值,还应包含 2 个指针,分别指向前一个元素和后一个元素。

通过查看 list 容器的源码实现,其对节点的定义如下:

template<class T>struct list_node {	T data;	struct list_node<T>* next;//节点的下一个节点的地址	struct list_node<T>* prev;//节点的上一个节点的地址	list_node(const T date)		:		data(date),		next(nullptr)		, prev(nullptr)	{}	};

注意,为了方便读者理解,此代码以及本节后续代码,都省略了和本节核心内容不相关的内容,如果读者对此部分感兴趣,可查看 list 容器实现源码。

可以看到,list 容器定义的每个节点中,都包含 *prev、*next 和 data。其中,prev 指针用于指向前一个节点;next 指针用于指向后一个节点;data 用于存储当前元素的值
一、list的核心框架接口的模拟实现
1.list迭代器
STL容器迭代器有两种实现方式,具体应根据容器底层数据结构实现:
1)原生指针,比如:vector string
2). 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下
方法:

  1. 指针可以解引用,迭代器的类中必须重载operator*()
  2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
  3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
    至于operator–()/operator–(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前 移动,所以需要重载,如果是forward_list就不需要重载–
  4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=():
//迭代器结构体	template<class T, class Ref, class Ptr>	struct list_Iterator {		typedef list_node<T> Node;		typedef list_Iterator<T, Ref, Ptr>  Self;		Node* node;	public:		bool operator!=(const Self& iter) {			return node != iter.node;		}		/*  list_Iterator& operator++(){			node =  node->next ;			  return *this;		  }*/		  //前置++		Self& operator ++()		{			node = node->next;			return *this;		}		//后置++和前置++构成重载		Self& operator ++(int)		{			node = node->next;			return *this;		}		list_Iterator(Node* _node)			:node(_node)		{		}		Self& operator--() {			node = node->prev;			return *this;		}		Self& operator--(int) {			node = node->prev;			return *this;		}		Ptr operator*()const {			return node->data;		}		Ptr operator->() {			return &node->data;		}		bool operator	!=(const Self& it) const {			return node != it->node;		}		bool operator	==( const Self& it) const {			return node == it.node;		}	};

list迭代器分为ierator和const_iterator迭代器,本来分别实现他的iterator迭代器和const_iterator迭代器。但是c++中有模板作为支撑。对list类采用三个模板来实现迭代器。

	typedef list_Iterator<T, T*, T&> iterator;		typedef list_Iterator<T, const T*, const T&> const_iterator;

list类中iterator和const_iterator模板定义分别对应迭代器中模板定义的:

	template<class T, class Ref, class Ptr>

采用向上传值的方式,传入不同值来采用不同迭代器。iterator传入的对应的是.
const_iterator传入的是对应的是,
迭代器根据传入不同值来判断采用的是那种迭代器。
首先是迭代器的++,分为前置++和后置++。两种运算符重载根据参数来构成运算符重载。

	  //前置++		Self& operator ++()		{			node = node->next;			return *this;		}		//后置++和前置++构成重载		Self& operator ++(int)		{			node = node->next;			return *this;		

迭代器前置++和后置++都是将node->nex来赋给node来达到迭代器的移动。
迭代器的operator*()和operator->()都是获取迭代器所对应的值。
但是operator*()返回的是模板的&可以都也可以写。
而operator->()返回的是模板对应指针。

		Ptr operator*()const {			return node->data;		}		Ptr operator->() {			return &node->data;		}

list其他接口的实现:
list接口实现其实和链表基本差不了多少。唯一的差别就是list就是用到了迭代器。
2.1 list的insert()
数是一个迭代器和要插入的值X

iterator insert(iterator pos, const T& x) {			Node* cue = pos.node;			Node* prev = cue->prev;			Node* newnode = new Node(x);			prev->next = newnode;			newnode->prev = prev;			newnode->next = cue;			cue->prev = newnode;			return iterator(newnode);		}

先保存pos对应的模板值接着参照链表的在pos的前一个节点和pos节点链接起来,返回pos’节点前一个节点的迭代器。

2.2 list的push_back()

	void push_back(const T& t) {			/*	  Node* l = head->prev;				  Node* newnode = new Node(t);				  newnode->prev = l;				  l->next = newnode;				  head->prev = newnode;				  newnode->next = head;*/			insert(end(), t);		}

list的push_back()只需调用insert()但是参数迭代器是end().
2.3 list的erase()

		iterator erase(iterator iter) {			assert(iter != end());			Node* cur = iter.node;			Node* prev = cur->prev;			Node* Next = cur->next;			prev->next = Next;			Next->prev = prev;			delete cur;			return iterator(Next);		}

list的erase()传入要删所对应的迭代器。然后保存迭代器所对应的模板值找到前一个节点和后一个节点将钱一个节点后一个节点连接起来。释放pos对应的节点。返回pos下一个节点对应的指针。
2.4 list构造
在list无参构造将head的next和prev都指向自己,早就循环链表。

list() {			head = new Node(T());			head->next = head;			head->prev = head;					}
		list(IteraterFirst first,IteraterFirstEnd en) {			head = new Node(T());			head->next = head;			head->prev = head;					while (first!=en) {				push_back(*first);				first++;			}				}
传统写法	list(const list<T>& x) {			head = new Node(T());			head->next = head;			head->prev = head;			const_iterator i = x.begin();			while (i != x.end()) {				push_back(*i);				i++;			}					}现代写法	list(const list<T>& t) {			head = new Node(T());				head->next = head;			head->prev = head;			list<T> temp(t.begin(),t.end());			swap(head, temp.head);		}		list<T> operator=(const list<T> lt) {					swap(head, lt.head);			return *this;		}

2.5 其他接口大多都是参照inser()和erase()实现的

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/125654.html

相关文章

  • php 验证格式的函数总结

    摘要:检查数据是否是格式判断是否为有效邮件地址判断是否为有效网址判断字符串是否为空判断是否为指定长度内字符串判断是否为合法用户名判断是否为合法用户密码判断是否为合法电话号码判断是否是某一范围内的合法值判断是否为合法邮编固定长度判断上传文件的扩展名 // ※CheckMoney($C_Money) 检查数据是否是99999.99格式// ※CheckEmailAddr($C_mailaddr)...

    lushan 评论0 收藏0
  • 从零开始写个编译器吧 - 开始写词法分析器(2)

    摘要:读到一个非数字非英文字母非下划线字符。此时立即跳转回状态。以一个双引号开始,并以一个双引号结束。另外,在读和时源代码不许结束,即读到符号,若结束,则判定为词法错误。对于而言,也有一些其他的词法错误判定,如,不能换行。 对于非 Normal 状态,我只需要关心两个过程: 何时从 Normal 跳转到该状态; 何时从该状态跳回 Normal 状态。 在上一章中,我已经写好了从 Nor...

    MarvinZhang 评论0 收藏0
  • 如何用 CSS 网格快速做出网站原型

    摘要:简评网格模块是创建网站模型的绝佳工具。如果你对网格完全陌生,你可能要浏览一下我的分钟介绍网格的文章。每一行代表一行,每一个字符,,,代表一个网格元素。无论标签在标记中是如何放置的,我们都能随意转换。这被称为源代码的独立性,这是的一大进步。 简评:CSS 网格模块是创建网站模型的绝佳工具。它是我尝试过的任何其他系统中最快让你体验布局的工具。 我们的网格 我们将从模仿一个经典网站的非常基本...

    thursday 评论0 收藏0
  • Python 调用 C 动态链接库,包括结构体参数、回调函数等

    摘要:调用以回调函数地址为参数的函数这个主题就稍微绕一些了,也就是说在接口中,需要传入回调函数作为参数。这个问题在中也可以解决,并且回调函数可以用定义。代码代码很简单回调函数的传入参数为,返回参数也是。 项目中要对一个用 C 编写的 .so 库进行逻辑自测。这项工作,考虑到灵活性,我首先考虑用 Python 来完成。 研究了一些资料,采用 python 的 ctypes 来完成这项工作。已经...

    NickZhou 评论0 收藏0

发表评论

0条评论

不知名网友

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<