现在我将尝试对指针进行一些澄清,并为了便于理解,现在我将以简化的方式阐述事情。
在C中,指针是一个变量,就像任何其他变量一样,其类型是内存中的地址。
不幸的是,仅有一个指向进程内存中某个位置的地址是不够的,无法正确地与指针所指向的其他实体(通常是变量)进行交互而不会破坏它们。出于这个原因,语言提供了指针的“资格”,允许用户指定它指向的对象(类型),并允许编译器选择使用的方式来访问内存以符合指向的对象。
说到这里,回到你最初的问题,现在应该清楚了,指针是一个变量,可以保存C语言中存在的任何类型(基本或派生)的地址。
现在我们进入正式部分。要与指针交互,我们需要一些运算符来处理它们,基本上是一个运算符,它将解析为变量的地址,一个运算符,它将从指针中解析出变量的值,以及一个声明运算符,用于“声明”指针。
让我们从“解引用运算符*”开始。该运算符返回指向对象的值(解引用)。但它也是指针的“声明符”,因为它是指针最自然的视觉表示。请看以下声明:
int * p_int;
按照C风格的声明方式来读,从右到左可以说:
p_int
是一个变量,其解引用后给出一个int
值。
它是一个指针。
现在,如果我们声明一个变量p_int
作为指向类型为int
的对象的指针,当我们对其进行解引用时,编译器知道为了返回一个int
值,它必须访问从指针所指向的内存字节开始的内存字节,并将请求的一些字节(在我们使用的机器/编译器上)打包在一起形成一个int
。
无论如何,指针,就像其他任何变量一样,必须初始化或分配一个有效地址,然后才能用于某些事情。因此,我们必须初始化/分配一个与其指向的对象类型兼容的值。如果我们有一个变量:
int an_int;
如果是兼容类型,它就可以适应范围。但指针保存的是对象的地址,因此我们无法直接赋值:
p_int = an_int
为了给它指定一个地址,我们必须获取变量在内存中的地址。我们需要一元运算符
&
,它会返回应用于其上的对象的地址。
p_int = &an_int; //we assign to p_int the address in memory of an_int
当然,我们可以通过解引用指针来再次访问存储在地址中的值:
int another_int = *p_int;
在结束之前,我们必须谈论一种C语言为
数组保留的一种
特殊处理。在C语言中,
数组的名称会自动转换为其第一个元素的地址(我们将在下面看到这个标准存在哪些限制)。这意味着数组声明后的2行代码是等价的:
int array_of_int[10];
int *p_int = array_of_int;
int *p_int1 = &array_of_int[0];
即使是 int *p_int1 = &array_of_int;
也等同于下面我们将要看到的原因。
现在我们来考虑你的例子。声明:
char *w[3];
char **p = &w[0];
char ***q = &p;
必须按照
"w
是一个包含3个指向字符的指针的数组,p
是一个指向指向int
类型指针的指针,q
是一个指向指向指向int
类型指针的指针的指针。"阅读。解码如下:数组
w
的每个元素都保存一个
char
的地址,如果在内存中有一个以该地址开头的
char
数组,则根据我们之前关于数组的传递属性所述,我们可以说每个
w
元素保存了未指定维度的3个
char
数组的第一个
char
的地址。当然,这些数组中的每一个都包含三个单词“Apple”,“Pear”和“Peach”。在声明中:
char **p = &w[0];
我们创建了一个变量,用于保存地址,即存储在数组w的第0个元素中的指向char类型指针的值所在的内存地址。需要注意的是,w [0]将给出字符串“Apple”开始的地址,而不是保存字符串“Apple”开始地址的地址。因此,我们使用一元运算符&来获取这样的地址。
真正有趣的一点是声明中的两个星号是声明性的,而不是操作符。为了澄清,请考虑:
char **p;
p = &w[0];
这与之前的完全相同,但在第一行中,我们声明了一个指向指针的char
,在第二行中,我们将其赋值为w
的第一个元素的地址。
这应该足以解释问题的其他部分。
现在让我们更加正式地查看C标准。我们已经说过,在某些情况下,数组和指针会被编译器自动转换。这在ISO/IEC 9899:2011 § 6.3.2.1 "Lvalues, arrays, and function designators" subsection 3中明确说明:
除非它是sizeof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串字面量,否则具有类型“type的数组”的表达式将转换为具有类型“type的指针”的表达式,该指针指向数组对象的初始元素而不是lvalue。如果数组对象具有寄存器存储类,则行为未定义。
这也解释了为什么对数组操作数使用&
运算符仍然解析为第一个数组对象的地址。
const char* w[3]
作为类型。 - Bathsheba