您的位置:首页 > 编程语言 > C语言/C++

C++ vector的使用方法

2016-08-11 23:01 274 查看

使用场合:

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;
}


初始化:

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;
}


赋值操作:

利用拷贝构造函数和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;
}


还可以利用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;
}


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;
}


插入元素:

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;
}


这里的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;
}


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;
}


也可以调用c.emplace(it,arg);其中it为指向一个数据的迭代器,arg为参数。

访问元素:

清空vector可以使用成员函数c.clear()

判断vector是否为空,可以使用成员函数empty(),如果为空返回true,否则返回false

vector输出最后一个元素的引用可以用back()成员函数,如果容器为空,则行为未定义

vector输出第一个元素的引用可以用front()成员函数,如果容器为空,则行为未定义

vector支持用下标访问元素,类似数组一样c
其中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;
}


删除元素

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;
}


注意,删除元素以后会使迭代器失效,所以循环删除元素这样写是错误的。

#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;
}


正确的删除方式

#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;
}


谨记,改变容器的长度的操作会使迭代器实效。

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;
}


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;
}


最后说一下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;
}


可能会觉得,那每次使用vector后都调用这个函数,不就很省内存了嘛。此言差矣,如果需要再向容器当中塞东西,那么就要重新给容器增加分配的内存,更浪费时间和效率。

多维vector的用法:

用二维向量做例子,所谓多维向量,就是一个向量一面存储的内容是一个向量。通常可以代替二维数组来使用。也可以使用向量的数组来表示。

二维向量的声明:

vector<vector<int>> vve;//在新版本的编译器当中这样写没问题
vector<vector<int> >vvve;//旧版本的编译器会把两个相连的尖括号当成流操作


初始化:

#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;
}


总之,和初始化一个普通的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;
}


遍历:

一般使用下标遍历、迭代器遍历

#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;
}


当然,也可以用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;
}


删除操作:

和一维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;
}


三维,或者更高维的也是如此。

容器的指针,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;
}


结果是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~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: