http://www.2cto.com/kf/201110/108041.html
所有的标准库容器都是类模板,用以存储单一类型元素的集合。顺序容器按元素位置存储访问,关联容器按键存储访问。
1 顺序容器
将单一类型的元素按顺序存储,以下标来访问元素。标准库定义了三种顺序容器:vector,list及deque。vector支持快速随机访问;list支持快速插入删除;deque是双端队列。
a)容器初始化:
C<T> c; 创建一个名为c的容器,容器类型为C,如vector或list,T为容器内元素类型。适用于所有容器;
C c1(c); 创建容器c的副本,c1和c必须具有相同的容器类型和元素类型,适用于所有容器;
C c(b,e); 创建容器c,元素是迭代器b,e标示范围内的副本,适用于所有容器;
C c(n,t); 创建容器c,元素为n个值为t的值,t的类型必须是容器C的元素类型或者可以转换为该类型,只适用于顺序容器;
C c(n); 创建具有n个值初始化元素的容器c,元素类型为值n的类型,只适用于顺序容器;
注:* 在使用迭代器复制元素时,不要求双方容器类型相同,容器内元素类型也可以不同,只要元素类型可以相互转换即可;
* 迭代器范围[b,e)是左闭右开区间,即e所指的元素并未被复制,e只是提供了停止复制的条件;
b)元素类型约束:
元素类型必须满足:
元素类型必须支持赋值;
元素类型的对象必须可以复制;
关联容器的键类型必须且只要实现<操作符即可。
注:*大多数类型都能满足上述最低限制的约束。例外,引用不支持一般意义上的赋值,IO库类型不支持复制或赋值,所以两者以及auto_ptr类型都不能作为容器的元素类型。
2 迭代器
一种数据类型,用于遍历容器内元素,标准库容器都有自己的迭代器,配合算法使用。迭代器是标准库容器类型的配套设施。
a)迭代器范围是一个左闭右开的区间,即[b,e);
b)一些容器操作会使指向容器元素的迭代器失效,要特别注意。
c)const_iterator迭代器和const迭代器的区别;
注:*迭代器往往和容器及泛型算法结合使用,本节仅简单描述,后面会有详尽的介绍。
3 顺序容器的操作
a)begin和end操作:
c.begin(),c.end(),c.rbegin(),c.rend();
非const版本:vector<string>::iterator = c.begin();
vector<string>::reverse_iterator = c.rbegin();
const版本: vector<string>::const_iterator = c.begin();
vector<string>::const_reverse_iterator = c.rbegin();
b)添加操作:
c.push_back();在容器尾部添加值为t的元素,返回void;
c.push_front();在容器头部添加值为t的元素,返回void,只适用于list和deque;
c.insert(p,t);在迭代器p所指向的元素前面插入值为t的元素,返回指向t的迭代器;
c.insert(p,n,t);在迭代器p所指向的元素前面插入n个值为t的元素,返回void;
c.insert(p,b,e);在迭代器p所指向的元素前面插入由迭代器b和e标记的范围内的元素,返回void;
注:*添加元素可能会使迭代器失效,所以每次操作之后都应该及时更新迭代器;
c)容器大小:
c.size();返回容器内元素个数,返回类型为c::size_type;
c.max_size();返回容器内最多可容纳的元素个数,返回类型为c::size_type;
c.empty();测试容器内是否有元素;
c.resize(n);调整容器大小,使其能容纳n个元素;
c.resize(n,t);调整容器大小,使其能容纳n个元素,新添加元素以t初始化;
注:*c.resize(n)中,当n < c.size(),删除多出来的元素;否则,采用值初始化添加新元素;
*c.reserve(n);仅调整c的最大容量,并不初始化元素;
*c.resize(n)和c.resize(n,t)可能会使迭代器失效;
d)访问元素:
c.front();返回容器内第一个元素的引用,如果c为空,该操作未定义;
c.back();返回容器内最后一个元素的引用,如果c为空,该操作未定义;
c[n];和c.at(n);返回下标为n的元素的引用,n越界时,该操作未定义,只用于vector和deque;
e)删除元素:
c.erase(p);删除迭代器p指向的元素,返回一个指向被删除元素后面的元素的迭代器;
c.erase(b,e);删除迭代器b和e标记范围内的所有元素,返回一个指向被删除元素段后面的元素的迭代器;
c.clear();删除容器内的所有元素,返回void;
c.pop_back();删除容器的最后一个元素,返回void;
c.pop_front();删除容器的第一个元素,返回void,只适用于list和deque;
注:*删除元素可能会使迭代器失效,所以每次操作之后都应该及时更新迭代器;
f)赋值操作:
cl = c2;删除cl的所有元素,将c2的所有元素复制给c1,c1和c2的容器类型及元素类型必须相同;
cl.swap(c2);交换c1和c2中的所有元素,c1和c2的容器类型及元素类型必须相同;
c.assign(b,e);重新给c赋值,内容为b和e所标记范围内的元素,b和e必须不是指向c中的元素的迭代器;
c.assign(n,t);将c中的元素重新设置为n个值为t的元素;
注:*赋值和assign操作会使左操作数中的迭代器失效,但是swap操作后,迭代器仍然指向相同的元素;
*swap操作在常量时间内完成,没有移动任何元素;
4 vector容器的自增长
vector预留了额外的存储区,用于存放新添加的元素,当现有的空间用完之后再自动重新分配存储空间。capacity()和reserve()使程序员可以和vector的内存分配的实现部分交互工作。
a)c.capacity();获取c在要分配更多的存储空间之前所能容纳的元素总数;
b)c.reserve(n);c应该预留n个元素的存储空间;
注:*c.resize(n);和c.reserve(n);的区别很微妙,前者对多出来的新元素进行值初始化;后者仅仅调整存储空间,而不进行任何初始化。
*c.max_size();和c.capacity();的区别也很微妙。简单的说,c.max_size();是c.capacity();的最大值,前者是一个常量,值取决于操作系统或C++库的实现,而后者则是一个变量。
www.cplusplus.com/reference/stl/vector/max_size/有清晰的描述。
例子:
// comparing size, capacity and max_size
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
// set some content in the vector:
for (int i=0; i<100; i++) myvector.push_back(i);
std::cout << "size: " << myvector.size() << "\n";
std::cout << "capacity: " << myvector.capacity() << "\n";
std::cout << "max_size: " << myvector.max_size() << "\n";
return 0;
}
结果:
size: 100
capacity: 128
max_size: 1073741823
*c.size(); <= c.capacity(); <= c.max_size();
5 顺序容器的选用
a)顺序容器的特点:
vector容器可以实现高效的随机访问,但是除了容器尾部外,在其他任何位置添加和删除元素却很低效;
list容器可以实现高效的添加删除,但是随机访问的效率却很低;
deque容器支持对所有元素的随机访问,在容器的首部和尾部可以高效地添加删除元素,但是在中间位置添加删除元素却又很低效。
注:*应用中占优势的操作将决定使用哪种类型的容器。
6 容器适配器
适配器概念的定义:是一种标准库类型,函数或迭代器,使一种标准库类型,函数或迭代器的行为类似于另一种标准库类型,函数或迭代器。本质上,适配器是使一种事物的行为类似于另一种事物行为的机制。容器适配器在其基础容器上定义一个新的接口,使基础容器拥有另一种不同容器的工作方式。
顺序容器适配器:stack(栈),queue(队列),priority_queue(优先级队列)。
a)容器适配器通用的操作和类型:
size_type 一种类型,存储此适配器类型最大对象的长度;
value_type 元素类型;
container_type 基础容器的类型;
A a;创建一个新的容器适配器;
A a(c);创建一个新的容器适配器a,初始化为c的副本;
关系操作符:==,!=,<,<=,>,>=。
b)适配器的初始化:
deque<int> deq;
stack<int> stk();
stack<int> stk(deq);
注:*stack适配器和queue适配器的基础容器默认为deque,priority_queue的基础容器默认为vector。
c)覆盖基础容器类型:
deque<int> deq;
stack<int,vector<int> > stk(deq);
注:*stack适配器关联的基础容器可以是任意一种顺序容器类型;
*queue适配器要求基础容器必须提供push_frong操作,只能建立在list容器上;
*priority_queue适配器要求提供随机访问操作,只能建立在vector或deque容器上;
d)栈适配器的操作:
s.empty();返回栈是否为空;
s.size();返回栈中的元素个数;
s.pop();删除栈顶元素,返回void;
s.top();返回栈顶元素,但不删除该元素;
s.push(item);在栈顶压入新元素;
注:*程序员不能直接使用栈适配器所关联的基础容器的操作。
e)队列和优先级队列的操作:
q.empty();返回队列是否为空;
q.size();返回队列中的元素个数;
q.pop();删除队首元素,返回void;
q.front();返回队首元素,但不删除元素,只适用于队列;
q.back();返回队尾元素,但不删除元素,只适用于队列;
q.top();返回具有最高优先级的元素,但不删除该元素,只适用于优先级队列;
q.push(item);对于queue,在队尾压入一个元素;对于priority_queue,在基于优先级的适当位置插入一个元素。
http://www.cnblogs.com/kingcat/archive/2012/05/10/2494672.html
标准库定义了三种顺序容器类型:vector、list
它们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。
标准库还提供了三种容器适配器(adaptors)。
实际上,适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。顺序容器适配器包括
顺序容器 | |
vector<T> | 支持快速随机访问 |
list<T> | 支持快速插入/删除 |
deque<T> | 双端队列 |
顺序容器适配器 | |
stack | 后进先出(LIFO)堆栈 |
queue | 先进先出(FIFO)队列 |
priority_queue | 有优先级管理的队列 |
), resieze()(重新划分容器容量)等操作; vector不用担心越界当空间不够用的时候,系统会自动按照一定的比例(对capacity( )大小)进行扩充。在vector序列末尾添加(push_back( ))或者删除(pop_back(
))对象效率高,在中间进行插入或删除效率很低,主要是要进行元素的移动和内存的拷贝,原因就在于当内存不够用的时候要执行重新分配内存,拷贝对象到新存储区,销毁old对象,释放内存等操作,如果对象很多的话,这种操作代价是相当高的。为了减少这种代价,使用vector最理想的情况就是事先知道所要装入的对象数目,用成员函式 reserve( ) 预定下来;vector最大的优点莫过于是检索(用operator[ ])速度在这三个容器中是最快的。
deque : deque是一个double-ended。queue是由多个连续内存块构成,deque是list和vector的兼容,分为多个块,每一个块大小是512字节,块通过map块管理,map块里保存每个块得首地址。因此该容器也有索引操作operator[ ],效率没vector高。另外,deque比vector多了push_front( ) & pop_front( )操作。在两端进行此操作时与list的效率 差不多。
学习顺序容器最重要的是了解每种容器的结构原理进而在实际应用中选择最合适的容器。
三种容器都是范型类型,可以定义不同的类型容器。
容器元素类型必须满足以下两个约束:
- 元素类型必须支持赋值运算。
- 元素类型的对象必须可以复制。
顺序容器有大量的属性和操作,最常用的是迭代器,增加,删除元素。
常用迭代器运算
*iter | 返回迭代器 iter 所指向的元素的引用 |
iter->mem | 对 iter 进行解引用,获取指定元素中名为 mem 的成员。等效于 (*iter).mem |
++iter iter++ | 给 iter 加 1,使其指向容器里的下一个元素 |
--iter iter-- | 给 iter 减 1,使其指向容器里的前一个元素 |
iter1 == iter2 | 比较两个迭代器是否相等(或不等)。当两个迭代器指向同一个容器中的同一个元素,或者当它们都指向同一个容器的超出末端的下一位置时,两个迭代器相等 |
iter + n | 在迭代器上加(减)整数值 n,将产生指向容器中前面(后面)第 n 个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一位置 |
iter1 += iter2 | 这里迭代器加减法的复合赋值运算:将 iter1 加上或减去 iter2 的运算结果赋给 iter1 |
iter1 - iter2 | 两个迭代器的减法,其运算结果加上右边的迭代器即得左边的迭代器。这两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置 只适用于 vector 和 deque 容器 |
>, >=, <, <= | 迭代器的关系操作符。当一个迭代器指向的元素在容器中位于另一个迭代器指向的元素之前,则前一个迭代器小于后一个迭代器。关系操作符的两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置 只适用于 vector 和 deque 容器 |
最重要的迭代器begin() 和end() 。
顺序容器添加元素操作
c.push_back(t) | 在容器 c 的尾部添加值为 t 的元素。返回 void 类型 |
c.push_front(t) | 在容器 c 的前端添加值为 t 的元素。返回 void 类型 只适用于 vector 和 deque 容器 |
c.insert(p,t) | 在迭代器 p 所指向的元素前面插入值为 t 的新元素。返回指向新添加元素的迭代器 |
c.insert(p,n,t) | 在迭代器 p 所指向的元素前面插入 n 个值为 t 的新元素。返回 void 类型 |
c.insert(p,b,e) | 在迭代器 p 所指向的元素前面插入由迭代器 b 和 e 标记的范围内的元素。返回 void 类型 |
顺序容器的大小操作
c.size() | 返回容器 c 中的元素个数。返回类型为 c::size_type |
c.max_size() | 返回容器 c 可容纳的最多元素个数,返回类型为 c::size_type |
c.empty() | 返回标记容器大小是否为 0 的布尔值 |
c.resize(n) | 调整容器 c 的长度大小,使其能容纳 n 个元素,如果 n < c.size(),则删除多出来的元素;否则,添加采用值初始化的新元素 |
c.resize(n,t) | 调整容器 c 的长度大小,使其能容纳 n 个元素。所有新添加的元素值都为 t |
看下面例子:
list<int> ilist(10, 42);
ilist.resize(15);
ilist.resize(25, -1);
ilist.resize(5);
resize 操作可能会使迭代器失效。在 vector 或 deque 容器上做 resize 操作有可能会使其所有的迭代器都失效。
对于所有的容器类型,如果 resize 操作压缩了容器,则指向已删除的元素迭代器失效。
vector 预分配机制: 可以在元素不存在的情况下预分配一段空间,为以后的存储做准备。这段空间可以用reserve()调节。capacity()返回的值就是可以存放元素的个数。capacity() - size()就是下次重新进行空间分配前的预留元素个数。至于max_size() 指的是一个vector结构可供储存元素的个数的上线,通常是由于寻址空间决定的。
访问顺序容器内元素的操作 c.back() | 返回容器 c 的最后一个元素的引用。如果 c 为空,则该操作未定义 |
c.front() | 返回容器 c 的第一个元素的引用。如果 c 为空,则该操作未定义 |
c[n] | 返回下标为 n 的元素的引用 如果 n <0 或 n >= c.size(),则该操作未定义 只适用于 vector 和 deque 容器 |
c.at(n) | 返回下标为 n 的元素的引用。如果下标越界,则该操作未定义 只适用于 vector 和 deque 容器 |
使用下标运算的另一个可选方案是 at 成员函数。这个函数的行为和下标运算相似,但是如果给出的下标无效,at 函数将会抛出 out_of_range 异常:
vector<string> svec;
cout << svec[0]; // run-time error: There are no elements in svec!
顺序容器有大量的成员可用,学习容器不需要记住所有成员操作时可查阅相关文档。
顺序容器还有三种适配器,主要作用是包装其它容器使之具有某种操作特征。
1 stack 堆栈适配器 ( 可用的容器类型 vector deque list)
2 queue 队列适配器 ( 可用的容器类型 deque list)
3 priority_queue 优先级队列 (可用的容器类型 deque vector)
stack 用法:
#include <iostream>
#include <stack> //堆栈适配器头文件
#include <list>
using namespace std;
int main(void)
{
std::stack< int,std::list<int> > charStack;
cout << "入栈:" << endl;
for(int i=0;i<10;i++)
{
cout<<i+66<<endl;
charStack.push(66+i);
}
cout<<"出栈:"<<endl;
int size=charStack.size();
for (i=0;i<size;i++)
{
cout<<charStack.top()<<endl;
charStack.pop();
}
cout<<endl;
return 0;
}
queue用法:
#include <iostream>
#include <queue>
#include <list>
using namespace std;
int main()
{
std::queue< int,list<int> > intQueue;
cout << "入队:" << endl;
for(int i=1;i<=10;i++)
{
intQueue.push(i*100);
cout<<i*100<<endl;
}
cout<<"出队:"<<endl;
int size=intQueue.size();
for(i=0;i<size;i++)
{
cout<<intQueue.front()<<endl;
intQueue.pop();
}
return 0;
}
priority_queue 用法:
#include <iostream>
#include <queue>
#include <list>
using namespace std;
int main()
{
std::priority_queue< int,vector<int>,std::greater<int> > intPQueue; //优先级greater<type> 可换成 less<type>
intPQueue.push(100);
intPQueue.push(500);
intPQueue.push(600);
intPQueue.push(200);
intPQueue.push(300);
intPQueue.push(400);
int size=intPQueue.size();
for(int i=0;i<size;i++)
{
cout<<intPQueue.top()<<endl;
intPQueue.pop();
}
return 0;
}