今日分享:C++ -- vector

发布于:2025-09-02 ⋅ 阅读:(21) ⋅ 点赞:(0)

😎【博客主页:你最爱的小傻瓜】😎

🤔【本文内容:C++ vector 😍  】🤔

--------------------------------------------------------------------------------------------------------------------------------

在 C++ 的典籍长廊中,vector 恰似一位博闻强识的书吏。

它以动态舒展的卷轴承载万千元素,不必忧虑篇幅局促 —— 新添内容时,只需轻挥 push_back 的笔墨,便有宣纸自然延展开来。那些沉睡的元素,如经卷中的字句般静候翻阅,用下标轻轻一点,便能唤醒某页某行的深意。

当你执迭代器之笔漫溯其间,如同指尖抚过古籍的竹笺,每一个元素都按序铺陈,静待细品。若想为这卷帙重整篇章,sort 函数便是最好的校勘师,转瞬便能将凌乱的字句编排得井然有序。

无论是数字的珠玑、字符的墨韵,还是自定义对象的锦绣文章,它都能妥帖收纳,恰似一座随需而建的藏书楼,让数据在其间各安其位,静待编程者翻阅取用。

---------------------------------------------------------------------------------------------------------------------------------在开始学习vector容器前,我们先要了解vector的概念:

( 一 ) vector 的介绍:

1. vector 是可变大小数组的序列容器。它具备数组采用的连续存储空间来存储元素的特性,又优化了数组无法动态改变大小的缺点。而对于vector容器的动态分配数组,是一个有利也有弊的处理方式当我们插入新的数据时,且vector容器需要增加存储空间。这时他会重新开一个适合的数组来将原先的数组的数据赋值过去而这里就有一个耗时的缺点为了应对它,不同的库会有不同的分配空间策略:(核心)

分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大 。但具体是由实现者来进行怎样分配空间的。
无论如何,重新分配都应该是对数增长的间隔大小(比如每次扩容为原来的 1.5 倍或 2 倍),以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的,不会触发重新分配空间。
vector容器与其它 动态序列容器相比(deque, list and forward_list), 其最大优势:访问元素的时候更加高效,在末尾添加和删除元素相对高效。相对的,vector不在末尾的删除和插入操作效率更低。 
还有:由于存储空间连续​​​​​​​   vector 的迭代器和引用,支持随机访问(像数组一样直接跳着访问元素)、更稳定(只要不扩容,引用和迭代器基本不会失效),而且在遍历、随机取元素时速度更快;而 list and forward_list  的迭代器只能逐个挪,还容易失效,效率也低。

(二)vector的使用

1.构造:

接口说明
vector ()(重点) 无参构造
vector (size_type n, const value_type& val = value_type()) 构造并初始化 n 个 val
vector (const vector& x);(重点) 拷贝构造
vector (InputIterator first, InputIterator last); 使用迭代器进行初始化构造

无参构造:就是构造一个空vector

//vector()
vector<int>v;

构造并初始化 n 个 val:

//vector (size_type n, const value_type& val = value_type())
vector<int>v(10,1); 

拷贝构造:用已存在的 vector 来构造新的 vector

// vector (const vector& x);
vector<int> v1{1, 2, 3};
vector<int> v2(v1);

使用迭代器进行初始化构造:利用其他容器(或 vector 自身部分范围)的迭代器来构造新 vector

// vector (InputIterator first, InputIterator last);
// 示例1:用数组迭代器构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v(arr, arr + 5);
// 示例2:用vector的部分迭代器构造
vector<int> v1{1, 2, 3, 4, 5};
vector<int> v2(v1.begin() + 1, v1.end() - 1);

2.迭代器访问:

iterator 的使用 接口说明
begin + end(重点) 获取第一个数据位置的 iterator/const_iterator,获取最后一个数据的下一个位置的 iterator/const_iterator
rbegin + rend 获取最后一个数据位置的 reverse_iterator,获取第一个数据前一个位置的 reverse_iterator

