Pointer to pointer clarification
我正在按照本教程关于指针如何工作。
让我引用相关段落:
1
2 int i = 5, j = 6, k = 7;
int *ip1 = &i, *ip2 = &j;Now we can set
1 int **ipp = &ip1;and
ipp points toip1 which points toi .*ipp isip1 , and**ipp isi , or 5. We can illustrate the situation, with our familiar box-and-arrow notation, like this:
If then we say
1 *ipp = ip2;we've changed the pointer pointed to by
ipp (that is,ip1 ) to contain a copy ofip2 , so that it (ip1 ) now points atj :
我的问题是:为什么在第二张图片中,
忘记关于指向类比的第二个问题。指针真正包含的是内存地址。
只是
因为您更改了
这个:
1 | *ipp = ip2; |
是相同的:
1 | ip1 = ip2; |
像C标签中的大多数初学者问题一样,这个问题可以通过回到第一原则来回答:
- 指针是一种值。
- 变量包含值。
-
& 运算符将变量转换为指针。 -
* 运算符将指针转换为变量。
(从技术上讲,我应该说"左值"而不是"变量",但我觉得将可变存储位置描述为"变量"更为明确。)
所以我们有变量:
1 2 | int i = 5, j = 6; int *ip1 = &i, *ip2 = &j; |
变量
变量
1 | int **ipp = &ip1; |
变量
让我们总结一下这个故事:
-
i 包含5 -
j 包含6 -
ip1 包含"指向i 的指针" -
ip2 包含"指向j 的指针" -
ipp 包含"指向ip1 的指针"
现在我们说
1 | *ipp = ip2; |
因此,这只是另一种说法
1 | ip1 = ip2; |
所以我们获取
我们只改变了一件事:
-
i 包含5 -
j 包含6 -
ip1 包含"指向j 的指针" -
ip2 包含"指向j 的指针" -
ipp 包含"指向ip1 的指针"
Why does
ipp still point toip1 and notip2 ?
分配给变量时,变量会发生变化。计算作业;变量没有比赋值更多的变化!首先分配
如果您想要更改
1 | ipp = &ip2; |
例如。
希望这段代码可以提供帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <iostream> #include <stdio.h> using namespace std; int main() { int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j; int** ipp = &ip1; printf("address of value i: %p ", &i); printf("address of value j: %p ", &j); printf("value ip1: %p ", ip1); printf("value ip2: %p ", ip2); printf("value ipp: %p ", ipp); printf("address value of ipp: %p ", *ipp); printf("value of address value of ipp: %d ", **ipp); *ipp = ip2; printf("value ipp: %p ", ipp); printf("address value of ipp: %p ", *ipp); printf("value of address value of ipp: %d ", **ipp); } |
它输出:
我个人的观点是,箭头指向这种方式的图片或使指针更难理解的图片。它确实使它们看起来像一些抽象的,神秘的实体。他们不是。
与计算机中的其他所有内容一样,指针是数字。名称"指针"只是一种说"含有地址的变量"的奇特方式。
因此,让我通过解释计算机实际工作原理来解决问题。
我们有一个
1 2 3 | Address Data Meaning 0x12345678 00 00 00 05 // The variable i 0x1234567C 00 00 00 06 // The variable j |
现在我们想指出这些变量。我们创建一个指向int,
1 2 3 | Address Data Meaning 0x12345680 12 34 56 78 // The variable ip1(equal to address of i) 0x12345684 12 34 56 7C // The variable ip2(equal to address of j) |
所以我们得到的只是一些包含数字的4字节内存块。在任何地方都没有任何神秘或神奇的箭头。
实际上,仅通过查看内存转储,我们无法判断地址0x12345680是否包含
然后我们用
1 2 | Address Data Meaning 0x12345688 12 34 56 80 // The variable ipp |
这种模式似乎很熟悉。另一个包含数字的4个字节的块。
现在,如果我们有一个上述虚构的小RAM的内存转储,我们可以手动检查这些指针指向的位置。我们查看存储在
因此,如果我们获取ipp,
1 2 3 | Address Data Meaning 0x12345680 12 34 56 7C // The variable ip1 0x12345684 12 34 56 7C // The variable ip2 |
(这些示例是为大端CPU提供的)
注意分配:
1 | ipp = &ip1; |
结果
所以对于
1 | ipp = &ip2; |
我们显然没有这样做。相反,我们正在更改
通过做下面的事情
1 | *ipp = ip2; |
我们只是替换
现在,
因此,
因为当你说
1 | *ipp = ip2 |
你说的是'
你不是说
1 | ipp = &ip1; |
以后的分配没有改变
使用
My question is: Why in the second picture, ipp is still point to ip1 but not ip2?
你放了漂亮的照片,我打算做一个漂亮的ascii艺术:
就像@ Robert-S-Barnes在他的回答中所说:忘记指针,什么指向什么,但从记忆的角度来思考。基本上,
因此,作为关于处理内存的指针,实际制作"有形"的最佳方法是显示指针代数对内存的作用。
所以,这是你的程序的内存(为了示例的目的简化):
1 2 3 | name: i j ip1 ip2 ipp addr: 0 1 2 3 4 mem : [ | | | | ] |
当你做初始代码时:
1 2 | int i = 5, j = 6; int *ip1 = &i, *ip2 = &j; |
这是你的记忆的样子:
1 2 3 | name: i j ip1 ip2 addr: 0 1 2 3 mem : [ 5| 6| 0| 1] |
在那里你可以看到
不要忘记,地址只是以特殊类型存储的整数。
然后你声明并定义
1 | int **ipp = &ip1; |
所以这是你的记忆:
1 2 3 | name: i j ip1 ip2 ipp addr: 0 1 2 3 4 mem : [ 5| 6| 0| 1| 2] |
然后,您将更改存储在
存储在
1 | *ipp = ip2; |
程序的记忆是
1 2 3 | name: i j ip1 ip2 ipp addr: 0 1 2 3 4 mem : [ 5| 6| 1| 1| 2] |
N.B。:因为
HTH
如果将取消引用运算符
例子:
1 2 3 4 5 | int i = 0; int *p = &i; // <-- N.B. the pointer declaration also uses the `*` // it's not the dereference operator in this context *p; // <-- this expression uses the pointed-to object, that is `i` p; // <-- this expression uses the pointer object itself, that is `p` |
因此:
1 2 | *ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself // therefore, `ipp` still points to `ip1` afterwards. |
你刚开始设定,
1 | ipp = &ip1; |
现在取消引用它,
1 2 | *ipp = *&ip1 // Here *& becomes 1 *ipp = ip1 // Hence proved |
考虑每个变量如下所示:
1 | type : (name, adress, value) |
所以你的变量应该像这样表示
1 2 3 4 5 | int : ( i , &i , 5 ); ( j , &j , 6); ( k , &k , 5 ) int* : (ip1, &ip1, &i); (ip1, &ip1, &j) int** : (ipp, &ipp, &ip1) |
由于
1 | *ipp = ip2; |
将addess
1 | (ip1, &ip1, &i) -> (ip1, &ip1, &j) |
但
1 | (ipp, &ipp, &ip1) |
所以
如果您希望
将
1 | ip1 = ip2; |
如果您希望
1 | ipp = &ip2; |
现在
因为您正在更改
现在我们在
我们不会改变
而已。
1 | ipp = &ip2; |
那么
维基说:
在
1 | ipp = &ip1; |
通过做
1 | *ipp = ip2; |
实际上
因此,在第二个图中,