C++ vector模拟实现

C++ vector模拟实现

  • 一.我们要实现的大致框架
    • 1.STL库中是如何实现的呢?
      • 1.迭代器
      • 2.成员变量
      • 3.vector的特性
      • 4.vector的成员变量大致情况
    • 2.我们要实现的大致框架
    • 3.前言
  • 二.具体实现
    • 1.迭代器,begin,end
    • 2.无参构造,析构,简单函数
    • 3.push_back
    • 4.reserve
      • 1.reserve的第一大坑点:野指针问题
      • 1.reserve的第二大坑点:浅拷贝问题
      • 3.正确版本
    • 5.resize
    • 6.insert
      • 1.insert内部的迭代器失效问题
      • 2.insert外部的迭代器失效问题
    • 7.erase
      • 1.erase的实现
      • 2.erase外部的迭代器失效问题
    • 8.push_back和pop_back对于insert和erase的复用
      • 1.push_back
      • 2.pop_back
    • 9.含参构造
    • 10.迭代器区间构造
    • 11.拷贝构造
      • 1.传统写法
      • 2.现代写法
    • 12.赋值运算符重载
      • 1.传统写法
      • 2.现代写法
  • 三.完整代码

首先我们先明确一点:
对于vector而言,最难的点是
1.它如何进行设计与封装的
2.迭代器失效问题
3.memcpy,memmove导致的浅拷贝问题
而不是顺序表的基础操作

一.我们要实现的大致框架

1.STL库中是如何实现的呢?

1.迭代器

在这里插入图片描述
vector中的迭代器其实就是指针
因为vector的底层物理空间是连续的(vector其实就是数据结构中的顺序表)

2.成员变量

在这里插入图片描述
也就是说STL库中的vector容器维护的是3个指针:start finish end_of_storage
关于这三个指针大家一定要牢记它们的作用,因为下面全是对这三个指针的操作

3.vector的特性

注意:vector作为STL库中的容器,是采用泛型编程和面向对象的思想来设计的
vector的元素不仅仅可以是内置类型,也可以是自定义类型,其他容器类型
其实是因为vector采用了类模板的技术,因此vector可以存放所有类型的数据
例如

vector<string>
vector<vector<xxx>>
vector<int>
vector<list>
等等等等....

4.vector的成员变量大致情况

其实vector中成员变量大致情况就是这样的
也就是说接下来我们所有的操作都是通过操作
_start _finish _endOfStorage这三个指针来完成的

#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace wzs
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		
	private:
		iterator _start;		// 指向数据块的开始
		iterator _finish;		// 指向有效数据的尾
		iterator _endOfStorage;  // 指向存储容量的尾
	};
}

2.我们要实现的大致框架

#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace wzs
{
	template<class T>
	class vector
	{
	public:
		///
		// 构造,拷贝构造,赋值运算符重载,析构
		vector();

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

		template<class InputIterator>
		//迭代器区间构造
		vector(InputIterator first, InputIterator last);
		
		//拷贝构造函数传统写法
		vector(const vector<T>& v);

		//拷贝构造函数现代写法
		vector(const vector<T>& v);
		
		//赋值运算符重载传统写法
		vector<T>& operator=(const vector<T>& v);
		
		//赋值运算符重载现代写法
		vector<T>& operator=(vector<T> v);

		~vector();

		/
		// 迭代器相关
		// vector的迭代器是一个原生指针
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin();

		iterator end();

		const_iterator begin() const;

		const_iterator end() const;

		//
		// 容量相关
		size_t size() const;

		size_t capacity() const;

		void reserve(size_t n);
		
		void resize(size_t n, const T& value = T());

		bool empty() const;

		///
		// 元素访问
		
		//operator[]运算符重载
		T& operator[](size_t pos);

		const T& operator[](size_t pos)const;
		
		//返回第一个数据的引用
		T& front();
		
		const T& front()const;
		
		//返回最后一个有效数据的引用
		T& back();

		const T& back()const;

		/
		// vector的修改操作
		
		void push_back(const T& x);
		
		void pop_back();

		void swap(vector<T>& v);
		
		//返回插入的元素的位置
		iterator insert(iterator pos, const T& x);
		
		//返回被删除的位置的下一个位置
		iterator erase(iterator pos);
	private:
		iterator _start;		// 指向数据块的开始
		iterator _finish;		// 指向有效数据的尾
		iterator _endOfStorage;  // 指向存储容量的尾
	};
}

3.前言

为了更好地演示整个实现过程,
首先我们先实现
1.迭代器
2.无参构造函数,析构函数和其他一些很简单的函数
3.push_back
4.reserve
5.resize
6.insert
7.erase 然后是push_back和pop_back的复用
8.含参构造函数
9.迭代器区间构造函数
10.拷贝构造函数和赋值运算符重载

期间
在介绍reserve的时候我们会介绍本文的第一个重点:
memcpy/memmove导致的浅拷贝问题

在介绍insert和erase的时候我们会介绍本文的第二个重点:
迭代器失效问题

二.具体实现

1.迭代器,begin,end

刚才说明了vector的迭代器其实就是指针类型,因此我们就可以这么来定义

typedef T* iterator;
typedef const T* const_iterator;

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

对于vector来说,iterator很简单,就是原生指针

2.无参构造,析构,简单函数

无参构造:
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_endOfStorage(nullptr)
{}
析构:
~vector()
{
	delete[] _start;
	_start = _finish = _endOfStorage = nullptr;
}
// 容量相关的简单函数
size_t size() const
{
	return _finish - _start;
}

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

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

// 元素访问的简单函数
T& operator[](size_t pos)
{
	return _start[pos];
}

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

T& front()
{
	return *_start;
}

const T& front()const 
{
	return *_start;
}

T& back()
{
	return *(_finish - 1);
}

const T& back()const
{
	return *(_finish - 1);
}

注意:
front是第一个有效数据的引用
back是最后一个有效数据的引用
operator[]是下标访问运算符重载,跟数组的下标访问是一样的用法

3.push_back

下面我们就来实现push_back

