vector模拟实现

前言

上期我们介绍了vector的使用,对vector已经有了一个基本的认识!本期我们在上面的基础上在来拔高一层,来探索一下vector的底层实现!

本期内容介绍

vector 常用接口的模拟实现

基本框架的搭建

OK,我们在开始模拟前先得有个基本的架子吧!比如最基本的成员变量是哪些!我们先来看看库里面的源码是如何做的!(这里采用的SGI的源码,有需要的伙伴直接私信我~!)

这里他有三个iterator的三个成员变量,分别表示元素的开始位置,有效元素的结束位置,和有效容量的下一个位置!

那什么是iterator呢?

我们可以看到,iterator是val_type*的重命名,而val_type是T的重命名!也就是说,iterator是T*。所以我们可以设计出如下的基本结构!

容量相关

size

这里实现size很简单,因为vector是物理空间连续的,所以我们可以直接使用_finish - _start来计算他们之间的元素个数!也就是元素的有效个数!

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

capacity

和size同理,直接使用_endofstorage - _start即可!

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

empty

直接复用上面的size,判断size是否 == 0

bool empty() const
{
	return size() == 0;
}

reserve

当n > capacity时,开心空间大小为n,将旧空间的数据拷贝到新空间!否则不作处理!

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];//开新空间
		memcpy(tmp, _start, sizeof(T) * size());//将旧空间的内容拷贝过去

		delete[] _start;//释放掉旧空间
		_start = tmp;//让_start指向新空间
		_finish = tmp + size();//修改新的_finish
		_endofstorage = _start + n;//修改新的_endofstorage
	}
}

但是这种方式是有问题的!

这里是把心的数据拷贝到新空间,然后释放旧空间,在让_strat指向新空间!_finish = _start + size(),这里就有问题!在你把新空间给_start时,原来的旧空间已经被释放了!此时的size计算时,_start指向新空间,而_finish指向旧空间!导致最后的奔溃!

解决这个问题的办法其实很简单!就是你在memcpy之前记录一下size即可!

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		size_t len = size();//提起记录一下size
		memcpy(tmp, _start, sizeof(T) * len);

		delete[] _start;
		_start = tmp;
		_finish = tmp + len;
		_endofstorage = _start + n;
	}
}

但这样写也会有一个浅拷贝的问题!如果你_start的置空空间里面原来存的是字符串或数组等就会形成浅拷贝!在析构时就会析构两次!

但是拷贝完了之后,会delete[]  _start,此时会把原来的那块空间的资源释放了!等再去访问时就已经是非法的访问了,而且等结束了再去析构就相当于析构两次了!为了解决这个问题我们不用memcpy,而直接用循环赋值形成深拷贝即可!

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		size_t len = size();//提起记录一下size
		//memcpy(tmp, _start, sizeof(T) * len);
		for (size_t i = 0; i < len; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;

		_start = tmp;
		_finish = tmp + len;
		_endofstorage = tmp + n;
	}
}

这样写就没问题了!

resize

resize还是分为三种情况:

n > capacity(), 扩容 + 尾插

size() < n < capacity(), 尾插

n < size(), 删除元素保留前n个

这里的实现思路也很简单,只要是n < size,我们就把_finish设置为_start + n就是前n个即可, 否则其他两种情况先上来把容量扩到n在从size开始插入val到n

void resize(size_t n, const T& val = T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);
		for (size_t i = size(); i < n; i++)
		{
			_start[i] = val;
		}

		_finish = _start + n;
	}
}

构造和析构

默认构造

这里vector的成员变量都是内置类型!所以可以不写,编译器会自动生成默认的构造!但是内置类型不做处理,我们可以在声明时给一个缺省值!只在内里面写一个空构造,函数体什么也不用写即可!

namespace cp
{
    template<class T>
    class vector
    {
    public:
        typedef T* iterator;

        vector()
        {}
     
     private:
        iterator _start = nullptr;
        iterator _finish = nullptr;
        iterator _endofstorage = nullptr;
    }
}

当然你也可以显示的写出来自己去初始化!

vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{}

析构

先把_start指向的那块空间给释放掉,然后将三个成员变量置空即可!

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

拷贝构造

开和v一样的空间,然后将v的数据一个个遍历未并尾插即可!

注意:由于我们没有选择缺省值的那种,所以这里还是要对三个成员进行初始化列表初始化的!

