指针到指针澄清

我正在跟踪这个 教程关于 指向指针的指针是如何工作的。

让我引用相关段落:


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

现在我们可以开始了

    int **ipp = &ip1;

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

enter image description here

如果我们说

    *ipp = ip2;

我们已经改变了指向 ipp(即 ip1)的指针,以包含 ip2的一个副本,因此它(ip1)现在指向 j:

enter image description here


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

12444 次浏览

因为您更改了 ipp指向的值,而不是 ipp的值。因此,ipp仍然指向 ip1(ipp的值) ,ip1的值现在与 ip2的值相同,所以它们都指向 j

这个:

*ipp = ip2;

等同于:

ip1 = ip2;

ipp可以保存(即指向) 指针指向指针类型对象的值

ipp = &ip2;

那么 ipp包含 变量(指针)的地址 ip2,它是(&ip2)类型 指针指向指针。现在第二张图片中的 ipp箭头将指向 ip2

维基百科说:
*操作符是一个操作指针变量的解引用运算符,返回一个相当于指针地址的值的
< strong > l-value (变量)。这就是所谓的解引用指针。

ipp上应用 *运算符将其解引用为 指向 int的指针类型的 l 值。取消引用的 l 值 *ipp指向 int的指针类型的,它可以保存 int类型数据的地址。在录完口供之后

ipp = &ip1;

ipp持有 ip1的地址,而 *ipp持有(指向) i的地址。你可以说 *ippip1的别名。**ipp*ip1都是 i的别名。
通过行动

 *ipp = ip2;

*ippip2都指向同一个位置,但是 ipp仍然指向 ip1

*ipp = ip2;实际上做的是将 ip2的内容(j的地址)复制到 ip1(因为 *ippip1的别名) ,实际上使得指针 ip1ip2指向同一个对象(j)。
因此,在第二个图中,ABC0和 ABC1的箭头指向 ABC2,而 ipp仍然指向 ABC0,因为没有做任何修改来改变 ipp的值

如果将解引用运算符 *添加到指针,就会从指针重定向到指向的对象。

例子:

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.
ipp = &ip1;

后面的赋值没有改变 ipp的值。这就是为什么它仍然指向 ip1

你对 *ipp所做的,也就是对 ip1所做的,并不改变 ipp指向 ip1的事实。

注意作业:

ipp = &ip1;

结果 ipp指向 ip1

所以对于 ipp指向 ip2,我们应该以类似的方式改变,

ipp = &ip2;

我们显然没有这样做。相反,我们正在改变的 按地址计价指向 ipp
通过以下方式

*ipp = ip2;

我们只是替换存储在 ip1中的值。

ipp = &ip1意思是 *ipp = ip1 = &i,
现在,*ipp = ip2 = &j
所以 *ipp = ip2ip1 = ip2本质上是一样的。

先别管什么指向类比了。指针真正包含的是内存地址。&是“ address of”操作符——也就是说,它返回对象内存中的地址。*操作符为您提供指针所引用的对象,也就是说,给定一个包含地址的指针,它返回该内存地址的对象。所以当你执行 *ipp = ip2时,你要做的就是 *ipp获取在 ipp中保存的地址 ip1中的对象,然后把存储在 ip2中的值赋给 ip1ip2j的地址。

很简单
&-> 地址
*-> 价值

如果你想让 ipp指向 ip2,你必须说 ipp = &ip2;。然而,这将使 ip1仍然指向 i

因为当你说

*ipp = ip2

你说的是“由 ipp指向的对象”来指向 ip2所指向的内存的方向。

你不是说 ipp指向 ip2

从你开始,

ipp = &ip1;

现在取消对它的引用,

*ipp = *&ip1 // Here *& becomes 1
*ipp = ip1   // Hence proved

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

你放了很多漂亮的照片,我要试着做一些漂亮的艺术品:

就像@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]

在那里你可以看到 ip1ip2得到的地址 ijipp仍然不存在。 不要忘记,地址只是用特殊类型存储的整数。

然后声明并定义 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]

注意: 由于 int*是一种特殊类型,我总是倾向于避免在同一行上声明多个指针,因为我认为 int *x;int *x, *y;符号可能会产生误导。我更喜欢写 int* x; int* y;

高温

希望这段代码能帮上忙。

#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);
}

输出:

enter image description here

考虑下面这样表示的每个变量:

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

我个人的观点是,带有箭头指向这个方向或那个方向的图片会使指针更难理解。这确实让他们看起来像是一些抽象的,神秘的实体。他们不是。

就像计算机中的其他东西一样,指针是 数字。“指针”这个名字只是表示“包含地址的变量”的一种花哨的方式。

因此,让我来解释一下计算机实际上是如何工作的。

我们有一个 int,它的名字是 i,值是5。它存储在内存中。就像存储在内存中的所有东西一样,它需要一个地址,否则我们就找不到它。假设 i最终位于地址0x12345678,其值为6的伙伴 j紧随其后。假设一个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* ip1int* 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是否包含 intint*。区别在于我们的程序如何选择使用存储在这个地址的内容。(我们程序的任务实际上就是告诉 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 的)

和 C 标签中的大多数初学者问题一样,这个问题可以通过回到第一原则来回答:

  • 指针是一种值。
  • 变量包含一个值。
  • &操作符将变量转换为指针。
  • *操作符将指针转换为变量。

(从技术上讲,我应该说“ lvalue”而不是“ variable”,但我觉得将可变存储位置描述为“ variable”更为明确。)

所以我们有变量:

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包含“指向 i的指针”
  • ip2包含“指向 j的指针”
  • ipp包含“指向 ip1的指针”

现在我们说

*ipp = ip2;

*操作符将指针返回到变量。我们获取 ipp的值,它是“指向 ip1的指针,并将其转换为一个变量。什么变量?当然是 ip1

所以这只是另一种说法

ip1 = ip2;

所以我们得到 ip2的值。这是什么?“指向 j的指针”。我们将指针值赋给 ip1,因此 ip1现在是“指向 j的指针”

我们只改变了一件事: ip1的价值:

  • i包含5个
  • j包含6个
  • ip1包含“指向 j的指针”
  • ip2包含“指向 j的指针”
  • ipp包含“指向 ip1的指针”

为什么 ipp仍然指向 ip1而不是 ip2

当您为变量赋值时,它会发生变化。计算赋值; 对变量的更改不能超过赋值!首先分配到 ijip1ip2ipp。然后分配给 *ipp,正如我们所看到的,它的意思与“分配给 ip1”相同。因为你没有分配到 ipp第二次,它没有改变!

如果你想改变 ipp,那么你必须实际上指定 ipp:

ipp = &ip2;

比如说。

因为您正在更改 *ipp的指针

  1. ipp(变量名)——进入。
  2. ipp内是 ip1的地址。
  3. 现在去(里面的地址) ip1

现在我们在 ip1*ipp(即 ip1) = ip2。
ip2包含 j.so ip1内容将被包含 ip2(即 j 的地址)所取代, 我们不会改变 ipp的内容。 就是这样。

*ipp = ip2;暗示:

ip2赋给 ipp所指向的变量。因此这等价于:

ip1 = ip2;

如果您希望 ip2的地址存储在 ipp中,只需:

ipp = &ip2;

现在 ipp指向 ip2