【C++初阶】七、STL---vector模拟实现

news2025/2/25 10:42:39

目录

一、模拟实现接口总览

1.1 接口总览

1.2 vector整体框架

1.3 vector成员变量介绍

二、vector模拟实现

2.1 构造函数

2.1.1 无参构造

2.1.2 迭代器区间构造

2.1.3 n个val构造

2.1.4 拷贝构造

2.2 赋值运算符重载

2.2.1 传统写法

2.2.2 现代写法

2.3 析构函数

2.4 Iterator

2.4.1 begin

2.4.2 end

2.5 Capacity

2.5.1 size

2.5.2 capacity

2.5.3 empty

2.5.4 resize

2.5.5 reserve

2.6 element access

2.6.1 operator[]

2.7 Modifiers

2.7.1 swap

2.7.2 push_back

2.7.3 pop_back

2.7.4 insert

2.7.5 erase

三、vector模拟实现全部代码


一、模拟实现接口总览

1.1 接口总览

Member functions
    //构造函数
		vector()
    //拷贝构造 -- 现代写法2
		vector(const vector<T>& v)
    //迭代器区间构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
    //n个val构造
		vector(size_t n, const T& val = T())
    //n个val构造 -- 重载,解决第一个参数的int size_t 的问题
		vector(int n, const T& val = T())
    //析构
		~vector()
    //赋值重载 -- 现代写法2
		vector<T>& operator=(vector<T> v)

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

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

//element access
		T& operator[](size_t pos)
        const T& operator[](size_t pos)const

//modifier
		void swap(vector<T>& v)
        void push_back(const T& x)
        void pop_back()
    //任意位置插入 -- 插入后认为迭代器失效,  迭代器失效 : 扩容引起,野指针问题
		iterator insert(iterator pos, const T& val)
    //任意位置删除 -- erase 之后也认为 pos 迭代器失效
		iterator erase(iterator pos)

        上面也是挑一些常用的进行模拟实现,要把自己实现的写在自己命名的命名空间里面,否则与库中的 vector 会产生冲突

注:

        vector类模拟实现,最主要也是实现 vector 的构造、拷贝构造、赋值运算符重载以及析构函数

1.2 vector整体框架

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

namespace fy
{
	template<class T>
	class vector
	{
	public:
		//vector的迭代器是一个原生指针
		typedef T* iterator;
		typedef const T* const_iterator;

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

注:

        后面的 STL 的模拟实现我们参考的是SGI版 STL3.0 的写法,写法跟上一个 string 模拟实现有所不同,虽然表面上看起来不一样,但是实际上表达的效果是大同小异的

1.3 vector成员变量介绍

在vector当中有三个成员变量_start、_finish、_endofstorage

  1. _start指向容器的头
  2. _finish指向容器当中有效数据的尾
  3. _endofstorage指向整个容器的尾

  • 指针减指针 就可以得到大小或容量
  • _finish - _start = size(有效数据的大小) 
  • _endOfStorage - _start = capacity(整个容器的容量)

二、vector模拟实现

2.1 构造函数

2.1.1 无参构造

这里要完成的工作是初始化,直接将构造对象的三个成员变量都设置为 nullptr

//构造函数
vector()
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{}

2.1.2 迭代器区间构造

        vector 支持使用一段迭代器区间进行对象的构造,但是这里要注意:该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的,所以我们这里需要将该构造函数设计为一个函数模板,然后再将数据一个个尾插即可

//迭代器区间构造
template<class InputIterator>//模板函数
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	while (first != last)//尾插
	{
		push_back(*first);
		++first;
	}
}

2.1.3 n个val构造

        vector 也支持 n 个 val 构造,比如构造10个1。对于该函数,可以先利用 reserve 进行扩容,一次性开好所需要的空间,然后进行尾插即可

//n个val构造
vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	reserve(n);//扩容
	for (size_t i = 0;i < n; ++i)//把数据进行尾插
	{
		push_back(val);
	}
}

但是,这里存在一个问题,执行这句代码时直接报错

fy::vector<int> v5(10, 2);

        这是因为 这句代码调用的不是 n个 val构造,而是调用了迭代器区间构造,迭代器区间构造对参数 first 和 last 进行了解引用,而 int类型不能进行解引用操作,所以报错说非法寻址

        造成这个的直接原因是 n 个 val构造的第一个参数 size_t ,与 fy::vector<int> v5(10, 2) 第一个参数类型不匹配,编译器就会优先调用迭代器区间构造,要解决这个问题可以对 v5(10, 2) 第一个参数强制类型转化成 size_t,但是这样会给使用者不舒服,另一种就是函数重载

