C++ STL --- vector类模拟实现

目录

1.构造模块

  (1)无参构造

  (2)半缺省构造

  (3)区间构造的必备知识

  (4)区间构造

  (5)拷贝构造

  (6)赋值运算符重载

  (7)析构函数

2.迭代器模块

  (1)正向迭代器

  (2)反向迭代器

3.容量模块

  (1)获取有效元素个数

  (2)获取容量大小

  (3)判空函数

  (4)扩容函数

  (5)设置有效元素个数

4.元素访问模块

  (1)获取首元素

  (2)获取尾元素

  (3)重载[]

5.修改模块

  (1)尾插

  (2)尾删

  (3)任意位置插入   

  (4)指定位置删除

  (5)清空有效元素

6.总体代码


        vector是这是C++中的典型容器,它是动态类型的顺序表任何类型的元素都可以存放。接下来将在以下几个方面进行模拟实现:构造模块、迭代器模块、容量模块、元素访问模块、修改模块

        因为类中的函数非常多,所以只着重实现一些常用的函数。(本文代码均在win10系统下的vs2019中验证)

1.构造模块

        在构造模块,先来思考一下该设置些什么成员变量?首先要有一个 _start 指针指向一块空间,其次一个 _finish 指针指向这块空间中最有一个有效元素的下一个位置,_end指针指向空间的最后一个位置的下一个位置。而空间中的有效元素个数和容量可以通过指针相减得到。

        用下面这幅图理解一下三个指针的位置:总空间有8个位置,前五个位置有元素,_finish指向第六个位置,_end需要指向第八个位置后面的空间。当有效元素把位置占满的时候,_finish和_end自然就指向同一块空间。

C++ STL --- vector类模拟实现_第1张图片

  (1)无参构造

        由于vector类是可以存放任意类型的元素的,要模拟实现它自然也要做到存放任意类型。那么就需要用到模板了。T是存放的元素类型,T*是指向T类型元素的指针。

        代码一:注意创建对象时必须声明存储什么类型的对象。语法:Vector v

        既然是空参构造,那么说明现在并不给对象中存放元素,那直接把三个指针都置空就好。

//代码一
#include "iostream"
using namespace std;

//Vector类存储的数据是任意类型,所以需要设置模板参数
template 
class Vector {
public:
	T* _start;//指向T类型元素的首元素的指针
	T* _finish;//指向最后一个有效元素下一个位置
	T* _end;//指向顺序表空间最后一个位置的下一个位置。
public:
	Vector() 
		:_start(nullptr)
		,_finish(nullptr)
		,_end(nullptr)
	{}
};

int main() {
	Vectorv;
}

  (2)半缺省构造

        代码二:这里n的类型一定要给成int,不可以给成size_t。不然运行时会遇到错误,在区间构造时就知道了。

//代码二
Vector(int n, const T& val = T())
	:_start(new T[n])//在初始化时直接从堆上申请空间
{
	for (int i = 0; i < n; i++) {
		_start[i] = val;
	}
	//_finish指向最后一个有效元素的下一个位置
	_finish = _start + n;
	//_end指向顺序表最后一个位置的下一个位置
	_end = _finish;
}

  (3)区间构造的必备知识

        vector类的区间构造很强大,不仅是在使用方法中介绍的利用数组来进行区间构造,也可以用链表来进行区间构造,所以实现时要格外注意不可以直接拷贝,需要将区间内元素遍历一遍才可以。

        在区间构造时候因为可以用链表来构造,所以传入的迭代器自然也是链表的迭代器,这样一来,T类型的迭代器就不能满足条件了,所以我们也要为迭代器设置模板参数,让任意类型的迭代器都可以进行区间构造。

  (4)区间构造

        代码三:这里搭配测试用例解释一下第二个循环。

        看一下区间构造,还记得上面为什么需要把参数类型给成int吗?如果你想构造对象时初始化10个5,是不是这样写 Vectorv(10,5)

        模板不会进行类型转换,根据你的参数推演,如果能找到匹配的就使用。检测到size_t发现不匹配就继续找,找到区间构造后发现可以使用,因为它可以把 Iterator 当成int类型。

        可是原本的迭代器是需要解引用的,现在对整形数字解引用,可不就出错了么?

