【C++】vector的使用及其模拟实现

这里写目录标题

  • 一、vector的介绍及使用
    • 1. vector的介绍
    • 2. 构造函数
    • 3. 遍历方式
    • 4. 容量操作及空间增长问题
    • 5. 增删查改
    • 6. vector二维数组
  • 二、vector的模拟实现
    • 1. 构造函数
    • 2. 迭代器和基本接口
    • 3. reserve和resize
    • 4. push_back和pop_back
    • 5. insert和erase
    • 5. 迭代器失效问题
    • 5. 浅拷贝问题
  • 三、vector模拟实现整体源码


一、vector的介绍及使用

1. vector的介绍

  1. vector是表示可变大小数组的序列容器。
  2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
  3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小,为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。 就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。 不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
  5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
  6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。 对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

2. 构造函数

vector学习时一定要学会查看文档:vector文档介绍,vector在实际中非常的重要,在实际中我们熟悉常见的接口就可以。当然我们使用vector之前需要包头文件#include<vector>

下面我们来看一下vector的构造函数:

在这里插入图片描述

因为在vector中是重载了[]访问运算符的,所以我们可以直接使用[]访问运算符来访问,vector中的元素。下面我们直接来举几个例子来看一下构造函数的用法:

在这里插入图片描述

这里我们可以看到和string不同的是,这里的capacity表示的就是所开辟的空间中最多能存储的元素的个数,但在string中则需要多开辟一个空间为’\0’预留位置。

迭代器区间构造本质上是一个函数模板,所以我们可以用任意类来构造vector对象。


3. 遍历方式

💕 [ ]遍历

在这里插入图片描述

int main()
{
	vector<int> v2(5, 0);
	for (int i = 0; i < v2.size(); i++)
		cout << v2[i] << " ";
	cout << endl;

	return 0;
}

在这里插入图片描述

当然了,因为[ ]重载了const和非const两个版本,所以[ ]既可以对const对象使用又可以对非const对象使用。

💕 使用迭代器遍历

在这里插入图片描述

vector和string一样,也有反向迭代器rbegin和rend,他们表示获取最后一个数据位置的reverse_iterator和第一个数据前一个位置的reverse_iterator。

