前言:使用vector,需要添加头文件
#include<vector>
vector算是本人用C++最常用的一个STL容器了。主要是替代new数组,实现未知容量数据的随机存取(并且不需要记得写delete)。
在C++中,需要在运行时确定数组容量可以这么操作:
int count;
cin>>count;
int *arr = new int[count];
//...
delete []arr;
因为count不是整型常量(包括整型常数、const整型常量和#define为整型的宏),不能作为int arr[X]里面的X,因此在运行时才确定容量的话,需要以new的形式申请内存空间。
但vector作为一个封装好的容器类,其与普通数组不一样的地方最明显的就是可以通过不断地push_back(object)来往容器里添加数据,而不需要事先关心需要填入多少数据(可以的话连cin>>count都省了),并且还支持随机存取。(没有随机存取的需要的话可以用链表结构)
比如说:
std::vector<int> v;
for(int i=0;i<1000;i++)v.push_back(i);
以上代码就可以生成一个存放整数0-999的数组容器。
那么问题来了,vector是怎么设计的呢?
如果我来写vector
设计vector需要考虑vector的作用。上面分析的vector作用有以下几点:
(1)可以在运行时不断地按需要添加数据,而不必事先确定有多少数据。(当然事先能确定也是适用的)
(2)重点使用随机存取功能(需要实现任意位置O(1)存取),和数组一样。
考虑以上两点,由于(2),底层必须是数组结构而不能是链表,虽然从push_back的角度看使用链表增删元素是很快,但不是这种容器的常用招。而在使用数组结构的基础上,怎么使得vector一定能够将所有推进去的元素容纳呢,就需要对vector进行一些扩容的底层操作。
首先,基本架构先给出来:
(vector是模板类,实例化时需要给出容器的数据类型。关于模板,可以参考这里)
template<typename T>
class vector{
private:
T *_array; //存放数据的数组(动态数组)
int currentSize; //当前vector的容量
public:
vector(); //构造函数,包含初始容量空间的申请与参数初始化
~vector(); //析构函数,包含_array数组释放
bool push_back(T x); //尾部推入一个元素
bool pop_back(); //尾部弹出一个元素
bool isEmpty(); //vector是否为空
int size(); //返回当前vector容量
T get(int index); //获得index下标的元素值
};
目前来看这些东西就基本够用了,然后尝试去分析各大函数的作用。
(1)vector():构造函数,包含初始容量空间的申请与参数初始化。初始容量定多少好呢,简单起见,可以定为10。然后什么都不存放的时候,当前容量为0。于是就有:
vector<T>::vector(){
_array = new int[10];
currentSize = 0;
}
但这里要思考一个问题,就是数组不一定就10个元素(现在动不动就海量数据,10个都不够看的,但定太多又容易造成内存浪费),所以这个容量不能一成不变,直接写10是不太好的,我们需要一个变量去控制这个容量,以后在关于什么扩容操作的时候会有用。在STL的vector里,这个参数叫capacity。
//class add
int capacity;
//-----------
template<typename T>
vector<T>::vector(){
capacity = 10;
_array = new T[capacity];
currentSize = 0;
}
(2)~vector():析构函数,基本就是用来释放_array动态申请的空间。
vector<T>::~vector(){
delete []_array;
}
但问题来了,vector可能会多次扩容,对于空间的申请与释放,可以独立出一个函数,让构造函数和析构函数去调用好了,还能减少代码出错风险。内存操作风险较大,应使用private权限避免实例对象的直接调用。
//class add
private:
bool memAllocate(); //申请内存空间
bool memRelease(); //释放内存空间
bool memExtend(); //扩容函数
//-----------
vector<T>::vector(){
capacity = 10;
_array = NULL;
memAllocate();
currentSize = 0;
}
vector<T>::~vector(){
memRelease();
}
新加的3个函数在这里先进行分析。
(3)memAllocate():申请一段连续内存空间,成功返回true,失败为false。这里是C++内存管理基操,简单实现即可。
template<typename T>
bool vector<T>::memAllocate(){
if(_array != NULL)return false; //当前数组必须为NULL才能操作(防止内存泄漏,虽然正常情况下不会触发。这里其实用异常抛出会更好)
_array = new T[capacity];
return _array != NULL;
}
(4)memRelease():释放_array的空间,成功返回true,失败为false。
template<typename T>
bool vector<T>::memRelease(){
if(_array == NULL)return false; //当前数组不为NULL才能操作。
delete []_array;
_array = NULL;
return true;
}
(5)memExtend():扩容。不同编译器定义的vector扩容规则有点不一样,我们也可以给自己的vector定义一个扩容规则,比如说,每push一个元素加一格空间(当然这种扩容算法效率非常的差,只是举个例子)。
template<typename T>
bool vector<T>::memExtend(){
T *_temp = _array; //用临时指针接管_array的内容
int exCapa = capacity; //保存原容量
_array = NULL;
capacity++; //指定扩容后vector的容量(自定义扩容规则就是在这行更改capacity的值)
bool success = memAllocate();//注意:在这里尝试申请空间
if(!success)
{
//申请新空间失败时
_array = _temp; //_array重新接管原内容
capacity = exCapa; //capacity值恢复原状
return false; //返回扩容失败
}
//申请成功
for(int i=0;i<currentSize;i++)_array[i]=_temp[i]; //复制原数组的内容过去
delete []_temp; //释放_temp的空间(注意这里不能用memRelease,注意函数的操作对象)
return true;
}
或者换个思路,直接拿临时指针尝试申请新空间,并且不需要过多地操作_array,风险更小。
template<typename T>
bool vector<T>::memExtend(){
int exCapa = capacity;
capacity++; //扩容算法
T *_temp = new T[capacity];
if(_temp == NULL){
//申请空间失败
capacity = exCapa;
return false;
}
for(int i=0;i<currentSize;i++)_temp[i]=_array[i];
if(!memRelease()){
//释放失败,释放temp的内容
delete []_temp;
return false;
}
_array = _temp; //接管temp的新内容
return true;
}
分析完3个内存管理函数,回到一开始的操作函数。
(6)push_back(T &x):在vector后面插入一个元素。由于插入元素可能导致当前元素数量超出容量,因此该操作是可能触发扩容的。触发扩容的时机可以在这里控制。比如说,当前空间满了(currentSize==capacity)时触发扩容,或者等于容量的一定百分比时扩容,但不能是size大于capa时才扩容,否则会有下标越位情况。
template<typename T>
bool vector<T>::push_back(T x){
if(currentSize==capacity) //这里定义扩容条件
{
//需要扩容
if(!memExtend()){
//扩容失败
return false; //不能插入元素
//可以看到这里只需要一个return即可,扩容成功失败时参数的变化都在函数里封装好,这里不需要关心
}
}
_array[currentSize++]=x;
return true;
}
(7)pop_back():弹出一个元素。非常简单的操作,不过要注意的是事先要检查容器中是否有元素,无物可弹时不能强弹。
template<typename T>
bool vector<T>::pop_back(){
if(currentSize<=0)return false; //容器中没有元素
currentSize--;
//这里只有1行,很精髓,只需要将size减少1即可,那个内存空间完全可以不动,反正push的时候会直接修改这个数据。在释放内存空间的时候也不需要currentSize这个变量。
return true;
}
(8)size():返回容器大小,即currentSize。基操。
int vector<T>::size(){return currentSize;}
(9)isEmpty():返回容器是否为空。(其实这个有替代语句)
bool vector<T>::isEmpty(){return currentSize==0;}
(10)get(int):获取index下标的元素。注意检查越界(因为有越界检查才使得popback的操作如此简单粗暴)
template<typename T>
T vector<T>::get(int index){
if(index < 0 || index >= currentSize){
//这里需要一个信号量或者throw表示数组越界错误,略
return 0;
}
return _array[i];
}
至此一个基本的vector已经写好,不过为了方便使用,原生vector给定义了一个数组下标的接口。这里也可以加一下,使用运算符重载的知识。关于运算符重载,请参考这里
//class add
T operator[](int index){
return get(index);
}
实际上原生[]是不检查下标越界的。即
T operater[](int index){
return _array[index];
}
综合一下上述代码,我们的vector可以写成:
//#define _DEBUG_VECTOR
template<typename T>
class vector{
private:
T *_array; //存放数据的数组(动态数组)
int currentSize; //当前vector的容量
int capacity; //vector占的内存空间
bool memAllocate(); //申请内存空间
bool memRelease(); //释放内存空间
bool memExtend(); //扩容函数
public:
vector(); //构造函数,包含初始容量空间的申请与参数初始化
~vector(); //析构函数,包含_array数组释放
bool push_back(T &x); //尾部推入一个元素
bool pop_back(); //尾部弹出一个元素
bool isEmpty(); //vector是否为空
int size(); //返回当前vector容量
T get(int index); //获得index下标的元素值
T operator[](int index);
};
template<typename T>
vector<T>::vector(){
capacity = 10;
_array = NULL;
memAllocate();
currentSize = 0;
#ifdef _DEBUG_VECTOR
std::cerr<<"vector initalize complete"<<std::endl;
#endif
}
template<typename T>
vector<T>::~vector(){
memRelease();
#ifdef _DEBUG_VECTOR
std::cerr<<"vector mem release complete"<<std::endl;
#endif
}
template<typename T>
bool vector<T>::memAllocate(){
if(_array != NULL){
#ifdef _DEBUG_VECTOR
std::cerr<<"ERROR at MemAllocate: _array isn't NULL"<<std::endl;
#endif
return false; //当前数组必须为NULL才能操作(防止内存泄漏,虽然正常情况下不会触发。这里其实用异常抛出会更好)
}
_array = new T[capacity];
return _array != NULL;
}
template<typename T>
bool vector<T>::memRelease(){
if(_array == NULL){
#ifdef _DEBUG_VECTOR
std::cerr<<"ERROR at MemRelease: _array is NULL"<<std::endl;
#endif
return false; //当前数组不为NULL才能操作。
}
delete []_array;
_array = NULL;
return true;
}
template<typename T>
bool vector<T>::memExtend(){
T *_temp = _array; //用临时指针接管_array的内容
int exCapa = capacity; //保存原容量
_array = NULL;
capacity++; //指定扩容后vector的容量(自定义扩容规则就是在这行更改capacity的值)
bool success = memAllocate();//注意:在这里尝试申请空间
if(!success)
{
//申请新空间失败时
#ifdef _DEBUG_VECTOR
std::cerr<<"ERROR at MemExtend: mem allocate failed"<<std::endl;
#endif
_array = _temp; //_array重新接管原内容
capacity = exCapa; //capacity值恢复原状
return false; //返回扩容失败
}
//申请成功
for(int i=0;i<currentSize;i++)_array[i]=_temp[i]; //复制原数组的内容过去
delete []_temp; //释放_temp的空间(注意这里不能用memRelease,注意函数的操作对象)
return true;
}
template<typename T>
bool vector<T>::push_back(T x){
if(currentSize==capacity) //这里定义扩容条件
{
//需要扩容
std::cerr<<"WARNING: vector memory extend needed"<<std::endl; //输出扩容提示
if(!memExtend()){
//扩容失败
#ifdef _DEBUG_VECTOR
std::cerr<<"ERROR at pushback: mem extend failed"<<std::endl;
#endif
return false; //不能插入元素
//可以看到这里只需要一个return即可,扩容成功失败时参数的变化都在函数里封装好,这里不需要关心
}
}
_array[currentSize++]=x;
return true;
}
template<typename T>
bool vector<T>::pop_back(){
if(currentSize<=0)return false; //容器中没有元素
currentSize--;
//这里只有1行,很精髓,只需要将size减少1即可,那个内存空间完全可以不动,反正push的时候会直接修改这个数据。在释放内存空间的时候也不需要currentSize这个变量。
return true;
}
template<typename T>
int vector<T>::size(){return currentSize;}
template<typename T>
bool vector<T>::isEmpty(){return currentSize==0;}
template<typename T>
T vector<T>::get(int index){
if(index < 0 || index >= currentSize){
//这里需要一个信号量或者throw表示数组越界错误,略
std::cerr<<"Error index:"<<index<<" by vector size "<<currentSize<<std::endl;
return 0;
}
return _array[index];
}
template<typename T>
T vector<T>::operator[](int index){
return get(index);
}
来试用一下
主函数写成:
int main(){
vector<int> v;
for(int i=0;i<20;i++)v.push_back(i+10);
std::cout<<"v.size()="<<v.size()<<std::endl;//第1次输出:推入5个元素后的size
std::cout<<v[0]<<std::endl;
std::cout<<v[1]<<std::endl;
std::cout<<v[20]<<std::endl; //越界测试(错误提示后仍会输出一个0)
v.pop_back();
v.pop_back();
std::cout<<"v.size()="<<v.size()<<std::endl;
return 0;
}
测试结果:
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
WARNING: vector memory extend needed
v.size()=20
10
11
Error index:20 by vector size 20
0
v.size()=18
基本上符合预期,但是可以看到触发了10次扩容,这是因为我们推入了20个元素而初始空间只有10个,每多加一个元素进行一次扩容所致。现用的这个扩容规则是不行的,在后续分析实际源代码时会对扩容算法进行对比。