void push_back(const T& x)
{
	//扩容
	if (size() == capacity())
	{
		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	//尾插
	*_finish = x;
	++_finish;
}

先扩容,后尾插

4.reserve

相信上面的那些函数对大家来说没有什么挑战
下面我们就来看一下reserve的实现过程中的坑点吧
reserve函数:扩容函数

1.reserve的第一大坑点:野指针问题

void reserve(size_t n);

如果n<=capacity:那么就不会进行任何操作(在这里我们不考虑缩容的情况,reserve是否可以缩容取决于编译器的具体实现)
只有当n>capacity时才会扩容

大家看一下这份代码有问题吗?

void reserve(size_t n)
{
	if (n > capacity())
	{
		//1.申请新空间
		T* tmp = new T[n];
		//2.将原有数据拷贝到新空间当中
		memmove(tmp, _start, sizeof(T) * size());
		//3.释放原有空间
		delete _start;
		//4.指向新空间
		_start = tmp;
		_finish = _start + size();
		_endOfStorage = _start + capacity();
	}
}

其实是有问题的,_finish和_endOfStorage会成为野指针
原因如下:
在这里插入图片描述

给大家调试来看一下:
在这里插入图片描述
可以看出,扩容结束之后,_finish和_endOfStorage仍然还是指向旧空间的对应位置

那么应该怎么办呢?
其实我们可以把旧空间的size保存下来
记为oldSize,这样只需要

_finish = _start + oldSize;  即可将_finish也指向新空间的相应位置

而_endOfStorage呢?
因为新空间的容量是n
所以

_endOfStorage = _start + n;  即可将_endOfStorage也指向新空间的相应位置

因此下面的代码才是"正确"的

void reserve(size_t n)
{
	if (n > capacity())
	{
		//1.保存原有空间的size
		int oldSize = size();
		//2.开辟新空间
		T* tmp = new T[n];
		//3.将原有空间的数据拷贝到新空间当中
		memmove(tmp, _start, sizeof(T) * oldSize);
		//4.释放旧空间
		delete _start;
		//5.指向新空间
		_start = tmp;
		_finish = _start + oldSize;
		_endOfStorage = _start + n;
	}
}

分为5步:
1.保存原有空间的size
2.开辟新空间
3.将原有空间的数据拷贝到新空间当中
4.释放旧空间
5.指向新空间

下面我们来调试看一下:
在这里插入图片描述

1.reserve的第二大坑点:浅拷贝问题

刚才那个代码其实也是不正确的
不过他不正确的原因是因为memmove的底层实现其实是浅拷贝
是以字节为单位进行拷贝的

因为刚才我们这个vector里面存放的数据类型是int这种内置类型
而对于内置类型来说是不会受到浅拷贝的影响的
不过对于开辟在堆上的自定义类型来说就会受到浅拷贝的影响导致出现同一内存空间多次释放的错误

比方说此时vector里面存放的是string类型
在这里插入图片描述
第二次扩容之前是没有任何问题的
不过当他发生了扩容之后
在这里插入图片描述
崩了,断言报错
为什么呢?
在这里插入图片描述
而且:
delete的时候会先调string的析构函数把string都析构(string的空间在string的析构函数当中释放)了,然后才会释放_start这个旧空间

3.正确版本

因为我们实现的在堆上开辟了空间的自定义类型都是有赋值运算符重载的,而我们实现的赋值运算符重载都是深拷贝
因此我们可以这样修改

void reserve(size_t n)
{
	if (n > capacity())
	{
		//提前保存偏移量oldSize
		int oldSize = size();
		//1.申请新空间
		T* tmp = new T[n];
		//2.将原有空间中的数据拷贝到新空间当中
		for (int i = 0; i < oldSize; i++)
		{
			tmp[i] = _start[i];
			//内置类型直接赋值即可,自定义类型会调用其赋值运算符重载,实现深拷贝
		}
		//3.释放原有空间
		delete[] _start;
		//4.将_start,_finish,_endOfStorage都指向到新空间
		_start = tmp;
		_finish = _start + oldSize;
		_endOfStorage = _start + n;
	}
}

在这里插入图片描述
此时就没有任何问题了

5.resize

resize:调整有效数据的个数

void resize(size_t n, const T& value = T())

作用是:
1.n<size:只保留该对象的前n个数据,其余数据全都删除

2.size<=n<=capacity:尾插value,直到该对象的size==n
同含参构造,value是缺省参数,默认值是T()

3.n>capacity:扩容+尾插数据
就是在进行尾插之前因为容量不够而需要扩容
扩容结束之后继续尾插

void resize(size_t n, const T& value = T())
{
	//1.n<size:删除数据
	if (n < size())
	{
		//将_finish移动到_start向后偏移n个单位的位置
		//我们知道:[_start,_finish)才是有效数据
		//因此就是只让前n个数据作为有效数据
		_finish = _start + n;
	}
	//2.size<=n<=capacity:  尾插数据
	else if (n <= capacity())
	{
		while (size() < n)
		{
			push_back(value);
		}
	}
	//3.n>capacity:扩容+尾插数据
	else
	{
		reserve(n);
		while (size() < n)
		{
			push_back(value);
		}
	}
}

注意:
n<size时:
我们知道:[_start,_finish)才是有效数据
因此_finish = _start + n;就是只让前n个数据作为有效数据

6.insert

1.insert内部的迭代器失效问题

大家看一下这个代码有没有问题?

iterator insert(iterator pos, const T& x)
{
	//检查pos的合法性
	assert(pos >= _start && pos <= _finish);
	//需要扩容的话扩容
	if (size() == capacity())
	{
		//扩容
		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	//开始insert
	//把[pos,_finish)的数据往后挪
	
	iterator end = _finish;
	while (end > pos)
	{
		*end = *(end - 1);
		--end;
	}
	
	//插入数据
	*pos = x;
	//更新size
	++_finish;
	return pos;
}

在这里插入图片描述
此时崩了,为什么呢?
我们来调试看一下:
在这里插入图片描述
我们可以看出:是因为扩容之后pos这个迭代器没有指向新空间
因此就会发生各种任何意想不到的错误
下面给大家画了一张图来帮助大家理解
在这里插入图片描述
经过前面reserve的野指针问题的启发,我们就能够很好地解决这个问题:

//发生扩容之后pos迭代器会失效,因此需要在扩容之前先保存偏移量,并在扩容之后重新调整pos迭代器的位置
iterator insert(iterator pos, const T& x)
{
	//检查pos的合法性
	assert(pos >= _start && pos <= _finish);
	//需要扩容
	if (size() == capacity())
	{
		//先保存pos的偏移量
		int gap = pos - _start;
		//扩容
		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
		//调整pos的位置
		pos = _start + gap;
	}
	//开始insert
	//把[pos,_finish)的数据往后挪
	
	iterator end = _finish;
	while (end > pos)
	{
		*end = *(end - 1);
		--end;
	}
	//插入数据
	*pos = x;
	//更新size
	++_finish;
	return pos;
}

在这里插入图片描述
此时就能成功运行了

2.insert外部的迭代器失效问题

大家可能会有一些疑惑:为什么insert要有返回值呢?
而且为什么返回值类型是iterator呢?
因为
在这里插入图片描述
所以设计STL库的大佬就规定:vector里面的insert需要返回插入的数据的迭代器
因此我们可以通过接收返回值的方式来让it这个迭代器"续命"
在这里插入图片描述

7.erase

1.erase的实现

iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);
	//保存pos
	iterator tmp = pos;
	//把[pos+1,_finish)的数据往前挪一格
	while (pos < _finish - 1)
	{
		*pos = *(pos + 1);
		++pos;
	}
	//调整size
	--_finish;
	return tmp;
}

