vector

175 阅读8分钟

STL(Standard Template Library标准模板库):

是c++标准库的重要组成部分.是一个包罗数据结构和算法的软件框架,

STL使得能够直接使用各种容器(vector、list、map等)和使用各种算法.

vector介绍

(1) vector是STL的一个顺序容器,底层使用一个可以动态增长的数组实现的.

cplusplus.com:

image.png (2) 同时它也是一个类模板,因为顺序表里可以存储不同类型的数据.

(3) allocator< T >是一个内存池,里面的数据都是T类型,(vector里alloc的模板参数给了缺省值)默认用库的,也可以自己显式传自己写的内存池.

--内存池介绍

一个自定义类,我们可以在它内部重载专属的operator new函数,

此时new这个类就会调用自己写的operator new函数.

如果多次new这个类,频繁向堆申请空间,效率不高.

池化技术可以解决频繁向堆申请内存的问题,即内存池

c++库里有一个内存池 —— allocator类,也叫空间配置器.

内存池一次向堆申请大量空间,此时申请空间不需要去找堆,而是找内存池申请(比向堆申请要快).

class ListNode
{
public:
	ListNode(int val = 0)
		:_next(nullptr)
		,_val(val)
	{}
	void* operator new(size_t n)
	{
		cout << "走重载的operator new" << endl;
		void* ret = _alloc.allocate(1);//在内存池里申请一个结点内存
		return ret;
	}

	void operator delete(void* block)
	{
		cout << "走重载的operator delete" << endl;
		//释放内存还给内存池
		_alloc.deallocate((ListNode*)block, 1);
	}
private:
	ListNode* _next;
	int _val;
	static allocator<ListNode> _alloc;//让该类的所有对象共用一个内存池【声明】
};
//定义
allocator<ListNode> ListNode::_alloc;

void test1()
{
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	delete n1;
	delete n2;
	delete n3;
}

image.png

--构造函数

(1) 全缺省的构造

explicit vector (const allocator_type& alloc = allocator_type());

explicit:不允许该单参数的构造函数进行隐式类型转换

allocator_type:即第二个模板参数Alloc类型,表示传递的内存池类型

allocator_type():调用该内存池类型的默认构造函数,用于初始化alloc

一般用c++库默认的内存池,使用时一般不需要传第二个模板参数Alloc

	void test2()
	{
		//显式实例化,显式传模板参数T,指定要存储的数据类型
		vector<int> v1;
		vector<double> v2;

		//调用成员函数push_back(),尾插数据
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);

		v2.push_back(1.1);
		v2.push_back(2.2);
		v2.push_back(3.3);

		//vector重载了operator[ ],可以像数组一样访问对应位置的值
		for (int i = 0; i < v1.size(); ++i)
		{
			cout << v1[i] << " ";
		}
		cout << endl;
		for (int i = 0; i < v2.size(); ++i)
		{
			cout << v2[i] << " ";
		}
	}

image.png

(2) 用n个val进行构造

explicit vector(size_type n, const value_type & val = value_type(), const allocator_type & alloc = allocator_type());

size_type:即size_t

value_type:即T,顺序表里存储的数据类型

value_type():调用T类型的默认构造函数,内置类型有默认值

	void test3()
	{
		int a = int();
		double b = double();
		
		//正常使用
		vector<int> v1(3, 1);//用3个1初始化v1

		vector<int> v2(3);//用3个默认的int初始化v2,即0
		vector<int*> v3(3);//用3个默认的int*初始化v3,即nullptr
	}

image.png

(3) 用一段迭代器区间,构造vector

template <class InputIterator>
vector(InputIterator first, InputIterator last,
	     const allocator_type & alloc = allocator_type());

为什么要写成函数模板?

因为可以用其他容器的迭代器区间来初始化vector.例:

	void test4()
	{
		string s1("abcdef");
		
		//char类型会隐式类型转换成int,迭代器类型string::iterator
		vector<int> v1(s1.begin(), s1.end());

		//迭代器类型vector<int>::iterator
		vector<int> v2(v1.begin(), v1.end());

		cout << "遍历v1:" << endl;
		for (size_t i = 0; i < v1.size(); ++i)
			cout << v1[i] << "  ";
		cout << endl;

		cout << "遍历v2:" << endl;
		for (size_t i = 0; i < v2.size(); ++i)
			cout << v2[i] << "  ";
		cout << endl;
	}

