C++模板三:STL向量容器vector简单实现、容器空间适配器allocator简单实现

一、STL向量容器vector简单实现

首先,我们了解一下什么是容器呢?
容器:在C++中,容器被定义为:在数据存储上,有一种对象类型,它可以持有其他对象或指向其他对象的指针,这种对象类型就叫做容器。 简单理解,即容器就是保存其他对象的对象。而且,这种“对象”还有处理“其他对象”的方法。 我们平常喝水的的杯子就是一个容器,里面装了水。栈呀,队列定义的对象都是容器,里面存储了一堆数据,这就是容器。

       容器是随着面向对象语言的诞生而提出的,它甚至被认为是早期面向对象语言的基础。现在几乎所有面向对象语言中都伴随着一个容器,C++中则是标准模版库(STL)。C++采用基于模版的方式处理容器,STL中的容器提供了多种数据结构。

vector容器: 是一个线性顺序结构。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。
它的特点是:
1.可以直接访问任何元素。
2.线性顺序结构。可以指定一块连续的空间,也可以不预先指定大小,空间可自动扩展,也可以像数组一样被操作,即支持[ ]操作符和vector.at(),因此可看做动态数组,通常体现在追加数据push_back()和删除末尾数据pop_back()。
3.当分配空间不够时,vector会申请一块更大的内存块(以2的倍数增长),然后将原来的数据拷贝到新内存块中并将原内存块中的对象销毁,最后释放原来的内存空间。因此如果vector保存的数据量很大时会很消耗性能,因此在预先知道它大小时性能最优。
4.节省空间。因为它是连续存储,在存储数据的区域是没有浪费的,但实际上大多数时候是存不满的,因此实际上未存储的区域是浪费的。
5.在内部进行插入和删除的操作效率低。由于vector内部按顺序表结构设计,因此这样的操作基本上是被禁止的,它被设计成只能在后端进行追加和删除操作。

我们利用类模板实现C++STL中的顺序容器vector:
我么这里的私有成员变量设置如下:
在这里插入图片描述

template <typename T>
class vector//向量容器
{
public:
	vector(int size = 10)//构造
	{
		_first = new T[size];
		_last = _first;
		_end = _first + size;
	}
	~vector()//析构
	{
		delete[]_first;
		_first = _last = _end = nullptr;
	}
	vector(const vector<T> &rhs)//拷贝构造
	{
		int size = rhs._end - rhs._first;//空间大小
		_first = new T[size];
		int len = rhs._last - rhs._first;//有效元素
		for (int i=0; i<len; ++i)
		{
			_first[i] = rhs._first[i];
		}
		_last = _first + len;
		_end = _first + size;
	}
	vector<T>& operator=(const vector<T> &rhs)//赋值运算符重载
	{
		if (this == &rhs)
		{
			return *this;
		}

		delete[]_first;

		int size = rhs._end - rhs._first;
		_first = new T[size];
		int len = rhs._last - rhs._first;
		for (int i=0; i<len; i++)
		{
			_first[i] = rhs._fisrt[i];
		}
		_last = _first + len;
		_end = _fitst = size;
		return *this;
	}
	void push_back(const T &val)//尾插
	{
		if (full())
		{
			expand();
		}
		*_last++ = val;
	}
	void pop_back()//尾删
	{
		if (empty()) return;
		--_last;
	}
	T back()const//返回容器末尾元素值
	{
		return *(_last - 1);
	}
	bool full()const
	{
		return _last == _end;
	}
	bool empty()const
	{
		return _first == _last;
	}
	int size()const//返回容器中元素个数
	{
		return _last - _first;
	}
private:
	T *_first;//起始数组位置
	T *_last;//指向最后一个有效元素后继位置
	T *_end;//指向数组空间的后继位置

	void expand()//扩容
	{
		int size = _end - _first;
		T *ptmp = new T[2*size];
		for (int i=0; i<size; ++i)
		{
			ptmp[i] = _first[i];
		}
		delete[]_first;
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2*size;
	}
};

int main()
{
	vector<int> vec;
	for (int i=0; i<20; ++i)
	{
		vec.push_back(rand() % 100);
	}

	while (!vec.empty())
	{
		cout << vec.back() << " ";
		vec.pop_back();
	}
	cout << endl;
	
	return 0;
}

测试一下:测试成功。
在这里插入图片描述
这里只是简单实现以下vector容器,但是还少了空间配置器allocator。
库中定义的vector:

template<class _Ty,
	class _Alloc = allocator<_Ty> >
	class vector
		: public _Vector_alloc<!is_empty<_Alloc>::value,
			_Vec_base_types<_Ty, _Alloc> >

二、容器空间适配器allocator简单实现

容器为什么需要空间配置器,不用空间配置器会怎么样?
出现问题1:
我们还是上面的容器,用Test来实例化:

class Test
{
public:
	Test(){cout << "Test()" << endl;}
	~Test(){cout << "~Test" << endl;}
};

对其进行实例化:

vector<Test> vec;

此时什么都没有做,这是一个空容器,但是却构造了10个Test对象,又析构了10次。因为构造时用了new,它不仅仅会开辟空间,还会去构造对象,因此构造了10个对象,使用十分不合理。
在这里插入图片描述
问题分析:
①定义容器对象时,底层只是进行空间开辟,而不能去构造对象。但是如果使用new的话,它会同时完成这两件事。
②析构时用了delete,将_first指针指向的数组每一个元素都当作有效的test对象析构了一遍。数组可能会很长,但是里面有效的元素可能只有几个,我们析构时只析构有效的元素,再将整个数组内存释放。

