vector实现

2023-12-25 23:02:06

vector介绍

可以将vector理解为一个动态数组,使用时要使用vector.h头文件

mystl

vector实现相关知识

  • 命名空间
  • 模板
  • 深拷贝和浅拷贝
  • 构造函数

vector实现细节反思


总结

封装——就是只暴露接口,并且在实现的时候一定要谨记这个函数的功能是什么,绝不超过它范围的实现,不要想着给他多来点儿功能,这不是一件好事儿,反而会对后面的函数使用留下不可估量的后患

声明变量

		typedef T* iterator;
		typedef const T* const_iterator;

typedef 只是将他取别名,千万不要将他以为是一个新的类型,就不知道如何去解引用了,他在预处理阶段就会转换成T*,这就是一个指针(最起码写代码的人要是这么以为的)

		//vector就是完完全全就是一个迭代器
		iterator _start = nullptr;//如果新开的空间,将新空间存在_start中
		iterator _finish = nullptr;
		iterator _EndOfStorage = nullptr;

typedef和声明的顺序要注意,现有typedef然后才能进行使用
要明确_finish指向的位置,指向的是最后一个元素的下一个位置(也就是说,这个空间有0个元素,_finish指向第一个元素位置)

迭代器
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const 
		{
			//使用const修饰this构成重载
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}

重载条件:相同作用域,相同函数名,参数类型不同/参数顺序不同/参数个数不同不以返回值类型进行区分
如果不加const就是错的const修饰this,这里就是改变了参数类型


构造函数
		vector()
		{
			//需要在声明的时候就给成nullptr
			//也可以在每一个构造中都给出nullptr
			//否则会出现野指针的情况
		}
		/*
		vector()
		:_start(nullptr),_finish(nullptr),_EndOfStorage(nullptr)
		{}
		*/

最好不要写成参数列表,最好在声明变量的时候就直接写成nullptr,或者在构造函数中严格控制好这些指针
如果自己的某个构造函数写错了,指针没有赋值,但是我们就是使用这个构造函数进行构造对象的,同时我们又要使用这个野指针解引用,那不就费了,如果我们将指针设置成nullptr,最起码比野指针要好吧


		vector(const size_t n,T val=T())
		{
			//reserve(n);
			这么写始终没有改变size的值
			for (int i = 0; i < n; i++)
				_start[i] = val;
			//for (int i = 0; i < n; i++)
			//{
			// //不能直接调用[],在调用[]的时候没有这么多空间可以使用
			// //只是进行赋值而已
			//	_start[i] = val;
			//	_finish++;
			//}
			//如果调用push_back就可以不提前开空间,直接复用就可以了
			for (int i = 0; i < n; i++)
				push_back(val);
		}
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		void push_back(T val = T())
		{
			//push_back不能使用&,可能传进来的是一个需要进行类型转换或者进行计算的值
			//默认生成一个val
			if (size() == capacity())
			{
				int new_capacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(new_capacity);
			}
			*_finish = val;
			_finish++;
		}

在实现这个插入函数的时候,可以选择复用函数或者手写
在复用函数的时候一定要明确另一个函数中所实现的内容,比如[]访问需要提起开好空间
但是在push_back的时候就不需要提前保证足够的空间


		vector(const vector<T>& v)
		{
			//需要进行值拷贝
			//reserve(v.capacity());
			//for (int i = 0; i < v.size(); i++)
			//	_start[i] = v[i];

			//只要调用push_back就不需要使用reserve进行扩容
			//使用auto引用是为了节省空间,如果vector中的类型是一个内存很大的结构体
			//那么auto每次拿到一个都需要进行拷贝,那样的时间消耗将是巨大的
			//况且在push_back中接收参数也是使用传值进行接收参数的
			//push_back比[]好
			//1.[]调用的是push_back
			//2.push_back使用的空间开辟手段更好
			for (auto& e : v)
				push_back(e);
		}

