【C++】模拟实现vector

目录

一、成员变量

二、迭代器

2.1 正向迭代器

三、容量相关

3.1 得到vector的属性

3.2 申请扩容 —— reserve

3.3 改变vector的有效长度 —— resize

四、元素访问

4.1 通过下标访问vector —— operator[]

4.2 访问vector的第一个元素 —— front

4.3 访问vector的最后一个元素 —— back

五、修改相关

5.1 尾插数据 —— push_back

5.2 尾删数据 ——  pop_back

5.3 在任意位置插入数据 —— insert

5.4 在任意位置删除数据 —— erase

5.5 交换两个vector对象 —— swap

5.6 清空vector —— clear

六、构造函数、析构函数以及赋值运算符重载

6.1 默认构造函数和拷贝构造

6.2 迭代器构造

6.3 填充构造

6.4 析构函数

6.5 赋值运算符重载 —— operate=

七、源码


一、成员变量

        vector和我们之前学过string非常像,都是通过数组来实现的容器,那么vector的成员变量是不是和string的成员变量一样,都是_str_size_capacity呢?但很遗憾并不是,vector的成员变量是三个迭代器

namespace simulation {

	template<class T>
	class vector {
	public:

        //vector容器的迭代器是指针
		typedef T* iterator;
		typedef const T* const_iterator;

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

         注:vector是一个模板容器,记得在vector类的上方要带上template<class T>。这样只要写一个vector类模板就能构建出存储不同数据类型的vector对象。

        初见这三个迭代器你可能不知道它们分别指向谁,有什么作用,那让我们来看一张图。

从图中我们可以看出:

        _startstring中的_str一样,都指向数据的起始位置(第一个数据)

        _finishstring中的end()一样,都指向最后一个数据的后面一个位置

        _end_of_storage则和string中末尾自带的'\0'一样,指向容量(当前容器的最大长度)的后面一个位置

有了这三个迭代器我们就能知道vector的有效长度_size和容量_capacity:

        有效长度_size = _finish - _start

        容量_capacity = _end_of_storage - _start

        所以vector看似没有记录有效长度和容量,其实这两个属性能够通过vector中的三个成员变量相减得到。


二、迭代器

2.1 正向迭代器

/*
    迭代器
*/

//可读可写
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

//可读不可写
const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

三、容量相关

3.1 得到vector的属性

//得到vector的有效长度
size_t size() const
{
	return _finish - _start;
}

//得到vector的容量
size_t capacity() const
{
	return _end_of_storage - _start;
}

//判断vector是否为空
bool empty() const
{
	return _start == _finish;
}

3.2 申请扩容 —— reserve

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldSize = size();

		iterator temp = new T[n];

		//避免因_start为nullptr时造成的错误 空指针不能被解引用
		if (_start)
		{
			for (size_t i = 0; i < oldSize; ++i)
			{
				temp[i] = _start[i];
			}

		}

		//空指针可以被delete 所以delete放在 if (_start)外面统一处理
		//因为delete会检查被delete的目标是否为空指针 如果是空指针则不做处理
        //一定要先delete再给_start赋值 不然会找不到_start指向的旧空间 这样会内存泄漏
		delete[] _start;
		_start = temp;
		_finish = _start + oldSize;
		_end_of_storage = _start + n;

	}
}

        这里有两个需要注意的地方:

        ① 我们需要提前记录扩容前的有效长度oldSize,因为_finish是通过_start加上有效长度得到的。你可能会感到疑惑,为什么不直接用函数size()呢?那是因为在修改_finish的时候,_finish_start已经不指向同一块空间了,而size()中求有效长度是通过_finish_start实现的,不指向同一块空间的两个指针相减是没有意义的!

        并且reserve扩容是不会改变vector的有效长度的,所以扩容前后的有效长度是不变的,因此可以通过_start + oldSize得到_finish在新空间上的位置。 

         实现reserve时,我是用operator[]for循环搭配来完成将旧空间的数据拷贝到新空间。你在实现时可能会用memcpy来完成拷贝工作,但我可以很负责任的告诉你,memcpy并不能很好的完成拷贝工作。因为memcpy是将一块空间的内容原封不动的拷贝到另一块空间,这就决定了memcpy是浅拷贝而不是深拷贝

        因此memcpy在拷贝内置类型的时候不会出错;而在拷贝自定义类型,并且自定义类型涉及到资源管理时就会出问题。


3.3 改变vector的有效长度 —— resize

