顺序容器
sequential container
- 向容器中添加或从容器中删除元素的代价
- 非顺序访问容器中元素的代价
string和vector中元素保存在连续的内存空间,由于元素时连续存储的,所有下表计算地址非常快,当从容器中间位置添加或删除元素非常耗时。
list和forward_list连个容器在任意位置添加和删除操作很快,但不支持随机访问。
deque与string和vector类似,支持快速随机访问。与string和vector一样,在deque的中间位置添加或删除元素代价很高。在deque的两端添加或删除元素很快。
array与内置数组相比更安全,更容易使用。它与内置数组类似,array对象大小固定,不支持添加和删除元素以及改变容器大小的操作。
forward_list没有size操作。
选择容器的原则:
-
优先选vector。
-
如果程序要求随机访问元素,使用vector后deque。
-
如果程序要求在容器中间插入或删除元素,使用list或forward_list。
-
如果程序在头尾插入或删除元素,但不在中间位置进行插入或删除,使用deque。
-
如果程序只有读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素。
使用vector,然后重排容器元素,避免在中间位置添加元素。
如果必须在中间位置插入元素,先用list,然后将list拷贝到vector。
顺序容器概览
迭代器
- 如果begin与end相等,则范围为空
- 如果begin与end不等,则范围至少包含一个元素,且biegin指向范围中的第一个元素
- 我们可以对begin递增若干次,使得begin==end
容器类型成员
元素类型 value_type
vector<int> c{1,2,3,3,4,5,6,8};
vector<int>::value_type tt;
tt = c[0]
元素的左值类型reference
begin和end成员
当不需要写访问时,应使用cbegin和cend。
list<string> a = {"mm","ll","kk"};
list<string>::iterator it1 = a.begin(); //显式指定类型
list<string>::const_iterator it2 = a.begin();
auto it3 = a.begin();//仅当a是const时,it3是const_iterator
auto it4 = a.cbegin();//it8是const_iterator
容器定义和初始化
将一个容器初始化为另一个容器的拷贝
方法1:直接拷贝整个容器,元素类型和容器类型必须相同
list<string> a = {"mm","ll","kk"};
vector<const char*> ar = {"a","b","c"};
list<string> b(a); //正确
方法2:(array除外)拷贝由一个迭代器对指定的元素范围。不要求容器类型相同。
forward_list<string> words(ar.begin(),ar.end());
迭代器表示拷贝的第一个元素和尾元素之后的位置。
//拷贝元素,直到(但不包括)it指向的元素
deque<string> aut(aut.begin(),it);
列表初始化
显式的指定了容器中每个元素的值,隐式的指定了容器大小。
list<string> a = {"mm","ll","kk"};
与顺序容器大小相关的构造函数
另一个构造函数,接受一个容器大小和一个(可选)元素初始值。
vector<int> ivec(10,-1); //10个int元素,每个初始化为-1
deque<string> svec(10); //10个元素,每个都是空string
标准库array具有固定大小
定义array时,除了指定元素类型,还要指定容器大小:
array<int,42> //类型为:保存42个int的数组
为了使用array类型,必须同时指定元素类型和大小:
array<int,10>::size_type i ; //数组类型包括元素类型和大小
对array初始化时,初始化的数目必须等于或小于array的大小。
array<int,10> ia1;
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[3] = {1,2,3};
int copy[3] = digs; //错误:内置数组不支持拷贝或赋值
array<int,3> digs = {1,2,3};
array<int,3> copy = digs; //正确
赋值和swap
赋值运算符适用于所有容器。
c1 = c2; //将c1的内容替换为c2中元素的拷贝
c1 = {a,b,c} //赋值后,c1大小为3
array<int,10> a1 = {1,2,3,4,5,6,7,8};
array<int,10> a2 = {0};
a1 = a2;
a2 = {0}; //错误,不能将一个花括号列表赋予数组
使用assign(仅顺序容器)
赋值运算符要求左边和右边的运算对象具有相同的类型。它将右边运算对象中所有元素拷贝到左边运算对象中。
list<string> names;
vector<const char*> oldstyle;
names = oldstyle; //错误:容器类型不匹配
names.assign(oldstyle.begin(),oldstyle.end());
用法2:
list<string> slist1(1);
slist1.assign(10,"hiya");
使用swap
swap操作交换两个相同类型容器的内容。元素本身并未交换,swap只是交换了两个容器的内部数据结构。
vector<string> svec1(10);
vector<string> svec2(10);
swap(svec1,svec2);
除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。
与其他容器不同,swap两个array会真正交换他们的元素。
容器大小
每个容器有3个大小相关的操作,size、empty和max_size。forward_list不支持size。
关系运算符
关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。
vector<int> v1 = {1,3,5,7,9,12}
vector<int> v2 = {1,3,9}
vector<int> v3 = {1,3,5,7}
vector<int> v1 = {1,3,5,7,9,12}
v1 < v2 //true v1和v2在元素[2]处不同,v1[2]小于等于v2[2]
v1 < v3 //false 所有元素相等,v1元素多于v3
v1 == v4 //true
v1 == v3
只有当元素类型也定义比较运算符时,才能使用关系运算符。
顺序容器操作
向顺序容器添加元素
使用push_back
除array和forward_list之外,每个顺序容器(包括string类型)都支持push_back。
由于string是一个字符容器,也可以用push_back在string末尾添加字符:
void pluralize(size_t cnt,string &word)
{
if(cnt > 1)
word.push_back('s'); //等价于 word += 's'
}
使用push_front
list、forwoard_list和deque容器支持push_front。
list<int> ilist;
for(size_t ix = 0; ix != 4; ++ix)
ilist.push_front(ix);
在容器中的特定位置添加元素
insert允许我们在容器中任意位置插入0个或多个元素。vector、deque、list和string都支持insert成员。forward_list提供特殊版本的insert成员。
slist.insert(iter,"Hello!")//将hello添加到iter之前的位置。
vector不支持push_front,但可以插入到begin之前,速度很慢
vector<string> svec;
svec.insert(svec.begin(),"Hello!");
list<string> slist;
slist.insert(slist.begin(),"Hello!"); //等价于slist.push_front("Hello!")
插入范围内元素
insert除第一个参数外,接受一个元素数目和一个值。
svec.insert(svec.end(),10,"Anna"); //将10个元素插入到svec的末尾。
接受的一对迭代器或一个初始化列表。
vector<string> v = {"a","b","c","d"};
slist.insert(slist.begin(),v.end()-2,v.end());
slist.insert(slist.end(),{"e","f","g"});
slist.insert(slist.begin(),slist.begin(),slist.end());//错误,不能指向slist
使用insert的返回值
通过使用insert的返回值,可以在容器中一个特定位置反复插入元素。
list<string> lst;
auto iter = lst.begin();
while(cin >> word)
iter = lst.insert(iter,word); //等价于push_front
使用emplace
emplace_front、emplace和emplace_back构造而不是拷贝元素。分别对应push_front、insert和push_back。
允许将元素放在容器头部、一个给定位置之前或容器尾部。
c.emplace_back("888",25,15.99);
c.push_back("999",35,25.99);//错误
c.push_back(Sales_data("000",35,43.44));
emplace_back会在容器管理的内存空间中直接创建对象,而调用push_back会创建一个局部临时对象,并将其压入容器中。
c.emplace_back(); //使用Sales_data的默认构造函数
c.empalce(iter,"99999"); //使用Sales_data(string)
传递给emplace函数的参数必须与元素类型的构造函数相匹配。