Vector总览
vector是C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。缺点就是她的异常处理机制不完善,这点你在接下来的内容将会看见很多。
为了可以使用vector,必须在你的头文件中包含下面的代码:
#include <vector>
vector属于std命名域的,因此需要通过命名限定,如下完成你的代码:
using std::vector;
或者是使用using namespace std来限定名称空间。
Vector的定义方式:
vector<Elem> c vector <Elem> c1(c2) vector <Elem> c(n) vector <Elem> c(n, elem) vector <Elem> c(beg,end) c.~ vector <Elem>() |
创建一个空的vector。 复制一个vector。 创建一个vector,含有n个数据,数据均已缺省构造产生。 创建一个含有n个elem拷贝的vector。 创建一个以[beg;end)区间的vector。 销毁所有数据,释放内存。 |
由图表可以看出vector的初始化方式是多样的,可以指定大小,可以进行已知的复制等等。
如:
Vector<int> vec1(10);
创建含有10个元素的容器,相当于C语言中的 int vec[10];
Vector<float> vec2(10,1)
创建含有10个元素的容器,每个的初始值设置为1;
Vector<float> vec3(vec2);
创建一个容器vec3并把vec2复制给他,那么他们拥有一样的长度和数据。
Int a[10]={1,2,3,4,5,6,7,8,9,10};
Vector<int> vec4(a,a+9);
创建一个容器并把数组a和a+9区间内的元素赋值给他
Vector提供了很多其他的接口函数,下面列出他的接口函数
c.assign(beg,end) c.assign(n,elem) |
将[beg; end)区间中的数据赋值给c。 将n个elem的拷贝赋值给c。 |
c.at(idx) |
传回索引idx所指的数据,如果idx越界,抛出out_of_range。 |
c.back() |
传回最后一个数据,不检查这个数据是否存在。 |
c.begin() |
传回迭代器重的可一个数据。 |
c.capacity() |
返回容器中数据个数。 |
c.clear() |
移除容器中所有数据。 |
c.empty() |
判断容器是否为空。 |
c.end() |
指向迭代器中的最后一个数据地址。 |
c.erase(pos) c.erase(beg,end) |
删除pos位置的数据,传回下一个数据的位置。 删除[beg,end)区间的数据,传回下一个数据的位置。 |
c.front() |
传回地一个数据。 |
get_allocator |
使用构造函数返回一个拷贝。 |
c.insert(pos,elem) c.insert(pos,n,elem) c.insert(pos,beg,end) |
在pos位置插入一个elem拷贝,传回新数据位置。 在pos位置插入n个elem数据。无返回值。 在pos位置插入在[beg,end)区间的数据。无返回值。 |
c.max_size() |
返回容器中最大数据的数量。 |
c.pop_back() |
删除最后一个数据。 |
c.push_back(elem) |
在尾部加入一个数据。 |
c.rbegin() |
传回一个逆向队列的第一个数据。 |
c.rend() |
传回一个逆向队列的最后一个数据的下一个位置。 |
c.resize(num) |
重新指定队列的长度。 |
c.reserve() |
保留适当的容量。 |
c.size() |
返回容器中实际数据的个数。 |
c1.swap(c2) swap(c1,c2) |
将c1和c2元素互换。 同上操作。 |
注:以上函数均可在附带的C++标准参考文档中找到。
接下来我将对其中几个方面做一些说明:
1:vector的大小(size())和内存容量(capacity())问题。
Vector的size()是指的是当前元素数量,而capacity()是能够容纳的元素数量,如果超过这个值,那么所有的迭代器,引用,指针都会失效,而且重新分配内存是非常耗时的,为了避免重新分配内存带来的时间消耗,你可以在容器初始化的时候给他足够的容量。或者在中途使用reverse函数重置他的大小。
Vector<int> vec5(10);
For(int i=1;i<50;i++)
{
If(vec5.size()<=10)
Vec5.push_back(i);
Else
Vec5.reverse(2*vec5.size());
}
当容量不够的时候重置容量,增加容器的长度。但是这里涉及到一个问题,一旦重置了长度,vector原来的迭代器都会失效,因为重置长度会发生内存改变,原来迭代器所指的地方已经不是新容器的地址了,所以要注意重新定义迭代器再使用,别使用重置之前的。
2:关于vector容器取元素的问题。
c.front() c.back() c.at(idx)这三个函数。
由于C语言中的动态数组使用索引来访问元素,为与之兼容,vector也重载了这个操作符,允许像数组一样来操作容器,但是STL中提供了另外一个函数 at()函数也能达到相同的目的。
大家在使用C语言的时候是否出现过这种情况的:
Int a[10];
`````````````````
Cout<<a[10]<<endl;
即使我们非常小心,但是数组越界的错误也不能完全避免,这将导致程序崩溃
而at函数则附加了更好的越界检查,如果越界将会跑出越界的异常,程序不至于崩溃,这是我们推荐使用的函数。
vector<int> v;
v.reserve(10);
for(int i=0; i<7; i++)
v.push_back(i);
try
{
int iVal1 = v[7]; // 不抛出异常
int iVal2 = v.at(7); // 抛出下标越界异常。
}
catch(const exception& e)
{
cout << e.what();
}
对于front和back这两个函数,返回开始和最后元素,但是在使用他们的时候你务必确保容器不为空,因为他们没有异常检测的功能,为空将导致未定义错误。
3:vector的插入和删除元素,insert和erase函数
这里我首先说明一点,对于插入和删除操作,如果你的程序主要是利用这两个操作,那么你最好别选用vector容器,如果你非要利用vector来执行很多的插入删除操作,那么我相信你浪费的效率将是非常可观的
对于这两种函数,vector中本身定义了两个比较方便在尾部删除和插入的元素,它们是push_back()
和pop_back()函数,效率还是挺高的。
但是在容器中间删除和插入,他的效率就有点力不从心了,因为大多数vector的实现版本都是模拟数组,一旦删除一个元素,后面的所有元素都要向前移一位。
执行插入删除需要注意的是:必须指向一个合法的位置执行,不能在空容器中删除。
c.erase(pos) pos为迭代器;
c.erase(beg,end) 参数为一个区间。
从中可以看到对于vector,他并没有提供删除某一具体数值的方法,比如删除容器中等于5的元素。
这个是用就需要我们的算法来辅助了:
std::vector<int> coll;
std::vector<int>::iterator it;
it=find(coll.begin(),coll.end(),5);
if(it!=coll.end())
coll.erase(it);
也就是先找到元素的位置还进行删除。
对于insert函数:
c.insert(pos,elem)
c.insert(pos,n,elem)
c.insert(pos,beg,end)
这些函数其实都是很简单的,我下面要说的还是那个老问题,迭代器失效的问题。
先看这段代码;
根据代码的意思,可以大概的出来他想干什么,他的容器中由于数字是123456789之间夹了很多个5,他想把其中的5都删除掉,只留下123456789。While循环里面的意思很明显,一次循环,为5的就删,不为5的就跳过。运行结果如下:
什么原因?下面我来仔细的分析一下:
循环第一步的时候没问题,第二步遇见一个5,然后删除掉,也没问题,可到了第三步就出问题咯,刚删了一个5,对于数组型的容器,他的后面元素依次向前移动一位,end()这个迭代器已经失效,指向一个未知的位置,理解不过来的可以画图看一下。
其实如果要删除里面所有的5,可以用remove函数:
运行结果:
哎呀,为什么又是这样勒?
带着这个问题,我们通过查找remove函数可以知道,实际上他为了效率并没有进行删除操作,只是把需要删除的元素的后一位向前移一位(前提是后一位不是要删除的对象,如果是,则继续后推),把需要删除的覆盖掉。依次类推,直到移到最后。然后他返回逻辑上的新终点的迭代器。所以程序中运用的end()是最原来的末位置,知道了这些,我们改写一下代码:
运行结果:
从上面几个例子可以看出,迭代器失效的问题和函数的实作细节是很重要的问题,上面那个程序还好end()有一个位置,如果没有的话 恐怕也崩溃了吧。下面我们可以下一个定义:
凡是在vector中执行了插入和删除操作,那么他之后的那些迭代器都会失效,不能再继续使用。当然在尾部插入删除是安全的。
大家务必记得,关于迭代器失效是STL中的一个难点,以后还会陆续的遇到,希望注意。关于vector的实例我就不举了,书上很多。
3:vector的resize()和reserve()函数的区别这两个函数的具体区别。
可能很多初学者不明白这两个函数在实作中的区别,下面我将带你了解他们。
在参考文档里可以看到,resize()是重置容器的容量,reserve是保留一定的容量,换句话说,如果你在使用时候reserve的时候,只是空出那么多内存,但是只是预留的,暂时不属于容器可以访问的范围,而resize函数则是增加内存,并可以访问,举例说明:
.vector<int> myVec;
.myVec.reserve( 100 ); // 新元素还没有构造,
// 此时不能用[]访问元素
.for (int i = 0; i < 100; i++ )
..{
myVec.push_back( i ); //新元素这时才构造
.}
myVec.resize( 102 ); // 用元素的默认构造函数构造了两个新的元素
.myVec[100] = 1; //直接操作新元素
.myVec[101] = 2;
运行结果:
再看reserve;
结果:
这下应该够明白了吧,这就是我们在vector刚创建的时候就调用reserve保留一定的内存,防止他由于重新分配内存带来的效率消耗。