【C基础】数组指针 or 指针数组???

来源:互联网 发布:pp手机助手 mac 编辑:程序博客网 时间:2024/05/21 15:43





转自:



1、数组指针(指向数组的指针)

(1)数组在内存中的表示

创建一个数组就是在内存里面开辟一块连续的空间,比如int a[4];就是在内存里面开辟了一个大小为4*sizeof(int)字节的内存空间。二维数组是特殊的一维数组。

先来看一段代码:

[cpp] view plain copy
  1. <strong><span style="font-size:16px;">void main()  
  2. {  
  3.     int a[2][2]={1,2,3,4};//这是一个2*2的二维数组  
  4.     int (*p)[2];//数组指针  
  5.     p=a;//令p指向数组a  
  6. }</span></strong>  


注意到代码中这句话:int (*p)[2];这里的p是一个数组指针变量。

a中各个元素在内存中是这样存放的:

 

(2)理解数组名和数组指针变量

OK,现在我们思考a,a[0],a[1],p,a+1,a[0]+1,p+1到底是什么,思考3秒钟:

分析:

a是一个数组名,类型是指向一维数组的指针,不是变量,a的值是指针常量,即不能有a++或者a=p这些操作。a指向这块连续空间的首地址,是&a[0][0]。

a[0]是一维数组名,类型是指向整型的指针,是&a[0][0],这个值是一个常量。

a[1]是一维数组名,类型是指向整型的指针,是&a[1][0],这个值是一个常量。

p是一个数组指针变量,指向一维数组的指针变量是&a[0][0]。可以执行p++;p=a等操作。

a+1表示指向下一行元素,也可以理解为指向下一个一维数组。

*(a+1)是取出第一行的首地址。

a[0]+1是指向第0行第1个元素,也可以理解为指向一维数组a[0]的第一个元素。

p+1同a+1

*(p+1)同*(a+1)

虽然a跟a[0]值是一样,但类型不一样,表示的意义不一样。通过分析就不难理解为什么*(*(a+i)+j)和a[i][j]等效了。

 

(3)指针是数组的迭代器

[cpp] view plain copy
  1. <span style="font-size:16px;">#include<stdio.h>  
  2. #define M 2  
  3. #define N 3  
  4.   
  5. int main()  
  6. {  
  7.     int a[M][N]={1,2,3,4,5,6};  
  8.     int *start=&a[0][0];  
  9.     int * const end=start+M*N;  
  10.     for(;start!=end;start++)  
  11.         printf("%-5d",*start);  
  12.     putchar('\n');  
  13.     return 0;  
  14. }</span>  


理解这段代码,用指针遍历一个二维数组,是不是很像C++标准库里面vector的迭代器。注意这里只用了一个for循环,这也可以说明二维数组其实就是特殊的一维数组。

 

(4)数组名与数组指针变量的区别

 

从(2)中的分析中得出数组名是指针,类型是指向元素类型的指针,但值是指针常量,声明数组时编译器会为声明所指定的元素数量保留内存空间。数组指针是指向数组的指针,声明指针变量时编译器只为指针本身保留内存空间。

 

看看这个代码:

[cpp] view plain copy
  1. <strong><span style="font-size:16px;">#include<stdio.h>  
  2. void main()  
  3. {  
  4.     int a[2][2]={1,2,3,4};//这是一个2*2的二维数组  
  5.     int (*p)[2];//数组指针  
  6.     p=a;//令p指向数组a  
  7.     printf("%d\n%d\n",sizeof a,sizeof p);  
  8. }</span></strong>  

猜一猜输出是什么?


困惑了吗?为什么结果会是这样的呢,让我们先初步了解一下sizeof关键字吧,下面是MSDN上sizeof的说明:

 



注意到说明中的红色字体,当sizeof用于变量时返回这个变量占用的实际空间的大小。当sizeof用于数组名时,返回整个数组的大小(这里的大小指占用的字节数)。p是一个指针变量,这个变量占用四个字节。而a是数组名,所以sizeof a返回数组a中的全部元素占用的字节数。

 了解了sizeof,猜猜下面这段代码输出什么

[cpp] view plain copy
  1. <span style="font-size:16px;"><strong>#include<stdio.h>  
  2.   
  3. void main()  
  4. {  
  5.     int a[2][2]={1,2,3,4};//这是一个2*2的二维数组  
  6.     int (*p)[2];//数组指针  
  7.     p=a;//令p指向数组a  
  8.     printf("%d\n%d\n",sizeof(a+1),sizeof(p+1));  
  9.     printf("%d\n%d\n",sizeof(a+0),sizeof(p+0));  
  10. }</strong></span>  


运行结果:

