无效的指针、引用和迭代器

首先以示例代码为例:

vector<int> v;

//添加一些元素
fir(int i=0; i<10; ++i)
	v.push_back(i);

int* my_favorite_element_ptr = &v[3];
cout<<"My favorite element = "<<(*my_favorite_element_ptr)<<endl;
cout<<"Its address = "<<my_favorite_element_ptr<<endl;

cout<<"Adding more elements.."<<endl;

//添加更多元素
for(int i=0; i<100; ++i)
	v.push_back(i*10);

cout<<"My favorite element = "<<(*my_favorite_element_ptr)<<endl;
cout<<"Its address = "<<&v[3]<<endl;

对于以上的代码,会发生什么样的情况?我们创建了一个包含10个元素的vector,并出于某种原因决定保存一个指向索引位置为3的元素的指针。接着,我们向这个vector添加了另一些元素,并试图复用前面所保存的指针。这段代码会有什么错误吗?

对于以上的代码,它的输出如下:

My favorite element = 3
Its address  = 0x1001000cc

Adding more elements...

My favorite element = 3
Its address  = 0x10010028c

注意,当我们向这个vector又添加一些元素之后,元素&v[3]的地址发生了变化。问题主要在于当我们向这个vector添加一些新元素时,现有的元素可能会移动到完全不同的位置。

当我们创建一个vector时,它默认分配一定数量的元素(通常是16)。接着,当我们试图添加超出容量的元素时,这个vector就会分配一个新的、更大的数组,把原先的元素从旧位置复制到新位置,然后继续添加新元素,直到新的容量也被用完。旧的内存被销毁,可以用于其他用途。

同时,我们的指针仍然指向旧的位置,现在已经是被销毁的内存。因此,如果继续使用这个指针会发生什么情况?如果没有人复用这块内存,我们就比较“走运”,不会注意到发生了什么。但是,即使是在这种最好的情况下,如果我们写入到这个位置(赋值),它将不会修改元素v[3]的值,因为它已经位于别处。

如果我们运气不佳,这块内存已经被其他人用于其他用途,这种操作的后果可能极为不妙,有可能修改了正好位于这个位置的一个不相关的变量的值,甚至可能有导致程序崩溃的可能。

前面的示例代码中所涉及的是指针,如果涉及的是引用,也会发生同样的事情。例如:我们不是写成:

int* my_favorite_element_ptr = &v[3];

而是写成:

int& my_favorite_element_ptr = &v[3]

其结果是完全相同的。原因是引用只是“解引用后的指针”。它知道一个变量的地址,但为了访问它所指向的内存,并不需要再变量前面加上星号。因此语法虽不同,但结果却是一样的。

最后,当我们使用迭代器时,也会出现相同的结果。例如:

vector<int> v;

fir(int i=0; i<10; ++i)
	v.push_back(i);

vector<int>::const_iterator old_begin = v.begin();

cout<<"Adding more elements..."<<endl;

for(int i=0; i<100; ++i)
	v.push_back(i*10);

vector<int>::const_iterator new_begin = v.begin();

if(old_begin == new_begin)
	cout<<"Begin-s are the same."<<endl;
else
	cout<<"Begin-s are DIFFERENT."<<endl;

cout<<"My favorite element = "<<(*my_favorite_element_ptr)<<endl;
cout<<"Its address = "<<&v[3]<<endl;

它的输出结果如下:

Adding more elements...

Begin-s are DIFFERENT.

因此,如果我们保存一个指向某个元素(可以是任何元素,并不一定是begin()所指向的元素),它可能会在vector的内容被修改之后失效,因此vector的内部数组以及begin()所产生的对应迭代器可能被移动到其他位置。

因此,在修改vector之前所得到的指向其中某个元素的任何指针、引用或迭代器在vector由于增加元素而被修改之后就不应该再使用。实际上,对几乎所有STL容器以及所有可能修改容器长度的操作(例如,添加或删除元素),情况都是如此。有些容器,例如hash_set和hash_map,并不正式属于STL,但它们与STL相似,将来很可能加入到STL中。在涉及这里所讨论的问题时,它们的行为与STL容器相同:在修改容器之后,迭代器就不再有效。虽然有些STL容器在添加或删除元素之后,仍然保留原先指向它的元素的迭代器,但STL库的整体精神是可以用一种容器替换另一种容器,而原先的代码仍然没有问题。因此,在STL容器或类似STL的容器被修改之后,就不应该假设它的迭代器仍然是有效的。

注意:在前面的示例代码中,我们在访问这个指针相同的线程内修改了容器,如果一个线程中保存了一个指针、引用和迭代器,同时在另一个线程中修改容器,不仅会出现相同的问题,还会导致更复杂的情况。

