指针指针的指针
我正在关注这个教程如何指针指针工作。
让我引用有关的一段话:
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的内存转储,我们可以手动检查这些指针指向的位置。 我们偷看存储在ipp
variables地址的内容,并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
上应用*
运算符将其指向int
types的指针的l值。 解除引用的l值*ipp
是指向int
指针types,它可以保存int
types数据的地址。 声明之后
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
的值 。