《STL vector实现原理深度解析:从内存管理到迭代器失效(模拟实现篇)》

前言:在C++标准模板库STL中,vector是最常用的容器之一。它以动态数组的形式提供联系内存存储,支持随机访问和高效的尾部插入\删除操作。然而,其底层实现远非简单的数组封装,而是通过精妙的内存管理策略和数据结构设计,平衡了性能与灵活性。本文将深入探讨vector的底层实现原理,包括其核心数据结构、动态扩容机制、迭代器设计,以及实际应用中的注意事项

目录

vector的存储结构的设计

二倍扩容实现

框架搭建

初始化

析构函数

尾插数据

扩容

迭代器

删除指定位置元素

插入元素在指定位置之前

拷贝构造

vector核心问题探究

迭代器失效问题

1)什么是迭代器失效

2)迭代器失效的表现

3)vector容器中哪些操作会导致迭代器失效?

memcpy的浅拷贝问题

二维vector的存储问题

1)二维数组的访问机制

2)二维vector和原生二维数组的区别?

总代码

头文件:vector.h

源文件:Test.cpp


vector的存储结构的设计

上图出自侯捷老师的《STL源码剖析》

一、核心结构:三指针(迭代器)管理

vector 内部靠 start、finish、end_of_storage 三个关键标记,划分内存区域:

start:指向数据区起始位置,是首个元素的 “入口”,类似数组名(但更灵活,支持动态调整)

finish:指向当前最后一个元素的下一个位置,finish - start 就是 size()(实际存储元素数量)

end_of_storage:指向已分配内存的末尾位置,end_of_storage - start 对应 capacity()(总可用容量,含未用 “备用空间”)

总结:这三指针,让 vector 清晰区分 “已存数据” 和 “备用空间”,为动态扩容、高效访问铺路。

接下来我们尝试用二倍扩容来模拟实现vector的功能,既然vector是通过模板来实现的,那么我们就可以在自定义空间中用模板实现类,来搭建我们的模板框架。

二倍扩容实现

框架搭建

类模板的实例化:空间声明+模板类类型

cyh::vector <string> S;
namespace cyh
{
	template<class T>
	class vector
	{
	public:
		
	private:
		T* _start;
		T* _finish;
		T* _end_of_storage;

初始化

这里只需要把三个指针都初始化为空就行

//构造初始化
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{ }

析构函数

//析构
~vector()
{
	//判断
	assert(_start);
	//释放空间
	delete[]_start;
	//置空
	_start = _finish = _end_of_storage = nullptr;
}

注意:析构不能释放空,因此需要先判断指针是否开辟了空间,然后再置空。

尾插数据

在尾插时我们可能需要更改三个指针的位置,因此需要先计算一下:size()、capacity

原理:指针-指针=中间的元素个数

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

在尾插时需要考虑:如果空间已满 或者 _start为空。如果_start为空,那么无法使用memcpy!

//尾插
void push_back(const T pc)
{
	if (_finish == _end_of_storage)
	{
		//说明此时空间已满 或者 空间为空
		size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
        //如果_start为空,那么无法使用memcpy
		if (_start == nullptr)
		{
			_start = new T[newcapacity];
			_finish = _start;
			_end_of_storage = _start + newcapacity;
		}
		else
		//开空间
		reserve(newcapacity);
	}
	//存
	*_finish = pc;
	_finish++;
}

扩容

扩容就是 reserve ,传入指定数量的空间,reserve负责开辟,

然后更新三个指针指向新空间。

这里还有reserve+resize的组合,

如果resize小于_finish,那么就保留resize及其以前的数据

如果resize大于_finish,那么就需要扩容+初始化(如果没有给初始值,调用构造函数)

//扩容+初始化
void resize(size_t n, const T& val=T())
{
	if (n < size())
	{
		//直接删
		while (size() > n)
		{
			_finish--;
		}
	}
	else
	{
		//扩容
		reserve(n);
		while (n != size())
		{
			*_finish = T();
			_finish++;
		}
	}
}

迭代器

迭代器应该是返回有效元素的这个区间的指针指向,而不是放大到容量

这里数据的开始刚好是_start

末尾的下一个位置刚好是_finish \ _start+元素个数

//迭代器
iterator begin()const
{
	return _start;
}
iterator end()const
{
	return _start + size();
}

删除指定位置元素

//删除指定位置元素
void erase(iterator pos)
{
	//判断有效性
	assert(pos >= _start && pos < _finish);
	//挪动元素
	while (pos + 1 < _finish)
	{
		*pos = *(pos + 1);
		pos++;
	}
	
	_finish--;
}

插入元素在指定位置之前

//插入元素在指定位置之前
void insert(iterator pos, T tmp)
{
	//检查范围
	assert(pos > _start && pos <= _finish);
	//看是否需要扩容
	if (_finish == _end_of_storage)
	{
		size_t newcapacity = capacity() * 2;
		reserve(newcapacity);
	}
	//挪动元素
	iterator it = _finish;
	while (it >= pos)
	{
		*it = *(it - 1);
		it--;
	}
	*(pos - 1) = tmp;
	_finish++;
}

拷贝构造

拷贝构造本质是用一个已经存在的对象去创建+初始化另一个对象 

注意:不能使用memcpy,因为memcpy是按字节拷贝,

如果是自定义类型会发生浅拷贝情况

虽然这里的 T 是int类型,但是为了泛化使用,避免自定义类型发生浅拷贝

对于连续的地址,可以使用下标[ ] 或者 直接解引用访问内容

vector核心问题探究

迭代器失效问题

1)什么是迭代器失效

迭代器失效:是指在 C++ 中,当容器的结构发生某些变化时,原本有效的迭代器变得不再有效,无法再正确地指向容器中的元素 。

迭代器失效的原理

迭代器本质上是一种 抽象的指针————指向容器内部元素的指针或指针封装,用于指向容器中的元素,方便对容器元素进行遍历和操作

容器内部通过维护一定的数据结构来存储元素,当容器执行某些操作(如:插入、删除元素,改变容量等)时,其内部数据结构可能发生改变。

例如:内存重新分配、元素位置调整等。


这就会导致原本迭代器所指向的内存地址、元素顺序等情况发生变化,使得迭代器无法再准确指向预期元素,即:迭代器失效

总结:

内存重新分配:容器扩容时,元素被移动到新内存,旧内存被释放
元素位置移动:插入/删除操作导致元素位置整体前移或后移

2)迭代器失效的表现

