Bootstrap

C++ STL 容器系列 (二)vector —— 随机访问与高效存储的完美融合

STL系列学习参考:

C++ STL系列__zwy的博客-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/bite_zwy/category_12838593.html?spm=1001.2014.3001.5482学习C++ STL的三个境界,会用,明理,能扩展,STL中的所有容器都遵循这个规律,下面我们就按照这三个境界来学习vector。

一、标准库中的vector

vector - C++ Referenceicon-default.png?t=O83Ahttps://legacy.cplusplus.com/reference/vector/vector/?kw=vector

vector是STL中非常重要的容器之一,就像数组一样,vector的元素使用连续的存储位置,vector像数组一样支持使用下标访问元素,并且与数组一样高效。但与数组不同的是,它的大小可以动态变化,容器会自动处理它们的存储

容器的属性:序列,序列容器中的元素按严格的线性顺序排序。单个元素通过它们在这个序列中的位置来访问。动态数组允许直接访问序列中的任何元素,甚至通过指针算术,并在序列末尾提供相对快速的添加/删除元素。Allocator-aware,容器使用分配器对象动态地处理其存储需求。

二、vecotr的常用接口

1、Constructor 构造函数

<1>、无参构造,默认构造(重点)
//int类型
vector<int> v1;
//string类型 
vector<string> v2;
// char 类型
vector<char> v3;
//vector<int>类型 即整形二维数组
vector<vector<int>> v4;

其中vector<vector<int>>表示vector中存放的是每个元素vector<int>类型,即构成了一个整形的二维数组。

<2>、vector(size_type n, const value_type& val = value_type())

使用n个value构造

//5个1构造
vector<int> v1(5, 1);

//3个string "hello world"构造
vector<string> v2(3, "helloworld");

//10个double类型的默认值构造 即0.0
vector<double> v3(10, double());
<3>、vector (const vector& x) 拷贝构造(重点)
//5个10构造v1
vector<int> v1(5, 10);
//利用v1拷贝构造v2,但注意类型要相同
vector<int> v2(v1);

vector<string> v3(3,"helloworld");
//v3来拷贝构造v4
vector<string> v4(v3);

//拷贝构造时,实例化类型不同会编译错误
//vector<char> v5(10,'a');
//vector<string> v6(v5);

注意:拷贝构造时,拷贝对象和被拷贝对象的实例化类型必须一致,否则无法构造,编译出错!

<4>、vector (InputIterator first, InputIterator last)

使用迭代器区间构造

string s1("helloworld");
//使用s1的迭代器区间构造
vector<char> v1(s1.begin(), s1.end());

//利用数组的原生指针,也可以进行迭代器初始化
int a[] = { 1,2,3,4,5 };
vector<int> v(a, a + sizeof(a) / sizeof(int));

vector<int> v2(5, 3);
//使用v2迭代器区间构造
vector<int> v3(v2.begin() + 1, v2.end() - 1);

vector<string> v4(10,"hello");
//v3的迭代器区间构造
vector<string> v5(v4.begin() + 2, v4.end() - 4);

数组的原生指针,也可以充当迭代器进行初始化。

使用迭代器区间构造时,也需要保持类型一致,否则编译出错!

<5>、 initializer list
vector<int> v1{ 1,2,3,4,5 };
vector<string> v2{ "hello","world","vecotr","string" };
vector<char> v3{ 'a','b','c','d' };
vector<double> v4{ 1.1,2.2,3.14 };

支持我们使用,{ }来进行vector的初始化。

不止是vector,C++11以后 STL中的其他容器也支持 initializer list进行初始化,具体可以参考下面这篇博文。

深入探索 C++11 第一弹:现代 C++ 编程的基石与革新_c++11新特性-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/bite_zwy/article/details/143723070?spm=1001.2014.3001.5501

2、vector iterator迭代器的使用

<1>、begin()+end()(重点)
begin()获取第一个数据位置的iterator/const_iterator,end()获取最后一个数据的下一个位置的iterator/const_iterator。迭代器可以像指针一样解引用访问元素。也可以支持++和--操作。
正向迭代器遍历vector:
 
vector<int> v1{ 1,2,3,4,5 };
auto it = v1.begin();
while (it != v1.end())
{
	cout << *it << " ";
	++it;
}
cout << endl;

<2>、rbegin()和 rend()
rbegin()获取最后一个数据位置的reverse_iterator, rend()获取第一个数据前一个位置的reverse_iterator。
反向迭代器遍历vector:
vector<int> v1{ 1,2,3,4,5 };
auto it = v1.rbegin();
while (it != v1.rend())
{
	cout << *it << " ";
	++it;
}
cout << endl;

注意:反向迭代器遍历,也是++操作!

3、vector的空间增长问题

<1>、size()

获取有效数据的个数

vector<int> v{ 1,2,3,4,5 };
cout << "v的有效数据个数为:" << v.size() << endl;

//也可以用来进行for循环遍历vector
for (size_t i = 0; i < v.size(); ++i)
{
	cout << v[i] << " ";
}
cout << endl;

<2>、capacity()

获取当前vector的容量大小

vector<int> v{ 1,2,3,4 };
cout << "v当前的容量大小为:" << v.capacity() << endl;

<3>、empty()

判断vector是否为空,为空返回1,否则返回0

vector<int> v1;
cout << v1.empty() << endl;

vector<int> v2{ 1,2,3,4,5 };
cout << v2.empty() << endl;
<4>、reszie()

设置vector的size()

(1)、void resize (size_type n)

将vector的有效数据个数设置为n,并且将新的数据初始化为0

vector<int> v1;
cout << "v1的初始size():" << v1.size() << endl;

v1.resize(10);
cout << "v1的当前size()为:" << v1.size() << endl;

如果n比当前的vector的 size()小,那么会将vector的size()更改为n,多余的数据被删除。

vector<int> v1{ 1,2,3,4,5 };
cout << "v1的初始size()为:" << v1.size() << endl;

v1.resize(3);
cout << "v1的当前size()为:" << v1.size() << endl;

(2)、void resize (size_type n, const value_type& val)

将vector的size()更改为n,新的数据用val初始化

vector<int> v1{ 1,2,3,4,5 };
//将v1的size更改为7 新增的数据初始化为10
v1.resize(7, 10);

                                                                                                                                                                                                                                                                                                                                                             如果n比当前的vector的 size()小,那么会将vector的size()更改为n,同样多余的数据被删除。

vector<int> v1{ 1,2,3,4,5 };
//将v1的size更改为3  多余的数据删除
v1.resize(3, 10);

<5>、void reserve (size_type n)

将vector的容量capacity()更改为n,如果n大于当前vector的capacity(),则将vector的容量更改为n或者大于n。reserve不会改变vector的size()

vector<int> v1;
cout << "v1的初始capacity()为:" << v1.capacity() << endl;
cout << "v1的初始size()为:" << v1.size() << endl;

v1.reserve(5);
cout << "v1的初始capacity()为:" << v1.capacity() << endl;
cout << "v1的当前size()为:" << v1.size() << endl;

4、vector的访问和遍历

<1>、vector的访问
(1)、front()和back()

front()获取vector中的第一个元素,back()获取vector中最后一个元素

vector<string> v1{ "front","mid","vector","back" };
cout << "v1的第一个元素为:" << v1.front() << endl;
cout << "v1的最后一个元素为:" << v1.back() << endl;

(2)、at()

返回对vector中位置n的元素的引用。

该函数强制检查n是否在vector中有效元素的范围内,如果不在,则抛出out_of_range异常(即,如果n大于或等于其大小)。这与下面要讲的成员操作符 operator[]相反,它不检查边界。

正常的访问:

vector<int> v1{ 1,2,3,4,5 };
cout << "第1个元素为:"<<v1.at(0) << endl;
cout << "第2个元素为:" << v1.at(1) << endl;
cout << "第3个元素为:" << v1.at(2) << endl;
cout << "修改前第4个元素为:" << v1.at(3) << endl;