一定要进行值拷贝,如果存储的类型是string类型,如果使用指针拷贝的话,在一个对象释放空间的时候,另一份空间将要去哪里找???所以要进行值拷贝
范围遍历for,需要加&防止内部的对象是一个内存很大的类型,那样大量数据出现时,拷贝的压力将会很大


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

操作符重载
		vector<T>& operator=(vector<T> temv)
		{
			swap(temv);
			return *this;
		}

使用现代的方法进行赋值


功能函数
		void reserve(size_t n)
		{
			//reserve是开多少空间
			//if (n > capacity())
			//{
			//	//可能发生迭代器失效
			//	T* old_Storage = _start;
			//	T* tem = new T[n];//使用new会直接生成T类型的看空间
			//	int t_size = size();
			//	_start = tem;
			//	_finish = _start + t_size;//结尾位置默认会向后移动一个位置
			//	_EndOfStorage = _start + n;
			//	//开心空间之后要将原来空间中的数据拷贝回来
			//	if(old_Storage)
			//		memcpy(_start, old_Storage, sizeof(T) * t_size);
			//	delete[] old_Storage;
			//}

			if (n > capacity())
			{
				T* tem = new T[n];
				int t_size = size();
				if (_start)
				{
					//不能使用memcpy,如果使string类型,需要深拷贝,但是使用的是浅拷贝
					for (int i = 0; i < t_size; i++)
					{
						tem[i] = _start[i];
					}
				}
				_start = tem;
				_finish = _start + t_size;
				_EndOfStorage = _start + n;
			}
		}

只要涉及到指针扩容,就可能产生迭代器失效
迭代器失效:由于扩容的时候没有将指向原来表中的指针改变指向,指向新的空间中


		void resize(size_t n)
		{
			//将没有设置的为置为0
			//缩容不会调整capacity,只会调整size
			if (n < size())
			{
				//缩容
				_finish = _start + n;
			}
			if (n > size())
			{
				reserve(n);
				//此时it已经在end的位置
				while (_finish < _EndOfStorage)
				{
					*_finish = 0;
					_finish++;
				}
			}
		}

resize在缩容的时候不改变总容量,只改变size;在扩容的时候增大总容量,并且将其余位置设置为0


		void push_back(T val = T())
		{
			//push_back不能使用&,可能传进来的是一个需要进行类型转换或者进行计算的值
			//默认生成一个val
			//if (size() == capacity())
			//{
			//	int new_capacity = capacity() == 0 ? 4 : capacity() * 2;
			//	reserve(new_capacity);
			//}
			//*_finish = val;
			//_finish++;
			insert(end(), val);
		}
		void insert(iterator pos, T val = T())
		{
			//任何插入的函数都不能使用&接收val
			assert(pos >= begin());
			assert(pos <= end());
			//如果开了空间,那么迭代器就可能失效了
			//int gap = pos - _start;
			//reserve(size() + 1);//如果比他大就开空间
			既然push_back也要开空间,那么可以合并到insert中,进行统一操作
			//pos = _start + gap;
			//iterator it = end();
			//while (it >= pos)
			//{
 		//		*it = *(it - 1);
			//	it--;
			//}
			//*pos = val;
			//_finish++;
			if (_EndOfStorage == _finish)
			{
				int gap = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + gap;
			}
			iterator it = end() - 1;
			while (it >= pos)
			{
				*it = *(it - 1);//c语言中允许向前访问越界,不允许向后访问越界
				it--;
			}
			*pos = val;
			_finish++;
		}

这里就需要体会代码复用的魅力,代码的高度复用使得代码未来修改的时候将会格外的方便
就拿这里来看,push_back复用了功能更强大的insert,插入的时候都需要进行扩容,在修改代码的时候也只需要在一个地方改就行了


		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			auto it = pos;
			while (it<_finish-1)
			{
				*it = *(it + 1);
				it++;
			}
			return pos;
		}

返回的是删去位置的下一个位置,也就是新的这个位置

文章来源:https://blog.csdn.net/2201_76033304/article/details/134961133
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。