第九章 顺序容器vector list deque
1.要定义某种特殊的容器,必须在容器名后加一对尖括号,尖括号里面提供容器中存放的元素的类型:vector<string> svec; //empty vector that can hold strings
所有的容器类型都定义了默认构造函数,用于创建指定类型的空容器对象。默认构造函数不带参数。除了默认构造函数,容器类型还提供其他的构造函数,使程序员可以指定元素初值,
C<T> c; //创建一个名为c的空容器。c是容器类型名,如vector,T是元素类型,如int或string.适合于所有容器
C c(c2); //容器c2和c必须具有相同的容器类型,并存放相同类型的元素。
C c(b, e); //创建c,其元素是迭代器b和e标本的范围内元素的副本。
C c(n, t); //用n个值为t的元素创建容器c,其中值t必须是容器类型C的元素类型的值,或是可转换为该类型的值。C c(n); //创建有n个值初始化元素的容器c。采用这种类型的初始化,元素类型必须是内置或复合类型,或是提供了默认构造函数的类类型 ,如果元素类型没有默认构造函数,则必须显示指定其元素的初始化式。
接受容器大小做形参的构造函数只适用与顺序容器,而关联容器不支持这种初始化。
2.容器内元素的类型约束必须满足以下两个约束:
*元素类型必须支持赋值运算
*元素类型的对象必须可以复制
引用不支持一般意义的赋值运算,因此没有元素师引用类型的容器。IO库类型不支持复制或赋值。因此,不能创建存放IO类型对象的容器。
支持复制和赋值功能是容器元素类型的最低要求。此外,一些操作对元素 类型还有特殊要求。如果元素类型不支持这些特殊要求,则相关的容器操作就不能执行;我们可以定义该类型的容器,但不能使用某些特定的操作。
因为容器受容器元素类型的约束,所以可定义元素是容器类型的容器。vector< vector<string> > lines; 必须用空格隔开两个相邻的>符号,以示这是两个分开的符号,否则,系统会以为>>是单个符号,为右操作符,并结果导致编译时错误。
使用迭代器编写程序时,必须留意哪些操作会使迭代器失效。使用无效迭代器将会导致严重的运行时错误。
3.顺序容器的操作 *在容器中添加元素 *在容器中删除元素 *设置容器大小 *(如果有的话)获取容器内的第一个和最后一个元素。
容器定义的类型别名
size_type 无符号整型,足以存储此容器类型的最大可能容器长度
iterator 此容器类型的迭代器类型
const_iterator 元素的只读迭代器类型
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 元素的只读逆序迭代器
difference_type 足够存储两个迭代器差值的有符号整型,可为负数
value_type 元素类型
reference 元素的左值类型,是value_type&的同义词
const_reference 元素的常量左值类型,等效于const value_type&
c.begin() 返回一个迭代器,它指向容器c的第一个元素
c.end() 它指向容器c的最后一个元素的下一位置
c.rbegin() 它指向容器c的最后一个元素
c.rend() 它指向容器c的第一个元素前面的位置
4.在顺序容器中添加元素
c.push_back(t) 在容器c的尾部添加值为t的元素,返回void类型
c.push_front(t) 在前端添加值为t的元素,返回void类型
只适用于list和depue容器类型
c.insert(p, t) 在迭代器p所指向的元素前面插入值为t的元素,返回指向新添加元素的迭代器。
c.insert(p, n, t) 返回void类型。
c.insert(p, b, e)
在容器中添加元素时,系统是将元素值复制到容器里。类似地,使用一段元素初始化新容器时,新容器存放的是原始元素的副本。被复制的原始值与新容器中的元素各不相关,此后,容器内元素值发送变化时,被复制的原值不会受到影响,反之亦然。
任何insert或push操作都可能导致迭代器失效。当编写循环将元素插入到vector或deque容器中时,程序必须确保迭代器在每次循环后都得到更新。
5.比较的容器必须具有相同的容器类型,而且元素类型也必须相同。容器的比较是基于容器内元素的比较。
容器大小的操作
c.size() 返回容器c中的元素个数。返回类型为c::size_type
c.max_size() 返回容器c中可容纳的最多元素个数,返回类型同上
c.empty()
c.resize(n) 调整容器c的长度大小,使其能容纳n个元素,如果n<c.size(), 则删除多出来的元素;否则,添加采用值初始化的新元素
c.resize(n, t) 调整容器c的大小,使其能容纳n个元素。所有新添加的元素值都为t
resize操作可能会使迭代器失效。在vector或deque容器上做resize操作有可能会使其所有的迭代器都失效。对于所有的容器类型,如果resize操作压缩了容器,则指向已删除的元素的迭代器失效。
6.访问元素
c.back() 返回容器c的最后一个元素的引用。如果c为空,则该操作未定义
c.front() 返回容器c的第一个元素的引用。如果c为空,则该操作未定义
c[n] 返回下标为n的元素的引用,如果n<0或n>=c.size(),则该操作未定义,适用于vector和deque容器。
c.at(n) 返回下标为n的元素的引用。如果下标越界,则该操作未定义,只适用于vector和deque容器。
7.删除元素
c.erase(p) 删除迭代器p所值的元素,返回一个迭代器,它指向被删除元素后面的元素。如果p指向容器内的最后一个元素,则返回的迭代器指向容器的超出末端的下一位置。如果p本身就是指向超出末端的下一位置的迭代器,则该函数未定义。
c.erase(b, e) 删除迭代器b和e所标记的范围内所有的元素,返回一个迭代器,它指向被删除元素段后面的元素。如果e本身就是指向超出末端的下一位置的迭代器,则返回的迭代器也指向容器的超出末端的下一位置
c.clear() 删除容器c的所有元素。返回void
c.pop_back() 删除容器c的最后一个元素,返回void。如果c为空容器,则该函数未定义
c.pop_front() 删除容器c的第一个元素。返回void。如果c为空容器,则该函数未定义
erase,pop_front,pop_back函数是指向被删除元素的所有迭代器失效。对于vector容器,指向删除点后面的元素的迭代器通常也会失效。而对于deque容器,如果删除时不包含第一个元素或最后一个元素,那么该deque容器相关的所有迭代器都会失效。
8.赋值与swap
赋值和assign操作使左操作数容器的所有迭代器失效。swap操作则不会使迭代器失效。完成swap操作后,尽管被交换的元素已经存放在另一容器中,但迭代器仍然指向相同元素。
c1=c2 删除容器c1的所有元素,然后将c2的元素复合给c1。c1和c2的类型(包括容器类型和元素类型)必须相同
c1.swap(c2) 交换内容:调用完该函数后,c1中存放的是c2原来的元素,c2中存放的则是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比将c2的元素复制到c1的操作快
c.assign(b, e) 重新设置c的元素:将迭代器b和e标记的范围内所有的元素复制到c中。b和e必须不是指向c中元素的迭代器
c.assign(n, t) 将容器c重新设置为存储n个值为t的元素
与复制容器元素的构造函数一样,如果两个容器类型相同,其元素类型也相同,就可以使用赋值操作符=将一个容器赋值给另一个容器。如果在不同(或相同)类型的容器内,元素类型不相同但是相互兼容,则其赋值运算必须使用assign函数。由于assign操作首先删除容器中原来存储的所有元素,因此,传递给assign函数的迭代器不能指向调用该函数的容器内的元素。
9.vector容器的自增长
标准库的实现者使用这样的内存分配策略:以最小的代价连续存储元素。由此而带来的访问元素的便利弥补了其存储代价。
capacity和reserve成员
capacity操作获取在容器需要分配更多的存储空间之前能够存储的元素总数,而reserve操作则告诉vector容器应该预留多少个元素的存储空间。
size指容器当前拥有的元素个数;而capacity则指容器在必须分配新存储空间之前可以存储的元素总数。
每当vector容器不得不分配新的存储空间时,以加倍当前容量的分配策略实现重新分配。
无论vector容器还是string类型都不支持push_front操作。
10.容器适配器
适配器是标准库中通用的概念,包括容器适配器、迭代器适配器和函数适配器。本质上,适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。
标准库提供了三种顺序容器适配器:queue、priority_queue和stack。
默认的stack和queue都基于deque容器实现,而priority_queue则在vector容器上实现。在创建适配器时,通过将一个顺序容器指定为适配器的第二个类型实参,可覆盖其关联的基础容器类型
对于给定的适配器,其关联的容器必须满足一定的约束条件。stack适配器所关联的基础容器可以是任意一种顺序容器类型。而queue适配器要求其关联的基础容器必须提供push_front运行,因此只能建立在list容器上,而不能建立在vector容器上。priority_queue适配器要求提供随机访问功能,因此可建立在vector或deque容器上,但不能建立在list容器上。
priority_queue不是直接将新元素放置在队列的尾部,而是放在比它优先级低的元素前面。标准库默认使用元素类型的<操作符来确定它们之间的优先级关系。
第十章 关联容器
关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。map的元素以键-值对的形式组织:键用作元素在map中的索引,而值则表示所存储和读取的数据。set仅包含一个键,并有效地支持关于某个键是否存在的查询。
1.引用:pair类型 该类型在utility头文件中定义。
pair<T1, T2> p1; 创建一个空的pair对象 ,它的两个元素分别是T1和T2类型,采用值初始化
pair<T1, T2> p1(v1, v2); 同时,其中first成员初始化为v1,而second成员初始化为v2
make_pair(v1, v2) 以v1和v2值创建一个新的pair对象,其元素类型分别是v1和V2的类型
p1 < p2 两个pair对象之间的小于运算,其定义遵循字典次序:如果p1.first<p2.first或者!(p2.first<p1.first)&&p1.second<p2.second,则返回true
p1==p2
p.first 返回p中名为first的(公有)数据成员
p.second 返回p中名为second的(公有)数据成员
2.关联容器共享大部分-------但并非全部--------的顺序容器操作。关联容器不提供front、push_front、pop_front、back、push_back以及pop_back操作。
“容器元素根据键的次序排列”这一事实就是一个重要的结论:在迭代遍历关联容器时,我们可确保按键的顺序访问元素,而与元素在容器中的存放位置完全无关。
3.map类型
在定义map对象时,必须分别指明键和值的类型。在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。默认情况下,标准库使用键类型定义的《操作符来实现键的比较。所用的比较函数必须在键类型上定义严格弱排序。所谓的弱排序可理解为键类型数据上的“小于”关系。虽然实际上可以选择将比较函数设计得更复杂。但无论这样的比较函数如何定义,当用于一个键与自身的比较时,肯定会导致false结果。此外,在比较两个键时,不能出现相互“小于”的情况,而且,如果k1“小于”k2,k2“小于”k3,则k1必然“小于”k3。对于两个键,如果它们相互之间都不存在“小于”关系,则容器将之视为相同的键。用做map对象的键时,可使用任意一个键值来访问相应的元素。
map定义的类型
map<K, V>::key_type 在map容器中,用做索引的键的类型
map<K, V>::mapped_type 在map容器中,键所关联的值的类型
map<K, V>::value_type 一个pair类型,它的first元素具有const _map<K, V>::key_type类型,而second元素则为map<K, V>::mapped_type类型
在学习map的接口时,需谨记value_type是pair类型,它的值成员可以修改,但键成员不能修改。
map迭代器进行解引用将产生pair类型的对象
给map添加元素
a.使用下标访问map对象 使用下标访问map与使用下标访问数组或vector的行为截然不同:用下标访问不存在的元素将导致在map容器中添加一个新的元素,它的键即为该下标值。所关联的值采用值初始化:类类型的元素用默认的构造函数初始化,而内置类型的元素则初始化为0。
b.map::insert的使用
m.insert(e) e是一个用在m上的value_tyep类型的值。如果键(e.first)不在m,则插入一个值为e.second的新元素;如果该键在m中已存在,则保持m不变。该函数返回一个pair类型对象,包含指向键e.first的元素的map迭代器,以及一个bool类型的对象,表示是否插入了该元素。如果该键已在容器中,则其关联的值保持不变,返回的bool值为false;如果该键不在容器中,则插入新元素,且bool值为true。
m.insert(beg, end) beg和end是标记元素范围的迭代器,其中的元素必须为m.value_type类型的键-值对。对于该范围内的所有元素,如果它的键在m中不存在,则将该键及其关联的值插入到m。返回void类型。
m.insert(iter, e) e是一个用在m上的value_type类型的值。如果键(e.first)不在m中,则创建新元素,并以迭代器iter为起点搜索新元素存储的位置。返回一个迭代器,指向m中具有给定键的元素。
P313传递给insert的实参相当笨拙,可用两种方法简化:使用make_pair或使用typedef.
查找并读取map中的元素 map容器提供了两个操作:count和find,用于检查某个键是否存在而不会插入该键。
m.count(k) 返回m中k的出现次数
m.find(k) 如果m容器中存在按k索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器。
从map对象中删除元素
m.erase(k) 删除m中键为k的元素。返回size_type类型的值,表示删除的元素个数
m.erase(p) 从m中删除迭代器p所指向的元素。p必须指向m中确实存在的元素,而且不能等于m.end()。返回void类型。
m.erase(b, e) 从m中删除一段范围内的元素,该范围由迭代器对b和e标记。b和e必须标记m中的一段有效范围:即b和e都必须指向m中的元素或最后一个元素的下一位置。而且,b和e要么相等(此时删除的范围为空),要么b所指向的元素出现在e所值的元素之前。返回void类型。
4.set类型
在set容器中,value_type不是pair类型,而是与key_type相同的类型。它们指的都是set中存储的元素类型,set存储的元素仅仅是键,set容器存储的键必须唯一,而且不能修改。
5.multimap和multiset类型
允许一个键对应多个实例。
带有一个键参数的erase版本将删除拥有该键的所有元素,并返回删除元素的个数。而带有一个或一对迭代器参数的版本值删除指定的元素,并返回void类型。
与众不同的面向迭代器的解决方案
m.lower_bound(k) 返回一个迭代器,指向键不小于k的第一个元素
m.upper_bound(k) 返回一个迭代器,指向键大于k的第一个元素
m.equal_range(k) 返回一个迭代器的pair对象 它的first成员等价于m.lower_bound(k),而second成员等价于m.upper_bound(k)
在同一个肩上调用lower_bound和upper_bound,将产生一个迭代器范围,指示出该键所关联的所有元素。如果改建不在multimap中,这两个操作将返回一个迭代器,指向依据元素的排序顺序该键应该插入的位置。
这些操作返回的也可能是容器自身的超出末端迭代器。如果所查找的元素拥有multimap容器中最大的键,那么在该键上调用upper_bound将返回超出末端迭代器。如果所查找的键不存在,而且比multimap容器中所有的键都大,则lower_bound也将返回超出末端迭代器。