访问错误:当使用失效的迭代器去访问容器元素时,程序可能会出现未定义行为。

比如:崩溃、获取到错误的数据值等 。

迭代错误:在迭代过程中,如果迭代器失效,会导致迭代逻辑出错。

比如:在循环遍历容器时,迭代器失效后继续进行迭代操作,可能无法正确遍历完容器或者跳过某些元素等

#include <vector>
#include <iostream>
            
int main() 
{
    std::vector<int> v = {1, 2, 3};
    auto it = v.begin();
                
    v.push_back(4); 			   // 可能导致扩容,it失效
    std::cout << *it << std::endl; // 使用失效迭代器,行为未定义
                
    return 0;
}

3)vector容器中哪些操作会导致迭代器失效?
 

一、容量改变操作

1. 扩容操作

push_back():当size达到capacity时
insert():插入元素导致扩容时
emplace_back():同push_back
resize():当新size大于当前capacity时
reserve():当新capacity大于当前capacity时
assign():几乎总会重新分配内存
2. 缩容操作

shrink_to_fit():可能重新分配更小的内存空间


二、元素修改操作

1. 插入操作

insert():在任意位置插入元素
插入点之后的所有迭代器都会失效
如果触发扩容,则所有迭代器失效

2. 删除操作

erase():删除任意位置元素
被删除元素之后的所有迭代器都会失效

pop_back():删除尾部元素
只使指向最后一个元素的迭代器失效
clear():清空所有元素
使所有迭代器失效

memcpy的浅拷贝问题

刚才我们已经简单说了一下,不能使用vector,那样会造成浅拷贝。

int main()
{
	mySpace::vector v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
	return 0;
}

我们再来总结一下:

memcpy是内存的二进制格式拷贝,它会将一段内存空间中的内容原封不动地拷贝到另一段内存空间中

当拷贝的是普通自定义类型元素时,memcpy既高效又不会出错。
但如果
自定义类型元素涉及资源管理时,使用memcpy就会出错,因为memcpy本质上是 浅拷贝

二维vector的存储问题

下面的动图展示了经典的杨辉三角,我们可以直观地看到这个三角的规模在不断变化。

因此,如果要存储杨辉三角,我们就要使用二维vector,但是二维vector又是怎么存储的呢?

// 以杨慧三角的前n行为例:假设n为5
void test2vector(size_t n)
{
 // 使用vector定义二维数组vv,vv中的每个元素都是vector<int>
cyh::vector<cyh::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];
 }
 }
}

这里cyh::vector<bit::vector<int>> vv(n); 构造一个vv动态二维数组,vv中总共有n个元素,每个元素都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:

vv中元素填充完成之后,如下图所示:

这里我们就发现使用标准库中vector构建动态二维数组时与上图实际是一致的。

1)二维数组的访问机制

//这是一个简化的vector模板类示意,非实际代码中的实现,只是为了说明二维vector的实现原理
template<class T>
class vector
{
	T& operator[](int pos)   //下标运算符,返回元素引用
	{
		assert(pos < _size); //断言下标有效
		return _a[pos];      //返回数组中对应位置的元素
	}
private:
	T* _a;			  //数据指针
	size_t _size;     //有效元素个数
	size_t _capacity; //总容量
};

/*---------------当T为int时,vector<int>的operator[]返回int&---------------*/
//当T为int时 -----> vector<int>的operator[]返回“int&”
class vector
{
	int& operator[](int i)
	{
		assert(i < _size);
		return _a[i];
	}
private:
	int* _a;
	size_t _size;
	size_t _capacity;
};

