9.1 顺序容器
1.vector和string采用顺序存储,可通过下标访问,但插入和删除会很耗时。
2.list和forward_list不支持随机访问,添加和删除很快
3.deque支持快速随机访问,中间位置添加和删除代价很高,但两端添加和删除很快
Note:通常,使用vector是最好的选择,除非有更加合适的理由选择其它容器(根据容器的特性);如果不确定使用哪种容器,可以只使用vectorhelist公共额操作:使用迭代器,不使用下标操作
9.2 容器库
1.顺序容器可以保存几乎任何类型,但某些类没有默认构造函数,构造容器是不能只传递给它一个元素数目参数;
vector<noDefault> v1(10,init);//OK
vector<noDefault> v2(10);//error
2.当将一个容器初始化为另一个容器拷贝时,两个容器的容器类型和元素类型都必须相同;但如果传递迭代器参数来拷贝一个范围时,就不要求容器类型相同了,而且元素只要能转换可以不同。
list<string> authors={"Milton","Shakspare","Austen"};
vector<const char*> articles={"a","an","the"};
list<string> list1(authors);//OK
deque<string> authlist(authors);//error
vector<string> words(articles);//error
forward_list<string> words(articles.begin(),articles.end());//OK
3.只要顺序容器的构造函数才接受大小参数,关联容器不接受
4.array:具有固定大小
array<int,5> ia1={1,2,3,4,5};
array<int,5> copy=ia1;//与内置数组类型的区别,可以对其进行赋值
5.assign赋值操作
//用法1
list<string> names;
vector<const char*> oldstyle;
names=oldstyle;//错误:容器类型不匹配
names.assign(oldstyle.cbegin(),oldstyle.cend());//OK
//用法2
list<string> slist(1);
slist1.assign(10,"Hiya");
6.使用swap
- 交换两个容器,元素本身没有交换,只交换内部数据结构
vector<string> vec1(10);
vector<string> vec2(20);
swap(vec1,vec2);//不会对元素进行拷贝、删除或插入操作
- array使用swap会真正交换它们的元素
- 新标准库中,容器既提供成员函数版本的awap。也提供非成员版本的swap。统一使用非成员版本的swap是一个好习惯,泛型编程应用广泛。
7.运算符的使用
每个容器都支持==和!=操作,除了无序关联容器外所有容器都支持<,>,<=,>=
Note:只有当元素也定义了相应的比较运算符时,才可以比较两个容器。
9.3 顺序容器操作
9.3.1 向容器中添加元素
1.添加元素
- forward_list不支持push_back和emplace_front操作,有自己专门的insert和emplase
- array不支持这些操作(会改变容器大小)
- vector、string或deque插入会使指向容器的迭代器、引用和指针失效
2.push_back插入到容器中是拷贝初始化
3.push_front插入到头部,vector不支持
4.insert指定位置添加元素
- 插入到迭代器指定位置之前
vector<string> svec;
svec.insert(svec.begin(),"Hello");//实现vector不支持push_front操作,但会很慢
- 插入范围内的元素
svec.insert(svec.end(),10,"Andy");//末尾插入10个元素
- 将给定范围中的元素插入到指定位置
svec.insert(svec.begin(),svec.end()-2,svec.end());//将最后两个元素插入到开始位置
svec.insert(svec.begin(),{"Andy","John","Joy"});//
- insert返回值:可实现反复插入元素
auto iter=svec.begin();
while(cin>>word)
iter=svec.insert(iter,word);//等价于push_front的意思
5.emplace操作
- emplace_front,emplace和emplace_back是构造而不是拷贝元素,对于push_front,insert,push_back
vector<Sales_data> c;
c.emplace("9999",25,15.65);//错误:
c.push_bace("9999",25,15.65);//OK
c.emplace(Sales_data("9999",25,15.65));//OK
c.emplace_back();//OK使用默认构造函数
c.emplace(iter,"9999");//OK使用Sales_data(string)构造函数
9.3.2 访问元素
1.每个顺序容器都有一个front函数,出forward_list之外还有一个back函数
auto val1=*c.begin(),val2=c.front();//两个都是得到第一个元素的值
auto val3=*(--c.end()),val4=c.back();//都是得到最好一个元素的值
2.at(i)和下标都只适用于string、vector、deque和array
3.访问元素的成员函数都是返回引用,如果容器是const,则返回const reference
auto &v=c.back();v=1027;//会改变值
auto v2=c.back();v=0;//不会改变值
9.3.3删除元素
1.pop_front和pop_back
vector和string不支持pop_front;forward_list不支持pop_back
2.erase
- 删除一个元素
it=lst.erase(it);//返回指向删除元素的后一个位置的迭代器
- 删除多个元素
elem1=lst.erase(elem1,elem2);//返回最后一个被删之后位置的迭代器
9.3.4 forward_list单向链表
1.和其他容器操作不同,所使用的是insert_after,emplace_after,erase_after操作
//删除元素为奇数的元素
forward_list<int> flst={0,1,2,3,4,5,6,7,8,9};
auto prev=flst.before_begin();//返回首前元素
auto curr=flst.begin();//返回第一个元素#
while(curr!=flst.begin()){
if(*curr%2)
curr=flst.erase_after(prev);
else{
prev=curr;
++curr;
}
}
9.3.5改变容器大小
使用resize
c.resize(n);
c.resize(n,t);//大小调整为n,任何新添加的元素都初始化为t
9.3.6 容器操作可能是迭代器失效
1.添加或删除vector、string和deque元素必须考虑迭代器、引用和指针失效问题。保证每个循环步骤都更新迭代器、引用或指针。如果是insert或erase,则更新可通过它们的返回值。
2.不要用局部变量保存end
9.4 vector对象是如何增长
1.capacity和size的区别
size指当前存储的元素大小
capacity指分配空间大小
9.5 额外的string操作
1.构造string的其它方法
除了与顺序容器相同的构造函数外,还提供:
string s(cp,n);//cp前n个字符
string s(s2,pos2);//s2下标pos2开始字符
string s(s2,pos2,len2);//s2从pos2开始len2个字符
s.substr(pos,n);//返回s中从pos开始的n个字符
2.改变string方法
- insert和erase
s.insert(s.size(),5,'!');//末尾插入5个!
s.erase(s.size()-5,5);//删除最后5个
const char *cp="What the fuck!";
s.assign(cp,7);//s="What ";
s.insert(s.size(),cp+7);//
- append和replace函数
s.append("Hello!");//等价于
s.insert(s.size(),"Hello");
s.replace(11,3,"Hello");//等价于
s.erase(11,3);s.insert(11,"Hello");
//习题代码有点乱,功能测试通过,后面再改吧,如果能用到KMP算法那就最完美了
void replaceOld(string& s, string oldVal, string newVal)
{
for (auto it1 = s.begin(); it1 != s.end();)
{
auto it = it1;
auto it2 = oldVal.begin();
for (;it2 != oldVal.end();)
{
if (*it1 == *it2)
{
++it1;
++it2;
}
else
break;
}
if (it2 == oldVal.end())
{
it = s.erase(it, it + oldVal.size() - 1);
for (auto it3 = newVal.end() - 1; it3 != newVal.begin(); --it3)
it = s.insert(it, *it3);
it = s.insert(it, *newVal.begin());
it1 = it + newVal.size();
}
else
it1 = ++it;
}
}
3.string的搜索操作
- find
string name("AnnaBelle")
auto pos=name.find("Anna");
- find_first_of/find_first_not_of
//查找name中第一个数字
string dept("0123456789"),name("r2d2");
auto pos=name.find_first_of(dept);//name中查找在dept中任意一个字符匹配的位置
- find_last_of/find_last_not_of
//查找任何一个字符最后一次出现的位置
- find指定开始位置查找
//搜索子串位置
string::size_type pos=0;
while((pos=name.find_first_of(numbers,pos))!=string::npos)//npos为找不到返回值
{
cout<<"found number at index:"<<pos
<<"element is:"<<name[pos]<<endl;
++pos;
}
- 逆向搜索rfind
4.compare函数
各参数版本
5.数字转换
int i=42;
string s=to_string(i);
double d=stod(s);
//stod第一个数值可能出现的位置
string s2="pi=3.14";
d=stod(s2.substr(s2.find_first_of("+-.0123456789")));//3.14
9.6 容器适配器
1.stack适配器:stack,queue,priority_queue
容器适配器是让一种已存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机制。其实仅是发生了接口转换。那么你可以把它理解为容器的容器,它实质还是一个容器,只是他不依赖于具体的标准容器类型,可以理解是容器的模版
接受一个顺序容器(array和foward_list除外)
- 定义一个适配器
STL 中提供的三种适配器可以由某一种顺序容器去实现。默认下stack 和queue 基于deque 容器实现,priority_queue 则基于vector 容器实现。当然在创建一个适配器时也可以指定具体的实现容器,创建适配器时在第二个参数上指定具体的顺序容器可以覆盖适配器的默认实现。
//deq是一个deque<int>
stack<int> stk(deq);//拷贝元素到std
stack