注意:vector是一个类模板,vector< int >才是实例化出来的类

image.png

(4) 拷贝构造

vector (const vector& x);

	void test5()
	{
		vector<int> v1(3, 1);

		//效果相同,内部实现深拷贝
		vector<int> v2 = v1;
		vector<int> v3(v1);
	}

--vector的遍历

(1)调用operator[ ]运算符重载,用[ ]访问对应位置的数据

reference operator[] (size_type n);

const_reference operator[] (size_type n) const;

reference:即T&,可以通过[ ]直接修改n下标位置的数据

const_reference:即const T&,const对象会调用第二个函数,不允许修改内容

(2) 迭代器遍历

vector是一个类模板,vector< T >是实例化出来的类,

所以它的迭代器类型为:vector<T>::iterator

	void test6()
	{
		vector<int> v(3, 2);
		vector<int>::iterator it = v.begin();
		//左闭右开
		while (it != v.end())
		{
			cout << *it << "  ";
			++it;
		}
	}

image.png

--扩容机制

size_type capacity() const;//返回vector当前容量

不断插入数据,若当前容量改变,打印出来,获取当前平台下vector的扩容机制.

	//测试vs2022的vector扩容机制
	void test7()
	{
		vector<int> v;
		cout << "初始容量:" << v.capacity() << endl;
		size_t curCapacity = v.capacity();
		size_t maxNum = 1000;
		for (size_t i = 0; i < maxNum; ++i)
		{
			v.push_back(i);
			//插入后如果发生扩容
			if (curCapacity != v.capacity())
			{
				curCapacity = v.capacity();
				cout << "新扩容:" << curCapacity << endl;
			}
		}
	}

image.png

扩容有消耗,若提前知道要插入的数据量,可以用reserve()提前设置容量,一次性申请空间.

--查找

vector内部没有提供find函数,

因为vector、list、map等容器,它们的find是通用的,都是在一段迭代器区间里找一个数据

而string不同,它可能要找一个字符、一个子串,同时要返回下标.

这个通用的find被放在algorithm文件中.

template <class InputIterator, class T>
   InputIterator find (InputIterator first, InputIterator last, const T& val);

在一个迭代器区间[first, last)查找val,并返回第一个出现的val对应的迭代器类型.

若没有找到,返回last.

#include <algorithm>
	void test8()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(2);
		vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
		if (it != v1.end())
		{
			//找到并修改
			*it = 4;
			for (auto x : v1)
				cout << x << " ";
		}
		else
			cout << "未找到" << endl;
	}

image.png

--插入

(1) 在迭代器位置position之前,插入val

iterator insert (iterator position, const value_type& val);

(2)在position之前插入n个val

void insert (iterator position, size_type n, const value_type& val);

(3)在position位置之前,插入一段 迭代器区间 的数据

template <class InputIterator>
    void insert (iterator position, InputIterator first, InputIterator last);

例:

	void test9()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
                
          //在3之前插入5
		//先找到3的位置
		vector<int>::iterator pos1 = find(v.begin(), v.end(), 3);
		if (pos1 != v.end())
		{
			//找到并插入5
			v.insert(pos1, 5);
			for (auto x : v)
				cout << x << " ";
		}
		cout << endl << endl;

		//1 2 5 3 4
		//在5之前插入3个1
		vector<int>::iterator pos2 = find(v.begin(), v.end(), 5);
		if (pos2 != v.end())
		{
			v.insert(pos2, 3, 1);
			for (auto x : v)
				cout << x << " ";
		}
		cout << endl << endl;

		vector<int> v2(3, 5);// 5 5 5
		//12111534
		//在2之前插入v2.begin()~v2.end()
		vector<int>::iterator pos3 = find(v.begin(), v.end(), 2);
		if (pos3 != v.end())
		{
			v.insert(pos3, v2.begin(), v2.end());
			for (auto x : v)
				cout << x << " ";
		}
	}

image.png

--删除

(1) 删除position位置的数据

iterator erase (iterator position);

(2)删除[first,last)该迭代器区间的所有数据