//由于at函数返回的是元素的引用,因此可以修改
v1.at(3) = 100;
cout << "修改后第4个元素为:" << v1.at(3) << endl;

异常访问:vs下和g++下都会抛出异常

//out_of_range异常的头文件
#include<stdexcept>
void Test()
{
	vector<int> v1{ 1,2,3,4,5 };
	//会抛出out_of_range异常,因为已经越界,超出vector的有效数据个数
	
	//捕获异常
	try
	{
		cout << v1.at(5) << endl;
	}
	catch (const out_of_range& e)
	{
		//打印异常信息
		cerr << "Caught out_of_range exception: " << e.what() << endl;
	}
}

vs下捕获的out_of_range异常:无效的vector下标,如果不捕获异常就会导致程序崩溃!

Linux下抛出的异常:

支持像数组下标一样访问vector,返回对vector容器中位置n的元素的引用。

vector<char> v1{ 'a','b','c','d','e'};
cout << "第1个元素为:" << v1[0] << endl;
cout << "第2个元素为:" << v1[1]	<< endl;
cout << "第3个元素为:" << v1[2] << endl;
cout << "修改前第4个元素为:" << v1[3] << endl;

//由于operator[]返回的也是元素的引用,因此可以修改
v1[3] = 'x';
cout << "修改后第4个元素为:" << v1[3] << endl;

operator[ ] 不会检查越界问题,但如果访问元素超出了有效范围,会导致一些未定义的行为,这与at函数不同,at会强制检查!

在vs下导致程序崩溃:

在Linux g++下则不会产生错误:编译成功后,程序的运行结果为0 

<2>、vector的遍历
(1)、for循环+operator[ ]
vector<char> v1{ 'a','b','c','d','e' };
for (size_t i = 0; i < v1.size(); ++i)
{
	cout << v1[i] << " ";
}
cout << endl;

(2)范围for
vector<char> v1{ 'a','b','c','d','e' };
for (auto& e : v1)
{
	cout<< e << " ";
}
cout << endl;

(3)、迭代器遍历

vector<char> v1{ 'a','b','c','d','e' };
auto it = v1.begin();
while (it != v1.end())
{
	cout << *it << " ";
	++it;
}
cout << endl;

5、vector的增删查改

<1>、push_back()

在vector的尾部插入新的元素,即尾插

vector<int> v1{ 1,2,3,4 };
cout << "尾插前:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;
v1.push_back(5);
v1.push_back(6);
cout << "尾插两次后:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;

<2>、pop_back()

删除vector的最后一个有效元素,即尾删。

vector<int> v1{ 1,2,3,4,5};
cout << "尾删前:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;
v1.pop_back();
v1.pop_back();
cout << "尾删两次后:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;

<3>、insert()

(1)、在vector中 迭代器 position位置 之前插入元素 val

vector<int> v1{ 1,2,3,4,5 };
cout << "insert前:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;	
//头插10
v1.insert(v1.begin(), 10);
//中间插入15
v1.insert(v1.begin()+3, 15);
//尾插20
v1.insert(v1.end(), 20);

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

(2)、在vector中 迭代器 position位置 之前插入n个 元素 val

vector<char> v1{ 'a','b','c','d'};
cout << "insert前:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;
//头插2个e
v1.insert(v1.begin(), 2,'e');
//中间插入3个f
v1.insert(v1.begin() + 3,3, 'f');
//尾插4个 h
v1.insert(v1.end(),4,'h');

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

(3)、在vector中 迭代器 position位置 之前插入一段迭代器区间

vector<char> v1{ 'a','b','c','d' };
cout << "insert前:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;
//插入一段迭代器区间
v1.insert(v1.begin(), v1.begin(), v1.end());
cout << "insert后:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;

<4>、erase()

删除迭代器 position位置的元素,并且返回被删除元素的下一个位置的迭代器!!!返回值是重点,有关处理迭代器失效问题!!