void resize(size_t n, const T& val = T())
{
	if (n <= size())
	{
        //情况一:n小于size()时 直接改变标志着有效长度的_finish 
        //因为正常调用api是不能访问到_finish后面的数据的 简介达到了删除的目的
        //当n == size()时 下面的语句可以视为_finish = _finish
		_finish = _start + n;
	}
	else
	{
        //提前扩容 然后把val一个一个插入进去 同时还能调整_finish的位置
        //_start + n为resize后_finish应该在的位置
		reserve(n);
		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

        你有没有发现一个东西非常奇怪,这个奇怪的东西就是const T& val = T(),其中T可以是任意类型,如intdouble等内置类型或者是string等自定义类型。当Tstring等自定义类型时我们知道T()就是在构建一个匿名对象,这个匿名对象会调用相应类型的默认构造函数,那当Tint等内置类型呢?难道也是在调用默认构造函数吗?是的,C++为了使模板更具通用性,对内置类型进行了升级,让内置类型也有了构造函数

        因此在resize延长vector时,如果不指定val就会填充默认值。这个默认值与T有关,如果Tint那么默认值是0Tdouble,默认值是0.0Tchar,默认值为'\0'……。


四、元素访问

4.1 通过下标访问vector —— operator[]

T& operator[](size_t pos)
{
	assert(pos >= 0 && pos < size());

	return _start[pos];
}

const T& operator[](size_t pos) const
{
	assert(pos >= 0 && pos < size());

	return _start[pos];
}

4.2 访问vector的第一个元素 —— front

T& front()
{
	return *_start;
}

const T& front() const
{
	return *_start;
}

4.3 访问vector的最后一个元素 —— back

T& back()
{
	return *(_finish - 1);
}

const T& back() const
{
	return *(_finish - 1);
}

五、修改相关

5.1 尾插数据 —— push_back

void push_back(const T& val = T())
{
    //如果_finish == _end_of_storage就说明vector已经存满数据了 需要扩容
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 2 : 2 * capacity());
	}

	*_finish = val;
	++_finish;
}

5.2 尾删数据 ——  pop_back

void pop_back()
{
	//判断vector内是否为空 同时也能判断_start是否为空指针
	//因为当_start为空指针是_finish也一定为空指针 空指针不可能会大于另一个空指针
	assert(_finish > _start);

	--_finish;
}

5.3 在任意位置插入数据 —— insert

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

	if (_finish == _end_of_storage)
	{
        //记录pos到_start的距离
        size_t len = pos - _start;

		reserve(capacity() == 0 ? 2 : 2 * capacity());

		//如果发生了增容 就需要重置pos 因为pos发生了迭代器失效
		pos = _start + len;
	}

    //从后开始挪动数据 为将插入的数据腾出位置
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = val;
	++_finish;

    //返回值为第一个新插入的数据
	return pos;
}

        insert在实现时最需要注意的点就是扩容后需要更新pos的位置。因为vector扩容并不是在原空间上进行延长,而是在别的地方开辟一块更大的空间,将旧空间的数据拷贝到新空间后再释放原空间。这就会导致_start等成员变量指向扩容后的新空间,而pos还指向已经释放掉的旧空间。

        如果不对pos进行修正就直接解引用的话,就相当于对野指针进行解引用了。 因此在扩容之后一定要对pos进行修正。


5.4 在任意位置删除数据 —— erase

iterator erase(iterator pos)
{
	//同时检查下标有效性和vector是否为空
	//当容器为空时 _start == _finish
    //所以下面的语句在vector为空时可改成 assert(pos >= _start && pos < _start)
	//显然 >= 和 < 不可能同时成立 
	assert(pos >= _start && pos < _finish);

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

	--_finish;

    //返回值为指向已删除数据的下一个数据的迭代器
	return pos;
}

5.5 交换两个vector对象 —— swap

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

5.6 清空vector —— clear

void clear()
{
	_finish = _start;
}

六、构造函数、析构函数以及赋值运算符重载

6.1 默认构造函数和拷贝构造

//默认构造
vector()
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{}

//拷贝构造
vector(const vector<T>& vT)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
    //提前开辟好与vT相同的容量 这样插入过程中就不会发生扩容
    //减少一边push_back一边扩容的消耗
	reserve(vT.capacity());
	for (const auto& k : vT)
	{
		push_back(k);
	}
}

6.2 迭代器构造

template<class inputIterator>
vector(inputIterator first, inputIterator last)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	while (first != last)
	{
		push_back(*first); // this->push_back(*first)
		++first;
	}
}

        这里不能写成vector(iterator first, iterator last),因为这里的iterator只代表vector的迭代器,这样写的话就只能用vector的迭代器来构造vector了,但是STLvector能用其他容器的迭代器来构建vector

        所以我们还需要一个模板template<class inputIterator>,让我们自己模拟的vector可以使用别的容器的迭代器来构造。 


6.3 填充构造

vector(size_t n, const T& val = T())
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	resize(n, val);
}

vector(int n, const T& val = T())
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	resize(n, val);
}

        你肯定会很疑惑,这两个函数到底有什么区别,除了一个是size_t n,一个是int n外就没有任何区别了。这就让我来告诉你吧,这两个函数真的没啥区别,两个函数的功能一模一样,都是填充构造,写第二个(int n)属实是无奈之举。

        你猜猜看,如果不写第二个填充构造(int n),那么vi的实例化编译器会选择哪个构造函数?