iterator erase (iterator first, iterator last);

	void test10()
	{
		vector<int> v;
		for (size_t i = 0; i < 10; ++i)
			v.push_back(i);//0~9
		
		//删除4
		vector<int>::iterator pos = find(v.begin(), v.end(), 4);
		if (pos != v.end())
		{
			v.erase(pos);
			for (auto x : v)
				cout << x << " ";
		}
		cout << endl << endl;

		//删除0~5的所有数据,不包括5
		vector<int>::iterator startV = find(v.begin(), v.end(), 0);
		vector<int>::iterator endV = find(v.begin(), v.end(), 5);
		if (startV != v.end() && endV != v.end())
		{
			v.erase(startV, endV);
			for (auto x : v)
				cout << x << " ";
		}
	}

image.png

--vector< char > 和 string 差异

(1) string对象定义时后面默认有'\0'

(2) vector不支持+=,string支持+=一个字符/字符串

(3) vector没有内置find,string内置find用于查找字符或子串

(4) string支持<<和>>重载,可以直接打印字符串,vector不支持等.

vector< char >无法替代string

vector模拟实现

sgi版的stl源代码来源:

sgi-stl: 《STL源码剖析》Cygnus C++ 2.91 for windows版本的STL实现 github拷贝过来的https://github.com/lwbaptx/sgi-stl (gitee.com)

也能点下面链接直接下载:

gitee.com/jiangyoushi…

源码的成员变量: image.png

模板不支持分离编译,类模板的函数声明和定义要在同一文件中

因此小函数定义在类里面(默认带了inline),大函数定义在类外面(但保证在同一文件)

namespace lyh
{	
	template<class T>
	class vector
	{
	public:
		//vector底层是个连续的数组,迭代器就是原生指针
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start;  //指向第一个数据
		iterator _finish; //指向最后一个数据的下一个位置
		iterator _end_of_storage;//指向当前开的空间的末尾
	};
}

--构造与析构函数

		//默认构造
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

		//用n个val构造
		vector(size_t n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			//开空间+初始化空间
			T* tmp = new T[n];
			for (size_t i = 0; i < n; ++i)
				tmp[i] = val;

			//更新成员变量
			_start = tmp;
			_finish = _start + n;
			_end_of_storage = _finish;//初始容量和初始数据个数一致
		}
                
		//用一段迭代器区间构造
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			//元素个数
			size_t n = last - first;
			
			//开空间+拷贝数据
			T* tmp = new T[n];
			for (size_t i = 0; i < n; ++i)
			{
				tmp[i] = first[i];
			}
                        
			//更新成员变量
			_start = tmp;
			_finish = tmp + n;
			_end_of_storage = tmp + n;
		}
		~vector()
		{
			std::cout << "成功调用析构函数" << std::endl;//仅用于验证
			delete[] _start;
		}

//以下实现的函数仅用来验证构造出来的vector的数据
		size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _end_of_storage - _start;
		}
		T& operator[](size_t n)
		{
			//检查是否越界访问
			assert(n < size());
			return *(_start + n);
		}

test11以及下面的测试函数,都在lyh这个命名空间中,

该文件已经展开了std命名空间,在当前命名空间找不到的符号,

也会到std中找.

namespace lyh
{
	void test11()
	{
		vector<int> v1;
		vector<int> v2(3, 1);
		string s1("abcdefg");
		vector<int> v3(s1.begin(), s1.end());
		cout <<"v1的数据个数:"<< v1.size() << "  v1的初始容量:" << v1.capacity() << endl;
		cout << "v2的数据个数:"<<v2.size() << "  v2的初始容量:" << v2.capacity() << endl;
		cout << "v3的数据个数:" << v3.size() << "  v3的初始容量:" << v3.capacity() << endl;
		
		cout << "v2的数据如下:" << endl;
		for (size_t i = 0; i < v2.size(); ++i)
		{
			cout << v2[i] << " "; 
		}
		cout << endl;

		cout << "v3的数据如下:" << endl;
		for (size_t i = 0; i < v3.size(); ++i)
		{
			cout << v3[i] << " ";
		}
	}
}	

image.png

注意:

		//当定义vector<int> v2(3,1)时;
		//由于编译器会调用最匹配的函数,会调用到迭代器区间构造的函数
		//因为该函数的两个模板参数都相同,不会去调用vector(size_t n, const T& val)
		//所以多提供一个vector(int n, const T& val)
		vector(int n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			//开空间+初始化空间
			T* tmp = new T[n];
			for (size_t i = 0; i < n; ++i)
				tmp[i] = val;

			//更新成员变量
			_start = tmp;
			_finish = _start + n;
			_end_of_storage = _finish;//初始容量和初始数据个数一致
		}

