容器类型上的操作形成了一种层次:
- 某些操作是所有容器类型都提供的。
- 另一些操作针对顺序容器、关联容器或无序容器。
- 还有一些操作只适用于一小部分容器。
一般每个容器都定义在一个头文件之中,文件名与类型名相同,即 deque
定义在头文件 deque
中,list
定义在 list
中,依次类推。
容器都是模板类,对大多数,但不是所有容器,还需要提供额外的元素类型信息。
对容器可以保存的元素类型的限制
顺序容器几乎可以保存任意类型的元素,特别是,可以定义一个容器,其元素类型是另一容器。
vector<vector<string>> lines;
交旧的编译器可能需要在两个尖括号之间键入空格:
vector<vector<string> > lines;
顺序容器构造函数的一个版本是接受容器大小参数,它使用了元素类型的默认构造函数,但某些类没有默认的构造函数,可以定义一个保存这种类型对象的容器,但构造这种容器时不能只传递一个元素数目参数:
//假设 noDefault 是一个没有默认构造函数的类型
vector<noDefault> v1(10, init); //正确,提供了元素初始化器
vector<noDefault> v1(10); //错误,必须提供一个元素初始化器
类型别名 | 说明 |
---|---|
iterator | 此容器类型的迭代器类型 |
const_iterator | 可以读取元素,但不能修改元素的迭代器类型 |
size_type | 无符号整数类型,足够保存此种容器类型最大可能容器的大小 |
difference_type | 带符号整数类型,足够保存两个迭代器之间的大小 |
value_type | 元素类型 |
reference | 元素的左值类型,与value_type |
const_reference | 元素的const左值类型,即const value_type& |
构造函数 | 说明 |
---|---|
C c | 默认构造函数,构造空容器 |
C c1(c2) | 构造c2的拷贝c1 |
C c(b,e) | 构造c,将迭代器b和e指定的范围内的元素拷贝到c ( array不支持) |
C c{a,b,c...} | 列表初始化c |
赋值与swap | 说明 |
---|---|
c1 = c2 | 将c1中的元素替换为c2中的元素 |
c1 = {a,b,c...} | 将c1中的元素替换为列表元素( array不支持) |
a.swap(b) | 交换a和b的元素 |
swap(a,b) | 与a.swap(b)等价 |
大小 | 说明 |
---|---|
c.size() | c中元素的数目,不支持forward_list |
c.max_size() | c可保存的最大元素数目 |
c.empty() | 若c中存储了元素,返回false,否则返回true |
添加、删除元素(不适用于array) | 说明 |
---|---|
c.insert(args) | 将args的元素拷贝进c |
c.emplace(inits) | 使用inits构造c中的一个元素 |
c.erase(args) | 删除args指定的元素 |
c.clear() | 删除c中所有元素,返回void |
关系运算符 | 说明 |
---|---|
==,!= | 所有容器都支持相等、不等运算符 |
<,<=,>,>= | 关系运算符,无序关联容器不支持 |
获取迭代器 | 说明 |
---|---|
c.begin(),c.end() | 返回指向c首元素和尾元素之后位置的迭代器 |
c.cbegin(),c.cend() | 返回const_iterator |
反向容器的额外成员(forward_list不支持) | 说明 |
---|---|
reverse_iterator | 按逆序寻址元素的迭代器 |
const_reverse_iterator | 不能修改元素的逆序迭代器 |
c.rbegin(),c.rend() | 返回指向c的尾元素和首元素之前位置的迭代器 |
c.crbegin(),c.crend() | 返回const_reverse_iterator |
迭代器
迭代器范围
一个迭代器的范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置,这两个迭代器通常被称为 begin
和 end
。这种元素范围称为左闭右开区间,即 [begin,end)
。
两个迭代器构成范围迭代器的要求:
- 它们指向同一个容器中的元素,或者是容器最后一个元素之后的位置。
- 可以通过反复递增
begin
来到达end
,也就是说,end
不应该在begin
之前。
假定 begin
和 end
构成一个合法的迭代器范围:
- 如果
begin
和end
相等,则范围为空。 - 如果
begin
和end
不相等,则范围至少包含一个元素,且begin指向范围中的第一个元素。 - 可以对
begin
递增若干次,使得begin == end
。
因此可以采用一个循环来处理一个元素范围:
while(begin != end){
*begin = val;
++begin;
}
容器类型成员
大多数容器还提供反向迭代器,反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义都是颠倒的,例如,对一个反向迭代器执行 ++
,会得到上一个元素。
如果需要使用元素类型,则可以使用容器的 value_type
。
如果需要元素类型的一个引用,可以使用 reference
或 const_reference
。
begin 和 end 成员
begin
和 end
操作生成指向容器第一个元素和尾后元素之后的位置的迭代器。这两个迭代器最常用的就是形成一个包含容器所有元素的迭代器范围。
r
开头的版本返回反向迭代器。
c
开头的版本返回 const
迭代器。
list<sting> a = { "Milton","Shakespeare","Austen" };
auto it1 = a.begin(); //list<string>::iterator
auto it2 = a.rbegin(); //list<string>::reverse_iterator
auto it3 = a.cbegin(); //list<string>::const_iterator
auto it4 = a.crbegin(); //list<string>::const_reverse_iterator
auto
与 begin
或 end
结合使用时,获取的迭代器类型依赖于容器类型:
//显示指定类型
list<string>::iterator it5 = a.begin();
list<string>::const_iterator it6 = a.begin();
auto it7 = a.begin(); //仅当a是const时,it7是const_iterator
auto it8 = a.cbegin(); //it8 是const_iterator
不需要写访问时,应该使用 cbegin
和 cend
。
容器定义和初始化
每个容器类型都定义了一个默认构造函数,除了 array 之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
将一个容器初始化为另一容器的拷贝
将一个新容器创建为另一个容器的拷贝方法有两种:
- 直接拷贝整个容器。
- 拷贝由一个迭代器对指定的元素范围(array除外)。
为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配。
传递迭代器参数来拷贝一个范围时,就不要求容器类型相同,而且新、旧容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。
list<sting> authors = { "Milton","Shakespeare","Austen" };
vector<const char*> articles = {"a","an","the"};
list<string> list2(authors); //正确,类型匹配
deque<string> authlist(authors); //错误,容器类型不匹配
vector<string> words(articles); //错误,容器必须匹配
//正确,可以将const char* 转换成string
forward_list<string> words(articles.begin(), articles.end());
//假设it表示articles的一个元素,可以拷贝一个指定的范围
forward_list<string> words2(articles.begin(), it));
列表初始化
list<sting> authors = { "Milton","Shakespeare","Austen" };
vector<const char*> articles = {"a","an","the"};
对于除 array
之外的容器类型,列表初始化还隐含的指定了容器的大小,容器将包含与初始值一样多的元素。
与顺序容器大小相关的构造函数
除了与关联容器相同的构造函数之外,顺序容器(array除外)还提供了另一个构造函数,它接受一个容器大小和一个元素初始值(可选),如果不提供初始元素值,则标准会创建一个值初始化:
vector<int> ivec(10, -1);
list<string> svec(10,"hi");
forward_list<int> ivec(10); //10个元素,每个元素都是0
deque<string> svec(10); //10个元素,每个元素都是空的string
- 如果元素类型是内置类型或者具有默认构造函数的类型,可以只为构造函数提供一个容器大小。
- 如果元素类型没有默认的构造函数,除了提供容器大小参数外,还必须指定一个显示的元素初始值。
- 只有顺序容器的构造函数接受大小参数,关联容器并不支持。
标准库 array 具有固定大小
当定义一个 array
时,除了需要指定元素类型,还需要指定容器的大小。
array<int,42>; //保存42个int的数组
array<string,42>; //保存42个string的数组
为了使用 array
类型,必须同时指定元素类型和大小:
array<int,42>::size_type i; //数组类型包括元素的类型和大小
array<int>::size_type j; //错误,array<int>不是一个类型
一个默认构造的 array
是空的:它包含了与其大小一样多的元素,元素被执行默认初始化。
如果对 array
进行列表初始化,初始值的数目必须等于或者小于 array
的大小。如果初始值数目小于 array
的大小,则它们被用来初始化 array
靠前的元素,剩余的元素执行值初始化。如果元素类型是一个类类型,那么该类必须有默认的构造函数,以使值初始化能够进行。
array<int,10> ia1; //10个默认初始化的int
array<int,10> ia2 = {0,1,2,3,4,5,6,7,8,9}; //列表初始化
array<int,10> ia3; = {42}; //ia3[0] = 42,其它值为0
内置数组不能执行拷贝或赋值,但是array不受此限制:
int digs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs; //错误,内置数组不支持拷贝或赋值
array<int,10> digits = {0,1,2,3,4,5,6,7,8,9};
array<int,10> copy = digits; //正确,只要数组类型匹配即可
赋值和 swap
赋值运算符将其左边容器中的全部元素替换为右边容器中的元素的拷贝:
c1 = c2; //c1的 内容替换为c2中的元素拷贝
c1 = {a,b,c} //赋值后,c1的大小为3
第一个赋值运算后,左边容器将与右边容器相等,如果两个容器的原来大小不同,赋值运算后的两者的大小都与右边容器的原大小相同。
array
类型允许赋值,赋值符号左右两边的运算对象必须具有相同的类型。
array<int,10> a1 = {0,1,2,3,4,5,6,7,8,9};
array<int,10> a1 = {0}; //所有元素的值都是0
a1 = a2;
a2 = {0}; //错误,不能将一个花括号列表赋予数组
由于右边运算对象的大小可能与左边运算对象的大小不同,因此 array
不支持 assign
,也不允许用花括号包围的值列表进行赋值。
使用 assign (仅顺序容器)
顺序容器(array 除外)还定义了一个 assign
成员,允许我们从一个不同但类型相容的类型赋值,或者从容器的一个子序列赋值。assign
操作用参数所指定的元素替换左边容器中的所有元素:
list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误,容器类型不匹配
names.assign(oldstyle.cbegin(),oldstyle.cend()); //正确,可以将const char* 转换成string
由于就的元素被代替,因此传递给 assign
的迭代器不能指向调用 assign
的容器。
assign 的第二个版本接受一个整型值和一个元素,它用指定书目且具有相同给定值的元素替换容器中的原有元素:
list<string> slist1(1); //1个元素,指定为空的string
slist1.assign(10,"hi"); //10个元素,每个都是 "hi"--
使用 swap
swap 操作交换两个相同类型容器的内容,调用swap之后,两个容器中的元素将会交换:
vector<string> svec1(10);
vector<string> svec2(24);
swap(svec1,svec2); //svec1将包含24个元素,svec2将包含10个元素
交换两个容器内容的操作保证会很快,元素本身并未交换,swap
只是交换了两个容器的内部数据结构。
除 array
外,swap
不对任何元素进行拷贝、删除或插入操作,因此能够保证在常数时间内完成。
元素不会被移动的事实意味着,除 string
之外,指向容器的迭代器、引用和指针在 swap
操作后不会失效。它们仍然指向 swap
之前所指向的那些元素,但是在 swap
之后,这些元素已经属于不同的容器了假定 iter
在 swap
之前指向 svec1[3]
的 string
,那么 swap
之后它指向 svec2[3]
的元素。
与其他容器不同,对一个 string
调用 swap
会导致迭代器、引用和指针失效。
与其他容器不同, swap
两个 array
会真正交换它们的元素,因此,交换两个 array
所需的时间与 array
中元素的数目成正比。对于 array
,在 swap
操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个 array
中对象的元素值进行了交换。
在新标准库中,容器既提供成员函数版本的 swap
,也提供了非成员版本的 swap
。统一使用非成员版本的swap
是一个好习惯。
容器大小操作
每个容器类型都有三个与大小相关的操作:
- 成员函数
size
返回容器中元素的数目。 empty
当size
为0时返回true
,否则返回false
。max_size
返回一个等于或大于该类型容器所能容纳的最大元素数的值。
forward_list
支持 max_size
和 empty
,但不支持 size
。
关系运算符
- 只有当其元素类型也定义了相应的比较运算符时,才可以使用关系运算符来比较两个容器。
- 每个容器都支持 相等运算符(==)和不等运算符(!=)。
- 除了无序关联容器外所有的容器都支持关系运算符 (>、>=、<、<=)。
- 关系运算符左右两边的运算对象必须是相同的容器类型,且必须保存相同类型的元素。
- 比较两个容器实际上是进行元素的逐对比较,这些运算符的工作方式与string的关系运算符类似:
- 如果两个容器具有相同的大小且所有的元素对应相等,则两个容器相等,否则两个容器不相等。
- 如果两个容器大小不同,但较小的容器中每个元素都等于较大容器中对应的元素,则较小的容器小于较大的容器。
- 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。