使用 vector<>::p ush_back() 时如何使用矢量迭代器

How to use vector iterators when using vector<>::push_back()

本文关键字:何使用 迭代器 lt vector gt 使用 ush back      更新时间:2023-10-16

为简单起见,我将坚持使用vector<int>,但我认为这适用于任何vector<T>对象。

如果我使用vector<int>::iterator来跟踪int型向量中的某个位置,然后我使用vector<int>::push_back(),迭代器变得毫无价值。意思是,我不能用&取它的地址或解引用它。当我以以下方式打印一些对象的地址时,直接原因就说得通了:

vector<int> my_vec(1); //my_vec[0] = 0
vector<int>::iterator it = my_vec.begin(); //it -> my_vec[0], *it = my_vec[0] = 0
cout << "&my_vec = " << &my_vec << "n";
cout << "&my_vec[0] = " << &my_vec[0] << "n";
cout << "&it = " << &it << "n"; //prints address, all good
cout << "*it = " << *it << "n"; //prints 0, all good
cout << "nn" << pushing back..." << "nn";
my_vec.push_back(1);
cout << "&my_vec = " << &my_vec << "n"; //same as before push_back()!
cout << "&my_vec[0] = " << &my_vec[0] << "n"; //different from before push_back()!!
cout << "&it = " << &it << "n"; //same as before push_back()
//cannot do &it or *it

显然it的地址没有改变,但是push_back()在内存中移动了一些东西,现在my_vec的不同"元素"的地址被改变了。my_vec[i]有一个新的地址对我来说是有意义的,但我有以下问题:

1)为什么my_vec的地址没有变化?看起来,如果push_back()导致my_vec[i]的地址发生变化,那么它也应该改变整个对象的地址。对于一个数组,my_array是一个指向my_array[0]的指针,所以我可以想象一个操作改变每个my_array[i]的地址,并更新指针指向my_array[0]的新地址,但指针my_array作为对象本身的地址不会改变。但是my_vec在任何意义上都不是指向my_vec[0]的指针,所以我很困惑为什么my_vec[i]的地址会改变,而不是对象my_vec

2)为什么vector<int>内部的任何改变my_vec[i]地址的操作(如push_back())不能正确地"更新"任何迭代器?这似乎是个好主意?没有?

3)考虑到#2是什么,当我调用push_back()时,我的迭代器变得毫无价值,处理这种情况的正确方法是什么?如果我需要使用push_back(),我应该不使用迭代器吗?如果有人要抱怨我使用迭代器和push_back()的用例,为了简洁起见,我把它排除了,但它基本上是使用vector<int>实现一个堆栈,我使用迭代器来跟踪堆栈的顶部。因为我不想要一个固定的大小开始,我尝试使用push_back()来扩大堆栈当我的迭代器到达my_vec.end()。但总的来说,我认为这是个有效的问题。

非常感谢你的帮助!

为什么my_vec的地址没有变化?

因为vector对象本身仍然是位于同一地址的同一对象。重新分配改变的是它所管理的动态数组的地址,而不是管理该数组的vector对象的地址。

为什么vector<int>内部的任何改变my_vec[i]地址的操作(如push_back())不能正确地"更新"任何迭代器?这似乎是个好主意?没有?

将有一个(可能很大的)运行时成本。要么vector必须跟踪所有迭代器(需要动态存储,每次创建迭代器时分配内存,并在vector改变时更新所有vector),要么每个迭代器都需要对容器的引用,每次访问都要检查,并且不能作为简单的指针实现。c++通常在可能的情况下避免运行时成本——尤其是在这种情况下,它们几乎总是不必要的。

处理这种情况的正确方法是什么?

有多种选择。您可以存储索引而不是迭代器。您可以将std::list这样的容器与稳定迭代器一起使用(尽管这样可能效率较低)。如果可以设置数组大小的上限,则可以保留该上限,这样就不需要重新分配了。您可以编写自己的容器(或适配器)来自动更新迭代器。对于堆栈,如果它确实是一个堆栈,那么除了vector的末尾,您不需要跟踪任何其他内容,因此根本不需要存储迭代器。或者你可以使用std::stack,而不是重新发明它。

还有其他vector<T>成员函数(除了那些明显的)对迭代器有这种影响吗?

当任何操作导致vector容器增长超过当前容量时,

迭代器通过重新分配失效。可以通过reserve功能控制容量。

同时,擦除元素将使引用被擦除元素的迭代器或序列后面的元素失效。

为什么my_vec的地址没有改变?

std::vector中的内存重新分配发生在保存其元素的内部缓冲区,而不是对象本身(即不是std::vector本身)。考虑下面的玩具示例:

template<typename T>
class vector {
  T *buffer;
  ...
public:
  ...
};

如果我定义一个vector对象(例如,vector<int> v),对象v有一个地址。当由于插入或擦除而发生重新分配时,改变的不是v的地址,而是其成员变量buffer的值(即buffer将指向一个新的地址位置)。

为什么vector内部的任何改变my_vec[i]地址的操作(如push_back())不能正确地"更新"任何迭代器?

迭代器无效的问题是众所周知的,当你必须处理这种情况时,你应该采取预防措施。

还有其他vector成员函数(除了明显的那些)对迭代器有这种影响吗?它是否依赖于T?

有(例如,std::vector::insert, std::vector::erase)。不,它不依赖于T(即,std::vector的元素的类型)。