/*---------------当T为vector<int>时,vector<vector<int>>的operator[]返回vector<int>&---------------*/
//当T为vector<int>时 -----> vector<vector<int>>的operator[]返回“vector<int>&”
class vector
{
	vector<int>& operator[](int i) // 返回内层vector的引用
	{
		assert(i < _size);
		return _a[i];
	}
private:
	vector<int>* _a; // 指针数组,每个元素是vector<int>
	size_t _size;
	size_t _capacity;
};

通过上面的这段代码,我们可以深入理解 C++ 中 模板类的实例化过程,以及 二维 vector 的内存结构和访问机制


模板类的实例化机制:

模板类 vector<T> 在编译时会根据实际使用的类型参数(如:int 或 vector<int>)生成对应的具体类。

vector<int>:T 被替换为 int,_a 成为 int*,operator[] 返回 int&
vector<vector<int>>:T 被替换为 vector<int>,_a 成为 vector<int>*,operator[] 返回 vector<int>&

这种实例化过程是 静态的(编译期完成),不同类型参数生成的类是独立的。


二维 vector 的内存结构:

二维 vector:vector<vector<int>> 本质是一个 动态数组的动态数组

外层:vector 管理一个 vector<int>* 类型的指针数组 _a,每个元素是一个指向内层 vector<int> 的指针。
内层:vector<int> 各自管理一个 int* 类型的指针数组,存储实际数据。

2)二维vector和原生二维数组的区别?

总代码

头文件:vector.h

#pragma once

/*==========================================任务1:包含需要使用的头文件==========================================*/
#include <iostream>
#include <assert.h>
#include <vector>
#include <list>
using namespace std;

/*==========================================任务2:定义mySpace自定义命名空间==========================================*/

//任务2:定义mySpace自定义命名空间
//		 任务2.1:实现类模板:vector
//	     任务2.2:实现函数模板:print_vector
//		 任务2.3:实现函数模板:print_container
namespace mySpace
{
	/*==========================================任务2.1:实现类模板vector==========================================*/
	template <class T>
	class vector
	{
	public:
		/*-------------------------------第一部分:类型定义-------------------------------*/
		//1.定义“正向迭代器”(原始指针,支持随机访问)
		typedef T* iterator;
		//2.定义“常量的正向迭代器”(指向常量的指针,禁止通过迭代器修改元素)
		typedef const T* const_iterator;

		/*-------------------------------第二部分:迭代器接口-------------------------------*/
		//1.实现:获取正向迭代器的首部
		iterator begin()
		{
			return _start;
		}

		//2.实现:获取正向迭代器的尾部
		iterator end()
		{
			return _finish;
		}

		//3.实现:获取常量正向迭代器的首部
		const_iterator begin()const       //注意:在C++11中使用的是cbegin()进行返回:常量正向迭代器,我们这里还是直接使用begin来返回常量正向迭代器
		{
			return _start;
		}

		//4.实现:获取常量迭代器的尾部
		const_iterator end()const
		{
			return _finish;
		}


		/*-------------------------------第三部分:构造and析构默认函数-------------------------------*/
		//1.实现:C++11默认构造函数
		vector() = default;  //自动生成无参构造,初始化指针为nullptr


		//2.实现:拷贝构造函数
		vector(const vector<T>& v)  //深拷贝另一个vector的内容
		{			//注意:vector<T>是模板实例化后的具体“类类型”
			//1.先预留目标容量,避免多次扩容
			reserve(v.size());

			//2.再遍历原vector容器,逐个将容器中的元素插入
			for (auto& it : v)
			{
				push_back(it);  //调用push_back实现元素拷贝(支持自定义类型拷贝)
			}
		}

		
		//3.实现:迭代器范围构造函数
		//template <class InputIterator>   //通过任意输入迭代器范围初始化vector
		//vector(InputIterator first, InputIterator last)  //注意:类模板的成员函数,还可以继续是函数模版
		//{
		//	//遍历迭代器的范围逐个插入元素
		//	while (first != last)
		//	{
		//		//1.插入
		//		push_back(*first);


		//		//2.移动
		//		++first;
		//	}
		//}
		