//n个val构造 -- 重载,解决第一个参数的int size_t 的问题
vector(int n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	reserve(n);
	for (int i = 0; i < n; ++i)
	{
				push_back(val);
	}
}

2.1.4 拷贝构造

拷贝构造也分传统写法和现代写法,推荐现代写法,注意深浅拷贝的问题,这里明显就是要进行深拷贝

(1)传统写法

思想:先开辟一块与该容器大小相同的空间,然后将该容器当中的数据拷贝过来

拷贝构造
//传统写法
vector(const vector<T>& v)
{
	T* tmp = new T[v.capacity()];//开辟一块大小相同的空间
	memcpy(tmp, v._start, sizeof(T) * v.capacity());//拷贝
	_start = tmp;//更新_start
	_finish = _start + v.size();//更新_finish
	_endOfStorage = _start + v.capacity();//更新_endOfStorage
}

        但是这里存在一个严重的问题,当 vector 存储的数据是内置类型或无需进行深拷贝的自定义类型时,使用 memcpy 函数是没什么问题的,但当vector存储的数据是需要进行深拷贝的自定义类型时,使用 memcpy 函数的弊端就体现出来

比如:vector 存储的数据是 string 时

vector 里面的每个储存对象都是 string,并且 string 也指向自己所开辟的空间,即指向自己储存的字符串

        使用 memcpy 函数进行拷贝构造的话,那么拷贝构造出来的 vecto r当中存储的每个 string 的成员变量的值,将与被拷贝的vector当中存储的每个 string 的成员变量的值相同,即两个vector当中的每个对应的string成员都指向同一个字符串空间,进行析构的时候就会出问题,string 指向的空间会被释放两次

如何解决?一个个进行深拷贝就可以了

vector(const vector<T>& v)
{
	_start = new T[v.capacity()];//开辟一块大小相同的空间
	for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来
	{
		_start[i] = v[i];//利用了赋值重载
			}
	_finish = _start + v.size();//更新_finish
	_endOfStorage = _start + v.capacity();//更新_endOfStorage
}

         这里利用了 string类的赋值重载,string类的赋值运算符重载函数就是深拷贝

拷贝完了之后结果是:

这样就完成了深拷贝

        所以,C语言的关于内存接口直接使用大多数是有问题的,尽量少使用 C语言的关于内存方面接口,C语言的关于内存方面接口对内置类型无影响,对自定义类型的影响就很大了

(2)现代写法

复用迭代器区间构造,再交换一下数据即可

//拷贝构造 -- 现代写法
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endOfStorage(nullptr)
{
	vector<T> tmp(v.begin(), v.end());
	swap(tmp);
}

注:swap 后面实现

2.2 赋值运算符重载

        vector 的赋值运算符重载也涉及深拷贝问题,也有传统写法和现代写法

2.2.1 传统写法

        判断是否是给自己赋值,若是给自己赋值则无需进行操作;若不是给自己赋值,则先开辟一块和 v 大 小相同的空间,然后将容器 v 当中的数据拷贝

赋值重载
		//传统写法
		vector<T>& operator=(const vector<T>& v)
		{
			if (this == &v)
			{
				return *this;//检查自我赋值
			}

			delete[] _start;
			T* tmp = new T[v.capacity()];
			memcpy(tmp, v._start, sizeof(T) * v.capacity());//拷贝
			_start = tmp;
			_finish = _start + v.size();
			_endOfStorage = _start + v.capacity();
			return *this;
		}

这里也是 memcpy 这个问题,跟上面一样,也是利用 赋值重载,一个个进行深拷贝就可以了

vector<T>& operator=(const vector<T>& v)
{
	if (this == &v)
	{
		return *this;//检查自我赋值
	}

	delete[] _start;
	_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();
	return *this;
}

2.2.2 现代写法

(1)现代写法1

复用拷贝构造,构造出 tmp,然后交换一下就可以了

//赋值重载 -- 现代写法1
vector<T>& operator=(const vector<T>& v)
{
	if (this == &v)
	{
		return *this;//检查自我赋值
	}

	vector<T> tmp(v);//复用拷贝构造
	swap(tmp);//复用swap
	return *this;
}

(2)现代写法2

         也是复用拷贝构造,但是参数不再是传引用传参,而是传值形参,而传值传参则会自动调用拷贝构造函数,但是这种写法无法检查自我赋值的情况,但是这种情况几乎不会出现,推荐这种写法

//赋值重载 -- 现代写法2
vector<T>& operator=(vector<T> v)//传值传参自动调用拷贝构造,不能检查自我赋值的问题,但不影响程序正确性
{
	swap(v);
	return *this;
}

2.3 析构函数

释放空间,置空即可

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

2.4 Iterator

        vector 的迭代器实际上也是指针,范围 for 底层也是迭代器

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

2.4.1 begin

返回容器的首地址即可

//iterator
iterator begin()
{
	return _start;
}

const 版本的,只能对数据进行读操作,而不能进行修改

const_iterator begin()const
{
	return _start;
}

2.4.2 end

返回容器当中有效数据的下一个数据的地址

iterator end()
{
	return _finish;
}

const 版本的,只能对数据进行读操作,而不能进行修改

const_iterator end()const
{
	return _finish;
}

2.5 Capacity

2.5.1 size

返回数据个数,指针相减即可

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

2.5.2 capacity

返回容器实际容量,指针相减即可

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

2.5.3 empty

        直接通过比较容器当中的_start和_finish指针的指向来判断容器是否为空,若所指位置相同,则该容器为空

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

2.5.4 resize

resize 规则: 

  1. 当所给值 n大于容器当前的 size时,将size扩大到 n,扩大的元素为第二个所给值 val,若未给出,则默认为容器所存储类型的默认构造函数所构造出来的值
  2. 当所给值 n小于容器当前的 size时,将size缩小到 n
void resize(size_t n, T val = T())
{
	if (n > capacity())//检查是否需要扩容
	{
		reserve(n);
	}

	if (n > size())
	{
		while (_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
	else
	{
		_finish = _start + n;//n < size,有效数据缩减
	}
}		

        第二个参数,默认给的是其对应类型的缺省值作为 "填充值"。由于这里我们不知道具体类型是什么,这里缺省值我们使用匿名对象 T(),使用匿名对象后 val 为容器所存储类型的默认构造函数所构造出来的值

2.5.5 reserve

reserve 规则:

  1. n大于对象当前的 capacity时,将capacity扩大到n或大于n
  2. n小于对象当前的 capacity时,什么也不做,也不缩容
void reserve(size_t n)
{
	if (n > capacity())//n大于 capacity 才扩容,reserve不缩容
	{
		size_t oldSize = size();
		T* tmp = new T[n];

		if (_start)
		{
			//memcpy对自定义类型会有浅拷贝问题,需要对每个元素使用拷贝构造进行深拷贝
			//memcpy(tmp, _start, sizeof(T) * oldSize);//error
			delete[] _start;
		}

		_start = tmp;
		_finish = tmp + oldSize;
		_endOfStorage = _start + n;
	}
}

这里也是 memcpy 这个问题,跟上面一样,也是利用 赋值重载,一个个进行深拷贝就可以了

void reserve(size_t n)
{
	if (n > capacity())//n大于 capacity 才扩容,reserve不缩容
	{
		size_t oldSize = size();//记录原来的size,避免扩容不能确定 _finish
		T* tmp = new T[n];

		if (_start)
		{
			for (size_t i = 0; i < oldSize; ++i)
			{
				tmp[i] = _start[i];//利用赋值重载
			}
			delete[] _start;
		}

		_start = tmp;
		_finish = tmp + oldSize;
		_endOfStorage = _start + n;
	}
}

2.6 element access

2.6.1 operator[]

vector也支持我们使用 “下标+[ ]” 的方式对容器当中的数据进行访问,实现时直接返回对应位置的数据即可

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

const 版本,只读,不能对数据进行修改

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

2.7 Modifiers

2.7.1 swap

        swap 函数用于交换两个容器的数据,直接调用库当中的 swap 函数将两个容器当中的各个成员数据进行交换即可

void swap(vector<T>& v)
{
	std::swap(_start, v._start);//使用库里面的 swap 函数
	std::swap(_finish, v._finish);
	std::swap(_endOfStorage, v._endOfStorage);
}

2.7.2 push_back

注意判断数据是否满,直接在尾上插入数据即可

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

	*_finish = x;
	++_finish;
}

2.7.3 pop_back

尾删数据,注意判空

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

2.7.4 insert

        这里要注意迭代器失效的问题,insert函数可以在所给迭代器pos位置插入数据,分四步:① 检查 pos 是否越界   ② 检查是否需要扩容  ③ 移动数据   ④ 插入数据

//任意位置插入 -- 插入后认为迭代器失效,  迭代器失效 : 扩容引起,野指针问题
iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos < _finish);

	//扩容会导致迭代器失效
	/*if (size() == capacity())
	{
		size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newCapacity);

	}*/

	if (size() == capacity())
	{
		size_t len = pos - _start;
		size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newCapacity);

		// 扩容会导致pos迭代器失效,需要更新处理一下
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)//挪动数据
	{
		*(end + 1) = *(end);
		--end;
	}
	*pos = val;
	++_finish;
	return pos;
}

