vector的实现

目录

1.vector的底层

2.vector构造函数的实现

①构造函数

②拷贝构造

3.访问函数实现

3.1迭代器iterator

3.2下标+[]访问

4.析构函数和计算size、capacity、swap简单函数的实现

①析构函数: 

②计算size:

③计算capacity:

④swap函数

5. reserve(提前开空间函数实现)

6.尾插尾删函数

7.insert指定位置插入实现

8.设计程序验证

源代码(vector.h)


1.vector的底层

我们在之前的文章顺序表的实现中了解到:

其底层存在一个指向动态开辟数组的指针、一个表示元素个数的size和一个表示顺序表容量大小的capacity。

而vector使用了一个start、一个finish和一个end_of_storage,虽然二者的表示形式不同,实则都有相同的道理,如图:

现在我们就依据此来实现一下vector。

2.vector构造函数的实现

①构造函数

class vector
{
public:
    vector()
		:_start(nullptr)
		, _finish(nullptr)
		, _endofstorage(nullptr)
		{}
private:
		//全缺省为空
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
}

②拷贝构造

//①传统写法
//vector(const vector<T>& v)
	//{
	//	_start = new T[v.capacity()];//先开空间
	//	memcpy(_start, v._start, v.size() * sizeof(T));
	//	_finish = _start + v.size();
	//	_endofstorage = _start + v.capacity();
	//}
//②现代写法
vector(const vector<T>& v)
	{
		reserve(v.capacity());//首先开一个与旧空间一样的新空间
		for (const auto& e : v)//添加引用&,防止深拷贝
		{
			push_back(e);//复用push_back函数
		}
	}

③析构函数 

~vector()
	{
		if (_start)
		{
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}
	}

3.访问函数实现

3.1迭代器iterator

①普通迭代器可读可写:

//普通迭代器 可读可写
typedef T* iterator;
iterator begin()
	{
		return _start;

	}
	iterator end()
	{
		return _finish;
	}

②const迭代器可读不可写:

//const迭代器 可读不可写
typedef const T* const_iterator;
const_iterator begin() const
	{
		return _start;
	}
	const_iterator end() const
	{
		return _finish;
	}

3.2下标+[]访问

①普通函数,可读可写:

//①可读可写
T& operator[](size_t pos)
	{
		assert(pos < size());//检查是否越界

		return _start[pos];//返回pos位置字符的引用,既可读又可写
	}
		

②const函数,可读不可写:

//②可读不可写
const T& operator[](size_t pos) const 
	{
		assert(pos < size());//检查是否越界

		return _start[pos];//返回pos位置字符的引用,既可读又可写
	}

4.析构函数和计算size、capacity、swap简单函数的实现

①析构函数: 

//析构函数
~vector()
	{
		if (_start)
		{
			delete[] _start;
			_start = _finish = _endofstorage = nullptr;
		}
	}

②计算size:

size_t size() const
	{
		return _finish - _start;
	}

③计算capacity:

size_t capacity() const
	{
		return _endofstorage - _start;
	}

④swap函数

void swap(vector<T>& v)
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_endofstorage, v._endofstorage);
	}

有了swap函数,我们就可以复用swap函数实现赋值构造函数:

//赋值
vector<T>& operator=(vector<T> v)
//注意这里不能添加&引用,如v3=v1
//无&引用表示v是v1的临时拷贝,v3再拷贝v,出了作用域v就会被销毁,不会改变v1
//而加上&引用则直接表示v1与v3交换了,即可以认为这里实现了swap函数的功能
	{
		swap(v);//复用swap函数
		return *this; 
	}

5. reserve(提前开空间函数实现)