		//4.实现:填充构造函数(size_t版本)
		vector(size_t n, const T& val = T())  //创建n个值为val的元素
		{
			//1.先预留目标容量,避免多次扩容
			reserve(n);

			//2.再使用for循环,连续插入n个val
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		//5.实现:填充构造函数(int版本)
		//vector(int n, const T& val = T())  //创建n个值为val的元素
		//{
		//	//1.先预留目标容量,避免多次扩容
		//	reserve(n);

		//	//2.再使用for循环,连续插入n个val
		//	for (int i = 0; i < n; i++)
		//	{
		//		push_back(val);
		//	}
		//}

		/*
		//6.1.实现:“拷贝赋值运算符重载函数”(传统写法:引用传递+赋值)
		vector<T>& operator=(const vector<T>& v)  //注意:这里的vector的类类型是vecotor<T>,而并不是vector
		{									//这是因为:vector不是普通的类,而是类模板
			//1.先判断是否对自身进行赋值操作
			if (this != &v)
			{
				//情况1:传入的容器的容量比较“小”
				//2.将当前的容器的中的内容清空 ---> 应对“小”容量的情况
				clear();

				//情况2:传入的容器的容量比较“大”
				//3.使用reserve开辟指定容量的空间 --->  应对“大”容量的情况
				reserve(v.size());

				//4.处理了上面的情况之后我们才可以进行赋值
				for (auto& it : v)
				{
					push_back(it);
				}
			}
			return *this;
		}
		*/

		//6.2.实现:拷贝赋值运算符重载函数(现代写法:值传递+交换)
		//vector<T>& operator=(vector<T> v) //注意:参数为值传递,自动触发拷贝构造
		//{
		//	//1.交换当前对象与临时对象v的数据
		//	swap(v);

		//	//2.返回当前对象(临时对象v离开作用域后自动析构)
		//	return *this;
		//}

		vector<T>& operator=(const vector<T>& v) 
		{
			//1.先判断是否是对自身进行的赋值操作
			if (this != &v) 
			{
				//2.再使用拷贝构造出临时对象
				vector<T> temp(v);  

				//3.然后进行交换
				swap(temp);        
			}
			//4.最后返回自身对象即可
			return *this;
		}

		//7.实现:析构函数
		~vector()
		{
			//1.确保指针非空
			if (_start)
			{
				//2.释放_start指针指向的资源
				delete[] _start;

				//2.将三指针置为空指针
				_start = _finish = _end_of_storage = nullptr;
			}
		}


		/*-------------------------------第四部分:容量管理操作-------------------------------*/
		//1.实现:“获取有效元素的个数的函数”
		size_t size() const
		{
			return _finish - _start;
		}

		//2.实现:“获取容器的总的容量的函数”
		size_t capacity()const
		{
			return _end_of_storage - _start;
		}

		//3.实现:“判断容器是否为空的函数”
		bool empty() const
		{
			return _start == _finish; //起始指针与结束指针重合
		}


		//4.实现:“预留容量的函数”
		void reserve(size_t n)  //预留至少n个元素的容量(不改变有效元素个数)
		{
			//注意:预留容量的操作只有在预留的容量n 大于 原vector容器的容量时候才会进行操作
			if (n > capacity())
			{
				/*---------第一步:保存原有元素的个数---------*/
				size_t old_size = size();


				/*---------第二步:分配新内存---------*/
				T* tmp = new T[n];


				/*---------第三步:拷贝旧数据---------*/
				//注意:使用for循环支持自定义类型的拷贝构造
				for (size_t i = 0; i < size(); i++)
				{
					tmp[i] = _start[i];  //调用T的拷贝赋值运算符
				}

				/*---------第四步:释放旧内存---------*/
				delete[] _start;


				/*---------第五步:更新指针成员---------*/

				//方法一:使用一个变量来记录之前vector中的元素的数量(三个迭代器的更新顺序没有要求)
				_start = tmp;

				//_finish = _start + size();    //失败
				//_finish = _start + old_size;  //成功

				//_finish = tmp + size();	    //失败
				_finish = tmp + old_size;       //成功

				_end_of_storage = tmp + n;

				/* 方法二:也可以不记录原vector中元素的数量来避免迭代器失效的问题,但是必须确保:_finish 的更新一定要在 _start 之前
				_finish = tmp + size();

				_start = tmp;

				_end_of_storage = tmp + n; 
				*/
				

			}
		}


		//5.实现:“调整有效元素的数量的函数”
		void resize(size_t n, T val = T())  //调整有效元素个数为n,不足时用val填充,多余时截断
		{
			//情景1:截断场景
			if (n < size())
			{
				//1.1:直接缩短有效长度为n
				_finish = _start + n;
			}

			//情景2:扩容场景
			else
			{
				//2.1:直接开辟最大容量为n
				reserve(n);

				//2.2:填充新元素
				while (_finish < _start + n)
				{
					//1.赋值
					*_finish = val;  //可能调用拷贝赋值

					//2.移动
					++_finish;
				}
			}

			//注意:这里的resize和reserve的区别:
					//reserve:对于每种调用情况并不是都一定会执行 (只有指定的扩容容量n大于当前vector容量是才会进行扩容)
					//resize:对于每种调用情况都会执行
		}

		/*-------------------------------第五部分:元素访问操作-------------------------------*/
		//1.实现:“下标访问运算符重载函数”(可读可写)
		T& operator[](size_t pos)
		{
			//1.使用断言:检查下标是否越界
			assert(pos < size());

			//2.通过指针偏移访问
			return *(_start + pos);

			//return _start[pos];
		}

		//2.实现:“下标访问运算符重载函数”(只读)
		const T& operator[](size_t pos)const
		{
			//1.使用断言:检查下标是否越界
			assert(pos < size());

			//2.通过指针偏移访问
			return *(_start + pos);

			//return _start[pos];
		}



		/*-------------------------------第六部分:元素修改操作-------------------------------*/
		//1.实现:“清空vector容器的函数”
		void clear()   //逻辑上清空,容量不变
		{
			_start = _finish;
		}

		//2.实现:“交换vector容器元素的函数”
		void swap(vector<T>& v) //对于自定义对象实现高效赋值
		{
			//使用标准库中的swap交换vector的三个私有的指针成员
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}


		//3.实现:“尾插元素到vector容器的函数”
		void push_back(const T& x)
		{
			//1.检查vector是否需要进行扩容
			if (_finish == _end_of_storage)
			{
				//扩容策略:
				//	1.vector容器的容量为0时:为vector容器分配4字节的容量
				//	2.vector容器的容量不为0时:为vector容器分匹配原容量的2倍容量

				//1.1:进行扩容
				reserve(capacity() == 0 ? 4 : 2 * capacity());

				//注意:push_back这里的扩容的大小是使用一个三目运算计算出来的
				//而并不像我们在operator=和resize中的直接使用reserve(v.size())或reserve(n)就可以了
			}

			//2.将新元素直接插入到vector容器的尾部
			*_finish = x;  //直接赋值,要求T支持拷贝赋值

			//3.有效指针后移
			++_finish;
		}


		//4.实现:“尾删vector容器中元素的函数”
		void pop_back()
		{
			//1.使用断言:确保vector容器非空容器
			assert(!empty());

			//2.有效指针前移
			--_finish;
		}

		//5.实现:“在pos位置插入元素的函数”(返回新元素的迭代器)
		iterator insert(iterator pos, const T& x)
		{//注意:这里我们可以对比我们在实现string类中的insert函数时候传入的pos的类型,一个是size_t,一个是iterator
			//1.使用断言:确保迭代器的有效性
			assert(pos >= _start && pos <= _finish);  //注意:取等号的时候类似于“头插”“尾插”

			//2.记录插入位置相对于起始的偏移量
			size_t len = pos - _start;

			//3.检查vector是否需要进行扩容
			if (_finish == _end_of_storage)
			{
				//扩容策略:
					//	1.vector容器的容量为0时:为vector容器分配4字节的容量
					//	2.vector容器的容量不为0时:为vector容器分匹配原容量的2倍容量

				//3.1:进行扩容
				reserve(capacity() == 0 ? 4 : 2 * capacity());

				//3.2:更新pos指针
				pos = _start + len;
			}


			//4.从末尾到pos位置的元素都向后挪动一位

			//4.1:定义一个迭代器指向我们需要挪动的元素中的最后一元素
			iterator end = _finish - 1;  //注意:回想一下我们string中实现的insert函数中的定义的end的类型是size_t,但是这里使用是迭代器
			while (end >= pos)
			{
				//4.2:赋值后移
				//_start[end + 1] = _start[end];  注意:这里不要使用[],因为我们重载[]运算符时候,使用了断言:assert(pos < size());
				*(end+1)=*end;					  //这就意味着我们使用[]时候,必须是[0~size-1]

				//4.3:end迭代器前移
				--end;
			}

			//5.插入元素
			//_start[pos] = x;  //这里也是和上面一样的情况

			*(pos)=x;

			//6.有效元素个数增加
			++_finish;

			//7.返回新元素的迭代器
			return pos;
		}


		//6.实现:“删除pos位置元素的函数”
		void erase(iterator pos)
		{
			//1.使用断言:确保迭代器的有效性
			assert(pos >= _start && pos < _finish);//注意:这里pos迭代器不能等于_finish,
			//这是因为_finish指向的其实是vecotr中最后一元素的下一个位置,所以我们不能删除_finish指向的值

			//2.从pos+1到末尾的元素都向前挪动一位

			//2.1:定义一个迭代器指向我们需要挪动的元素中的第一个元素
			iterator begin = pos + 1;
			while (begin != end())
			{
				//2.2:赋值交换
				_start[begin - 1] = _start[begin];
				//*(begin-1)=*(begin);

				//2.3:begin迭代器后移
				++begin;
			}

			//3.有效元素个数减少
			--_finish;
		}


	private:
		/*-----------------------私有成员:三指针结构-----------------------*/
		iterator _start;			//指向数据起始位置
		iterator _finish;			//指向有效数据的下一个位置
		iterator _end_of_storage;	//指向容量末尾的下一个位置
	};



