一、简介
对于使用C语言开发的人来说,指针,大家都是非常熟悉的。数组,大家也同样熟悉。但是这两个组合到一起的话,很多人就开始蒙圈了。这篇文章,就详细的介绍一下这两个概念。
指针数组和数组指针,听起来非常像,但是两者是完全不同的概念。从名字上就可以知道,一个是数组,一个是指针。
那如何区分呢?
最简单的方法,就是根据语句中符号的优先级来。
优先级关系:( ) > [ ] > *。
有了这个概念后,我们再来看如下两个定义:
int *a[4];
int (*a)[4];
*a[4]语句中,因为优先级[ ] > *。所以,[ ]就是这个变量的“根”,即数组才是这个变量的本质。所以*a[4]就是数组。因为该数组前面加了取址符 * ,所以,它就是指针数组。
(*a)[4]语句中,因为优先级() > [],所以先看括号内的东西,是*a。这个一看就知道是个指针,所以,这个变量的本质就是个指针。又因为这个指针后面加了[ ]。所以,它是数组指针。
一句话,谁不重要,谁是定语。(中文语法不好的,回去补一补!)人话就是:本质是什么,最后两个字就是什么。
*a[4]本质是数组,那就是指针数组。(*a)[4]本质是指针,那就是数组指针。
好啦,概念搞懂了,接下来就是看看其含义了。
int *a[4]是个数组,那就要干数组的活。指针数组的意思就是,这个数组里面的元素都是指针。指针的类型是int,指向的内容也是int型。
int (*a)[4]是个指针,那就要干指针的活。数组指针的意思就是,这个指针,指向了长度为4的数组。这个数组的类型是int型。
好了,含义的概念也说完了。是不是感觉还是不懂。没关系,接下来用实例来对以上两个东西介绍一下。
二、指针数组
在上面的文章中说过,指针数组的本质是数组,数组内的元素都是指针。看如下这个例子
char a[] = "123";
char b[] = "456";
char c[] = "789";
char d[] = "012";
char *p[4];
p[0] = a;
p[1] = b;
p[2] = c;
p[3] = d;
这里定义了一个指针数组char *p[4]。数组内的元素都是指针,所以,把abcd四个地址赋值给指针。
上面的写法可以用下面这个写法来代替。
char *p[4] = {“123”,“456”,“789”,“012”};
那以如下的例子来详细讲解一下
char a[] = "123";
char b[] = "456";
char c[] = "789";
char d[] = "012";
char *p[4];
LOG_I(TAG,"&p[0] = %d, &p[1] = %d, &p[2] = %d, &p[3] = %d",p[0], p[1], p[2], p[3]);
p[0] = a;
p[1] = b;
p[2] = c;
p[3] = d;
LOG_I(TAG,"&a = %d, &b = %d, &c = %d, &d = %d",a, b, c, d);
LOG_I(TAG,"&p[0] = %d, &p[1] = %d, &p[2] = %d, &p[3] = %d",p[0], p[1], p[2], p[3]);
LOG_I(TAG,"p = %d, *p = %d, *(p+1) = %d, *(*(p+1)) = %c, *(*p+1) = %c, **p = %c",p, *p, *(p+1), *(*(p+1)), *(*p+1), **p);
LOG_I(TAG,"p[0] = %s, p[1] = %s, p[2] = %s, p[3] = %s",p[0], p[1], p[2], p[3]);
先看结果
可以看到,在赋值之前,p[0],p[1],p[2],p[3]的地址都是指向乱地址。赋值之后,地址就与abcd相同。
从上面的打印,我们可以看到,p与abcd变量的地址都不相同。按照数组的概念来说的话,数组p的地址应该与首元素相同。但是这里却不同,相反,*p的内容却与a变量的地址相同。由此我们可以得到如下结论,p实际上是一个地址的地址。而其内容才是元素的地址。再看**p的内容,是1。元素的内容为1,即指向a数组的内容。与我们的结论相同。
这里需要注意的是 *(p+1) , *(*(p+1)) , *(*p+1)。
刚才我们得到结论,p实际是一个地址的地址。而*p[4]的本质还是一个数组,那么p+1,实际上是指向了第二个元素地址的地址。取内容就是第二个元素的地址。从打印内容可以看到*(p+1)与第二个元素b的地址是相同的。
有了上面的结论,那就可以推断出, *(*(p+1))就是第二个元素的内容。从打印上可以看到,打印的结果是4,与b数组的第一个元素是能对应上的。
至于*(*p+1),上面结论是,*p是a数组的地址,其地址+1再取值,即a数组的第二个元素。其打印结果为2,也与结论相对应。
最后,打印各个元素指针所指向的内容,也就是abcd四个数组的内容。
因为p是数组,所以不能执行p++的操作。
说了这么多,可能会有人问,指针数组一般用在哪里?
比如说,我们在编程的时候某些地方需要做寻址操作,例如汉字或语音的寻址。因为这些内容的地址是不连续的,但是又不可能每次都去调用寻址。那么就可以一个指针数组,该数组内的指针元素对应各个不同地址的内容。后续我们只需要调用这个指针数组的各个元素,就能调用到不同的地址内容。同样的,这个指针数组各个元素也可以是函数指针,这样,就在同一个数组内,调用不同的回调函数,非常方便。
三、数组指针
数组指针的本质是指针,指向一个数组。
数组指针一般与二维数组配合使用。见如下例子
int (*p)[5];
int a[3][5] = {{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};
p = a;
LOG_I(TAG,"&a[0] = %d, a[0] = %d, &a[1] = %d, a[1] = %d, &a[2] = %d, a[2] = %d",&a[0], a[0], &a[1], a[1], &a[2], a[2]);
LOG_I(TAG,"&a = %d,&p = %d, p = %d, *p = %d, *(p+1) = %d, *(*(p+1)) = %d, *(*p+1) = %d, **p = %d",&a, &p, p, *p, *(p+1), *(*(p+1)), *(*p+1), **p);
p++;
LOG_I(TAG,"&a = %d,&p = %d, p = %d, *p = %d, *(p+1) = %d, *(*(p+1)) = %d, *(*p+1) = %d, **p = %d",&a, &p, p, *p, *(p+1), *(*(p+1)), *(*p+1), **p);
先看结果
先创建一个数组指针(*p)[5]。这个指针指向长度为5的数组。
把a的地址赋值给数组指针p。
跟一般的指针一样,&p与p是不同的。但是这里可以发现p与*p相同。一般来说,对于指针而言,p是地址,而*p是该地址的内容。这里看到p与*p相同,且都与a数组的地址相同。这里可以按照数组a与&a理解。
*(p+1)。与正常的指针一样,p+1即移动到下一个地址,不同的是因为该数组指针指向的是一个长度为5的数组,所以p+1则直接移动5个元素的长度。从打印的结果中也可以看到*(p+1)与 a[1]的地址相同。
*(*(p+1))。上述结论中,*(p+1)表示二维数组a[1]的地址,那么取值就是取该数组的值。为6.
*(*p+1)。上述结论中,*p是二维数组a[0]的地址,那么*p+1则为a[0]数组的第二个元素的地址。*(*p+1)则表示取值,为2。
**p。上述结论中,*p是二维数组a[0]的地址,那么*p则为a[0]数组的第一个元素的地址。**p则表示取值,为1。
因为p的本质是个指针,那么是可以进行p++的操作的。p++将直接移动到数组长度的位置,即从a[0]直接移动到a[1]。所以**p的值为6。