2.7.5 erase

这里也要注意迭代器失效的问题,erase函数可以删除所给迭代器pos位置的数据

//任意位置删除 -- erase 之后也认为 pos 迭代器失效
iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

	iterator begin = pos + 1;
	while (begin < _finish)//挪动数据
	{
		*(begin - 1) = *(begin);
		++begin;
	}
	--_finish;

	return pos;
}

注:此处没有新开辟空间,也就没有野指针问题,erase 缩容也可能导致 pos 迭代器失效(具体看编译器实现),SGI版不缩容

        看一下文档对 erase 返回值的描述,返回被删除位置的后一个位置的迭代器,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了,所以统一认为 erase 之后 pos 迭代器失效

        迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃) 

对于 vector可能会导致其迭代器失效的操作有

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等
  2. 指定位置元素的删除操作:erase 

vector 的模拟实现两大问题:深拷贝和迭代器失效

        而 string 模拟实现的时候并不注意迭代器失效这个问题,因为 string 大多数用的都是下标进行各项操作,基本不用迭代器进行操作,所以就忽略了

三、vector模拟实现全部代码

vector.h

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

namespace fy
{
	template<class T>
	class vector
	{
	public:
		//vector的迭代器是一个原生指针
		typedef T* iterator;
		typedef const T* const_iterator;

		//构造函数
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{}

		//拷贝构造
		传统写法
		//vector(const vector<T>& v)
		//{
		//	_start = new T[v.capacity()];//开辟一块大小相同的空间
		//	for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来
		//	{
		//		_start[i] = v[i];
		//	}
		//	_finish = _start + v.size();//更新_finish
		//	_endOfStorage = _start + v.capacity();//更新_endOfStorage
		//}
		
		//vector(const vector<T>& v)
		//{
		//	T* tmp = new T[v.capacity()];//开辟一块大小相同的空间
		//	memcpy(tmp, v._start, sizeof(T) * v.capacity());//拷贝,error
		//	_start = tmp;//更新_start
		//	_finish = _start + v.size();//更新_finish
		//	_endOfStorage = _start + v.capacity();//更新_endOfStorage
		//}

		//拷贝构造 -- 传统写法2
		/*vector(const vector<t>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(v.capacity());
			for (const auto& e : v)
			{
				push_back(e);
			}
		}*/

		//拷贝构造 -- 现代写法
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

		//迭代器区间构造
		template<class InputIterator>//模板函数
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			while (first != last)//尾插
			{
				push_back(*first);
				++first;
			}
		}