2.erase外部的迭代器失效问题

大家可能会有疑惑:为什么erase也会有迭代器失效问题呢?
别急,我们通过一个例子来验证一下:
比方说:一个数组当中删除所有的偶数
在这里插入图片描述
结果是1 2 3 5,没删干净啊?
为什么呢?
也是迭代器失效导致的
在这里插入图片描述
刚才还是好的情况,如果最后一个元素是偶数的话
就会因为越界访问而报错
在这里插入图片描述
为什么呢?
跟刚才一样
在这里插入图片描述
在这里插入图片描述
那么怎么办呢?
erase返回被删除的元素的下一个位置
因此我们可以这样做:
在这里插入图片描述
因此,对于insert和erase来说:
我们认为insert和erase的迭代器都会失效,不能再访问了,结果是未定义的(也取决于编译器的具体实现)
如果想要继续使用 请接收返回值

8.push_back和pop_back对于insert和erase的复用

1.push_back

复用前:

void push_back(const T& x)
{
	//空间不够的话要扩容
	if (size() == capacity()) 
	{
		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	//尾插数据
	*_finish = x;
	++_finish;
}

复用后:

void push_back(const T& x)
{
	insert(end(), x);
}

2.pop_back

复用前:

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

复用后:

void pop_back()
{
	erase(_finish - 1);
}

9.含参构造

vector支持这样来构造:vector<int> v(10,99):意思是构造v这个对象时向里面写入10个99
vector<int> v(10):意思是构造v这个对象时向里面写入10个0(int的默认值是0)

//用n个value来构造该对象
vector(int n, const T& value = T())
	//1.先初始化为nullptr
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	//2.预扩容:扩容为n个大小,将capacity扩容为n
	reserve(n);
	//3.尾插这n个数据(value)
	for (int i = 0; i < n; i++)
	{
		push_back(value);
	}
}

注意:
1.这是一个半缺省构造函数,value的默认值是T()
2.这里的T()是匿名对象(是调用T这个类型的默认构造函数生成的)
在模板这个语法出现之后C++支持了内置类型的默认构造函数

int a();//默认用0来构造a
int a(10);//就是用10来构造a

同理:double默认用0.0构造  int*默认用nullptr来构造  等等等等....

其实整个步骤就是:
1.先初始化为nullptr
2.预扩容:扩容为n个大小,将capacity扩容为n
3.尾插这n个数据(value)

10.迭代器区间构造

template<class InputIterator>
//迭代器区间构造
//用[first,last)这个区间内的数据来构造该对象
vector(InputIterator first, InputIterator last)
	//1.初始化为nullptr
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	//2.尾插
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

注意:
1.如果依然使用iterator做迭代器来构造,会导致初始化的迭代器区间[first,last)只能是vector的迭代器
因此需要重新声明迭代器,让迭代器区间[first,last)可以是任意容器的迭代器