vector<int> vi(2, 100);

        我猜你会认为编译器一定会选择填充构造,因为vi一眼就是用2个100来构造的。那你就大错特错了,因为编译器会选择迭代器构造。

        这是因为编译器会把2和100都看作int类型,但第一个填充构造(size_t n)的第一个参数是size_t,而迭代器构造的两个参数都是同一类型inputIterator,所以编译器会把inputIterator实例化为int类型,可2和100根本不是迭代器,故而导致了错误。因此我们必须写第二个填充构造(int n)来避免这种乌龙事件。


6.4 析构函数

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

6.5 赋值运算符重载 —— operate=

vector<T>& operator=(const vector<T>& vT)
{
	if (this != &vT)
	{
		vector<T> temp(vT);
		swap(temp);
	}

	return *this;
}

七、源码

#pragma once
#include <iostream>
#include <cassert>

namespace simulation {

	template<class T>
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		/*
			构造函数
		*/

		//默认构造
		vector()
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

		vector(size_t n, const T& val = T())
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			resize(n, val);
		}

		vector(int n, const T& val = T())
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			resize(n, val);
		}

		template<class inputIterator>
		vector(inputIterator first, inputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first); // this->push_back(*first)
				++first;
			}
		}


		vector(const vector<T>& vT)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(vT.capacity());
			for (const auto& k : vT)
			{
				push_back(k);
			}
		}

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

		vector<T>& operator=(const vector<T>& vT)
		{
			if (this != &vT)
			{
				vector<T> temp(vT);
				swap(temp);
			}

			return *this;
		}


		/*
			迭代器
		*/
		iterator begin()
		{
			return _start;
		}

		const_iterator begin() const
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator end() const
		{
			return _finish;
		}

		/*
			容量相关
		*/

		//得到vector的有效长度
		size_t size() const
		{
			return _finish - _start;
		}

		//得到vector的容量
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		void resize(size_t n, const T& val = T())
		{
			if (n <= size())
			{
				//情况一:n小于size()时 直接改变标志着有效长度的_finish 
				//因为正常调用api是不能访问到_finish后面的数据的 简介达到了删除的目的
				//当n == size()时 下面的语句可以视为_finish = _finish
				_finish = _start + n;
			}
			else
			{
				//提前扩容 然后把val一个一个插入进去 同时还能调整_finish的位置
				//_start + n为resize后_finish应该在的位置
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		//判断vector是否为空
		bool empty() const
		{
			return _start == _finish;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldSize = size();

				iterator temp = new T[n];

				//避免因_start为nullptr时造成的错误 空指针不能被解引用
				if (_start)
				{
					for (size_t i = 0; i < oldSize; ++i)
					{
						temp[i] = _start[i];
					}

				}

				//空指针可以被delete 所以delete放在 if (_start)外面统一处理
				//因为delete会检查被delete的目标是否为空指针 如果是空指针则不做处理
				//一定要先delete再给_start赋值 不然会找不到_start指向的旧空间 这样会内存泄漏
				delete[] _start;
				_start = temp;
				_finish = _start + oldSize;
				_end_of_storage = _start + n;

			}
		}

		/*
			元素访问
		*/

		T& operator[](size_t pos)
		{
			assert(pos >= 0 && pos < size());

			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos >= 0 && pos < size());

			return _start[pos];
		}

		T& front()
		{
			return *_start;
		}

		const T& front() const
		{
			return *_start;
		}

		T& back()
		{
			return *(_finish - 1);
		}

		const T& back() const
		{
			return *(_finish - 1);
		}

		/*
			修改相关
		*/

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

			*_finish = val;
			++_finish;
		}

		void pop_back()
		{
			//判断vector内是否为空 同时也能判断_start是否为空指针
			//因为当_start为空指针是_finish也一定为空指针 空指针不可能会大于另一个空指针
			assert(_finish > _start);

			--_finish;
		}

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

			if (_finish == _end_of_storage)
			{
				//记录pos到_start的距离
				size_t len = pos - _start;

				reserve(capacity() == 0 ? 2 : 2 * capacity());

				//如果发生了增容 就需要重置pos 因为pos发生了迭代器失效
				pos = _start + len;
			}

			//从后开始挪动数据 为将插入的数据腾出位置
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			++_finish;

			//返回值为第一个新插入的数据
			return pos;
		}

		iterator erase(iterator pos)
		{
			//同时检查下标有效性和vector是否为空
			//当容器为空时 _start == _finish,所以下面的语句在vector为空时可改成 assert(pos >= _start && pos < _start)
			//显然 >= 和 < 不可能同时成立 
			assert(pos >= _start && pos < _finish);

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

			--_finish;
			return pos;
		}

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

		void clear()
		{
			_finish = _start;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

  • 56
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值