返回首页 C 高阶教程

数组与指针的纠葛

在C语言中,根据定义,表达式 e1[e2] 准确地对应于表达式 *((e1)+(e2))。因此,要求表达式 e1[e2] 的其中一个操作数是指针,另一个操作数是整数。且这两个操作数的顺序可以颠倒。

故: a[4] 等同于 4[a] 等同于 *(a+4)*
编译器把所有的e1[e2]表达式转换成 \
((e1)+(e2))。 所以,以下标的形式访问在本质上与以指针的形式访问没有区别,
只是写法上不同罢了**!

多维数组

二维数组a[i][j]
编译器总是将二维数组看成是一个一维数组,而一维数组的每个元素又都是一个数组。

多维数组定义的下标从前到后可以看做是最宏观的维到最微观的维
例:三维数组 a[i][j][k] 可理解为 共有 i 个大组,每个大组里有 j 个小组,每个小组里有k个元素。
所以:
a 表示为整个三维数组,其值为 &a[0][0][0]
&a+1 为整个三维数组后面的第一个位置。(偏移整个三维数组的长度)
a+1 为第二个大组的首位置处(偏移一个大组的长度)
【数组名a代表的是数组首元素的首地址,即:第一个大组的首地址】

a[0] 表示为三维数组的 i 个大组中的第一个大组【可看做一个二维数组】,其值与 &a[0][0][0] 的值相同。
&a[0]+1 为第二个大组的首位置处(偏移一个大组的长度)
a[0]+1 为第一个大组中第二个小组的首位置处(a[0]可看做是一个二维数组名,故其代表的是第一个小组的首地址)(偏移一个小组的长度)

a[0][0] 表示为第一个大组中的第一个小组【可看做一个一维数组】,其值与 &a[0][0][0] 的值相同。
&a[0][0]+1 为第一个大组中第二个小组的首位置处(偏移一个小组的长度)
a[0][0]+1 为第一个大组中第一个小组的第二个元素位置处(偏移一个元素的长度)
a[0][0][0] 表示为第一个大组中的第一个小组中的第一个元素。其值为&a[0][0][0],a[0][0][0]+1为首元素值加1。(因为a[0][0][0]为元素值而不是地址)

数组的数组名(即:二维数组名)退化为数组的(常量)指针,而不是指针的指针。 同理, n 维数组名退化为 n-1 维数组的(常量)指针。
【总结:指针代表的是谁的首地址 就以谁的长度为偏移单位。】
【规律:与定义比较,缺少几对方括号,就是几维数组的数组名,如上例:a缺少3对方括号,即为3维数组的数组名(代表的是2维数组的地址);a[0]缺少2对方括号,即为2维数组的数组名(代表的是1维数组的地址);a[0][0]缺少1对方括号,即为1维数组的数组名(代表的是数组元素的地址)】

【数组名与整数相加,首先要转换成数组的首元素地址与整数相加,而首元素的存储大小就是相加的单位】

对多维数组的解析

我们可以用上面那种从前到后的解析方式来思考,
a:就表示整个多维数组。
a[m]:就表示第m+1大组(大组即数组最大的维),
a[m][n]:就表示第m+1大组中的第n+1小组。(小组即次大的维),
以此类推,即多维数组的解析是层层细化的。

指针数组与数组指针

指针数组:首先它是一个数组。数组的元素都是指针。它是“存储指针的数组”的简称。
数组指针:首先它是一个指针。它指向一个数组。它是“指向数组的指针”的简称。

例:
int p1[10]; //它是指针数组。(因为[]的优先级比*高,p1先与[]结合,构成一个数组的定义)
int (*p2)[10] ; //它是数组指针。(括号的优先级较高,*与p2构成一个指针的定义) 它指向一个包含10个int型数据的数组。
int (\
p)[10][5] ; //则p指向一个int型的二维数组a[10][5]。
【规律:数组指针,把定义中括号内的指针看成是一个普通的字母,则其表示的就是 数组指针所指的对象类型】

int a[5][5] ;
int (*p)[4] ;
p=a ;
问:&p[4][2]-&a[4][2]的值为多少?

设二维数组的首地址为0,则a[4][2]为第5组的第3个位置,因为int a[5][5];即有5组,每组有5个元素。故:&a[4][2]是(4*5+2)*sizeof(int)。
int (*p)[4] ; 指针指向一个含4个int型的元素的数组,故p[4]相对于p[0]向后移动了“4个int型数组”的长度,然后在此基础上再向后移动2个int型的长度(即,其步长按维度逐步递减,多维数组也可按此方式理解)。最后其值为(4*4+2) sizeof(int)
最后切记:地址值参与的加减运算(地址不能被乘),整数的单位是地址值代表的元素的存储大小!
&p[4][2]-&a[4][2]结果为-4。若分开比较&p[4][2]和&a[4][2]则相差4
sizeof(int)个字节。

数组参数与指针参数

1、二维数组名做实参

int  main(void)  
{
    int  a[4][5] ;  
    ………  
    ………  
    fun(a);  
    ………  
}  
被调函数:  
①fun( inta[4][5] )  
②fun( inta[ ][5] )  
③fun( int(*a)[5] )  
{   ………  
    a[i][j]=………  
    ………  
} 

以上三种方式皆可。无论是那种方式,它们只是写法不同,但编译器的处理方式相同,都把它们看做是一维数组指针。
因为二维数组名退化为一个一维数组指针,故是以一维数组指针的形式来传递二维数组的。

2、指针数组做实参

int main(void)  
{
    int  a[4][5] , i, *p[4] ;  
    for(i=0;i<4; i++)  
        p[i]= a[i] ;  
    ………  
    fun(p);  
    ………  
}  
被调函数:  
①fun(int*q[4])  
②fun(int *q[])  
③fun(int **q)  
{   ……… 
    q[i][j]=……… //取出指针数组中的第i个元素(为指针),再偏移j个单位 
    ………  
} 

以上三种方式皆可。无论是那种方式,写法不同,但编译器的处理方式相同,都把它们看做是二级指针。
因为指针数组名退化为数组首元素的地址,即二级指针,故是以二级指针的形式来传递指针数组的。
而多维数组名退化为次维数组的指针,即数组指针,故是以数组指针的形式来传递多维数组的。
【C中函数实参与形参之间是传值引用的,所以你要改变实参的值,就传递它的地址】

上一篇: 数组与指针(一) 下一篇: 函数指针