begin 用于获取容器中第一个数据位置的 iterator(可修改元素)或 const_iterator(不可修改元素,只读);end 用于获取容器中最后一个数据的下一个位置的 iterator 或 const_iterator

// begin + end(iterator 示例,可修改元素)
vector<int> v{1, 2, 3, 4, 5};
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
    *it *= 2; // 修改元素值
}
// begin + end(const_iterator 示例,只读)
vector<int> v1{1, 2, 3, 4, 5};
for (vector<int>::const_iterator cit = v1.cbegin(); cit != v1.cend(); ++cit) {
    cout << *cit << " "; // 只能读取元素值,不可修改
}

rbegin 用于获取容器中最后一个数据位置的 reverse_iterator(反向迭代器,从后往前遍历的起始位置);rend 用于获取容器中第一个数据前一个位置的 reverse_iterator(反向遍历的结束位置)。

// rbegin + rend(reverse_iterator 示例)
vector<int> v{1, 2, 3, 4, 5};
for (vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit) {
    cout << *rit << " "; // 从后往前遍历并输出元素
}

3.容量的访问:

容量空间 接口说明
size 获取数据个数
capacity 获取容量大小
empty 判断是否为空
resize(重点) 改变 vector 的 size
reserve(重点) 改变 vector 的 capacity

size 用于获取 vector 中数据的个数。

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

capacity 用于获取 vector 的容量大小(即当前为 vector 分配的存储空间能容纳的元素个数)。

vector<int> v;
v.push_back(1);
cout << "容量大小:" << v.capacity() << endl;

empty 用于判断 vector 是否为空(即是否没有元素)

vector<int> v;
if (v.empty()) {
    cout << "vector 为空" << endl;
} else {
    cout << "vector 不为空" << endl;
}

resize 用于改变 vector 的 size(元素个数)。若新 size 大于原 size,会用指定值(若未指定则用默认构造值)填充新增位置;若新 size 小于原 size,则会删除多余元素。

// resize 示例1:新 size 大于原 size,用默认值填充
vector<int> v{1, 2, 3};
v.resize(5);
for (int num : v) {
    cout << num << " "; // 输出:1 2 3 0 0
}
cout << endl;
// resize 示例2:新 size 大于原 size,用指定值填充
vector<int> v1{1, 2, 3};
v1.resize(5, 10);
for (int num : v1) {
    cout << num << " "; // 输出:1 2 3 10 10
}
cout << endl;
// resize 示例3:新 size 小于原 size
vector<int> v2{1, 2, 3, 4, 5};
v2.resize(3);
for (int num : v2) {
    cout << num << " "; // 输出:1 2 3
}

reserve 用于改变 vector 的 capacity(容量),提前为 vector 分配足够的存储空间,避免后续多次扩容带来的性能开销。

vector<int> v;
v.reserve(10); // 提前分配能容纳 10 个元素的空间
cout << "容量大小:" << v.capacity() << endl;

4.增删查改:

vector 增删查改 接口说明
push_back(重点) 尾插
pop_back(重点) 尾删
find 查找。(注意这个是算法模块实现,不是 vector 的成员接口)
insert 在 position 之前插入 val
erase 删除 position 位置的数据
swap 交换两个 vector 的数据空间
operator [](重点) 像数组一样访问

push_back 用于在 vector 末尾插入元素(尾插)。

vector<int> v;
v.push_back(1);
v.push_back(2);
// 此时v中的元素为[1, 2]

pop_back 用于删除 vector 末尾的元素(尾删)

vector<int> v{1, 2, 3};
v.pop_back();
// 此时v中的元素为[1, 2]

insert 用于在指定位置(position 迭代器指向的位置之前)插入元素

vector<int> v{1, 3, 4};
// 在第二个元素位置(值为3的位置)前插入2
v.insert(v.begin() + 1, 2);
// 此时v中的元素为[1, 2, 3, 4]