		//n个val构造
		vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(n);//扩容
			for (size_t i = 0;i < n; ++i)//把数据进行尾插
			{
				push_back(val);
			}
		}

		//n个val构造 -- 重载,解决第一个参数的int size_t 的问题
		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

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

		//赋值重载
		传统写法
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this == &v)
		//	{
		//		return *this;//检查自我赋值
		//	}

		//	delete[] _start;
		//	_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();
		//	return *this;
		//}
		
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this == &v)
		//	{
		//		return *this;//检查自我赋值
		//	}

		//	delete[] _start;
		//	T* tmp = new T[v.capacity()];
		//	memcpy(tmp, v._start, sizeof(T) * v.capacity());
		//	_start = tmp;
		//	_finish = _start + v.size();
		//	_endOfStorage = _start + v.capacity();
		//	return *this;
		//}

		赋值重载 -- 现代写法1
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this == &v)
		//	{
		//		return *this;//检查自我赋值
		//	}

		//	vector<T> tmp(v);//复用拷贝构造
		//	swap(tmp);//复用swap
		//	return *this;
		//}

		//赋值重载 -- 现代写法2
		vector<T>& operator=(vector<T> v)//传值传参自动调用拷贝构造,不能检查自我赋值的问题,但不影响程序正确性
		{
			swap(v);
			return *this;
		}

		//-------------------------------------------------------------
		//iterator
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin()const
		{
			return _start;
		}

		const_iterator end()const
		{
			return _finish;
		}

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

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

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

		void reserve(size_t n)
		{
			if (n > capacity())//n大于 capacity 才扩容,reserve不缩容
			{
				size_t oldSize = size();//记录原来的size,避免扩容不能确定 _finish
				T* tmp = new T[n];

				if (_start)
				{
					for (size_t i = 0; i < oldSize; ++i)
					{
						tmp[i] = _start[i];//
					}
					delete[] _start;
				}

				_start = tmp;
				_finish = tmp + oldSize;
				_endOfStorage = _start + n;
			}

			//if (n > capacity())//n大于 capacity 才扩容,reserve不缩容
			//{
			//	size_t oldSize = size();
			//	T* tmp = new T[n];

			//	if (_start)
			//	{
			//		//memcpy对自定义类型会有浅拷贝问题,需要对每个元素使用拷贝构造进行深拷贝
			//		//memcpy(tmp, _start, sizeof(T) * oldSize);//error
			//		delete[] _start;
			//	}

			//	_start = tmp;
			//	_finish = tmp + oldSize;
			//	_endOfStorage = _start + n;
			//}
		}

		void resize(size_t n, T val = T())
		{
			if (n > capacity())//检查是否需要扩容
			{
				reserve(n);
			}

			if (n > size())
			{
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
			else
			{
				_finish = _start + n;//n < size,有效数据缩减
			}
		}		

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

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

		//-------------------------------------------------------------
		//Modifiers
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);//使用库里面的 swap 函数
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage, v._endOfStorage);
		}

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

			*_finish = x;
			++_finish;
		}

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

		//任意位置插入 -- 插入后认为迭代器失效,  迭代器失效 : 扩容引起,野指针问题
		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start && pos < _finish);

			//扩容会导致迭代器失效
			/*if (size() == capacity())
			{
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newCapacity);

			}*/

			if (size() == capacity())
			{
				size_t len = pos - _start;
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newCapacity);

				// 扩容会导致pos迭代器失效,需要更新处理一下
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *(end);
				--end;
			}
			*pos = val;
			++_finish;
			return pos;
		}
		//任意位置删除 -- erase之后也认为 pos 迭代器失效
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);

			iterator begin = pos + 1;
			while (begin < _finish)//挪动数据
			{
				*(begin - 1) = *(begin);
				++begin;
			}
			--_finish;

			return pos;
		}

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

Test.cpp

#include "vector.h"

void vectorTest()
{
	//构造
	fy::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(6);
	
	for (size_t i = 0; i < v1.size(); ++i)
	{
		cout << v1[i] << " ";
	}
	cout << endl;

	fy::vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	//拷贝构造
	cout << "--拷贝--" << endl;
	fy::vector<int> v2(v1);
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	//赋值重载
	cout << "--赋值重载--" << endl;
	fy::vector<int> v3;
	v3 = v1;
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	//迭代器区间构造
	cout << "--迭代器区间构造--" << endl;
	fy::vector<int> v4(v1.begin(), v1.end());
	for (auto e : v4)
	{
		cout << e << " ";
	}
	cout << endl;

	v4.pop_back();
	v4.pop_back();
	v4.pop_back();
	v4.pop_back();
	v4.pop_back();
	for (auto e : v4)
	{
		cout << e << " ";
	}
	cout << endl;

	//n个val构造
	cout << "--n个val构造--" << endl;
	fy::vector<int> v5(10, 2);
	for (auto e : v5)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << "----------" << endl;
	fy::vector<char> v6(10, 'a');
	for (auto e : v6)
	{
		cout << e << " ";
	}
	cout << endl;

	v6.resize(15);
	for (auto e : v6)
	{
		cout << e << " ";
	}
	cout << endl;

	v6.resize(5);
	for (auto e : v6)
	{
		cout << e << " ";
	}
	cout << endl;

	v6.insert(v6.begin() + 3, 'x');
	v6.insert(v6.begin() + 3, 'z');
	v6.insert(v6.begin() + 3, 'x');
	for (auto e : v6)
	{
		cout << e << " ";
	}
	cout << endl;

	v6.erase(v6.end() - 1);
	v6.erase(v6.end() - 1);
	v6.erase(v6.end() - 1);
	v6.erase(v6.end() - 1);
	for (auto e : v6)
	{
		cout << e << " ";
	}
	cout << endl;
}

