C 中的声明是以表达式为中心的,这意味着声明的形式应该与可执行代码中的表达式形式相匹配。
例如,假设我们有一个指向名为 的整数的指针p
。我们想要访问 指向的整数值p
,所以我们取消引用指针,如下所示:
x = *p;
表达式 的类型*p
是int
; 因此,声明p
的形式为
int *p;
在这个声明中,int
是类型说明符,*p
是声明者。声明器引入了被声明对象的名称 ( p
),以及类型说明符未提供的附加类型信息。在这种情况下,附加类型信息是p
指针类型。声明可以读作“p
是指向类型的指针int
”或“p
是指向类型的指针int
”。我更喜欢使用第二种形式,其他人更喜欢第一种。
这是 C 和 C++ 语法的一个意外,您可以将该声明编写为int *p;
or 或int* p;
. 在这两种情况下,它都被解析为int (*p);
-- 换句话说,*
总是与变量名相关联,而不是类型说明符。
现在假设我们有一个指向 的指针数组int
,并且我们想要访问数组的第 i 个元素所指向的值。我们对数组进行下标并取消引用结果,如下所示:
x = *ap[i]; // parsed as *(ap[i]), since subscript has higher precedence
// than dereference.
同样,表达式 *ap[i]
的类型是int
,所以声明ap
是
int *ap[N];
其中声明*ap[N]
符表示这ap
是一个指向 的指针数组int
。
只是为了说明问题,现在假设我们有一个指向指针的指针int
并想要访问该值。同样,我们尊重指针,然后我们取消引用该结果以获得整数值:
x = **pp; // *pp deferences pp, then **pp dereferences the result of *pp
由于表达式的类型**pp
是int
,因此声明是
int **pp;
声明符**pp
指示pp
是指向另一个指向int
.
双重间接显示很多,通常当您想要修改传递给函数的指针值时,例如:
void openAndInit(FILE **p)
{
*p = fopen("AFile.txt", "r");
// do other stuff
}
int main(void)
{
FILE *f = NULL;
...
openAndInit(&f);
...
}
在这种情况下,我们希望函数更新f
; 为了做到这一点,我们必须传递一个指向f
. 由于f
已经是一个指针类型 ( FILE *
),这意味着我们正在传递一个指向 a 的指针FILE *
,因此声明为p
as FILE **p
。请记住,表达式 *p
inopenAndInit
指的是与表达式 in 相同的f
对象main
。
在声明和表达式中,两者[]
的()
优先级都高于一元*
。例如,*ap[i]
被解释为*(ap[i])
; 表达式ap[i]
是指针类型,并且*
取消引用该指针。因此ap
是一个指针数组。如果你想声明一个指向数组的指针,你必须*
用数组名显式地分组,像这样:
int (*pa)[N]; // pa is a pointer to an N-element array of int
当你想访问数组中的一个值时,你必须pa
在应用下标之前遵守:
x = (*pa)[i];
与功能类似:
int *f(); // f is a function that returns a pointer to int
...
x = *f(); // we must dereference the result of f() to get the int value
int (*f)(); // f is a pointer to a function that returns an int
...
x = (*f)(); // we must dereference f and execute the result to get the int value