【C++修行之路】STL——模拟实现vector

前言

大家好久不见,今天我们一起来模拟实现一下STL中的vector容器。

模拟

类框架

因为物理空间连续,所以我们选择直接将原生指针作为迭代器,且整个vector容器的容量(capacity)和大小(size)均是采用三个迭代器相减来表示,有关迭代器前开后闭,我在string的模拟实现中已经讲解,不理解的可以回去看看。

namespace teacher
{
    
    
	template<class T>
	class vector
	{
    
    
	public:
		//原生指针作为迭代器
		typedef T* iterator;
		typedef const T* const_iterator;
		//容量和大小
		int capacity() const
		{
    
    
			return _end_of_storage - _start;
		}
		int size() const
		{
    
    
			return _finish - _start;
		}

		//迭代器
		iterator begin()
		{
    
    
			return _start;
		}
		iterator end()
		{
    
    
			return _finish;
		}

		const_iterator begin() const
		{
    
    
			return _start;
		}
		const_iterator end() const
		{
    
    
			return _finish;
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

构造和析构

不管是构造函数还是拷贝构造,都是要把元素一个一个的赋值上去,因此先写一个push_back函数,在其他构造中调用即可。

另外c++允许在一个模板里的函数使用模板。

需要说明的是,匿名对象的生命周期只在那一行,但如果是const T& x 修饰,生命周期就会延长到x结束为止。

vector() {
    
    };
vector(size_t n, const T& val = T())
{
    
    
	reserve(n);
	for (int i = 0; i < n; i++)
	{
    
    
		push_back(val);
	}
}
vector(int n, const T& val = T())
{
    
    
	reserve(n);
	for (int i = 0; i < n; i++)
	{
    
    
		push_back(val);
	}
}

template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
    
    
	while (first != last)
	{
    
    
		push_back(*first);
		++first;
	}
}

vector(const vector<T>& v)
{
    
    
	vector<T> tmp(v.begin(),v.end());
	swap(tmp);
}

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

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


深拷贝与浅拷贝问题

当我们的vector内存有指向其他位置的指针的时候,简单的字节拷贝(值拷贝)会让两个vector指向同一个位置。这个问题非常严重,因为如果指向同一位置,意味着要析构多次,这会让我们的程序崩溃,因此我们首先来解决一下深浅拷贝的问题。

如下图所示的扩容逻辑,可以看到我们为vector的每一个元素都重新开辟了空间,这样可以避免浅拷贝的问题。

void reserve(size_t n)
{
    
    
	size_t sz = size();
	// 容量不够才进行扩容
	if (n > capacity())
	{
    
    
		T* tmp = new T[n];
		//拷贝原来的内容
		if (_start)
		{
    
    
			for (int i = 0; i < sz; i++)
			{
    
    
				tmp[i] = _start[i];
			}
			delete[] _start;
		}
	}
	_start = tmp;
	_finish = _start + sz;
	_end_of_storage = _start + n;
}

但这样写也会有一个很严重的问题,那就是 tmp[i] = _start[i] 这一步其实也是一个浅拷贝,即会出现如图所示的问题:
在这里插入图片描述
为了解决这个问题,我们需要手写一个深拷贝的 赋值重载

void swap(vector<T>& v)
{
    
    
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

vector<T> operator=(vector<T> v)
{
    
     
	//v是临时对象,传过来完成了拷贝构造,走了push back的逻辑,因此再交换是开了新空间的。
	swap(v);
	return *this;
}

[]重载

因为我们习惯了数组的[]读取数据,并且vector的内存连续,所以我们重载一下[]

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

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

insert和erase

逻辑与string的模拟实现基本一样,因为这里是迭代器也不用考虑npos的问题。

尽管这里都返回了迭代器,但使用这两个函数后,我都建议你不要再使用pos,认为pos已经失效!

iterator insert(iterator pos, const T& val)
{
    
    
	if (_finish == _end_of_storage)
	{
    
    
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 20);
		pos = _start + len;
		//扩容后更新pos 防止pos失效
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
    
    
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	++_finish;
	return pos;
}

iterator erase()
{
    
    
	iterator start = pos + 1;
	while (start != _finish)
	{
    
    
		*(start - 1) = *start;
		++start;
	}
	--_finish;
	return pos;
}

push_pop、empty、resize

这三个函数较为简单,一笔带过

bool empty()
{
    
    
	return _start == _finish;
}
void pop_back()
{
    
    
	assert(!empty());
	--_finish;
}

void resize(size_t n,T val = T())
{
    
    
	if (n < size())
	{
    
    
		_finish = _start + n;
	}
	else
	{
    
    
		if (n > capacity())
		{
    
    
			reserve(n);
		}

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

结语

以上就是本篇文章全部内容了,希望对你有所收获,我们下次再见

猜你喜欢

转载自blog.csdn.net/m0_73209194/article/details/129471841