vector(const vector<T>& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{
	reserve(v.capacity());
	for (auto& e : v)
	{
		push_back(e);
	}
}

用n个val构造

这里可以直接调用resize,也可以直接自己手搓一个!

vector(size_t n, const T& val = T())
{
	resize(n, val);
}
vector(size_t n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

这样写会与一段迭代器区间构造(就在下面)的冲突!如下:


我们定位到错误的那一行发现根本就没有走到,用n个val初始化的构造里面!而是走到了迭代器区间构造的这里了!为什么了?原因是int和int去匹配size_t和int存在转换,但是这里的迭代器区间的话不需要转换直接搞就可以了!所以这里就选择了迭代器区间!我们看看库里面是如何解决的?

源码里面的处理方式是重载了一个int版本的~!所以我么这里也需要重载一个int版本的即可!

vector(int n, const T& val = T())
{
	reserve(n);
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

用一段迭代器区间构造

我们可以使用一个vector的衣服分区初始化另一个vector!初始化和被初始化的类型得相同或可以支持隐式类型转化!

实现思路:当左端的迭代器不等于右端的迭代器时取当前左端迭代器的值尾插

template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

以数组的形式初始化

这是C++11提供的,我们在使用时用过但没有介绍!这里先介绍一下!

后面的空间配置器先别管!他就只有一个initializer_list<value_type> 类型的il对象!我们看看initializer_list<value_type> 是啥?

他是一个类模板,表示一组相同类行元素集合!实例化时将T转换为相应的类型的类,也就是说,实际上用vector<int> v = {1,2,3};初始化时先转换为initializer_list<int> il的对象,然后拿这个对象去初始化的!

我们现在要想在我们的vector中支持这个操作其实我们只需要中在一个构造即可!

我们可以先开il。size()一样大的空间,因为他们支持size和迭代器,所以我们直接把il里面的数据拿出来尾插即可!!

vector(initializer_list<T> il)
{
	reserve(il.size());
	for (auto& e : il)
	{
		push_back(e);
	}
}

赋值拷贝

传统写法:把原来的空间_start释放掉,然后开和要赋值的一样大的空间!然后将v的数据拷贝给_start即可!

注意这里用到了重载后的[],不了解点击目录查看operator[](size_t n)!写在这里是为了完成整!

vector<T>& operator=(const vector<T>& v)
{
	delete[] _start;
	_start = new T[v.capacity()];
			
	for (size_t i = 0; i < v.size(); i++)
	{
		_start[i] = v[i];
	}
			
	_finish = _start + v.size();
	_endofstorage = _start + v.capacity();
	return *this;
}

现代写法:我们把形参去掉引用,让他变成实现的一份拷贝!然后让他与当前的对象交换即可!

注意:这里用到了swap,不了解点击目录查看:swap(vector<T>& v)

vector<T>& operator=(vector<T> v)
{
	if (&v != this)
	{
		swap(v);
	}
	return *this;
}

但是我觉得这样写还是不够优雅!因为如果真是自己给自己赋值的话,形参已经拷贝了实参!这样不太好,我们可以还是形参是引用,等判断不是自己给自己赋值后再开空间拷贝并交换!

vector<T>& operator=(const vector<T>& v)
{
	if (&v != this)
	{
		vector<T> tmp(v);
		swap(tmp);
	}
	return *this;
}

元素访问

[ ]

这里和string 的一模一样,直接返回该位置元素的引用(因为访问时可能需要修改!)即可!如果是const的vector或者我们只读的,我们可以加const修饰保证权限

T& operator[](size_t pos)
{
	return _start[pos];
}

const T& operator[](size_t pos) const
{
	return _start[pos];
}

front

取0号位置的数据!

T& front()
{
	return _start[0];
}

back

取size()-1位置的数据!

T& back()
{
	return _start[size()-1];
}

修改相关

push_back

先判断扩容,之后再_finish的位置直接插入即可!最后让_finish偏移到下一个位置即可!

void push_back(const T& val)
{
	if (_finish == _endofstorage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}

	*_finish++ = val;
}

pop_back

再删除前一定先判断是否为空!如果为空就别删了!否则让_finish--即可!

void pop_back()
{
	assert(!empty());
	--_finish;
}

insert

我们先判断要插入的位置是否合法!在判断是否扩容,然后挪动数据,最后插入并返回新插入元素的指针!

iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;//一开始扩容前的长度
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = _start + len;//扩容后调整pos到原来合适的位置
	}

	iterator end = _finish;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = val;
	++_finish;
	return pos;
}

erase

pos位置的后一个元素开始逐一往前覆盖,最后--_finish,返回删除元素后面的第一个元素的指针!

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

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

	--_finish;
	return pos;
}

