第九章_顺序容器_9.2 容器库概览

9.2 容器库概览

容器类型上的操作形成了一种层次:

  • 某些操作是所有容器类型都提供的
  • 另外一些操作仅针对顺序容器、关联容器或无序容器
  • 还有一些操作仅适用于一小部分容器

一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。容器均定义为模板类,对大多数,但不是所有容器,还需要额外提供元素类型信息。

对容器可以保存的元素类型的限制

顺序容器几乎可以保存任意类型的元素。特别是,可以定义一个容器,其元素类型是另一个容器:

vector<vector<string>> lines; // vector 的 vector

较旧的编译器可能需要在两个尖括号之前键入空格,例如

vector<vector<string> > lines;

虽然可以在容器中保存几乎任何类型,但某些容器操作对元素类型有其自己的特殊要求。可以为不支持特定操作需求的类型定义容器,但是这种情况下就只能使用那些没有特殊要求的容器操作。

容器操作如下:

在这里插入图片描述

在这里插入图片描述

9.2.1 迭代器

迭代器有着公共的接口:如果一个迭代器提供某个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。

迭代器范围(iterator range)

一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。这两个迭代器通常被称为 begin 和 end,或者是 first 和 last,它们标记了容器中元素的一个范围。

迭代器范围中的元素包含 begin 所表示的元素以及从 begin 开始直至 end(但不包含 end)之间的所有元素。

这种元素范围被称为左闭合区间(left-inclusive interval),其标准数学描述为:

[begin, end)

表示范围自 begin 开始,于 end 之前结束。迭代器 begin 和 end 必须指向相同的容器。end 可以与 begin 指向相同的位置,但不能指向 begin 之前的位置。

使用左闭合范围蕴含的编程假定

假定 begin 和 end 构成了一个合法的迭代器范围,则:

  • 如果 begin 和 end 相等,则范围为空
  • 如果 begin 和 end 不等,则范围内至少包含一个元素,且 begin 指向该范围中的第一个元素
  • 我们可以对 begin 递增若干次,使得 begin == end

因此我们可以使用一个循环来处理一个元素范围,例如:

while (begin != end) {
    *begin = val;	// 正确:范围非空,因此 begin 指向一个元素
    ++begin;		// 移动迭代器,获取下一个元素
}

9.2.2 容器类型成员

每个容器都定义了多个类型,如 size_type、iterator 和 const_iterator。

大多数容器还提供反向迭代器,反向迭代器是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义也都发生了颠倒。例如:对一个反向迭代器执行 ++ 操作,会得到上一个元素。

通过类型别名,可以在不了解容器中元素类型的情况下使用它。如果需要元素类型,可以使用容器的 value_type。如果需要元素类型的一个引用,可以使用 reference 或 const_reference。为了使用这些类型,必须显式使用其类名:

// iter 是通过 list<string> 定义的一个迭代器类型
list<string>::iterator iter;
// count 是通过 vector<int> 定义的一个 difference_type 类型
vector<int>::difference_type count;

9.2.3 begin 和 end 成员

begin 和 end 有多个版本:带 r 的版本返回反向迭代器;以 c 开头的版本则返回 const 迭代器:

list<string> 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

不以 c 开头的函数都是被重载过的。以 c 开头的版本是 C++ 11 引入的,用以支持 auto 与 begin 和 end 函数结合使用。当 auto 与 begin 和 end 结合使用时,获得的迭代器类型依赖于容器类型,与我们想要如何使用迭代器毫不相干。但以 c 开头的版本还是可以获得 const_iterator 的,而不管容器的类型是什么。

// 显式指定类型
list<string>::iterator it5 = a.begin();
list<string>::const_iterator it6 = a.cbegin();

auto it7 = a.begin();	// 是 iterator 还是 const_iterator 依赖于 a 的类型
auto it8 = a.cbegin();	// 一定是 const_iterator

9.2.4 容器定义和初始化

每个容器类型都定义了一个默认构造函数。除 array 之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。

在这里插入图片描述

将一个容器初始化为另一个容器的拷贝

将一个新容器创建为另一个容器的拷贝的方法有两种:

  • 直接拷贝整个容器
  • 拷贝一个由迭代器对指定的元素范围(array 除外)

为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配。不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且,新容器和原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。

// 每个容器有三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milon", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};

list<string> list2(authors);		// 正确:容器类型和元素类型均匹配
deque<string> authList(authors);	// 错误:容器类型不匹配
vector<string> words(articles);		// 错误:容器类型不匹配
forward_list<string> words(articles.begin(), articles.end());  // 正确:元素类型可以互相转换

由于两个迭代器表示一个范围,因此可以使用这种构造函数来拷贝一个容器中的子序列。例如,假定迭代器 it 表示 authors 中的一个元素:

// 拷贝元素,直到(不包括)it 指向的元素
deque<string> authList(authors.begin(), it);

