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 迭代器的代码:


  1 //确定缓冲区大小的函数
2 inline size_t __deque_buf_size(size_t n, size_t sz)
3 {
4 return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));
5 }
6
7 template <class T, class Ref, class Ptr>
8 struct __deque_iterator {
9 //定义一些类型别名
10 typedef __deque_iterator<T, T&, T*> iterator;
11 typedef __deque_iterator<T, const T&, const T*> const_iterator;
12 static size_t buffer_size() {return __deque_buf_size(0, sizeof(T)); }
13
14 typedef random_access_iterator_tag iterator_category;
15 typedef T value_type;
16 typedef Ptr pointer;
17 typedef Ref reference;
18 typedef size_t size_type;
19 typedef ptrdiff_t difference_type;
20 typedef T** map_pointer;
21
22 typedef __deque_iterator self;
23
24 //图片中的几个指针
25 T* cur;
26 T* first;
27 T* last;
28 //中控器结点
29 map_pointer node;
30
31 //构造函数
32 __deque_iterator(T* x, map_pointer y)
33 : cur(x), first(*y), last(*y + buffer_size()), node(y) {}
34 __deque_iterator() : cur(0), first(0), last(0), node(0) {}
35 __deque_iterator(const iterator& x)
36 : cur(x.cur), first(x.first), last(x.last), node(x.node) {}
37
38 //以下全是重载
39 reference operator*() const { return *cur; }
40 pointer operator->() const { return &(operator*()); }
41 //注意这个操作符
42 difference_type operator-(const self& x) const {
43 return difference_type(buffer_size()) * (node - x.node - 1) +
44 (cur - first) + (x.last - x.cur);
45 }
46 //注意这个操作符
47 self& operator++() {
48 ++cur;
49 if (cur == last) {
50 set_node(node + 1);
51 cur = first;
52 }
53 return *this;
54 }
55 self operator++(int) {
56 self tmp = *this;
57 ++*this;
58 return tmp;
59 }
60
61 self& operator--() {
62 if (cur == first) {
63 set_node(node - 1);
64 cur = last;
65 }
66 --cur;
67 return *this;
68 }
69 self operator--(int) {
70 self tmp = *this;
71 --*this;
72 return tmp;
73 }
74 //注意这个操作符
75 self& operator+=(difference_type n) {
76 difference_type offset = n + (cur - first);
77 if (offset >= 0 && offset < difference_type(buffer_size()))
78 cur += n;
79 else {
80 difference_type node_offset =
81 offset > 0 ? offset / difference_type(buffer_size())
82 : -difference_type((-offset - 1) / buffer_size()) - 1;
83 set_node(node + node_offset);
84 cur = first + (offset - node_offset * difference_type(buffer_size()));
85 }
86 return *this;
87 }
88
89 self operator+(difference_type n) const {
90 self tmp = *this;
91 return tmp += n;
92 }
93
94 self& operator-=(difference_type n) { return *this += -n; }
95
96 self operator-(difference_type n) const {
97 self tmp = *this;
98 return tmp -= n;
99 }
100
101 reference operator[](difference_type n) const { return *(*this + n); }
102
103 bool operator==(const self& x) const { return cur == x.cur; }
104 bool operator!=(const self& x) const { return !(*this == x); }
105 bool operator<(const self& x) const {
106 return (node == x.node) ? (cur < x.cur) : (node < x.node);
107 }
108 //用来跳一个缓冲区
109 void set_node(map_pointer new_node) {
110 node = new_node;
111 first = *new_node;
112 last = first + difference_type(buffer_size());
113 }
114 };

  代码中最重要的就是迭代器重载的那些操作符,有*,->,-,前置++,后置++,前置--,后置--,+=,+,-=,-,[],==,!=,<!可以看出
deque 的迭代器是一个 Random Access
Iterator。我们要注意的几个操作符是++,--,+=,-=,+,-,这些操作都涉及到指针的移动,而deque是伪连续线性空间,在到移动到一个缓冲区尾部时,应该要用函数set_node()跳到下一个缓冲区。也就是说,我们要处理好边界情况。deque
的迭代器有些复杂,关键我们要知道 deque 的逻辑结构,才能知道迭代器操作符的的具体操作步骤。

时间: 2024-10-26 09:23:12

vector,list,deque容器的迭代器简单介绍的相关文章

C++ STL中vector(向量容器)使用简单介绍

原文:http://www.seacha.com/article.php/knowledge/cbase/2013/0903/2205.html C++ vector(向量容器)是一个线性顺序结构.相当于数组,但其大小可以不预先指定,并且自动扩展.它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组,或者作为动态内存. 在创建一个vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定,这个大小即cap

C++——STL之vector, list, deque容器对比与常用函数