swap

这里的交换还是对,对象的成员属性直接交换,代价比直接使用库里面的小(库里面的会形成拷贝)!我们直接利用库里面的swap对属性进行交换!

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

clear

本质是让有效元素的个数为0,所以这里直接让_start == _finish即可!

void clear()
{
	_finish = _start;
}

迭代器

由于vector的物理空间是连续的,所以它的原生指针就是天然的迭代器!begin就是_start,end就是_finish,如果是const的直接加const返回const_iterator

typedef T* iterator;
typedef const T* const_iterator;

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

非成员函数

swap

专门实现这个也是为了防止误操作,调到库里面的那个!这样重载后这个就优先被调了!

但是这里_start等都是私有成员,外部无法访问,所以我们可以搞成友元函数!

template<class T>
void swap(vector<T>& v1, vector<T>& v2)
{
	std::swap(v1._start, v2._start);
	std::swap(v1._finish, v2._finish);
	std::swap(v1._endofstorage, v2._endofstorage);
}

全部源码

#pragma once
#include <assert.h>

namespace cp
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			reserve(v.capacity());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

        
		//vector(size_t n, const T& val = T())
		//{
		//	resize(n, val);
		//}

		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(initializer_list<T> il)
		{
			reserve(il.size());
			for (auto& e : il)
			{
				push_back(e);
			}
		}

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}


		//vector<T>& operator=(const vector<T>& v)//传统写法
		//{
		//	delete[] _start;
		//	_start = new T[v.capacity()];
		//	
		//	for (size_t i = 0; i < v.size(); i++)
		//	{
		//		_start[i] = v[i];
		//	}
		//	
		//	_finish = _start + v.size();
		//	_endofstorage = _start + v.capacity();
		//	return *this;
		//}

		vector<T>& operator=(const vector<T>& v)
		{
			if (&v != this)
			{
				vector<T> tmp(v);
				swap(tmp);
			}
			return *this;
		}

		//vector<T>& operator=(vector<T> v)
		//{
		//	if (&v != this)
		//	{
		//		swap(v);
		//	}
		//	return *this;
		//}

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t len = size();//提起记录一下size
				//memcpy(tmp, _start, sizeof(T) * len);
				for (size_t i = 0; i < len; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;

				_start = tmp;
				_finish = tmp + len;
				_endofstorage = tmp + n;
			}
		}

		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				for (size_t i = size(); i < n; i++)
				{
					_start[i] = val;
				}

				_finish = _start + n;
			}
		}

		void push_back(const T& val)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}

			*_finish++ = val;
		}

		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;//一开始扩容前的长度
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + len;//调整pos到原来合适的位置
			}

			iterator end = _finish;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			++_finish;
			return pos;
		}

		iterator erase(iterator pos)
	    {
		    assert(pos >= _start);
		    assert(pos < _finish);

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

		    --_finish;
		    return pos;
	    }

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

		void clear()
		{
			_finish = _start;
		}

		T& front()
		{
			return _start[0];
		}

		T& back()
		{
			return _start[size()-1];
		}

		T& operator[](size_t pos)
		{
			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			return _start[pos];
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

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

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

		bool empty() const
		{
			return size() == 0;
		}

	private:
		template<class T>
		friend void swap(vector<T>& v1, vector<T>& v2);

		iterator _start;
		iterator _finish;
		iterator _endofstorage;

		//iterator _start = nullptr;
		//iterator _finish = nullptr;
		//iterator _endofstorage = nullptr;
	};

	template<class T>
	void swap(vector<T>& v1, vector<T>& v2)
	{
		std::swap(v1._start, v2._start);
		std::swap(v1._finish, v2._finish);
		std::swap(v1._endofstorage, v2._endofstorage);
	}
	
	template<class T>
	void print(const vector<T>& v)
	{
		for (const auto& e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

OK,好兄弟本期分享就到这里,我们下一期再见!

结束语:且视他人之凝目如盏盏鬼火,大胆地去走自己的夜路!

  • 19
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值