vector<string> v1{"vector", "list", "string", "map", "set"};
cout << "erase之前:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;
//删除第一个元素 头删
v1.erase(v1.begin());
//删除中间的元素
v1.erase(v1.begin() + 2);
//删除最后一个元素 尾删
v1.erase(v1.end()-1);
cout << "erase之后:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;

删除一段迭代器区间:[first,last)  左闭右开

vector<string> v1{ "vector", "list", "string", "map", "set" };
cout << "erase之前:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;
v1.erase(v1.begin(), v1.begin() + 2);
cout << "erase之后:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;

 <5>、find()

注意:find不是vector的成员函数,而是STL算法部分给出的函数!

在一段迭代器区间内,查找值为val的元素,找到的话,返回第一个值val的迭代器,没有的话返回查找区间最后位置的迭代器。

vector<int> v1{ 1,2,1,3,4,5 };
//返回第一个1的迭代器
auto it = find(v1.begin(), v1.end(), 1);
while (it != v1.end())
{
	cout << *it << " ";
	++it;
}
cout << endl;

//返回end()位置的迭代器
auto it1 = find(v1.begin(), v1.end(), 10);
if (it1 != v1.end())
	cout << *it1 << endl;
else
	cout << "查找失败" << endl;

<6>、swap()

交换两个数组的底层空间

vector<int> v1{ 1,2,3,4 };
vector<int> v2{ 5,6,7,8 };
v1.swap(v2);
cout << "交换后v1的数据:" << " ";
for (auto& e : v1)
{
	cout << e << " ";
}
cout << endl;
cout << "交换后v2的数据:" << " ";
for (auto& e : v2)
{
	cout << e << " ";
}
cout << endl;

<7>、emplace_back()

emplace_back涉及到C++11右值引用的移动语义问题,在C++11讲解中对emplace_back()的使用及原理做了详细讲解,大家请移步至下面这篇博文,这里就不赘述了!

深入探索C++11 第三弹:C++11完结,迈进高效编程的新纪元-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/bite_zwy/article/details/143832840?spm=1001.2014.3001.5501

三、不同编译器下vector的扩容问题

很多同学会默认vector的扩容都是二倍扩容,其实在不同的编译器下,vector的扩容机制有所不同。

1、vs下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下使用的STL基本是按照1.5倍方式扩容。

2、g++下vector的扩容机制

同样的代码,在g++下编译运行后可以看到,g++下vector是按2倍方式扩容!

3、提高效率
如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够就可以避免边插入边扩容导致效率低下的问题了。

void TestVectorExpandOP()
{
	vector<int> v;
	size_t sz = v.capacity();
	v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "making bar 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';
		}
	}
}

四、vector的迭代器失效问题(重点)


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

1、 扩容等操作导致迭代器失效

比如:resizereserveinsertassign、push_back等。

void Test()
{
	vector<int> v{ 1,2,3,4,5,6 };
	auto it = v.begin();
	// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
	// v.resize(100, 8);
	// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
	// v.reserve(100);
	// 插入元素期间,可能会引起扩容,而导致原空间被释放
	// v.insert(v.begin(), 0);
	// v.push_back(8);
	// 给vector重新赋值,可能会引起底层容量改变
	//v.assign(100, 8);
	/*
	出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释
	放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块
	已经被释放的空间,而引起代码运行时崩溃。
	解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给
	it重新赋值即可。
	*/
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

2、vs下erase导致的迭代器失效问题

int a[] = { 1, 2, 3, 4 };
vector<int> v(a, a + sizeof(a) / sizeof(int));
// 使用find查找3所在位置的iterator
auto pos = find(v.begin(), v.end(), 3);
// 删除pos位置的数据,导致pos迭代器失效。
v.erase(pos);
cout << *pos << endl; // 此处会导致非法访问,程序崩溃

此时pos位置的迭代器中数据已被erase,空间被释放,继续访问则会导致访问崩溃!

erase删除pos位置元素后,pos位置之后的元素会往前移,没有导致底层空间的改变,理
论上讲迭代器应该不会失效,但是如果pos刚好是最后一个元素,删完之后pos刚好是end
的位置,而end位置是没有元素的,那么pos就失效了。
为了避免这种情况,vs规定删除vector中任意位置上元素时,vs就认为该位置迭代器失效了!!!重点记住!
以下vs下代码的功能是删除vector中所有的偶数,请问那个代码是正确的,为什么?大家思考一下!
void Test1()
{
	
		vector<int> v{ 1, 2, 3, 4 };
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
				v.erase(it);
			++it;
		}
}

