数组指针和指针数组的区别
数组指针(也称⾏指针)
定义 int (*p)[n];
()优先级⾼,⾸先说明p是⼀个指针,指向⼀个整型的⼀维数组,这个⼀维数组的长度是n,也可以说是p的步长。也就是说执⾏p+1时,p要跨过n个整型数据的长度。
如要将⼆维数组赋给⼀指针,应这样赋值:
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的⾸地址的值。
如要将⼆维数组赋给⼀指针数组:
int *p[3];
int a[3][4];
p++; //该语句表⽰p数组指向下⼀个数组元素。注:此数组每⼀个元素都是⼀个指针
for(i=0;i<3;i++)
p[i]=a[i]
这⾥int *p[3] 表⽰⼀个⼀维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。
这样两者的区别就豁然开朗了,数组指针只是⼀个指针变量,似乎是C语⾔⾥专门⽤来指向⼆维数组的,它占有内存中⼀个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的⼀点就是,同时⽤来指向⼆维数组时,其引⽤和⽤数组名引⽤都是⼀样的。
⽐如要表⽰数组中i⾏j列⼀个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]
优先级:()>[]>*
=========================================================================
⼀、指针数组和数组指针的内存布局
初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:⾸先它是⼀个数组,数组的元素都是指针,数组占多少个字节由数组本⾝的⼤⼩决定,每
⼀个元素都是⼀个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
数组指针:⾸先它是⼀个指针,它指向⼀个数组。在32 位系统下任何类型的指针永远是占4 个字节,⾄于它指向的数组占多少字节,不知道,具体要看数组⼤⼩。它是“指向数组的指针”的简称。
下⾯到底哪个是数组指针,哪个是指针数组呢:
A)
int *p1[10];
B)
int (*p2)[10];
每次上课问这个问题,总有弄不清楚的。这⾥需要明⽩⼀个符号之间的优先级问题。
“[]”的优先级⽐“*”要⾼。p1 先与“[]”结合,构成⼀个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是⼀个数组,其包含10 个指向int 类型数据的指针,即
指针数组。⾄于p2 就更好理解了,在这⾥“()”的优先级⽐“[]”⾼,“*”号和p2 构成⼀个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这⾥并没有名字,是个匿名数组。那现在我们清楚p2 是⼀个指针,它指向⼀个包含10 个int 类型数据的数组,即数组指针。我们可以借助下⾯的图加深理解:
⼆、int (*)[10] p2-----也许应该这么定义数组指针
这⾥有个有意思的话题值得探讨⼀下:平时我们定义指针不都是在数据类型后⾯加上指针变量名么?这个指针p2 的定义怎么不是按照这个语法来定义的呢?也许我们应该这样来定义p2:
int (*)[10] p2;
int (*)[10]是指针类型,p2 是指针变量。这样看起来的确不错,不过就是样⼦有些别扭。其实数组指针的原型确实就是这样⼦的,只不过为了⽅便与好看把指针变量p2 前移了⽽已。你私下完全可以这么理解这点。虽然编译器不这么想。^_^
三、再论a 和&a 之间的区别
既然这样,那问题就来了。前⾯我们讲过a 和&a 之间的区别,现在再来看看下⾯的代码:
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[5] = &a;
char (*p4)[5] = a;
return 0;
}
上⾯对p3 和p4 的使⽤,哪个正确呢?p3+1 的值会是什么?p4+1 的值⼜会是什么?毫⽆疑问,p3 和p4 都是数组指针,指向的是整个数组。&a 是整个数组的⾸地址,a是数组⾸元素的⾸地址,其值相同但意义不同。在C 语⾔⾥,赋值符号“=”号两边的数据类型必须是相同的,如果不同需要显⽰或隐式的类型转换。p3 这个定义的“=”号两边的数据类型完全⼀致,⽽p4 这个定义的“=”号两边的数据类型就不⼀致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下警告:
warning C4047: 'initializing' : 'char (*)[5]' differs in levels of indirection from 'char *'。
还好,这⾥虽然给出了警告,但由于&a 和a 的值⼀样,⽽变量作为右值时编译器只是取变量的值,所以运⾏并没有什么问题。不过我仍然警告你别这么⽤。
既然现在清楚了p3 和p4 都是指向整个数组的,那p3+1 和p4+1 的值就很好理解了。
但是如果修改⼀下代码,把数组⼤⼩改⼩点,会有什么问题?p3+1 和p4+1 的值⼜是多少呢?
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[3] = &a;
char (*p4)[3] = a;
return 0;
}
甚⾄还可以把代码再修改,把数组⼤⼩改⼤点:
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[10] = &a;
char (*p4)[10] = a;
return 0;
}
这个时候⼜会有什么样的问题?p3+1 和p4+1 的值⼜是多少?
上述⼏个问题,希望读者能仔细考虑考虑,并且上机测试看看结果。
测试结果:
(1).char (*p2)[5]=a;必须使⽤强制转换,如:char (*p2)[5]=(char (*)[5])a;
(2).把数组⼤⼩改变,都会编译不通过,提⽰:
error C2440: 'initializing' : cannot convert from 'char (*)[5]' to 'char (*)[3]'
error C2440: 'initializing' : cannot convert from 'char (*)[5]' to 'char (*)[10]'
(3).把以上程序测试代码如下:
int main()
{
char a[5]={'a','b','c','d'};
char (*p1)[5]= &a;
char (*p2)[5]=(char (*)[5])a;
printf("a=%d\n",a);
printf("a=%c\n",a[0]);
printf("p1=%c\n",**p1);
printf("p2=%c\n",**p2);
printf("p1+1=%c\n",**(p1+1));
printf("p2+1=%c\n",**(p2+1));
return 0;
}
输出:
a=1638208
p1=a
p2=a
p1+1=?
p2+1=?
Press any key to continue
结论:
根据指针类型及所指对象,表⽰指针⼤⼩,每次加1,表⽰增加指针类型⼤⼩的字节.----后⾯还会有解释说明.
四、地址的强制转换
先看下⾯这个例⼦:
struct Test
{
int Num;
char *pcName;
short sDate;
指针变量本身有地址吗
char cha[2];
short sBa[4];
}*p;
假设p 的值为0x100000。如下表表达式的值分别为多少?
p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___?
(unsigned int*)p + 0x1 = 0x___?
我相信会有很多⼈⼀开始没看明⽩这个问题是什么意思。其实我们再仔细看看,这个知识点似曾相识。⼀个指针变量与⼀个整数相加减,到底该怎么解析呢?
还记得前⾯我们的表达式“a+1”与“&a+1”之间的区别吗?其实这⾥也⼀样。指针变量与⼀个整数相加减并不是⽤指针变量⾥的地址直接加减这个整数。这个整数的单位不是byte ⽽是元素的个数。所以:p + 0x1 的值为0x100000+sizof(Test)*0x1。⾄于此结构体的⼤⼩为
20byte,前⾯的章节已经详细讲解过。所以p +0x1 的值为:0x100014。
(unsigned long)p + 0x1 的值呢?这⾥涉及到强制转换,将指针变量p 保存的值强制转换成⽆符号的长整型数。任何数值⼀旦被强制转换,其类型就改变了。所以这个表达式其实就是⼀个⽆符号的长整型数加上另⼀个整数。所以其值为:0x100001。
(unsigned int*)p + 0x1 的值呢?这⾥的p 被强制转换成⼀个指向⽆符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。
上⾯这个问题似乎还没啥技术含量,下⾯就来个有技术含量的:在x86 系统下,其值为多少?
intmain()
{
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);//指向a数组后⾯的内存单元,&a+1表⽰向后移16个存储单元
int *ptr2=(int *)((int)a+1);//表⽰a的存储单元的地址增加⼀个字节
printf("%x,%x",ptr1[-1],*ptr2);//ptr1[-1]其实指向的是a数组的最后⼀个单元,*ptr1则表⽰a数组的地址后移⼀个字节之后的4个连续存储单元所存储的值
return 0;
}
这是我讲课时⼀个学⽣问我的题,他在⽹上看到的,据说难倒了n 个⼈。我看题之后告诉他,这些⼈肯定不懂汇编,⼀个懂汇编的⼈,这种题实在是⼩case。下⾯就来分析分析这个问题:
根据上⾯的讲解,&a+1 与a+1 的区别已经清楚。
ptr1:将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1 肯定指到数组a 的下⼀个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其值为0x4。
ptr2:按照上⾯的讲解,(int)a+1 的值是元素a[0]的第⼆个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2 的值应该为元素a[0]的第⼆个字节开始的连续4 个byte 的内容。
其内存布局如下图:
好,问题就来了,这连续4 个byte ⾥到底存了什么东西呢?也就是说元素a[0],a[1]⾥⾯的值到底怎么存储的。这就涉及到系统的⼤⼩端模式了,如果懂汇编的话,这根本就不是问题。既然不知道当前系统是什么模式,那就得想办法测试。⼤⼩端模式与测试的⽅法在第⼀章讲解union 关键字时已经详细讨论过了,请翻到彼处参看,这⾥就不再详述。我们可以⽤下⾯这个函数来测试当前系统的模式。
int checkSystem()
{
union check
{
char ch;
} c;
c.i = 1;
return (c.ch ==1);//如果当前系统为⼤端模式这个函数返回0;如果为⼩端模式,函数返回1。
}
如果当前系统为⼤端模式这个函数返回0;如果为⼩端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。如果此函数的返回值为0 的话,*ptr2 的值为0x100。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。