int main()
{
	vector<int> v2;
	v2.push_back(1);
	v2.push_back(2);
	v2.push_back(3);
	v2.push_back(4);
	v2.push_back(5);
	vector<int>::iterator it = v2.begin();
	while (it != v2.end())
	{
		cout << *it << " ";
	}
	cout << endl;

	vector<int>::reverse_iterator rit = v2.rbegin();
	while (rit != v2.rend())
	{
		cout << *rit << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

💕 使用范围for遍历

int main()
{
	vector<int> v2;
	v2.push_back(1);
	v2.push_back(2);
	v2.push_back(3);
	v2.push_back(4);
	v2.push_back(5);
	for (auto e : v2)
		cout << e << " ";
	cout << endl;
	return 0;
}

在这里插入图片描述

当然范围for遍历并不是什么高深的语法,它的本质还是被替换成了迭代器进行遍历。


4. 容量操作及空间增长问题

💕 容量操作

在这里插入图片描述

这是vector中的容量操作的几个api接口,其中第二个max_sizeshrink_to_fit是我们在之前学习string的时候没有见过的接口,其中max_size是返回这个容器中可以容纳的最多的元素的个数,这个接口我们一般都不会用。shrink_to_fit是让我们容器的容量减少到容器中元素的个数。因为缩容所消耗的代价是比较大的。一般都不会轻易缩容,所以这个接口我们一般也不会用。

最重要的两个接口还是reserve和rsize,reserve只用于扩容,不会改变size的大小,但是resize不仅会扩容,他还会改变size的个数。

在这里插入图片描述

💕 空间增长问题

对于不同的STL版本,他的扩容机制也是不同的。我们知道,vs下和g++分别用的是不同的STL版本,下面我们分别来看一下他们的扩容机制有什么不同。

//测试vector的默认扩容机制
void TestVectorExpand()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making v grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

vs下的扩容机制:

在这里插入图片描述

g++下的扩容机制:

在这里插入图片描述

这里我们需要注意几点:

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问
    题。
  • resize在开空间的同时还会进行初始化,影响size

5. 增删查改

在这里插入图片描述

push_back和push_back这两个接口我们在string中见过,表示的是尾插和尾删,这里我们不再细说。

findswap

注意: 这里的find函数是算法库里的一个函数,而并非vector提供的接口。

在这里插入图片描述

这里需要提供一段迭代器区间,然后在后面跟上我们需要查找的元素,如果查找成功就返回他的迭代器,如果查找不成功,就返回end迭代器位置。

在这里插入图片描述

在这里插入图片描述

既然算法库里面已经有swap这个接口了,为什么在vector中还要再次设计一个呢?

其实这是因为算法库中的swap函数具有深拷贝的问题,深拷贝的代价是很大的,对于vector这样的类来说直接使用算法库里面的swap函数的拷贝代价太大,所以vector自己提供了一个自己的swap函数,其实vector中的swap函数的内部也是用了算法库中的swap。

inserterase

这里的inserterase和string也有所不同,string中的这两个接口在操作完成后会返回对象本身,但是在vector中则是操作完成后返回要插入或者删除的那个位置的迭代器。当然了,不光光是vector,STL中的所有容器的插入和删除接口都是这样设计的。

在这里插入图片描述

基本用法如下:

在这里插入图片描述

这里我们需要注意一点是:当我们删除一个元素的时候,删除位置的范围是【begin(),end()),但是当我们插入一个元素的时候,插入位置的范围是【begin(),end()】

当然了,这里存在一种迭代器失效的问题,下面我们在模拟实现的时候再来细说。


6. vector二维数组

int main()
{
	//设置二维数组的行数和列数
	int row = 5, col = 5;
	
	//创建二维数组并初始化为全0
	vector<vector<int>> vv(row, vector<int>(col, 0));
	
	//获取二维数组的行数
	int size_row = vv.size();
	
	//获取二维数组的列数
	int size_col = vv[0].size();

	//插入列元素
	vv[0].push_back(100);
	
	//插入行
	vv.push_back(vector<int>(5, 1));
	
	//遍历二维数组
	for (size_t i = 0; i < vv.size(); i++)
	{
		for (size_t j = 0; j < vv[i].size(); j++)
		{
			cout << vv[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

在这里插入图片描述


二、vector的模拟实现

当我们在模拟实现vector容器之前,需要先来参考一下源码,主要是为了整体看一下库里面vector的底层实现逻辑。

那么我们应该如何参考源码呢?让我们完全看懂源码是不可能的,所以我们应该抓住重点核心来看,最重要的就是这个类的成员变量和核心的成员函数,下面我们来看一下我们在模拟vector之前需要关注的源码中的地方。

template <class T, class Alloc = alloc>
class vector
{
public:
	typedef T value_type;
	typedef value_type* iterator;
	typedef const value_type* const_iterator;
	//...
	size_type size() const { return size_type(end() - begin()); }
	size_type max_size() const { return size_type(-1) / sizeof(T); }
	size_type capacity() const { return size_type(end_of_storage - begin()); }
	iterator begin() {return start;}
	iterator end() {return finish;}
private:
	iterator start;
	iterator finish;
	iterator end_of_storage;
};

通过这一部分的源码我们可以看到vector中的三个成员变量是由三个迭代器实现的,在这里迭代器是一个原生指针。然后我们根据源码中的size()capacity()等函数。可以推断出这三个指针所代表的意义:

在这里插入图片描述

start指向第一个元素的位置,finish指向容器中有效元素中最后一个元素后面的位置,end_of_storage指向容器能容纳最大容量之后的位置。其实和我们在C数据结构阶段实现的顺序表没有什么本质的区别,不过是使用了三个指针来维护所开辟的空间罢了。


1. 构造函数

💕 默认无参构造

//无参构造
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{}

💕 n个val构造

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

我们在写构造函数的时候为了防止野指针的出现,需要在初始化列表中将三个指针全部初始化为空。为了减少频繁扩容所带来的消耗,可以先调用reserve函数预先开辟一块空间,然后依次将要初始化的对象进行尾插。这里我们可以要初始化的值val预先设定缺省参数,这里我们不能将缺省值设置为0,因为要初始化的对象可能是int类型、double类型、还有可能是一个自定义类型,所以这里我们可以将缺省值给定为一个T类型匿名对象

这里我们还需要注意的是:引用会延长匿名对象的生命周期到引用对象域结束,因为这里的val就是匿名对象的别名,如果匿名对象在当前行就之后就销毁的话,val也会被销毁。同时,因为匿名对象具有常性,所以我们需要用const来修饰val。

💕 迭代器区间构造

template <class InputIterator>		
vector(InputIterator first, InputIterator end)
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{
	while (first != end)
	{
		push_back(*first);
		first++;
	}
}

这里我们提供一个迭代器模板,可以提供任意类型的迭代器。

在这里插入图片描述

在提供以上的函数接口之后,如果我们尝试构造一个内容为5个10的对象时候,这里会出现间接寻址的错误,这是因为编译器在进行模板实例化以及函数参数匹配时会调用最匹配的那一个函数,当我们将T实例化为int之后,由于两个参数都是int,所以迭代器构造函数会直接将Inputlterator实例化为int,但是如果n个val构造来说,不仅需要将T实例化为int,还需要将第一个参数隐式转换为size_t;所以编译器会优先调用迭代器区间构造函数,直接对int类型解引用的话就会报错。

为了防止这种情况发生,我们可以采取和STL中一样的解决方式——重载一份第一个参数为int的构造函数。

vector(int n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(n);
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

💕 拷贝构造

vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	_start = new T[v.capacity()];
	memcpy(_start, v._start, v.size() * sizeof(T));//——会导致浅拷贝问题
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

2. 迭代器和基本接口

💕 迭代器的实现

为了能够使用范围for循环和其他别的功能,这里我们来实现一下vector的迭代器,同时,为了能够对const对象也使用迭代器,这里我们还需要设计一份const版本的迭代器。

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;
}

💕 基本接口的实现

size_t size() const
{
	return _finish - _start;
}
size_t capacity() const
{
	return _end_of_storage - _start;
}
size_t empty() const
{
	return _start == _finish;
}
void clear()
{
	_finish = _start;
}
//析构函数
~vector()
{
	delete[] _start;
	_start = _finish = _end_of_storage = nullptr;
}

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

由于以上的接口比较简单,这里我们就不再做过多的解释。


3. reserve和resize

💕 reserve的实现

void reserve(size_t n)
{
	if (n > capacity())
	{
		int sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			memcpy(tmp, _start, size() * sizeof(T));
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

如果我们需要扩的容量的大小小于原来的容量时,我们默认不执行扩容操作。这里我们需要先定义一个临时变量sz将扩容之前的元素个数保存下来,因为扩容之后给finish赋值时,start已经指向了新的空间,但是finishi还未改变,所以直接使用size()会出现问题,当然了,在这个接口里面存在着一个严重的问题,我们到后面会指正。

💕 resize的实现

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++;
		}
	}
}

这里我们和前面的构造函数一样,给一个匿名对象的缺省值,如果需要扩的容量比原来的容量小时,
我们只需要将容器中的有效的数据个数size()减少即可。否则的话,如果n的个数大于原来的容量时,我们可以先使用reserve函数将原来的空间扩大,然后再依次添加到finish之后即可。直到finish的大小等于capacity的大小。


4. push_back和pop_back

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

//pop_back的实现
void pop_back()
{
	assert(!empty());
	_finish--;
}

这两个接口的实现就非常简单了,push_back的实现,如果finish等于capacity容量的大小我们则需要先调用reserve函数进行扩容。当然在这里我们判断capacity的时候,需要特殊处理一下,因为如果第一次创建的未初始化的对象的capacity是0时,直接调用reverse(capacity)会出现问题。对于pop_back的实现我们还需要先实现一个判空操作的函数。


5. insert和erase

💕 insert

iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos <= _finish);
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;
	return pos;
}

这里的实现逻辑也很简单,先判断要插入的位置是否符合规范,在判断是否需要扩容,最后我们重后往前挪动数据即可。当然,最后别忘了finish需要++。

💕 erase

iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);
	iterator end = pos + 1;
	while (end < _finish)
	{
		*(end - 1) = *end;
		end++;
	}
	_finish--;
	return pos;
}