	/*==========================================任务2.2:实现函数模板:print_vector==========================================*/


	//1.不使用模板实现print_vector函数
	void print_vector(const vector<int>& v)
	{
		//注意:下面我们可以使用两种不同的方式实现vector容器的遍历

		/*----------------方法一:使用迭代器遍历----------------*/
		//1.定义vector容器的迭代器
		//vector<int>::const_iterator it = v.begin();
		auto it = v.begin(); //注意:也可写成这个形式,其实范围for中的auto it本质上就是这行代码(所以:范围for的使用依赖迭代器)

		//2.使用while循环进行迭代遍历vector容器
		while (it != v.end())
		{
			//1.输出遍历到的元素
			std::cout << *it << " ";

			//2.让迭代器向后移动
			++it;
		}
		std::cout << std::endl;


		/*----------------方法二:使用范围for循环遍历----------------*/
		for (auto it : v)
		{
			std::cout << it << " ";
		}
		std::cout << std::endl;
	}




	//2.使用模板实现print_vector函数
	template<class T>
	void print_vector(const vector<T>& v)
	{
		//注意:下面我们可以使用两种不同的方式实现vector容器的遍历

		/*
		//----------------方法一:使用迭代器遍历----------------
		//1.定义vector容器的迭代器
		//vector<T>::const_iterator it = v.begin();
		auto it = v.begin();

		//2.使用while循环进行迭代遍历vector容器
		while (it != v.end())
		{
			//1.输出遍历到的元素
			std::cout << *it << " ";

			//2.让迭代器向后移动
			++it;
		}
		std::cout << std::endl;
		*/

		/*----------------方法二:使用范围for循环遍历----------------*/
		for (auto it : v)
		{
			std::cout << it << " ";
		}
		std::cout << std::endl;
	}



