vector模拟实现

本文详细介绍了如何模拟实现STL中的vector容器,对比了与string的区别,重点关注了erase、resize、operator=和reserve等函数的使用及其潜在问题,包括深浅拷贝和内存管理的注意事项。
摘要由CSDN通过智能技术生成

一. vector的模拟实现

我们知道:
在STL中,我们上次学了string
但其实准确来说string并不算是STL中的成员。
因为string出现的比STL要早
所以string的设计相比于STL中其他的容器要麻烦的多。

但是今天带来的vector可以说是是完完全全的STL中的容器。

这里可能还有人不知道容器是什么东西。
容器准确来说是用来处理数据的
就比如我们之前学习进程的时候,在管理文件的时候
文件:文件内容+文件属性
本质都是数据,所以我们能看到很多的链表和顺序表的成分在

而之后我们要学习的算法
作用则是:处理数据
存储数据+处理数据可以说是组成了程序

1.0 与string的区别

为什么这里要讲string和vector的区别?

因为string和vector一样实际上就是一个顺序表

但是还是有很大的区别:

而vector通过模板的功能来实现,能存储多种类型的数据

string相当于将char类型的数据拎出来,为它拓宽了很多接口的同时,为了兼容C语言,保留了很多字符串的特殊点。
所以:
i. string只能存储char类型
ii. string保留了末尾是’\0’的成分

而vector则是全新的对象,不需要兼容C语言。
i. 所以vector能存储各种类型
ii. 同时末尾不用添加’\0’

1.1 实现内容

我们模拟实现vector的目的:
是为了更好的了解vector来帮助我们更好的使用和学习。

所以这里就挑几个比较重要的接口进行实现
做到一个比较基础的vector。

在这里插入图片描述
这个就是大致的实现内容。

因为这个和string的实现实在是太像了,所以就选几个比较重点的讲

其他未讲到的会后面会放在整体代码中。

实现方法

这里不同于string的capacity和data实现方法

这里vector用的是三个指针成员变量的实现方法,但是实际上没有相差多少,用string的同样可以实现。

class vector 
{
public:
private:
	iterator _start;
	iterator _finish;
	iterator _eofstorage;
};

这里的:
_start:就是开辟数组的开始指针
_finishi:就是开辟数组最后一个元素的指针
_eofstorage:数组开辟空间的空间的最后位置的地址

这个实现方法区别其实不重要,用string的实现方法依旧可以实现。

二. vector模拟中重点讲解内容(坑)

2.1 erase的使用问题

erase
这个函数的功能是:
删除指定位置的元素。

int main()
{
	std::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	std::vector<int>::iterator begin = v1.begin();
	v1.erase(begin);
	for (auto i : v1)
	{
		std::cout << i;
	}
}

在这里插入图片描述
具体就是这样使用。

接下来就来演示erase的一个非常经典的错误用法。

这里用的是STL中的vector,因为官方的使用中,这个问题也是十分常见的。

#include<iostream>
#include<vector>
#include"vector.h"
int main()
{
	std::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	std::vector<int>::iterator begin = v1.begin();
	v1.erase(begin);
	while (begin != v1.end())
	{
		std::cout << *begin;
		begin++;
	}
}

在这里插入图片描述
这里能发现程序崩掉了。

这是我们发现
这里我们把begin迭代器指定的内容删除后
然后继续使用了begin迭代器。

但是按照我们的道理来讲
这个begin的指向的方向应该还是老方向。
erase之后,各类元素会向前面补位,begin应该指向新补位的2了。

按道理来说这个begin应该是可以继续使用的。

因为在vs中,这个问题算是迭代器失效
在使用迭代器对象进行insert与erase之后。
vs会默认该迭代器无法继续使用

我们知道STL不同编译器下是不同的,因为官方给了一个STL的最基本的设定,具体的修改和实现方法是取决于公司自己

所以在linux中,可能就不会判断这个迭代器失效的效果。

这里我们来试一下。

在这里插入图片描述
还是同样的代码
在这里插入图片描述

这里能发现在linux中能进行访问。

但是这样的话为什么vs要禁止用户使用呢?
这里就要讲下允许使用这个的坏处了。

这里我们在linux中用这个测试一道题:
删除数组中的所有偶数
在这里插入图片描述

在这里插入图片描述
这里结果我们发现少删除了一个2。
这里其实仔细观察一下我们就能发现问题所在。

在这里插入图片描述
这里就能发现it删除完,还会进行一次++,会略过连续的偶数。

这里掠过算是比较ok一点的错误了,但是如果想象一下最后一位是偶数,这样的话会直接掠过数组的最后一位,直接向后面遍历,非常容易导致越界访问

这里其实要解决也是很简单。

在这里插入图片描述
只要在其中加一个else就可以了。
在这里插入图片描述

但是这样虽然能解决问题,但是大大限制了它的平台可移植性。

所以这个情况也是开发者所在意的。

在这里插入图片描述
这里能发现erase有一个iterator 的返回值。
这个iterator是用来返回下一个位置的值的。
在这里插入图片描述
所以这里需要去接受一下返回值,才能继续用这个begin变量。

2.2 resize的特殊写法

void resize(int n, const T& val = T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		reserve(n);
		while (_finish != _start + n)
		{
			*_finish = val;
			_finish++;
		}
	}
}

