1. 顺序容器概述
#include <vector> //可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
#include <deque> //双端队列。支持快速随机访问。在头尾位置插入、删除速度都很快
#include <list> //双向链表。只支持双向顺序访问。在list中任何位置进行插入、删除操作速度都很快
#include <array> //固定大小数组。 支持快速随机访问。不能添加或删除元素
#include <string> //与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入、删除速度快
/*通常,使用vector是最好的选择,除非有很好的理由选择其他容器
可以定义一个容器,其元素的类型是另一个容器:vector<vector<string>> lines;
此处lines是一个vector,其元素类型是string的vector
2. 左闭合范围
一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或尾元素之后的位置。
左闭合区间:[begin,end)表示范围自begin开始,于end之前结束(end不能指向begin之前的位置)
使用左闭合范围有三种方便的性质:
a) 如果begin==end,范围为空
b) 如果begin!=end,则范围至少包含一个元素
c) 可以对begin递增若干次,使begin==end
因此,我们可以用循环处理一个元素范围,给定合法范围的begin和end,若范围为空,则退出循环
3. vector和string迭代器支持>、>=、<、<=关系运算符,但list不支持
list<int> lst1;
list<int>::iterator iter1=lst1.begin(),iter2=lst1.end();
/*while(iter1<iter2) 这是不可以的诶*/
while(iter1!=iter2) /*这才是对的*/
4. begin(返回指向第一个元素或第一个字符的迭代器)和end(返回指向容器或string对象尾元素的下一位置的迭代器)有多个坂(版)本:
a) rbegin和rend返回反向迭代器
b) 以c开头的坂本则返回const迭代器
list<string> a={"Milton","Shakespear","Austen"};
auto it1=a.begin(); //list<string>::iterator
auto it2=a.rbegin(); //list<string>::reverse_iterator
auto it3=a.cbegin(); //list<string>::const_iterator
auto it4=a.crbegin(); //list<string>::const_reverse_iterator
5. 迭代器类型
a) 当不需要写访问时,应使用cbegin和cend
b) 如果对象是个常量,只能使用const_iterator
vector<int> v1;
const vector<int> v2;
auto it1=v1.begin(),it2=v2.begin(); //error,it1和it2类型不一致
auto it3=v1.cbegin(),it4=v2.cbegin();
6. 容器定义和初始化
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v1; //默认初始化。如果不是array, 容器为空
vector<int> v2(v1); //v2初始化为v1的拷贝。这两个容器必须是相同的容器类型,
vector<int> v3=v1; //且保存的是相同的元素类型
vector<int> v4{1,2,3}; //列表初始化,初始化为初始化列表中元素的拷贝,
vector<int> v5={1,2,3}; //列表中元素的类型必须与容器的元素类型相同
vector<int> v6(v1.begin(),v1.end()); //初始化为两个迭代器指定范围中的元素的拷贝
vector<int> v7(10,-1); //10个int元素,每个都初始化为-1
vector<int> v8(10); //10个int元素,每个都初始化为0
return 0;
}
注意:当传递迭代器参数来拷贝一个范围时,不要求容器类型是相同的,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可:
vector<float> ivec1={0.1,0.5,5.6};
vector<int> ivec2(ivec1.begin(),ivec1.end());
7. swap:交换两个相同类型容器的内容。
元素本身并未交换,swap只是交换了两个容器的内部数据结构,which means除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效,它们仍指向
swap操作之前所指向的那些元素。
8. 容器大小操作
每个容器都有三个与大小相关的操作:
a) size返回容器中元素的数目
b) empty当size为0时返回布尔值true
c) max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值
d) 每个容器类型都支持相等运算符(==和!=);除了无序关联容器外的所有容器都支持关系运算符(>、>=、<、<=)
e) 关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素
9. 向顺序容器添加元素(vector和string不支持push_front和emplace_front)
c.push_back(t) //在c的尾部创建一个值为t或由args创建的元素
c.emplace_back(args) //返回void
c.insert(p,t) //在迭代器p指向的元素之前创建一个值为r或由args创建的元素
c.emplace(p,args) //返回void
c.insert(p,n,t) //在迭代器p指向的元素之前插入n个值为t的元素。
//返回指向新添加的第一个元素的迭代器;若n为0,则返回p
c.insert(p,b,e) //将迭代器p和e指定的范围内的元素插入到迭代器p指向的元素之前。b和e不能指向c中的元素。
//返回指向新添加的第一个元素的迭代器;若范围为空,则返回p
c.insert(p,il) //il是一个花括号包围的元素值列表。将这些给定值插入到迭代器p指向的元素之前。
//返回指向新添加的第一个元素的迭代器;若列表为空,则返回p
a) 向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效
b) 当用一个对象初始化容器时(或将一个对象插入到容器中时),实际上放到容器中的是对象值的一个拷贝而不是其本身;随后对容器中元素的任何改变都不会影响到原始对象
c) 每个insert函数都接受一个迭代器作为其第一个参数,将元素插入到迭代器所指定的位置之前
vector<int> vec{1,10,24,3};
vec.insert(vec.begin(),4);
for(auto c:vec)
cout<<c<<" ";
输出结果为4 1 10 24 3
d) insert还有一个版本接受的参数为一个元素数目和一个值,它将指定数量的元素添加到指定位置之前,这些值都按给定值初始化
vector<string> svec{"hhh"};
svec.insert(svec.end(),10,"A233");
输出结果为hhh A233 A233 A233
e) 接受一对迭代器或一个初始化列表的insert坂本将给定范围中的元素插入到指定位置之前
vector<string> svec1={"i","love","study","and"};
vector<string> svec2={"study","makes","me","happy"};
svec2.insert(svec2.begin(),svec1.begin(),svec1.end()-2); //将svec1最后的两个元素添加到svec2的开始位置
for(auto c:svec2)
cout<<c<<" ";
svec1.insert(svec1.end(),{"i","want","to","be","a dalao"});
for(auto n:svec1)
cout<<n<<" ";
注意:拷贝的范围是左闭合区间,即第三行的svec1.end()-2,the string "study",没有被拷贝过去
f) 使用insert的返回值可以在容器中一个特定位置反复插入元素,因为insert函数返回的是新添加的(第一个元素的)迭代器
vector<string> svec={"anita","Anita","ANITA","gcn","GCN"};
auto iter=svec.begin();
string word;
while(cin>>word)
iter=svec.insert(iter,word); //等价于调用push_front
for(auto c:svec)
cout<<c<<" ";
10. 访问元素
a) 每个顺序容器都有一个front成员函数,除forward_list之外的所有顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用
c.back() //返回c中尾元素的引用,若c为空,函数行为未定义
c.front() //返回c中首元素的引用,若c为空,函数行为未定义
c[n] //返回c中下标为n的元素的引用,n是一个无符号整数,若n>=c.size(),函数行为未定义
c.at[n] //返回下标为n的元素的引用。如果下标越界,则抛出一out_of_range异常
b) 不可以对空容器调用front和back
c) 在容器中访问元素的成员函数(front、back、下标和at)返回的是引用。如果容器是一个const对象,返回值是const引用。如果容器不是const,可以用返回的普通引用改变元素的值
vector<int> ivec={7,2,4,6,2,0,9};
if(!ivec.empty()){
ivec.front()=42; //将42赋予c中的第一个元素
auto &v1=ivec.back(); //获得指向最后一个元素的引用c
v1=1024;
auto v2=ivec.back(); //v2不是一个引用,只是ivec.back()的拷贝
v2=0; //v2不是引用,未改变c中的元素
}
for(auto a:ivec)
cout<<a<<" "; //输出结果是42 2 4 6 2 0 1024
11. 删除元素(与vector和string不支持push_front一样,vector和string也不支持pop_front)
c.pop_back() //删除c中尾元素,返回void
c.pop_front() //删除c中首元素,返回void
c.erase(p) //删除迭代器p所指定的元素,返回一个指向被删元素之后元素的迭代器。若p指向尾元素,返回尾后迭代器。
c.erase(b,e) //删除迭代器b和e所指定范围内的元素。返回一个指向最后一个被删的元素之后元素的迭代器
c.clear() //删除c中所有元素,返回void
a) pop_front和pop_back都返回void。如果需要弹出的元素的值,应在执行弹出操作之前保存它
b) 成员函数erase从容器中指定位置删除元素,两种形式的erase都返回指向删除的(最后一个)元素之后位置的迭代器。下面的栗子删除ivec中的所有奇数,调用了接受一个迭代器的erase坂本的函数
vector<int> ivec;
int n;
while(cin>>n)
ivec.push_back(n);
auto it=ivec.begin();
while(it!=ivec.end()){ //不为空
if(*it%2)
it=ivec.erase(it); //erase返回被删元素之后元素的迭代器
else
++it;
}
for(auto c:ivec)
cout<<c<<" ";
c) 接受一对迭代器的erase坂本允许我们删除一个范围内的元素。依旧是左闭合区间,栗子在下面
vector<int>ivec{0,1,2,3,4,5,6,7,8,9};
auto iter1=ivec.begin()+1,iter2=ivec.end()-2; //*(ivec.end()-2)是8,前面的那个是1
ivec.erase(iter1,iter2);
for(auto c:ivec)
cout<<c<<"";
d) 为了删除一个容器中的所有元素,既可以调用clear函数,也可以用begin和end获得的迭代器作为参数调用erase
ivec1.clear();
ivec2.erase(ivec2.begin(),ivec2.end());
//等价调用,都是删除容器中所有元素
12. 改变容器大小
a) 用resize函数增大或缩小容器
c.resize(n) //调整c的大小为n个元素。
//若n<c.size(),则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化
c.resize(n,t) //调整c的大小为n个元素。任何新添加的元素都初始化为值t
b) 如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于新大小,会将新元素添加到容器后部
vector<int> ivec(10,42); //10个int,每个int的值都是42
ivec.resize(15); //将5个值为0的元素添加到ivec的末尾
ivec.resize(25,-1); //将10个值为-1的元素添加到ivec的末尾
ivec.resize(5); //从ivec的末尾删除20个元素
13. 容器操作可能使迭代器失效
a) 在向容器vector或string添加元素后,且存储空间被重新分配,指向容器的迭代器、指针或引用都会失效;如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用都会失效
b) 删除vector或string的一个元素后,指向被删元素之前元素的迭代器、指针和引用仍有效
c) 删除元素时,尾后迭代器总会失效
14. 构造string的其他方法
string s1
string s2(s1)
string s2=s1 //等价于s2(s1)
string s3("value")
string s3="value"
string s4(n,'c')
string有与其他顺序容器相同的构造函数,在前面。下面是新内容
string s(cp,n) //s是cp指向的数组中前n个字符的拷贝。此数组至少应该包含n个字符
string s(s2,pos2) //s是strings2从下标pos2开始的字符的拷贝
string s(s2,pos2,len2) //s是string s2从下标pos2开始len2个字符的拷贝。不管len2的值是多少,构造函数至多拷贝s2.size()-pose2个字符
string s(cp,n) //s是cp指向的数组中前n个字符的拷贝。此数组至少应该包含n个字符
string s(s2,pos2) //s是strings2从下标pos2开始的字符的拷贝
string s(s2,pos2,len2) //s是string s2从下标pos2开始len2个字符的拷贝。不管len2的值是多少,构造函数至多拷贝s2.size()-pose2个字符
string s("hello world");
string s2=s.substr(0,5); //s2=hello
string s3=s.substr(6); //s3=world
string s4=s.substr(6,11); //s4=world
substr操作返回一个string,它是原始string的一部分或全部的拷贝。可以传递给substr一个可选的开始位置和计数值
16. string搜索操作
a) string类提供了6个不同的搜索函数。每个搜索操作都返回一个string::size_type的值,表示匹配发生位置的下标;如果搜索失败,返回npos(初始化为值-1)
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开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认为0
cp,pos,n 从s中位置pos开始查找指针cp指向的数组的前n个字符。pos和n无默认值
17.compare函数
a) 类似于strcmp,根据string是等于、大于还是小于参数指定的字符串,s.compare()返回0、正数或负数
b) 根据是要比较两个string还是一个string与一个字符数组,参数各有不同。以下是s.compare()的6种参数形式
s2 比较s和s2
pos1,n1,s2 将s中从pos1开始的n1个字符与s2进行比较
pos1,n1,s2,pos2,n2 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较
cp 比较s与cp指向的以空字符结尾的字符数组
pos1,n1,cp 将s中从pos1开始的n1个字符与cp指向的以空字符结尾的字符数组进行比较
pos1,n1,cp,n2 将s中从pos1开始的n1个字符与指针cp指向的地址开始的n2个字符进行比较
18.容器适配器
a) 标准库定义了三个顺序容器适配器:stack、queue和priority_queue
b) 一种适配器本质上是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。例如,stack适配器接受一个顺序容器,并使其操作起来像一个stack一样
c) 所有容器适配器都支持的操作和类型:
size_type 一种类型,足以保存当前类型的最大对象的大小
value_type 元素类型
container_type 实现适配器的底层容器类型
A a; 创建一个名为a的空适配器
A a(c); 创建一个名为a的适配器,带有容器c的一个拷贝
关系运算符 每个适配器都支持所有关系运算符:==、!=、<、<=、>和>=,
这些运算符返回底层容器的比较结果
a.empty() 若a包含任何元素,返回false,否则返回true
a.size() 返回a中的元素数目
swap(a,b) 交换a和b的内容,a和b必须有相同类型,
a.swap(b) 包括底层容器类型也必须相同
19.定义一个适配器
a) 每个适配器都定义了两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器
b) 可以使用除array和forward_list之外的任何容器类型来构造stack
c) queue可以构造于list或deque之上,但不能基于vector构造
d) priority_queue可以构造于vector或string之上
20.栈适配器(可以在vector、deque或list之上时间)
a) stack类型定义在stack头文件中。以下是stack所支持的操作:
s.pop() 删除栈顶元素,但不返回该元素值
s.push(item) 创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,
s.emplace(args) 或者由args构造
s.top() 返回栈顶元素,但不将元素弹出栈
下面是一个小栗子:
stack<int>intStack; //空栈
for(size_t ix=0;ix!=10;ix++)
intStack.push(ix); //intStack保存0到9十个数
while(!intStack.empty()){ //intStack中有值就继续循环
int value=intStack.top();
intStack.pop(); //弹出栈顶值的元素,继续循环
}
b) 每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作,不能使用底层容器类型的操作。例如,不能在一个stack上调用push_back,而必须使用stack自己的操作——push
21. 队列适配器(queue默认基于deque实现,priority_queue默认基于vector实现)
a) queue和priority_queue适配器定义在queue头文件中。以下是queue和priority_queue支持的操作
q.pop() 返回queue的首元素或priority_queue的最高优先级的元素,但不返回此元素
q.front() 返回首元素或尾元素,但不删除瓷元素
q.back() 只适用于queue
q.top() 返回最高优先级元素,但不删除该元素,只适用于priority_queue
q.push(item) 在queue末尾或priority_queue中恰当的位置创建一个元素,
q.emplace(args) 其值为item,或者由args构造
b) 标准库queue使用一种先进先出的存储和访问策略:进入队列的对象被放置到队尾,而离开队列的对象则从队首删除
c) priority_queue允许为队列中的元素建立优先级,新加入的元素会排在所有优先级比它低的已有元素之前