vector,list,deque容器的迭代器简单介绍
我们知道标准库中的容器有vector,list和deque。另外还有slist,只不过它不是标准容器。而谈到容器,我们不得不知道进行容器一切操作的利器---迭代器。而在了解迭代器之前,我们得先知道每个容器的结构,包括它的逻辑结构和物理结构。让我们先说说vector:
一、vector
我们先来看看vector容器内元素在内存中的布局:
其中的#0,#1...就是容器内的元素。从上图可以看出vector维护的是一个连续的线性空间,和数组是一样的。所以不论其元素为何种型别,普通指针就可以作为vector的迭代器!因为vector迭代器所需要的操作如operator*,operator->,operator++,operator+,operator-,operator+=,operator-=,普通指针天生就具备。查看vector的源码,我们可以看到vector的迭代器并没有另外定义为一个模版类,而是直接 typedef value_type* iterator。 更可以看出 vector 的迭代器就是一个普通指针。对于普通指针,我就不在多说。相信大家也早已理解。
二、list
还是先来看看list的结构:从list的名字我们就可以看出 list 的结构应该是一个链表,事实上他的结构确实是一个链表---一个环状双向链表。他的结构图如下:;
画的可能有点乱,但是如果你知道双链表的结构,你可以自行画出。上图的每个结点就是 list 容器中用来保存元素值的结构了。其中的#0,#1...就是容器的实际保存的元素值。而 list 的迭代器本身是一个模板类,我们看看 list 的迭代器设计:
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;
}
};
这个迭代器的模板类其实并没有多少东西。只包括:
1.定义一些类型别名
2.定义一个 node 成员变量
3.必要的构造函数和重载了的操作符
其中真正起作用的是 node 成员变量,它是指向 list 链表结构的结点的普通指针, list 链表结点的结构定义代码如下:
template <class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
就是一般的结构体啦,不过这里是模板形式的。其中的 prev 和 next是双向链表必须的两个指针分别指向前一个结点和后一个结点。data 用来保存实际的值。可以看出,list 的迭代器只是封装了 list node 的指针 ,并重载了迭代器应有的操作符而已。想想我们在用普通操作链表的时候,要想指向下一个结点,也就是实现指针的自增是怎么做的?是不是用 p = p->next啊,只不过这里把他用++操作符代替了我们的操作,更加方便了而已!所以 list 的迭代器也挺简单。list 迭代器重载了 ==, !=, *, ->, 前置++,后置++,前置--,后置--。没有重载 +,-,+=,-n,所以 list 的迭代器只是一个 Bidirectional Iterator。而 vector 的迭代器是普通指针,它是 Random Access Iterator。
三、deque
我们知道 vector 是个单向开口的连续线性空间,而 deque 则是一种双向开口的连续线性空间。所以 vector 从尾端插入元素效率较高,而如果从头部插入,则效率奇差。deque 可以从两端插入,效率也很高。在介绍 deque 迭代器之前,我们先来了解一下 deque 的逻辑结构。deque 到底是什么样的一个结构、在内存中如何布局,才可以从两端插入且是连续线性空间呢?我还是先来张图,根据图我们再娓娓道来:
看到这个图,大家也许蒙了,第一反映是怎么这么复杂?跟 vector 内存布局比起来,确实很复杂。因为它并不是真正的连续线性空间,而是模拟的。看到图中标志的缓冲区(node-buffer)没,它才是用来存储 deque 容器元素的真正承担者。他们是一段段定量连续空间。其大小可以自己指定,默认是 512bytes。接下来我们看看 map 这个结构:它也是一个连续的线性空间,不过它保存的是指向每个缓冲区(node-buffer)首地址的指针。map 起着中央控制器的作用,所以我们称其为中控器。既然 deque 在内存中如此布局,那如何伪装成一个连续的线性空间呢?造成这个假象的任务全落到了迭代器的身上。我们来看看 deque 迭代器、中控器、缓冲区之间的相互关系: 为了更好的说明问题,我给出一个实际的例子。现在假设有一个 deque 有 20 个元素,每个缓冲区是 8 个元素大小。其结构如下图:
我们看到实例中有三个缓冲区(node-buffer),可以保存24个元素,而现在deque只有20个,所以还剩4个剩余空间(图中灰色部分)。map是中控器,我们可以看到其并没有满,而且起始位置也不是在 map 首地址,这都是为了能够实现在头尾两端进行插入。再看看 start 和 finish,他们分别是 deque 的 begin()和 end() 返回的迭代器。看完迭代器、中控器、缓冲区之间的关系,我们来看看 deque 迭代器的代码:
//确定缓冲区大小的函数
inline size_t __deque_buf_size(size_t n, size_t sz)
{
return n != ? n : (sz < ? size_t( / sz) : size_t());
} template <class T, class Ref, class Ptr>
struct __deque_iterator {
//定义一些类型别名
typedef __deque_iterator<T, T&, T*> iterator;
typedef __deque_iterator<T, const T&, const T*> const_iterator;
static size_t buffer_size() {return __deque_buf_size(, sizeof(T)); } typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T** map_pointer; typedef __deque_iterator self; //图片中的几个指针
T* cur;
T* first;
T* last;
//中控器结点
map_pointer node; //构造函数
__deque_iterator(T* x, map_pointer y)
: cur(x), first(*y), last(*y + buffer_size()), node(y) {}
__deque_iterator() : cur(), first(), last(), node() {}
__deque_iterator(const iterator& x)
: cur(x.cur), first(x.first), last(x.last), node(x.node) {} //以下全是重载
reference operator*() const { return *cur; }
pointer operator->() const { return &(operator*()); }
//注意这个操作符
difference_type operator-(const self& x) const {
return difference_type(buffer_size()) * (node - x.node - ) +
(cur - first) + (x.last - x.cur);
}
//注意这个操作符
self& operator++() {
++cur;
if (cur == last) {
set_node(node + );
cur = first;
}
return *this;
}
self operator++(int) {
self tmp = *this;
++*this;
return tmp;
} self& operator--() {
if (cur == first) {
set_node(node - );
cur = last;
}
--cur;
return *this;
}
self operator--(int) {
self tmp = *this;
--*this;
return tmp;
}
//注意这个操作符
self& operator+=(difference_type n) {
difference_type offset = n + (cur - first);
if (offset >= && offset < difference_type(buffer_size()))
cur += n;
else {
difference_type node_offset =
offset > ? offset / difference_type(buffer_size())
: -difference_type((-offset - ) / buffer_size()) - ;
set_node(node + node_offset);
cur = first + (offset - node_offset * difference_type(buffer_size()));
}
return *this;
} self operator+(difference_type n) const {
self tmp = *this;
return tmp += n;
} self& operator-=(difference_type n) { return *this += -n; } self operator-(difference_type n) const {
self tmp = *this;
return tmp -= n;
} reference operator[](difference_type n) const { return *(*this + n); } bool operator==(const self& x) const { return cur == x.cur; }
bool operator!=(const self& x) const { return !(*this == x); }
bool operator<(const self& x) const {
return (node == x.node) ? (cur < x.cur) : (node < x.node);
}
//用来跳一个缓冲区
void set_node(map_pointer new_node) {
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
}
};
代码中最重要的就是迭代器重载的那些操作符,有*,->,-,前置++,后置++,前置--,后置--,+=,+,-=,-,[],==,!=,<!可以看出 deque 的迭代器是一个 Random Access Iterator。我们要注意的几个操作符是++,--,+=,-=,+,-,这些操作都涉及到指针的移动,而deque是伪连续线性空间,在到移动到一个缓冲区尾部时,应该要用函数set_node()跳到下一个缓冲区。也就是说,我们要处理好边界情况。deque 的迭代器有些复杂,关键我们要知道 deque 的逻辑结构,才能知道迭代器操作符的的具体操作步骤。
vector,list,deque容器的迭代器简单介绍的更多相关文章
- C++ STL中vector(向量容器)使用简单介绍
原文:http://www.seacha.com/article.php/knowledge/cbase/2013/0903/2205.html C++ vector(向量容器)是一个线性顺序结构.相 ...
- C++——STL之vector, list, deque容器对比与常用函数
STL 三种顺序容器的特性对比: vector 可变数组,内存空间是连续的,容量不会进行缩减.支持高效随机存取,即支持[]和at()操作.尾部插入删除效率高,其他位置插删效率较低: list 双向链表 ...
- 迭代器类vector::iterator 和 vector::reverse_iterator 的实现、迭代器类型、常用的容器成员
一.迭代器 迭代器是泛型指针 普通指针可以指向内存中的一个地址 迭代器可以指向容器中的一个位置 STL的每一个容器类模版中,都定义了一组对应的迭代器类.使用迭代器,算法函数可以访问容器中指定位置的元素 ...
- C++进阶 STL(1) 第一天 [容器,算法,迭代器] string容器 vector容器 deque容器
课程大纲 02实现基本原理 容器,算法,迭代器 教室:容器 人:元素 教室对于楼:容器 序列式容器: 容器元素在容器中的位置是由进入容器的时间和地点来决定 序列式容器 关联式容器: 教室中 按年龄排座 ...
- [C++ STL] 各容器简单介绍
什么是STL? 1.STL(Standard Template Library),即标准模板库,是一个高效的C++程序库. 2.包含了诸多常用的基本数据结构和基本算法.为广大C++程序员们提供了一个可 ...
- C++ 顺序容器 vector list deque 之比较
在C++标准库中定义了三种顺序容器类型:vector,list和deque.所谓顺序容器就是根据位置来存储和访问元素,元素的排列次序与元素的值无关,而是由元素添加到容器的次序决定的. vector的底 ...
- STL容器 vector,list,deque 性能比较
C++的STL模板库中提供了3种容器类:vector,list,deque对于这三种容器,在觉得好用的同时,经常会让我们困惑应该选择哪一种来实现我们的逻辑.在少量数据操作的程序中随便哪一种用起来感觉差 ...
- C++顺序容器vector、deque、list
1.容器元素类型 C++中大多数数据类型能够作为容器的元素类型.容器元素类型必须满足一下两个条件:支持赋值和复制操作. 所以没有元素是引用类型的容器,同一时候IO对象和auto_ptr也不能作为容器的 ...
- STL vector容器 和deque容器
前言 STL是C++的框架,然后vector容器和deque容器又是STL的一部分... 这块的内容都是理解.概念为主,没什么捷径,希望读者能静下来记. 先来讲vector容器(单端动态数组) 1.v ...
随机推荐
- HTTP基础:URL格式、 HTTP请求、响应、消息
HTTP URL 格式: http://host[:port][abs_path] 其中http表示要通过HTTP协议来定位网络资源. host表示合法的Internet主机域名或IP地址(以点分十进 ...
- Base64原理简介
Base64是一种编码方式,通常用于将二进制数据转换成可见字符的形式,该过程可逆. 过程大致如下: 1. 对64个可见字符,进行一个索引编码.索引是二进制的值,对应找到一个可见字符. Base64 编 ...
- DataGrid列的合并
/// <summary> /// DataGrid列的合并 /// 注意:1.DataGrid在绑定的时候进行分组和排序,才能让相同的行放在一起 /// 2.方法应用的时机,应该在Dat ...
- SQL Server 2008文件与文件组的关系
此文章主要向大家讲述的是SQL Server 2008文件与文件组,其中包括文件和文件组的含义与关系,文件.文件组在实践应用中经常出现的问题,查询文件组和文件语句与MSDN官方解释等相关内容的介绍. ...
- 域名解析-delphi 源码
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Syste ...
- jBPM 6 开发 eclipse 插件安装
jBPM 6 开发 eclipse 插件安装 概述 与之前的jBPM 5相比,jBPM 6 新引入的kjars及mavenized的特性,使流程开发设计与之前有了很大的不同,本文主要说明jBPM 6 ...
- Linux使用fdisk进行磁盘管理
Fdisk分区工具1. Overview*Fdisk是IBM的老牌分区工具,支持绝大多数操作系统,几乎所有的Linux操作系统都默认装有fdisk:包括在Linux Rescue模式下 ...
- jQuery 个人随笔
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- repeater控件实现分页
repeater控件实现排序的方法,今天我再向大家介绍repeater控件如何实现分页的效果. 分页分为真分页和假分页. 真分页:控件上一页需要显示多少数据,就从数据库取出并绑定多少数据,每次换页时都 ...
- angularjs入门整理
之前发过一篇博文,从mobile angular ui的demo和其官网初识整个angularjs的大体使用,但是没很好学习,只是通过一些技术博文初步认识,陷入很多坑.所以现在在中文官网正式整理下知识 ...