vector基础功能实现C++

vector的大部分逻辑和string实现类似,我们先来看都要实现vector的哪些功能:

template<class T>
class vector
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;
    
    iterator begin()
    {
        return _start;
    }
    
    iterator end()
    {
	    return _finish;
    }
    
    const_iterator cbegin()const
    {
	    return _start;
    }
    const_iterator cend()const
    {
	    return _finish;
    }

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

    size_t capacity()const
    {
	    return _end_of_storage - _start;
    }

    
private:

    iterator _start;
    iterator _finish;
    iterator _end_of_storage;
};

事实上,vector私有成员只有三个迭代器,我们可以通过三个迭代器的相对位置来计算出目前vector的size,capacity等,并且可以获取到begin和end位置的迭代器。

 vector的构造函数:

//什么都不传,默认全为空
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{}


vector(size_t n, const T& value = T())
{
	reserve(n);

	for (size_t i = 0; i < n; ++i)
		push_back(value);
}

vector(int n, const T& value = T())
{
	reserve(n);

	for (int i = 0; i < n; ++i)
		push_back(value);
}
//拷贝构造
vector(const vector<T>& val)
{
	reserve(val.capacity);

	for (auto e : val)
		push_back(e);
}

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

第一个什么都不传,在初始化链表将成员全部初始化为nullptr。

第二个和第三个都是初始化n个T类型,但是为什么要有size_t  和 int 的区分呢?这是因为要避免和传迭代器区间构造的冲突,我们来看下面的构造:

int main()
{
    vector<int> v(10,1);
    
    return 0;
}

 编译器默认会把10,1认为是整形,此时在我们看来应是初始化了10个1,在编译器看来,如果我们只写了size_t和迭代器区间构造,那么上面这种做法编译器会优先考虑迭代器,因为这是最匹配的一种,因此到时候就会对整形进行解引用操作,肯定会报错,因此我们又补加了如下:

vector(int n, const T& value = T())
{
	reserve(n);

	for (int i = 0; i < n; ++i)
		push_back(value);
}

这样就不会冲突了。最后我们来看构造n个T的原理:

原理也非常简单,我们直接给其reserve n个空间,空间肯定足够,然后循环n次,不断将value尾插即可。

这里我们使用了reserve 和 push_back函数,我们先来看看这两个:

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

		//memcpy(tmp, _start, sizeof(T) * oldsize);
		for (size_t i = 0; i < oldsize; ++i)
		{
			tmp[i] = _start[i];
		}

		delete[] _start;

		_start = tmp;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
}

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

 这里的reserve 和我们实现的 string中的略有不一样,其他的地方基本一致,我们来看看不同点:

  1. 我们不直接使用memcpy了,为什么呢?
  2. 这里我们首先求出了原来的size大小,为什么呢?

首先我们来解释第一点:

(如果是memcpy) 这里结合reserve 代码来看,好像没错啊,把上面的memcpy拷贝下来,再释放原来的空间,而且原来的_start也delete掉了。

但是我们要注意,如果是内置类型例如整形,这样写是不会出错的,但是我们别忘了,我们实现的vector里面也可以存其他的自定义类型例如 string,如果是自定义类型(拿string来举例),我们使用memcpy拷贝的是每个字节,每个string里面还有单独的一个_str指针,memcpy只是简单的浅拷贝,因此我们虽然delete了原来的_start,但是最后析构的时候就会报错。

再来看第二点:

为什么要求出原来的size大小呢(也就是为什么不能写成_finish = _start + size() ):

还是扩容的原因,此处就比较简单了,因为我们扩容了,_start已经发生改变,因此我们需要先求出oldsize,此后计算就可以方便许多。

push_back很简单,扩容后插入即可,这里就单独解释。

我们再来看拷贝构造和传迭代器区间构造:

//拷贝构造
vector(const vector<T>& val)
{
	reserve(val.capacity);

	for (auto e : val)
		push_back(e);
}

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

拷贝构造也比较简单,我们提前开好和val一样大的空间,然后遍历val不断将其内容尾插入即可。

迭代器区间构造,这里就考我们对迭代器理解的深度了,其实本质也是很简单,就我们目前实现的简单vector,暂且先把其理解为传入了两个指针,然后对这两个指针区间内容操作即可,把first的解引用不断插入即可。

接下来我们来看几个简单的功能:

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

void pop_back()
{
	assert(!empty());
	--_finish;
}

void clear()
{
	_finish = _start;
}

T& operator[](size_t x)
{
	return _start[x];
}

const T& operator[](size_t x)const
{
	return _start[x];
}

这五个功能都比较简单,但是这里着重说一下后两个,对于[ ]运算符重载来讲,我们需要返回该位置的引用。

最后来看几个修改的功能:

void resize(size_t n, const T& val = T())
{
	assert(n >= 0);
	if (n > capacity())
	{
		reserve(n);

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

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

	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = _start + len;
	}
	iterator cur = _finish;
	while (cur != pos)
	{
		*cur = *(cur - 1);
		cur--;
	}
	*pos = x;
	_finish++;

	return pos+1;
}

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

先来看resize功能,我们在string里面也学习过resize函数,如果我们要求改变的size值大于目前的capacity,那么我们首先要进行扩容,然后再不断将默认值写入并且++_finish即可。如果n小于capacity,我们只需要改变_finish位置到 _start + n。

最后的insert和erase函数功能和string里面的功能类似,这里就不仔细讲解全部,但是在insert里面我们要注意的是:因为只要是插入数据,就会牵扯到扩容,如果进行了扩容,那么我们传入的pos位置就会失效,也就是我们所说的迭代器失效问题:

因此我们需要注意的就是,如果进行了扩容,我们需要及时更新新空间中pos 和 _start的相对位置,先提前计算好距离,最终进行 pos = _start + len 即可。

以上内容为自己对于vector学习后的理解,并且简单构造了基础的功能,如果有错误,欢迎批评指正!!!

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值