出现问题2:
执行如下代码:

int main()
{
	Test t1, t2, t3;
	cout << "-------------------------" << endl;
	vector<Test> vec;
	vec.push_back(t1);
	vec.push_back(t2);
	vec.push_back(t3);
	cout << "-------------------------" << endl;
	vec.pop_back();
	cout << "-------------------------" << endl;
	
	return 0;
}

但是执行结果如下:出现了问题
在这里插入图片描述
问题分析:
①按道理来说,容器只开辟了内存,没有在内存上构造过对象,当push_back时,t1,t2,t3在容器内存够一个位置上构造了三个新对象,值与t1,t2,t3一样。但此时生成容器时,底层使用了new,每一个位置其实已经放了test对象,push_back时,相当于给已经存在的test对象赋值,就出现了问题。
②而pop_back时,可能占用外部资源,但我们实现的只是将_last–,pop_back在删除时,并没有将对象析构掉。_last–只是这个对象访问不到了,但是该对象外部可能还占用了资源,并没有做处理。下次再添加对象时,test原来外部堆内存就找不到了,指针被覆盖。但也不能使用delete析构,delete不仅仅调用析构,还做了free操作,我们应该只析构,不释放数组堆内存。

       解决思路:①需要我们把内存开辟与对象构造分开处理了。②析构容器有有效的元素,然后释放_first指针指向的堆内存。③只需要析构对象,将对象的析构和内存分离开。 此时就需要我们的容器空间配置器了。

容器的空间配置器allocator:空间配置器的核心功能就是把对象的内存开辟和对象构造的过程分解开,对象析构和内存释放的过程分解开。 容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间适配器来实现。
我们来采用空间配置器将vector实现:

//容器的空间配置器
template <typename T>
struct Allocator
{
	T* allocate(size_t size)//只负责内存开辟
	{
		return (T*)malloc(sizeof(T) * size);
	}
	void deallocate(void *p)//只负责内存释放
	{
		free(p);
	}
	void construct(T *p, const T &val)//已经开辟好的内存上,负责对象构造
	{
		new (p) T(val);//定位new,指定内存上构造val,T(val)拷贝构造
	}
	void destroy(T *p)//只负责对象析构
	{
		p->~T();//~T()代表了T类型的析构函数
	}
};

template <typename T, typename Alloc = Allocator<T>>
class vector//向量容器
{
public:
	vector(int size = 10)//构造
	{
		//_first = new T[size];
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	}
	~vector()//析构
	{
		//delete[]_first;
		for (T *p=_first; p!=_last; ++p)
		{
			_allocator.destroy(p);//把_first指针指向的数组的有效元素析构
		}
		_allocator.deallocate(_first);//释放堆上的数组内存
		_first = _last = _end = nullptr;
	}
	vector(const vector<T> &rhs)//拷贝构造
	{
		int size = rhs._end - rhs._first;//空间大小
		//_first = new T[size];
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;//有效元素
		for (int i=0; i<len; ++i)
		{
			//_first[i] = rhs._first[i];
			_allocator.construct(_first+i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
	}
	vector<T>& operator=(const vector<T> &rhs)//赋值运算符重载
	{
		if (this == &rhs)
		{
			return *this;
		}

		//delete[]_first;
		for (T *p=_first; p!=_last; ++p)
		{
			_allocator.destory(p);//把_first指针指向的数组的有效元素析构
		}
		_allocator.deallocate(_first);//释放堆上的数组内存

		int size = rhs._end - rhs._first;//空间大小
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;//有效元素
		for (int i=0; i<len; ++i)
		{
			_allocator.construct(_first+i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
		return *this;
	}
	void push_back(const T &val)//尾插
	{
		if (full())
		{
			expand();
		}
		//*_last++ = val;
		_allocator.construct(_last, val);//_last指针指向的内存构造一个值为val的对象
		_last++;
	}
	void pop_back()//尾删
	{
		if (empty()) return;
		//--_last;
		//不仅要把_last指针--,还需要析构删除的元素
		--_last;
		_allocator.destroy(_last);
	}
	T back()const//返回容器末尾元素值
	{
		return *(_last - 1);
	}
	bool full()const
	{
		return _last == _end;
	}
	bool empty()const
	{
		return _first == _last;
	}
	int size()const//返回容器中元素个数
	{
		return _last - _first;
	}
private:
	T *_first;//起始数组位置
	T *_last;//指向最后一个有效元素后继位置
	T *_end;//指向数组空间的后继位置
	Alloc _allocator;//定义容器的空间配置器对象

	void expand()//扩容
	{
		int size = _end - _first;
		//T *ptmp = new T[2*size];
		T *ptmp = _allocator.allocate(2*size);
		for (int i=0; i<size; ++i)
		{
			_allocator.construct(ptmp+i, _first[i]);
			//ptmp[i] = _first[i];
		}
		//delete[]_first;
		for (T *p=_first; p!=_last; ++p)
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2*size;
	}
};

class Test
{
public:
	Test(){cout << "Test()" << endl;}
	~Test(){cout << "~Test" << endl;}
	Test(const Test&){cout << "Test(const Test&)" << endl;}
};

int main()
{
	Test t1, t2, t3;
	cout << "-------------------------" << endl;
	vector<Test> vec;
	vec.push_back(t1);
	vec.push_back(t2);
	vec.push_back(t3);
	cout << "-------------------------" << endl;
	vec.pop_back();//只需要析构
	cout << "-------------------------" << endl;

	return 0;
}

执行执行:成功执行。
在这里插入图片描述

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值