vector模拟实现

Posted DR5200

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vector模拟实现相关的知识,希望对你有一定的参考价值。

一.vector接口总览

namespace lyp
{
	//模拟实现vector
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//默认成员函数
		vector();                                           //构造函数
		vector(size_t n, const T& val);                     //构造函数
		template<class InputIterator>                      
		vector(InputIterator first, InputIterator last);    //构造函数
		vector(const vector<T>& v);                         //拷贝构造函数
		vector<T>& operator=(const vector<T>& v);           //赋值运算符重载函数
		~vector();                                          //析构函数

		//iterator
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//capacity
		size_t size()const;
		size_t capacity()const;
		void reserve(size_t n);
		void resize(size_t n, const T& val = T());
		bool empty()const;

		//modifiers
		void push_back(const T& x);
		void pop_back();
		void insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void swap(vector<T>& v);

		//access
		T& operator[](size_t i);
		const T& operator[](size_t i)const;

	private:
		iterator _start;        //指向容器的头
		iterator _finish;       //指向有效数据的尾
		iterator _endofstorage; //指向容器的尾
	};
}

二.vector模拟实现

默认成员函数

构造函数

(1). 构造函数1

构造一个空vector,size 和 capacity 为 0,将 _start,_finish, _endofstorage 都置为空指针即可

vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{}

(2). 构造函数2

用 n 个 val 值进行初始化,可以复用 resize 接口,但要注意调用resize()接口需要计算size()和capacity(),所以要将成员变量进行初始化

vector(size_t n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
		resize(n,val);
}

(3). 构造函数3

用一段迭代器区间进行初始化,由于不同类型的容器迭代器类型可能不同,因此设计成函数模板,将区间内的内容尾插入vector即可,但注意调用push_back接口通过 _finish == _endofstorage 判断是否满,需要初始化

template<class InputIterator>
vector(InputIterator first, InputIterator last)
// 调用push_back接口通过 _finish == _endofstorage 判断是否满,需要初始化
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

注意 : 构造函数3和构造函数2写在一起时,那么如果按照下面这样构造会优先调用函数模板,会因为对int类型解引用而报错,所以我们还应该重载两个版本

vector<int> v(10,5);
vector(int n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
		resize(n,val);
}
vector(long n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
		resize(n,val);
}

拷贝构造函数

传统写法 :
1). 新开辟一块和 v 同样容量的空间,更新 _start, _finish, _endofstorage
2). 将 v 中的数据拷贝到新开辟的空间中

//传统写法
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	_start = new T[v.capacity()]; //开辟一块和容器v大小相同的空间
	for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来
	{
		_start[i] = v[i];
	}
	_finish = _start + v.size(); //容器有效数据的尾
	_endofstorage = _start + v.capacity(); //整个容器的尾
}

注意 : 不要使用memcpy函数拷贝数据,如果数据是内置类型或浅拷贝的自定义类型,使用memcpy是没有什么问题的,但如果数据是需要深拷贝的自定义类型(string),问题就出现了,拷贝的数据和源数据指向同一块空间

因此,我们使用for循环依次赋值,调用string的赋值运算符重载完成深拷贝

现代写法 :

使用范围for进行遍历,变量e是v中元素的拷贝,如果v中元素是需要深拷贝的自定义类型,会调用拷贝构造函数构造e,从而使e和v中元素所指向的空间不一样 (auto& e : v 也可以,因为push_back在实现的时候还会调用深拷贝类型的赋值运算符重载)

//现代写法
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(v.capacity()); //调用reserve函数将容器容量设置为与v相同
	for (auto e : v) //将容器v当中的数据一个个尾插过来
	{
		push_back(e);
	}
}

赋值运算符重载

传统写法
1).释放原空间,新开一块容量和v一样大的空间,更新_start,_finish, _endofstorage
2).将v中的数据拷贝到新空间中

注意 : 不能使用memcpy进行拷贝

vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		delete[]_start; // 释放原空间
		_start = new T[v.capacity()]; // 开辟新空间
		for (size_t i = 0; i < v.size(); i++) // 拷贝数据
		{
			_start[i] = v[i];
		}
		_finish = _start + v.size(); // 更新_finish
		_endofstorage = _start + v.capacity(); // 更新_capacity
	}
	return *this;
}

现代写法
1).调用拷贝构造函数生成tmp对象
2).分别交换tmp和this的_start,_finish, _endofstorage

vector<T>& operator=(const vector<T>& v)
{
	if (this != &v) // 防止自己给自己赋值
	{
		vector<T> tmp(v); // 拷贝构造tmp对象
		swap(tmp); // 交换_start,_finish, _endofstorage
	}
	return *this;
}
vector<T>& operator=(vector<T>& v) // 拷贝构造v对象
{
	swap(v); // 交换_start,_finish, _endofstorage
	return *this;
}

析构函数

1). 判断容器是否为空,若为空无需析构
2). 若不为空,将空间释放掉,_start,_finish, _endofstorage置为空指针