--reserve和resize

reserve仅仅改变容量,不会改变数据量

resize改变数据量,必要时也会扩容

           //设置数据个数为n,新增加的数据初始化为val
		void resize(size_t n, const T& val)
		{
			//(1) n > 当前数据量
			if (n > size())
			{
				//扩容+初始化
				reserve(n);
				for (size_t i = size(); i < n; i++)
					_start[i] = val;

				_finish = _start + n;
			}
			//(2) n < 当前数据量
			else if (n < size())
			{
				//删除多出n的数据
				_finish = _start + n;
			}
			//(3) n =当前数据量,无操作
			else{}
		}
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//开空间,拷贝数据
				T* newBlock = new T[n];
				for (size_t i = 0; i < size(); ++i)
				{
					newBlock[i] = _start[i];
				}
				
				//记录当前size()的值,因为它内部是_finish - _start算出来的,更新成员变量时,不能使用size()
				size_t st = size();

				//释放旧空间
				delete[] _start;

				//更新成员变量
				_start = newBlock;
				_finish = newBlock + st;
				_end_of_storage = newBlock + n;
			}
		}
	void test12()
	{
		vector<int> v;
		//提前申请空间,不会有扩容
		//v.reserve(1000);

		v.resize(1000, 0); //改变数据量,改变容量
		cout << "初始容量:" << v.capacity() << endl;
		cout << "初始数据量:" << v.size() << endl;
		size_t curCapacity = v.capacity();
		size_t maxNum = 1000;
		for (size_t i = 0; i < maxNum; ++i)
		{
			v.push_back(i);
			//插入后如果发生扩容
			if (curCapacity != v.capacity())
			{
				curCapacity = v.capacity();
				cout << "新扩容:" << curCapacity << endl;
			}
		}
	}

image.png

image.png

--尾插尾删

		//const引用可以引用常量数据、引用隐式类型转换中间产生的临时变量
		//引用减少拷贝
		void push_back(const T& x)
		{
			//1 检查扩容
			if (_finish == _end_of_storage)
				reserve(capacity() ==   0 ?  5 : 2 * capacity() );
			//2 插入数据
			*_finish = x;
			++_finish;
		}
                
		void pop_back()
		{
			assert(size() > 0);
			--_finish;
		}                

--迭代器

		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}
	void test13()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.pop_back();

		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
          
          //依次取出容器v的每一个迭代器,解引用赋值给x(如果x不是引用)
		for (auto& x : v)
		{
			cout << x << " ";
		}
	}

image.png

--insert和erase的迭代器失效【重点】

insert和erase的实现

( 1 ) 以下的insert实现存在问题:

若发生扩容,_start等成员变量全都指向全新的空间,

但传过去的pos仍然指向旧空间,此时pos失效了(内部扩容发生的失效)

		//第一种插入实现:在pos位置之前插入val
		void insert(iterator pos, const T& val)
		{
			//允许头插或尾插
			assert(pos >= _start && pos <= _finish);
			
			//判断扩容
			if (_finish == _end_of_storage)
				reserve(capacity() == 0 ? 5 : capacity() * 2);
			
			//从后往前拷贝
			iterator cur = _finish;
			iterator pre = _finish - 1;
			while (cur != pos)
			{
				*cur = *pre;
				--pre;
				--cur;
			}
			//当cur == pos,说明所有数据挪好了

			//放置数据,更新_finish
			*pos = val;
			++_finish;
		}

