概述: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:如果既需要頻繁的中間插入,又需要頻繁的隨機讀取,則使用概率統計方法統計出哪種操作所佔比重較大,則優先考慮使用哪種有利於佔比較大操作的容器,如果有時候兩種操作比重差不多大,則應該進行運行時間測試,從而確定最佳容器以及最佳方案。