【C++】vector的实现

模拟实现vector类

  • 前言
  • 一、迭代器
  • 二、重载 [ ]
  • 三、构造函数相关(重点)
    • (1)构造函数
    • (2)构造并使用n个值为value的元素初始化
    • (3)区间构造
    • (4)拷贝构造
  • 三、析构函数
  • 四、[赋值运算符重载](https://so.csdn.net/so/search?q=%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6%E9%87%8D%E8%BD%BD&spm=1001.2101.3001.7020)
  • 五、reserve的实现--改变容器容量大小
  • 五、resize的实现--改变容器有效字符个数
  • 六、几种简单接口(empty、size、capacity、clear)
  • 七、push_back和pop_back的实现
  • 八、insert的实现(重点)--迭代器失效问题一
  • 八、erase的实现(重点)--迭代器失效问题二


前言

类框架与之前string类的模拟实现类似,区别在于vector 是用 _finish 和 _end_of_storage 两个指针来维护空间,但本质上其实是一样的 ,
_size = _finish - _start
_capacity = _end_of_storage - _start

class vector
{
 public:
     //成员函数
 private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
}

接下来介绍成员函数的实现。

一、迭代器

iterator 与 const_iterator 的作用:
遍历容器内的元素,并访问这些元素的值。

iterator 与 const_iterator 的区别:
iterator 可以改元素值,但 const_iterator 不可以改元素值。

const_iterator 对象可以用于 const 对象 或非 const 对象,它自身的值可以改(可以指向其他元素),但不能改写其指向的元素值。

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;
		}

二、重载 [ ]

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

三、构造函数相关(重点)

(1)构造函数

直接将所有的成员变量置为空即可

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

(2)构造并使用n个值为value的元素初始化

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

其中,第二个参数val是一个缺省参数。对于没有给定确定值的时候,有两种情况:
(1)如果T是内置类型,T()的值为0;
(2)如果T是自定义类型,T()调用的就是该自定义类型的默认构造函数。
因此,对于自定义类型一定要有默认构造函数,否则会报错!
重点在于第一个参数的类型,只能是int,如果是size_t类型,会报错。原因如下:
接口测试部分
构造函数错误调用问题

(3)区间构造

//迭代器区间构造
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

为什么单独定义一个这样的函数模板呢?template
答:这是因为vector是一个底层连续的容器,它可以用来存储任意类型的数据,
而这里使用区间的方式来初始化vector的容器就需要做到对任意数据都可以进行初始化,因此就需要以模板的形式给出。

(4)拷贝构造

拷贝构造有三种实现方式,最推荐的当然是通过vector内部的swap()函数实现
参数v是const修饰的,它只能调用const类型的成员函数,因此这里必须调用const修饰的迭代器:

//拷贝构造 -- 现代写法
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end()); //复用构造函数和swap函数
			swap(tmp);
		}

//写法2
		//vector(const vector<T>& v)
		//{
		//	T* tmp = new T[v.capacity()];
		//	memcpy(tmp, v._start, sizeof(T) * v.capacity());
		//	_start = tmp;
		//	_finish = _start + v.size();
		//	_end_of_storage = _start + v.capacity();
		//}

//写法3
		//vector(const vector<T>& v)
		//	: _start(nullptr)
		//	, _finish(nullptr)
		//	, _end_of_storage(nullptr)
		//{
		//	reserve(v.capacity());
		//	for (size_t i = 0; i < v.size(); i++)
		//		push_back(v[i]);
		//}


三、析构函数

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

四、赋值运算符重载

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

五、reserve的实现–改变容器容量大小

【C++】vector的实现_第1张图片

void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldSize = size();
				T* tmp = new T[n];

				if (_start)
				{
					// 扩容必须深拷贝,否则会导致迭代器失效
					for (size_t i = 0; i < oldSize; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}

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

五、resize的实现–改变容器有效字符个数

【C++】vector的实现_第2张图片

void resize(size_t n, T val = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}

			if (n > size())
			{
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

六、几种简单接口(empty、size、capacity、clear)

bool empty() const
		{
			return _finish == _start;
		}

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

size_t capacity() const
		{
			return _endofstorage - _start;
		}
void clear()
		{
			_finish = _start;
		}

七、push_back和pop_back的实现

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

			*_finish = x;
			++_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; //记录pos,避免扩容后pos变为野指针
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newCapacity);

				pos = _start + len; //更新pos
			}

			//挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = val;
			++_finish;

			return pos;
		}

迭代器失效问题一:这里是由于insert之后pos 的意义变了–不再指向原来的元素,而是指向新插入的元素。因此,如果不读pos进行更新,pos就会成为野指针。

八、erase的实现(重点)–迭代器失效问题二

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

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

迭代器失效问题二:这里是由于erase之后pos 的意义变了,分为两种情况:
(1)删除元素后pos 不再指向原来的元素,而是指向该元素的后一个元素,所以 erase 之后会导致一个元素被跳过;
(2)在极端情况下–删除最后一个元素之后,pos 就等于 _finish,会发生越界。

因此insert 和 erase 之后迭代器失效,必须更新后才能再次使用。

具体的更新措施:
【C++】vector的实现_第3张图片

【C++】vector的实现_第4张图片

你可能感兴趣的:(c++,前端,javascript)