学习“数组指针”和“指针数组”

本文详细解析了数组指针和指针数组的概念,强调了它们在C语言中的优先级规则,并通过示例展示了它们在内存中的存储方式和类型转换的必要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在深入学习之前我们先理解:“数组指针”和“指针数组”这两个名词。

如何理解呢?这就需要我们在这个两个名词中间各加一个“的”字帮助理解-----》

        数组的指针:是一个指针,什么样的指针呢?指向数组的指针。

        指针的数组:是一个数组,什么样的数组呢?装着指针的数组。

然后,需要明确一个优先级顺序:() > [ ] > *,所以:

        (*p)[n]:根据优先级,先看括号内,则p是一个指针,这个指针指向一个一维数组,数组长度为n,这是“数组的指针”,即数组指针;

        *p[n]:根据优先级,先看[ ],则p是一个数组,再结合*,这个数组的元素是指针类型,共n个元素,这是“指针的数组”,即指针数组。

根据上面两个分析,可以看出,p是什么,则词组的中心词就是什么,即数组“指针”和指针“数组”。
 

在了解两个名词后,我们就要深入剖析学习指针数组和数组数组:

指针数组:int  *p1[5];
数组指针:int  (*p2)[5];

        首先,对于语句 int *p1[5],因为 “[ ]” 的优先级要比 “ * ” 要高,所以  p1  先与 “[ ]” 结合,构成一个数组的定义,数组名为 p1,而 “ int* ” 修饰的是数组的内容,即数组的每个元素。也就是说,该数组包含 5 个指向 int 类型数据的指针,如图 1 所示,因此,它是一个指针数组。 

                                                                          图1 

        其次,对于语句 int (*p2)[5],“( )” 的优先级比 “[ ]” 高,“ * ” 和  p2  构成一个指针的定义,指针变量名为  p2,而  int  修饰的是数组的内容,即数组的每个元素。也就是说,p2  是一个指针,它指向一个包含 5 个 int 类型数据的数组,如图 2 所示。很显然,它是一个数组指针,数组在这里并没有名字,是个匿名数组。

                                                                              图2 

        由此可见,对指针数组来说,首先它是一个数组,数组的元素都是指针,也就是说该数组存储的是指针,数组占多少个字节由数组本身决定;而对数组指针来说,首先它是一个指针,它指向一个数组,也就是说它是指向数组的指针,在 32 位系统下永远占 4 字节,至于它指向的数组占多少字节,这个就不能确定了,要看具体实际情况。

数组指针 (*p)[n]

数组指针:是指针-----》指向数组的指针。

看下面的例子进行理解:


#include <stdio.h>

int main()
{
	int a[5] = { 1, 3, 5, 7, 9 };	/*	一维数组	 */
	int(*p)[5];				        /*	步长为5的数组指针,即数组里有5个元素 */
	p = &a;							/*	把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身	*/

	//%p输出地址, %d输出十进制
	//\n回车
	//在C中,在几乎所有使用数组的表达式中,数组名的值是个指针常量,也就是数组第一个元素的地址,它的类型取决于数组元素的类型。
	printf("         a:%p\n", a);			/*	 输出数组名,一般用数组的首元素地址来标识一个数组,则输出数组首元素地址	 */
	printf("       a+1:%p\n", a + 1);
	printf("        &a:%p\n", &a);			/*	 &数组名,取出的是数组的地址。&数组名,数组名表示整个数组	*/
	printf("      &a+1:%p\n", &a + 1);
	printf("         p:%p\n", p);			/*	根据上面,p为数组a的地址,输出数组a的地址	*/
	printf("       p+1:%p\n", p + 1);
	printf("        *p:%p\n", *p);			/*	 *p表示数组a本身,一般用数组的首元素地址来标识一个数组	*/
	printf("     &a[0]:%p\n", &a[0]);		/*	a[0]的地址 */
	printf("     &a[1]:%p\n", &a[1]);		/*	a[1]的地址 */
	printf("      p[0]:%p\n", p[0]);		/*	数组首元素的地址 */
	printf("       **p:%d\n", **p);			/*	*p为数组a本身,即为数组a首元素地址,则*(*p)为值,当*p为数组首元素地址时,**p表示首元素的值1	*/
	printf("     *p[0]:%d\n", *p[0]);		/*	根据优先级,p[0] 表示首元素地址,则* p[0]表示首元素本身,即首元素的值1	 */
	printf("*(*p[0]+1):%d\n", *p[0]+1);		/*	根据优先级,p[0] 表示首元素地址,则* p[0]表示首元素本身,* p[0]+1 为首元素的值加1 即值为2	*/
	printf("   *(*p+1):%d\n", *(*p + 1));   /*	*p表示数组a本身,数组的首元素地址;(*p + 1)为数组首元素地址+1,即为数组a[1]的地址,取 * 解引用值为数组第二个元素3	*/
	printf("\n");

	//将二维数组赋给指针
	int b[3][4] = { {1,3,5,7},{9,11,13,15},{17,19,21,23} };
	int(*pp)[4];			/*	定义一个数组指针,指向含4个元素的一维数组	 */
	pp = b;					/*	 将该二维数组的首地址赋给pp,也就是b[0]或& b[0],二维数组中pp = b和pp = &b[0]是等价的	*/

	printf("         b:%p\n", b);				/*	输出数组名,一般用数组的首元素地址来标识一个数组,则输出数组首元素地址		*/
	printf("       b+1:%p\n", b + 1);
	printf("        &b:%p\n", &b);				/*	&数组名,取出的是数组的地址。&数组名,数组名表示整个数组	*/
	printf("      &b+1:%p\n", &b + 1);
	printf("        pp:%p\n", pp);				/*	根据上面,pp为数组b的地址,输出数组b的地址		*/
	printf("      pp+1:%p\n", pp + 1);			/*	pp+1,该语句执行过后pp的指向从行b[0][]变为了行b[1][],pp=&b[1]	 */
	printf("       *pp:%p\n", *pp);				/*	*pp表示数组b本身,一般用数组的首元素地址来标识一个数组	*/
	printf("     &b[0]:%p\n", &b[0]);			/*	b[0]的地址		*/
	printf("     &b[1]:%p\n", &b[1]);			/*	b[1]的地址	 */
	printf("     pp[0]:%p\n", pp[0]);			/*	数组第一行首元素的地址	*/
	printf("     pp[1]:%p\n", pp[1]);			/*	数组第二行首元素的地址	*/
	printf("      **pp:%d\n", **pp);			/*	*pp为数组b本身,即为数组b首元素地址,则*(*pp)为值,当*p为数组首元素地址时,**p表示首元素的值1		*/
	printf("    *pp[0]:%d\n", *pp[0]);			/*	根据优先级,pp[0] 表示第一行首元素地址,则* pp[0]表示第一行首元素本身,即首元素的值1	 */
	printf("    *pp[1]:%d\n", *pp[1]);			/*	根据优先级,pp[1] 表示第二行首元素地址,则* pp[0]表示第二行首元素本身,即首元素的值9	 */
	printf("*(*pp[0]+1):%d\n", *pp[0] + 1);	/*	根据优先级,pp[0] 表示数组第一行首元素地址,则* pp[0]表示数组第一行首元素本身,* pp[0]+1 为数组第一行首元素的值加1 即值为2	*/
	printf("   *(*pp+1):%d\n", *(*pp + 1));  /*	*pp表示数组b本身,数组的第一行首元素地址;(*pp + 1)为数组第一行首元素地址+1,即为数组b[0][1]的地址,取 * 解引用值为数组第一行第二个元素3	*/
	return 0;
}

