Sequential Containers 顺序容器
C++ 的标准库定义了 3 种顺序容器类型:
vector、list 和 deque(是双端队列"double-ended queue"的简写,发音为"deck")
它们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。
标准库还提供了 3 种容器适配器(adaptors)。
实际上,适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。
顺序容器适配器包括 stack、queue 和 priority_queue 类型,见下表——
//顺序容器 vector 支持快速随机访问 list 支持快速插入/删除 deque 双端队列 //顺序容器适配器 stack 后进先出(LIFO)堆栈 queue 先进先出(FIFO)队列 priority_queue 有优先级管理的队列
类模板(class template)的概念:“一个可创建许多潜在类类型的蓝图。”
使用类模板时,必须给出实际的类型和值。
例如,vector 类型是保存给定类型对象的模板。
创建一个 vector 对象是,必须指出这个 vector 对象所保存的元素的类型。
vector<int> 保存 int 的对象,而 vector<string> 则保存 string 对象,以此类推。
//容器构造函数的几种不同形式 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 个值初始化元素的容器 c。 只适用于顺序容器
尽管不能直接将一种容器内的元素复制给另一种容器,但系统允许通过传递一对迭代器间接实现该实现该功能。
使用迭代器时,不要求容器类型相同。容器内的元素类型也可以不相同,
只要它们相互兼容,能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。
迭代器标记了要复制的元素范围,这些元素用于初始化新容器的元素。
迭代器标记出要复制的第一个元素和最后一个元素,采用这种初始化形式可复制不能直接复制的容器。
更重要的是,可以实现复制其他容器的一个子序列:
// initialize slist with copy of each element of svec list<string> slist(svec.begin(), svec.end()); // find midpoint in the vector vector<string>::iterator mid = svec.begin() + svec.size()/2; // initialize front with first half of svec: The elements up to but not including *mid deque<string> front(svec.begin(), mid); // initialize back with second half of svec: The elements *mid through end of svec deque<string> back(mid, svec.end());
指针就是迭代器,因此允许通过使用内置数组中的一对指针初始化容器也就不奇怪了:
char *words[] = {"stately", "plump", "buck", "mulligan"}; // calculate how many elements in words size_t words_size = sizeof(words)/sizeof(char *); // use entire array to initialize words2 list<string> words2(words, words + words_size);
这里,使用 sizeof 计算数组的长度。
将数组长度加到指向第一个元素的指针上就可以得到指向超出数组末端的下一位置的指针。
通过指向第一个元素的指针 words 和指向数组中最后一个元素的下一位置的指针,
实现了 words2 的初始化。
其中第二个指针提供停止复制的条件,其所指向的位置上存放的元素并没有复制。
创建顺序容器时,可显式指定容器大小和一个元素初始化式(可选的)。
容器大小可以是常量或非常量表达式,元素初始化则必须是可用于初始化其元素类型的对象的值:
const list<int>::size_type list_size = 64; list<string> slist(list_size, "eh?"); // 64 strings, each is eh?
创建容器时,也可以只指定容器大小:
list<int> ilist(list_size); // 64 elements, each initialized to 0
不提供元素初始化式时,标准库将为该容器实现值初始化。
采用这种类型的初始化,元素类型必须是内置或复合类型,或者是提供了默认构造函数的类类型。
如果元素类型没有默认构造函数,则必须显式指定其元素初始化式。
接受容器大小做形参的构造函数只适用于顺序容器,而关联容器不支持这种初始化。
容器内元素的类型约束
C++ 语言中,容器元素类型必须满足以下两个约束:
1. 元素类型必须支持赋值运算。
2. 元素类型的对象必须可以复制。
此外,关联容器的键类型还需满足其他的约束。
大多数类型满足上述最低限度的元素类型要求。
除了引用类型外,所有内置或复合类型都可用做元素类型。
引用不支持一般意义的赋值运算,因此没有元素是引用类型的容器。
除输入输出(IO)标准库类型(auto_ptr 类型)之外,
所有其他标准库类型都是有效的容器元素类型。
特别地,容器本身也满足上述要求,因此,可以定义元素本身就是容器类型的容器。
IO 库类型不支持复制或赋值。因此,不能创建存放 IO 类型对象的容器
容器操作的特殊要求
支持复制和赋值功能是容器元素类型的最低要求。
此外,一些容器操作对元素类型还有特殊要求。
如果元素类型不支持这些特殊要求,则相关的容器操作就不能执行:
我们可以定义该类型的容器,但不能使用某些特定的操作。
vector<Foo> empty; // ok: no need for element default constructor vector<Foo> bad(10); // error: no default constructor for Foo vector<Foo> ok(10, 1); // ok: each element initialized to 1
我们定义一个存放 Foo 类型对象的空容器,
但是,只有在同时指定每个元素的初始化式时,
才能使用给定容器大小的构造函数来创建同类型的容器对象。
在描述容器操作时,我们应该留意(如果有的话)每个操作对元素类型的约束。
因为容器受容器元素类型的约束,所以可定义元素是容器类型的容器。
例如,可以定义 vector 类型的容器 lines,其元素为 string 类型的 vector 对象:
// note spacing: use ">>" not ">>" when specifying a container element type vector< vector<string> > lines; // vector of vectors
注意,在指定容器元素为容器类型时,必须使用合适的空格:
vector< vector<string> > lines; // ok: space required between close > vector< vector<string>> lines; // error: >> treated as shift operator
必须用空格隔开两个相邻的 > 符号,以示这是两个分开的符号,
否则,系统会认为 >> 是单个符号,为右移操作符,并导致编译时错误。
迭代器和迭代范围
每种容器类型都提供若干共同工作的迭代器类型。与容器类型一样,所有迭代器具有相同的接口:
如果某种迭代器支持某种操作,那么支持这种操作的其他迭代器也会以相同的方式支持这种操作。
例如,所有容器迭代器都支持以解引用运算从容器中读入一个元素。
类似地,容器都提供自增和自减操作符来支持从一个元素到下一个元素的访问。
下表列出迭代器为所有标准库容器类型所提供的运算——
//迭代器为所有标准库容器类型所提供的运算 *iter 返回迭代器 iter 所指向的元素的引用
iter->mem 对 iter 进行解引用,获取指定元素中名为 mem 的成员。等效于 (*iter).mem
++iter iter++ 给 iter 加 1,使其指向容器里的下一个元素 --iter iter-- 给 iter 减 1,使其指向容器里的前一个元素
iter1 == iter2 iter1 != iter2 比较两个迭代器是否相等(或不等)。 当两个迭代器指向同一个容器中的同一个元素, 或者当它们都指向同一个容器的超出末端的下一位置时,两个迭代器相等
C++ 定义的容器类型中,只有 vector 和 deque 容器提供下面两种重要的运算集合:
迭代器算术运算,以及使用除了 == 和 != 之外的关系操作符来比较两个迭代器。
而 == 和 != 这两种关系运算适用于所有容器。
下表总结了这些相关的操作符——
iter ± n 在迭代器上加/减整数值n, 将产生指向容器中前面/后面第 n 个元素的迭代器。 新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一位置 iter1 ±= iter2 将 iter1 加上或减去 iter2 的运算结果赋给 iter1 iter1 - iter2 两个迭代器的减法,其运算结果加上右边的迭代器即得左边的迭代器。 这两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置 只适用于 vector 和 deque 容器 >, >=, <, <= 当一个迭代器指向的元素在容器中位于另一个迭代器指向的元素之前,则前一个迭代器小于后一个迭代器。 关系操作符的两个迭代器必须指向同一个容器中的元素或超出容器末端的下一位置 只适用于 vector 和 deque 容器。