一个容器就是一些特定类型对象的集合。**顺序容器(sequential container)**为程序员提供了控制元素存储和访问顺序的能力。
标准库中的容器都提供了快速访问元素的能力,不同的是这些容器在
- 向容器添加或从容器删除元素的代价
- 非顺序访问容器元素的代价
两方面进行了不同的性能折衷
顺序容器类型
顺序容器类型 | |
---|---|
vector | 可变数组大小 支持快速随机访问,尾部之外插入和删除数据可能很慢 |
deque | 双端队列,支持快速随机访问,头尾插入删除速度快 |
list | 双向链表 只支持双向顺序访问 在任何位置插入删除都很快 |
forward_list | 单向链表 只支持单向顺序访问 在任何位置插入删除都很快 |
array | 固定大小数组,支持快速随机访问,不能添加或删除 |
string | 与vector类似的容器,但专门保存字符,随机访问快,尾部删除插入快 |
容器保存元素的策略有着固有的,有时是重大的影响。在某些情况下,存储策略决定特定策略是否支持特定操作
例如,string和vector将元素顺序存储在连续的内存空间中。连续存储使得利用下标计算其位置是很快的。但是在中间进行元素添加和删除会非常耗时:每次操作后需移动其后的所有元素。
list和forward_list为了支持快速增删,取消了随机访问。访问必须从头遍历。而且与其他容器相比,他们的内存消耗很大。
与内置数组相比,array更加安全,也更容易使用
forward_list没有size操作,而其他容易的size操作是一个快速的常量操作
通常使用vector是最好的选择,除非你有很好的理由选择其他容器
- 首先,确定是否真的要在容器中间添加元素。当处理输入数据时,可以很容易在vector后添加数据,然后使用sort将元素重排,从而避免在中间添加元素
- 如果必须在中间插入元素,考虑在输入阶段用list。输入完成后将list的内容拷贝到vector中
如果程序既需要随机访问,又要在中间增删元素,那么就取决于list或forward_list中访问元素和vector或deque中增删元素的相对性能。
c++Primer 第五版 第295页 容器操作
迭代器范围的概念是标准库的基础
一个迭代器范围指的是一对迭代器表示,两个迭代器通常称为begin和end
元素范围为左闭合区间 即[begin,end),这使得有三个好处
- 如果begin和end相等,则范围为空
- 如果begin与end不等,则至少有一个元素,且begin指向第一个元素
- 我们可以对begin++直至其等于end
c++Primer第五版第297页 容器类型成员
每个容器都定义了多个类型,如size_type,iterator,const_iterator,反向迭代器,
通过类型别名,我们可以在不了解元素类型的情况下使用它。如果需要使用元素类型,可以用value_type。如果需要元素类型的引用,可以使用reference或const_reference。这些在泛型编程中十分有用
list<string> a = {"xys","sod"};
auto it1 = a.begin();// iterator
auto it2 = a.rbegin();// reverse_iterator
auto it3 = a.cbegin();// const_iterator
auto it4 = a.crbegin();//const_reverse_iterator
不需要写访问时,使用cbegin和cend
C++Primer 第五版 第299页 容器定义和初始化
C c; | 默认构造函数。array类型则执行默认初始化,其他则为空 |
C c1(c2); C c1=c2 | c1初始化为c2的拷贝,二者必须是相同类型,对于array还必须是相同大小 |
C c{a,b,c…} C c={a,b,c…} | c初始化为列表元素的拷贝 |
C c(b,e) | c初始化为迭代器b和e之间元素的拷贝 |
只有顺序容器(不包括array)的构造函数才接受大小参数 | |
C seq(n) | seq中包含n个元素 |
C seq(n,t) | seq中包含n个初始化为t的元素 |
为了创建容器为另外一个容器的拷贝,两个容器的类型及其元素类型必须匹配。不过,通过传递迭代器来拷贝一个范围时,容器类型就不要求相同了。而且,新容器和元容器的元素类型也可以不同,只要可以相互转换即可。
C++Primer 第五版 第301页 标准库array
标准库array和内置数组一样,除了指定元素类型,还要指定容器大小
array<int,42>
array<string,10>
//为使用array类型,我们必须同时指定类型和大小
array<int,10>::size_type i;//正确 必须指定array类型和大小
array<int>::size_type i;//错误,未指定大小
值得注意的是,虽然我们对内置数组类型无法执行拷贝或者对象赋值操作,但array并无此限制
C++Primer 第五版 第302页 容器赋值运算
容器赋值运算 | (未指定必须是顺序容器) |
---|---|
c1=c2 | 将c1元素替换为c2元素的拷贝,二者必须具有相同的类型 |
c={a,b,…} | |
swap(c1,c2) c1.swap(c2) | 交换C1,c2,swap通常比从c2拷贝要快 |
assign不适用于关联容器和array | assign替换左边容器中所有元素 |
seq.assign(b,e) | 将seq中的元素替换为b和e范围内的元素,但b和e不能指向seq中的元素 |
seq.assign(il) | 将seq中的元素替换为初始化列表il中的元素 |
seq.assign(n,t) | 将seq中的元素替换为n个值为t的元素 |
注意,赋值相关运算会导致左边容器内部的迭代器、指针和引用失效。而swap操作不会导致指向指针的迭代器、指针和引用失效(容器类型为array和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个元素 为空
slist1.assign(10,"Hiya");//10个元素
与其他容器不同,swap会真正交换两个array中的元素;与其他容器不同,swap会导致两个string的迭代器、引用、指针失效
在新标准库中,容器既能提供成员函数版本的swap,也能提供非成员函数版本的swap。在泛型编程中,非成员函数版本非常重要。统一使用非成员版本的swap是一个好习惯
forward_list支持的大小操作有max_size和empty,不支持size
每个容器都支持==和!=,除了无序关联容器,都支持关系运算符(>,>=,<,<=),容器的比较实际上是进行元素的逐个比较;注意只有当元素类型也定义了比较运算符时才能够比较两个容器
c++Primer 第五版 第305页 顺序容器操作
容器添加元素
c.push_back(t) c.emplace_back(args) | 在c的尾部创建一个值为t或由args创建的元素,返回void;forward_list不支持 |
c.push_front(t) c.emplace_front(args) | 在c的头部…,vector和string不支持 |
c.insert(p,t) c.emplace(p,args) | 在迭代器p指向的元素前创建,返回指向新添加元素的迭代器,forward_list有自己专有版本 |
c.insert(p,n,t) | 在p前创建n个t,若n为0,返回p |
c.insert(p,b,e) | |
c.insert(p,il) |
除array和forward_list之外,每个顺序容器(包括string和list)都支持push_back
新标准引入的三个新成员emplace,emplace_front,emplace_back允许构造而不是拷贝元素
//在c的末尾构造一个Sale_data
//使用三个参数的Sales_data构造函数
c.emplace_back("0000",25,15.99);
//错误:没有接受三个参数的push_back
c.push_back("0000",25,15.99);
//正确,创建临时Sales_data
c.push_back(Sales_data("0000",25,15.99));
c++Primer 第五版第309页 容器访问
包括array在内的每个顺序容器都有一个front,除了forward_list之外都有一个back
//解引用迭代器或调用front back前要先检查是否有元素
if(!c.empty()){
auto val = *c.begin();val2 = c.front();
auto last = c.end();
auto val3 = *(--last);//forward_list不能递减
auto val4 = c.back();
}
此程序用两种不同方式获得c的首尾元素
值得注意的有两点,一是end指向尾元素之后的元素,因此需要–才能获得尾元素;二是调用front ,back前必须检查容器非空,否则操作未定义
访问成员函数返回的是引用
if(!c.empty()){
c.front() = 42;//将42赋给第一个元素
auto &v = c.back();//获得引用
v = 1024;
auto v2 = c.back();//v2不是引用 而是一个拷贝
v2 = 0;
}
//和往常一样 如果使用auto 记得将变量定义为引用类型
提供快速随机访问的容器支持下标,如果希望确保下标是合法的,可以使用at成员函数
vector<string> svec;//空vector
cout<<svec[0];//运行时错误
cout << svec.at(0);//错误会抛出out_of_range
顺序容器删除操作 | |
---|---|
c.pop_back() | forward_list不支持 |
c.pop_front() | vector和string不支持 |
c.erase§ | 删除后返回p后的迭代器,如果p指向尾元素,则返回尾后迭代器 |
c.erase(b,e) | |
c.clear() |
c++ Primer 第五版 第313页 特殊的forward_list
由于链表删除或者添加元素时,删除或者添加元素之前的元素的后继会发生改变,因此我们需要访问其前驱。但是forward_list作为单向链表,无法简单的访问前驱。因此在forward_list中添加或者删除元素,总是通过改变指定元素之后的元素来完成
forward_list中插入或者删除元素 | |
---|---|
lst.before_begin() lst.cbefore_begin() | 返回首元素之前的不存在的元素的迭代器,不能解引用,cbefore返回const |
lst.insert_after(p,t) | |
lst.insert_after(p,n,t) | |
lst.insert_after(p,b,e) | |
lst.insert_after(p,il) | |
emplace_after(p,args) | |
lst.erase_after(p ) | |
lst.erase_after(b,e) |
改变容器大小,不适用array
c.resize(n) | 调整c的大小为n,若n<c.size(),则抛弃多出的 |
c…resize(n,t) | 调整c的带下为n,且全部为t |
c++Primer 第五版 第315页 迭代器失效问题
添加、删除vector、string、deque元素的循环程序必须考虑迭代器、引用、指针可能失效的问题
//傻瓜循环,删除偶数,复制奇数
vector<int> vi = {0,1,2,3,4,5,6,7,8,9}
auto iter = vi.begin();//调用begin而不是cbegin,因为要改变vi
while(iter != vi.end()){
if (*iter %2) {
iter = vi.insert(iter,*iter);
iter += 2;//向前移动迭代器,跳过元素以及插入元素
}else
iter = vi.erase(iter);
//不应向前移动,iter指向删除后的元素
}
c++Primer 第五版 第318页 vector对象大小
管理容量
c.shrink_to_fit() | 将capacity()减小到size相同大小,只适用vector,string和deque |
c.capacity() | 不重新分配内存的话,c可以保存多少元素 |
c.reserve(n) | 分配至少能容纳n个元素的空间 |
C++Primer 第五版 第321页 string的其他构造方法
string s(cp,n) | s是cp指向的数组前n个字符的拷贝,此数组应至少包含n个字符 |
stirng s(s2,pos2) | s是s2从pos2开始的字符的拷贝,若pos2>s2.size() 行为未定义 |
string s(s2,pos2,len2) |
这些构造函数接受一个string 或const char*参数
s.substr(pos,n)//返回s中从pos开始的n个字符的拷贝
//string类定义了两个额外的成员函数 append和replace
s.insert(s.size(),"AAA")
s.append("AAA")
//replace则可以看做是调用erase和insert
s.replace(11,3,"fifth");//删除3个,插入5个
c++Primer第五版 第325页
string搜索 | |
---|---|
s.find(args) | 查找s中args第一次出现的位置 |
s.rfind(args) | 查找s中args最后一次出现的位置 |
s.find_first_of(args) | 在s中查找args中任意一个字符 第一次出现的位置 |
s.find_last_of(args) | 在… 最后一次出现位置 |
s.find_first_not_of(args) | 在s中查找第一个不在args中的字符 |
s.find_last_not_of(args) | 在s中查找最后一个… |
args必须是以下形式之一 | |
c,pos | 从s中位置pos查找c,pos默认0 |
s2,pos | |
cp,pos | |
cp,pos,n | 从s中位置pos开始查找指针cp指向的数组前n个字符 |
c++Primer第五版 第328页
string和数值之间的转换 | |
---|---|
to_string(val) | |
stoi(s,p,b) stol(s,p,b) stoul(s,p,b) stoll(s,p,b) stoull(s,p,b) | 返回s的起始子串(表示整数内容)的数值,返回类型由函数名决定,b为基数默认为10,p默认为0 |
stof(s,p) stod(s,p) stold(s,p) | 返回转为浮点数 |
c++Primer 第五版 第329页 容器适配器
除顺序容器外,标准库还定义了三个顺序容器适配器
stack、queue、priority_queue
适配器(adapter)是标准库的一个通用概念。容器、迭代器、函数都有适配器。本质上,适配器是一种机制,使得某种事物的行为看起来像另一种一样。