int main()
{
	vectorTest();

	return 0;
}

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯刷题018——和与乘积(贪心)

2021国赛&#xff1a;和与乘积 题目描述 给定一个数列 &#xff0c;问有多少个区间[L,R] 满足区间内元素的乘积等于他们的和&#xff0c;即 输入描述 输入第一行包含一个整数 n&#xff0c;表示数列的长度。 第二行包含 n 个整数&#xff0c;依次表示数列中的数 a1​,a2​,⋯,a…

【Vue】模板语法——文本插值

一、模板语法什么是模板语法Vue 使用一种基于 HTML 的模板语法&#xff0c;使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML&#xff0c;可以被符合规范的浏览器和 HTML 解析器解析。在底层机制中&#xff0c;Vue 会将模板编…

(十四)docker安装nacos

一、简介 操作系统&#xff1a;Linux CentOS 7.3 64位 docker版本&#xff1a;19.03.8 nacos版本&#xff1a;默认拉取最新版本 二、实践 1、拉取镜像 docker pull nacos/nacos-server 2、运行容器 docker run --name nacos -p 8848:8848 \ --privilegedtrue \ --restar…

二、什么是GStreamer

GStreamer是一个用于创建流媒体应用程序的框架。基本的设计来自俄勒冈研究生院的视频管道&#xff0c;还有一些来自DirectShow的想法。 GStreamer的开发框架使编写任何类型的流媒体应用程序成为可能。GStreamer框架旨在使编写处理音频或视频或两者同时处理的应用程序变得容易。…

01_学习springdoc的基本使用

文章目录1 什么是 springdoc ?2 springdoc 基本信息3 maven 依赖4 正文来袭4.1 给 Controller 加注解4.2 给 Model 加注解5 大功告成1 什么是 springdoc ? 网上冲浪&#x1f3c4;&#x1f3fb;‍♂️时&#xff0c;无意间发现 java web 应用程序的在线接口文档&#xff0c;除…

开源大数据分析平台的内容有什么?

在大数据时代&#xff0c;做好数据管理是非常重要的一个步骤。可以给企业做出正确的经营决策&#xff0c;指引新的发展方向。因此&#xff0c;随着数字化时代的到来&#xff0c;很多企业都倾向于寻找适宜的开源大数据分析平台&#xff0c;以此提升企业办公协作效率&#xff0c;…

【184】Win10下Java8调用Python的face_recognition库来实现人脸识别

前言 face_recognition 是一个开源的、人脸识别的Python库。本文讲解了在 Windows 10操作系统上&#xff0c;使用 Java8 来调用 Python 的 face_recognition 库来实现人脸识别。虽然 Java8 有 Jython&#xff0c;但是由于截至发文时 Jython 支持的版本太老&#xff08;只有Pyt…

oracle——列表分页查询(原理)

文章目录前言数据表的准备分页sql1、简单分页实现2、排序分页3、排序优化前言 在平时的开发中&#xff0c;Oracle的分页查询用的特别多&#xff0c;接下来不止是说使用&#xff0c;更讲分页sql写法的原理。 数据表的准备 创建一张数据表&#xff0c;并填充大量的数据。 cre…

大数据技术架构(组件)12——Hive:判断函数

1.4.6、判断函数1.4.6.1、ifselect if(11,a,b),if(12,a,b) ;1.4.6.2、isnullselect isnull(1),isnull(null);1.4.6.3、isnotnullselect isnotnull(1),isnotnull(null);1.4.6.4、nvlselect nvl(1,1),nvl(null,1);1.4.6.5、coalesceselect coalesce(1,null,2,3,null,4,null),coal…

感谢第三弹 | 开启地铁国产化浪潮 GBASE获多方城市“地下动脉”肯定