这里我们首先需要注意的是,pos位置不能等于finish,因为finish是最后一个有效元素的后一个位置,重前往后挪动数据。最后记得finish–。


5. 迭代器失效问题

💕 野指针问题

在这里插入图片描述

这里我们可以看到,当我们插入一个元素后,再次打印的时候就会出现问题,其实本质上还是我们的insert函数写的有问题。

在这里插入图片描述

这里我们修改一下insert函数,当需要扩容的时候先使用一个变量记录一下pos位置相对于第一个元素的位置,再扩容之后更新一下pos指针的位置。这样第一种迭代器失效的问题就得到解决了。

iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos <= _finish);
	if (_finish == _end_of_storage)
	{
		int sz = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + sz;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;
	return pos;
}

在这里插入图片描述

💕 扩容之后pos位置的迭代器失效

在这里插入图片描述

这里我们可以看到在我们将某个位置插入一个数字后,迭代器可能会失效,因为是传值传递所以insert之后pos的地址可能已经发生改变了,所以insert之后,默认pos迭代器失效了的,因为不知道哪一次会进行扩容操作。所以insert之后的我们需要用pos接受一下就可以解决这种问题。

vector<int> v2;
v2.push_back(1);
v2.push_back(2);
v2.push_back(3);
v2.push_back(4);