//代码三
template
Vector(Iterator first, Iterator last) {
	Iterator it = first;
	size_t num = 0;//统计元素个数
	while (it != last) {
		num++;
		it++;
	}

	_start = new T[num];
	_finish = _start;
	while (first != last) {
		*_finish = *first;
		_finish++;
		first++;
	}
	_end = _finish;
}

int main() {
	int arr[] = { 1,2,3,4,5,6 };
	Vectorv(arr,arr+sizeof(arr)/sizeof(arr[0]));
}

        搭配图片理解一下第二个循环:

        arr指向的是首元素,sizeof(arr)/sizeof(arr[0]) 是求数组元素个数,共6个。arr+6就指向了数组外的空间。当first移动到last位置说明元素遍历完毕,此时对应的_finish也指向了最后一个有效元素的后一个位置。(同时这也是顺序表的最后一个位置的后一个位置了,因为申请时只申请了这么多空间)

        重点来了:注意first迭代器也是可以++的,这是因为对应的类中现在基本都会重载++,这样链表也就不需要用指针指向next这样的操作来访问了,会方便很多,也更直观。

C++ STL --- vector类模拟实现_第2张图片

  (5)拷贝构造

        代码四:

//代码四
Vector(const Vector& v)
	:_start(new T[v.Size()]) //这里用到的Size函数后面会实现的
{
	size_t num = v.Size();
	for (size_t i = 0; i < num; i++) {
		_start[i] = v._start[i];
	}
	//让对应的指针指向应该指向的位置
	_finish = _start + num;
	_end = _finish;
}

  (6)赋值运算符重载

        代码五:因为这里传递的是值,会发生值拷贝,产生临时对象,那么我们把两个对象的指针指向的空间交换即可。一定要返回,因为要实现连续赋值。

//代码五:
Vector& operator=(const Vector v) {
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end, v._end);
	return *this;
}

  (7)析构函数

        代码六:这里检查指针是否为空,虽然经过测试指针指向空值时,delete[] 指针不会报错,但出于谨慎,还是加上检查比较好。

//代码六
~Vector() {
	if (_start) {
		delete[] _start;
	}
	_start = _finish = _end = nullptr;
}

2.迭代器模块

        迭代器模块的模板参数要单独给出,不可以使用区间构造那块的,因为迭代器不涉及多种元素,它只返回本类的元素迭代器,只有一种。

  (1)正向迭代器

        代码七:

//代码七
typedef T* iterators;

iterators Begin() {
	return _start;
}

iterators End() {
	return _finish;
}

  (2)反向迭代器

        代码八:反向迭代器可以直接复用正向迭代器。

//代码八
typedef T* iterators;

iterators Rbegin() {
	return End();
}

iterators Rend() {
	return Begin();
}

3.容量模块

  (1)获取有效元素个数

        这里就可以体会到指针的便捷了,直接相减就得到了元素个数。

        代码九:

//代码九
size_t Size() const{
	return _finish - _start;
}

  (2)获取容量大小

        代码十:道理同上。

//代码十
size_t Capacity()const {
	return _end - _start;
}

  (3)判空函数

      代码十一:  这两个指针相同说明并没有元素的存在。

//代码十一
bool Empty()const {
	return _start == _finish;
}

  (4)扩容函数

           代码十二:只考虑扩大,不考虑缩小。

//代码十三
void Reserve(size_t newCapacity) {
	size_t oldCapacity = Capacity();
	size_t oldSize = Size();

	T* temp = new T[newCapacity];
	//当要设置的新空间大于旧空间
	if (newCapacity > oldCapacity) {
		//判断首指针是否是空
		if (_start) {
			//进行深拷贝
			//思考:memcpy可以使用吗?
			for (size_t i = 0; i < oldSize; i++) {
				temp[i] = _start[i];
			}
			//释放旧空间
			delete[] _start;
		}
		_start = temp;
		_finish = _start + oldSize;
		_end = _start + newCapacity;
	}	
}

    在扩容函数中思考一个问题,为什么没有使用memcpy函数?原因是memcpy是按字节拷贝,会有程序崩溃的风险。使用下面的例子来验证。

        代码十三:举例,假设使用扩容函数中使用memcpy函数,并且vector中存储S类型的对象。这里因为是举例子,所以代码给的很粗糙,理解一下意思就好。

        首先使用s1和s2构造v对象,然后进行尾插,这样会导致扩容。

//代码十三
class S {
public:
	char* str;
};