erase 用于删除指定位置(position 迭代器指向的位置)的元素。

vector<int> v{1, 2, 3, 4};
// 删除第三个元素(值为3的元素)
v.erase(v.begin() + 2);
// 此时v中的元素为[1, 2, 4]

operator[] 用于像访问数组元素一样,通过下标访问 vector 中的元素。

vector<int> v{10, 20, 30};
cout << v[0] << " " << v[1] << " " << v[2] << endl; // 输出:10 20 30
v[1] = 25;
cout << v[1] << endl; // 输出:25

(三) vector 模拟实现(代码里面的注释很重要):

开始了解vector:对于vector类,由于要储存不同种类的数据,那么就需要模板来实现了。

namespace xin
{
	template<class T >
	class vector
	{
	public:
		typedef T* iterator;            //常规的迭代器,可以读写
		typedef const T* const_iterator;//const修饰的迭代器,只能读无法写。
    //模拟实现    
	private:
		iterator _start = nullptr;      //指向vector开头的数据
		iterator _finish = nullptr;     //指向vector最后一个的数据
		iterator endofstorage = nullptr;//指向vector存储空间最后的位置
	};
};

我们先是像实现string类构建一个自己的命名空间域。接着写出模板,然后写迭代器(这里是指针),再在类里面的private里面构建成员变量。(这些就是我们先要了解的)

下一步完善vector:

1.空vector的构造:

vector()
{}

2.析构函数:

~vector()
{
    if (_start)
	{
		delete[] _start;
		_start = _finish = _endofstorage = nullptr;
	}
}

说到底:析构函数就是将之前申请的空间还给操作系统,然后将之前所用到的指针赋值为空,防止野指针的出现。

3.迭代器:

//迭代器的访问方式:
iterator begin()//可读写,且在函数内部能进行修改所属对象。
{
	return _start;//返回头指针
}
iterator end()
{
	return _finish;//返回指向vector最后一个的数据的指针
}
const_iterator begin()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象
{
	return _start;
}
const_iterator end()const
{
	return _finish;
}

通过迭代器来去访问vector数据的开头的指针,和指向vector最后一个的数据的指针。

迭代器这个访问方式是每个容器都有的相同访问方式。优势:

1.统一接口

2.抽象底层实现

3.便于算法复用

4.支持范围操作

4.size:

size_t size()const //加这个const是为了在该函数内部,不能修改所属对象。
{
	return _finish - _start;//这里用指针减指针的方式来计算数据的个数
}

计算vector里面存储的数据大小。

5.capacity:

size_t capacity()const//加这个const是为了在该函数内部,不能修改所属对象
{
	return _endofstorage - _start;//这里用指针减指针的方式来计算存储空间的大小
}

计算vector 存储空间大小。

6.reserve:

void reserve(size_t n)
{
	if (n > capacity())//扩容的条件。
	{
		size_t sz = size();//计算原数组的数据大小
		T* tmp = new T[n];//申请新的数组
		//memcpy(tmp, _start, sizeof(T) * sz);
		//这里为什么不用 memcpy来将原先的数据赋值给新建的数组,是因为深拷贝的问题 兼容自定义类型的深拷贝需求 
		for (int i = 0;i < sz;i++)//for循环的更安全可靠。
		{
			tmp[i] = _start[i];
		}
		delete[] _start;
		//将这些指针给调到适合的位置。
		_start = tmp;
		_finish = _start + sz;
		_endofstorage = _start + n;
	}
}

保留:为vector预留空间。

对于不用memcpy是因为:

当拷贝 string 这类包含指针成员的自定义类型时,若使用 memcpy 进行拷贝,会引发浅拷贝问题