2.然后后面依然是初始化和尾插的操作

11.拷贝构造

1.传统写法

vector(const vector<T>& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_endOfStorage(nullptr)
{
	reserve(v.capacity());
	for (auto& e:v)
	{
		push_back(e);
	}
}

我们可以直接预扩容,然后尾插即可

2.现代写法

//现代写法:复用构造函数+swap即可
vector(const vector<T>& v)
	//1.初始化为nullptr
	:_start(nullptr)
	,_finish(nullptr)
	,_endOfStorage(nullptr)
{
	//2.利用迭代器区间构造来构造一个临时变量tmp
	vector<T> tmp(v.begin(), v.end());
	//3.交换this和tmp
	swap(tmp);
}

注意:swap之后原有的this会通过tmp这个形参析构,因此需要先将:_start(nullptr), _finish(nullptr), _endOfStorage(nullptr) 初始化为空指针然后再swap
否则会因为delete时释放野指针指向的空间导致出错

12.赋值运算符重载

1.传统写法

vector<T>& operator=(const vector<T>& v)
{
	//防止自己给自己赋值
	if (this != &v)
	{
		//1.开辟新空间
		T* tmp = new T[v.capacity()];
		//2.将数据拷贝到新空间当中
		for (int i = 0; i < v.size(); i++)
		{
			tmp[i] = v._start[i];
		}
		//3.释放原有空间
		delete[] _start;
		//4.指向新空间
		_start = tmp;
		_finish = _start + v.size();
		_endOfStorage = _start + v.capacity();
	}
	return *this;
}

2.现代写法

//赋值运算符重载现代写法
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}
//只需要交换this和v的三个指针即可
void swap(vector<T>& v)
{
	//调用标准库中的swap函数
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endOfStorage, v._endOfStorage);
}

我们知道:传值传参时:自定义类型会调用其拷贝构造函数形成形参
形参是实参的一份临时拷贝,因此我们可以让这个形参跟我们的this交换
这样的话就可以一举两得:
1.我们的this就成功被赋值为我们想要的值了
2.this指向的旧空间在交换后被形参v所指向,出了这个作用域之后,形参v会调用其析构函数释放掉this指向的旧空间

因此只需要传值传参+swap交换就可以完成开辟新空间+拷贝数据+释放原有空间+指向新空间这4个步骤了

不过大家请注意:这里一定要传值传参
如果传引用:那么就是swap了,而不是赋值了

三.完整代码

#include <iostream>
using namespace std;
#include <string>
#include <assert.h>
namespace wzs
{
	template<class T>
	class vector
	{
	public:
		// Vector的迭代器是一个原生指针
		typedef T* iterator;
		typedef const T* const_iterator;

		///
		// 构造和销毁
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endOfStorage(nullptr)
		{}

		vector(size_t n, const T& value = T())
			:_start(nullptr)
			,_finish(nullptr)
			,_endOfStorage(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(value);
			}
		}

