第9章 顺序容器
- 顺序容器和关联容器
顺序容器内的元素按其位置存储和访问。
关联容器,其元素按键(key)排序。 - 顺序容器(sequential container)。
顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。 - 标准库定义了三种顺序容器类型:vector、list 和 deque(是双端队列“double-ended queue”的简写,发音为“deck”)。
头文件如下:
#include <vector>
#include <list>
#include <deque> - 标准库还提供了三种容器适配器(adaptors)。适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。
顺序容器适配器包括 stack、queue 和 priority_queue 类型。 - 顺序容器
vector 支持快速随机访问,支持下标访问 ,但在中间随机插入/删除速度慢 list 支持快速插入/删除,不支持随机访问。 deque 双端队列
stack后进先出(LIFO)堆栈queue 先进先出(FIFO)队列priority_queue有优先级管理的队列 - 所有的容器都是类模板:
vector<string> svec; // empty vector that can hold strings
list<int> ilist; // empty list that can hold ints
deque<Sales_item> items; // empty deque that holds Sales_items - 容器元素的初始化(构造函数)
容器类型最常用的构造函数是默认构造函数。在大多数的程序中,使用默认构造函数能达到最佳运行时性能,并且使容器更容易使用。
C<T> c; 创建一个名为 c 的空容器。C 是容器类型名,如 vector,T 是元素类型,如 int 或 string 适用于所有容器。 C c(c2); 创建容器 c2 的副本 c;c 和 c2 必须具有相同的容器类型,并存放相同类型的元素。适用于所有容器。要求容器类型和容器里元素的类型都必须相同。 C c(b, e); 创建 c,其元素是迭代器 b 和 e 标示的范围内元素的副本。适用于所有容器。不要求容器类型相同,容器里元素类型也可以不相同,相互兼容即可。 C c(n, t); 用 n 个值为 t 的元素创建容器 c,其中值 t 必须是容器类型 C 的元素类型的值,或者是可转换为该类型的值。 只适用于顺序容器。 C c(n); 创建有 n 个值初始化(第 3.3.1 节)(value-initialized)元素的容器 c。 只适用于顺序容器。 - 将一个容器初始化为另一个容器的副本。
vector<int> ivec;
vector<int> ivec2(ivec); // ok: ivec is vector<int>
将一个容器复制给另一个容器时,容器类型和元素类型都必须相同。 - 初始化为一段元素的副本。
不能直接将一种容器内的元素复制给另一种容器,但允许通过传递一对迭代器间接实现该实现该功能。
使用迭代器时,不要求容器类型相同,容器内的元素类型也可以不相同,只要它们相互兼容,能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。
list<string> slist(svec.begin(), svec.end());
指针就是迭代器,因此允许通过使用内置数组中的一对指针初始化容器。
char *words[] = {"stately", "plump", "buck", "mulligan"};
size_t words_size = sizeof(words)/sizeof(char *);
list<string> words2(words, words + words_size); - 分配和初始化指定数目的元素
创建顺序容器时,可显式指定容器大小和一个(可选的)元素初始化式。容器大小可以是常量或非常量表达式,元素初始化则必须是可用于初始化其元素类型的对象的值:
const list<int>::size_type list_size = 64;
list<string> slist(list_size, "eh?"); // 64 strings, each is eh?
这段代码表示 slist 含有 64 个元素,每个元素都被初始化为“eh?”字符串。
也可以只指定容器大小:
list<int> ilist(64); // 64 elements, each initialized to 0 - 容器内元素的类型约束
容器元素类型必须满足以下两个约束:
• 元素类型必须支持赋值运算。
• 元素类型的对象必须可以复制。
关联容器的键还必须支持“<”操作符。 - 容器的容器
可定义元素是容器类型的容器:vector< vector<string> > lines; //必须用空格隔开两个相邻的 > 符号。 - 常用迭代器的运算
*iter 返回迭代器 iter 所指向的元素的引用 iter->mem 对 iter 进行解引用,获取指定元素中名为 mem 的成员。等效于(*iter).mem ++iter,iter++ 给 iter 加 1,使其指向容器里的下一个元素 --iter,iter-- 给 iter 减 1,使其指向容器里的前一个元素 iter1 == iter2,
iter1 != iter2比较两个迭代器是否相等(或不等)。当两个迭代器指向同一个容器中的同一个元素,或者当它们都指向同一个容器的超出末端的下一位置时,两个迭代器相等 - vector 和 deque 容器的迭代器独有支持:迭代器算术运算:iter + n , iter - n , iter1 += iter2, iter1 -= iter2,
除了 == 和 != 之外的关系操作符来: > , >=, < , <= - list迭代器不支持算术运算,也不支持关系元算,只支持:++iter, iter++ 和 iter1 == iter2, iter1 != iter2 。
- 迭代器范围:左闭合区间
[ first, last) : 表示范围从 first 开始,到 last 结束,但不包括 last。
1). 当 first 与 last 相等时,迭代器范围为空;
2). 当 first 与不相等时,迭代器范围内至少有一个元素,而且 first 指向该区间中的第一元素。此外,通过若干次自增运算可以使 first 的值不断增大,直到 first == last 为止。 - 容器定义的类型
size_type 无符号整型,足以存储此容器类型的最大可能容器长度 iterator 此容器类型的迭代器类型 const_iterator 元素的只读迭代器类型 reverse_iterator 按逆序寻址元素的迭代器 const_reverse_iterator 元素的只读(不能写)逆序迭代器 difference_type 足够存储两个迭代器差值的有符号整型,可为负数 value_type 元素类型 reference 元素的左值类型,是 value_type& 的同义词 const_reference 元素的常量左值类型,等效于 const value_type& - 所有容器的迭代器支持的操作: begin 和 end 成员
c.begin() : 返回一个迭代器,它指向容器 c 的第一个元素
c.end() : 返回一个迭代器,它指向容器 c 的最后一个元素的下一位置
c.rbegin() : 返回一个逆序迭代器,它指向容器 c 的最后一个元素
c.rend() : 返回一个逆序迭代器,它指向容器 c 的第一个元素前面的位置 - 容器元素都是副本
在容器中添加元素时,系统是将元素值复制到容器里(所以要求元素的类型必须支持复制)。类似地,使用一段元素初始化新容器时,新容器存放的是原始元素的副本。被复制的原始值与新容器中的元素各不相关,此后,容器内元素值发生变化时,被复制的原值不会受到影响,反之亦然。 - 如果用容器存副本,则容器销毁的时候,副本也会自动被删除。
如果用容器存指针,则容器销毁的时候,不会删除这些指针所指向的对象,因此必须先手工删除完毕之后,再销毁容器。 - 顺序容器中添加元素
c.push_back(t) 在容器 c 的尾部添加值为 t 的元素。返回 void 类型。 c.push_front(t) 在容器 c 的前端添加值为 t 的元素。返回 void 类型。只适用于 list 和 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 类型。 - 添加元素可能会使迭代器失效
任何 insert 或 push 操作都可能导致某些或所有迭代器失效。当编写循环将元素插入到 vector 或 deque 容器中时,程序必须确保迭代器在每次循环后都得到更新。 - 关系操作符
所有的容器类型都支持用关系操作符来实现两个容器的比较。相比较的容器必须具有相同的容器类型,而且其元素类型也必须相同。 - 顺序容器的大小操作
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 resize 操作可能会使迭代器失效。在 vector 或 deque 容器上做 resize 操作有可能会使其所有的迭代器都失效。 - 访问顺序容器内元素的操作
c.back() 返回容器 c 的最后一个元素的引用。如果 c 为空,则该操作未定义 c.front() 返回容器 c 的第一个元素的引用。如果 c 为空,则该操作未定义 c[n] 返回下标为 n 的元素的引用 如果 n <0 或 n >= c.size(),则该操作未定义。只适用于 vector 和 deque 容器 c.at(n) 返回下标为 n 的元素的引用。如果下标越界,则该操作未定义。只适用于 vector 和 deque 容器
- 删除顺序容器内元素的操作
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 为空容器,则该函数未定义。只适用于 list 或 deque 容器。 - 顺序容器的赋值操作
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 的元素。 - 选择容器的提示
1). 如果程序要求随机访问元素,则应使用 vector 或 deque 容器。
2). 如果程序必须在容器的中间位置插入或删除元素,则应采用 list 容器。
3). 如果程序不是在容器的中间位置,而是在容器首部或尾部插入或删除元素,则应采用 deque 容器。
4). 如果只需在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可考虑在输入时将元素读入到一个 list 容器,接着对此容器重新排序,使其适合顺序访问,然后将排序后的 list 容器复制到一个 vector 容器。 - string
string类型可以视为字符容器。除了一些特殊操作,string 类型提供与 vector 容器相同的操作。string 类型与 vector 容器不同的是,它不支持以栈方式操纵容器:在 string 类型中不能使用 front、back 和 pop_back 操作。
string 类型提供了容器类型不支持其他几种操作:
• substr 函数,返回当前 string 对象的子串。
• append 和 replace 函数,用于修改 string 对象。
• 一系列 find 函数,用于查找 string 对象。 - string:substr 操作
使用 substr 操作可在指定 string 对象中检索需要的子串。
s.substr(pos, n) 返回一个 string 类型的字符串,它包含 s 中从下标 pos开始的 n 个字符 s.substr(pos) 返回一个 string 类型的字符串,它包含从下标 pos 开始到s 末尾的所有字符 s.substr() 返回 s 的副本 - append 和 replace
s.append(args) 将 args 串接在 s 后面。返回 s 引用 s.replace(pos, len, args) 删除 s 中从下标 pos 开始的 len 个字符,用 args 指定的字符替换之(在pos位置插入args)。返回 s 的引用 s.replace(b, e, args) 删除迭代器 b 和 e 标记范围内所有的字符,用 args 替换之(在pos位置插入args)。返回 s 的引用 - string 类型的查找操作
s.find( args) 在 s 中查找 args 的第一次出现 s.rfind( args) 在 s 中查找 args 的最后一次出现 s.find_first_of( args) 在 s 中查找 args 的任意字符的第一次出现 s.find_last_of( args) 在 s 中查找 args 的任意字符的最后一次出现 s.find_first_not_of( args) 在 s 中查找第一个不属于 args 的字符 s.find_last_not_of( args) 在 s 中查找最后一个不属于 args 的字符
- 关联容器支持通过键来高效地查找和读取元素;顺序容器是通过元素在容器中的位置顺序存储和访问元素。
- map 的元素以键-值(key-value)对的形式组织:键用作元素在 map 中的索引,而值则表示所存储和读取的数据。
set 仅包含一个键,并有效地支持关于某个键是否存在的查询。
set适用于存储不同值的集合。map 容器则更适用于需要存储(乃至修改)每个键所关联的值的情况。
map 关联数组,元素通过键来存储和读取 set 大小可变的集合,支持通过键实现的快速读取 multimap 支持同一个键多次出现的 map 类型 multiset 支持同一个键多次出现的 set 类型 - pair类型
pair也是一种模板类型。
pair<T1, T2> p1; 创建一个空的 pair 对象,它的两个元素分别是 T1 和 T2 类型,采用值初始化。 pair<T1, T2> p1(v1, v2); 创建一个 pair 对象,它的两个元素分别是 T1 和 T2 ,其中 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 如果两个 pair 对象的 first 和 second 成员依次相等,则这两个对象相等。该运算使用其元素的 == 操作符。 p.first 返回 p 中名为 first 的(公有)数据成员。 p.second 返回 p 的名为 second 的(公有)数据成员 。 - 关联容器不提供front、push_front、pop_front、back、push_back以及pop_back操作;
- 要使用map对象,则必须包含map头文件;
定义map对象时,必须分别指明键和值的类型。map<string, int> word_count;
map<k, v> m; 创建一个名为 m 的空 map 对象,其键和值的类型分别为 k 和 v map<k, v> m(m2); 创建 m2 的副本 m,m 与 m2 必须有相同的键类型和值类型 map<k, v> m(b, e); 创建 map 类型的对象 m,存储迭代器 b 和 e 标记的范围内所有元素的副本。元素的类型必须能转换为 pair<const k, v> - 关联容器的键类型必须支持 < 操作符,而且该操作符应能“正确地工作”;
- 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 类型 谨记: value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。 - 给 map 添加元素
(1) 使用insert 成员实现;
(2) 先用下标操作符获取元素,然后给获取的元素赋值。 - 使用下标访问 map 对象
使用下标[i]访问map中不存在的元素将导致在map容器中添加一个新的元素,它的键即为该下标值; - map::insert 的使用
m.insert(e) e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则插入一个值为 e.second 的新元素;如果该键在 m 中已存在,则保持 m 不变。该函数返回一个 pair 类型对象,包含指向键为 e.first 的元素的 map 迭代器,以及一个 bool 类型的对象,表示是否插入了该元素。 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 中具有给定键的元素。 - 查找并读取 map 中的元素
不修改 map 对象的查询操作:
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 类型 - map 对象的迭代遍历
与其他容器一样,map 同样提供 begin 和 end 运算,以生成用于遍历整个容器的迭代器。例如,可如下将 map 容器 word_count 的内容输出:
map<string, int>::const_iterator map_it = word_count.begin();
while (map_it != word_count.end())
{
cout << map_it->first << " occurs " << map_it->second << " times" << endl;
++map_it;
}
在使用迭代器遍历map 容器时,迭代器指向的元素按键的升序排列。 -
setmap 容器是键-值对的集合,set 容器只是单纯的键的集合。
两种例外包括:set 不支持下标操作符,而且没有定义 mapped_type 类型。
在 set 容器中,value_type 不是 pair 类型,而是与 key_type 相同的类型。它们指的都是 set 中存储的元素类型。与 map 一样,set 容器存储的键也必须唯一,而且不能修改。 - multimap 允许一个键对应多个实例。
multimap不支持下标运算,因为某个键可能对应多个值; - multimap添加元素
由于键不要求是唯一的,因此每次调用 insert 总会添加一个元素。 - multimap 删除元素
带有一个键参数的 erase 版本将删除拥有该键的所有元素,并返回删除元素的个数。 - 在 multimap 和 multiset 中查找元素
关联容器 map 和 set 的元素是按顺序存储的。
在 multimap 中,同一个键所关联的元素必然相邻存放。
方法一:使用 find 和 count 操作
count 函数求出某键出现的次数,而 find 操作则返回一个迭代器,指向第一个拥有正在查找的键的实例:
string search_item("Alain de Botton");
typedef multimap<string, string>::size_type sz_type;
sz_type entries = authors.count(search_item);
multimap<string,string>::iterator iter = authors.find(search_item);
for (sz_type cnt = 0; cnt != entries; ++cnt, ++iter)
cout << iter->second << endl; // print each title
首先,调用 count 确定某作者所写的书籍数目,然后调用 find 获得指向第一个该键所关联的元素的迭代器。for 循环迭代的次数依赖于 count 返回的值。在特殊情况下,如果 count 返回 0 值,则该循环永不执行。
方法二:与众不同的面向迭代器的解决方案
更优雅简洁的方法是使用两个未曾见过的关联容器的操作:lower_bound 和 upper_bound。
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) typedef multimap<string, string>::iterator authors_it;
authors_it beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item);
while (beg != end)
{
cout << beg->second << endl; // print each title
++beg;
}
第11章 泛型算法
每个泛型算法的实现都独立于单独的容器,并且不依赖于容器存储的元素类型。
泛型算法需要使用迭代器,迭代器有如下要求:
• 支持自增操作:从一个元素定位下一个元素
• 提供解引用:访问元素的值
• 支持相等和不等操作符:用于判断2个迭代器是否相等
• 提供解引用:访问元素的值
• 支持相等和不等操作符:用于判断2个迭代器是否相等
“普通”的迭代器不修改基础容器的大小。算法可能会改变存储在容器中的元素的值,也许会在容器内移动元素,但是,算法从不直接添加或删除元素。
- 头文件
使用泛型算法必须包含 algorithm 头文件:
#include <algorithm>
标准库还定义了一组泛化的算术算法(generalized numeric algorithm),使用这些算法则必须包含 numeric 头文件:
#include <numeric>
- 只读算法
(1) find
vector<int> vec;int search_value = 42;
...
vector<int>::const_iterator result = find(vec.begin(), vec.end(), search_value);
内置数组也可以使用迭代器和find实现查找
int ia[6] = {27, 210, 12, 47, 109, 83};
int search_value = 83;
int *result = find(ia, ia + 6, search_value);
(2) count
count在容器中查找某个值出现的次数
int cnt = count(vec.begin(), vec.end(), search_value);
(2) accumulate
int sum = accumulate(vec.begin(), vec.end(), 42);//将 sum 设置为 vec 的元素之和再加上 42
(3) find_first_of
该算法带有2段迭代器范围,在第一段范围内查找与第二段范围内任意元素匹配的元素,找到了返回第一个匹配的元素 迭代器,否则就返回第一段范围的end迭代器。 - 写入容器的算法
(1) fill
fill(vec.begin(), vec.end(), 0); // reset each element to 0
(2) fill_n 和 back_inserter
vector<int> vec; // empty vector
// ok: back_inserter creates an insert iterator that adds elements to vec
fill_n (back_inserter(vec), 10, 0); // appends 10 elements to vec - 写入到目标迭代器的算法
(1) copy
vector<int> ivec; // empty vector
// copy elements from ilst into ivec
copy (ilst.begin(), ilst.end(), back_inserter(ivec));
//copy 从输入范围中读取元素,然后将它们复制给目标 ivec。更好的方法如下:
// better way to copy elements from ilst
vector<int> ivec(ilst.begin(), ilst.end()); //直接初始化
(2) replace
//这个调用将所有值为 0 的实例替换成 42
replace(ilst.begin(), ilst.end(), 0, 42);
(3) replace_copy vector<int> ivec;
replace_copy (ilst.begin(), ilst.end(),back_inserter(ivec), 0, 42);
//调用该函数后,ilst 没有改变,ivec 存储 ilst 一份副本,而 ilst 内所有的 0 在 ivec 中都变成了 42 - 对容器元素重新排序的算法
(1) sort-排序
sort(words.begin(), words.end());//调用 sort 后,此 vector 对象的元素按次序排列
(2) unique-删除重复元素
vector<string>::iterator end_unique = unique(words.begin(), words.end());
//该算法删除相邻的重复元素,然后重新排列输入范围内的元素,并且返回一个迭代器,表示无重复的值范围的结束。
(3) erase-清空指定范围内的元素
words.erase(end_unique, words.end()); - 插入迭代器
• back_inserter,创建使用 push_back 实现插入的迭代器。
• front_inserter,使用 push_front 实现插入。
• inserter,使用 insert 实现插入操作。除了所关联的容器外,inserter 还带有第二实参:指向插入起始位置的迭代器。 - iostream 迭代器
istream_iterator 用于读取输入流,而 ostream_iterator 则用于写输出流
istream_iterator<int> in_iter(cin); // read ints from cin
istream_iterator<int> eof; // istream "end" iterator
vector<int> vec(in_iter, eof); // construct vec from an iterator range - 反向迭代器
rbegin 和 rend
vector<int>::reverse_iterator r_iter;
for (r_iter = vec.rbegin(); // binds r_iter to last element
r_iter != vec.rend(); // rend refers 1 before 1st element
++r_iter) // decrements iterator one element
cout << *r_iter << endl; // prints 9,8,7,...0
以降序排列 vector,只需向 sort传递一对反向迭代器:
// 按升序排序
sort(vec.begin(), vec.end());
// 按降序排序
sort(vec.rbegin(), vec.rend());
- const 迭代器