int main(){
    S s1;
    S s2;
    vector v{s1,s2};
    
    S s3;
    v.push_back(s3);
}

        下面这幅图就是验证图。

        S对象内存储的是指针,构造Vector时里面有两个S类型的元素,两个s元素中分别有一个指针指向一个有"\0"元素的空间。然后尾插一个元素,导致扩容。

        扩容后使用memcpy按字节将vector中的元素拷贝到新空间中。一切看起来都那么完美。可是旧的vector对象中地址指针(蓝色的地址线)需要释放,释放后新空间中的指针(红色地址线)却依然指向被释放的空间。这不就是野指针吗?这样一来,一旦试图访问这块空间,必然程序崩溃。

C++ STL --- vector类模拟实现_第3张图片

  (5)设置有效元素个数

        这里要告诉大家,不要使用memset函数。因为memset函数也是按字节将元素直接放到指定空间中。C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

        字符的字节是1字节,如果你要用它放n个T类型数据到指向的空间,它是怎么做的?它会把T类型的数据往空间中的前n个字节,每个字节都放一个!

        代码十四:

//代码十四:
void Resize(size_t newSize, const T& val = T()) {
	size_t oldCapacity = Capacity();
	size_t oldSize = Size();
	//要设置的有效元素个数大于现在的有效元素
	if (newSize > oldSize) {
		//要设置的有效元素个数大于现有空间,扩容
		if (newSize > oldCapacity) {
			Reserve(newSize);
		}
		//思考:memeset可以用吗?
		for (size_t i = oldSize; i < newSize; i++) {
			_start[i] = val;
		}
	}
	_finish = _start + newSize;
}

4.元素访问模块

  (1)获取首元素

        代码十五:这里给出两种类型,以便于const对象使用。

//代码十五
T& Front() {
	return _start[0];
}
const T& Front()const {
	return _start[0];
}

  (2)获取尾元素

        代码十六:两种类型,道理同上。

//代码十六
T& Back() {
	return *(_finish - 1);
}
const T& Back()const {
	return *(_finish - 1);
}

  (3)重载[]

        代码十七:也是两种类型。注意assert函数使用时需要包含头文件。