岁末年初&#xff0c;GBASE收到了来自深圳地铁、高新现代智能系统股份有限公司、深圳达实智能股份有限公司等客户及合作伙伴发来的荣誉证书及感谢信。作为亲密无间的战友&#xff0c;GBASE携手高新现代、达实智能在深圳地铁CLC、ACC、AFC多个条线项目中通力合作&#xff0c;助力…

背包问题学习

01背包 01背包&#xff08;0-1 Knapsack Problem&#xff09; 有NNN件物品和一个容量为VVV的背包。放入第iii件物品耗费的费用是CiC_iCi​,得到的价值为WiW_iWi​。求解将哪些物品装入背包可以使价值总和最大 设F[i,v]F\left[i,v\right]F[i,v]表示前iii件物品敲好放入一个容量…

软件防错设计

出现非预期错误的原因解决方案原理介绍断根原理原理&#xff1a;将可能造成人错误的原因/条件从根本上排除断绝掉。通过冗余设计&#xff0c;屏蔽掉其中9种错误的方式&#xff1b;案例&#xff1a;USB的SD卡防写开关。4种可能性断根设计为只有1种可能是正确的。软件设计&#x…

Linux学习之环境准备【Vm+Centos】

文章目录前言一 VMware Workstation17 Pro下载和安装1.1 Vm下载指南1.2 VM安装指南二 Centos7安装2.1 装机器2.2 装系统三 补充内容3.1 卸载Centos前言 工欲善其事&#xff0c;必先利其器&#xff0c;我们要学习Linux当然需要Linux的环境由于大部分人使用的是Windows系统无法进…

介绍OAuth2

目录 一、什么是OAuth2&#xff1f; 二、OAuth2中的角色 1、资源所有者 2、资源服务器 3、客户 4、授权服务器 三、认证流程 四、OAuth2授权方式 注&#xff1a;使用令牌方式的优点 1、授权码 2、隐藏方式 3、密码方式 4、凭证方式 一、什么是OAuth2&#xff1f…

深入浅出ConcurrentHashMap1.8

前言 HashMap是我们平时开发过程中用的比较多的集合&#xff0c;但它是非线程安全的&#xff0c;在涉及到多线程并发的情况&#xff0c;进行get操作有可能会引起死循环&#xff0c;导致CPU利用率接近100%。 final HashMap<String, String> map new HashMap<String,…

基于Springboot搭建java项目(二十四)——权限认证框架SpringSecurity

权限认证框架SpringSecurity 一、为什么要做权限认证 在WEB开发的过程当中&#xff0c;安全一直是很重要的一方面。安全和直接的业务是没有直接挂钩的&#xff0c;但是在业务实现的初期如果没有考虑的安全性的化&#xff0c;在后期迭代的时候就会出现很多的问题。例如存在着安…

4.1--贪心--活动安排问题

今天开始&#xff0c;快速过一遍贪心&#xff0c;贪心要比动态规划简单许多&#xff0c;但是&#xff0c;我们也要理解其中的证明过程 贪心算法采用自顶向下&#xff0c;以迭代的方法做出相继的贪心选择&#xff0c;每做一次贪心选择就将所求问题简化为一个规模更小的子问题&am…

Maven实战-3.setting.xml标签说明

前言 持续更新中… setting.xml文件 1.<localRepository> 自定义本地Maven仓库目录地址。 这样本地的仓库地址就是配置的目录了。 2.<proxies> 基于安全因素考虑&#xff0c;使用通过安全认证的代理访问因特网&#xff0c;这时候需要为Maven配置HTTP代理&…

TOOM大数据舆情监控系统方案,如何做好舆情大数据监测分析?

大数据舆情监控是指通过对大量数据的收集、分析和处理&#xff0c;了解舆论的发展趋势和活动&#xff0c;并对舆论风险进行预警和评估&#xff0c;以保障企业的舆论安全。TOOM大数据舆情监控系统方案&#xff0c;如何做好舆情大数据监测分析? 一、大数据舆情监控系统方案 1.…

车载以太网 - SomeIP - SomeIP Entry - 04

前面总纲内容有说,车载以太网中的SomeIP内容是非常多的,并且为了实现SOA的相关需求,提供了多种多样的报文类型,因此也就有了今天要说的SomeIP-SD中的重要组成部分之一的条目(Entry)部分,而SomeIP-SD在车载网络中占有相当大的比重,可以当做是一定要实现的,如果这块不实…