( 2 ) 版本2:先计算扩容前pos与_start的相对位置,发生扩容后修改pos

		void insert(iterator pos, const T& val)
		{
			//允许头插或尾插
			assert(pos >= _start && pos <= _finish);

			//先计算当前pos与_start的相对位置
			size_t relative_pos = pos - _start;

			//保证扩容后,pos指向的相对位置与之前的一致
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 5 : capacity() * 2);
				pos = _start + relative_pos;//一定要更新pos
			}
			//接下来正常拷贝

			//从后往前拷贝
			iterator cur = _finish;
			iterator pre = _finish - 1;
			while (cur != pos)
			{
				*cur = *pre;
				--pre;
				--cur;
			}
			//当cur == pos,说明所有数据挪好了

			//放置数据,更新_finish
			*pos = val;
			++_finish;
		}
	void test14()
	{
		vector<int> v;
		v.insert(v.end(), 1);//尾插
		v.insert(v.begin(), 2);//头插
		//2  1
		for (auto& x : v)
			cout << x << " ";
		cout << endl << endl;

		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		//2 1 3 4 5
		//在3之前插入一个6
		//找到3的位置
		vector<int>::iterator pos = find(v.begin(), v.end(), 3);
		v.insert(pos, 6);
		for (auto& x : v)
			cout << x << " ";
	}

image.png

提问1:插入数据后,外面的pos还可以用吗?

在pos位置插入数据后,不要访问pos,因为插入后如果发生扩容,pos会失效成为野指针.

image.png

提问2:可以把函数原型改成:void insert(iterator& pos, const T& val)吗?

不能,会有下面的情况

image.png

(3) erase实现(没有缩容)

		void erase(iterator pos)
		{
			assert(size() > 0);
			assert(pos >= _start);
			assert(pos < _finish);
			//把后面的数据往前挪动
			iterator pre = pos;
			iterator cur = pos + 1;
			while (cur != _finish)
			{
				*pre = *cur;
				++pre;
				++cur;
			}
			--_finish;
		}

erase会导致pos失效吗?

如果内部有缩容机制,pos会失效,否则没有失效

STL是一种规范,规定了vector要实现的功能:插入、删除等。

但没有规定如何去实现。在少数编译器下,会有缩容机制(以空间换时间).

所以调用erase后,也不要去访问外面的pos.

insert和erase的使用

(1) 要求删除所有出现的1

	void test18()
	{
		//删除所有出现的1
		vector<int> v(3, 1);
		v.push_back(4);
		v.push_back(5);
		v.push_back(1);
		v.push_back(6);

		//遍历vector
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			if (*it == 1)
			{
				//由于erase后,迭代器会有失效的风险,
				//所以erase会返回 被删除元素 的 下一个元素 的迭代器,可以用it接收
				it = v.erase(it);
				//此时it已经指向下一个元素,不应该再++
			}
			else
				++it;
		}

		for (auto x : v)
			cout << x << " ";
		
	}

image.png

库里的erase的一种声明:

image.png

完整的erase模拟实现代码:

stl规定了erase会返回 被删除元素 的 下一个元素 的迭代器

		iterator erase(iterator pos)
		{
			assert(size() > 0);
			assert(pos >= _start);
			assert(pos < _finish);
			//把后面的数据往前挪动
			iterator pre = pos;
			iterator cur = pos + 1;
			while (cur != _finish)
			{
				*pre = *cur;
				++pre;
				++cur;
			}
			--_finish;
			return pos;
		}

(2) 要求在所有的2前面,插入一个9

	void test19()
	{
		//在所有的2前面插入9
		vector<int> v;
		v.push_back(2);
		v.push_back(2);
		v.push_back(4);
		v.push_back(2);
		v.push_back(7);
		// 2  3  4  2  7  
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			if (*it == 2)
			{
				//插入数据后同样需要更新it,否则it可能会是野指针
				//stl规定返回 插入的新数据位置的迭代器
				it = v.insert(it, 9);
				++it;
				//所以应该跳过刚才已经遍历过的数据
				++it;
			}
			else
				++it;
		}
		
		for (auto x : v)
			cout << x << " ";
	}

image.png

完整的insert模拟实现代码:

insert会返回新插入元素的迭代器位置

		//先计算扩容前pos与_start的相对位置,发生扩容后修改pos,返回新插入数据的迭代器位置
		iterator insert(iterator pos, const T& val)
		{
			//允许头插或尾插
			assert(pos >= _start && pos <= _finish);

			//先计算当前pos与_start的相对位置
			size_t relative_pos = pos - _start;

			//保证扩容后,pos指向的相对位置与之前的一致
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 5 : capacity() * 2);
				pos = _start + relative_pos;//一定要更新pos
			}
			//接下来正常拷贝

			//从后往前拷贝
			iterator cur = _finish;
			iterator pre = _finish - 1;
			while (cur != pos)
			{
				*cur = *pre;
				--pre;
				--cur;
			}
			//当cur == pos,说明所有数据挪好了

			//放置数据,更新_finish
			*pos = val;
			++_finish;

			//返回新插入数据的迭代器位置
			return pos;
		}