void Test2()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			it = v.erase(it);
		else
			++it;
	}
}

Test1中,删除元素后直接执行++it。由于it已经失效,这会导致未定义行为。具体来说,erase函数会返回一个新的迭代器,指向被删除元素之后的元素,但Test1没有将这个返回值重新赋值给it。Test2正确地处理了erase操作对迭代器的影响,通过将it更新为erase函数的返回值,确保了迭代器的有效性,从而能够正确地遍历和删除vector中的偶数元素。

3、g++下迭代器失效问题

Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理方法也没有vs下极端。

<1>、扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了.

// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	auto it = v.begin();
	cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
	// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
	v.reserve(100);
	cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
	// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
	// 虽然可能运行,但是输出的结果是不对的
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
	cout << endl;
	return 0;
}

g++下程序可以运行,但是运行结果是错误的!

<2>erase删除任意位置代码后,与vs下不同,Linux g++下迭代器并没有失效 ,因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的。

int main()
{
	vector<int> v{ 1,2,3,4,5 };
	auto it = find(v.begin(), v.end(), 3);
	v.erase(it);
	cout << *it << endl;
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

g++下程序可以正常运行,结果也是正确的!

<3>、erase删除的迭代器如果是最后一个元素,删除之后it已经超过end 此时迭代器是无效的,++it导致程序崩溃
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	// vector<int> v{1,2,3,4,5,6};
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
	for (auto e : v)
		cout << e << " ";
	cout << endl;
	return 0;
}
=========================================================================
使用第一组数据 v {1,2,3,4,5}时,程序可以正确运行:删除了所有偶数
=========================================================================
使用第二组数据时 v {1,2,3,4,5,6} 哇v咩咩moj时程序最终会崩溃,因为最后删除最后一个偶数6之后,end()往前移动到了it的位置,此时 ++it后,超出了end(),迭代器失效导致错误!
上述例子可以看出,迭代器失效后,程序并不一定会崩溃,但是运行结果肯定不对,如果it不在beginend范围内,程序肯定会崩溃的,具体还是取决于编译器的标准!
迭代器失效的解决方法:在使用前,对迭代器重新赋值即可
可以将erase 和++it操作分开,erase的返回值赋值给it,如果是偶数erase删除成功后返回被删除元素下一个位置的迭代器,如果不是偶数就直接 ++it,只要迭代器不超出end()迭代器就不会失效!
void Test()
{
	 vector<int> v{1,2,3,4,5,6};
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			it = v.erase(it);
		else
			++it;
	}
	for (auto e : v)
		cout << e << " ";
	cout << endl;
}

vs下可以正常运行:

g++下也可以正常运行:

五、vector深度剖析及模拟实现

注意我们这里使用数组来模拟实现vector,start是起始位置的指针,指向数组开始的第一个元素,finish是结束位置的指针,指向最后一个有效元素后面的位置,end_of_storage则是代表容量大小的指针,指向已开辟空间的下一个位置。

