笔记:
9.1 顺序容器概述
如果不确定使用哪种类型的容器,那么可以使用vector和list的公共操作,使用迭代器,不适用下标操作。
list 的迭代器不支持比较运算。list不支持<运算,只支持递增递减、==以及!=操作。
9.2 容器库概述
每个容器都定义了一个默认构造函数。
只有顺序容器(不包括array)的构造函数才能接受参数大小。
当将一个容器初始化另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。
只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
虽然我们不能对内置数组类型进行拷贝或对象赋值操作,但是array并没有这个限制。
赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效,而swap不会。(array和string情况除外。)
swap:
swap只是交换了两个容器的内部数据结构,除了array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。
与其他容器不同,swap两个array会真正交换他们的元素,因此交换两个array所需的时间与array中元素的数目成正比。
9.3顺序容器操作
emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配。
提供快速随机访问的容器(vector,string,deque,array),也提供下标运算符。
forward_list有一个首前迭代器,即指向首元素之前的那个不存在的元素,和尾后迭代器一样。当在尾后插入元素,以及使用erase_after的时候,要使用首前迭代器。
如果在一个循环中插入/删除deque、string、或vector中的元素,不要缓存end返回的迭代器。
list和forward_list与其他容器的一个不同是,迭代器不支持加减运算,即迭代器不支持加减一个数,究其原因,链表中元素并非在内存中连续存储,因此无法通过地址的加减在元素间远距离移动。
9.4 vector对象是如何增长的
在virtual studio2013上,vector对象的增长好像不是以翻倍的速度进行的。
而在windows上用g++进行编译,vector对象就是翻倍的增加容量的。
9.5 额外的string操作
很多string的操作函数,等以后用的时候可以再仔细回来研究。
9.6 容器适配器
栈默认基于deque实现,也可以在list或vector之上实现。
课后习题:
练习 9.1:对于下面的程序任务,vector、deque和 list 哪种容器最为适合?解释你的选择的理由。如果没有哪一种容器优于其他容器,也请解释理由。
(a) 读取固定数量的单词,将它们按字典序插入到容器中。我们将在下一章中看到,关联容器更适合这个问题。
(b) 读取未知数量的单词,总是将单词插入到末尾。删除操作在头部进行。
(c) 从一个文件读取未知数量的整数。将这些数排序,然后将它们打印到标准输出。
答:(a)“按字典顺序插入到容器中”意味着进行插入排序操作,从而需要在容器内部频繁进行插入操作,vector在尾部之外的位置插入和删除元素都很慢,deque在头尾之外的位置插入和删除元素很慢,而list在任何位置插入、删除速度都很快。因此这个任务选择list更为适合。
(b)由于需要在头、尾分别进行插入、删除等操作,因此将vector排除在外,deque和list都可以达到很好的性能。如果还需要频繁进行随机访问,则deque更好。
(c)由于整数占用空间很小,且快速的排序算法需频繁随机访问元素,将list排除在外。由于无需在头部进行插入、删除操作,因此使用vector即可,无需使用deque。
练习 9.2:定义一个list 对象,其元素类型是int 的deque。
list<deque<int>> a;
练习 9.3:构成迭代器范围的迭代器有何限制?
答:两个迭代器begin和end必须指向同一个容器中的元素,或者是容器最后一个元素之后的位置;而且,对begin反复进行递增操作,可以保证达到end,即end不在begin之前。
练习 9.4:编写函数, 接受一对指向vector<int> 的迭代器和一个int 值。在两个迭代器指定的范围中查找给定的值,返回一个布尔值来指出是否找到。
// 练习 9.4
#include <iostream>
#include <vector>
using namespace std;
typedef vector<int>::iterator asv; //类型别名定义
bool myFind(asv it1, asv it2, int num)
{
for (; it1 != it2; ++it1)
{
if (*it1 == num)
{
return true;
}
}
return false;
}
int main()
{
vector<int> vi;
//给vector对象赋值
for (int i = 0; i < 10; ++i)
{
vi.push_back(i);
}
vector<int>::iterator it1 = vi.begin();
vector<int>::iterator it2 = vi.end();
int val = 41;
cout << myFind(it1, it2, val) << endl;
system("pause");
return 0;
}
练习 9.5:重写上一题的函数,返回一个迭代器指向找到的元素。注意,程序必须处理未找到给定值的情况。
// 练习 9.5
#include <iostream>
#include <vector>
using namespace std;
typedef vector<int>::iterator asv; //类型别名定义
asv myFind(asv it1, asv it2, int num)
{
for (; it1 != it2; ++it1)
{
if (*it1 == num)
{
return it1;
}
}
return it2; //搜索失败返回尾迭代器
}
int main()
{
vector<int> vi;
//给vector对象赋值
for (int i = 0; i < 10; ++i)
{
vi.push_back(i);
}
vector<int>::iterator it1 = vi.begin();
vector<int>::iterator it2 = vi.end();
int val = 6;
//输出目标值迭代器与首元素迭代器之间的差值
cout << myFind(it1, it2, val) - it1 << endl;
system("pause");
return 0;
}
练习 9.6:下面程序有何错误?你应该如何修改它。
list<int> lst1;
list<int>::iterator iter1 = lst1.begin(),
iter2 = lst1.end();
while(iter1 < iter2) /* ... */
list不支持<运算,只支持递增递减、==以及!=操作。可改成iter1 != iter2。list中元素是以链表的方式进行存储的。
练习 9.7:为了索引int 的vector 中的元素,应该使用什么类型?
答:使用迭代器类型vector<int>::iterator来索引int的vector中的元素。索引使用迭代器。
练习 9.8:为了读取string 的list 中的元素,应该使用什么类型?如果写入list,又该使用什么类型?
答:为了读取string的list中的元素,应使用list<string>::valus_type,写入数据需要引用类型,使用list<string>::reference。读取使用元素类型。
练习 9.9:begin 和cbegin 两个函数有什么不同?
答:cbegin是C++新标准引入来的,用来与auto结合使用。它返回指向容器第一个元素的const迭代器,可以用来只读地访问容器,但不能对容器元素进行修改。因此,当不需要写访问时,应该使用cbegin。
begin则是被重载过的,有两个版本:其中一个是const成员函数,也返回const迭代器;另一个则返回普通迭代器,可以对容器元素进行修改。
练习 9.10:下面4 个对象分别是什么类型?
vector<int> v1;
const vector<int> v2;
auto it1 = v1.begin(), it2 = v2.begin();
auto it3 = v1.cbegin(), it4 = v2.cbegin();
//it1是iterator,it2是const_iterator
//it3是const_iterator,it4是const_iterator
练习 9.11:对6 种创建和初始化vector 对象的方法,每一种都给出一个实例。解释每个vector 包含什么值。
(1)vector<int> ilist;
//默认初始化,vector为空,容器中尚未有元素。
(2)vector<int> ilist2(ilist);
//ilist2初始化为ilist的拷贝,ilist必须与ilist2类型相同。
(3)vector<in> ilist = {1,2,3.0,4,5,6,7};
//初始化为列表中元素的拷贝,列表中的元素类型必须与ilist的元素类型相容,这里必须是与整型相容的数值类型。
(4)vector<int> ilist3(ilist.begin()+2, ilist.end()-1);
//ilist3初始化为两个迭代器指定范围中的元素的拷贝,范围中的元素类型必须与ilist的元素类型相容,
//这里ilist3被初始化为{3, 4, 5, 6}.
(5)vector<int> ilist4(7);
//默认值初始化,ilist4中将包含7个元素,每个元素进行缺省的值初始化,对于int,也就是被赋值为0.
(6)vector<int> ilist5(7, 3);
//指定值初始化,ilist5被初始化为包含7个值为3的int。
练习 9.12:对于接受一个容器创建其拷贝的构造函数,和接受两个迭代器创建拷贝的构造函数, 解释它们的不同。
答:接受一个已有容器的构造函数会拷贝此容器中的所有元素,这样,初始化完成后,我们得到此容器的一个一模一样的拷贝、当我们确实需要一个容器的完整拷贝时,这种初始化方式非常方便。这要求两个容器的类型以及其元素类型必须匹配。
当我们不需要已有容器中的全部元素,而只是想拷贝其中一部分元素时,可使用接受两个迭代器的构造函数。这不要求容器类型相同,新容器和原容器中的元素类型也可以不同,只要能将要拷贝的元素转换成要初始化的容器的元素类型即可。
练习 9.13:如何从一个list<int> 初始化一个vector<double> ?从一个vector<int>又该如何创建?编写代码验证你的答案。
// 练习 9.13
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main()
{
list<int> ilist = { 1, 2, 3, 4, 5, 6, 7 };
vector<int> ivec =