int x=10;
int *y=&x;
int *z= &y;
printf("%d\n",*(*z));
我想了解我们为什么需要int **z
?这里有什么问题?
这是一个方便的表格,显示了基于您的声明的各种表达式的类型:
Expression Type Value
---------- ---- -----
x int 10
&x int * address of x
y int * address of x
&y int ** address of y
由于表达式 &y
的类型是int **
,因此您需要声明z
为int **
以保存该值(因此表达式&z
将具有 type int ***
)。
那么,这有什么关系呢?指针就是指针,指针就是指针,对吧?
嗯,不一定。指向不同类型的指针可能有不同的大小和表示。来自2011年在线标准:
6.2.5 类型
...
28 指向的指针void
应具有与指向字符类型的指针相同的表示和对齐要求。48)同样,指向兼容类型的合格或不合格版本的指针应具有相同的表示和对齐要求。所有指向结构类型的指针都应具有彼此相同的表示和对齐要求。所有指向联合类型的指针都应具有彼此相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。
48) 相同的表示和对齐要求意味着作为函数的参数、函数的返回值和联合成员的可互换性。
在桌面和服务器世界中,大多数架构都是这样的,所有指针类型都具有相同的大小和表示形式,但是有很多奇怪的架构并非如此。您可以拥有一个字寻址架构,其中多个char
值被打包到一个字中;achar *
需要有几个额外的位来索引该单词以获得特定char
值。您可以拥有哈佛架构,其中数据的地址行少于代码的地址行(反之亦然),因此指向对象类型的指针和指向函数类型的指针将具有不同的大小。
这里还有另一个问题:指针算法。由于y
指向一个类型的对象int
,表达式y++
将前进y
到指向下一个类型的对象int
。由于z
指向一个类型的对象int *
,表达式z++
将前进z
到指向下一个类型的对象int *
。如果int
和int *
具有不同的大小,那么y
和z
将被推进不同的数量。
简而言之,类型很重要,即使对于指针也是如此。
x
是一个类型的变量int
,意味着它拥有一个整数值。的类型10
是int
。
y
是 type 的变量int*
,这意味着它保存一个值,该值表示存储 type 值的内存地址int
。一元运算符&
产生其参数的内存地址,并且通常包含类型信息,因此x
int 所持有的任何地址的类型都是x
will be int*
。
z
是一个类型变量int**
,这意味着它保存一个值,代表一个类型变量的地址int*
。&y
产生 的内存地址y
,因此结果的类型为int**
.
包含类型信息的原因不仅仅是严格/静态类型的问题;执行指针算术的方式(例如p++
,等同于p += 1
)将根据所指向的类型的大小而有所不同。因此,对于指向 4 字节值的指针,将指针递增 1 将导致指针指向原始位置后四个字节的位置,而对于指向 1 字节值的指针,将指针递增 1 将导致指向原始位置后一个字节的位置的指针。本质上,将整数添加到指针会导致pointer_value + integer*sizeof(underlying_type)
.
当然,您可以对指针类型使用内联强制转换来更改整数的因子,但这是另一个话题。
让我们一步一步来,好吗?
int x = 10;
此代码在某个内存位置创建一个int
变量并将其值设置为 10。该变量可以通过方便的名称访问x
。
int *y=&x;
此代码在某个内存位置创建一个指向int
变量的指针,并将其值设置为x
变量的地址。这个变量可以通过方便的名称访问y
。如果您打印了该值,或者y
您将获得x
. 如果您取消引用y
(通过执行*y
)并打印结果,那么您将得到 10
int *z= &y; // error
这不会编译:z
是一个指向int
. 由于y
已经是指向 的指针int
,因此当您获取其地址(通过执行&y
)时,您得到的是指向 的指针int
。这就是为什么你需要双星。
int **z = &y;
现在这将编译。此代码创建一个指向int
变量指针的指针,并将其值设置为变量的地址y
。如果您z
现在打印该值,您将获得y
. 如果您取消引用z
(通过执行*z
)并打印结果,您将获得x
.
因此,您需要双星。
x = 10(x 地址处的内存保存 hte 值 10)。
y = x 的地址。如果你取消引用 y (*y),它会显示 10。如果你在 y 的地址打印值,它会显示 x 的地址。如果你打印 y 的地址,它会告诉你一些与这 3 件事不同的东西。
z = 到 y 的地址。请记住,当您现在取消引用 z (*z) 时,它将为您提供保存在 y 处的值,即 x 的地址。当您对此进行双重取消引用时,它会为您提供 x 地址处的值,这就是您想要的(因此,为什么需要双重取消引用)。
在 c/c++ 中,从右到左阅读变量声明有助于更好地理解它们的类型。在您的代码段中,这将产生以下描述:
x
是一个int
y
是一个指针int
z
是一个指针int
在第一行中,您分配10
给x
. 因为10
是int
,所以成功没有问题。
在第二行中,您正在分配x
to的地址y
。由于指针保存地址,x
is anint
并且y
是指向 an 的指针int
,因此这一行也成功。
第三行是你遇到障碍的地方。您正在分配y
to的地址z
。通过获取指向 an 的指针的地址,z
期望得到指向 an 的地址。int
int
正如 DardDust 所提到的,您需要更改您的定义z
,使其成为指向整数指针的指针。
int **z = &y;
一旦你明白了,就会更容易理解最后一行和你的第一个问题。
该printf
函数需要一个,int
但您正在使用z
. 为了获得该int
值,您需要进行双重取消引用。第一个解引用将返回一个指向 an 的指针int
,第二个是int
它本身。
x
int
是存储在内存某处的类型变量。&x
返回存储的内存位置的地址x
。那是一个指针。因此返回的类型&x
是int *
。现在,这个指针再次使用变量存储在内存中的某个地方y
。z
via的地址&y
。该变量y
已经存储了一个指针,所以现在您有一个指向 int 指针的指针,即int **
.printf
语句中,您需要将该链爬回:
*z
读取指向的内存z
。既然z
有 的内存位置y
,就*z
返回 的内存位置y
。y
然后你读取(间接)指向的内存。并y
点到x
,让你读完x
。有点令人费解,但在使用 C 时理解这一点至关重要。
每次您需要时,a pointer to
您都需要将 a 添加*
到您的类型中:
int x=10;
声明一个 int 并为其赋值10
-> 不a pointer to
,所以不*
int *y=&x;
声明一个指向 int 的指针并保存并分配它的x
存储地址
->1
a pointer to
所以1
*
最后
int **z= &y;
声明一个指向 int 指针的指针,并为其分配y
存储地址
->2
a pointer to
所以2
*