指针澄清的指针

时间:2014-02-06 13:56:57

标签: c pointers

我正在关注这个tutorial关于指向指针的指针是如何工作的。

让我引用相关段落:


    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

16 个答案:

答案 0 :(得分:142)

忘记关于指向类比的第二个问题。指针真正包含的是内存地址。 &是“地址”运算符 - 即它返回对象内存中的地址。 *运算符为您提供指针引用的对象,即给定包含地址的指针,它返回该存储器地址处的对象。因此,当您执行*ipp = ip2时,您正在做的是*ipp将对象放在ipp ip1中的地址,然后分配给ip1存储在ip2中的值,即j的地址。

<强>简单地
& - &gt;的地址 * - &gt;值

答案 1 :(得分:43)

因为您更改了ipp指向的值而不是ipp的值。因此,ipp仍然指向ip1ipp的值),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);
}

输出:

enter image description here

答案 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

分配给它时,变量会发生变化。计算作业;变量没有比赋值更多的变化!您首先要分配到ijip1ip2ipp。然后,您可以分配到*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是否包含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提供的)

答案 5 :(得分:8)

注意作业:

ipp = &ip1;

结果ipp指向ip1

因此,ipp指向ip2,我们应该以类似的方式进行更改,

ipp = &ip2;

我们显然没有这样做。相反,我们正在更改ipp指向的地址处的值 通过做下面的

*ipp = ip2;

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

ipp = &ip1,表示*ipp = ip1 = &i
现在,*ipp = ip2 = &j
因此,*ipp = ip2ip1 = 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]

您可以看到ip1ip2获取ij的地址,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的指针。这意味着

  1. ipp(可变名称)----进去。
  2. ipp内是[{1}}。
  3. 的地址
  4. 现在ip1所以请转到(内部地址)*ipp
  5. 现在我们在ip1ip1(即*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),实际上使指针ip1ip1指向同一个对象(ip2)。
因此,在第二个图中,jip1箭头指向ip2,而j仍然指向ipp,因为没有修改是完成以更改ip1 的值。