void reserve(size_t n)
	{
		if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
		{
			T* tmp = new T[n];//开辟新空间
			if (_start)//判断原vector是否为空
			{
				//如果原vector不为空,就将从_start位置开始的n*sizeof(T)个数据拷贝到新空间tmp里
				memcpy(tmp, _start, n * sizeof(T));
				delete[] _start;//释放旧空间
		    }
			//再指向新空间
			_start = tmp;
			_finish = _start + size();
			_endofstorage = _start + capacity();
	}

这样写程序出现了bug:

这是因为

因为size()=_finish - _start;
 所以_finish=_start+_finish-_start=_finish
可是这里的_start已经指向了新空间,而_finish指向的还是旧空间
一个空间的末尾 - 另一个空间的开头 ==== 错误!
_endofstorage = _start + capacity();
同样的道理,capacity()还是旧空间,_start是新空间 也= 错误!
所以这里正确的做法应该是_endofstorage = _start + n;

正确代码:

void reserve(size_t n)
	{
		if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
		{
			size_t old = size();//在扩容之前将size()先保存一下
			T* tmp = new T[n];//开辟新空间
			if (_start)//判断原vector是否为空
			{
				//如果原vector不为空,就将从_start位置开始的old*sizeof(T)个数据拷贝到新空间tmp里
				memcpy(tmp, _start, old * sizeof(T));
				delete[] _start;//释放旧空间
			}
				//再指向新空间
				_start = tmp;
				_finish = _start + old;
				_endofstorage = _start + n;
		}
	}

完成了reserve函数,我们就可以复用reserve函数实现拷贝构造的现代写法:

//②现代写法
vector(const vector<T>& v)
	{
		reserve(v.capacity());//首先开一个与旧空间一样的新空间
		for (const auto& e : v)//添加引用&,防止深拷贝
		{
			push_back(e);//复用push_back函数
		}
	}

6.resize(提前开空间并初始化函数实现) 

使用resize开空间时,我们需要分3种情况:

①要开辟的空间小于原有的有效元素数,即:n<size(),那么这时我们就需要删除数据;

②要开辟的空间大于原有的有效元素数而小于原有的容量,即:size()<n<capacity(),那么这时我们就需要插入数据;

③要开辟的空间大于原有的容量,即:n>capacity(),那么这时我们就需要扩容+插入数据。

void resize(size_t n, T val = T())
    {
		if (n > size())//判断是否需要插入数据
		{
			reserve(n);//无关扩容,先调用reserve扩容
			while (_finish < _start + n)
			{
				*_finish = val;
				++_finish;
			}
		}
		else//删除数据
			{
            	_finish = _start + n;
			}
	}

对于resize()参数的说明:

val不传值时,整个resize函数处于半缺省状态,val默认传缺省值;
如果T是自定义类型,那么就去调用自定义类型的构造函数;
如果T是内置类型,为了兼容C++的模板,int的缺省值就是0,double的缺省值就是0.0,指针的缺省值就是空指针,以此类推。

7.erase函数 

//erase函数
void erase(iterator pos)
	{
		assert(pos >= _start && pos < _finish);

		iterator it = pos + 1;
		while (it < _finish)
		{
			*(it - 1) = *it;
			++it;
		}
		_finish--;
	}

8.尾插尾删函数

        //尾插函数
		void push_back(const T& x)
		{
			if (_finish == _endofstorage)//判断vector有没有满
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;//如果满了就扩容
				reserve(newcapacity);
			}
			//扩容之后赋值
			*_finish = x;
			++_finish; 
		}

		//尾删函数
		void pop_back()
		{
			assert(size() > 0);
			--_finish;
		}

9.insert指定位置插入实现

void insert(iterator pos, const T& x)//在pos位置之前插入x
	{
		assert(pos <= _finish && pos >= _start);//检查pos是否合法

		//检查vector是否充满
		if (_finish == _endofstorage)
		{
			reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
		}
		//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
		memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
		*pos = x;//挪动之后再把x放到pos位置
		++_finish;
	}

这段代码在实际运行时有时会出现错误,这是为什么呢?

原因是:

reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
观察reserve函数:不扩容还好,当发生扩容时,reserve会销毁旧空间
此时_start指向新空间,pos仍然指向旧空间,pos在扩容之后失效了
因此这里的解决办法就是计算偏移量,更新一下pos的值

正确代码:

void insert(iterator pos, const T& x)//在pos位置之前插入x
	{
		assert(pos <= _finish && pos >= _start);//检查pos是否合法

		//检查vector是否充满
		if (_finish == _endofstorage)
		{
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
			pos = _start + len;
		}
		//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
		memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
		*pos = x;//挪动之后再把x放到pos位置
		++_finish;
	}

10.设计程序验证

void test_vector()
	{
		vector <int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		//迭代器访问
		vector <int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		//范围for访问
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
		//下标+[]访问
		for (size_t i = 0; i < v1.size(); i++)
		{
			cout << v1[i] << " ";
		}
		cout << endl;
	}

源代码(vector.h)

#pragma once

#include <iostream>
#include <assert.h>

using namespace std;


