1、vector
1、vector特性
(1)vector有自动扩容操作,每次扩容伴随着“配置新空间 / 移动旧数据 / 释放旧空间”的操作,因此有一定时间成本。
(2)vector提供了reserve接口,如果能够对元素个数有大概了解,可以一开始就分配合适的空间。
(3)vector的内存空间是连续的,对插入元素的操作而言,在vector尾部插入才是合适的选择。维护的是一个连续线性空间,所以vector支持随机存取。
(4)vector动态增加大小时,并不是在原空间之后持续新空间(无法保证原空间之后尚有可供配置的空间),而是以原大小的2倍另外配置一块较大的空间,接着将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。
2、vector与array的区别
vector与array非常相似。两者的唯一区别在于空间运用的灵活性。array是静态空间,一旦配置了就不能改变;vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助。
3、vector迭代器和数据结构
vector的迭代器是普通指针。因为vector迭代器需要的操作行为,如operator*,operator->,operator++,operator+,operator+=
等,普通指针天生具备;vector支持随机存取,普通指针正有这种能力。所以vector提供的是Random Access Iterators。
vector所采用的数据结构非常简单:线性连续空间。为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充,这便是容量的观念。增加新元素时,如果容量不足,则扩充至2倍(若原大小为0,则配置为1),2倍容量仍不足,就扩充至足够大的容量。
部分操作示例:
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
vector<int> iv(2,9);
cout << "size=" << iv.size()<< endl; // size=2
cout << "capacity=" << iv.capacity()<< endl; // capacity=2
iv.push_back(1);
cout << "size=" << iv.size() << endl; // size=3
cout << "capacity=" << iv.capacity() << endl; // capacity=4
iv.push_back(2);
cout << "size=" << iv.size() << endl; // size=4
cout << "capacity=" << iv.capacity() << endl; // capacity=4
//支持指定位置存取。下面示例随机存。
vector<int>::iterator ite = find(iv.begin(), iv.end(), 1);
if (ite != iv.end())
iv.insert(ite,3,7);//在指针ite处,加入3个7
cout << "size=" << iv.size() << endl; // size=7
cout << "capacity=" << iv.capacity() << endl; // capacity=8
for(int i=0; i<iv.size(); ++i)
cout << iv[i] << ‘ ‘; // 9 9 7 7 7 1 2
cout << endl;
iv.clear();//清空
cout << "size=" << iv.size() << endl; // size=0
cout << "capacity=" << iv.capacity() << endl; // capacity=8
return 0;
}
2、list
1、list特性
(1)相较于vector的连续线性空间,list就显得复杂许多。
(2)它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。
(3)对于任何位置的元素插入或元素移除,list永远是常数时间。
(4)list不仅是一个双向链表,而且还是一个环状双向链表。它只需要一个指针便可完整表现整个链表。
(5)插入操作和接合操作都不会造成原有的list迭代器失效,这在vector是不成立的。因为vector的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效。甚至list的元素删除操作(erase),也只有“指向被删除元素”的那个迭代器失效,其他迭代器不受任何影响。
(6)list不再能够像vector那样以普通指针作为迭代器,因为其节点不保证在储存空间中连续。list提供的是Bidirectional Iterators。
2、list的构造与内存管理
list缺省使用alloc作为空间配置器,并据此定义了一个list_node_allocator
,以节点大小为配置单位。
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
list<int> ilist;
cout << "size=" << ilist.size() << endl; // size=0
ilist.push_back(0);
ilist.push_back(1);
ilist.push_back(2);
ilist.push_back(3);
ilist.push_back(4);
cout << "size=" << ilist.size() << endl; // size=5
list<int>::iterator ite;//声明迭代器
for(ite = ilist.begin(); ite != ilist.end(); ++ite)
cout << *ite << ‘ ‘; // 0 1 2 3 4
cout << endl;
//插入和接合操作不会造成原迭代器失效
ite = find(ilist.begin(), ilist.end(), 3);
if (ite != ilist.end())
ilist.insert(ite, 99);//在以前3的位置处插入一个数99,
//插入完成后,新节点位于ite所指节点前方
cout << "size=" << ilist.size() << endl; // size=6
cout << *ite << endl; // 3 !!!!
for(ite = ilist.begin(); ite != ilist.end(); ++ite)
cout << *ite << ‘ ‘; // 0 1 2 99 3 4
cout << endl;
//删除操作时,指向被删除元素的那个迭代器失效,其他不受影响
ite = find(ilist.begin(), ilist.end(), 1);
if (ite != ilist.end())
cout << *(ilist.erase(ite)) << endl;// 2 !!!
cout<<*ite<<endl;// 1 !!!
cout<<*(ite++)<<endl;// 1 !!!
for(ite = ilist.begin(); ite != ilist.end(); ++ite)
cout << *ite << ‘ ‘; // 0 2 99 3 4
cout << endl;
return 0;
}
3、list的元素操作
(1)push_front, push_back,
(2)erase(iterator position), //移除迭代器position所指节点
(3)pop_front, pop_back,
(4)clear,
(5)remove(const T& value), //将数值为value的所有元素移除
(6)unique, //移除数值相同的连续元素,只剩下一个。注意相同而连续。
(7)splice, //接合操作。将某连续范围的元素从一个list移动到另一个(或同一个)list的某个定点。
(8)merge, //将x合并到*this身上。两个lists内容必须先递增排序。
(9)reverse,
(10)sort()//list不能使用STL算法sort(),必须使用自己的sort()成员函数,因为STL算法sort()只接受RamdonAccessIterator。list中sort()函数采用quick sort。
splice()、reverse()、sort()操作示例:
int iv[5] = { 5,6,7,8,9 };
list<int> ilist2(iv, iv+5);
// 目前,ilist 的內容为0 2 99 3 4
ite = find(ilist.begin(), ilist.end(), 99);//在99前面插入
ilist.splice(ite,ilist2); // 0 2 5 6 7 8 9 99 3 4
ilist.reverse(); // 4 3 99 9 8 7 6 5 2 0
ilist.sort(); // 0 2 3 4 5 6 7 8 9 99
3、deque
1、deque与vector的区别
(1)vector是单向开口的连续线性空间,用户只能在vector尾部进行插入删除操作(也允许在某个pos处插入,但由于vector的底层实现是数组,过多非队尾位置的插入会有性能上的消耗)。而deque是一种双向开口的连续线性空间,允许在头尾两端分别做插入和删除操作。
(2)deque允许在常数时间内对起头端进行元素的插入或移除操作。
(3)deque没有所谓容量概念。它是动态地用分段连续的空间组合而成,随时可以增加一段新的空间并连接起来。没有必要提供所谓的空间保留(reserve)功能。
(4)deque的最大任务,便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随机存取的接口。避开了“重新配置、复制、释放”的轮回,代价则是复杂的迭代器架构。
(5)既是分段连续线性空间,就必须有中央控制,而为了维持整体连续的假象,数据结构的设计及迭代器前进后退等操作都颇为繁琐。deque的实现代码分量远比vector或list都多得多。所以,我们应尽可能选择vector而非deque。
2、deque的中控器
deque采用一块所谓的map(注意,不是STL的map容器)作为主控。这里所谓map是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。SGI STL 允许我们指定缓冲区大小,默认值0表示将使用512 bytes 缓冲区。
总结:
(1)map是块连续空间,其内的每个元素都是一个指针,指向一块缓冲区。
(2)进一步发现,map其实是一个T**
,也就是说它是一个指针,所指之物又是一个指针,指向型别为T的一块空间。
3、deque的迭代器
deque是分段连续空间。维持其”整体连续”假象的任务,落在了迭代器的operator++和operator–两个运算子身上。
deque的迭代器应该具备什么结构:
(1)它必须能够指出分段连续空间(亦即缓冲区)在哪里
(2)它必须能够判断自己是否已经处于其所在缓冲区的边缘,如果是,一旦前进或后退就必须跳跃至下一个或上一个缓冲区。为了能够正确跳跃,deque必须随时掌握管控中心(map)。
所以在迭代器中需要定义:当前元素的指针,当前元素所在缓冲区的起始指针,当前元素所在缓冲区的尾指针,指向map中指向所在缓冲区地址的指针。
deque在效率上不如vector,因此有时候在对deque进行sort的时候,需要先将元素移到vector再进行sort,然后移回来。
4、deque的构造与内存管理
由于deque的设计思想就是由一块块的缓存区连接起来的,因此它的内存管理会比较复杂。插入的时候要考虑是否要跳转缓存区、是否要新建map节点(和vector一样,其实是重新分配一块空间给map,删除原来空间)、插入后元素是前面元素向前移动还是后面元素向后面移动(谁小移动谁)。而在删除元素的时候,考虑是将前面元素后移覆盖需要移除元素的地方还是后面元素前移覆盖(谁小移动谁)。移动完以后要析构冗余的元素,释放冗余的缓存区。
#include <deque>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
deque<int,alloc,32> ideq(20,9); // 注意,alloc 只適用於G++
cout << "size=" << ideq.size() << endl; // size=20
// 现在,已经构造了一个deque,有20个int元素,初值皆为9
//每个缓冲区大小为32 bytes
for(int i=0; i<ideq.size(); ++i)
ideq[i]= i;
for(int i=0; i<ideq.size(); ++i)
cout << ideq[i] << ‘ ‘; // 0 1 2 3 4 5 6...19
cout << endl;
// 在最尾端增加3個元素,其值為0,1,2
for(int i=0;i<3;i++)
ideq.push_back(i);
for(int i=0; i<ideq.size(); ++i)
cout << ideq[i] << ‘ ‘; // 0 1 2 3 ... 19 0 1 2
cout << endl;
cout << "size=" << ideq.size() << endl; // size=23
// 在最尾端增加1個元素,其值為3
ideq.push_back(3);
for(int i=0; i<ideq.size(); ++i)
cout << ideq[i] << ‘ ‘; // 0 1 2 3 ... 19 0 1 2 3
cout << endl;
cout << "size=" << ideq.size() << endl; // size=24
// 在最前端增加1個元素,其值為99
ideq.push_front(99);
for(int i=0; i<ideq.size(); ++i)
cout << ideq[i] << ‘ ‘; // 99 0 1 2 3...19 0 1 2 3
cout << endl;
cout << "size=" << ideq.size() << endl; // size=25
// 在最前端增加2個元素,其值分別為98,97
ideq.push_front(98);
ideq.push_front(97);
for(int i=0; i<ideq.size(); ++i)
cout << ideq[i] << ‘ ‘; // 97 98 99 0 1 2 3...19 0 1 2 3
cout << endl;
cout << "size=" << ideq.size() << endl; // size=27
// 搜尋數值為99的元素,並列印出來。
deque<int,alloc,32>::iterator itr;
itr = find(ideq.begin(), ideq.end(), 99);
cout << *itr << endl; // 99
cout << *(itr.cur) << endl; // 99
return 0;
}
另外,deque 的元素操作也包括pop_back, pop_front, clear, erase, insert
等。