C++ 在向量中插入元素是否会损坏指向该向量的指针?
在一个模拟逻辑门的程序中,我从使用阵列转换而来C++ 在向量中插入元素是否会损坏指向该向量的指针?,c++,pointers,vector,C++,Pointers,Vector,在一个模拟逻辑门的程序中,我从使用阵列转换而来 node N[1000]; 到向量 vector<node> N; 向量N; 在使用矢量之前,我的程序工作得很好,但现在它打印错误的结果,所以我尝试调试,发现错误发生在这里: node* Simulator::FindNode(string h) { int i; for(i = 0; i < NNodes; i++) { if (N[i].getname() == h)
node N[1000];
到向量
vector<node> N;
向量N;
在使用矢量之前,我的程序工作得很好,但现在它打印错误的结果,所以我尝试调试,发现错误发生在这里:
node* Simulator::FindNode(string h)
{
int i;
for(i = 0; i < NNodes; i++)
{
if (N[i].getname() == h)
{
return &N[i];
}
}
node n ;
N.push_back(n);
N[NNodes].setname(h);
NNodes++;
return &N[NNodes-1]; //why?because of NNodes++
}
// ...
node* inp1;
node* inp2;
node* out;
string NodeName;
inp_file >> NodeName;
inp1 = FindNode(NodeName);
s1 = inp1;
inp_file >> NodeName;
inp2 = FindNode(NodeName); //inp1 is destroyed here
inp_file >> NodeName;
out = FindNode(NodeName); //inp2 and inp1 are destroyed here
node*模拟器::FindNode(字符串h)
{
int i;
对于(i=0;i>节点名;
inp1=FindNode(节点名称);
s1=inp1;
inp_文件>>节点名;
inp2=FindNode(NodeName)//inp1在这里被破坏了
inp_文件>>节点名;
out=FindNode(节点名称)//inp2和inp1在这里被破坏
第一次调用FindNode
时,第一个指针inp1指向正确的位置,即&N[0]
第二次调用FindNode
时,第一个指针inp1指向垃圾,第二个指针inp2指向正确的位置&N[1]
第三次调用FindNode
时,第一个和第二个指针(inp1
,inp2
)都指向垃圾!第三个指针指向正确的位置
为什么会发生这种情况?当我向vector中插入项时,vector是如何工作的?我应该使用哪种指针指向vector项?是的,它可以重新分配整个缓冲区,使所有指向旧位置的指针无效
您可以通过预分配来限制这一点,但这实际上只是性能提升。更好的方法是使用索引而不是原始指针。当一个向量增长时,它会被重新分配,这会有效地使指向向量元素的所有指针无效
如果事先知道向量中有多少元素,可以使用该方法预先分配空间。返回指向内部STL成员的指针可能不是最好的主意。当您将一个对象交给STL容器时,您基本上放弃了对它的控制。您告诉STL它可以在它认为合适的时候移动它,以维护容器给您的承诺。像Steven Sudit提到的那样,返回节点所在的索引是一个更好的主意
获得索引后,可以创建一个函数,返回感兴趣的节点内容的副本。通过这种方式,您还可以使用STL容器维护数据封装,不允许任何其他人修改其内容。是的,插入将使重新分配时指向向量元素的旧指针无效。如果您想非常努力地使用稳定指针,可以从vector切换到deque。它提供了一个与vector非常相似的接口,可以在不重新分配的情况下增长,并通过分配更多的块来移动以前的内容 使用deque而不是矢量所付出的代价是在随机访问上多了一层间接寻址。根据您的使用情况,这可能是完全不相关的。如果需要,您应该使用迭代器对整个deque进行迭代。这和在向量上迭代一样快
收益是:零再分配 假设您需要编写基于数组的代码,以便在必要时可以重新调整数组的大小 想象一下你会怎么做 想象一下陈旧的指针会发生什么 重写代码以使用索引而不是指针,或者根据需要确保不会发生重新分配。一些事情 首先,据我所知,
NNodes
只是跟踪大小。但是你有std::vector::size()
来解决这个问题。然后使用它来获取最后一个插入的元素,但您只需使用std::vector::back()
即可:return&N.back()代码>
另外,您的参数是通过值传递的,而它可能应该通过常量引用传递:conststring&h
。这避免了不必要的副本,并且通常*您应该通过常量引用而不是通过值来传递内容
这很糟糕:
node n;
N.push_back(n);
N[NNodes].setname(h);
节点
可能应该有一个构造函数,该构造函数接受一个常量字符串&
,并在初始化期间设置名称。这样,您就永远不会有没有名称的节点,如:
node n(h);
N.push_back(n);
或更简洁:
N.push_back(node(h));
好多了
其次,是的,vector
可以使指向元素的指针无效;也就是说,当载体的容量需要增加时。如果可以,reserve()。在你的情况下,你不能,所以你可以走两条不同的路线
第一条路线是。不要直接指向对象,而是将它们的索引放入数组中。注意,虽然它们的地址可能会改变,但它们在向量中的位置不会改变。您需要Simulator::FindNode
返回一个size\u t
,并返回N.size()-1
。添加一个成员,比如node&GetNode(size\u t index)
,它只返回N[index]代码>(如果愿意,将进行错误检查)。现在,每当您需要一个成员时,将该成员的索引交给GetNode
,您就会得到该节点的引用
另一种方法是更换容器。例如,您可以使用deque
。它没有连续的存储,但很像vector
push_-back
和pop_-back
仍然是O(1),并且它仍然具有良好的缓存一致性。(顺便说一句,deque
用连续存储交换了在O(1)时间内push_front
和pop_front
的能力)
重要的是,deque
在从任意一端进行推送或弹出操作期间不会使指针无效。它通过一种向量列表混合方式工作,在这里你可以得到
void foo(const std::string& s)
{
std::string ss(s); // make a copy, use copy
}
void foo(std::string s) // make a copy, use copy
{
}