finish-start则表示当前vector的size(),end_of_storage-start表示当前vector的capacity()。begin()指向start,end()指向finish。
使用std::vector 的核心框架接口的模拟实现zwy ::vector.
namespace zwy {
	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;
			}


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


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


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

			size_t capacity()
			{
				return _end_of_storage - _start;
			}


			Vector()
			{

			}

			Vector(size_t n,  const T& val = T())
			{
				reserve(n);
				for (size_t i = 0; i < n; i++)
				{
					push_back(val);
				}
			}
			Vector(int n, const T& val = T())
			{
				reserve(n);
				for (size_t i = 0; i < n; i++)
				{
					push_back(val);
				}
			}

			~Vector()
			{
				if (_start)
				{
					delete[] _start;
					_start = _finish = _end_of_storage = nullptr;
				}
			}

			//拷贝构造
			Vector(const Vector<T>&  v)
			{
				reserve(v.size());
				for (auto& e : v)
				{
					push_back(e);
				}
			}

			//迭代器区间构造
			template <class InputIterator>
			Vector(InputIterator first, InputIterator last)
			{
				while (first != last)
				{
					push_back(*first);
					++first;
				}
			}


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

		void resize(size_t n, T val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					push_back(val);
					/**_finish=val;
					++_finish;*/
				}
			}
		}

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


			void push_back(const T& x)
			{
				if (size()==capacity())

				{
					reserve(capacity() == 0 ? 4 : capacity() * 2);
				}
				*_finish = x;
				++_finish;
			}

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

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


			void  erase(iterator pos)

			{
				assert(pos >= _start);
				assert(pos <_finish);
				iterator it = pos +1;
				while (it!=end())	
				{
					*(it - 1) = *it;
					++it;
				}
				--_finish;

			}

			bool Empty() const

			{
				return (_finish - _start == 0);
			}

			void clear()
			{
				_start = _finish;
			}

			现代写法
			//void Swap(const T& v)
			//{
			//	std::swap(_start, v._start);
			//	std::swap(_finish, v._finish);
			//	std::swap(_end_of_storage, v._end_of_storage);
			//}
		 //   Vector<T>& operator=(Vector<T> v)
			//{
			//	T tmp;
			//	Swap(tmp);
			//	return *this;
			//}

			Vector<T>&  operator=(const Vector<T>& v)
			{
				if (this != &v)
				{
					clear();
					reserve(v.size());
					for (auto& e:v)
					{
						push_back(e);
					}
				}

				return *this;
			}



			
	private:
		iterator _start=nullptr;
		iterator _finish=nullptr;
		iterator _end_of_storage=nullptr;

	};


	
	template<class Container>
	void Print_Vector (const Container& v)
	{
		// 规定,没有实例化的类模板里面取东西,编译器不能区分这里const_iterator
		// 是类型还是静态成员变量
		//typename  Vector<T>::const_iterator it = v.begin();
		auto it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

	/*	for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;*/
	}

}

六、使用memcpy拷贝问题

假设模拟实现的 vector 中的 reserve 接口中,有使用 memcpy 进行的拷贝,以下代码会发生什么问
题?
void Test()
{
	zwy::vector<bite::string> v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
}
注意:memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存
空间中, 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
 
结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为
memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。需要使用深拷贝来解决!  

七、动态二维数组的理解

以杨辉三角为例:

// 以杨慧三角的前n行为例:假设n为5
void test2vector(size_t n)
{
	// 使用vector定义二维数组vv,vv中的每个元素都是vector<int>
	zwy::vector<bit::vector<int>> vv(n);
	// 将二维数组每一行中的vecotr<int>中的元素全部设置为1
	for (size_t i = 0; i < n; ++i)
		vv[i].resize(i + 1, 1);
	// 给杨慧三角出第一列和对角线的所有元素赋值
	for (int i = 2; i < n; ++i)
	{
		for (int j = 1; j < i; ++j)
		{
			vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
		}
	}
}

八、小结

vector也是STL中很基础同时也很重要的容器,里面涉及深浅拷贝,扩容机制,迭代器失效等多种问题,这些问题需要在使用时注意,避免出现错误,同时也要知道这些问题产生的原因,以及如何正确处理。

接下来会给大家带来C++ STL中其他容器的深度讲解,创作不易,还请多多支持。

如果觉得对你有帮助,关注博主,为你带来更多优质内容 ! 在你成为一名合格的C++高手道路上祝你一臂之力!

如上讲解如有不足,还望各位大佬评论区斧正!!

;