我正在关注这个tutorial关于指向指针的指针是如何工作的。
让我引用相关段落:
int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j;
现在我们可以设置
int **ipp = &ip1;
和
ipp
指向ip1
,指向i
。*ipp
为ip1
,**ipp
为i
,或5.我们可以用我们熟悉的方框箭头符号说明情况,如下所示:
如果那时我们说
*ipp = ip2;
我们更改了
ipp
指向的指针(即ip1
)以包含ip2
的副本,以便它(ip1
)现在指向在j
:
我的问题是:为什么在第二张图片中ipp
仍然指向ip1
而不是ip2
?
答案 0 :(得分:142)
忘记关于指向类比的第二个问题。指针真正包含的是内存地址。 &
是“地址”运算符 - 即它返回对象内存中的地址。 *
运算符为您提供指针引用的对象,即给定包含地址的指针,它返回该存储器地址处的对象。因此,当您执行*ipp = ip2
时,您正在做的是*ipp
将对象放在ipp
ip1
中的地址,然后分配给ip1
存储在ip2
中的值,即j
的地址。
<强>简单地强>
&
- &gt;的地址
*
- &gt;值
答案 1 :(得分:43)
因为您更改了ipp
指向的值而不是ipp
的值。因此,ipp
仍然指向ip1
(ipp
的值),ip1
的值现在与ip2
&#39相同; s值,因此它们都指向j
。
此:
*ipp = ip2;
与:
相同ip1 = ip2;
答案 2 :(得分:21)
#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\n", &i);
printf("address of value j: %p\n", &j);
printf("value ip1: %p\n", ip1);
printf("value ip2: %p\n", ip2);
printf("value ipp: %p\n", ipp);
printf("address value of ipp: %p\n", *ipp);
printf("value of address value of ipp: %d\n", **ipp);
*ipp = ip2;
printf("value ipp: %p\n", ipp);
printf("address value of ipp: %p\n", *ipp);
printf("value of address value of ipp: %d\n", **ipp);
}
输出:
答案 3 :(得分:20)
与C标签中的大多数初学者问题一样,这个问题可以通过回到第一原则来回答:
&
运算符将变量转换为指针。*
运算符将指针转换为变量。(从技术上讲,我应该说&#34;左值&#34;而不是&#34;变量&#34;,但我觉得将可变存储位置描述为&#34;变量&#34;更为明确。)
所以我们有变量:
int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;
变量ip1
包含指针。 &
运算符将i
转换为指针,并将指针值指定给ip1
。因此ip1
包含指向i
的指针。
变量ip2
包含指针。 &
运算符将j
转换为指针,并将该指针指定给ip2
。因此ip2
包含指向j
的指针。
int **ipp = &ip1;
变量ipp
包含一个指针。 &
运算符将变量ip1
转换为指针,并将指针值分配给ipp
。因此ipp
包含指向ip1
的指针。
到目前为止,让我们总结一下这个故事:
i
包含5 j
包含6 ip1
包含&#34;指向i
&#34; ip2
包含&#34;指向j
&#34; ipp
包含&#34;指向ip1
&#34; 现在我们说
*ipp = ip2;
*
运算符将指针变回变量。我们获取ipp
的值,这是&#34;指向ip1
的指针并将其转换为变量。什么变量? ip1
当然!
因此,这只是另一种说法
ip1 = ip2;
所以我们获取ip2
的值。它是什么? &#34;指向j
&#34;的指针。我们将指针值指定给ip1
,因此ip1
现在是指向j
&#34;
我们只改变了一件事:ip1
的价值:
i
包含5 j
包含6 ip1
包含&#34;指向j
&#34; ip2
包含&#34;指向j
&#34; ipp
包含&#34;指向ip1
&#34; 为什么
ipp
仍然指向ip1
而不是ip2
?
分配给它时,变量会发生变化。计算作业;变量没有比赋值更多的变化!您首先要分配到i
,j
,ip1
,ip2
和ipp
。然后,您可以分配到*ipp
,我们已经看到它与&#34;分配给ip1
&#34;相同。由于您没有第二次分配给ipp
,因此它没有变化!
如果您想更改ipp
,那么您必须实际分配到ipp
:
ipp = &ip2;
例如。
答案 4 :(得分:12)
我个人的观点是箭头指向这种方式的图片或指针更难以理解的图片。它确实使它们看起来像一些抽象的,神秘的实体。他们不是。
与计算机中的其他内容一样,指针是数字。名称“指针”只是说“包含地址的变量”的一种奇特方式。
因此,让我通过解释计算机的实际运作方式来解决问题。
我们有一个int
,它的名称为i
,值为5.它存储在内存中。就像存储在内存中的所有内容一样,它需要一个地址,否则我们将无法找到它。假设i
最终在地址0x12345678处,其伙伴j
的值为6,就在它之后。假设32位CPU,其中int是4个字节,指针是4个字节,那么变量存储在物理内存中,如下所示:
Address Data Meaning
0x12345678 00 00 00 05 // The variable i
0x1234567C 00 00 00 06 // The variable j
现在我们想指出这些变量。我们创建一个指向int,int* ip1
和一个int* ip2
的指针。就像计算机中的所有内容一样,这些指针变量也会在内存中的某处分配。让我们假设它们在j
之后立即到达内存中的下一个相邻地址。我们将指针设置为包含先前分配的变量的地址:ip1=&i;
(“将i的地址复制到ip1”)和ip2=&j
。这些行之间会发生什么:
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是否包含int
或int*
。不同之处在于我们的程序选择如何使用存储在此地址的内容。 (我们程序的任务实际上只是告诉CPU如何处理这些数字。)
然后我们用int** ipp = &ip1;
添加另一个间接层。再一次,我们只获得了一大块内存:
Address Data Meaning
0x12345688 12 34 56 80 // The variable ipp
这种模式似乎很熟悉。还有一个包含数字的4个字节的块。
现在,如果我们有一个上述虚构的小RAM的内存转储,我们可以手动检查这些指针指向的位置。我们查看存储在ipp
变量地址的内容,找到内容0x12345680。这当然是存储ip1
的地址。我们可以去那个地址,检查那里的内容,找到i
的地址,最后我们可以到那个地址找到5号。
因此,如果我们获取ipp的内容*ipp
,我们将得到指针变量ip1
的地址。通过编写*ipp=ip2
我们将ip2复制到ip1,它等同于ip1=ip2
。无论哪种情况,我们都会得到
Address Data Meaning
0x12345680 12 34 56 7C // The variable ip1
0x12345684 12 34 56 7C // The variable ip2
(这些示例是为大端CPU提供的)
答案 5 :(得分:8)
注意作业:
ipp = &ip1;
结果ipp
指向ip1
。
因此,ipp
指向ip2
,我们应该以类似的方式进行更改,
ipp = &ip2;
我们显然没有这样做。相反,我们正在更改ipp
指向的地址处的值
通过做下面的
*ipp = ip2;
我们只是替换ip1
中存储的值。
ipp = &ip1
,表示*ipp = ip1 = &i
,
现在,*ipp = ip2 = &j
。
因此,*ipp = ip2
与ip1 = ip2
基本相同。
答案 6 :(得分:5)
ipp = &ip1;
以后的分配没有更改ipp
的值。这就是为什么它仍指向ip1
。
您对*ipp
所做的事情,即使用ip1
,并未改变ipp
指向ip1
的事实。
答案 7 :(得分:5)
因为当你说
时*ipp = ip2
你说的是ipp
'指向的对象指向ip2
所指向的记忆方向。
您并不是说ipp
指向ip2
。
答案 8 :(得分:5)
我的问题是:为什么在第二张图片中,ipp仍然指向ip1而不是ip2?
你放了漂亮的照片,我将尝试制作漂亮的ascii艺术品:
就像@ Robert-S-Barnes在他的回答中所说:忘记指针,什么指向什么,但从内存的角度思考。基本上,int*
表示它包含变量的地址,int**
包含包含变量地址的变量的地址。然后,您可以使用指针的代数来访问值或地址:&foo
表示address of foo
,*foo
表示value of the address contained in foo
。
因此,由于指针是关于处理内存的,所以实际制作“有形”的最佳方法是显示指针代数对内存的作用。
所以,这是你的程序的内存(为了示例的目的而简化):
name: i j ip1 ip2 ipp
addr: 0 1 2 3 4
mem : [ | | | | ]
执行初始代码时:
int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;
这是你的记忆的样子:
name: i j ip1 ip2
addr: 0 1 2 3
mem : [ 5| 6| 0| 1]
您可以看到ip1
和ip2
获取i
和j
的地址,ipp
仍然不存在。
不要忘记地址只是以特殊类型存储的整数。
然后你声明并定义ipp
,例如:
int **ipp = &ip1;
所以这是你的记忆:
name: i j ip1 ip2 ipp
addr: 0 1 2 3 4
mem : [ 5| 6| 0| 1| 2]
然后,您正在更改ipp
中存储的地址所指向的值,即。{
存储在ip1
中的地址:
*ipp = ip2;
程序的内存是
name: i j ip1 ip2 ipp
addr: 0 1 2 3 4
mem : [ 5| 6| 1| 1| 2]
N.B。:因为int*
是一种特殊类型,我宁愿总是避免在同一行上声明多个指针,因为我认为int *x;
或int *x, *y;
表示法可能会产生误导。我更喜欢写int* x; int* y;
HTH
答案 9 :(得分:4)
如果将解引用运算符*
添加到指针,则从指针重定向到指向对象。
示例:
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`
因此:
*ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself
// therefore, `ipp` still points to `ip1` afterwards.
答案 10 :(得分:3)
如果您希望ipp
指向ip2
,则必须说ipp = &ip2;
。但是,这会使ip1
仍然指向i
。
答案 11 :(得分:3)
你刚开始设置,
ipp = &ip1;
现在取消引用它,
*ipp = *&ip1 // Here *& becomes 1
*ipp = ip1 // Hence proved
答案 12 :(得分:3)
考虑每个变量如下所示:
type : (name, adress, value)
所以你的变量应该像这样表示
int : ( i , &i , 5 ); ( j , &j , 6); ( k , &k , 5 )
int* : (ip1, &ip1, &i); (ip1, &ip1, &j)
int** : (ipp, &ipp, &ip1)
由于ipp
的值为&ip1
所以结构为:
*ipp = ip2;
将地址&ip1
的值更改为ip2
的值,这意味着ip1
已更改:
(ip1, &ip1, &i) -> (ip1, &ip1, &j)
但ipp
仍在:
(ipp, &ipp, &ip1)
因此ipp
的值仍为&ip1
,这意味着它仍然指向ip1
。
答案 13 :(得分:1)
因为您正在更改*ipp
的指针。这意味着
ipp
(可变名称)----进去。ipp
内是[{1}}。ip1
所以请转到(内部地址)*ipp
。 现在我们在ip1
。
ip1
(即*ipp
)= ip1
2。
ip
包含ip2
的地址。所以j
内容将被包含ip2(即j的地址)替换,
我们不会更改ip1
内容。
而已。
答案 14 :(得分:1)
*ipp = ip2;
暗示:
将ip2
分配给ipp
指向的变量。所以这相当于:
ip1 = ip2;
如果您希望将ip2
的地址存储在ipp
中,只需执行以下操作:
ipp = &ip2;
现在ipp
指向ip2
。
答案 15 :(得分:0)
ipp
可以保存(即指向)指向类型对象的指针的值。当你这样做
ipp = &ip2;
然后ipp
包含变量(指针){<1}} 的地址,即指针指针<的ip2
/ em>的。现在,第二张图片中&ip2
的箭头将指向ipp
。
Wiki说:
ip2
运算符是一个解引用运算符,对指针变量进行操作,并返回与指针地址处的值等效的l-value(变量)。这称为取消引用指针。
在*
上应用*
运算符将其转换为指向ipp
类型的l值。取消引用的l值int
是指向*ipp
的类型,它可以保存int
类型数据的地址。声明后
int
ipp = &ip1;
持有ipp
的地址,ip1
持有(指向)*ipp
的地址。您可以说i
是*ipp
的别名。 ip1
和**ipp
都是*ip1
的别名
通过做
i
*ipp = ip2;
和*ipp
都指向同一位置,但ip2
仍然指向ipp
。
ip1
实际上是将*ipp = ip2;
(ip2
的地址)的内容复制到j
(因为ip1
是别名*ipp
),实际上使指针ip1
和ip1
指向同一个对象(ip2
)。
因此,在第二个图中,j
和ip1
的箭头指向ip2
,而j
仍然指向ipp
,因为没有修改是完成以更改ip1
的值。