概述:C++容器是一个功能十分强大的库,利用好了这些容器资源,不仅可以提高书写代码的速度,更重要的是还可以提高代码的健壮性。这篇文章旨在尽可能详细地说明各种容器的优缺点和适用场合以及最重要的就是如何使用。
主要内容:本文章不会对其源代码进行深入分析,而是对其方法进行详细介绍,以便于在实际应用中使用。
C++容器库概述:
C++容器分为顺序容器和关联容器两大类,其中顺序容器主要包括vector,deque,list,forward_list,array,string六种,而关联容器主要包括map,set,multimap,multiset,unordered_map,unordered_set,unordered_multimap,unordered_multiset八种,此外,针对于顺序容器,标准库还定义了三个适配器,分别是stack,queue,priority_queue。
叙述顺序:
首先是介绍所有容器共有的属性和获取属性的方法,然后介绍如何使用各种顺序容器,然后总结各种顺序容器的适用场合和优缺点以及各种操作的对比,然后介绍各种适配器的使用,然后介绍如何使用各种关联容器,最后总结各种关联容器的适用场合和优缺点以及各种操作的对比。对于每一种容器的操作,无非就是如何定义和初始化一个容器,然后就是访问方法,再就是如何修改容器。
注:本文章使用的容器库为c++11标准,使用的IDE为 visual studio 2017 。
一、顺序容器共有属性
1.共有的类型别名:
类型别名有7个可分为5类;
第一类是容器的迭代器类型;
第二类是容器的大小类型;
第三类是容器的两迭代器之间的距离类型;
第四类是容器中元素的元素类型;
第五类是容器中元素的左值类型(就是引用);
具体分别是:
第一类:iterator和const_iterator reverse_iterator和const_reverse_iterator(反向迭代器)
第二类:size_type
第三类:difference_type(注意!迭代器运算仅仅支持可变长度线性存储结构,即vector,string,deque;而list,forward_list和array则不支持)
第四类:value_type
第五类:reference和const_reference
代码示例:(以vector为例)
#include<iostream>
#include<vector>
#include<cstdlib>
using namespace std;
int main() {
vector<int> ival = { 1,2,3,4,5,6,7,8,9 };
//第一类
cout << "使用普通迭代器遍历输出:";
for (vector<int>::iterator i = ival.begin();i != ival.end();++i) {
cout << *i << " ";
}
cout << endl<<"使用常迭代器遍历输出";
for (vector<int>::const_iterator i = ival.begin();i != ival.end();++i) {//注意!这儿的begin与end获取的迭代器发生了类型转换
cout << *i << " ";
}
cout << endl;
//第二类
cout << "使用下标遍历输出:";
for (vector<int>::size_type i = 0;i < ival.size();++i) {
cout << ival[i] << " ";
}
cout << endl;
//第三类
cout << "使用的迭代器的差来获取容器长度:";
vector<int>::difference_type length = ival.end() - ival.begin();
cout << "长度为" << length << endl;
//第四类
cout << "使用元素类型以及范围for语句来遍历容器:";
for (vector<int>::value_type x : ival) {
cout << x << " ";
}
cout << endl;
//第五类
cout << "使用元素引用来使原容器中每个元素的值增大1并输出:";
for (vector<int>::reference x : ival) {
++x;
}
for (auto x : ival) {
cout << x << " ";
}
cout << endl;
cout << "使用元素常引用来遍历输出容器中元素:";
for (vector<int>::const_reference x:ival) {
cout << x << " ";
}
cout << endl;
system("pause");
}
运行结果:
2.共有的访问属性的方法
总共有两类:
第一类有三个分别是获取容器的元素数量、获取容器中可保存元素的最大数量以及判断该容器是否为空的方法;
第二类是获取迭代器的方法,总共有8种方法;
具体分别是:
第一类:c.size() c.max_size() c.empty()
第二类:c.begin()和c.end() c.cbegin()和c.cend() c.rbegin()和c.rend() c.crbegin()和c.crend()
代码示例:(以vector为例)
#include<iostream>
#include<vector>
#include<cstdlib>
using namespace std;
int main() {
vector<int> ival = { 1,2,3,4,5,6,7,8,9 };
//第一类
cout << "使用size()成员函数来获取容器元素数量:有" << ival.size() << "个元素" << endl;
cout << "使用max_size()成员函数来获取容器当前最大可存储元素的数量:最大可存储" << ival.max_size() << "个元素" << endl;
cout << "使用empty()成员函数来判断容器是否为空:";
if (ival.empty()) {
cout << "容器为空" << endl;
}
else
{
cout << "容器不为空" << endl;
}
//第二类
cout << "使用begin()与end()使容器中各个元素的值都扩大二倍:现在容器中元素的值为 ";
for (vector<int>::iterator i = ival.begin();i != ival.end();++i) {
(*i) *= 2;
}
for (auto x : ival) {
cout << x << " ";
}
cout << endl;
cout << "使用cbegin()与cend()访问容器中的元素:";
for (vector<int>::const_iterator i = ival.cbegin();i != ival.cend();++i) {
cout << *i << " ";
}
cout << endl;
cout << "使用rbegin()与rend()访问容器中的元素 ";
for (vector<int>::reverse_iterator i = ival.rbegin();i != ival.rend();++i) {
cout << *i << " ";
}
cout << endl;
cout << "使用crbegin()与crend()访问容器中的元素:";
for (vector<int>::const_reverse_iterator i = ival.crbegin();i != ival.crend();++i) {
cout << *i << " ";
}
cout << endl;
system("pause");
}
运行结果:
二、顺序容器
1.vector
(1)定义和初始化
对于vector的定义和初始化,有8种方法。可以分为五种场合看待;
第一种是定义一个空的容器;
第二种就是将一个新的容器定义为某个旧容器的副本;
第三种就是使用多个重复的值初始化一个容器;
第四种就是使用多个确定的值初始化一个容器;
第五种就是使用另一个容器的迭代器来初始化一个新的容器。
(其中除了第一种和第五种只有一种方法,其他的都有两种方法。而那两种方法的唯一区别就是一个是直接初始化,一个是值初始化。)
具体分别是:
第一种:vector<T> v1;
第二种:vector<T> v2(v1); vector<T> v2=v1;
第三种:vector<T> v3(n,val); vector<T> v3(n);
第四种:vector<T> v5{a,b,c...}; vector<T> v5={a,b,c...};
第五种:vector<T> v6(b,e);
代码示例:
#include<iostream>
#include<vector>
#include<string>
#include<cstdlib>
using namespace std;
//遍历输出一个int型的vector对象
void Print(string name,vector<int> &ival) {
cout << "初始化方法:" << name <<" ---> ";
for (auto x : ival) {
cout << x << " ";
}
cout << endl;
}
int main() {
//第一种
vector<int> v1;
v1.push_back(1);//这个操作是向这个空容器的后面加一个数字1
//第二种
vector<int> v2(v1);
vector<int> v3 = v1;
//第三种
vector<int> v4(10, 2);
vector<int> v5(10);//这个初始化方法有一个局限,就是每个对象必须有默认的初始值,否则编译不通过
//第四种
vector<int> v6{ 1,2,3,4,5,6 };
vector<int> v7 = { 1,2,3,4,5,6 };
auto begin = v7.cbegin() + 1, end = v7.cend() - 1;//只访问不改变时推荐使用const_iterator类型的迭代器。
vector<int> v8(begin,end);
//以下遍历输出各种容器来查看各种容器中的内容
Print("第一种", v1);
Print("第二种(1)", v2);
Print("第二种(2)", v3);
Print("第三种(1)", v4);
Print("第三种(2)", v5);
Print("第四种(1)", v6);
Print("第四种(2)", v7);
Print("第五种", v8);
system("pause");
}
运行结果:
(2)访问容器
对于vector的访问可以分为四种方法:
第一种是通过下标进行访问;
第二种是通过迭代器;
第三种是通过at()成员函数;
第四种特殊的是获取容器首尾元素引用;
具体分别是:
第一种:c[n]
第二种:(*i) /*i是c的迭代器*/
第三种:c.at(n)
第四种:c.back()/*返回c尾元素引用*/ c.front()/*返回c首元素引用*/
代码示例:
#include<iostream>
#include<vector>
#include<cstdlib>
using namespace std;
int main() {
vector<int> ival = { 1,2,3,4,5,6,7,8,9 };
//注意访问元素的时候一定要保证容器是非空的,否则进行访问操作是未定义的。
//以下代码使用各种访问方法输出该容器的第3项元素(获取的都是该元素的引用)
//第一种
cout << "第一种使用下标访问: ---> 该元素的值为" << ival[2] << endl;
//第二种
auto i = ival.cbegin() + 2;
cout << "第二种使用迭代器访问: ---> 该元素的值为" << *i << endl;
//第三种
cout << "第三种使用at()成员函数访问: ---> 该元素的值为" << ival.at(2) << endl;
//最后一种方法用于获取首尾元素的引用(一定要注意是引用)
//第四种
cout << "第四种获取该容器的尾元素的引用: ---> 该元素的值为" << ival.back() << endl;
cout << "第四种获取该容器的首元素的引用: ---> 该元素的值为" << ival.front() << endl;
system("pause");
}
运行结果:
(3)修改容器
修改vector容器的类别可以分为三类:
第一类是对一个定义好的vector对象赋值,包括三种情况:
第一种是将一个容器值赋一个初值;
第二种是交换两个容器中的内容;
第三种是使用assign()方法进行赋值;(assign的好处是可以跨容器赋值)
第二类是向一个vector容器中添加一个或多个新的元素,对于插入的位置,都是在指定迭代器前面,也包括三种情况:
第一种是在容器的后面插入单个元素;
第二种是在容器任意位置插入单个元素;
第三种是在容器任意位置插入多个元素;
(当然,第一种情况完全是第二种情况的一个特例。不过使用的比较多就分别列出了)
第三类就是从vector容器中删除一个或多个元素也包括三种情况;
第一种是删除容器尾后元素;
第二种是删除容器中某个特定的元素;
第三种是删除容器中某个范围的元素,当然也可以是全部;
具体分别是:
第一类:
情况1:c1=c2; c={a,b,c,...};
情况2:swap(c1,c2); c1.swap(c2);
情况3:seq.assign(b,e); seq.assign(列表(il)); seq.assign(n,t);
第二类:
情况1:c.push_back(t)和c.emplace_back(args);/*返回void*/
情况2:c.insert(p,t)和c.emplace(p,args);/*返回指向新添加的元素的迭代器*/
情况3:c.insert(p,n,t); c.insert(p,b,e); c.insert(p,il);/*插入n个相同元素,插入其他容器在迭代器b和e范围中的元素,插入列表il中元素,都返回新添加的元素中第一个元素的迭代器*/
注:在以上方法中insert与emplace的唯一区别是前者是拷贝对象,而后者是构造对象。
第三类:
情况1:c.pop_back()/*返回void*/
情况2:c.erase(p)/*返回被删元素之后的元素的迭代器*/
情况3:c.erase(b,e)/*返回一个指向最后一个被删元素之后元素的迭代器*/; c.clear()/*清空,返回void*/
代码示例:
#include<iostream>
#include<string>
#include<list>
#include<vector>
using namespace std;
void Print(string name,vector<int> &x) {
cout << name<<":";
for (auto i : x) {
cout << i << " ";
}
cout << endl;
}
int main() {
vector<int> c1{ 1,2,3,4,5,6,7,8,9 };
Print("c1中元素:", c1);
vector<int> c2;
vector<int> c3;
cout << "第一类:" << endl;
cout << "第一种:" << endl;
c2 = c1;
Print("使用\'=\'赋值后c2中元素", c2);
c3 = { 9,8,7,6,5,4,3,2,1 };
Print("使用列表赋值后c3中元素", c3);
cout << endl;
cout << "第二种:" << endl;
cout << "将c2与c3中的元素交换:" << endl;
cout << "未交换前:" << endl;
Print("c2中元素", c2);
Print("c3中元素", c3);
cout << "交换后:" << endl;
swap(c2, c3);
Print("使用swap(c1,c2)交换后c2中元素", c2);
Print("使用swap(c1,c2)交换后c3中元素", c3);
cout << "现在使用另外一种交换方法交换回来:" << endl;
c2.swap(c3);
Print("使用c1.swap(c2)交换后c2中元素", c2);
Print("使用c1.swap(c2)交换后c3中元素", c3);
cout << endl;
cout << "第三种:" << endl;
vector<int> c4;
vector<int> c5;
vector<int> c6;
list<int> c7 = { 1,3,1,4 };
cout << "使用assign将c7中的元素拷贝到c4中:" << endl;
c4.assign(c7.cbegin(), c7.cend());
Print("现在c4中的元素为", c4);
cout << "使用assign将c5初始化为{7,4,1,7,4,1}:" << endl;
c5.assign({ 7,4,1,7,4,1 });
Print("现在c5中的元素为", c5);
cout << "使用assign将c6初始化为7个1" << endl;
c6.assign(7, 1);
Print("现在c6中的元素为", c6);
cout << "\n\n";
cout << "第二类:" << endl;
cout << "第一种:" << endl;
cout << "使用push_back()在c1后面添加数字10:" << endl;
int num1 = 10;
c1.push_back(num1);//或者直接c1.push_back(10);
Print("现在c1中的内容变成了", c1);
cout << "使用emplace_back()在c1后面添加数字11:" << endl;
int num2 = 11;
c1.emplace_back(num2);//或者直接c1.emplace_back(11);
Print("现在c1中的内容变成了", c1);
cout << endl;
cout << "第二种" << endl;
cout << "使用insert()在c1的第三项之前插入新的数字100:" << endl;
auto v1 = c1.cbegin() + 2;
v1=c1.insert(v1, 100);
cout <<"新添加的元素是"<< *v1<<endl;
Print("现在c1中的内容变成了", c1);
cout << "使用emplace()在c1的第三项之前插入新的数字200:" << endl;
v1 = c1.emplace(v1, 200);
cout << "新添加的元素是" << *v1 << endl;
Print("现在c1中的内容变成了", c1);
cout << endl;
cout << "第三种" << endl;
cout << "使用insert()在c1的第三项之前插入4个300:" << endl;
auto v2 = v1;
v2 = c1.insert(v2, 4, 300);
cout << "新添加的元素是4个" << *v2 << endl;
Print("现在c1中的内容变成了", c1);
cout << "使用insert()在c1的第三项之前插入容器c3中的第3到7项:" << endl;
auto v3 = v2;
v3 = c1.insert(v3, c3.cbegin() + 2, c3.cbegin() + 6);
cout << "新添加的元素是";
for (auto x = c3.cbegin() + 2;x != c3.cbegin() + 6;++x) {
cout << *x << " ";
}
cout << endl;
Print("现在c1中的内容变成了", c1);
cout << "使用insert()在c1的第三项之前插入{400,500,600,700}"<<endl;
auto v4 = v3;
v4 = c1.insert(v4, { 400,500,600,700 });
cout << "新插入的元素是";
for (auto i = c1.cbegin() + 2;i != c1.cbegin() + 6;++i) {
cout << *i << " ";
}
cout << endl;
Print("现在c1中的内容变成了", c1);
cout << "\n\n";
cout << "第三类" << endl;
cout << "第一种" << endl;
cout << "使用pop_back()在c1后面删除一个元素:" << endl;
c1.pop_back();
Print("现在c1中的内容变成了", c1);
cout << endl;
cout << "第二种"<<endl;
cout << "使用erase()删除c1的第三项:" << endl;
int d = *(c1.cbegin() + 2);
auto v5=c1.erase(c1.cbegin() + 2);
cout << "删除的元素是" << d << ",现在迭代器v5指向的元素是"<<*v5<<endl;
Print("现在c1中的内容变成了", c1);
cout << endl;
cout << "第三种" << endl;
cout << "使用erase()删除c1从第5项到10项的全部元素:" << endl;
cout << "删除的元素是:";
for (auto i = c1.cbegin() + 4;i != c1.cbegin() + 9;++i) {
cout << *i << " ";
}
cout << endl;
c1.erase(c1.cbegin() + 4, c1.cbegin() + 9);
cout << "使用clear()清空c1中的所有元素:" << endl;
c1.clear();
cout << "c1现在是空的吗?";
if (c1.empty()) {
cout << "是的" << endl;
}
else
{
cout << "不是" << endl;
}
system("pause");
}
运行结果:
2.list
(1)初始化方法
对于list的初始化,与vector完全相同,这里不再重复
(2)访问方法
相比于vector,list不支持随机访问,即使用下标访问和使用at()成员函数是无效的。还有一个重要的区别就是list的迭代器仅支持++、--和*运算,对于两个迭代器的差和迭代器和数字运算是不支持的。
(3)修改方法
修改方法除了支持vector的全部操作以外,还有两个新的操作,就是在容器头部进行插入
具体有两种方法,分别是
push_front和emlace_front
代码示例
#include<iostream>
#include<cstdlib>
#include<string>
#include<list>
using namespace std;
void Print(string name, list<int> ilist) {
cout << name << ":";
for (auto x : ilist) {
cout << x << " ";
}
cout << endl;
}
int main() {
list<int> c1 = { 1,2,3,4,5,6,7 };
Print("当前c1中的元素为", c1);
cout << "使用push_front()在c1前面插入一个数字0:" << endl;
c1.push_front(0);
Print("当前c1中的元素为", c1);
cout << "使用emplace_front()在c1前面插入一个数字-1:" << endl;
c1.emplace_front(-1);
Print("当前c1中的元素为", c1);
system("pause");
}
运行结果:
3.deque
deque除了支持vector的全部操作,还支持前插操作(即使用push_front和emplace_front方法)
4.array
对于array来说,容器一经定义其大小就是固定的了。所以定义的时候要指明其大小。
(1)定义和初始化
定义和初始化
对于array来说,定义和初始化的操作分为三种,
第一种是定义一个没有赋任何值得“空”数组;
第二种是定义为一个其他容器的副本;
第三种就是定义为一个初始化列表中的元素;
对比于vector,array不支持同时赋多个同样的值,另外还不支持迭代器范围赋值
具体方法为:(以int为例)
第一种 array<int,3> a1;
第二种 array<int,3> a2(a1); array<int,3> a3=a1;
第三种 array<int,3> a4{1,2,3}; array<int,3> a5={1,2,3};
代码示例:
#include<iostream>
#include<cstdlib>
#include<string>
#include<array>
using namespace std;
void Print(string name,array<int, 3> iarray) {
cout << name << ":";
for (auto x : iarray) {
cout << x << " ";
}
cout << endl;
}
int main() {
array<int, 3> a1;
array<int, 3> a2{ 1,2,3 };
array<int, 3> a3 = { 4,5,6 };
array<int, 3> a4(a2);
array<int, 3> a5 = a3;
Print("a2", a2);
Print("a3", a3);
Print("a4", a4);
Print("a5", a5);
system("pause");
}
运行结果:
(2)访问容器
和vector完全一样,这里不再赘述
(3)修改容器
array是一个长度固定的数组,因此所有修改容器的操作都不能改变容器的大小
以下是关于array的操作分类,一共有二类:
第一类:对已经定义好的array进行赋值
第二类:对两个array中的内容进行交换
其中赋值操作是通过"="号进行实现的,注意进行赋值一定要保证array类型的完全相同。
而交换是使用swap方法,也是有两种。
具体代码也不再一一展示
5.forward_list
这是一种前插式单向链表,不支持后插操作,也不支持从后向前遍历
(1)定义和初始化
初始化方法与vector完全一样,这里不再赘述
(2)访问容器
由于是前插式的链表,所以就没有反向遍历这种操作,因此也就不存在反向迭代器。另外一个值得注意的地方是forward_list没有size()方法,因此要想正向遍历一个forward_list容器,就只有两种可选的方式,一种是使用正向迭代器,另一种就是使用范围for语句。
因为需要在此表的前部插入一些内容,所以还定义了一个指向表的首元素之前的一个不存在元素的一个迭代器,以便于在表的头部插入数据。
具体方法为
lst.before_begin()和lst.cbefore_begin()
(3)修改容器
根据修改数据方式的不同,可以分为两类
第一类是插入数据,有四种方法
第一种是在某个迭代器之后插入一个元素;
第二种是在某个尾后迭代器之后插入n个值;
第三种是在某个尾后迭代器之后插入另一个容器某一迭代器范围内的元素;
第四种是在某个迭代器之后插入一个初始化列表中的元素;
第二类是删除数据,有两种方法
第一种是删除某个迭代器之后的数据
第二种是删除某个迭代器之间的数据,不包括前迭代器的元素,但是包括后迭代器指向的元素
具体为:
第一类
第一种:lst.insert_after(p,t)和lst.emplace_after(p,args);
第二种:lst.insert_sfter(p,n,t);
第三种:lst.insert_after(p,b,e);
第四种:lst.insert_after(p,il);
第二类
第一种:lst.erase_after(p);
第二种:lst.erase_after(b,e);
代码示例:
#include<iostream>
#include<cstdlib>
#include<forward_list>
#include<vector>
#include<string>
using namespace std;
void Print(string name, const forward_list<int> &iflist) {
cout << name << ":";
for (auto x : iflist) {
cout << x << " ";
}
cout << endl;
}
int main() {
forward_list<int> c1 = { 1,2,3,4,5,6,7 };
vector<int> c2(c1.begin(), c1.end());
cout << "第一类:" << endl;
cout << "在c1前面插入一个元素0" << endl;
c1.insert_after(c1.cbefore_begin(),0);
Print("现在c1中的元素为", c1);
cout << "在c1前面再插入5个-1" << endl;
c1.insert_after(c1.cbefore_begin(), 5, -1);
Print("现在c1中的元素为", c1);
cout << "在c1前面插入c2的第二到5个位置的元素" << endl;
c1.insert_after(c1.before_begin(), c2.begin() + 1, c2.begin() + 5);
Print("现在c1中的元素为", c1);
cout << "在c1前面插入列表{100,200,300}中的元素" << endl;
c1.insert_after(c1.cbefore_begin(), { 100,200,300 });
Print("现在c1中的元素为", c1);
cout << "\n\n";
cout << "第二种" << endl;
cout << "将c1的首元素删除" << endl;
c1.erase_after(c1.cbefore_begin());
Print("现在c1中的元素为", c1);
auto i1 = c1.before_begin();
auto i3 = c1.begin();
for (int i = 0; i < 10; ++i) {
++i3;
}
cout << "删除c1前面10个元素" << endl;
cout << "被删除的元素为:" << endl;
for (auto i = c1.begin(); i != i3; ++i) {
cout << *i << " ";
}
cout << endl;
c1.erase_after(i1, i3);
Print("现在c1中的元素为", c1);
system("pause");
}
运行结果:
6.string
string可以看作是一个char型的vector容器,除了支持与vector<char>的全部操作外,还有额外的定义,访问和修改的方法。
以下只叙述string相比于vector<char>的额外的操作
这些操作的类型要么是提供string类与c风格字符数组之间的相互转换,要么是增加了允许我们使用下标代替迭代器的版本
(1)构造string的其他方法
构造string的新方法可以分为两种:
第一种是提供从c风格字符数组来进行初始化的一个方法;
第二种是提供从另一个string对象的下标计算的部分初始化方法;
具体分别是:
第一种:string s(cp,n);(新的string对象会被初始化为cp数组前n个字符)
第二种:string s(s2,pos2); string s(s2,pos2,len2); 或者使用substr(eg:string s=s2.substr(0,5);<前闭后开>,如果只有一个参数,表明从某位置开始截取,一直到串尾,如果没有参数,则等同于"="号)。
程序示例:
#include<iostream>
#include<cstdlib>
#include<string>
using namespace std;
int main() {
const char a[20] = "hello world!!!";
cout << "a:" << a << endl;
cout << "将s1中的内容初始化为a的前12个字符" << endl;
string s2(a, 12);
cout << "s2:" << s2 << endl;
cout << "将s3初始化为s2从下标6开始后的所有字符:" << endl;
string s3(s2, 6);
cout << "s3:" << s3 << endl;
cout << "将s4初始化为s3从下标0开始后的5个字符:" << endl;
string s4(s3, 0, 5);
cout << "s4:" << s4 << endl;
cout << "使用substr将s5初始化为s2从下标6开始的所有字符:" << endl;
string s5 = s2.substr(6);
cout << "s5:" << s5 << endl;
cout << "使用substr将s6初始化为s5从下标0开始后的5个字符:" << endl;
string s6 = s5.substr(0, 5);
cout << "s6:" << s6 << endl;
cout << "如果substr没有参数,则等同于\"=\"号:" << endl;
string s7 = s2.substr();
cout << "s7:" << s7 << endl;
system("pause");
}
运行结果:
(2)改变string的其他方法
string为了与c风格字符数组兼容以及支持使用下标来进行字符串的操作,重载了assign,insert和erase,另外还定义了append和replace两个函数用来追加和替换容器。
具体分别是:
s.insert(pos,args) 在pos之前插入args指定的字符,pos可以是一个下标或一个迭代器。接受下标的版本返回一个指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。
s.erase(pos,len) 删除从位置pos开始的len个字符。如果len被省略,则删除从pos开始直至s末尾的所有字符。返回一个指向s的引用。
s.assign(args) 将s中的字符替换为args指定的字符。返回一个指向s的引用。
s.append(args) 将args追加到s。返回一个指向s的引用。
s.replace(range,args) 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。返回一个指向s的引用。
args可以是下列形式之一;append和assign可以使用所有形式
str不能与s相同,迭代器b和e不能指向s
str 字符串str
str,pos,len str中从pos开始最多len个字符
cp,len 从cp指向的数组前(最多)len个字符
cp cp指向的以空字符结尾的字符数组
n,c n个字符c
b,e 迭代器b和e指定范围内的元素
初始化列表。
replace和insert所允许的args形式依赖于range和pos是如何指定的
replace(pos,len,args) | replace(b,e,args) | insert(pos,args) | insert(iter,args) | args可以是 |
---|---|---|---|---|
✔ | ✔ | ✔ | ✘ | str |
✔ | ✘ | ✔ | ✘ | str,pos,len |
✔ | ✔ | ✔ | ✘ | cp,len |
✔ | ✔ | ✘ | ✘ | cp |
✔ | ✔ | ✔ | ✔ | n,c |
✘ | ✔ | ✘ | ✔ | b2,e2 |
✘ | ✔ | ✘ | ✔ | 初始化列表 |
示例代码:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
int main() {
string s("hello");
//insert与erase:
cout << "s:" << s << endl;
//insert(pos,n,c)
s.insert(s.size(), 5, '!');//在s后面插入5个'!'
cout << "s:" << s << endl;
//erase(pos,len)
s.erase(s.size() - 5, 5);//在s后面删除5个字符
cout << "s:" << s << endl << endl;
cout << endl;
//assign与insert:(c语言风格字符串风格)
const char *cp = "Stately,plump Buck";
//assign(cp,len)
s.assign(cp, 7);//s="Stately"
cout << "s:" << s << endl;
//insert(cp,len)
s.insert(s.size(), cp + 7);//s="Stately,plump Buck"
cout << "s:" << s << endl;
cout << endl;
//inser(字符串)
string s2 = "some string", s3 = "some other string";
cout << "s2:" << s2 << endl;
cout << "s3:" << s3 << endl;
//insert(pos,str)
s2.insert(0, s3);
cout << "s2:" << s2 << endl;
//insert(pos,str,pos,len)
s2.insert(0, s3, 0, s3.size());
cout << "s2:" << s2 << endl;
cout << endl;
//append与replace
string s4("C++ Primer"), s5 = s4;
s4.insert(s4.size(), " 5th Ed");//s4="C++ Primer 5th Ed"
s4.append(" 5th Ed");//与上面等价
cout << "s4:" << s4 << endl;
cout << "s5:" << s5 << endl;
//将5th替换为6th
s4.erase(11, 3);
s4.insert(11, "6th");
s4.replace(11, 3, "6th");//与上面等价
s4.replace(11, 3, "sixth");
cout << "s4:" << s4 << endl;
system("pause");
}
运行结果
(3)string搜索操作
string类提供了6个不同的搜索,每个搜索操作有4个重载版本,如果搜索成功,都返回匹配位置的下标,如果搜索失败,则返回一个非常大的整数
6种搜索操作分别是:
第一种:寻找s中args第一次出现的位置;
第二种:寻找s中args最后一次出现的位置;
第三种:在s中查找args任何一个字符第一次出现的位置
第四种:在s中查找args任何一个字符最后一次出现的位置
第五种:在s中查找第一个不在args中出现的字符
第六种:在s中查找最后一个不在args中出现的字符
具体分别是:
第一种:s.find(args);
第二种:s.rfind(args);
第三种:s.find_first_of(args);
第四种:s.find_last_of(args);
第五种:s.find_first_not_of(args);
第六种:s.find_last_not_of(args);
args必须是以下形式之一 | 功能描述 |
---|---|
c,pos | 从s中位置pos开始查找字符c。pos默认为0 |
s2,pos | 从s中位置pos开始查找字符串s2.pos默认为0 |
cp,pos | 相当于s换成了字符数组cp |
cp,pos,n | 相当于s换成了字符数组cp |
代码示例1:
#include<iostream>
#include<cstdlib>
#include<string>
using namespace std;
//寻找某子串在主串中的数量,使用string的find()方法
int findAmount(const string &str, const string &childStr) {
auto x = str.find(childStr);
if ( x > str.size()) {
return 0;//如果str中不存在childStr,那么x会是一个超级大的整数,此时返回0;
}else{
string temp(str, x + 1, str.size() - x - 1);//递归搜索后面的字符串
return 1+findAmount(temp, childStr);
}
}
int main() {
string s1 = "aacvcaa aacvcaa aacvcaa aacvcaa";
string s2 = "cvc";
cout <<"子串"<<s2<<"在主串"<<s1<<"中的数量为"<<findAmount(s1, s2)<<endl;
system("pause");
}
运行结果1:
代码示例2:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
int main() {
string name("AnnaBelle");
auto pos1 = name.find("Anna");
cout << "pos1:" << pos1 << endl;
cout << endl;
//大小写敏感
string lowercase("annabelle");
pos1 = lowercase.find("Anna");
cout << "pos1:" << pos1 << endl;
string numbers("0123456789"), name1("r2d2");
auto pos = name1.find_first_of(numbers);
cout << "pos:" << pos << endl;
string dept("03714p3");
auto pos2 = dept.find_first_not_of(numbers);
cout << "pos2:" << pos2 << endl;
cout << endl;
//指定位置搜索
string::size_type pos3 = 0;
while ((pos3 = name1.find_first_not_of(numbers, pos3)) != string::npos) {
cout << "在" << pos3 << "处发现数字" << name1[pos3] << endl;
++pos3;
}
cout << endl;
//逆向搜索
string river("Mississippi");
auto first_pos = river.find("is");
cout << "first_pos:" << first_pos << endl;
auto last_pos = river.rfind("is");
cout << "last_pos:" << last_pos << endl;
system("pause");
}
运行结果:
(4)string的compare函数
字符串的比较就是使用字典序比较,没什么可说的
返回值分三种,若s小于目标串,则返回负数,相等返回0,大于则返回正数。
使用方法为s.compare(args);
args可以是以下几种形式之一:
s2:比较s与s2
pos1,n1,s2:将s中从pos1开始的n1个字符与s2进行比较
pos1,n1,s2,pos2,n2:将s中从pos1开始的n1个字符与s2从pos2开始的n2个字符进行比较
cp:与c风格字符串比较
pos1,n1,cp:从pos1开始的n1个字符与cp比较
pos1,n1,cp,n2:从pos1开始的n1个字符与cp后的n2个字符进行比较
代码示例:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
int main() {
const char *cp = "zzc_hello_world!";
string s1 = "ddfg_hello_world!!!";
string s2 = "jjjiur_hello_world!!";
if (s1.compare(5, 5, s2, 7, 5) == 0) {
cout << "局部相等" << endl;
}else {
cout << "局部不相等" << endl;
}
if (s1.compare(5, 5, cp + 5, 5)) {
cout << "局部相等" << endl;
}else {
cout << "局部不相等" << endl;
}
system("pause");
}
运行结果:
(5)string与数值之间进行转换的方法
字符串与数值之间的转换,分两种情况,字符串转数值与数值转字符串:
其中字符串转数值又分为转为整型数或浮点型数
to_string(val)转字符串,有各种重载
stoi(s,p,b); string->int
stol(s,p,b); string->long
stoul(s,p,b); string->unsigned long
stoll(s,p,b); string->long long
stoull(s,p,b); string->unsigned long long
其中p是s中第一个数字字符,默认为0,b是使用的进制,默认为10
stof(s,p); string->float
stod(s,p); string->double
stold(s,p); string->long double
s,p同上
示例代码:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
int main() {
int i = 42;
string s = to_string(i);
double d = i + 0.5;
string s2 = to_string(d);
double d2 = stod(s2);
cout << "s:" << s << endl;
cout << "d2:" << d2 << endl;
string s3 = "PI=3.1415";
double d3 = stod(s3.substr(s3.find_first_of("+-.0123456789")));
cout << "d3:" << d3 << endl;
system("pause");
}
运行结果:
7.在文章的最开始,我们提到了c++还为这些顺序容器提供了三种适配器,所谓适配器,就是使容器的行为看起来像其他的存储结构一样,即栈,普通队列和优先队列,优先队列就是按照某种规则排序的队列。(stack,queue,priority_queue)
首先介绍所有适配器都具有的字段和函数:
介绍就不说了,都可以顾名思义:
size_type; value_type; container_type;(这个是实现适配器的底层类型) A a;(定义一个适配器)
A a(c);适配器带有c容器的拷贝 关系运算 ==,!=,<,<=,>,>= a.empty(); a.size(); a.swap(b) swap(a,b);
默认情况下stack和queue是基于deque实现的,priority_queue是基于vector实现的
当然他们都可以指定实现的容器:
例如:用list实现一个栈,则可以这样写:
stack<int,list<int>> istk_list;
其他的类似
其中需要注意的是:
stack可以使用除array和forward_list以外的容器,queue相比stack不可使用vector,priority_queue相比stack不可使用list
下面是栈特有的操作:
s.pop(); 出栈,但不返回栈顶元素
s.push(item); 进栈
s.emplace(args); 进栈
s.top(); 返回栈顶元素
下面是队列特有的操作:
q.pop(); 出队列,但不返回栈顶元素
q.front(); 返回首元素
q.back(); 返回尾元素
q.push(item); 进队列
q.emplace(args); 进队列
其中优先队列不支持上面的back()操作,而优先队列还另外支持一个q.top()操作
下面是一个示例:
代码:
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<cstdlib>
using namespace std;
int main() {
vector<string> s{ "123", "456", "789" };
stack<string, vector<string>> str_stk(s);
cout << "栈str_stk的栈顶元素:" << str_stk.top() << endl << endl;;
stack<int> intStack;
int value;
for (size_t ix = 0; ix != 10; ++ix) {
intStack.push(ix);
}
while (!intStack.empty()) {
value = intStack.top();
cout << value << " ";
intStack.pop();
}
cout << endl << "栈空!" << endl;
system("pause");
}
运行结果:
常用的顺序容器以及操作至此已全部叙述完毕:下面来看一看什么情况下应该选取什么样的容器才是最合适的:
1.首选vector,如果是字符操作和字符串操作,首选string
2.如果程序中有许多小的元素,而且可用空间不是很乐观,则不要使用list或者forward_list
3.如果程序会随机访问容器中的元素,则应该使用vector或者deque
4.如果程序会频繁的在容器中间插入或者删除数据,则优先考虑使用list或者forward_list
5:如果程序只会频繁的在容器的头部和尾部进行数据的插入和删除,则应优先考虑使用deque
6:如果要先进行频繁改变后又进行频繁的随机访问,则有以下两种推荐方案:
1)考虑使用assign方法将list或者forward_list中的元素拷贝到vectoe或者deque中再进行操作
2)如果要进行对输入数据进行即时排序,则应考虑在vector尾后添加元素后再调用sort方法进行排序。
7:如果既需要频繁的中间插入,又需要频繁的随机读取,则使用概率统计方法统计出哪种操作所占比重较大,则优先考虑使用哪种有利于占比较大操作的容器,如果有时候两种操作比重差不多大,则应该进行运行时间测试,从而确定最佳容器以及最佳方案。