使用場合:
vector算是一個比較萬金油的容器,它是一個可變大小數組,支持隨機訪問,不過在尾部以外的位置進行增加和刪除操作會比較耗時。通常用vector來代替原始的數組來使用,比較方便。
聲明與初始化:
首先要包含頭文件,vector的頭文件名就是< vector >。
聲明方式:
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve;//聲明一個存儲int型數據的容器ve
vector<vector<int>> vve;//聲明一個二維的vector,C++11標準寫法
vector<vector<string> > vvs;//兩個尖角括號之間要留一個空格,舊編譯器的寫法
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
初始化:
vector的初始化方式很多,書上介紹的如下
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> va;//調用默認構造函數,裏面什麼也沒有
for(int i=0;i<5;i++)
va.push_back(i);//放5個元素進去
vector<int> vb(va);//用va初始化vb,此種用法要求va與vb必須是同一種容器,且類型相同
vector<int> vc{1,2,3,4};//初始化列表
vector<int> vc2={1,2,3,4};//同上,C++11新標準
vector<int> vd(va.begin(),va.end());//用迭代器指定的範圍初始化
list<int> li={2,3,4};
vector<int> vli(li.begin(),li.end())//使用迭代器可以把不同容器,類型相同的元素用來初始化
vector<int> ve(10);//包含10初始化值的元素,在ve當中裏面有10個0次構造函數是explicit
vector<int> vf(10,1);//在vf裏面塞進10個1
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
賦值操作:
利用拷貝構造函數和swap函數可以實現賦值操作
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> va,vb,vc;
va={1,2,3,4};//用C++11的初始化列表來賦值
for(int i=1;i<=10;i++)//在vb中塞入10個數
vb.push_back(i);
va=vb;//把vb拷貝給va
swap(va,vc);//交換va和vc
for(auto x:vc)//輸出應該是va裏面的值
cout<<x<<" ";
for(auto x:va)//va裏面應該什麼也沒有
cout<<x<<" ";
cout<<endl;
va.swap(vc);//再把vc和va換回來,用成員函數的形式
for(auto x:vc)//裏面什麼也沒有
cout<<x<<" ";
for(auto x:va)//
cout<<x<<" ";
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
還可以利用assign函數實現
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> vb,va={1,3,5,7,9};
vb.assign(va.begin(),va.begin()+3);//使用va的迭代器給vb賦值
//這裏注意,不可以自己給自己用迭代器賦值
for(int i=0;i<vb.size();i++)
cout<<vb[i]<<endl;//輸出1 3 5
vb.assign({2,4,6});//裏面可以放一個初始化列表
for(int i=0;i<vb.size();i++)
cout<<vb[i]<<endl;//輸出2 4 6
list<int> li={2,4,6,8};
vb.assign(li.begin(),li.end());//可以把其他容器,但是類型相同的迭代器用來賦值
for(int i=0;i<vb.size();i++)
cout<<vb[i]<<endl;
list<string> names={"abc","efg"};
vector<const char*> oldstyle={"qwe","ert"};
names.assign(oldstyle.begin(),oldstyle.end());//vector給list初始化,也可以用cbegin
//把const cahr*賦值給string
for(auto x:names)
cout<<x<<endl;//qwe ert
vector<string> vc;
vc.assign(3,"abc");//向vc中塞入3個abc
for(int i=0;i<vc.size();i++)
cout<<vc[i]<<endl;//abc abc abc
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
vector的比較
vector重載了運算符<,>,==,<=,>=等比較符號。其比較規則與字典序類似,如果兩個容器具有相同大小,而且每個元素對應相等,那麼這兩個vector相同。
如果兩個vector大小不同,但是公有的元素和對應位置全都相同,那麼大的容器比小的容器大。
如果兩個容器完全不同,那麼,比較第一個不同的元素哪個大那個小作爲判斷標準。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> v1={1,3,5};
vector<int> v2={1,4};
vector<int> v3={1,3,5};
cout<<(v1<v2)<<endl;//true
cout<<(v1==v3)<<endl;//true
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
插入元素:
vector當中插入元素主要使用push_back向後面插入一個元素。
在vector當中沒有push_front的用法!
如果想再任意位置插入一個元素,可以用insert成員函數,同樣會涉及到數據移動。
具體用法:
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve;
//ve.push_back(e)在ve的尾部添加一個e元素
ve.push_back(1);//在尾部插入一個1
//ve.insert(it,n,e)//在it指向元素的前面添加n個元素e
ve.insert(ve.end(),1,2);//相當於在尾部添加1個2
ve.insert(ve.begin(),1,0);//在頭部添加1個0
//ve.insert(it,beg,ed)//在it指向元素的前面添加迭代器[beg,ed)範圍內的元素
vector<int> v={-2,-1};
ve.insert(ve.begin(),v.begin(),v.end());//在ve的前面添加v的元素
//ve.insert(it,li)在it所指的元素的前面添加一個列表li
ve.insert(ve.end(),{3,4,5});
//錯誤用法
//ve.insert(ve.begin(),ve.begin(),ve.end())不能用調用對象自己的迭代器當做賦值範圍
for(auto x:ve)
cout<<x<<endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
這裏的push_back操作是對拷貝值進行操作。
在C++11標準當中,insert函數具有返回值,返回第一個新加入元素的迭代器(不能是插入列表)。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve={1,2,3,4};
auto it=ve.insert(ve.end(),5);//返回指向最後一個元素的迭代器
cout<<*it<<endl;//5
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
emplace操作:
當調用emplace操作的函數時,每次會新構造一個對象,並添加到容器當中。添加的方法要和類當中的構造函數相匹配。
在vector當中有c.emplace()和c.emplace_back()操作兩種。參數當中可以
#include <bits/stdc++.h>
using namespace std;
class A
{
public:
int a;
string s;
A(){a=0,s="";}
A(int aa,string ss){a=aa,s=ss;}
};
int main()
{
ios::sync_with_stdio(false);
vector<A> va;
va.emplace_back(1,"abc");//添加
va.emplace_back();//添加一個默認構造函數
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
也可以調用c.emplace(it,arg);其中it爲指向一個數據的迭代器,arg爲參數。
訪問元素:
- 清空vector可以使用成員函數c.clear()
- 判斷vector是否爲空,可以使用成員函數empty(),如果爲空返回true,否則返回false
- vector輸出最後一個元素的引用可以用back()成員函數,如果容器爲空,則行爲未定義
- vector輸出第一個元素的引用可以用front()成員函數,如果容器爲空,則行爲未定義
- vector支持用下標訪問元素,類似數組一樣c[n]其中n是一個無符號整數,如果n大於容器的長度,那麼行爲未定義
- vector爲了防止越界訪問,其中有成員函數c.at(n),返回下標爲n的元素的引用。如果下標越界,那麼拋出out_of_range的異常
以上返回引用會根據容器的類型來判斷,也就是說如果聲明瞭一個const容器,那麼返回一個const的引用,否則返回一個普通的引用
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve={1,2,3,4};
ve.back()=5;//4變成5
ve.front()=0;//1變成0
cout<<ve[0]<<" "<<ve[ve.size()-1]<<endl;
try
{
ve.at(6);
}
catch(out_of_range &e)
{
cerr<<e.what()<<endl;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
刪除元素
vector當中刪除元素通常使用pop_back()用來刪除最後一個元素,也可以使用erase()成員函數來刪除任意位置的元素。由於vector的性質,刪除任意位置的元素會降低效率。
- pop_back()成員函數用來刪除vector中的最後一個元素,如果容器爲空會出現未定義行爲。
- c.erase(it)成員函數,刪除迭代器it所指向的元素,返回一個指向被刪除元素之後的迭代器,如果it指向最後一個元素,那麼返回以爲尾後迭代器(通常是end())。若it就是end(),那麼行爲未定義。
- c.erase(beg,ed)刪除[beg,ed)範圍的元素,同時返回最後一個元素的後面的迭代器,如果ed就是尾後迭代器,那麼還返回一個尾後迭代器。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve={1,2,3,4};
ve.erase(ve.begin(),ve.begin()+1);//刪除第一個元素,結果是2 3 4
ve.erase(ve.begin());//刪除第一個元素,結果是3 4
ve.pop_back();//刪除最後一個元素,結果變成3
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
注意,刪除元素以後會使迭代器失效,所以循環刪除元素這樣寫是錯誤的。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve={1,2,3,4,5,6,7,8,9};
for(auto it=ve.begin();it!=ve.end();it++)//錯誤寫法
if((*it)%2)//刪除奇數的元素
ve.erase(it);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
正確的刪除方式
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve={1,2,3,4,5,6,7,8,9};
for(auto it=ve.begin();it!=ve.end();)//沒毛病
{
if((*it)%2)
ve.erase(it);
else
it++;
}
for(auto x:ve)
cout<<x<<endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
謹記,改變容器的長度的操作會使迭代器實效。
vector的容量與內存管理:
vector是一個可以增長長度的容器,用戶可以多自行改變容器長度。C++11標準當中爲了節約內存,新增加了一個函數用來回收多餘的內存。
- size()成員函數,返回容器當中元素的個數。
- resize(n)成員函數,重新定義容器的元素的個數。注意,不是容量。如果n大於當前的size(),那麼多出的長度用默認值補充,如果n小於當前的size()那麼只留下前n個元素,剩下的刪除。
- resize(n,t)成員函數,原則同上,只不過多出的元素用t補充而已
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve={1,2,3,4,5,6,7,8,9};
cout<<ve.size()<<endl;//9
ve.resize(15);
cout<<ve.size()<<endl;//15 多出來的用默認值補充
ve.resize(3);//就留下前3個數,剩下的都刪除
ve.resize(10,-1)//多出的元素用-1補充
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- capacity()成員函數是不重新分配內存的情況下,容器可以保存多少元素
- reserve(n)成員函數是分配智商能容納n個元素的內存空間。比如ve當中有三個元素,現在使用ve.reserve(1),那麼沒有變化。如果使用ve.reserve(10),那麼ve.size()還是原來的值,不過ve.capacity()會變成10
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve;
for(int i=0;i<100;i++)//放入100個元素
ve.push_back(i);
cout<<ve.size()<<endl;//100
cout<<ve.capacity()<<endl;//128
ve.reserve(99);//比100小,所以什麼也不做
cout<<ve.size()<<endl;//100
cout<<ve.capacity()<<endl;//128
ve.reserve(150);
cout<<ve.size()<<endl;//100
cout<<ve.capacity()<<endl;//150
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
最後說一下C++11的新函數,shrink_to_fit()說簡單點,就是把多餘沒用到的空間回收一下,這個函數是向系統提交一個申請,而不是一個強制命令。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<int> ve;
for(int i=0;i<100;i++)//放入100個元素
ve.push_back(i);
cout<<ve.size()<<endl;//100
cout<<ve.capacity()<<endl;//128
ve.shrink_to_fit();
cout<<ve.size()<<endl;//100
cout<<ve.capacity()<<endl;//100
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
可能會覺得,那每次使用vector後都調用這個函數,不就很省內存了嘛。此言差矣,如果需要再向容器當中塞東西,那麼就要重新給容器增加分配的內存,更浪費時間和效率。
多維vector的用法:
用二維向量做例子,所謂多維向量,就是一個向量一面存儲的內容是一個向量。通常可以代替二維數組來使用。也可以使用向量的數組來表示。
二維向量的聲明:
vector<vector<int>> vve;//在新版本的編譯器當中這樣寫沒問題
vector<vector<int> >vvve;//舊版本的編譯器會把兩個相連的尖括號當成流操作
- 1
- 2
初始化:
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<vector<int>> vva(10,vector<int>(0));//vve裏面放10個vector,每個vector裏面初始化一個0元素
vector<vector<int>> vvb(10,{1,2,3,4});//v裏面放10個vector,每個vector用列表初始化
vector<int> va={0,2,3};
vector<vector<int>> vvc(3,va);//vvc中裏面放入3個va
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
總之,和初始化一個普通的vector沒什麼兩樣,只不過就是初始化的元素變成了vector而已
插入:
這裏使用push_back和insert
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<vector<int>> vvb(4,{1,2,3,4});
for(int i=0;i<3;i++)
{
vector<int> vtemp={i};
vvb.push_back(vtemp);//每次塞入一個用i初始化過的vtemp
}
vector<int> vt={666};
vvb.insert(vvb.begin(),vt);//把vt放在vvb的頭部
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
遍歷:
一般使用下標遍歷、迭代器遍歷
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<vector<int>> vvb(4,{1,2,3,4});
for(int i=0;i!=vvb.size();i++)
{
for(int j=0;j!=vvb[i].size();j++)
cout<<vvb[i][j]<<" ";
cout<<endl;
}
for(auto bit=vvb.begin();bit!=vvb.end();bit++)
{
for(auto bbit=bit->begin();bbit!=bit->end();bbit++)//bit類似指向一個vvb[i],裏面是向量中的元素
cout<<*bbit<<" ";
cout<<endl;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
當然,也可以用C++11的新遍歷方式
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<vector<int>> vvb(4,{1,2,3,4});
for(auto x:vvb)
{
for(auto xx:x)
cout<<xx<<" ";
cout<<endl;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
刪除操作:
和一維vector刪除元素沒什麼區別
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
vector<vector<int>> vvb(4,{1,2,3,4});
vvb.pop_back();//刪除最後一個元素
vvb.erase(vvb.begin());//刪除第一個向量元素
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
三維,或者更高維的也是如此。
容器的指針,vector::data:
調用vector::data會返回一個指向第一個元素的指針,由於vector的內存是連續的,所以指針的移動,也就對應vector當中的元素遍歷。看代碼
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
std::vector<int> myvector (5);
int* p = myvector.data();
*p = 10;
++p;
*p = 20;
p[2] = 100;
cout << "myvector contains:";
for(unsigned i=0; i<myvector.size(); ++i)
cout << ' ' << myvector[i];
cout << '\n';
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
結果是myvector contains: 10 20 0 100 0
上面的代碼來自http://www.cplusplus.com/reference/vector/vector/data/
小結:
vector的成員函數當中還有一個get_allocate,是獲取整塊內存類似,c語言當中memcpy函數。這裏先不介紹,後面補上。
vector在C++代碼當中使用十分廣泛,可以用來表示矩陣,表示向量,當做鄰接表來使用,十分方便。
加上與泛型算法和模板的搭配,使用功能也非常強大。
現在就總結這些,在編程和學習當中如果遇到有關vector的問題,還會在這裏繼續補充。如果寫出來的觀點和代碼有什麼問題,請各位看官不吝指正,我會及時更改。
to be continue~