func(v2);
auto pos = find(v2.begin(), v2.end(), 3);
if (pos != v2.end())
{
	pos = v2.insert(pos, 100);
}
(*pos)++;
func(v2);

在这里插入图片描述

💕 erase之后默认迭代器失效

其实不仅仅是insert,erase之后迭代器也会失效,下面我们来看一下erase之后迭代器失效的问题,这里我们先来举三个例子——删除一个序列中所有的偶数。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

为什么同样的代码当数据不同时会出现截然不同的结果呢?

  • 第一份代码能够运行出正确的结果完全是一个偶然。可以看到,第二份代码由于删除元素后 pos 不再指向原位置,而是指向下一个位置,所以 erase 之后会导致一个元素被跳过,导致部分偶数没有被删除,但好在末尾是奇数,所以程序能够正常运行。
  • 但是最后一份代码就没那么好运了,由于最后一个元素是偶数,所以 erase 之后 pos 直接指向了 _finish 的下一个位置,循环终止条件失效,发生越界。

所以,我们统一认为 insert 和 erase 之后迭代器失效,必须更新后才能再次使用。

那么正确的做法就是当每次插入或者删除数据后都是用pos来接受一下。更新一下pos位置的数据,如果没有插入或者删除数据,则直接pos++即可。

	auto it2 = v3.begin();
	while (it2 != v3.end())
	{
		if (*it2 % 2 == 0)
		{
			it2 = v3.erase(it2);
		}
		else
		{
			it2++;
		}
	}
	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;

5. 浅拷贝问题

💕 拷贝构造问题

//拷贝构造
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	_start = new T[v.capacity()];
	memcpy(_start, v._start, v.size() * sizeof(T));//——会导致浅拷贝问题
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

这份代码在我们看来并没有什么问题,但其实它存在一个隐藏的问题,当我们实例化的对象是内置类型时,并不会发生浅拷贝,但是如果我们实例化的对象是自定义类型时,特别是自定义类型的对象中有资源分配的时候,这份代码就会导致严重的问题。

在这里插入图片描述
在这里插入图片描述

当我们的vector中的元素是string类型时,直接这样写就会导致浅拷贝问题,这里我们来看一下原因。

在这里插入图片描述

在这里我们确实是开辟了一块新的空间,同时也将原来对象中的数据拷贝到了新的空间中,但是由于这个对象是一个string字符串类型,直接使用memcpy进行拷贝是按照字节进行了拷贝,也就是说两个对象中的vector中的string中的_str同时指向了一块空间,所以最后调用自定义类型的析构函数时,同一块空间会被析构两次。最后程序奔溃。

正确的写法:

vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	_start = new T[v.capacity()];
	for (size_t i = 0; i < v.size(); i++)
	{
		_start[i] = v._start[i];
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

💕 reserve问题

//reserve扩容
void reserve(size_t n)
{
	if (n > capacity())
	{
		int sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			memcpy(tmp, _start, size() * sizeof(T));
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

这份代码和上面的代码的原因大同小异,也是因为memcpy会带来浅拷贝问题,下面我们来看一个例子:当我们大量插入元素的时候,会导致扩容的出现,那么扩容又会带来什么样的问题呢?

在这里插入图片描述
在这里插入图片描述

当在push_back内部调用 reserve 函数进行扩容时,扩容时我们虽然进行了深拷贝,但是空间里面的内容我们是使用 memcpy 按字节拷贝过来的,这就导致原来的 v 里面的 string 元素和临时空间里面的string元素指向的是同一块空间。所以当拷贝完数据就会delete掉原来的空间,由于二者指向的是同一块空间,所以现在v中的string元素指向的是一块已经被释放掉的空间,当最后出了作用域调用析构的时候就会出现问题。

正确的写法:

void reserve(size_t n)
{
	if (n > capacity())
	{
		int sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

💕 赋值运算符重载的问题

如果我们不重载=赋值运算符,使用vector中的元素是vector类型时,这里还是会出现浅拷贝问题,因为库中的string类重载了=,所以直接赋值进行的是深拷贝,这里我们需要重载一下=,这样无论我们的vector中存储的是哪一种自定义类型都可以进行深拷贝了。

vector<T>& operator=(const vector<T>& v)
{
	vector<T> tmp(v);
	swap(tmp);
	return *this;
}

三、vector模拟实现整体源码

namespace cjl
{
	template <class T>
	class vector 
	{
	public:
		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()
		//	:_start(nullptr)
		//	,_finish(nullptr)
		//	,_end_of_storage(nullptr)
		//{}

		构造函数
		//vector(size_t n, const T& val = T())
		//	:_start(nullptr)
		//	,_finish(nullptr)
		//	,_end_of_storage(nullptr)
		//{
		//	reserve(n);
		//	for (int i = 0; i < n; i++)
		//	{
		//		push_back(val);
		//	}
		//}

		//构造函数的——现代写法(在成员变量声明的时候给缺省值)
		//无参构造
		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);
			}
		}

		//拷贝构造
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

		//迭代器区间构造
		template <class InputIterator>		
		vector(InputIterator first, InputIterator end)
		{
			while (first != end)
			{
				push_back(*first);
				first++;
			}
		}
		
		//resize的实现
		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++;
				}
			}
		}

		//reserve的实现
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				int sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

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

		//pop_back的实现
		void pop_back()
		{
			assert(!empty());
			_finish--;
		}

		//insert的实现
		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start && pos <= _finish);
			if (_finish == _end_of_storage)
			{
				int sz = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + sz;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;
			return pos;
		}
		
		//erase的实现
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator end = pos + 1;
			while (end < _finish)
			{
				*(end - 1) = *end;
				end++;
			}
			_finish--;
			return pos;
		}
		
		//重载[]
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}

		//重载=运算符
		vector<T>& operator=(const vector<T>& v)
		{
			vector<T> tmp(v);
			swap(tmp);
			return *this;
		}

		//swap函数的实现
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		size_t empty() const
		{
			return _start == _finish;
		}

		void clear()
		{
			_finish = _start;
		}
		//析构函数
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

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

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

相关文章

docker 基础(二)

常见命令 Docker最常见的命令就是操作镜像、容器的命令&#xff0c;详见官方文档&#xff1a;https://docs.docker.com/ 数据卷 命令说明文档地址docker volume create创建数据卷docker volume createdocker volume ls创建数据卷docker volume lsdocker volume rm查看所有数…

NGINX 高频面试题及实践总结

NGINX 是一个高性能的开源 Web 服务器和反向代理服务器&#xff0c;被广泛应用于互联网架构中。在面试中&#xff0c;对 NGINX 的相关知识可能会成为考察的重点。下面我们整理了一些常见的 NGINX 面试题及答案&#xff0c;希望对大家在面试前的准备有所帮助。 ## 1. 什么是 NG…

如何系统性的学习推荐系统?

推荐一本适合推荐系统、计算广告、个性化搜索领域的从业人员阅读的书&#xff1a;《互联网大厂推荐算法实战》。快手公司算法专家10余年的实战经验总结。涵盖一线互联网公司当前采用的主流推荐算法&#xff0c;凸显可用性、实用性提供从算法基本原理&#xff0c;到技术框架再到…

python语言1

一、pytho中的注释 1.1注释的理解 程序员在代码中对代码功能解释说明的标注性文字可以提高代码的可读性注释的内容将被python解释器忽略&#xff0c;不被计算机执行 1.2注释的分类 注释分为&#xff1a;单行注释、多行注释、中文声明注释 &#xff08;1&#xff09;单行注…

java项目打包运行报异常:xxxxx-1.0-SNAPSHOT.jar中没有主清单属性

pom.xml中加入这段话即可 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.4</version><executions><execution><…

雨云:为你拨开云雾见青天

一、雨云品牌概览 雨云&#xff0c;这名字一听就让人联想到蓝天白云&#xff0c;清爽自然。那么&#xff0c;这个品牌是否真的如其名&#xff0c;能为我们这些在数字世界中漂泊的旅人提供一片宁静、稳定的“云”呢&#xff1f;接下来&#xff0c;让我们深入了解雨云的资质、能…

【Micropython教程】I2C的使用

文章目录 前言一、I2C的使用1.1 分析一种情况1.2 初始化I2C总线1.3 扫描可用的I2C设备1.4 向指定地址写入数据1.5 读取指定地址的数据1.6 关闭I2C总线 二、示例代码总结 前言 MicroPython 是一种精简的 Python 实现&#xff0c;旨在运行在微控制器和嵌入式系统上。在嵌入式开发…