	/*==========================================任务2.3:实现函数模板:print_container==========================================*/

	template<class container>
	void print_container(const container& con)
	{
		/*
		//----------------方法一:使用迭代器遍历----------------
		auto it = con.begin();
		while (it != con.end())
		{
			//1.输出
			cout << *it << " ";

			//2.移动
			++it;
		}
		cout << endl;
		*/

		/*----------------方法二:使用范围for循环遍历----------------*/
		for (auto it : con)
		{
			std::cout << it << " ";
		}

		std::cout << std::endl;
	}
}

源文件:Test.cpp

#include"vector.h" 

namespace mySpace
{
	void test_vector01()
	{
		//1.
		vector<int> v;

		//2.尾插元素
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);


		/*------------------测试:三种遍历方式------------------*/
		cout << "==========测试:vector的遍历方式==========" << endl;
		//3.1:使用“下标”进行访问
		cout << "使用“下标”进行访问" << endl;

		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;


		//3.2:使用“迭代器”进行访问
		cout << "使用“迭代器”进行访问" << endl;
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;


		//3.3:使用“范围for”进行访问
		cout << "使用“范围for”进行访问" << endl;
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;


		//4.调用模板函数打印
		cout << "调用模板函数打印" << endl;
		print_vector(v);

		//测试存储double类型
		cout << "==========测试:存储double类型==========" << endl;
		vector<double> vd;

		vd.push_back(1.1);
		vd.push_back(2.1);
		vd.push_back(3.1);
		vd.push_back(4.1);
		vd.push_back(5.1);

		print_vector(vd);
	}



	void test_vector02()
	{
		cout << "==========测试:查找元素的操作==========" << endl;
		std::vector<int> v; // 测试标准库vector与自定义vector的兼容性

		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		cout << "vector中原始的元素为:" << endl;
		//print_vector(v);  错误,无法打印出vector容器v中的元素
		print_container(v);


		/*------------------测试:查找元素的操作------------------*/
		cout << "请输入要查找的元素>:";
		//1.输入要查找的元素
		int x;
		cin >> x;

		//2.使用标准算法find查找元素(依赖迭代器兼容性)
		auto p = find(v.begin(), v.end(), x);
		if (p != v.end())
		{
			//3.在p位置插入40,返回新元素的迭代器
			cout << "在" << x << "元素位置的前面的插入元素40" << endl;
			p = v.insert(p, 40); //注意:插入元素后迭代器p会失效,需用insert返回值更新

			//4.修改插入位置的下一个元素(演示迭代器算术操作)
			cout << "将" << x << "元素+1后再*10" << endl;
			(*(p + 1)) *= 10;
		}

		print_container(v);
	}