总结

vector中insert和erase的迭代器失效有两种:

第一种是 插入时发生扩容 或 删除时发生缩容 导致外面的pos成了野指针

解决方案:接收insert和erase返回的迭代器更新pos.

第二种是 pos接收insert和erase返回值后,pos已经不是指向原来的值,

insert返回新插入元素的迭代器位置,erase返回 被删除元素 的 下一个元素迭代器位置.

拷贝构造与赋值运算符重载

默认的拷贝构造和赋值运算符重载,会按字节进行浅拷贝.

与string一样:

(1) 两个对象内部指向同一块空间,一个对象修改影响另一个对象

(2) 同一块空间会被delete[]两次,程序崩溃

所以要手动实现深拷贝.

拷贝构造函数手动拷贝写法

注意内部不能浅拷贝T类型数据,因为vector存储的数据类型可能也需要深拷贝.

		//1 手动拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			//开空间,拷贝数据
			T* tmp = new T[v.capacity()];
			//也可以用v.size()

			//memcpy实现的浅拷贝,若出现vector<vector<int>> vv,
			//vector内部不能浅拷贝T类型,也必须是深拷贝
			//错误:memcpy(tmp, v._start, sizeof(T) * v.size());
			
			for (size_t i = 0; i < v.size(); i++)
			{
				tmp[i] = v[i]; //相当于内部调用T类型的赋值运算符重载完成深拷贝

				//注意(const vector<T>类型)v调用const类型的[]运算符重载,没有会报错
			}

			//更新成员变量
			_start = tmp;
			_finish = tmp + v.size();
			_end_of_storage = tmp + v.capacity();
		}

用已有的构造函数帮助拷贝构造

用已有的构造函数,构造出和v相同数据的临时对象,然后this的所有成员变量与临时对象的交换.

		void swap(vector<T>& tmp)
		{
			//交换所有成员变量
			std::swap(tmp._start, _start);
			std::swap(tmp._finish, _finish);
			std::swap(tmp._end_of_storage, _end_of_storage);
		}

		//2 用已有的构造函数帮助拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			//调用之前写的迭代器区间构造函数
			vector<T> tmp(v.begin(), v.end());
			
			//交换tmp和this的所有成员变量
			swap(tmp);
			//直接用库里的swap交换2个类对象,会调用vector<T>的1次拷贝构造,和vector<T>的2次赋值运算符重载
			//所以内部写一个swap用于交换两个类的成员变量即可
		}

赋值运算符重载,手动拷贝版本

注意要释放旧空间,以及内部不能浅拷贝T类型数据.

		//1 赋值运算符重载,手动拷贝
		vector<T>& operator=(const vector<T>& v)
		{
			//不是自我赋值才处理
			if (&v != this)
			{
				//开空间+拷贝数据
				T* tmp = new T[v.capacity()];

				//与拷贝构造相似,不能浅拷贝T类型
				//memcpy(tmp, v._start, v.size() * sizeof(T));
				for (size_t i = 0; i < v.size(); ++i)
				{
					tmp[i] = v[i];
				}

				//释放旧空间
				delete[] _start;

				//更新this的成员变量
				_start = tmp;
				_finish = tmp + v.size();
				_end_of_storage = tmp + v.capacity();
			}
			return *this;
		}

赋值运算符重载,调用拷贝构造函数帮助拷贝:

		//2 赋值运算符重载,调用拷贝构造实现
		vector<T>& operator=(vector<T> v)
		{
			//形参拷贝构造实参,此时v正是this需要的

			//this的成员变量与v的成员变量交换
			//让形参v在函数调用结束后帮忙清理旧空间
			swap(v);
			return *this;
		}
	void test20()
	{
		vector<int> v1(2, 3);
		for (auto x : v1)
			cout << x << " ";
		cout << endl;

		//拷贝构造
		vector<int> v2(v1);
		for (auto x : v2)
			cout << x << " ";
		cout << endl;

		vector<int> v3;
		//赋值运算符重载
		v3 = v1;
		for (auto x : v3)
			cout << x << " ";
	}

image.png

image.png