STL 三种顺序容器的特性对比: vector 可变数组,内存空间是连续的,容量不会进行缩减.支持高效随机存取,即支持[]和at()操作.尾部插入删除效率高,其他位置插删效率较低: list 双向链表,内存空间可不连续,不支持随机存取.插入和删除的效率很高: deque  双端队列,内存空间是多个连续的内存块,在一个映射结构中保存对这些块以及顺序的跟踪,可利用的内存更大,且内存大小是可以自动缩减的.支持随机存取,但是随机存取性能没有vector 好.首尾插入效率高,其他位置插删效率低: 使用注意

STL之deque容器的实现框架

说明:本文仅供学习交流,转载请标明出处,欢迎转载! vector底层采用的是一个数组来实现,list底层采用的是一个环形的双向链表实现,而deque则采用的是两者相结合,所谓结合,并不是两种数据结构的结合,而是某些性能上的结合.我们知道,vector支持随机访问,而list支持常量时间的删除,deque支持的是随机访问以及首尾元素的删除. deque是double ended queue的缩写,读作deck.首先我们用一个图来说明deque底层所采用的数据结构. 这个数据结构有一种似曾相识的感觉

c++STL容器之deque容器

deque是双端数组. deque和vector的区别: vector对于头部的插入和删除效率低,数据量越大,效率越低: deque相对于而言,对头部的插入和删除比vector快: vector访问元素时速度比deque快,这和两者的内部实现有关: deque内部工作原理: deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放着真实数据.中控器维护的是每个缓冲区的地址,使得使用每个deque时像一块连续的内存空间. deque容器的迭代器是支持随机访问的. 一.deque构造函数 de

C++各个容器比较(vector,deque,list,set,map,queue,stack)

1.vector(连续的空间存储,可以使用[ ]操作符)可以快速的访问随机的元素,快速的在末尾插入元素,但是在序列中间随机的插入.删除元素要慢.而且,如果一开始分配的空间不够时,有一个重新分配更大空间的过程. 2.deque(小片的连续,小片间用链表相连,实际上内部有一个map的指针,因为知道类型,所以还是可以使用[ ],只是速度没有vector快)快速的访问随机的元素,快速的在开始和末尾插入元素.随机的插入删除元素要慢,空间的从新分配空间后,原有的元素不需要备份.对deque的排序操作,可将d

C++顺序容器vector、deque、list

1.容器元素类型 C++中大多数数据类型能够作为容器的元素类型.容器元素类型必须满足一下两个条件:支持赋值和复制操作. 所以没有元素是引用类型的容器,同一时候IO对象和auto_ptr也不能作为容器的元素类型. 2.vector容器的自增长 vector容器中存储的元素在内存中是连续存储的.假如容器中没有空间容纳新元素.此时因为元素必须连续存储以便索引訪问,所以不能在内存中随便找个地方存储这个新的元素,于是vector必须又一次分配空间.用于存放原来的元素和新加入的元素:存放在旧容器中的元素被拷

C++三种容器:list、vector和deque的区别

在写C++程序的时候会发现STL是一个不错的东西,减少了代码量,使代码的复用率大大提高,减轻了程序猿的负担.还有一个就是容器,你会发现要是自己写一个链表.队列,或者是数组的时候,既要花时间还要操心怎么去维护,里面的指针啊,内存够不够用啊,长度问题,有没有可能溢出啊等等一系列的问题等着我们去解决,还是比较头疼的.所以容器的出现解决了这一个问题,它将这些数据结构都封装成了一个类,只需要加上头文件,我们就可以轻松的应用,不用那么复杂,就连指针也被封装成了迭代器,用起来更方便,更人性化,方便了我们的编程

STL容器 vector,list,deque 性能比较

C++的STL模板库中提供了3种容器类:vector,list,deque对于这三种容器,在觉得好用的同时,经常会让我们困惑应该选择哪一种来实现我们的逻辑.在少量数据操作的程序中随便哪一种用起来感觉差别并不是很大,但是当数据达到一定数量后,会明显感觉性能上有很大差异. 本文就试图从介绍,以及性能比较两个方面来讨论这个问题. vector - 会自动增长的数组 list - 擅长插入删除的链表 deque - 拥有vector和list两者优点的双端队列 性能竞技场 性能总结与使用建议 测试程序清

stl容器区别: vector list deque set map及底层实现

在STL中基本容器有: vector.list.deque.set.map set 和map都是无序的保存元素,只能通过它提供的接口对里面的元素进行访问 set :集合, 用来判断某一个元素是不是在一个组里面,使用的比较少 map :映射,相当于字典 ,把一个值映射成另一个值,如果想创建字典的话使用它好了 底层采用的是树型结构,多数使用平衡二叉树实现 ,查找某一值是常数时间,遍历起来效果也不错, 只是每次插入值的时候,会重新构成底层的平衡二叉树,效率有一定影响. vector.list.dequ