这个是resize的函数实现。
我们知道:
reserve:开辟空间,插入数据时不扩容
resize:开空间+初始化为指定值

但是resize初始化为指定值的时候,指定值有多种不同的类型。

但是有时候如果用户不输入初始化的值该怎么办

void resize(int n, const T& val = T())

这个第二个参数,const T& val=T();
这个可以说是一个新写法,这个前面是类型

val=T();是缺省值。
那这个T();
其实就是调用T类型的构造函数的的意思。

那这样我们大致就能明白是什么意思了
如果用户没有给初始化值,那就去调用vector类型T的构造函数

2.3 operator =

这里的operator和string的的一样,要注意深浅拷贝的问题。

所以同样可以用到swap深拷贝函数。

void check_capacity()
		{
			if (_finish == _eofstorage)
			{
				if (_start == NULL)
					reserve(4);
				else
					reserve(capacity() * 2);
			}
		}

2.4 reserve

void reserve(int n)
{
	if (n > capacity())
	{
		int oldsize = capacity();
		T* new_vec = new T[n];
		if (_start != nullptr)
		{
    		for(int i=0;i<oldsize;i++)
    		{
         		new_vec[i]=_start[i];
    		}
			delete _start;
		}
		_start = new_vec;
		_finish = _start + oldsize;
		_eofstorage = _start + n;

	}
}

这个是完成版的reserve

		void reserve(int n)
		{
			if (n > capacity())
			{
				int oldsize = capacity();
				T* new_vec = new T[n];
				if (_start != nullptr)
				{
					memcpy(new_vec, _start, oldsize*sizeof(T));
					delete _start;
				}
				_start = new_vec;
				_finish = _start + oldsize;
				_eofstorage = _start + n;

			}
		}

这个是改版前的reserve函数

这里发现只有一个地方发生了了改变。

if (_start != nullptr)
{
		memcpy(new_vec, _start, oldsize*sizeof(T));
		delete _start;
}

这里乍一看好像没有什么问题。

但是我们仔细想想,如果vector的类型是一个自定义类型,具有地址成员变量。

那memcpy对new_vec与_start进行改变时,就会这个是浅拷贝,无法进行深拷贝,就会发生严重的问题

所以就改成了

for(int i=0;i<oldsize;i++)
  {
    	new_vec[i]=_start[i];
  }

这里使用赋值重载来进行深度拷贝。
就没有这样的问题了

三. 整体代码

#include<string.h>
#include<assert.h>
#include<iostream>
namespace my_vector 
{
	template<typename T>
	class vector 
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_eofstorage(nullptr)
		{}
    vector(const vector<T>& v)
    {
      _eofstorage=_finish=_start=nullptr;
      for(int i=0;i<v.size();i++)
      {
        push_back(v[i]);
      }
    }
    vector(int n,const T& data)
    {
      _start=nullptr;
      _finish=nullptr;
      _eofstorage=nullptr;
      resize(n,data);
    }
    template<class alliterator>
    vector(alliterator first,alliterator end)
    {
      _finish=nullptr;
      _start=nullptr;
      _eofstorage=nullptr;
      while(first!=end)
      {
        push_back(*first);
        first++;
      } 
    }
		~vector()
		{
			delete _start;
			_start = _finish =_eofstorage = nullptr;
		}
		void check_capacity()
		{
			if (_finish == _eofstorage)
			{
				if (_start == NULL)
					reserve(4);
				else
					reserve(capacity() * 2);
			}
		}
    vector<T>&  operator= (const vector<T>& v)
    {
      if(this!=v)
      { 
        std::swap(_start,v._start);
        std::swap(_finish,v.finish);
        std::swap(_eofstorage,v._eofstorage);
      }
      return *this;

    }
		void push_back(const T& data)
		{
			check_capacity();
			insert(size(), data);
		}
		void reserve(int n)
		{
			if (n > capacity())
			{
				int oldsize = capacity();
				T* new_vec = new T[n];
				if (_start != nullptr)
				{
          for(int i=0;i<oldsize;i++)
          {
            new_vec[i]=_start[i];
          }
					delete _start;
				}
				_start = new_vec;
				_finish = _start + oldsize;
				_eofstorage = _start + n;

			}
		}
    
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		size_t size() const
		{
			return _finish - _start;
		}
		void insert(int pos,const T& data)
		{

			assert(pos >= 0 && pos <= size());
			check_capacity();
			T* end = _finish;
			while (end > _start+pos)
			{
				*end = *(end - 1);
				end--;
			}
			_start[pos] = data;
			_finish++;
		}
		void erase(int pos)
		{

			assert(pos >=0 && pos <= size());
			T* end = _start+pos;
			while (end <_finish)
			{
				*end = *(end + 1);
				end++;
			}
			_finish--;
		}
		size_t capacity() const
		{
			return _eofstorage - _start;
		}
		T& operator[](int pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](int pos) const
		{
			assert(pos < size());
			return _start[pos];
		}
		void pop_back()
		{
			erase(_finish - _start);
		}

		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		void resize(int n,const T& val=T())
		{
			if (n < size())
			{
				_finish=_start + n;
			}
			else
			{
				reserve(n);
        while(_finish!=_start+n)
        {
          *_finish=val;
          _finish++;
        }
			}
		}



	private:
		iterator _start;
		iterator _finish;
		iterator _eofstorage;

	};
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想学c啊啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值