指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
下面哪个是数组指针,哪个是指针数组呢:
int *p1[10];
int (*p2)[10];
这里需要明白一个符号之间的优先级问题:
int *p1[10] : “[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。
int (*p2)[10]: 在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。下面这幅图可以帮助我们很好的理解他们:
2.理解数组名和数组指针变量
看下面的代码:
int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[5] = &a;
char (*p4)[5] = a;
return 0;
}
&a 是整个数组的首地址,而a是数组首元素的首地址,其值相同但意义不同,这在上一篇博文中已经讲到过。p3 这个定义的“=”号两边的数据类型完全一致,都是指向char型数组的指针,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual studio 2010编译器中无法编译成功。
再看下面这段代码:
int main()
{
int a[5] = {0,1,2,3,4};
int (*p)[5] = &a;
cout<<&a<<endl;
cout<<&a+1<<endl;
cout<<a+1<<endl;
cout<<p<<endl;
cout<<p+1<<endl;
return 0;
}
这里面的a,&a,&a+1,a+1,p,p+1究竟是指向哪里呢?
上面代码的输出结果为:
&a: 001EFC9C
&a+1: 001EFCB0
a+1: 001EFCA0
p: 001eFC9C
p+1: 001EFCB0
分析结果可得出:
a: 是一个数组名,类型是指向int型整数的指针,不是变量,a的值是指针常量,即不能有a++或者a=p这些操作。a指向这块连续空间的首地址。
&a: &a是指向这个一位数组的首地址。
&a+1: 前面我们讲过,指针加上一个数,并不是真是的加上这个数,而是加上这个数乘以指针类型多占的空间,即 sizeof(&a),而&a占据了20byte(5*sizeof(int)),所以&a+1得到了如上的结果。
a+1:a在直接做加法运算时转化成了指针变量,而不再时数组名,此时a代表的含义是指向数组首元素的地址,因此a+1,实际是在a地址的基础上加了sizeof(a),而这里的a的类型是指向int型的指针,大小是sizeof(int)。
p: 是一个数组指针变量,有点类似于数组名的概念,不同的是它是变量,可以执行p++;p=a等操作。
p+1:这就很好理解了,p是指向数组的指针,因此p+1其实就是p的地址加上sizeof(&a),等同于&a+1。
再看看下面这段代码:
int main ()
{
int a[2][2] = {1,2,3,4};
int (*p)[2];
p = a;
cout<<&a<<endl; // 001CFCC0
cout<<&a[0]<<endl; // 001CFCC0
cout<<a[0]+1<<endl; // 001CFCC4
cout<<&a[1]<<endl; // 001CFCC8
cout<<p<<endl; // 001CFCC0
cout<<(a+1)<<endl; // 001CFCC8
cout<<(&a+1)<<endl; // 001CFCD0
cout<<*(a+1)<<endl; // 001CFCC8
cout<<**(a+1)<<endl; // 3
cout<<(p+1)<<endl; // 001CFCC8
cout<<*(p+1)<<endl; // 001CFCC8
cout<<**(p+1)<<endl; // 3
return 0;
}
首先我们认识到a时一个二维数组,其实也就是一个特殊的额一位数组,只不过其每一位上的元素又是一个数组。
还要注意的是这里p跟上面那段代码里一样,是一个指向int型数组的指针,但是不一样的是,对p进行初始化的语句似乎有所改变,上面那段代码是这样的p = &a,而这里直接是 p = a,也可以这样 p = &a[0];其实这种改变很容易理解,一切看类型,p是指向一位数组的指针,而在上一段代码中,a就是表示的一位数组的数组名,因此p = &a是合适的,而在这段代码中,a是二维数组,直接取地址p = &a是不妥的,类型上不匹配,而p = a,此处的a代表的是二维数组的第一个元素,也就是一位数组,这样就合适了,同理p = &a[0] 其实是显示的指出p指向的是二维数组的第一个元素。
有了上面的解释,这里代码的输出应该很好理解了。
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]值是一样,但类型不一样,表示的意义不一样。
3 数组名与数组指针变量的区别
从以上分析中得出数组名是指针,类型是指向元素类型的指针,但值是指针常量,声明数组时编译器会为声明所指定的元素数量保留内存空间。数组指针是指向数组的指针,声明指针变量时编译器只为指针本身保留内存空间。
再看下面这段代码
int main()
{
int a[2][2] = {1,2,3,4};
int (*p)[2];
p = a;
cout<<sizeof(a)<<endl; // 16
cout<<sizeof(p)<<endl; // 4
cout<<sizeof(a+1)<<endl; // 4
cout<<sizeof(p+1)<<endl; // 4
cout<<sizeof(a+0)<<endl; // 4
cout<<sizeof(p+0)<<endl; // 4
cout<<sizeof(a[0])<<endl; // 8
}
1. 当sizeof用于数组名时,返回整个数组的大小(这里的大小指占用的字节数)。p是一个指针变量,这个变量占用4个字节。而a是数组名,所以sizeof a返回数组a中的全部元素占用的字节数,因此输出16。
2. 从结果中看出,a在做+运算时是转化成了指针变量,此时a+i的类型是一个指针变量,而不是一个数组名。但a[i]是一个一维数组的数组名,sizeof(a[0])的值是8。
再看下面这段代码
void f(int a[][2])
{
cout<<sizeof a<<endl;
}
void main()
{
int a[2][2]={1,2,3,4};
cout<<sizeof a<<endl; // 16
f(a); // 4
}
这是因为传参的时候数组名转化成指针变量,注意到函数f中f(int a[][2])这里并不需要指定二维数组的长度,此处可以改为int (*a)[2]。所以传过来的就是一个数组指针变量。
本篇博文部分内容出自以下博客,写的都很好:
http://c.biancheng.net/cpp/html/476.html
http://blog.csdn.net/touch_2011/article/details/6966980
个人水平有限,如有错误,敬请指正!