以下是指针声明的基本规则:
T *p; // p is a pointer to T
T *ap[N]; // ap is an array of pointers to T
T *fp(); // fp is a function returning a pointer to T
T (*pa)[N]; // pa is a pointer to an array of T
T (*pf)(); // pf is a function returning a pointer to T
T * const p; // p is a const pointer to T - *p is writable, but p is not
const T *p; // p is a non-const pointer to const T - p is writable, but *p is not
T const *p; // same as above
const T * const p; // p is a const pointer to const T - neither p nor *p are writable
T const * const p; // same as above
要阅读一个毛茸茸的声明,找到最左边的标识符并按照上述规则解决问题,将它们递归地应用于任何函数参数。例如,下面是 C 标准库中 signal 函数的声明如何分解:
signal -- signal
signal( ) -- is a function taking
signal( sig ) -- parameter sig
signal(int sig ) -- is an int
signal(int sig, func ) -- parameter func
signal(int sig, (*func) ) -- is a pointer to
signal(int sig, (*func)( )) -- a function taking
signal(int sig, (*func)( )) -- unnamed parameter
signal(int sig, (*func)(int)) -- is an int
signal(int sig, void (*func)(int)) -- returning void
(*signal(int sig, void (*func)(int))) -- returning a pointer to
(*signal(int sig, void (*func)(int)))( ) -- a function taking
(*signal(int sig, void (*func)(int)))( ) -- unnamed parameter
(*signal(int sig, void (*func)(int)))(int) -- is an int
void (*signal(int sig, void (*func)(int)))(int); -- returning void
您可以通过替换构建更复杂的指针类型。例如,如果要声明一个返回指向数组的指针的函数,则可以将其构建为
T a [N]; // a is an array of T
|
+---+----+
| |
T (* pa )[N]; // pa is a pointer to an array of T
|
++--+
| |
T (* fpa() )[N]; // fpa is a function returning a pointer to an array of T
如果您想要一个指向函数的指针数组,这些函数返回指向T 的指针,那么您可以将其构建为
T * p ; // p is a pointer to T
|
+----------+
| |
T * fp (); // fp is a function returning pointer to T
|
+---+-----+
| |
T * (* pf ) (); // pf is a pointer to a function returning pointer to T
|
++---+
| |
T * (* apf[N] ) (); // apf is an array of pointers to functions returning pointer to T
指针运算是根据对象而不是字节来完成的。如果p 存储T 类型对象的地址,则p+1 产生该类型的下一个对象的地址。如果pa 存储T 的N 元素数组的地址,则pa+1 产生T 的下一个N 元素数组的地址。
在您的代码中
int main()
{
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));
return 0;
}
表达式&a + 1 产生int 跟在a 之后的下一个5 元素数组的地址。该地址值被强制转换为int *,因此它被视为a 的最后一个元素之后的第一个int 的地址。图表可能会有所帮助:
int[5] int int *
------ --- -----
+---+ +---+ +---+
a: | | a[0]: | 1 | ptr: | |
+ - + +---+ +---+
| | a[1]: | 2 | |
+ - + +---+ |
| | a[2]: | 3 | |
+ - + +---+ |
| | a[3]: | 4 | |
+ - + +---+ |
| | a[4]: | 5 | |
+---+ +---+ |
a + 1: | | | ? | <--------+
+ - + +---+
| | | ? |
+ - + +---+
... ...
表达式a 和a+1 的类型为int [5],每个a[i] 的类型为int,ptr 的类型为int *。
因此,ptr-1 产生a[4] 的地址。
请记住,C 声明语法反映了表达式语法 - 如果您有一个指向名为 pa 的数组的指针,并且您想要访问指向数组的第 i'th 元素,则必须取消引用 pa然后下标结果(假设T 是int 对于这个例子):
printf( "indexed value = %d\n", (*pa)[i] );
在表达式和声明中,后缀运算符[] 和() 的优先级高于一元*,因此*pa[i] 将被解析为*(pa[i]),这不是我们在这种情况下想要的。我们需要明确地将* 运算符与pa 分组,这样我们才能取消引用正确的东西。
表达式(*pa)[i]的类型是int,所以pa的声明写成
int (*pa)[N];
因此,声明的形状告诉您代码中表达式的形状。从那里它只是记住各种子表达式是如何键入的:
Expression Type
---------- ----
pa int (*)[N];
*pa int [N];
(*pa)[i] int
数组下标操作a[i] 定义为*(a + i) - 给定起始地址a,偏移i 元素(不是字节 - 记住上面对指针运算的讨论)并取消引用结果。这意味着如果p是一个指针,那么
*p == *(p + 0) == p[0]
意味着您可以像数组一样为指针表达式下标1。
鉴于你的声明
int mat[200][9];
int (*p)[9] = mat;
然后你可以索引到p,就像你可以索引到mat一样:
(*p)[j] == (*(p + 0))[j] == p[0][j]
因此p[i][j] 产生与mat[i][j] 相同的值。
- 数组不是指针,但数组表达式会根据需要转换为指针表达式。