自定义类型(如 std::string)一般由栈上的指针(用于管理内部字符数组等)和指针指向的堆内存(存储实际数据)组成。memcpy 只是二进制层面复制栈上的指针值,不会复制指针指向的堆内存,这会导致:

  • 两个对象的指针成员指向同一块堆内存(浅拷贝);
  • 析构时对同一块内存多次释放(double free),造成程序崩溃;
  • 一个对象修改数据会影响另一个对象,破坏拷贝独立性。

对于 vector,本身是深拷贝,但当 vector 中存储的是 string 数组或其他含指针成员的自定义类型数组时,若用 memcpy 处理,也会因浅拷贝而出错。

7.resize(调整容器大小)

void resize(size_t n, const T& val = T())//T()是指确保生成一个符合 “默认初始化” 语义的 T 类型对象。
{
	if (n < size())
	{
		_finish = _start + n;//(比size()小时,我们直接截取到我们想要的n个数据)
	}
	else
	{
		reserve(n);
		while (_finish != _start + n)//比size()大时,我们就重新申请空间,在将后面的空间赋值T()
		{
			*_finish = val;
			++finish;
		}
	}
}
这里要补充一个概念:

在 C++ 中,T() 是一种值初始化语法,用于创建 T 类型的默认构造对象,这是由 C++ 标准规定的语法规则:

  1. 对于内置类型(如 int、double 等)
    T() 会生成该类型的 “零值”。例如:

    • int() 等价于 0
    • double() 等价于 0.0
    • 指针类型 int*() 等价于 nullptr
  2. 对于自定义类型(类 / 结构体)
    T() 会显式调用该类型的默认构造函数(即无参数的构造函数)。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数,此时 T() 会使用这个编译器生成的版本。

8.insert(pos的位置插入):

iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start && pos <= _finish);//判断pos是否在数组内
	if (_finish == _endofstorage)//判断两种请况:空 ,两个不同概念的尾部指针重合.以及处理方式。
	{
		size_t len = pos - _start;//为处理迭代器失效而留下的坐标。
		size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(new_capacity);

		pos = _start + len;//解决迭代器失效的问题
	}
	iterator end = _finish - 1;//这里减一是为了更好的访问和挪移数据。
	while (end > pos)//往后挪移
	{
		*(end + 1) = *end;
		--end;
	}
	//插入数据
	*pos = x;
	++_finish;
	return pos;
}

pos位置的插入。这里有个迭代器失效的问题我们要注意一下,并去解决。

迭代器失效的本质

迭代器底层多是指针或指针封装(如 vector 迭代器类似原生指针 T*)。迭代器失效,是指其底层对应指针指向的空间被销毁,继续使用会导致程序崩溃。解决扩容导致内部迭代器失效的关键逻辑通过 size_t len = pos - _start; 和 pos = _start + len; 两行代码解决:

  • 记录偏移量len = pos - _start 计算插入位置 pos 相对于容器起始地址 _start 的偏移量(相对位置,不受内存释放 / 移动影响)。
  • 重定位迭代器:扩容时旧内存释放、_start 指向新内存,利用 pos = _start + len 重新确定 pos 在新内存中的位置。

避免失效的原因

  • 绝对地址(如旧内存地址)会因内存释放失效,但偏移量是相对位置,不受内存块移动影响。
  • 即便 pos 原本指向特殊地址,偏移量计算仍能准确定位新内存中的位置。

关于 pos 为 0 的小知识点

有效内存空间的地址不会是 0,0 是操作系统预留的 “无效地址”(对应 nullptr),不会分配给用户程序正常内存空间。所以指向容器有效范围的迭代器,底层指针绝不可能为 0,不存在 “pos 为 0” 的隐患。

返回值 pos 的作用

用于解决外部迭代器失效,防止类似 “先获取迭代器,再执行插入操作后,原迭代器因容器内部变化而失效” 的问题

外部迭代器失效问题

外部代码若保存插入前的迭代器,当插入触发 vector 扩容时,旧内存被释放,该迭代器会指向无效地址(失效)。若继续使用失效迭代器(如 it += 10),会访问已释放内存,引发未定义行为(如程序崩溃),这类操作属于高危行为。

