下面这四个变量声明代表四个不同含义:
type **a; 指向指针的指针
type a[n1][n2]; 二维数组type *a[n]; 指针数组(即数组元素是指针)type (*a)[n]; 指向数组的指针1.type **a 代表是指向指针的指针,其实声明类似“type **...*a”形式的都合法,代表“指向指针的指针的...指针的指针”。
例程: int a = 9; int * b = &a; int **c = &b; int ***d = &c; int ****e = &d; int *****f = &e; printf("value of a is %d\n",a); printf("address of a is %x\n",b); printf("address of b is %x\n",c); printf("address of c is %x\n",d); printf("address of d is %x\n",e); printf("address of e is %x\n",&e);输出: value of a is 9 address of a is 12ff7c address of b is 12ff78 address of c is 12ff74 address of d is 12ff70 address of e is 12ff6c每一个指针的值都是上一个变量的地址,同时也能验证了栈地址空间增加的方向是从高到低的。另外,这种类型的指针只能初始化一维数组,可以初始化形式如 : type *a = (type *)(malloc(sizeof(type) * n)); //创建一维数组,元素是type型 type **a = (type **)(malloc(sizeof(type *) * n)); //创建一维数组,元素是type *型其实,函数malloc(n)只关注参数n的值,n代表申请的字节数,不论n有多大,malloc一概返回void *指针,除非堆空间内存不够时返回NULL。前面再加上强制类型转换,使返回值变成想要的类型(只要它和void * 可以强制转换)。就算你写成 int a = (int)(malloc(1000));单看这一条语句编译器不会报错,没有类型宽度检查。申请一块内存想让它以什么方式被编译器认可,编译器就以什么方式认可,至于以后会不会出错人家不管。又由于,编译器对"type **...*a"这种方式声明的变量最多按照一维数组处理,即: type * a = (type *)(malloc(...)),a[m]是type型 type **a = (type **)(malloc(...)), a[m]是type *型 type ***a = (type **)(malloc(...)), a[m]是type **型 ...以上语句后,出现“a[m][n]”会出错!!所以只能初始化一维数组,不管指针指向多大空间,都安一维数组处理。2.a[n1][n2]代表一个二维数组,数组可以使任意维度,声明类似“a[n1][n2]...[nm]”都合法,代表m维数组。
以整型二维数组为例:int a[n1][n2]初始化方法有两种, 静态初始化:a[n1][n2] = { {1,2,3,..., n2}, {2, 2, 3,..., n2}, ... {n1, 2, 3,..., n2} }; 动态初始化:(后面再提);静态初始化后,数组所有元素被分配在程序栈空间内。有以下等式成立:&a = a = &a[0] = a[0] = &a[0][0];&a代表叫做a这个名字的变量的地址;a代表数组a首元素的地址;由于数组a是在栈上分配空间的,所以数组名a这个变量和数组本身分配在一个地址上,所以&a = a;a[0]是数组首元素,所以&a[0] = a;a[0]代表数组a[0]的首元素地址,a[0][0]是a[0]的首元素,所以&a[0][0] = a[0];其实往简单了想,a,a[0],a[0][0]都是二位数组的头部,所以起始地址是一样的。所以静态初始化的二维数组:&a = a = &a[0] = a[0] = &a[0][0],永远成立。(对于&a, a, &a[0], a[0], &a[0][0]相等与否还有另外两种情况,后面再提)。3.type *a[n] 代表一个数组,数组的元素是指针,类似于"type **...*a[n]"的声明都是合法的,代表数组的元素是n维指针。
先明确这里a是一个数组,语句 int * a[n]执行后,编译器在栈区开辟一个空间,保存n个指针值。对于这样的语句 int * a[10] = (int *[10])(malloc(...));(或int * a[10] = new int*[10];)是编译错误的,因为不能把一个void * 型转化成数组型,从另一个角度考虑,数组可以看做指针常量,不能给一个常量赋值。如果一定要在堆区建立指针数组,可以使用指针的指针: int **a = (int **)(malloc(...));也可以使用指针的指针: (后面再提)。上面说到,二维数组的动态初始化,可以用指针数组完成。 int *a[10]; for(int i = 0; i < 10; ++i){ a[i] = (int *)(malloc(sizeof(int) * 10)) 或 a[i] = new int[10]; }这样就完成了动态初始化,创建了一个10 * 10的数组,可以用a[n][m]访问具体元素。现在分析一下这种情况下&a, a, &a[0], a[0], &a[0][0]的关系:数组a在栈区建立,所以 &a = a = &a[0],所有a[i]指向的数组在堆区建立,所以a[0] = &a[0][0].即&a = a = &a[0] != a[0] = &a[0][0].另外a[i]之间的地址,也不一定满足a[i + 1] - a[i] = 4 * 10,因为每一个a[i]都是分别创建空间。4.type (*a)[n],代表指向数组的指针。“int (*a)[n1][n2]...[nm]”的声明都是合法的,表示指向m维数组的指针。
其实,int (*a)[n] 和 int b[n][...],这里a和b比较类似。a是指针名,b是数组名。区别是b是常量,值固定,a是变量,值可变,可以做赋值运算,例如 a = b;也可以动态初始化二维数组: int (*a)[10] = (int (*)[10])(malloc(4 * 10)) 或 int (*a)[10] = new int[...][10];再来分析一下&a, a, &a[0], a[0], &a[0][0]的关系。由于变量a在栈区建立,而二维数组其他部分在堆区建立,所以有&a != a = &a[0] = a[0] = &a[0][0],而且相邻a[i]之间的差值一定为 4 * 10。对于上面提到的,在堆区分配指针数组问题,可以这样解决: int *(*a)[10] = (int *(*)[10])(malloc(10 * 4));
^_^