关于C语言指针和数组的深入探讨

来源:互联网 发布:石家庄编程培训 编辑:程序博客网 时间:2024/04/27 22:44

我们先看一个例子:

A.int a[3][4];

B.int b[12];

C.int (*p)[12];

 

他们分别是一个二维数组,一个一维数组,一个指向一维数组的指针。我们知道整型,用int表示,字符用char表示,一些基本的类型都有字面上的一种表示,那么像int a[3][4]这种它的字面上的表示是什么呢?编译器可不知道什么“指向整型的二维数组”之类的东西,实际上他们有自己的类型,不过不像基本类型那样统一明确。很大程度上,他们在不同编译器中给出的类型表示是不同的,如vc下面的int [3][4],gcc下面的A3_A4_i,但是他们传递的意义是一样的。

 

我们知道数组可以跟指针转换。转换的本质就是“降维“,那么:

int [1][2][3]  ==>  int (*)[2][3]

int [3][4]      ==>   int (*)[4]

int [12]        ==>   int *

 

还有一个事实就是,C语言在赋值时要求类型匹配(当然很多同学都无视这些警告 -_-!)。所以当B赋值给C时,就会报警告,因为B类型为int [12],而C为int (*)[12],所以正确的赋值时p = &b,因为&b的类型就是int (*)[12],一切就OK了。

 

这里不得不提一个问题就是a[3][4]降维之后为什么’3’没了,’4’还在?因为在内存布局上,一个二维数组(高维道理一样),是以行为布局单位,一行挨一行的放。在这个例子里,我们知道3为行数,4为列数,所以降维意味着我们能看得更细,我们就看到了它在内存里是一行一行的(当然有的人眼更厉害,他可以看到每个字节。。。这是另一种情况,我们后面会说)。

 

这里分几点来说:

1.  赋值与比较

C语言属于强类型语言,类型检查比较严格,类型不匹配的会报警告,但是有些情况除外,即转换形式(如数组转换成等价的指针形式)一致的两个类型,是兼容的。

例如:int (*p)[4]和int a[3][4],这时p=a的赋值时正常的,原因就是int [3][4]其等价的指针形式为int (*)[4],而这个跟p的类型是相符的。

2.  加减运算

在开始的那个例子中,int a[3][4]的类型是int [3][4],那么a + 1呢?

这里的a要转换为等价的指针形式来运算(数组加减。。。没听说过!),那个a的指针形式就是int (*)[4],运算时就要根据这个类型来算。另外一点,指针加减中的数字,是一个数量的意思,意思是加减n * sizeof(type),注意这里说的type是指针指向的类型,如本例,a是int (*)[4]类型,它指向的是int [4]

类型,那么加1,就以为这加1 * sizeof(int [4]),即16字节。

3.  取一个元素(就是前面所说的那种好眼力)

假设int a[3][3] = {{1,2,3}, {4,5,6},{7,8,9}},我们知道a[2][1]可以取到值8,那么*(*(a+2)+1)为什么也能取到这个值呢?有了上面所说的,理解应该比较简单了。我们最终要取得一个int,而现在摆在眼前的是int [3][3]类型,那么我们就可以通过一步步转换和降维来做到。a[2][1],位于第3行中的第2个数(位于二维层面上的三行二列的位置),所以我们先定位第3行,再在这一行中定位第2个数就行了。

a的指针等价形式为int (*)[3],所以a+2,就是从开始位置,找到2*sizeof(int [3]),即第三行(记住在内存布局上,是一行一行挨在一起放的),此时a+2的类型还是int (*)[3],因为指针上加减数字并不改变类型。此时*(a+2)就是为int (*)[3]的指针类型取内容,很明显得到的类型就是int [3]了。因为*(a+1)的类型为int [3],其等价的指针形式就是char *,自然*(a+1)+1,就是指向int [3]数组中的第二个元素,此时的类型还是char *,那么拿到最终的元素,用*来取内容就行了,也就是*(*(a+2)+1)。

 

编译器就是按照这些规则来处理千变万化的表达式,而只要按照规则,处理起来就是一样的道理,类似递归那样一层层处理下去。大家在碰到类似的情况时,按照这些方法去理解,去处理,问题就好解决了。

原创粉丝点击