列表初始化

可以对一个容器进行列表初始化:

// 每个容器有三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milon", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};

当使用列表初始化时,显式地指定了容器中每个元素的值,对除了 array 之外的容器类型,初始化列表还隐含的指定了容器的大小:容器将包含于初始值一样多的元素。

与顺序容器大小相关的构造函数

vector<int> ivec(10, -1);		// 10 个 int 元素,每个都初始化为 -1
list<string> svec(10, "hi!");	// 10 个 string 元素,每个都初始化为 hi!
forward_list<int> ivec(10);		// 10 个 int 元素,每个都初始化为 0
deque<string> sevc(10);			// 10 个 string 元素,每个都是空 string

如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。如果元素类型没有默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值。

只有顺序容器的构造函数才接受大小参数,关联容器并不支持。

标准库 array 具有固定大小

当定义一个 array 时,除了指定元素类型,还要指定容器大小:

array<int, 42> 			// 保存 42 个 int 的数组
array<string, 10> 		// 保存 10 个 string 的数组

为了使用 array 类型,必须同时指定元素类型和大小

array<int, 10>::size_type i;	// 数组类型包括元素类型和大小
array<int>::size_type j;		// 错误:array<int>不是一个类型

一个默认构造的 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;	// 正确:只要数组类型匹配即合法

9.2.5 赋值和 swap

在这里插入图片描述

上图列出的与赋值相关的运算符可用于所有容器。赋值运算将其左边容器中的全部元素替换为右边容器中的元素的拷贝。

使用 assign(仅顺序容器)

顺序容器(array 除外)定义了一个名为 assign 的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。

assign 操作用参数所指定的元素(的拷贝)替换它左边容器中的所有元素。

// 将一个 vector 中的一段 char* 值赋给一个 list 的 string
list<string> names;
vector<const char*> oldstyle;
// 错误:容器类型不匹配
names = oldstyle;  
// 正确:可以将 const char* 转换为 string
names.assign(oldstyle.cbegin(), oldstyle.cend());

由于其旧元素将被替换,因此传递给 assign 的迭代器不能指向调用 assign 的容器。

另一个版本的 assign 接受一个整型值和一个元素值,用指定数目且具有相同给定值的元素替换容器中原有的元素:

// 等价于 slist1.clear()
// 后跟 slist1.insert(slist1.begin(), 10, "Hiya!");
list<string> slist1(1);			// 1 个元素,为空 string
slist1.assign(10, "Hiya!");		// 10 个元素,每个都是 “Hiya!”

使用 swap

swap 操作交换两个相同类型容器的内容。调用 swap 后,两个容器中的元素将会交换:

vector<string> svec1(10);	// 10 个元素
vector<string> svec2(24);	// 24 个元素
swap(svec1, svec2);			// svec1 现在有 24个 元素,svec2 现在有 10 个元素

除 array 外,swap 不对任何元素进行拷贝、删除或插入操作,元素本身并未交换,swap 只是交换了两个容器的内部数据结构。

除 string 外,指向容器的迭代器、引用和指针在 swap 操作之后都不会失效,它们扔指向 swap 操作之前所指向的那些元素。但是在 swap 之后,这些元素已经属于不同的容器了。与其他容器不同,对一个 string 调用 swap 会导致迭代器、引用和指针失效。

swap 两个 array 会真正交换它们的元素,对于 swap ,在 swap 操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另一个 array 中对应元素的值进行了交换。

9.2.6 容器大小操作

成员函数 size 返回容器中元素的数目;

成员函数 empty 当 size 为 0 时返回布尔值 true,否则返回 false;

成员函数 max_size 返回一个大于或等于该类型容器所能容纳的最大元素数的值。

forward_list 支持 max_size 和 empty,但不支持 size.

9.2.7 关系运算符

每个容器类型都支持相等运算符(==!=);除了无序关联容器外的所有容器都支持关系运算符(>>=<<=)。

关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。

比较两个容器实际上时进行元素的逐队比较:

  • 如果两个容器具有相同大小且所有元素都两两对应相等,则这两个容器相等;否则两个容器不等;
  • 如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器;
  • 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。

下面是一个示例:

vector<int> v1 = {1,3,5,7,9,12};
vector<int> v2 = {1,3,9};
vector<int> v3 = {1,3,5,7};
vector<int> v4 = {1,3,5,7,9,12};
v1 < v2;	// true: v1 和 v2 在元素[2]处不同;v1[2] < v2[2]
v1 < v3;	// false: 所有元素都相等,但 v3 中元素数目更少
v1 == v4;	// true:每个元素都相等,且 v1 和 v4 大小相同
v1 == v2;	// false:元素数目不相同

容器的关系运算符使用元素的关系运算符完成比较

只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。

容器的相等运算符实际上是使用元素的==运算符实现比较的,而其他关系运算符是使用元素的<运算符。如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值