AVL 树

AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决…

vue3的router

需求 路由组件一般放在&#xff0c;pages或views文件夹, 一般组件通常放在component文件夹 路由的2中写法 子路由 其实就是在News组件里面&#xff0c;再定义一个router-view组件 他的子组件&#xff0c;机会渲染在router-view区域 路由传参 <RouterLink :to"/news…

腾讯云最新活动_腾讯云促销优惠_代金券-腾讯云官网入口

腾讯云服务器多少钱一年&#xff1f;62元一年起&#xff0c;2核2G3M配置&#xff0c;腾讯云2核4G5M轻量应用服务器218元一年、756元3年&#xff0c;4核16G12M服务器32元1个月、312元一年&#xff0c;8核32G22M服务器115元1个月、345元3个月&#xff0c;腾讯云服务器网txyfwq.co…

xsslabs第七关

源码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;charsetutf-8"> <script> window.alert function() { confirm("完成的不错&#xff01;"…

《2023年勒索软件攻击态势报告》

获取方式&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1zd-yVsuGwJADyyGNFR_TIQ?pwd2lo0 提取码&#xff1a;2lo0

游戏空间划分技术

一、 前言 空间划分算法有很多&#xff0c;比如均匀网格&#xff0c;四/八叉树&#xff0c;k-d树&#xff0c;Bsp树&#xff0c;每一种算法都有自己的优缺点&#xff0c;我们需要从理论上理解这些算法&#xff0c;然后在实际项目中进行灵活的运用。 游戏中经常使用空间划分算…

k8s二进制部署的搭建

1.1 常见k8s安装部署方式 ●Minikube Minikube是一个工具&#xff0c;可以在本地快速运行一个单节点微型K8S&#xff0c;仅用于学习、预览K8S的一些特性使用。 部署地址&#xff1a;Install Tools | Kubernetes ●Kubeadm Kubeadm也是一个工具&#xff0c;提供kubeadm init…

【前端素材】推荐优质后台管理系统网页Hyper平台模板(附源码)

一、需求分析 1、系统定义 后台管理系统是一种用于管理和控制网站、应用程序或系统的管理界面。它通常被设计用来让网站或应用程序的管理员或运营人员管理内容、用户、数据以及其他相关功能。后台管理系统是一种用于管理网站、应用程序或系统的工具&#xff0c;通常由管理员使…

Charles抓包 - 安装、激活、证书配置

最近刚好又遇到了抓包的需求&#xff0c;之前一直使用 Fiddler 抓包&#xff0c;这几年一直听大家都在用 Charles 抓包&#xff0c;正好一起了解下&#xff08;一般建议掌握一种抓包方式即可&#xff0c;都可以解决同种需求场景&#xff09; 抓包 Fiddler抓包 Charles 下载、安…

深度解读篇章:剖析构建互联网大厦的基石——TCP/IP协议全貌

&#x1f440;&#x1f440;&#x1f440; 引言 今天&#xff0c;我们一同揭幕的是驱动全球互联网脉搏跳动的核心机密——TCP/IP协议体系。没有它&#xff0c;就不会有现今这般高效便捷的网络生活体验&#xff0c;无论在线教育、远程办公&#xff0c;抑或是电子商务、社交媒体…

强大而灵活的python装饰器

装饰器&#xff08;Decorators&#xff09; 一、概述 在Python中&#xff0c;装饰器是一种特殊类型的函数&#xff0c;它允许我们修改或增强其他函数的功能&#xff0c;而无需修改其源代码。装饰器在函数定义之后立即调用&#xff0c;并以函数对象作为参数。装饰器返回一个新…

Docker容器与虚拟化技术:OpenEuler 部署 docker容器应用

目录 一、实验 1.环境 2.OpenEuler 安装 docker 2.镜像加速 3.docker部署LAMP 二、安装docker报错 2.docker如何快速删除容器与镜像 3.docker创建mysql容器失败 4.docker创建apache容器失败 5.docker创建php-fpm容器失败 6. 80端口与php访问失败 7.httpd容器进入不…

【刷题】Leetcode 1609.奇偶树

Leetcode 1609.奇偶树 题目描述广度优先搜索&#xff08;BFS&#xff09;深度优先算法&#xff08;DFS&#xff09; 思路一&#xff08;BFS&#xff09;思路二&#xff08;DFS&#xff09;Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&a…