指针指针的指针
我正在关注这个教程如何指针指针工作。
让我引用有关的一段话:
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 ?
忘记一下关于指点的比喻。 一个指针真正包含的是一个内存地址。 &是“操作符的地址”,即返回对象内存中的地址。 *运算符为您提供一个指针引用的对象,即给定一个包含地址的指针,它将返回该内存地址处的对象。 所以当你执行*ipp = ip2 ,你正在做的是*ipp获取ipp中保存的ip1地址处的对象,然后分配给ip1存储在ip2的值,这是j的地址。
只是
& – >地址
* – >价值在
因为你改变了ipp指向的值而不是ipp的值。 因此, ipp仍然指向ip1 ( ip1的值), ip1的值现在与ip2的值相同,所以它们都指向j 。
这个:
*ipp = ip2;
是相同的:
ip1 = ip2;
希望这段代码可以帮助。
#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); }
它输出:

像C标签中的大多数初学者问题一样,这个问题可以通过回到第一个原则来回答:
- 指针是一种价值。
- 一个variables包含一个值。
-
&运算符将一个variables变成一个指针。 -
*运算符将一个指针变成一个variables。
(从技术上说,我应该说“左值”而不是“variables”,但是我觉得将可变的存储位置描述为“variables”会更清楚)。
所以我们有variables:
int i = 5, j = 6; int *ip1 = &i, *ip2 = &j;
variablesip1 包含一个指针。 &运算符将i变成一个指针,并将该指针赋值给ip1 。 所以ip1 包含一个指向i的指针。
variablesip2 包含一个指针。 &运算符将j转换为一个指针,并将该指针分配给ip2 。 所以ip2 包含一个指向j的指针。
int **ipp = &ip1;
variablesipp包含一个指针。 &运算符将variablesip1转换为一个指针,并将该指针值赋给ipp 。 所以ipp包含一个指向ip1的指针。
我们来总结一下这个故事:
-
i包含5 -
j包含6 -
ip1包含“指向i” -
ip2包含“指向j指针” -
ipp包含“指向ip1指针”
现在我们说
*ipp = ip2;
*运算符将一个指针变回一个variables。 我们获取ipp的值,它是“指向ip1指针并将其变成一个variables。什么variables? ip1当然!
所以这只是另一种说法
ip1 = ip2;
所以我们取ip2的值。 它是什么? “指向j ”。 我们把这个指针值赋给ip1 ,所以ip1现在是“指向j指针”
我们只改变了一件事: ip1的值:
-
i包含5 -
j包含6 -
ip1包含“指向j指针” -
ip2包含“指向j指针” -
ipp包含“指向ip1指针”
为什么
ipp仍然指向ip1而不是ip2?
分配给它的variables会发生变化。 统计任务; variables的变化不能超过赋值! 您先分配给i , j , ip1 , ip2和ipp 。 然后分配给*ipp ,正如我们所看到的那样,意思是“分配给ip1 ”。 既然你没有第二次分配给ipp ,它没有改变!
如果你想改变ipp那么你将不得不实际分配给ipp :
ipp = &ip2;
例如。
我个人的看法是,用箭头指向这个方向的图片或者指点难以理解的图片。 这确实使他们看起来像一些抽象的,神秘的实体。 他们不是。
就像电脑里的其他东西一样,指针也是数字 。 “指针”这个名字只是说“包含地址的variables”的一种奇特的方式。
因此,让我通过解释计算机是如何工作的。
我们有一个int ,它有名字i和值5.这是存储在内存中。 像存储在内存中的所有东西一样,它需要一个地址,否则我们将无法find它。 比方说, i结束了在地址0x12345678,其值为6的好友j刚结束。 假设一个32位的CPU,其中int是4个字节,指针是4个字节,那么这些variables就像这样存储在物理内存中:
Address Data Meaning 0x12345678 00 00 00 05 // The variable i 0x1234567C 00 00 00 06 // The variable j
现在我们要指出这些variables。 我们创build一个指向int, int* ip1和一个int* ip2指针。 像计算机中的所有内容一样,这些指针variables也被分配到内存中的某处。 让我们假设他们在j之后紧接着在内存中的下一个相邻地址。 我们设置指针来包含以前分配的variables的地址: 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* 。 不同的是我们的程序select使用存储在这个地址的内容。 (我们程序的任务实际上只是告诉CPU如何处理这些数字。)
然后我们用int** ipp = &ip1;添加另一个级别的间接int** ipp = &ip1; 。 再次,我们只是得到一大块内存:
Address Data Meaning 0x12345688 12 34 56 80 // The variable ipp
模式看起来很熟悉。 还有另一个包含一个数字的4个字节块。
现在,如果我们有上述虚构小RAM的内存转储,我们可以手动检查这些指针指向的位置。 我们偷看存储在ippvariables地址的内容,并find内容0x12345680。 这当然是ip1存储的地址。 我们可以去那个地址,查看那里的内容,然后findi的地址,最后我们可以去那个地址find数字5。
所以如果我们把ipp的内容, *ipp ,我们将得到指针variablesip1的地址。 通过写*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
(这些例子是给一个big endian CPU的)
注意作业:
ipp = &ip1;
结果ipp指向ip1 。
所以为了ipp指向ip2 ,我们应该以类似的方式改变,
ipp = &ip2;
我们显然没有这样做。 相反,我们正在改变ipp指出的地址的价值 。
通过做下面的事情
*ipp = ip2;
我们只是replaceip1存储的值。
ipp = &ip1 ,意思是*ipp = ip1 = &i ,
现在, *ipp = ip2 = &j 。
所以, *ipp = ip2与ip1 = ip2基本相同。
ipp = &ip1;
后来的任务没有改变ipp的价值。 这就是为什么它仍然指向ip1 。
你用*ipp做什么,即用ip1 ,不会改变ipp指向ip1的事实。
因为当你说
*ipp = ip2
你说' ipp指向的对象'指向ip2指向的内存的方向。
你不是说ipp指向ip2 。
如果将反引用操作符*添加到指针,则可以将指针redirect到指向的对象。
例子:
int i = 0; int *p = &i; // <-- NB 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.
我的问题是:为什么在第二张图片中,ipp仍然指向ip1而不是ip2?
你放了很好的照片,我会尽量做好ascii艺术:
就像@罗伯特·S·巴恩斯在他的回答中所说: 忘记指针 ,什么指向什么,但是从记忆angular度来看。 基本上,一个int*表示它包含一个variables的地址,一个int**包含一个包含variables地址的variables的地址。 那么你可以使用指针的代数来访问值或地址: &foo表示&foo address of foo , *foo表示value of the address contained in foo 。
所以,作为指针是关于内存的处理,实际做出“有形”的最好方法是展示指针代数对内存的影响。
所以,这里是你的程序的内存(简单的例子):
name: ij ip1 ip2 ipp addr: 0 1 2 3 4 mem : [ | | | | ]
当你做你的初始代码:
int i = 5, j = 6; int *ip1 = &i, *ip2 = &j;
这里是你的记忆如何:
name: ij ip1 ip2 addr: 0 1 2 3 mem : [ 5| 6| 0| 1]
在那里你可以看到ip1和ip2获取i和j的地址, ipp依然不存在。 不要忘记,地址只是一个特殊types的整数。
然后你声明和定义ipp如:
int **ipp = &ip1;
所以这里是你的记忆:
name: ij ip1 ip2 ipp addr: 0 1 2 3 4 mem : [ 5| 6| 0| 1| 2]
然后,您将更改ip地址中存储的地址所指向的值。
*ipp = ip2;
程序的内存是
name: ij ip1 ip2 ipp addr: 0 1 2 3 4 mem : [ 5| 6| 1| 1| 2]
注意:由于int*是一个特殊的types,我宁愿总是避免在同一行上声明多个指针,因为我认为int *x; 或者int *x, *y; 记法可能会引起误解。 我更喜欢写int* x; int* y; int* x; int* y;
HTH
如果你想要ipp指向ip2 ,你必须说ipp = &ip2; 。 但是,这将使ip1仍然指向i 。
你一开始设定,
ipp = &ip1;
现在把它解释为,
*ipp = *&ip1 // Here *& becomes 1 *ipp = ip1 // Hence proved
考虑如下所示的每个variables:
type : (name, adress, value)
所以你的variables应该像这样表示
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;
将addess &ip1的值更改为ip2的值,这意味着ip1被更改:
(ip1, &ip1, &i) -> (ip1, &ip1, &j)
但是ipp依然:
(ipp, &ipp, &ip1)
所以ipp仍然是&ip1的值,这意味着它仍然指向ip1 。
因为你正在改变*ipp的指针。 它的意思是
-
ipp(可变名)—-进去吧。 - 里面的ip是
ip1地址。 - 现在
*ipp所以去(内部地址)ip1。
现在我们在ip1 。 *ipp (即ip1 )= ip 2。
ip2包含ip2地址ip1内容将被replace为包含ip2(即地址j),我们不会改变ipp内容。 而已。
*ipp = ip2; 暗示:
将ip2分配给ipp指向的variables。 所以这相当于:
ip1 = ip2;
如果你想把ip2的地址存储在ipp ,只需要:
ipp = &ip2;
现在ipp指向ip2 。
ipp可以保存(即指向) 指针types对象的指针 。 当你这样做
ipp = &ip2;
那么ipp包含variables(指针) ip2的地址 ,这是指向指针的types指针的 ( &ip2 )。 现在第二张图中的ipp箭头指向ip2 。
维基说:
*运算符是一个解引用运算符对指针variables进行操作,并返回一个等价于指针地址值的l值 (variables)。 这被称为解引用指针。
在ipp上应用*运算符将其指向inttypes的指针的l值。 解除引用的l值*ipp是指向int指针types,它可以保存inttypes数据的地址。 声明之后
ipp = &ip1;
ipp持有ip1的地址, *ipp持有(指向) i的地址。 你可以说*ipp是ip1的别名。 **ipp和*ip1都是i别名。
通过做
*ipp = ip2;
*ipp和ip2都指向相同的位置,但ipp仍然指向ip1 。
What *ipp = ip2; 实际上是它将ip2的内容( j的地址)复制到ip1 (因为*ipp ip是ip1的别名),实际上使得指针ip1和ip2指向相同的对象( j )。
因此,在第二个图中, ip1和ip2箭头指向j而ipp仍然指向ip1因为没有修改来改变ipp的值 。