namespace xxk
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;//普通迭代器 可读可写
		typedef const T* const_iterator;//const迭代器 可读不可写

		//构造函数
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

		//拷贝构造
		//①传统写法
		//vector(const vector<T>& v)
		//{
		//	_start = new T[v.capacity()];//先开空间
		//	memcpy(_start, v._start, v.size() * sizeof(T));
		//	_finish = _start + v.size();
		//	_endofstorage = _start + v.capacity();
		//}
		//②现代写法
		vector(const vector<T>& v)
		{
			reserve(v.capacity());//首先开一个与旧空间一样的新空间
			for (const auto& e : v)//添加引用&,防止深拷贝
			{
				push_back(e);//复用push_back函数
			}
		}

		//赋值
		vector<T>& operator=(vector<T> v)
		//注意这里不能添加&引用,如v3=v1
		//无&引用表示v是v1的临时拷贝,v3再拷贝v,出了作用域v就会被销毁,不会改变v1
		//而加上&引用则直接表示v1与v3交换了,即可以认为这里实现了swap函数的功能
		{
			swap(v);//复用swap函数
			return *this; 
		}

		//析构函数
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

		//迭代器begin()和end()实现
		//普通迭代器 可读可写
		iterator begin()
		{
			return _start;

		}
		iterator end()
		{
			return _finish;
		}
		//const迭代器 可读不可写
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}


		//size capacity函数
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _endofstorage - _start;
		}

		//下标+[]访问函数的实现
		//①可读可写
		T& operator[](size_t pos)
		{
			assert(pos < size());//检查是否越界

			return _start[pos];//返回pos位置字符的引用,既可读又可写
		}
		//②可读不可写
		const T& operator[](size_t pos) const 
		{
			assert(pos < size());//检查是否越界

			return _start[pos];//返回pos位置字符的引用,既可读又可写
		}

		//reserve扩容函数
		//error:
		//void reserve(size_t n)
		//{
		//	if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
		//	{
		//		T* tmp = new T[n];//开辟新空间
		//		if (_start)//判断原vector是否为空
		//		{
		如果原vector不为空,就将从_start位置开始的n*sizeof(T)个数据拷贝到新空间tmp里
		//			memcpy(tmp, _start, n * sizeof(T));
		//			delete[] _start;//释放旧空间
		//		}
		//		//再指向新空间
		//		_start = tmp;
		//		_finish = _start + size();
		//		//因为size()=_finish - _start;
		//		//所以_finish=_start+_finish-_start=_finish
		//		//可是这里的_start已经指向了新空间,而_finish指向的还是旧空间
		//		//一个空间的末尾 - 另一个空间的开头 ==== 错误!
		//		_endofstorage = _start + capacity();
		//		//同样的道理,capacity()还是旧空间,_start是新空间 也= 错误!
		//		//所以这里正确的做法应该是_endofstorage = _start + n;
		//	}
		//}

		void reserve(size_t n)
		{
			if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
			{
				size_t old = size();//在扩容之前将size()先保存一下
				T* tmp = new T[n];//开辟新空间
				if (_start)//判断原vector是否为空
				{
			//如果原vector不为空,就将从_start位置开始的old*sizeof(T)个数据拷贝到新空间tmp里
					memcpy(tmp, _start, old * sizeof(T));
					delete[] _start;//释放旧空间
				}
					//再指向新空间
					_start = tmp;
					_finish = _start + old;
					_endofstorage = _start + n;
			}
		}

		//resize函数
		void resize(size_t n, T val = T())
			//val不传值时,整个resize函数处于半缺省状态,val默认传缺省值。
			//如果T是自定义类型,那么就去调用自定义类型的构造函数;
			//如果T是内置类型,为了兼容C++的模板,int的缺省值就是0,double的缺省值就是0.0,指针的缺省值就是空指针,以此类推
		{
			if (n > size())//判断是否需要插入数据
			{
				reserve(n);//无关扩容,先调用reserve扩容
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
			else//删除数据
			{
				_finish = _start + n;
			}
		}

        //erase函数
		void erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);

			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it - 1) = *it;
				++it;
			}
			_finish--;
		}

		//尾插函数
		void push_back(const T& x)
		{
			if (_finish == _endofstorage)//判断vector有没有满
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;//如果满了就扩容
				reserve(newcapacity);
			}
			//扩容之后赋值
			*_finish = x;
			++_finish; 
		}

		//尾删函数
		void pop_back()
		{
			assert(size() > 0);
			--_finish;
		}

		//指定位置插入(insert)函数
		//error:
		//void insert(iterator pos, const T& x)//在pos位置之前插入x
		//{
		//	assert(pos <= _finish && pos >= _start);//检查pos是否合法

		//	//检查vector是否充满
		//	if (_finish == _endofstorage)
		//	{
				//reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
				观察reserve函数:不扩容还好,当发生扩容时,reserve会销毁旧空间
				此时_start指向新空间,pos仍然指向旧空间,pos在扩容之后失效了
				因此这里的解决办法就是计算偏移量,更新一下pos的值
		//	}
		//	//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
		//	memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
		//	*pos = x;//挪动之后再把x放到pos位置
		//	++_finish;
		//}

		void insert(iterator pos, const T& x)//在pos位置之前插入x
		{
			assert(pos <= _finish && pos >= _start);//检查pos是否合法

			//检查vector是否充满
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
				pos = _start + len;
			}
			//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
			memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
			*pos = x;//挪动之后再把x放到pos位置
			++_finish;
		}

		//swap函数
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

	private:
		//全缺省为空
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
	};
	
}
  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值