欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > C++初阶-迭代器失效和vector::insert函数的最终实现

C++初阶-迭代器失效和vector::insert函数的最终实现

2025/5/21 9:34:51 来源:https://blog.csdn.net/2401_86446710/article/details/148074129  浏览:    关键词:C++初阶-迭代器失效和vector::insert函数的最终实现

目录

1.vector::insert函数

1.1问题分析

1.2vector::insert函数的最终实现

1.3vector::insert函数的分析

2.第二种迭代器失效

3.第三种迭代器失效

4.迭代器失效deepseek的回答

1. 迭代器失效的原因

2. 不同容器的迭代器失效情况

(1)std::vector

(2)std::deque

(3)std::list / std::forward_list

(4)std::map / std::set(及 unordered_ 版本)

3. 如何避免迭代器失效?

(1)更新迭代器

(2)使用索引替代迭代器

(3)先收集再删除

(4)谨慎使用 reserve / rehash

4. 总结

5.总结


1.vector::insert函数

我们测试一下这个函数:

void func(const td::vector<int>& v)
{for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}
void test()
{vector<int> v1; v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);func(v1);v1.insert(v1.begin(), 10);func(v1);v1.erase(v1.begin() + 3);func(v1);
}

那么运行结果为:

如果我们把代码改为如下形式:

void test()
{vector<int> v1; v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);func(v1);v1.insert(v1.begin(), 10);func(v1);v1.erase(v1.begin() + 3);func(v1);
}

则运行结果为:

也就是说insert函数是一定有问题的,这个需要我们调试分析一下代码:

1.1问题分析

我们发现在经过这个reserve后_start地址改变了,而这个pos地址不变,也就是说pos指向旧空间,但reserve函数在调用时会释放旧空间,it是_finish-1,而pos还是旧空间,在循环时一直判断it>=pos,那么就不会结束,会造成越界。

这种情况就是迭代器失效,pos指向旧空间,但扩容导致迭代器失效,本质上就是野指针了。

所以我们应该这样写:

1.2vector::insert函数的最终实现

//insert函数的实现2
void insert(iterator pos, const T& x)
{//判断是否合法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 it = _finish - 1;//移动数据while (it >= pos){*(it + 1) = *it;--it;}*pos = x;++_finish;
}

1.3vector::insert函数的分析

这是一个比较浅的迭代器失效的问题,但是这里也提醒我们了一个很重要的点:需要注意函数调用后可能会导致迭代器失效的问题。

2.第二种迭代器失效

我们看如下代码:

void test()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);int i;cin >> i;auto it = v1.begin() + 1;v1.insert(it, 10);cout << *it << endl;
}

那么运行结果为:

不管我们输入0-3中的任意数字,最终打印结果一定不是最终值,这是因为再进行插入,那么最终会导致扩容,而扩容导致原地址失效,导致it为野指针,这个it的类型可以写为:td::vector<int>::iterator,所以导致了打印结果不正确。且insert是否扩容是不确定的,无法判断是否有迭代器失效。

那如果我们把insert改为如下定义:

void insert(iterator& pos, const T& x)
{//判断是否合法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 it = _finish - 1;//移动数据while (it >= pos){*(it + 1) = *it;--it;}*pos = x;++_finish;
}

也就是说我们把第一个形参改为引用,这样就可以了吗?

用如下函数测试:

void test()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);int i;cin >> i;auto it = v1.begin() + 1;//不行v1.insert(it + 1, 10);//不行v1.insert(v1.begin(), 10);cout << *it << endl;
}

那么会有两个报错:

所以我们不能这样写!

所以我们最终解决办法就只能是再创建一个迭代器,否则很容易出错!

3.第三种迭代器失效

int main()
{std::vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}++it;}for (const auto& e : v1){cout << e << " ";}cout << endl;return 0;
}

那么运行结果为:

首先声明一下:这里的是库里面的函数,而且如果测试都是没有其他问题的,但是如果你打印也会有问题。

为什么用erase也会失效?

首先我们若遇到偶数,判断出2为偶数,但是v1会直接跳过3的位置去判断下一个位置了,所以会导致结果不准确,然后最终非法访问?

那么我们改代码为如下形式:

int main()
{std::vector<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}else{++it;}}for (const auto& e : v1){cout << e << " ";}cout << endl;return 0;
}

为什么还是会有问题(这个取决于编译器,在g++下是运行正确的)?

这个取决于编译器,VS会认为erase、insert后的迭代器是失效的。可以任务迭代器有个标记,在经过这些过程后会改变标记,因为这是一个更加严格的检查!而在不同的编译器可能运行结果是不同的,所以我们在之后的学习中要把一段程序在多个编译器运行,保证它在每个编译器都能运行

但是也要注意:erase在多数编译器中会只挪动数据,但有些编译器可能为了节省空间而缩容,所以单单erase也可能导致野指针!

结论:erase(it)以后,不同平台实现有差异,但是我们统一认为it失效了!我们在用完了一个后需要进行迭代器的更新!

4.迭代器失效deepseek的回答