有趣的是,在以上的示例代码中,索引在指针失败时仍然起作用:如果通过一个基数为0的索引标记了一个元素(即第一个例子中,对int index_of_my_favorite_element = 3这样的语句),这个例子能够正确的继续执行。当然,使用索引的开销(速度更慢)要大于指针,因为访问与索引对应的元素时,vector必须执行一些运算,即每次使用[]操作符时计算变量的地址。它的优点是能够起作用,缺点是只适用于vector。对于所有其他STL容器,一旦修改了容器,必须再次找到指向所需元素的迭代器。

总结:修改了容器之后,不要再保存指向容器内元素的指针、引用或迭代器。

无效的指针、引用和迭代器

时间: 2024-11-07 05:13:46

无效的指针、引用和迭代器的相关文章

JSON的put方法是一个指针引用

JSON的put方法是一个指针引用; import org.json.simple.JSONObject; JSONObject a=new JSONObject(); a.put("date","2015-11-08");a.put("time","15:48:28"); a.toJSONString() --> {"date":"2015-11-08","time&q

等式转换(熟悉一下链表,指针引用)

1 /***************************************************************************** 2 *输入一个正整数X,在下面的等式左边的数字之间添加 + 号或者 - 号,使得等式成立. 3 *1 2 3 4 5 6 7 8 9 = X 4 *比如: 5 *12 - 34 + 5 - 67 + 89 = 5 6 *1 + 23 + 4 - 5 + 6 - 7 - 8 - 9 = 5 7 *请编写程序,统计满足输入整数的所有整数个数

编程题:字符串的指针引用。用指针法实现。功能:将字符串str2复制连接到str1

#include<stdio.h> void main() { char *s1,*s2, str1[30]="beijing",str2[10]="China"; for(s1=str1;*s1!='\0';s1++); for(s2=str2;*s2!='\0';) *s1++=*s2++; *s1='\0'; printf("%s\n",str1); } 编程题:字符串的指针引用.用指针法实现.功能:将字符串str2复制连接到s

浅谈运用指针引用字符串

一.字符串的引用方式 1.如果字符串存放在字符数组中可以用数组名和下标引用 char string[10] = "Hello word"; printf("%s",string); 2.用字符指针变量指向字符串常量,用字符指针引用字符串 char *strp = "Hello word"; printf("%s",strp); //系统将字符串常量以字符数组形式保存在内存中,字符串末尾系统自动加了一个'\0'.再将字符数组的首

C++ 指针引用

//指针引用 #include<iostream> using namespace std; struct Teacher{ char name[30]; int age; }; int InitA(Teacher **pout/*out*/){ int ERRO_MSG = 0; if (pout==NULL) { ERRO_MSG = 1; printf("pout==NULL erro msg:%d\n", ERRO_MSG); return ERRO_MSG; }

二叉查找树-优化版,使用了指针引用

1 //与上一次实现不同,这里使用了大量的指针引用, 2 //因此节省了一个指向父节点的指针 3 #include<iostream> 4 #include<vector> 5 #include<ctime> 6 #include<cstdlib> 7 8 using namespace std; 9 10 template<class T> 11 class BinarySearchTree 12 { 13 private: 14 struct

传指针和传指针引用的区别/指针和引用的区别(本质)

转自:http://blog.sina.com.cn/s/blog_673ef8130100imsp.html 指针传递参数本质上是值传递的方式,它所传递的是一个地址值.值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本.值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值.(这里是在说实参指针本身的地址值不会变) 而在引用传递过程中,被调函数的形式参数虽然也作为局

C++二级指针和指针引用

前提 已经清晰一级指针和引用. 可参考:指针和引用与及指针常量和常量指针 或查阅其他资料. 一级指针和二级指针 例子 个人觉得文字描述比较难读懂,直接看代码运行结果分析好些,如果想看文字分析,可参考文末参考文章. #include <iostream> using namespace std; void make(int **pp) { **pp = 66;//这样只是改变了指向的值,即a, 指向没有改变 } int main() { int a=5; int *q=&a; int *

STL(1)---从对-&gt;运算符的重载到灵巧指针再到迭代器

首先->也就是指针间接引用运算符一定是 一个成员函数,必须返回值是一个对象或者对象的引用,或者必须返回一个指针,被用于选择指针间接引用运算符箭头所指的内容 所谓灵巧指针就是一个对象表现的像一个指针,这时候这个指针比一般的指针具有与生俱来的灵巧性,常称作灵巧指针 所谓迭代器就是一个对指针进行封装的结构,通过对指针封装从而对数据结构中的数据进行访问,从而使指针不直接接触数据,既安全有方便用户 #include <iostream> #include<vector> using n