~vector()
{
	// 不为空,空间释放掉
	if (_start)
	{
		delete[]_start;
	}
	// _start,_finish, _endofstorage置为空指针
	_start = _finish = _endofstorage = nullptr;
}

iterator

begin/end

begin()返回第一个元素的地址,end()返回最后一个元素下一位置的地址,为了能够让const对象调用,加入const版本的begin()和end()

iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator begin()
{
	return _start;
}
const_iterator end()
{
	return _finish;
}

capacity

size

返回容器中有效数据的个数,用 _finish - _start 即可

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

capacity

返回容器的容量,用_endofstorage - _start 即可

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

reserve

1). 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2). 当n小于对象当前的capacity时,什么也不做。

实现步骤
1). 新开辟一块空间,若容器为空,将_start,_finish指向新开辟空间的首元素地址, _endofstorage指向新开辟空间的最后一个元素下一个位置
2). 若容器不为空,将数据拷贝到新空间,释放掉旧空间,更新_start,_finish, _endofstorage的位置

注意 : 将数据拷贝到新空间,仍然不能用memcpy函数,因为对于需要深拷贝的自定义类型,使用memcpy函数以后,新开辟空间里的元素和原空间里的元素所指向的内存空间是一样的,当旧空间被释放时,会调用自定义类型的析构函数,从而使得新开辟空间里的元素指向的内存空间也被释放掉了

void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n]; // 新开辟一块空间
		// 容器不为空
		if (_start)
		{
			size_t sizecp = size(); // 计算原来容器size个数
			for (size_t i = 0; i < size(); i++) // 拷贝数据
			{
				tmp[i] = _start[i];
			}
			delete[]_start; // 释放旧空间
			_start = tmp; // 更新_start
			_finish = _start + sizecp; // 更新_finish
			_endofstorage = _start + n; // 更新_endofstorage
		}
		// 容器为空,更新_start,_finish,_endofstorage的位置
		else
		{
			_start = _finish = tmp;
			_endofstorage = _start + n;
		}
	}
}

resize

1). 当 n < size 时,直接将 _finish = _start + n (将有效数据长度缩小)即可
(2).当 size < n <= capacity 时,我们将有效数据的长度增加到 n,增加出来的有效数据内容是val
(3).当 n > capacity时,先调用上面的 reserve 函数进行增容,再将有效数据的长度增加到 n,增加出来的有效数据内容是val

void resize(size_t n,const T& val = T())
{
	// 第一种 n < size()
	if (n < size())
	{
		_finish = _start + n;
	}
	// n > size()
	else
	{
		// 增容
		if (n > capacity())
			reserve(n);
		// 填充数据val
		size_t count = n - size();
		while (count--)
		{
			*_finish = val;
			++_finish;
		}
	}
}

empty

判断 size() == 0 即可

bool empty()const
{
	return size() == 0;
}

modifiers

push_back

尾插入数据,首先要检查是否已满,已满则进行增容,增容后尾插即可

void push_back(const T& val)
{
	// 增容
	if (_finish == _endofstorage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	// 尾插入数据
	*_finish = val;
	// 使 size++
	++_finish;
}

pop_back

尾删时,首先要判断容器是否为空,若为空,则断言报错,不为空,_finish-- 即可

void pop_back()
{
	assert(!empty()); // 断言处理
	--_finish;
}

insert

1). 容量不够,先增容,增容之前先记录下 pos - _start 的值,否则增容之后,pos 还指向原来已经被释放的空间
2). 将 pos 位置往后的数据往后挪动一位,在pos位置插入值val

void insert(iterator pos, const T& val)
{
	// 增容
	if (_finish == _endofstorage)
	{
		// 记录pos相对于_start的位置
		size_t poscp = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		// 更新pos位置
		pos = _start + poscp;
	}
	// 挪动pos位置后的数据
	for (iterator i = _finish - 1; i != pos; i--)
	{
		*(i + 1) = *i;
	}
	// 在pos位置插入值val
	*pos = val;
	// 使size++
	++_finish;
}

erase

容器若为空,则做断言处理,若不为空,将pos位置往后的数据向前挪动一位

iterator erase(iterator pos)
{
	// 断言
	assert(!empty());
	// 将pos位置往后的数据向前挪动一位
	for (iterator i = pos + 1; i != _finish; i++)
	{
		*(i - 1) = *i;
	}
	// --size
	--_finish;
	// 返回删除数据的下一个位置
	return pos;
}

access

operator[]

返回底层数组对应位置的引用即可

T& operator[](size_t i)
{
	// 检查下标有效性
	assert(i < size());
	// 返回对应位置的引用
	return _start[i];
}
const T& operator[](size_t i)const
{
	// 检查下标有效性
	assert(i < size());
	// 返回对应位置的引用
	return _start[i];
}

以上是关于vector模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

vector之三(模拟实现vector)

vector之三(模拟实现vector)

vector使用+模拟实现

C++vector模拟实现

C++vector模拟实现

C++初阶vector(中)