【C++ STL设计思路与实际源代码挖掘①】vector(1)

前言:使用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个,每多加一个元素进行一次扩容所致。现用的这个扩容规则是不行的,在后续分析实际源代码时会对扩容算法进行对比。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值