指针指针的指针

我正在关注这个教程如何指针指针工作。

让我引用有关的一段话:


int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j; 

现在我们可以设置

  int **ipp = &ip1; 

ipp指向ip1 ,指向i*ippip1**ippi或5.我们可以用我们熟悉的方框和箭头符号来说明情况,如下所示:

在这里输入图像说明

如果那我们说

  *ipp = ip2; 

我们已经改变了由ipp指向的指针(即ip1 )以包含ip2的副本,以便它( ip1 )现在指向j

在这里输入图像说明


我的问题是:为什么在第二张图片中, ipp仍然指向ip1而不是ip2

忘记一下关于指点的比喻。 一个指针真正包含的是一个内存地址。 &是“操作符的地址”,即返回对象内存中的地址。 *运算符为您提供一个指针引用的对象,即给定一个包含地址的指针,它将返回该内存地址处的对象。 所以当你执行*ipp = ip2 ,你正在做的是*ipp获取ipp中保存的ip1地址处的对象,然后分配给ip1存储在ip2的值,这是j的地址。

只是
& – >地址
* – >价值在

因为你改变了ipp指向的值而不是ipp的值。 因此, ipp仍然指向ip1ip1的值), 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的变化不能超过赋值! 您先分配给ijip1ip2ipp 。 然后分配给*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是否包含intint* 。 不同的是我们的程序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 = ip2ip1 = 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] 

在那里你可以看到ip1ip2获取ij的地址, 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的指针。 它的意思是

  1. ipp (可变名)—-进去吧。
  2. 里面的ip是ip1地址。
  3. 现在*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的地址。 你可以说*ippip1的别名。 **ipp*ip1都是i别名。
通过做

  *ipp = ip2; 

*ippip2都指向相同的位置,但ipp仍然指向ip1

What *ipp = ip2; 实际上是它将ip2的内容( j的地址)复制到ip1 (因为*ipp ip是ip1的别名),实际上使得指针ip1ip2指向相同的对象( j )。
因此,在第二个图中, ip1ip2箭头指向jipp仍然指向ip1因为没有修改来改变ipp的值