【C语言】指针数组和数组指针的区别
指针数组和数组指针的区别
指针数组
指针数组:指针类型的数组。指针数组可以说成是”指针的数组”,首先这个变量是一个数组。其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型。在 32 位系统中,指针占四个字节。
数组指针(也称行指针)
数组指针:指向整个数组的指针。数组指针可以说成是”数组的指针”,首先这个变量是一个指针。其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
根据上面的解释,可以了解到指针数组和数组指针的区别,因为二者根本就是两种类型的变量。
int *p[5]
和 int (*p)[5]
哪个是数组指针,哪个是指针数组呢?
int *p[5]
:指针数组
- 指针数组,是一维数组,每个元素都是int *;
- 指针数组经常结合二维数组使用,存储每行首元素的地址;
- 指针数组的本质是数组,这个数组中存储内容是指针而已。
- 定义
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 *arr[5]; //这里arr 是一个包含五个整数指针的数组
int a = 10, b = 20, c = 30, d = 40, e = 50;
arr[0] = &a;
arr[1] = &b;
arr[2] = &c;
arr[3] = &d;
arr[4] = &e;
这个指针数组中,地址分别为:1024,1032,1048,2036,3040;变量的值分别赋值为:10,20,30,40,50;那么,这个指针数组中存放的则是数组的指针,arr[0] 指向变量 a;
解引用指针数组,每个arr[index]
都保存了一个整数变量的地址,*arr[index]
将给出它们存储的值,即:arr[0] = 1024; *(arr[0]) = 10;
如要将二维数组赋给一指针数组:
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]
,所以要分别赋值。
int (*p)[5]
:数组指针
- 指向数组的指针;
- 本质是指针,指向的内容是含有 5 个元素的一维数组;
- 一维数组名取地址,或者二维数组名,都可以赋给它;
- 定义
int (*p)[n]
; ()优先级高,首先说明 p 是一个指针,指向一个整型的一维数组,这个一维数组的长度是 n,也可以说是 p 的步长。也就是说执行 p+1 时,p 要跨过 n 个整型数据的长度。
指针可以指向数组中的特定元素,与之类似,我们可以声明一个指向整个数组的指针:
int arr[5] = { 10 ,20 ,30 ,40 ,50 };
int (*ptr)[5];
ptr = &arr;
这是一个指向包含五个整数的数组的指针,我们取数组的基地址赋给指针 ptr,ptr 保存基地址 1024; * ptr
是第一个元素的地址:1024; ** ptr
则是第一个数组元素地址中的值:10。
所以,如要将二维数组赋给一指针,应这样赋值:
int a[3][4]; //3行4列二维数组
int (*p)[4]; //该语句是定义一个数组指针,指向含 4 个元素的一维数组。
p=a; //将该二维数组的首地址赋给 p,也就是 a[0]或 &a[0][0]
p++; //该语句执行过后,也就是 p=p+1;p 跨过行 a[0][]指向了行 a[1][],如下图所示
所以数组指针也称指向一维数组的指针,亦称行指针。
这样两者的区别就豁然开朗了,数组指针只是一个指针变量,本质上它是一个指针,似乎是 C 语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中 i 行 j 列一个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]
优先级:() > [] > *
指针数组和数组指针的内存布局
初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在 32 位系统下任何类型的指针永远是占 4 个字节。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下任何类型的指针永远是占 4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。
回到上面的问题,再来一次加深印象。下面到底哪个是数组指针,哪个是指针数组呢:
1、int *p1[10];
2、int (*p2)[10];
如果逻辑上理解起来较为复杂的话,我们来关注符号之间的优先级问题。(优先级:() > [] > *
)
int *p1[10];
“[]”的优先级比“*”要高。 p1 先与 “[]” 结合,构成一个数组的定义,数组名为 p1,int *修饰的是数组的内容,即数组的每个元素。所以很容易知道,这是一个数组,其包含 10 个指向 int 类型数据的指针,即指针数组。int (*p2)[10];
的情况就更好理解了,在这里“()”的优先级比 “[ ]” 高,“ * ”号和 p2 构成一个指针的定义,指针变量名为 p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。所以 p2 是一个指针,它指向一个包含 10 个 int 类型数据的数组,即数组指针。
换一种写法可能会更加直观,int (*)[10] p2
这里有个有意思的话题值得探讨一下:平时我们定义指针不都是在数据类型后面加上指针变量名么?这个指针 p2 的定义怎么不是按照这个语法来定义的呢?也许我们这样来定义 p2 更加直观:int (*)[10] p2; //这样写的话,编译器会报错
int ( *)[10]
是指针类型的数组,p2 是指针变量。其实数组指针的原型确实就是这样子的,只不过为了方便与好看把指针变量 p2 前移了而已。你私下完全可以这么理解这点。虽然编译器不这么想。^_^
假设有如下定义:
int a[1000];
int (*p)[100];
如果想把一维数组当作一个 10 x 100 的二维数组使用,那么可以进行如下强制转化:
p = (int (*)[100]) a; // 将a 切分为 10 x 100的二维数组
将数组 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 这个定义的“=”
号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。虽然 &a
和 a
的值一样,但意义不同。
既然现在清楚了 p3 和 p4 都是指向整个数组的,那 p3+1 和 p4+1 的值就很好理解了。根据指针类型及所指对象,表示指针大小,每次加 1,表示增加指针类型大小的字节。p3 + 1
跳过了整个数组的字节大小,而 p4 + 1
仅跳过一个 char
数据类型的字节大小。不过,这里编译会不通过,地址需要强制转换:
int main()
{
char a[5]={'a','b','c','d'};
char (*p3)[5]= &a;
char (*p4)[5]=(char (*)[5])a;
return 0;
}
int main()
{
int *ptr1 = (int *)(&a+1); //指针加上整个数组的字节大小+1
int *ptr2 = (int *)((int)a+1); //指针增加数组中首元素的地址+1
}
其内存布局如下图:
以上。仅供学习与分享交流,请勿用于商业用途!
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!