根据上面二维数组可以得出,数组指针也称指向一维数组的指针,所以数组指针也称行指针。 

指针数组 *p[n]

指针数组:是数组-----》装着指针的数组。

看下面的例子进行理解:


#include <stdio.h>

int main()
{
	int a = 1;
	int b = 2;
	int* p[2];
	p[0] = &a;
	p[1] = &b;

	printf(" p[0]:%p\n", p[0]);		/* a的地址 */
	printf("   &a:%p\n", &a);		/* a的地址 */
	printf( "p[1]:%p\n", p[1]);		/* b的地址 */
	printf("   &b:%p\n", &b);		/* b的地址 */
	printf("*p[0]:%d\n", *p[0]);	/* p[0]表示a的地址,则*p[0]表示a的值 */
	printf("*p[1]:%d\n", *p[1]);	/* p[1]表示b的地址,则*p[1]表示b的值 */
	printf("\n");
	//将二维数组赋给指针数组
	int* pp[3]; //一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值
	int c[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12} };
	for (int i = 0; i < 3; i++) {
		pp[i] = c[i];
	}	
	printf("    &c:%p\n", &c);		/* c的地址 */	
	printf(" pp[0]:%p\n", pp[0]);	/* c[0][]的地址 */
	printf(" pp[1]:%p\n", pp[1]);	/* c[1][]的地址 */
	printf("*pp[0]:%d\n", *pp[0]);	/* pp[0]表示c[0][]的地址,则*pp[0]表示c[0][0]的值 */
	printf("*pp[1]:%d\n", *pp[1]);	/* pp[1]表示c[1][]的地址,则*pp[1]表示c[1][0]的值 */

	return 0;
}


  

最后,从上文来看:

        数组指针是一个指针变量,占有内存中一个指针的存储空间;

        指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间。

在了解指针数组和数组指针二者之间的区别之后,继续来看下面的示例代码:

int arr[5]={1,2,3,4,5};
int (*p1)[5] = &arr;
/*下面是错误的*/
int (*p2)[5] = arr;

以上的示例代码中可以看出,&arr 是指整个数组的首地址,而 arr 是指数组首元素的首地址,虽然所表示的意义不同,但二者之间的值却是相同的。那么问题出来了,既然值是相同的,为什么语句“int(*p1)[5]=&arr”是正确的,而语句“int(*p2)[5]=arr”却在有些编译器下运行时会提示错误信息呢(如在 Microsoft Visual Studio 2019 中提示的错误信息为 “ 警告  C4047  “初始化”:“int (*)[5]”与“int *”的间接级别不同 ”)?

   官方文档中解释了编译器报错的原因 :      

        在 C 语法中,赋值运算符 “ = ” 两边的数据类型必须是相同的,如果不同,则需要显示或隐式类型转换。在这里,p1 和 p2 都是数组指针,指向的是整个数组。p1 这个定义的 “ = ” 号两边的数据类型一致,而 p2 这个定义的 “ = ”号两边的数据类型不一致(左边的类型是指向整个数组的指针,而右边的数据类型是指向单个字符的指针),因此编译器会报错误信息。

以上主要摘取大牛博主优秀文章,如有侵权请联系我删稿!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值