		vector(int n, const T& value = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(value);
			}
		}

		template<class InputIterator>

		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			,_finish(nullptr)
			,_endOfStorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endOfStorage(nullptr)
		{
			reserve(v.capacity());
			for (auto& e:v)
			{
				push_back(e);
			}
		}

		vector<T>& operator=(const vector<T>& v)
		{
			//防止自己给自己赋值
			if (this != &v)
			{
				//1.开辟新空间
				T* tmp = new T[v.capacity()];
				//2.将数据拷贝到新空间当中
				for (int i = 0; i < v.size(); i++)
				{
					tmp[i] = v._start[i];
				}
				//3.释放原有空间
				delete[] _start;
				//4.指向新空间
				_start = tmp;
				_finish = _start + v.size();
				_endOfStorage = _start + v.capacity();
			}
			return *this;
		}

		//现代写法
		//vector<T>& operator=(vector<T> v)
		//{
		//	swap(v);
		//	return *this;
		//}

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

		/
		// 迭代器相关
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		//
		// 容量相关
		size_t size() const
		{
			return _finish - _start;
		}

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

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



		//错误版本1
		
		//void reserve(size_t n)
		//{
		//	if (n > capacity())
		//	{
		//		//1.申请新空间
		//		T* tmp = new T[n];
		//		//2.将原有数据拷贝到新空间当中
		//		memmove(tmp, _start, sizeof(T) * size());
		//		//3.释放原有空间
		//		delete _start;
		//		//4.指向新空间
		//		_start = tmp;
		//		_finish = _start + size();
		//		_endOfStorage = _start + capacity();
		//	}
		//}


		//错误版本2

		//void reserve(size_t n)
		//{
		//	if (n > capacity())
		//	{
		//		//1.保存原有空间的size
		//		int oldSize = size();
		//		//2.开辟新空间
		//		T* tmp = new T[n];
		//		//3.将原有空间的数据拷贝到新空间当中
		//		memmove(tmp, _start, sizeof(T) * oldSize);
		//		//4.释放旧空间
		//		delete _start;
		//		//5.指向新空间
		//		_start = tmp;
		//		_finish = _start + oldSize;
		//		_endOfStorage = _start + n;
		//	}
		//}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//提前保存偏移量oldSize
				int oldSize = size();
				//1.申请新空间
				T* tmp = new T[n];
				//2.将原有空间中的数据拷贝到新空间当中
				
				for (int i = 0; i < oldSize; i++)
				{
					tmp[i] = _start[i];
					//内置类型直接赋值即可,自定义类型会调用其赋值运算符重载,实现深拷贝
				}

				//3.释放原有空间
				delete[] _start;
				//4.将_start,_finish,_endOfStorage都指向到新空间
				_start = tmp;
				_finish = _start + oldSize;
				_endOfStorage = _start + n;
			}
		}

		void resize(size_t n, const T& value = T())
		{
			//1.n<size:删除数据
			if (n < size())
			{
				_finish = _start + n;
			}
			//2.n>size:插入数据直到size==n为止
			else
			{
				//先扩容
				reserve(n);
				//尾插数据
				while (size() < n)
				{
					push_back(value);
				}
			}
		}

		///
		// 元素访问
		T& operator[](size_t pos)
		{
			return _start[pos];
		}

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

		T& front()
		{
			return *_start;
		}

		const T& front()const 
		{
			return *_start;
		}

		T& back()
		{
			return *(_finish - 1);
		}

		const T& back()const
		{
			return *(_finish - 1);
		}
		/
		// vector的修改操作
		
		//void push_back(const T& x)
		//{
		//	//空间不够的话要扩容
		//	if (size() == capacity()) 
		//	{
		//		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		//		reserve(newcapacity);
		//	}
		//	//尾插数据
		//	*_finish = x;
		//	++_finish;
		//}

		//复用insert
		void push_back(const T& x)
		{
			insert(end(), x);
		}

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

		//复用erase
		void pop_back()
		{
			erase(_finish - 1);
		}

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


		//错误版本:pos迭代器会失效
		
		//iterator insert(iterator pos, const T& x)
		//{
		//	//检查pos的合法性
		//	assert(pos >= _start && pos <= _finish);
		//	//需要扩容的话扩容
		//	if (size() == capacity())
		//	{
		//		//扩容
		//		int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		//		reserve(newcapacity);
		//	}
		//	//开始insert
		//	//把[pos,_finish)的数据往后挪
		//	iterator end = _finish;
		//	while (end > pos)
		//	{
		//		*end = *(end - 1);
		//		--end;
		//	}
		//	//插入数据
		//	*pos = x;
		//	//更新size
		//	++_finish;
		//	return pos;
		//}

		//发生扩容之后pos迭代器会失效,因此需要在扩容之前先保存偏移量,并在扩容之后重新调整pos迭代器的位置
		
		iterator insert(iterator pos, const T& x)
		{
			//检查pos的合法性
			assert(pos >= _start && pos <= _finish);
			//需要扩容
			if (size() == capacity())
			{
				//先保存pos的偏移量
				int gap = pos - _start;
				//扩容
				int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				//调整pos的位置
				pos = _start + gap;
			}
			//开始insert
			//把[pos,_finish)的数据往后挪
			
			iterator end = _finish;
			while (end > pos)
			{
				*end = *(end - 1);
				--end;
			}
			
			//插入数据
			*pos = x;
			//更新size
			++_finish;
			return pos;
		}

		// 返回删除数据的下一个数据
		// 方便解决:一边遍历一边删除的迭代器失效问题
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			//保存pos
			iterator tmp = pos;

			//把[pos+1,_finish)的数据往前挪一格
			while (pos < _finish - 1)
			{
				*pos = *(pos + 1);
				++pos;
			}
			//调整size
			--_finish;
			return tmp;
		}

	private:
		iterator _start;		// 指向数据块的开始
		iterator _finish;		// 指向有效数据的尾
		iterator _endOfStorage;  // 指向存储容量的尾
	};
}

