vector | 可变大小数组,支持快速随机访问(在除了尾部之外部分插入删除元素很慢) |
deque | 双端队列,支持快速随机访问(在头尾插入删除元素很快) |
list | 双向链表,仅支持双向顺序访问(在任何位置插入删除元素都很快) |
forward_list | 单向链表,仅支持单向顺序访问(在任何位置插入删除元素都很快) |
array | 固定大小数组,支持快速随机访问,不能插入删除元素 |
string | 仅支持保存字符的类似vector容器 |
tips:通常使用vector是最好的选择,当然如有必要也可选择其他容器
如果不确定使用哪种容器,可以只使用vector和list公共的操作:iterator,无下标,避免随机访问
iterator:
注意:forward_list不支持递减运算符(--)
vector和string的迭代器运算同样支持deque和array
如果begin==end,范围为空(否则范围内至少有一个元素,若干次递增begin可以使得begin==end)
容器类型成员:(详情见Chap16)
需要元素类型,使用容器的value_type
需要元素类型的一个引用,使用reference或const_reference
例如: list<string>::iterator iter;
vector<int>::difference_type count;
begin和end成员:
//显式指定类型 list<string>::iterator it3=a.begin();
//c++11 auto it2=a.begin();或auto it4=a.cbegin();(it4 是const_iterator)
将一个容器初始化为另一个容器的拷贝:(要求容器类型和元素类型必须一致)
#include<list>
#include<vector>
#include<string>
#include<deque>
#include<forward_list>
#include<iostream>
using namespace std;
int main(){
list<string>authors={"Milton","Shakespeare","Austen"};//c++11,列表初始化
vector<const char*>articles={"a","an","the"};
list<string> list2(authors);
forward_list<string>words(articles.begin(),articles.end());//可以将const char*转换为string
deque<string> authList(authors.begin(),it);//假定it是authors的一个元素,拷贝范围:开始到it之前的一个元素
}
只有顺序容器的构造函数才支持大小参数(有时候得自己显式提供初始值),关联容器并不支持
例如:vector<int> ivec(10,-9);(表示10个初始值元素的值都是-9)
标准库容器array:
必须指定固定大小:array<int,80>;
允许对array进行拷贝和对象赋值操作(与内置数组类型不同):
array<int,19> digits=copy;(array<int ,19>copy=/*......*/)
array<int,19> digits=cpy;(int cpy[19]={/*............*/};)
assign允许从一个不同但相容的类型赋值,或者从容器的一个子序列赋值:
list<string> names;
vector<const char*> oldstyles;
names.assign(oldstyles.cbegin(),oldstyles.cend());
swap交换两个相同类型容器的内容(string类型使用swap会使其引用和指针失效,array如果使用swap时间与其元素规模成正比)
swap只是改变了容器的数据结构,元素的值不变(只是指向该元素的容器变了)(array则正好相反)
除了无序关联容器之外的所有容器都支持关系运算符(>,<,>=,<=),只能用于相同类型容器且储存相同类型元素(而且元素本身得支持运算符)
不仅是vector,除了array和forward_list之外,所有顺序容器都支持push_back
注意:容器的元素都是拷贝(与提供该值的对象是谁无关)
list、forward_list、deque还支持push_front,将元素插入到容器头部
vector、deque、list、string还支持insert,在容器的任意位置插入元素(不同容器间的性能差距可想而知)
c++11表示:insert的接受范围或个数的版本返回值为第一个新加入元素的迭代器
例如:list<string> lst;
auto iter=lst.begin();
while(cin>>word)
iter=lst.insert(iter , word);//此举可以一直在头部添加元素
与push_back、push_front、insert对应的,c++11引入的emplace_back、emplace_front、emplace进行的是构造元素(而非拷贝元素)
例如://c保存Sales_data元素
//使用三个参数的Sales_data构造函数,传递给emplace成员函数的参数必需与对象的构造函数参数匹配
c.emplace("23152415215",25,17.99);
//等效地,构建局部临时对象
c.push_back(Sales_data("23152415215",25,17.99));
所有顺序容器都有front成员函数,除了forward_list之外的所有顺序容器都有back成员函数(分别返回首元素和尾元素的引用)
使用这两个函数或者解引用begin和end之前必须确保容器非空
如果希望通过设置变量来改变容器中指定的元素,必须设为引用:
auto &v=c.back(); v=20;(此时通过v改变了c的尾元素的值)
string,vector,array,deque都支持下标运算符(提供快速随机访问)
如果希望确保下标合法,可以使用at成员函数:(如果越界则抛出异常)
vector<string> ivec; cout<<ivec.at(0);(等效于cout<<ivec[0];)
vector和string除了不支持push_front,也不支持pop_front(显而易见的道理)
弹出操作要求容器必须非空(类比front和back函数),返回void(如果弹出的值仍然有用,需要再弹出前对其保存)
搞特殊的forward_list:
改变容器大小:(resize不适用于array)
c.resize(n) | 调整c的大小为n个元素,若n<c.size(),多余的元素被丢弃(如果必须添加新元素,则对新元素进行值初始化) |
c.resize(n,t) | 任何新添加的元素都初始化为t |
resize缩小容器后,被删除的元素的迭代器、引用和指针都失效;对vector、string、deque进行resize操作同样可能使迭代器、引用和指针都失效
在每次添加和删除元素后重新定位迭代器是必要的(旧有的迭代器很可能失效)
不要保存end返回的迭代器,直接使用end迭代器就好(end迭代器调用的很快,也很容易失效)
如:end=v.end();这样的行为常常导致未定义的后果
管理容量的成员函数:(shrink_to_fit只适用于vector、string、deque)
c.shrink_to_fit() | 将capacity()减少为与size()相同大小(只是一个请求,不见得一定实现) |
c.capacity() | 不重新分配内存空间的话,c可以保存的元素数,只适用于vector和string |
c.reserve(n) | 分配至少能容纳n个元素的内存空间,只适用于vector和string(只有需要的空间超过当前容量时才会调用) |
capacity不同于size(好比一瓶水:capacity相当于瓶子大小,size相当于已经装下的水的量)
只有迫不得已之时,容器才能分配新的内存空间
额外的string操作:
unsigned n,len2,pos2;
string s(cp,n);//s是cp(char*类型)指向的数组中前n个字符的拷贝(如果希望完全拷贝cp,要求cp必须以空字符结束:string s(cp);)
string s(s2,pos2);//s是string s2从下标pos2开始的字符的拷贝
string s(s2,pos2,len2)//s是string s2从下标pos2开始len2个字符的拷贝
substr操作:获取原string的一部分拷贝(也可以是全部)
s.substr(pos,n);/返回一个string,从pos开始的n个字符的拷贝(pos默认为0,n默认为pos开始之后的所有字符)
除了标准的insert和erase版本,string还提供了接受下标的版本:
s.insert(s.size(),5,'!');//在s末尾加入5个!
s.erase(s.size()-5,5);//从s删除最后5个字符
string还接受c字符数组(以空字符结尾)的insert和assign版本:
char *cp="Stately,plump Buck"
s.assign(cp,7);//s替换为cp的头七个字符 s=="Stately"
s.insert(s.size(),cp+7);//在s的末尾加入cp第7个字符后面的部分 s=="Stately,plump Buck"
string还接受string的insert版本:
string s="some string", s2="some other string"
s.insert(0,s2);//在s的0位置之前插入s2
s.insert(0,s2,0,s2.size());//在s的0位置之前插入从s2[0]开始到s2.size()的部分
string的特殊函数:append和replace函数
append是在string末尾插入的简写形式:s.append(" sth ");等价于s.insert(s.size()," sth ");
replace是erase和insert的一种简写形式:s.replace(11,5,"Fifth");等价于s.erase(11,5); s.insert(11,"Fifth");(插入的字符串不见得一定与删除的一样长)
string的搜索操作:
6种搜索操作都返回一个string::size_type值,表示匹配发生位置的下标(如果没找到则返回string::npos的static值)
这些string::size_type值都是unsigned类型,所以不适合用带符号类型存储
s.find(args) | 查找s中args第一次出现的位置 |
s.rfind(args) | 查找s中args最后一次出现的位置 |
s.find_first_of(args) | 在s中查找args中任何字符第一次出现的位置 |
s.find_last_of(args) | 在s中查找args中任何字符最后一次出现的位置 |
s.find_first_not_of(args) | 在s中查找第一个不在args中的字符 |
s.find_last_not_of(args) | 在s中查找最后一不在args中的字符 |
args的形式必须是以下之一:
c,pos | 从s中位置pos开始查找字符c,pos默认为0 |
s2,pos | 从s中位置pos开始查找字符串s2,pos默认为0 |
cp,pos | 从s中位置pos开始查找c字符数组cp(以空字符结尾),pos默认为0 |
cp,pos,n | 从s中位置pos开始查找c字符数组cp(一空字符结尾)的前n个字符,pos和n无默认值 |
#include<string>
#include<iostream>
using namespace std;
int main(){
//一种常见的查找模式
string numbers(""),name("r2d2");
string::size_type pos=;
while((pos=name.find_first_of(numbers,pos))!=string::npos){
cout<<"found number at index: "<<pos
<<" element is "<<name[pos]<<endl;
++pos;
}
}
string的compare函数:
s.compare(args);根据s比另一个string等于、大于、小于,返回0,正数,负数。
args的几种形式;
s2 | 比较s和s2 |
pos1,n1,s2 | 将s中从pos1开始的n1个字符与s2比较 |
pos1,n1,s2,pos2,n2 | 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符比较 |
cp | 比较s和cp(cp是c字符数组,以空字符结尾) |
pos1,n1,cp | 将s中从pos1开始的n1个字符与cp比较 |
pos1,n1,cp,n2 | 将s中从pos1开始的n1个字符与cp中的前n2个字符比较 |
string的数值转换:
容器适配器:
标准库定义了3个顺序容器适配器:stack、queue(此二者基于deque实现)、priority_queue(基于vector实现)
适配器的拷贝操作:
deque<int>deq={/*..................*/};
stack<int> stk(deq);//从deq拷贝元素到stk
stack适配器可由除了array和forward_list之外的其他容器进行构造,queue可由list和deque构造,priority_queue可由vector和deque构造
这些适配器的使用与数据结构课上自己写的结构的使用方法一样,而且比自己写的性能更好