从结果中看出,a在做+运算时是转化成了指针变量,此时a+i的类型是一个指针变量,而不是一个数组名。但a[i]是一个一维数组的数组名,sizeof(a[0])的值是8

 

现在再来看一段代码:

[cpp] view plain copy
  1. <strong><span style="font-size:16px;">#include<stdio.h>  
  2.   
  3. void f(int a[][2])  
  4. {  
  5.     printf("%d\n",sizeof a);  
  6. }  
  7. void main()  
  8. {  
  9.     int a[2][2]={1,2,3,4};//这是一个2*2的二维数组  
  10.     printf("%d\n",sizeof a);  
  11.     f(a);  
  12. }</span></strong>  

再猜一下输出是什么?

是不是又有点困惑呢?

解释:这是因为传参的时候数组名转化成指针变量,注意到函数f中f(int a[][2])这里并不需要指定二维数组的长度,此处可以改为int (*a)[2]。所以传过来的就是一个数组指针变量。这样明白了吧!

 

总结:数组名的类型是指向元素类型的指针,值是指针常量。(a+1)的类型是一个指针变量。把数组名作为参数传递的时候实际上传递的是一个指针变量。sizeof对变量和数组名操作时返回的结果会不一样。数组指针是指向数组的指针,其值可以是变量。

2、指针数组(存放指针的数组)

(1)认识指针数组

一个存放int类型的数组称为整型数组,那么存放指针的数组就叫指针数组。

[cpp] view plain copy
  1. <strong><span style="font-size:16px;">#include<stdio.h>  
  2.   
  3. void main()  
  4. {  
  5.     int i=1,j=2;  
  6.     //p先跟[]结合,然后再跟*结合  
  7.     int *p[2];//指针数组,存放指针的数组  
  8.     p[0]=&i;  
  9.     p[1]=&j;  
  10.     printf("%d",sizeof(p));  
  11. }</span></strong>  

 断点调试分析:

 

此例数组p就两个元素,p[0]是指向i的指针,p[1]是指向j的指针。这两个指针都是int型指针,所以p是存放int型指针的数组。sizeof(p)返回数组占用的总空间,所以程序输出是8

 

(2)指针数组用法举例

来自《the c programming language》的一个例子,对字符串进行排序,看了下面这个例子,相信你就体会到了指针数组的好处了。

 

 

 3、深入思考

数组名a、a[0]、a[1],数组指针p到底在内存中是怎么存储的呢?存放在内存中的哪里呢?是在代码段吗?求解惑!

 

 4、参考资料

《c和指针》 《the c programming language》 等



-------------------------------------------------------------------------------------------------------------------------------------------------



数组指针(也称行指针)
定义 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]

优先级:()>[]>*

出处:http://www.cnblogs.com/hongcha717/archive/2010/10/24/1859780.html

=========================================================================

一、指针数组和数组指针的内存布局

初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在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
a=a
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
  {
      int i;
      char ch;
  } c;
  c.i = 1;
  return (c.ch ==1);//如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。
}

如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。如果此函数的返回值为0 的话,*ptr2 的值为0x100。

出处:http://see.xidian.edu.cn/cpp/html/476.html


其他:


http://blog.csdn.net/men_wen/article/details/52694069


http://blog.csdn.net/touch_2011/article/details/6966980


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 骨龄比实际年龄大怎么办 小孩崴脚了肿了怎么办 报到证过期2年多怎么办 报到证过了期限怎么办 报到证超过两年怎么办 报到证过期4年了怎么办 学校没发报到证怎么办 未报到证过期了怎么办 专升本的报到证怎么办 蓝色报到证丢了怎么办 报到证用过了怎么办 大学报到证丢了怎么办 qq好被冻结了怎么办啊 qq突然被冻结了怎么办 扣扣密码被冻结怎么办 蠕动泵不抽水了怎么办 失眠多梦压力大怎么办 大福的皮硬了怎么办 安卓手机root失败怎么办 你的手机已root怎么办 移动网不能看片怎么办 网站的密码忘了怎么办 拍拍贷换手机了怎么办 六个半月的宝宝发烧怎么办 5个月的小孩发烧怎么办 3个月小孩发烧怎么办 2岁宝宝感冒发烧怎么办 2岁宝宝发烧38度怎么办 2岁宝宝反复发烧怎么办 八个月的孩子发烧怎么办 2岁宝宝咳嗽厉害怎么办 2岁宝宝睡觉咳嗽怎么办 2岁宝宝感冒咳嗽怎么办 2岁宝宝晚上咳嗽怎么办 1岁宝宝老便秘怎么办 2岁多宝宝老便秘怎么办 1岁半宝宝便秘怎么办 2岁宝宝拉稀黄水怎么办 二岁宝宝便秘该怎么办 2岁半宝宝拉稀怎么办 手机中了病毒该怎么办