以上就是C++ vector模拟实现的全部内容,希望能对大家有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/308442.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

React Native 桥接原生常量

一、编写并注册原生常量方法 在 SmallDaysAppModule 这个模块中有一个方法 getConstans &#xff0c;重载这个方法就可将自定义的常量返回&#xff0c;系统会自行调用该方法并返回定义的常量将其直接注入到 JS 层&#xff0c;在 JS 层直接获取即可。 二、JS 层获取原生常量&am…

电脑USB接口不同颜色的含义

当你看到笔记本电脑或台式机的USB端口时&#xff0c;你会发现USB端口的颜色很多&#xff1b;这些颜色可不只是为了好看&#xff0c;实际上不同颜色代表着不同的性能&#xff0c;那么这些带颜色的USB端口都是什么含义呢&#xff0c;下面就具体介绍下不同颜色代表的含义。-----吴…

钉钉逐浪AI Agent

文&#xff5c;郝 鑫 编&#xff5c;刘雨琦 “大公司代表落后生产力&#xff0c;是慢半拍的”&#xff0c;“小创新靠大厂&#xff0c;大创新仍然要靠小厂”&#xff0c;这是以李彦宏和王小川为代表的创业老炮&#xff0c;在2023年总结出来的创新规律&#xff0c;从移动互…

单片机原理及应用:中断嵌套

​中断嵌套是指中断系统正在执行一个中断服务时&#xff0c;有另一个优先级更高的中断提出中断请求&#xff0c;这时会暂时终止当前正在执行的级别较低的中断源的服务程序&#xff0c;去处理级别更高的中断源&#xff0c;待处理完毕&#xff0c;再返回到被中断了的中断服务程序…

阿里云的通义千问VS百度的文心一言~~

最近人工智能热度迅速升温&#xff0c;我体验了一下各大厂商的大模型的能力&#xff0c;发现他们确实很智能&#xff01; 我想问一下“南方小土豆”这个梗是如何火起来的&#xff0c;结果如下&#xff1a; 文心一言&#xff1a; 回答的比较准确&#xff0c;但有一些过于“官方”…

ChatGPT4+Python近红外光谱数据分析及机器学习与深度学习建模进阶应用

2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT3.5&#xff0c;将人工智能的发展推向了一个新的高度。2023年4月&#xff0c;更强版本的ChatGPT4.0上线&#xff0c;文本、语音、图像等多模态交互方式使其在…

vue知识-03

购物车案例 要实现的功能&#xff1a; 1、计算商品总价格 2、全选框和取消全选框 3、商品数量的增加和减少 <body> <div id"app"><div class"row"><div class"col-md-6 col-md-offset-3"><h1 class"text-center…

SpringCloudAlibaba微服务架构实战派上下册技术交流!

另外我的新书RocketMQ消息中间件实战派上下册&#xff0c;在京东已经上架啦&#xff0c;目前都是5折&#xff0c;非常的实惠。 https://item.jd.com/14337086.html​编辑https://item.jd.com/14337086.html “RocketMQ消息中间件实战派上下册”是我既“Spring Cloud Alibaba微…

Springboot药物不良反应智能监测系统源码

一、系统简介 ADR指的是药品不良反应&#xff0c;即在合格药品在正常用法用量下&#xff0c;出现与用药目的无关或意外的有害反应。ADR数据辨别引擎、药品ADR信号主动监测引擎、ADR处置行为分析引擎。ADR数据辨别引擎&#xff0c;通过主动监测患者具象临床指标&#xff0c;比如…

