指向数组的指针的理解
开发工具与关键技术:Visual Studio、C++
作者:张国军
撰写时间:2019年05月03日
数组和指针是两种不同的类型,数组具有确定数量的元素,而指针只是一个标量值。数组可以在某些情况下转换为指针,当数组名在表达式中使用时,编译器会把数组名转换为一个指针常量,是数组中的第一个元素的地址,类型就是数组元素的地址类型,如:
int a[5]={0,1,2,3,4};
数组名a若出现在表达式中,如int *p=a;那么它就转换为第一个元素的地址,等价于int p=&a[0];
再来一个:
int aa[2][5]={0,1,2,3,4, 5,6,7,8,9};
数组名aa若出现在表达式中,如int (p)[5]=aa;那么它就转换为第一个元素的地址,等价于int (p)[5]=&aa[0];
但是int (p)[5]=aa[0]; 这个就不对了,根据规则我们推一下就很明了了,aa[0]的类型是int [5],是一个元素数量为5的整型数组,就算转化,那么转化成的是数组(int [5])中第一个元素的地址&aa[0][0],类型是 int 。所以,要么是int (p)[5]=aa;要么是int (p)[5]=&aa[0];
只有在两种场合下,数组名并不用指针常量来表示–就是当数组名作为sizeof操作符或单目操作符&的操作数时,sizeof返回整个数组的长度,使用的是它的类型信息,而不是地址信息,不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量值的指针。
如对数组a,&a表示的是指向数组a的指针,类型是int () [5],所以int p=&a;是不对的,因为右边是一个整形数组的指针int ()[5],而p是一个整形指针int ;
数组的sizeof问题会在下面中仔细讨论。
二、数组与指针的下标引用
int a[5]={0,1,2,3,4};
如a[3],用下标来访问数组a中的第三个元素,那么下标的本质是什么?本质就是这样的一个表达式:(a+3),当然表达式中必须含有有效的数组名或指针变量。
其实a[3]和3[a]是等价的,因为他们被翻译成相同的表达式(顶多顺序不同而已),都是访问的数组a中的元素3。
指针当然也能用下标的形式了,如:int p=a; 那么p[3]就是(p+3),等同于3[p](不要邪恶。。。3P,3P),同样访问数组a中的元素3。
根据这一规则,我们还能写出更奇怪的表达式,如:
int aa[2][5]={0,1,2,3,4,5,6,7,8,9};
1[aa][2],这个看起来很别扭,首先 1[aa],就是(1+aa),那么1[aa][2]就是((1+aa)+2),也就是aa[1][2]。
1[2][aa],这个就不对了,因为前半部分1[2]是不符合要求的。
当然在实际中使用这样的表达式是没有意义的,除非就是不想让人很容易的看懂你的代码。
三、数组与指针的定义和声明
数组和指针的定义与声明必须保持一致,不能一个地方定义的是数组,然后再另一个地方声明为指针。
首先我们解释一下数组名的下标引用和指针的下标应用,它们是不完全相同的,从访问的方式来讲。
int a[5]={0,1,2,3,4};
int p=a;
对于a[3]和p[3]都会解析成(a+3)和(p+3),但是实质是不一样的。
首先对于a[3],也就是(a+3):
(1)把数组名a代表的数组首地址和3相加,得到要访问数据的地址,这里注意,数组名a直接被编译成数组的首地址;
(2)访问这个地址,取出数据。
对于p[3],也就是(p+3):
(1)从p代表的地址单元里取出内容,也就是数组首地址,指针名p代表的是指针变量的存储地址,变量的地址单元里存放的才是数组的首地址;
(2)把取出的数组首地址和3相加,得到要访问的数据的地址;
(3)访问这个地址,取出数据。
下面给出一个例子来说明若定义和声明不一致带来的问题:
设test1.cpp中有如下定义:
char s[]=“abcdefg”;
test2.cpp中有如下声明:
extern char *s;
显然编译是没有问题的。
那么在test2.cpp中引用s[i]结果怎样呢?如s[3],是‘d’吗?好像是吧
下面我们对test2.cpp中的s[3]进行分析:
s的地址当然是由test1.cpp中的定义决定了,因为在定义时才分配内存空间的;
我们根据上面给出的指针下标引用的步骤进行计算
(1)从s代表的地址单元的取出内容(4个字节),这里实际上是数组s中的前4个元素,这个值是“abcd”,也就是16进制64636261h,到这一步应该就能看出来问题了;
(2)然后把取出的首地址和3相加,得到要访问的数据的地址64636261h+3,这个地址是未分配未定义的;
(3)取地址64636261h+3的内容,这个地址单元是未定义的,访问就会出错。
我们来看一下例子
#include <iostream>
int main() {
int a[5] = {1,2,3,4,5};
int * p=a;
for (int i = 0; i < 5; i++)
{
cout << *(p+i)<<endl;
}
return 0;
}
这是我们一个简单int型的数组跟指针,我们int类型的指针指向的是一维数组,我们可以通过指针进行输出值,但是我们需要的操作是进行解址操作(),当然我们也可以通过下标来进行输出数值。
指针的话我们可以通过(p+i)进行查询我们数组里边的数值至于我们的i,应为数组的内存是连续的,所以我们需要查询下一个值的时候,我们只需要加上位移量就可以的到我们相对应的数值了。有一个值的注意的地方就是,当我们加的长度过长的时候我们所使用的那块内存不属于我们这个数值的时候,那么将会报错,所以我们使用的时候需要知道我们数组的长度。
#include <iostream>
int main() {
int a[5] = {1,2,3,4,5};
int * p=a;
for (int i = 0; i < 5; i++)
{
cout << a[i]<<endl;
}
return 0;
}
这个是我们下标继续输出的 结果和代码,从结果上我们可以看出并没有什么区别,所以我们
可以通过我们的指针或者下标方式对我们数组里边的值进行操作。
至于 各位需要用到什么方式去获得我们数组里边的值,那就看各位的需要了。
下边是我们指针指向我们的二维数组
#include <iostream>
int main() {
int a[5][2] = {1,2,3,4,5,6,7,8,9,0};
int * p=*a;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 2; j++)
{
cout << a[i][j]<<endl;
}
}
return 0;
}
这是我们二维数组使用下标方式进行输出。
当然他也是可以通过指针方式进行输出的,我这里呢就不给大家讲解了,需要大家自己去思考一下,
这是我对指向数组的指针的理解,如果大家有什么不同的想法呢可以一起分享,一起言论。