解决方法

insert 操作会返回新的有效迭代器,应改用该返回值来操作,而非使用插入前可能失效的旧迭代器。示例代码:

 
auto it = vec.begin() + 1;
// 插入后,it可能失效,改用返回的新迭代器
auto new_it = vec.insert(it, 200);
*new_it += 10; // 安全,new_it指向新内存中正确位置

insert 返回值的意义

提供扩容后已重定位的有效迭代器,替代可能失效的旧迭代器。

迭代器失效总结

  • 内部防失效:通过偏移量 len 记录相对位置,确保护容后 pos 在新内存中正确定位,保证 insert 内部逻辑正常。
  • 外部防失效insert 返回更新后的有效迭代器,提醒用户放弃使用可能失效的旧迭代器,改用新迭代器。

9.push_back(尾插):

void push_back(const T& x)//尾插
{
	/*if (_finish == _endofstorage)//判断数据情况,如容器是否为空,为空的处理方式,不为空的处理方式。
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	//尾插。
	*_finish = x;
	++_finish;*/
    insert(end(),x);
}

10.构造函数和拷贝构造函数:

vector(size_t n, const T& val = T())//这里的构造就像当于截取,但因为是初始化,刚开始_start = _finish,所以他是从开头赋值初始化
{
	resize(n, val);
}
vector(int n, const T& val = T())//这个是因为当我们在传参的时有我们传<int int>时,编译器会更具“精确匹配优先于需要隐式转换的匹配”,会到迭代器哪里但int又不是迭代器,所以会报错。
{
	resize(n, val);
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)//对于迭代器的初始化,而这里为什么用push_back呢,这是因为当我们不知道你要存储的数据大小时,我们只能一个一个填下去,尽管他是会遇到vector的这一缺点但还是要这样做,因为不知道有大小。
{
	while (first != last)
	{
		push_back(*first);//因为刚开始_start = _finish。
		++first;
	}
}

vector(const vector<T>& v)
{
	_start = new T[v.capacity()];//拷贝构造,申请空间
	//memcpy(_start,v._start,sizeof(T)*v.size);//是因为不适合自定义类形,浅拷贝与深拷贝的问题
	for (size_t i = 0;i < v.size();i++)
	{
		_start[i] = v._start[i];
	}
	//为其赋值。
	_finish = _start + v.size();
	_endofstorage = _start + v.capacity();
}
//旧版
/*vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
	{
		reserve(v.capacity());
		for (auto e : v)
		{
			push_back(e);
		}
	}*/

这里有一个概念要拿出来讲:

那便是精确匹配优先于需要隐式转换的匹配

对于:

vector<int>v(10,1);

你认为会用哪构造(如果没有int这个显示的),是size_t, 还模板的那个。他会是模板那个因为编译器会把10,1都看成int,满足InputIterator first, InputIterator last的参数类型要求(两个参数类型相同)因此它符合精确匹配优先于需要隐式转换的匹配。而int —》size_t这个隐式转换优先级低。

解决方法:

核心就是不让它隐式转换。

1.外:

vector <int> v(10u, 1);

2.内:

vector(int n, const T& value = T())
{
    resize(n, value);
}

11.erase(删除pos位置数据):

iterator erase(iterator pos)
{
	assert(pos >= _start && pos <= _finish);//判断pos是否在数组内
	iterator it = pos + 1;//这里加一是为了更好的访问和挪移数据
	while (it != _finish)//往前覆盖
	{
		*(it - 1) = *it;
			++it;
	}
	--_finish;
	return pos;
}

迭代器失效的定义与标准规定

对 vector 执行 erase(pos) 后,被删除元素的迭代器 pos 及其之后的所有迭代器都会失效。此时对失效迭代器进行解引用(如 *it)或递增(如 ++it)操作,属于未定义行为(标准不规定执行结果,编译器可自由处理,可能崩溃、输出错误结果甚至 “看似正常运行”)。