	void test_vector03()
	{
		cout << "==========测试:删除元素的操作==========" << endl;
		std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		cout << "vector中原始的元素为:" << endl;
		print_container(v);

		/*------------------测试:删除元素的操作------------------*/
		cout << "删除所有偶数:" << endl;
		//删除所有偶数(经典erase-remove惯用法)
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				it = v.erase(it); //注意:erase返回下一个元素的迭代器
			}
			else
			{
				++it;
			}
		}
		print_container(v);
	}



	void test_vector04()
	{
		cout << "==========测试:resize和reserve的区别==========" << endl;
		/*------------------测试:resize和reserve的区别------------------*/
		vector<int> v;
		v.resize(10, 1); //有效元素10个,值为1,容量至少10
		v.reserve(20);   //容量扩大到20,有效元素仍为10

		cout << "vector中原始容器的:“内容/长度/容量” 为:" << endl;
		print_container(v);
		cout << v.size() << endl;     // 输出10
		cout << v.capacity() << endl; // 输出≥20(具体值取决于扩容策略)


		cout << "调用:resize(15, 2)后:" << endl;
		v.resize(15, 2);   //有效元素15个,新增5个元素值为2
		print_container(v);

		cout << "调用:resize(25, 3)后:" << endl;
		v.resize(25, 3);   //容量不足25,先扩容再填充
		print_container(v);

		cout << "调用:resize(5)后:" << endl;
		v.resize(5);       //截断到5个元素,剩余元素被"销毁"(此处为简单类型,无实际销毁操作)
		print_container(v);
	}



	void test_vector05()
	{
		cout << "==========测试:拷贝构造和赋值运算符==========" << endl;
		cout << "v1中原始的元素为:" << endl;
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		print_container(v1);

		cout << "v3中原始的元素为:" << endl;
		vector<int> v3;
		v3.push_back(10);
		v3.push_back(20);
		v3.push_back(30);
		v3.push_back(40);
		v3.push_back(50);
		print_container(v3);
		/*------------------测试:拷贝构造和赋值运算符------------------*/

		cout << "调用拷贝构造函数:vector<int> v2 = v1 后,v2为:" << endl;
		vector<int> v2 = v1; //调用拷贝构造函数
		print_container(v2);

		cout << "调用拷贝赋值运算符:v1 = v3 后,v1为:" << endl;
		v1 = v3;			 //调用拷贝赋值运算符
		print_container(v1);
	}



	void test_vector06()
	{
		cout << "==========测试:各种构造函数==========" << endl;
		cout << "v1中原始的元素为:" << endl;
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(4);
		v1.push_back(4);
		print_container(v1);
		/*------------------测试:各种构造函数------------------*/

		/*--------------迭代器范围构造--------------*/
		//cout << "迭代器范围构造:" << endl;
		////1.截取部分数据
		//vector<int> v2(v1.begin(), v1.end());
		//print_container(v1);
		//print_container(v2);

		////2.从其他容器复制数据
		//list<int> lt;
		//lt.push_back(10);
		//lt.push_back(10);
		//lt.push_back(10);
		//lt.push_back(10);

		//vector<int> v3(lt.begin(), lt.end());  //注意:list迭代器是双向迭代器,支持输入迭代器操作
		//print_container(lt);
		//print_container(v3);


		/*--------------填充构造--------------*/
		cout << "填充构造:" << endl;
		cout << "vector<string> v4(5, \"1111111\")" << endl;
		vector<string> v4(5, "1111111");  //填充构造:5个string类型元素,初始值为"1111111"
		print_container(v4);

		//不同参数类型的填充构造(int和size_t)
		cout << "vector<int> v5(10)" << endl;
		vector<int> v5(10);      //10个默认构造的int(值为0)
		print_container(v5);

		cout << "vector<int> v6(10u, 1)" << endl;
		vector<int> v6(10u, 1);  //10个int,值为1(size_t参数)
		print_container(v6);

		cout << "vector<int> v7(10, 1)" << endl;
		vector<int> v7(10, 1);   //同上(int参数自动转换为size_t)
		print_container(v7);
	}


	void test_vector07()
	{

		/*------------------测试:大容量字符串的存储与扩容------------------*/
		cout << "==========测试:大容量字符串的存储与扩容==========" << endl;
		vector<string> v;
		v.push_back("11111111111111111111"); // 20个字符的字符串
		v.push_back("11111111111111111111");
		v.push_back("11111111111111111111");
		v.push_back("11111111111111111111");
		print_container(v);
		cout << "此时v的长度为:" << v.size() << endl;
		cout << "此时v的容量为:" << v.capacity() << endl;

		cout << "-----------向v中再添加一个字符串-----------" << endl;

		v.push_back("11111111111111111111"); // 触发扩容(假设当前容量为4,扩容后为8)
		print_container(v);
		cout << "此时v的长度为:" << v.size() << endl;
		cout << "此时v的容量为:" << v.capacity() << endl;
	}



	/*-----------------------测试:vector的扩容策略-----------------------*/

	void test_vector08()
	{
		cout << "==========测试:vector的扩容策略==========" << endl;
		/*---------------第一部分:准备阶段---------------*/
		//1.创建一个空vector容器
		vector<int> v;
		//2.定义一个变量的用来记录容量的变化
		size_t sz;
		//3. 预分配100个元素的容量
		v.reserve(100);  //注意:直接设置容量为100,不触发多次扩容

		/*---------------第二部分:初始状态输出---------------*/
		//1.获取当前容器的初始容量
		sz = v.capacity();
		cout << "容器的初始容量为: " << sz << '\n';   //输出:100(一次分配到位)

		/*---------------第三部分:不断的向容器中添加元素同时记录容量的变化---------------*/
		cout << "容器的容量的变化……:\n";
		for (int i = 0; i < 100; ++i)
		{
			//1.插入元素
			v.push_back(i);			//容量足够,不会触发扩容

			//2.监测容器的容量的变化
			if (sz != v.capacity()) //容量变化时打印(此处不会执行)
			{
				sz = v.capacity();
				cout << "容量的变为: " << sz << '\n';
			}
		}
	}


	/*-----------------------测试:reserve函数对容量的影响(不改变有效元素个数)-----------------------*/
	void test_vector09()
	{
		cout << "==========测试:reserve函数对容量的影响==========" << endl;
		//TestVectorExpand(); // 注释:如需测试扩容,取消此行注释

		cout << "v容器的初始状态为:" << endl;
		vector<int> v(10, 1);   //构造包含10个1的vector,初始容量≥10(具体由自定义vector实现决定)
		print_container(v);

		cout << "调用v.reserve(20)之后," << endl;
		v.reserve(20);			//确保容量至少20(若原容量不足则扩容)
		cout << "长度:" << v.size() << endl;     //输出:10  (有效元素个数不变)
		cout << "容量:" << v.capacity() << endl; //输出:≥20(例如:自定义vector可能扩容到20或更大)

		cout << "调用v.reserve(15)之后," << endl;
		v.reserve(15);			  //容量已≥15,无操作
		cout << "长度:" << v.size() << endl; //仍为10
		cout << "容量:" << v.capacity() << endl; //保持不变

		cout << "调用v.reserve(5)之后," << endl;
		v.reserve(5); //容量≥5,无操作(reserve不会减少容量)
		cout << "长度:" << v.size() << endl; //仍为10
		cout << "容量:" << v.capacity() << endl; //保持不变(不会回退到5)
	}



	/*-----------------------测试:resize函数对元素个数和容量的影响-----------------------*/
	void test_vector10()
	{
		cout << "==========测试:resize函数对元素个数和容量的影响==========" << endl;
		//TestVectorExpand(); // 注释:如需测试扩容,取消此行注释

		cout << "v容器的初始状态为:" << endl;
		vector<int> v(10, 1); // 10个1,容量≥10
		print_container(v);
		cout << "调用v.reserve(20)之后," << endl;
		v.reserve(20);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;

		cout << "调用v.resize(15, 2)之后," << endl;
		v.resize(15, 2);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;

		cout << "调用v.resize(25, 3)之后," << endl;
		v.resize(25, 3);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;

		cout << "调用v.resize(5)之后," << endl;
		v.resize(5);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;
	}




	/*-----------------------测试:插入、输入输出及不同类型存储-----------------------*/
	void test_vector11()
	{
		cout << "==========测试:插入、输入输出及不同类型存储==========" << endl;
		cout << "v容器的初始状态为:" << endl;

		vector<int> v(10, 1);	//10个1
		v.push_back(2);			//尾插2,变为11个元素
		v.insert(v.begin(), 0); //在头部插入0,变为12个元素(0,1,1,...,1,2)
		print_container(v);

		cout << "在索引为3处插入10之后的vector为:";
		v.insert(v.begin() + 3, 10); //在索引3处插入10,元素后移
		for (auto it : v)
		{
			cout << it << " ";   //输出:0 1 1 10 1 ... 1 2(索引3变为10)
		}
		cout << endl;

		cout << "请输入5个整数>:" << endl;
		vector<int> v1(5, 0);
		for (size_t i = 0; i < 5; i++)
		{
			cin >> v1[i];   //输入5个整数,覆盖默认值0
		}

		cout << "该vector中的内容为:" << endl;
		for (auto e : v1)
		{
			cout << e << ","; // 例如输入1 2 3 4 5,输出1,2,3,4,5,
		}
		cout << endl;
	}




	/*-----------------------测试:vector存储自定义类型(如:string)及二维vector-----------------------*/
	void test_vector12()
	{
		cout << "==========测试:vector存储自定义类型(如:string)及二维vector==========" << endl;
		cout << "vector中存储的自定义类型的string为:" << endl;
		//1.定义一个存储string的vector容器
		vector<string> v1;

		/*------------情况1:插入字符串对象------------*/
		//2.1:构造string对象
		string s1("xxxx");
		//2.2:插入string对象(深拷贝)
		v1.push_back(s1);

		/*------------情况2:插入字符串字面量------------*/
		//2.
		v1.push_back("yyyyy");

		//3.
		for (const auto& it : v1) //常量引用遍历,避免拷贝大字符串
		{
			cout << it << " ";
		}
		cout << endl;


		cout << "修改二维数组的第3行第2列的元素为2" << endl;
		/*-------------------第一部分:创建一个二维数组-------------------*/
		//构造二维vector:7个元素,每个元素是vector<int>(5,1)

		/*-------方法一:填充构造法-------*/
		std::vector<int> v(5, 1);				  //5个1的一维vector
		std::vector<std::vector<int>> vv(7, v); //7个v的拷贝,形成“7行5列”的二维数组

		/*-------方法二:嵌套构造法-------*/
		//std::vector<std::vector<int>> vv(7, std::vector<int>(5, 1)); // 直接指定每行的初始值

		/*-------------------第二部分:修改二维数组-------------------*/
		vv[2][1] = 2; //修改第3行(索引2)第2列(索引1)的元素为2
		//等价于:vv.operator[](2).operator[](1) = 2; 注释:operator[]的嵌套调用


		/*-------------------第三部分:打印二维数组-------------------*/
		//遍历二维vector并输出
		for (size_t i = 0; i < vv.size(); i++)
		{
			for (size_t j = 0; j < vv[i].size(); ++j)
			{
				cout << vv[i][j] << " "; //前两行全为1,第三行第二个元素为2,其余为1
			}
			cout << endl;
		}
		cout << endl;
	}

}


int main()
{
	mySpace::test_vector01(); // 调用命名空间mySpace中的test_vector1函数
	mySpace::test_vector02();
	mySpace::test_vector03();
	mySpace::test_vector04();
	mySpace::test_vector05();
	mySpace::test_vector06();
	mySpace::test_vector07();



	mySpace::test_vector08();
	mySpace::test_vector09();
	mySpace::test_vector10();
	mySpace::test_vector11();
	mySpace::test_vector12();

	return 0;
}

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值