通过deepseek我们可以更加深入的了解迭代器失效的问题,也方便了我们之后避免此类问题。以下是deepseek的搜索结果:

在 C++ 中,迭代器(iterator) 是指向容器(如 vectorlistmap 等)元素的抽象指针。但某些操作会导致迭代器失效(invalidated),即它不再指向有效的元素,继续使用会导致未定义行为(UB),如程序崩溃或数据错误。


1. 迭代器失效的原因

迭代器失效通常发生在容器结构被修改时,例如:

  • 插入元素insertpush_backemplace 等)

  • 删除元素erasepop_backclear 等)

  • 容器扩容(如 vector 的 reserve 或自动增长)

失效的根本原因是:

  • 内存重新分配(如 vector 扩容,旧迭代器指向被释放的内存)

  • 元素位置移动(如 vector 插入/删除导致后续元素位移)

  • 数据结构变化(如 unordered_map 的 rehash)


2. 不同容器的迭代器失效情况

不同容器对迭代器失效的敏感程度不同:

(1)std::vector

操作迭代器失效情况
push_back / emplace_back可能失效(如果触发扩容,所有迭代器失效;否则仅 end() 失效)
insert / emplace可能失效(插入点及之后的迭代器失效,如果扩容则全部失效)
erase被删元素及之后的迭代器失效
pop_back仅 end() 失效
clear / resize所有迭代器失效
reserve不影响现有迭代器(除非 capacity 改变)

示例:

std::vector<int> v = {1, 2, 3};
auto it = v.begin() + 1; // 指向 2
v.push_back(4);          // 可能扩容,it 失效!
// std::cout << *it;     // 未定义行为(UB)!

(2)std::deque

操作迭代器失效情况
push_back / push_front通常不影响现有迭代器(除非重新分配)
insert / emplace所有迭代器失效
erase被删元素及之后的迭代器失效
pop_back / pop_front仅被删元素的迭代器失效
clear所有迭代器失效

特点

  • deque 是分段连续存储,头尾插入通常不影响中间迭代器。

  • 但 insert 可能导致所有迭代器失效(因为可能重新分配)。


(3)std::list / std::forward_list

操作迭代器失效情况
push_back / push_front不影响现有迭代器
insert / emplace不影响其他迭代器
erase仅被删元素的迭代器失效
pop_back / pop_front仅被删元素的迭代器失效
clear所有迭代器失效

特点

  • list 是双向链表forward_list 是单向链表

  • 插入/删除只影响当前节点,其他迭代器仍然有效。

示例:

std::list<int> l = {1, 2, 3};
auto it = ++l.begin(); // 指向 2
l.erase(it);           // it 失效,但其他迭代器仍然有效

(4)std::map / std::set(及 unordered_ 版本)

操作迭代器失效情况
insert / emplace不影响现有迭代器
erase仅被删元素的迭代器失效
clear所有迭代器失效

特点

  • 基于红黑树map/set)或哈希表unordered_map/unordered_set)。

  • 插入/删除通常不影响其他迭代器(除非 rehash,如 unordered_map 扩容)。

unordered_map 的 rehash 问题:

std::unordered_map<int, int> m;
auto it = m.begin();
m.reserve(1000); // 可能触发 rehash,it 失效!
// std::cout << it->first; // UB!

3. 如何避免迭代器失效?

(1)更新迭代器

某些操作(如 erase)会返回新的有效迭代器

std::vector<int> v = {1, 2, 3};
auto it = v.begin() + 1;
it = v.erase(it); // it 现在指向 3(原 2 被删)

(2)使用索引替代迭代器

vector 可以用 [] 或 at() 访问:

std::vector<int> v = {1, 2, 3};
size_t idx = 1;
v.erase(v.begin() + idx); // 用索引计算新迭代器

(3)先收集再删除

避免在循环中直接删除:

std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> to_remove = {2, 4};
for (auto val : to_remove) {v.erase(std::remove(v.begin(), v.end(), val), v.end());
}

(4)谨慎使用 reserve / rehash

vector / unordered_map 扩容会导致所有迭代器失效

std::vector<int> v = {1, 2, 3};
auto it = v.begin();
v.reserve(100); // 可能扩容,it 失效!

4. 总结

容器插入操作删除操作扩容/rehash
vector可能失效可能失效全部失效
deque通常安全部分失效可能失效
list安全仅当前失效不适用
map/set安全仅当前失效不适用
unordered_map安全仅当前失效全部失效(rehash)

最佳实践:

  • vector:小心 push_back / insert / erase,尽量用索引。

  • list / map:插入/删除较安全,但 erase 后别用旧迭代器。

  • unordered_map:注意 reserve / rehash 导致失效。

理解迭代器失效机制,能避免很多隐藏的 UB(未定义行为)问题!

5.总结

这讲内容比较简单,但是还是说明我们也需要进行判断自己的迭代器是否会失效,这也是比较重要的知识。

下节将继续讲解vector的模拟实现的内容。好了,这讲结束了,喜欢的可以一键三连哦!下节再见!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词