第二部分 容器和算法
[第9章 顺序容器]
三种顺序容器:vector、list、deque。三种顺序容器适配器:stack、queue、priority_queue。
容器容纳特定类型对象的集合,所有容器都是类模板。容器只定义了少量操作,大多数额外操作都由算法库提供。
顺序容器的元素排列顺序与元素值无关,而由元素添加到容器中的顺序决定。
容器元素类型必须满足两个要求:元素类型支持赋值运算、元素类型的对象可复制。
除了引用类型外,所有内置或复合类型都可以用做容器的元素类型。没有元素是引用类型的容器。除输入输出IO标准库类型(以及auto_ptr)之外,所有其他标准库类型都是有效的容器元素类型。可以定义元素类型本身就是容器的容器。
C++用一对迭代器来标记容器内元素的一个范围,这个范围用左闭合区间表示,第一个迭代器指向范围第一个元素,第二个迭代器指向范围最后一个元素的下一个元素。如果两个迭代器相等,则这个范围内元素个数为0。
所有容器的迭代器都支持的操作:*解引用、->、++、--、==、!=。vector和deque的元素在内存中是连续存放的,因此可以快速随机地访问其元素。vector和list的迭代器还支持算术运算和更多的关系元素,包括iter+n、iter+=n、iter-n、iter-=n、iter1-iter2、>、>=、<、<=。
使用迭代器时,要注意防止因为容器内元素的改变导致迭代器失效,使用无效的迭代器将会引起严重的运行时错误。
顺序容器的操作:
(1)构造函数:C c; C c(c2); C c(b, e); C c(n, t); C c(n)。
(2)添加元素:c.push_back(t)、c.push_front(t)、c.insert(p, t)、c.insert(p, n, t)、c.insert(p, b, e)。向容器中添加元素时,系统是将元素值复制到容器中,复制完成后,容器中的值和被赋值的变量不再关联。
(3)容器的比较:如果两个容器类型完全相同,则可以对这两个容器直接进行比较。容器的比较是基于容器内元素的比较进行的,规则类似于string类型的比较。如果容器元素类型不支持某种比较操作符,则容器不能用于这种比较。
(4)容器大小操作:c.size()、c.max_size()、c.empty()、c.resize(n)、c.resize(n, t)、c.capacity()、c.reserve()。
(5)访问元素:c.front()、c.back()、c[n]、c.at(n)。
(6)删除元素:c.erase(p)、c.erase(b, e)、c.clear()、c.pop_back()、c.pop_front()。
(7)赋值与swap:c1=c2、c1.swap(c2)、c.assign(b, e)、c.assign(n, t)。
c.push_front(t)、c.pop_front(t)不能用于vector。
利用容器的复制构造函数C c1(c2);以及对容器赋值c1=c2;两种操作都要求c1和c2类型及元素类型完全相同,否则不能进行这两种操作。
vector和deque的元素在物理上都是连续存放的,随机访问快,但要在中间删除或插入元素效率低。vector支持pop_back()、push_back()操作,不支持pop_front()、push_front()操作。deque同时支持pop_back()、push_back()、pop_front()、push_front()操作。
list逻辑上类似于一个双向队列,相邻元素通过指针联系,在物理上不连续存放。list随机访问操作效率低,但插入删除元素效率高。
适配器是使一事物的行为类似于另一事物的行为的一种机制。标准库提供的适配器包括容器适配器、迭代器适配器、函数适配器。
顺序容器适配器有三种:queue、priority_queue、stack。
stack和queue默认基于deque容器实现,priority_queue默认基于vector容器实现。在创建适配器时,可以指定适配器的基础容器类型,比如stack > str_stk; 指定str_stk的基础实现容器是vector类型。
stack适配器可关联任一种顺序容器类型作为基础实现容器;queue适配器要求基础容器类型提供push_front操作,因此queue只能关联list和deque;priority_queue适配器要求基础容器类型提供随机访问功能,因此只能关联vector或deque。
[第10章 关联容器]
关联容器和顺序容器的本质区别在于:关联容器通过键存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。
关联容器包括map、set、multimap、multiset。map和set中的key是唯一的,multimap和multiset中的key不唯一。关联容器不提供front、push_front、pop_front、back、push_back、pop_back操作。
默认情况下,标准库使用键类型定义的小于操作<来实现键的比较,因此关联容器的键应该定义<比较操作,其他的关系或相等运算不作要求。
map定义了几个相关类型,map::key_type是键的类型,map::mapped_value是值的类型,map::value_type是pair类型,其first元素类型是const map::key_type,second元素类型是map::mapped_type。
对map迭代器解引用将产生pair类型的对象,其first成员是const类型的,不能更改。
把键作为下标方式访问map,将产生一个mapped_type类型的左值。如果下标对应的键在map中不存在,则map中将插入这个键的一条记录,值将采用默认初始化的方式进行初始化。
set不能通过下标方式访问。set没有定义mapped_type类型,其value_type类型和key_type类型相同。set存储的键不能修改。
multimap不支持下标操作。
由于键可以重复,multimap和multiset的insert操作总会插入一个新的元素。而map和set中如果存在键则insert操作将失败。
multimap和multiset如果某个键存在多个实例,则这些实例在容器中将相邻存放。
要在multimap或multiset中查找元素,可以用三种方式:
1)先用m.count(K)得到这个key出现的次数n,然后用m.find(K)返回K第一次出现的迭代器,然后顺序往后访问n次;
2)用m.lower_bound(K)返回K第一次出现的迭代器,用m.upper_bound(K)返回K最后一次出现的下一个位置的迭代器,这两个迭代器定义的范围就是K出现的范围;
3)用m.equal_range(K)返回存储一对迭代器的pair对象,pair中存储了K出现的范围。
[第11章 泛型算法]
泛型算法基于迭代器及其操作实现,不直接操作容器,可以作用在多种容器上。泛型算法可能会改变容器元素的值,但不会直接添加或删除元素致使容器的元素数量改变。
容器除了提供常见的普通迭代器外,还提供插入迭代器(包括back_inserter、front_inserter、inserter)、流迭代器(包括istream_iterator、ostream_iterator)、反向迭代器(reverse_iterator)、const迭代器(const_iterator)。
根据算法对迭代器的要求,可以把迭代器分为五类:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。