C语言声明的语法有时会带来严重的问题。C语言声明的语法对于编译器的处理来说并不是什么大不了的事,但是对于一般的程序员,它却会成为障碍。C语言声明的语法渗透于整个语言使用的方方面面,毫不夸张的说,正是由于在组合类型方面的笨拙行为,C语言被显著且毫无必要的复杂化了。

C语言的声明模型之所以如此晦涩,原因之一是早期在设计C语言时,出现的一种C语言设计哲学,要求对象的声明形式与它的使用形式尽可能相似。一个int类型的指针数组被声明为 int *p[3];并以*p[i]这样的表达式引用或使用指针所指向的int数据,所以它的声明形式和使用形式非常相似。

这种做法的好处是各种不同操作符的优先级在“声明“和”使用“时是一样的。它的缺点在于操作符的优先级过于复杂,程序员需要记住特殊的规则才能推断出int *p[3]到底是一个int型的指针数组,还是一个指向int数组的指针。

数组指针与指针数组:

数组指针(也称行指针)
定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:

1
2
3
4
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

指针数组
定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:

1
2
3
4
5
6
int *p[3];//这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针,所以要分别赋值。
for(i=0;i<3;i++){
p[i]=a[i];
}

数组指针和指针数组在指向二维数组时,其引用是一样的,比如要表示数组中i行j列一个元素:

1
2
3
4
*(p[i]+j)
*(*(p+i)+j)
(*(p+i))[j]
p[i][j]

C语言的声明所存在的最大的问题是你无法以一种人们所习惯的自然方式从左向右阅读——一个声明,在ANSI C引入volatile和const关键字后,情况就更糟糕了。由于这些关键字只能出现在声明中(而不是使用中),这就使得现如今声明形式和使用形式能完全对的上号的例子越来越少了。

如果想要把什么东西的类型强制转换为指向数组的指针,就不得不使用下面的语句来表示这个强制类型转换:

1
2
char (*j)[20];  /*j是一个指向数组的指针,组内右20个char元素。*/
j = (char (*)[20])malloc(20);

如果把星号两边看上去多余的括号拿掉,代码会变成非法的。
涉及指针和const的声明可能会出现几种不同的顺序:

1
2
3
const int * grape;
int const * grape;
int * const grape;

最后一种情况指针是只读的,在前两种情况下,指针所指向的对象是只读的。当然,如果要对象和指针都只读,如下面两种声明所示:

1
2
const int * const grape;
int const * const grape;