//代码十七
#include 
T& operator[](size_t index) {
	//需要判断是否越界
	assert(-1

5.修改模块

  (1)尾插

        代码十八:尾插可能需要扩容,一旦扩容,就给的空间大一点,尽量减少扩容次数。

//代码十八
void Push_back(const T& val) {
	//判断顺序表是否已经存满了
	if (_finish == _end) {
		Reserve(Capacity() * 2);
	}
	*_finish = val;
	_finish++;
}

  (2)尾删

        代码十九:

//代码十九
void Pop_back() {
	if (Empty()) {
		return;
	}
	_finish--;
}

  (3)任意位置插入   

        代码二十:  需要考虑越界问题

//代码二十
iterators Insert(iterators index, const T& val) {
	//判断迭代器是否越界
	if (index < _start || index > _finish)
		return End();
	//判断空间是否存满
	if (_start == _finish)
		Reserve(Capacity() * 2);

	auto it = _finish;
	while (it != index) {
		*it = *(it - 1);
		it--;
	}
	*index = val;
	_finish++;
	return index;
}

  (4)指定位置删除

        代码二十一:需要考虑越界问题

//代码二十一
iterators Erase(iterators index) {
	//判断迭代器是否越界
	if (index < _start || index > _finish)
		return End();
	auto it = index;
	//将index后的元素往前搬移一位
	while (it != _finish) {
		*it = *(it + 1);
		it++;
	}
	_finish--;
	return index;
}

  (5)清空有效元素

        代码二十二:

//代码二十二
void Clear() {
	_finish = _start;
}

6.总体代码

        将代码进行调整后改成整体代码如下:

#include 
#include "iostream"
using namespace std;

//Vector类存储的数据是任意类型,所以需要设置模板参数
template 
class Vector {
private:
	T* _start;//指向T类型元素的首元素的指针
	T* _finish;//指向最后一个有效元素下一个位置
	T* _end;//指向有效空间的下一个位置。
public:
//构造模块
//
	//无参构造
	Vector() 
		:_start(nullptr)
		,_finish(nullptr)
		,_end(nullptr)
	{}
	//半缺省构造
	Vector(int n, const T& val = T())
		:_start(new T[n])//在初始化时直接从堆上申请空间
	{
		for (int i = 0; i < n; i++) {
			_start[i] = val;
		}
		//_finish指向最后一个有效元素的下一个位置
		_finish = _start + n;
		//_end指向顺序表最后一个位置的下一个位置
		_end = _finish;
	}
	//区间构造
	template
	Vector(Iterator first, Iterator last) {
		Iterator it = first;
		size_t num = 0;//统计元素个数
		while (it != last) {
			num++;
			it++;
		}

		_start = new T[num];
		_finish = _start;
		while (first != last) {
			*_finish = *first;
			_finish++;
			first++;
		}
		_end = _finish;
	}
	//拷贝构造函数
	Vector(const Vector& v)
		:_start(new T[v.Size()]) //这里用到的Size函数后面会实现的
	{
		size_t num = v.Size();
		for (size_t i = 0; i < num; i++) {
			_start[i] = v._start[i];
		}
		//让对应的指针指向应该指向的位置
		_finish = _start + num;
		_end = _finish;
	}
	//赋值运算符重载
	Vector& operator=(const Vector v) {
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_end, v._end);
		return *this;
	}
	//析构函数
	~Vector() {
		if (_start) {
			delete[] _start;
		}
		_start = _finish = _end = nullptr;
	}
//迭代器模块
//
	typedef T* iterators;
	//正向迭代器 
	iterators Begin() {
		return _start;
	}
	iterators End() {
		return _finish;
	}
	
	//反向迭代器
	iterators Rbegin() {
		return End();
	}
	iterators Rend() {
		return Begin();
	}
//容量模块
//
	//有效元素个数
	size_t Size() const{
		return _finish - _start;
	}
	//容量大小
	size_t Capacity()const {
		return _end - _start;
	}
	//判空函数
	bool Empty()const {
		return _start == _finish;
	}
	//扩容函数
	void Reserve(size_t newCapacity) {
		size_t oldCapacity = Capacity();
		size_t oldSize = Size();

		T* temp = new T[newCapacity];
		//当要设置的新空间大于旧空间
		if (newCapacity > oldCapacity) {
			//判断首指针是否是空
			if (_start) {
				//进行深拷贝
				//思考:memcpy可以使用吗?
				for (size_t i = 0; i < oldSize; i++) {
					temp[i] = _start[i];
				}
				//释放旧空间
				delete[] _start;
			}
			_start = temp;
			_finish = _start + oldSize;
			_end = _start + newCapacity;
		}	
	}
	//设置有效元素函数
	void Resize(size_t newSize, const T& val = T()) {
		size_t oldCapacity = Capacity();
		size_t oldSize = Size();
		//要设置的有效元素个数大于现在的有效元素
		if (newSize > oldSize) {
			//要设置的有效元素个数大于现有空间,扩容
			if (newSize > oldCapacity) {
				Reserve(newSize);
			}
			//思考:memeset可以用吗?
			for (size_t i = oldSize; i < newSize; i++) {
				_start[i] = val;
			}
		}
		_finish = _start + newSize;
	}
//元素访问模块
//
	//获取首元素
	T& Front() {
		return _start[0];
	}
	const T& Front()const {
		return _start[0];
	}
	//获取尾部元素
	T& Back() {
		return *(_finish - 1);
	}
	const T& Back()const {
		return *(_finish - 1);
	}
	//重载[]
	T& operator[](size_t index) {
		//需要判断是否越界
		assert(-1 _finish)
			return End();
		//判断空间是否存满
		if (_start == _finish)
			Reserve(Capacity() * 2);

		auto it = _finish;
		while (it != index) {
			*it = *(it - 1);
			it--;
		}
		*index = val;
		_finish++;
		return index;
	}
	//任意位置删除
	iterators Erase(iterators index) {
		//判断迭代器是否越界
		if (index < _start || index > _finish)
			return End();
		auto it = index;
		//将index后的元素往前搬移一位
		while (it != _finish) {
			*it = *(it + 1);
			it++;
		}
		_finish--;
		return index;
	}
	//清空有效元素
	void Clear() {
		_finish = _start;
	}
};

int main() {
	Vector v(10, 5);
	v.Push_back(9);
	v.Erase(v.End());
}

你可能感兴趣的:(C++,c++,STL,vector,容器,vector模拟实现)