Simpy简介:python仿真模拟库-03/5

一、说明 在过去的两篇文章中&#xff0c;我们了解了 simpy 的基础知识、声明变量和处理表达式。值得注意的例子包括评估导数和积分。现在&#xff0c;让我们继续使用函数。 二、SymPy — 函数类 SymPy 包包含 sympy.core.function 模块中的 Function 类。该类作为各种数学函数…

腾讯云优惠券介绍、领取方法及使用教程

腾讯云作为国内领先的云服务提供商&#xff0c;为了吸引更多的用户使用其产品&#xff0c;经常会推出各种优惠券活动。本文将详细介绍腾讯云的优惠券、领取方法和使用教程。 一、腾讯云优惠券介绍 腾讯云优惠券是腾讯云为了吸引用户使用其产品而推出的促销活动。用户可以通过领…

软件测试工程师,从6K到25k的测试之路养成,一路狂飙...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、技术方向 就技…

静态网页设计——滑板官网(HTML+CSS+JavaScript)(dw、sublime Text、webstorm、HBuilder X)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a;https://www.bilibili.com/video/BV1Cw411u7hj/?vd_source5f425e0074a7f92921f53ab87712357b 源码&#xff1a;https://space.bilibili.com…

基于传统机器学习的项目开发过程——@挑大梁

1 场景分析 1.1 项目背景 描述开发项目模型的一系列情境和因素&#xff0c;包括问题、需求、机会、市场环境、竞争情况等 1.2. 解决问题 传统机器学习在解决实际问题中主要分为两类&#xff1a; 有监督学习&#xff1a;已知输入、输出之间的关系而进行的学习&#xff0c;从而…

statsmodels.tsa 笔记 detrend(去趋势)

1 基本使用方法 statsmodels.tsa.tsatools.detrend(x, order1, axis0) 2 参数说明 x数据。如果是二维数组&#xff0c;那么每一行或每一列将独立地去除趋势&#xff0c;但趋势的阶数是一样的。order趋势的多项式阶数。0 表示常数趋势&#xff08;即没有趋势&#xff09;&…

炫技作品!极好!独家原创!一种新型改进的蜣螂优化算法(CCCDBO)

炫技作品&#xff01;&#xff0c;独家原创&#xff01; 蜣螂优化算法DBO的含金量不用我多介绍了吧&#xff0c;这是和麻雀优化算法SSA同一个课题组出的算法&#xff0c;业内公认的比较好的算法&#xff0c;这个算法认可度很高&#xff01; 一种新型改进蜣螂优化算法&#xf…

无法访问Bing网站 - 解决方案

问题 Bing官方网址&#xff1a;https://www.bing.com/ 电脑无法访问Bing网站&#xff0c;但手机等移动设备可以访问Bing网站&#xff0c;此时可尝试以下方案。 以下方案适用于各种系统&#xff0c;如Win/Linux系统。 解决方案 方案1 修改Bing网址为&#xff1a;https://www4…

RocketMq直接上手(火箭班)

Apache RocketMQ官方文档&#xff1a;https://rocketmq.apache.org/zh/docs/bestPractice/06FAQ/&#xff0c;这里面涵盖了所有的基本知识、各种搭建环境、基础代码测试…还有各种问题总结&#xff0c;很值得自主学习。 1.配置依赖&#xff1a;pom.xml文件 可以只截取maven仓库…

跟随鼠标3D倾斜

创建一个vanilla-tilt.js文件将一下代码黏贴进去 export var VanillaTilt (function () {use strict;/*** Created by Sergiu Șandor (micku7zu) on 1/27/2017.* Original idea: https://github.com/gijsroge/tilt.js* MIT License.* Version 1.7.2*/class VanillaTilt {cons…

一道新能:周期底部,TOPCon“红利牛”IPO来了

2023年无疑是TOPCon技术路线正式登台的一年&#xff0c;而风口之下必有“黑马”的诞生&#xff0c;去年最后一个工作日递交招股书申请创业板上市的一道新能正是如此。 复盘发现&#xff0c;短短成立五年&#xff0c;一道新能在双碳红利期中&#xff0c;实现资产规模、营收规模…