不同平台的表现差异

  • VS:对迭代器有效性做严格额外检查,若检测到使用失效迭代器,会直接报错或崩溃,能提前暴露问题。
  • Linux:默认不做强制检查,失效迭代器可能恰好指向未回收内存,导致程序 “看似能运行”,但这是巧合,本质仍为错误代码。

错误代码示例与问题

 // 错误示例:删除所有偶数
    for (auto it = v.begin(); it != v.end(); ++it) {
        if (*it % 2 == 0) {
            v.erase(it); // 删除后,it已失效
            // 此处it指向被删除元素的下一个位置,但标准未定义其有效性
        }
    }
    
    // 输出结果可能异常(如跳过元素、循环提前结束等)
    for (int num : v) {
        cout << num << " ";
    }
    return 0;

如删除偶数的代码(erase 后直接 ++it),会因迭代器失效出现逻辑错误(如跳过元素),且删除本质是移动数据覆盖待删数据,会导致 finish 前移,后续 it 可能永远不等于 end,循环无法正确结束。

看似 “可行” 代码的隐患

即使在 Linux 下 “通过测试” 的代码,也存在问题:

  • 不符合 C++ 标准,erase(it) 后 it 已失效,后续 it != v.end() 判断也可能出错。
  • 移植性极差,换用 VS 等编译器或调试模式会暴露崩溃 / 逻辑错误。
  • 若触发扩容(内存重分配),失效 it 指向旧内存块,操作会导致严重错误。

正确做法

vector 删除元素时,必须通过 erase 的返回值更新迭代器,这是唯一能保证跨平台一致性和程序正确性的做法。其他 “看似可行” 的写法,本质是依赖未定义行为的侥幸,隐藏着难以排查的风险。判断代码正确性,应看是否符合语言标准,而非 “在某平台能运行”。

12.尾删:

void pop_back()
{
	erase(--end());
}

直接用erase任意位置删除,如果不想用erase也可以在erase里面的代码里找删除的原理再套用在尾部。

13.重载:

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
}

// v1 = v2
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}
T& operator[](size_t pos)
{
	assert(pos < size());

	return _start[pos];
}

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

	return _start[pos];
}

这里的重载都与strin类里面的类似。主要是swap这个交换,以及对_start指针的运用。

完整代码:

#include<assert.h>
#include<string>
#include<iostream>
using namespace std;
namespace xin
{
	template<class T >
	class vector
	{
	public:
		typedef T* iterator;            //常规的迭代器,可以读写  
		typedef const T* const_iterator;//const修饰的迭代器,只能读无法写。
		//析构函数:
		~vector()
		{
			if (_start)
			{
				delete[] _start; //从头指针开始找 释放空间
				_start = _finish = _endofstorage = nullptr; //将指针赋值为空
			}
		}
		//迭代器的访问方式:
		iterator begin()//可读写,且在函数内部能进行修改所属对象。
		{
			return _start;//返回头指针
		}
		iterator end()//可读写,且在函数内部能进行修改所属对象。
		{
			return _finish;//返回指向vector最后一个的数据的指针
		}
		const_iterator begin()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象
		{
			return _start;
		}
		const_iterator end()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象
		{
			return _finish;
		}
		size_t size()const //加这个const是为了在该函数内部,不能修改所属对象。
		{
			return _finish - _start;//这里用指针减指针的方式来计算数据的个数
		}
		size_t capacity()const//加这个const是为了在该函数内部,不能修改所属对象
		{
			return _endofstorage - _start;//这里用指针减指针的方式来计算存储空间的大小
		}
		void reserve(size_t n)
		{
			if (n > capacity())//扩容的条件。
			{
				size_t sz = size();//计算原数组的数据大小
				T* tmp = new T[n];//申请新的数组
				//memcpy(tmp, _start, sizeof(T) * sz);
				//这里为什么不用 memcpy来将原先的数据赋值给新建的数组,是因为深拷贝的问题 兼容自定义类型的深拷贝需求 
				for (int i = 0;i < sz;i++)//for循环的更安全可靠。
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
				//将这些指针给调到适合的位置。
				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}
		void resize(size_t n, const T& val = T())//T()是指确保生成一个符合 “默认初始化” 语义的 T 类型对象。
		{
			if (n < size())
			{
				_finish = _start + n;//(比size()小时,我们直接截取到我们想要的n个数据)
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)//比size()大时,我们就重新申请空间,在将后面的空间赋值T()
				{
					*_finish = val;
					++finish;
				}
			}
		}
		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);//判断pos是否在数组内
			if (_finish == _endofstorage)//判断两种请况:空 ,两个不同概念的尾部指针重合.以及处理方式。
			{
				size_t len = pos - _start;//为处理迭代器失效而留下的坐标。
				size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(new_capacity);

				pos = _start + len;//解决迭代器失效的问题
			}
			iterator end = _finish - 1;//这里减一是为了更好的访问和挪移数据。
			while (end > pos)//往后挪移
			{
				*(end + 1) = *end;
				--end;
			}
			//插入数据
			*pos = x;
			++_finish;
			return pos;
		}
		void push_back(const T& x)//尾插
		{
			if (_finish == _endofstorage)//判断数据情况,如容器是否为空,为空的处理方式,不为空的处理方式。
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			//尾插。
			*_finish = x;
			++_finish;
			insert(_finish, x);
		}
		vector(size_t n, const T& val = T())//这里的构造就像当于截取,但因为是初始化,刚开始_start = _finish,所以他是从开头赋值初始化
		{
			resize(n, val);
		}
		vector(int n, const T& val = T())//这个是因为当我们在传参的时有我们传<int int>时,编译器会更具“精确匹配优先于需要隐式转换的匹配”,会到迭代器哪里但int又不是迭代器,所以会报错。
		{
			resize(n, val);
		}
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)//对于迭代器的初始化,而这里为什么用push_back呢,这是因为当我们不知道你要存储的数据大小时,我们只能一个一个填下去,尽管他是会遇到vector的这一缺点但还是要这样做,因为不知道有大小。
		{
			while (first != last)
			{
				push_back(*first);//因为刚开始_start = _finish。
				++first;
			}
		}
		vector(const vector<T>& v)
		{
			_start = newT[v.capacity];//拷贝构造,申请空间
			//memcpy(_start,v._start,sizeof(T)*v.size);//是因为不适合自定义类形
			for (size_t i = 0;i < v.size;i++)
			{
				_start[i] = v._start[i];
			}
			//为其赋值。
			_finish = _start + v.size;
			_endofstorage = _start + v.capacity;
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos <= _finish);//判断pos是否在数组内
			iterator it = pos + 1;//这里加一是为了更好的访问和挪移数据
			while (it != _finish)//往前覆盖
			{
				*(it - 1) = *it;
					++it;
			}
			--_finish;
			return pos;
		}
		void swap(const vector<T>& v)
		{
			_start = v._start;
			_finish = v._finish;
			_endofstorage = v._endofstorage;
		}
		vector<T>& operator=(vector<T>& v)
		{
			swap(v);
			return *this;
		}
		T& operator [](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator [](size_t pos)const
		{
			assert(pos < size());
			return _start[pos];
		}

	private:
		iterator _start = nullptr;      //指向vector开头的数据
		iterator _finish = nullptr;     //指向vector最后一个的数据
		iterator _endofstorage = nullptr;//指向vector存储空间最后的位置
	};
};

❤️总结

相信坚持下来的你一定有了满满的收获。那么也请老铁们多多支持一下,为爱博,点点举报,偶布,是点点关注,收藏,点赞。❤️