容器类型与选择
所有顺序容器都提供了快速顺序访问元素的能力。但是它们的应用有各自优缺点:
- 像容器添加或从容器中删除元素的代价
- 像顺序访问容器中元素的代价
注:array的长度是固定的
在实际应用时选择容器的基本原则:
- 通常,使用vector是最好的选择,除非有特殊需求
- 如果程序中有很多小的元素,且空间的额外开销很重要,则不要使用
list
或forward_list
- 如果程序要求随机访问元素,应使用
vector
或deque
- 如果程序要求在容器的中间插入或删除元素,应该使用
list
或forward_list
- 如果程序需要在头尾位置插入或删除元素,但是不会在中间位置进行插入或删除操作,则使用
deque
- 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则:
—— 首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector
追加数据,然后再调用标准库地sort
函数来重排容器中地元素,从而避免再中间位置添加元素。
—— 如果必须在中间位置插入元素,考虑在输入阶段使用list
,一旦输入完成,将list
中地内容拷贝到一个vector
中。
容器操作
迭代器
一个迭代器范围可以由一对迭代器表示,两个点带起分别指向同一个容器中地元素或者尾元素之后地位置。这两个迭代器通常被称为begin
和end
。
重要问题:begin
和cbegin
两个函数有什么不同?
答:begin
和end
都有多个版本,分别返回普通、反向和const迭代器。cbegin
时C++新标准引入的,用来与auto结合使用。它返回指向容器第一元素的const迭代器,可以用来只读地访问容器元素,但不能对容器元素进行修改。
begin
被重载过,由两个版本,其中一个是const成员函数,也返回const迭代器,另一个则返回普通迭代器,可以对容器元素进行修改。
list<string>::iterator it5 = a.begin(); // 返回普通迭代器
list<string>::const_iterator it6 = a.begin(); // 返回const迭代器
auto it7 = a.begin(); // 返回普通迭代器
autoit8 = a.cbegin(); // 返回const迭代器
当auto与begin或end结合使用时,获得地迭代器类型依赖于容器类型,与我们想要如何使用迭代器毫不相干(建议以auto来获取迭代器)
容器定义及初始化
基于拷贝的初始化
// 每个容器由三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milton", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};
list<string> authors list2(authors); // True,类型匹配
deque<string> authList(authors); // False,容器的类型不匹配
vector<string> words(articles); // False,容器内元素的类型不匹配
// True, 可以转换
forward_list<string> words(articles.begin(), articles.end());
列表初始化
// 每个容器由三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milton", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};
与顺序容器大小相关的构造函数
vector<int> ivec(10, -1) ; // 10个Int元素,每个都初始化为-1
vector<int> ivec(10); // 10个Int元素,每个都初始化为0
注:标准库的array具有固定大小,初始化长度后就不能变改
赋值和swap
赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝:
c1 = c2;
c1 = {a,b,c};
与内置数组不同,标准库array
类型允许赋值。但是赋值号左右两边的容器类型(元素类型、长度)必须相同
array<int, 3> a1 = {1, 2, 3};
array<int, 3> a2 = {0};
a1 = a2; // True, 替换a1中的元素
a2 = {0}; // False, 左右类型不同
使用assign(仅顺序容器)
// 每个容器由三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milton", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};
// True, 可以转换
authors.assign(articles.begin(), articles.end());
// 每个容器由三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milton", "Shakespeare", "Austen"};
authors.assign(10, "Hiya");
assign的参数决定了容器中将由多少个元素(长度)以及它们的值都是什么
使用swap
vector<int> ivec1(10);
vector<int> ivec2(24);
swap(svec1, svec2);
除array外,swap不对任何元素进行拷贝、删除或者插入操作,因此可以保证在常数时间内完成。
除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。但是,在swap之后,这些元素以及属于不同的容器了。
vector<int> ivec1(10);
vector<int> ivec2(24);
vector<int>::iterator it = ivec1.cbegin(); // it指向ivec1的首元素
swap(svec1, svec2);
//现在it指向ivec2的首元素了
注:与其他容器不同,swap两个array会真正交换它们的元素。因此,交换两个array所需的时间与array中的元素的数目成正比
除此之外,对于array,在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但是元素值已经与另一个array中对于元素的值进行了交换