【C++】vector 模拟笔记

文章目录

成员变量和迭代器

下面有vector 存储示意图:vector 是一个左闭又开的空间,_finish 不能存储有效数据。vector 的 iteratorT 类型的指针,不要认为 iterator 就是指针,而且后面还有 list,map,set 等各种容器的迭代器,它们都不是原生指针。在这里我们 vector 写成模板,同时不能与库里面的空间产生冲突,我们需要自己定一个命名空间,具体代码看最下面的整体带代码。

reserve()函数易错点

扩容是异地扩容,需要开一块新的空间,然后把数据拷贝过去,再将原来的空间释放掉。这里注意必须开始首位指针的相对位置,异地扩容tmp赋值给_start,此时 _start和_finish 并不指向同一块空间,而 size() 却是由 _start和_finish 计算而来,由此出错。

错误代码: _finish=_start+size();

cpp 复制代码
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t len = size();
		T* tmp = new T[n];
		if (_start)
		{
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + len;
		_end_of_storage = _start + n;
	}
}

假若对象是自定义类型那么还要完成深拷贝,深拷贝不能用 memcpy 拷贝,比如对象是 stringmemcpy 是值拷贝只会把*string对象的指针地址拷贝给另外一个对象,导致两个string对象的_str指针指向同一块数组空间,而析构两次。因此我们需要调用string类自己的赋值运算符重载函数进行赋值

错误代码: memcpy(_start, v._start, sizeof(T)*v.size() );

迭代器区间初始化易错点

T 类型为 int ,运行这句代码 vector v1(10, 1); 就会报下面这个错误,说明它并没有调用上面这个函数。因为第一个形参为 size_t ,第二个实例化为 int ,而实参两个都为int ,且第二个用迭代器区间初始化函数,两个形参相同,此时更匹配第二个函数。调用第二个函数后对 int 解引用而报错

错误代码: vector(size_t n, const T& val = T() );

cpp 复制代码
vector(int n, const T& val = T())
{
	//参数用int而不用size_t避免当两个参数都为int时,与下面的用迭代器初始化函数冲突
	resize(n, val);
}
//用迭代器区间初始化
template<class in_T>
vector(in_T first, in_T last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

迭代器失效

由上面的扩容逻辑可以知道,插入数据有时候需要扩容,而扩容是异地扩容,原来空间已经被释放了,那么指向原来空间的迭代器自然失效了。因此我们要更新迭代器,让 insert()和erase() 函数返回新的迭代器,新的迭代器和原来迭代器到 _start 的相对距离相等。虽然有时候插入迭代器并没有失效,但是考虑不同平台实现的封装和代码的可移植性,默认它就是会失效,vs库里面实现的也是默认失效,继续用直接报错

整体代码

cpp 复制代码
#pragma once
#include <assert.h>
namespace Me
{
	template<class T>
	class vector
	{
	public:
		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;
		}

		//容量和size
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		size_t size() const
		{
			return _finish - _start;
		}

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

		//无参构造
		vector()
		{}
		//深拷贝
		//不用memcpy拷贝,因为对象可能是自定义类型,比如<string>,memcpy是值拷贝,
		//会导致两个string对象的_str指针指向同一块数组空间
		//而析构两次,因此我们需要调用赋值运算符重载函数进行拷贝
		
		//vector(const vector<T>& v)
		//{
		//	_start = new T[v.capacity()];
		//	for (size_t i = 0; i < v.size(); i++)
		//	{
		//		//调用赋值运算符重载
		//		_start[i] = v._start[i];
		//	}
		//	_finish = _start + v.size();
		//	_end_of_storage = _start + v.capacity();
		//}

		//现代写法
		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto& e : v)
			{
				//会自动更新_finish指针
				push_back(e);
			}
		}
		//赋值运算符重载
		vector<T>& operator=(vector<T> v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
			return *this;
		}
		
		vector(int n, const T& val = T())
		{
			//参数用int而不用size_t避免当两个参数都为int时,与下面的用迭代器初始化函数冲突
			resize(n, val);
		}
		//用迭代器区间初始化
		template<class in_T>
		vector(in_T first, in_T last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

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

		//扩容和调整
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//记录原来相对位置,后面_start和_finish并不指向同一块空间
				size_t len = size();
				T* tmp = new T[n];
				if (_start)
				{
					for (size_t i = 0; i < size(); i++)
					{
						//这里和上面一样,必须深拷贝
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + len;
				_end_of_storage = _start + n;
			}
		}
		void resize(size_t n, const T& val = T())
		{
			if (n < size()) {
				_finish = _start + n;
			}
			else {
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}

		//打印
		void print()
		{
			for (auto e : *this)
			{
				cout << e << " ";
			}
			cout << endl;
		}

		//插入 迭代器使用一次后可能会失效,
		//因此要返回新的迭代器,旧的默认失效处理
		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);
			if (_finish == _end_of_storage)
			{
				//扩容需要记录相对位置,以免迭代器失效
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//更新迭代器
			}
			T* end = _finish;
			_finish++;
			while (end >= pos)
			{
				*end = *(end - 1);
				--end;
			}
			*pos = x;
			return pos;
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			_finish++;
		}

		//删除 
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator end = pos;
			_finish--;//先-- 因为_finish不存储有效数据
			while (end < _finish)
			{
				*end = *(end + 1);
				end++;
			}
			return pos;
		}
		void pop_back()
		{
			--_finish;
		}
		void pop_front()
		{
			erase(begin());
		}

	private:
		T* _start = nullptr;
		T* _finish = nullptr;
		T* _end_of_storage = nullptr;
	};

}
相关推荐
伏虎山真人2 分钟前
QT程序开机自启方案
开发语言·qt
2401_857600952 分钟前
企业OA管理系统:Spring Boot技术实践与案例分析
java·spring boot·后端
潜洋10 分钟前
Spring Boot 教程之六:Spring Boot - 架构
java·spring boot·后端·架构
lsx20240611 分钟前
Ruby 模块(Module)
开发语言
DY009J20 分钟前
如何搭建C++环境--1.下载安装并调试Microsoft Visual Studio Previerw(Windows)
c++·microsoft·visual studio
豆包MarsCode20 分钟前
我用豆包MarsCode IDE 做了一个 CSS 权重小组件
开发语言·前端·javascript·css·ide·html
铅华尽22 分钟前
Java---JDBC案例--手机信息管理系统
java·开发语言·智能手机
nuo53420229 分钟前
The 2024 ICPC Kunming Invitational Contest
c语言·数据结构·c++·算法
X 西安32 分钟前
第十章JavaScript的应用
开发语言·javascript·ecmascript
小王同学的C++34 分钟前
移动语义和拷贝语义有什么区别?
c++