C语言指针数组和数组指针

前言

代码分析是由我自己根据代码和结果分析所得,可以将代码直接复制运行,按你自己的理解。


目录

前言

1.1一维数组

 1.2最普通的指针

1.3数组指针

1.4指针转为数组的写法 

一维数组与指针 

二维数组与指针

二维数组的大小

1.5指针数组 

1.6二维数组与指针

二维数组与指针的转换

​编辑

总结:


 

1.1一维数组

&是取地址符,用于取某个变量的地址,*是解引用,用于取出某一段地址的内容

代码如下

#include<stdio.h>
int main()
{
	int b[10] = { 1,12,3,4,5,6,7,8,9,10 };
	printf("b[0]的地址=%p\t", &b[0]);
	printf("b[1]的地址=%p\t", &b[1]);
	printf("b=%p\n", b);
	printf("*b=%p\n", *b);
	//printf("**b=%d\n", **b);//会报错:"*" 的操作数必须是指针,但它具有类型 "int"	

	printf("&b=%p\n", &b);
	printf("*&b=%p\n", *&b);//整个数组的地址,解引用变为首元素的地址,+1,即第一行的第二个数的地址
	printf("**&b=%p\n", **&b);
	//printf("***&b=%p\n", ***&b);//会报错:"*" 的操作数必须是指针,但它具有类型 "int"	
	printf("b+1=%p\n", b + 1);
	printf("*(b+1)=%p\n", *(b + 1));
	printf("*b+1=%d\n", *b + 1);//数组首元素地址,*b即第一个元素
	return 0;
}

运行结果 

638ae20af95f49b6be955bbcc171d26d.png

代码解释: 

  • 一维数组中,b表示数组首元素的地址,即b[0]的地址,而&b表示整个数组的地址,也用数组首元素地址来表示,也是b[0]的地址,所以b和&b的值是一样的,但意义不相同,*&b也表示首元素地址,即b,**&b第一个元素的那个整数值,这和*b是一样的,都是第一个元素的那个整数值,*和&相互抵消。
  • *(b+1),b是数组首元素地址,(b+1),则是b[1]的地址,等价于&b[1],*(b+1)是对地址解引用,即b[1],注意%p是以16进制输出,c表示十进制里的12
  • *b,对数组首元素地址解引用,取出地址内容,即b[0],*b+1,则是b[0]+1,输出2

 1.2最普通的指针

代码如下 

    int x = 3;
	int y = 4;
	int* px;
	int* py;

	px = &x;
	py = &y;
	//上面两个等价于int *px=&x; int* py=&y;

 

        指针变量的定义:数据类型*指针名,如int*px;表示指针其指向的类型是int(将变量旁边的*一起去掉则是指针指向的类型,指针自己的类型将变量名去除,这里是int*)

  1. &是取地址符,用于取某个变量的地址,*是解引用,用于取出某一段地址的内容
  2. px代表他命名了一个叫px的变量
  3. *代表他是一个一级指针
  4. int 代表这个指针指向的是一个int类型的变量
  5. 指针的使用:一开始就进行初始化:int*px=&x,或先声明再赋值:int *px; px=&x; 
  6. px表示他自己的内容,这个内容需要是一个地址或指向空(int*px=NULL),要不然会成为野指针,x表示一段内存名字,通过&来获取这段内存的地址,即&x。
  7. &用于取出一段内存的地址,用&变量名表示,如&px代表:取出px命名的那段内存的地址,即px的地址(指针也有他自己的地址,px存放的是他指向内容的地址)。
  8. *取出内容,如:当px这个指针指向了x后(int*px=&x),px存放的就是x的地址,*px表示取x地址的内容,即3,同样的*(&x),也是3  ,当然*&x不用加()反正都会抵消。

    1.3数组指针

        代码如下:

#include<stdio.h>
int main()
{
	int b[10] = { 1,12,3,4,5,6,7,8,9,10 };
	int(*pb)[10] = &b;//规范写法,取整个数组
	//int(*pb)[10] = b;//和上面得出结果是一样的
	//int* pb = &b;//所有1处,"*" 的操作数必须是指针,但它具有类型 "int",无论是b还是&b

	printf("&pb的地址%p\t", &pb);
	printf("b[0]的地址%p\t", &b[0]);
	printf("b[1]的地址%p\n", &b[1]);

	printf("pb的大小=%d\t", sizeof(pb));
	printf("*pb的大小=%d\t", sizeof(*pb));
	printf("pb+1的大小=%d\t", sizeof(pb + 1));//pb跳的字节数取决于int(*pb)[10] = &b,里面的10或其他数
	printf("*(pb+1)的大小=%d\n", sizeof(*(pb + 1)));
	printf("**(pb+1)的大小=%d\t", sizeof(**(pb + 1)));
	printf("*pb+1的大小=%d\n", sizeof(*pb + 1));
	printf("b[0]的大小=%d\t", sizeof(b[0]));
	printf("&b的大小=%d\t", sizeof(&b));
	printf("&b[0]的大小=%d\t", sizeof(&b[0]));
	printf("b的大小=%d\n", sizeof(b));

	printf("pb=%p\t", pb);//指针的内容,整个数组地址
	printf("*pb=%p\t", *pb);//整个数组地址的内容,首元素的地址
	printf("**pb=%p\t", **pb);//
	printf("**pb=%d  ", **pb);//首元素地址的内容,首元素的整数值----1

	printf("pb+1=%p\n", pb + 1);//整个数组的地址+1,类似于二维数组,地址是第二行的地址,跳跃40个字节
	printf("&pb+1=%p\n", &pb + 1);//自己的地址跳跃,8个字节
	printf("*(pb+1)=%p\n", *(pb + 1));//第二行地址,再解引用,是首元素地址
	printf("**(pb + 1)其值?=%d\t", **(pb + 1));//首元素的整数值-------1
	printf("**(pb + 1)地址?=%p\n", **(pb + 1));//首元素的整数值------1
	//printf("***(pb + 1)=%d\t", ***(pb + 1));//元素再进行解引用,无法运行

	printf("*pb+1=%p\t", *pb + 1);//整个数组地址解引用,首元素地址,加1,第二个元素地址
	printf("*(*pb+1)=%d\n", *(*pb + 1));//-------1
	return 0;
}

 运行结果:

75d00ce4f1164df2abc4ee9b3c721b8a.png  你注意到*(pb+1)为什么是40个字节,而pb+1是8个字节吗?等会告诉你。 

结果分析 

  • 数组指针的表示:int(*pb)[10],[]的优先级高于*,加上()pb先于*结合表明他是一个指针,再于[10]结合表示指向10个元素,最后与int结合,也就代表他是指向装有10个整型元素的数组指针,是一个指针,指针4个或8个字节(看所在环境)。int(*pb)[10]=b,可以用,但int(*pb)[10]=&b是更为规范的写法,前者表示指向首元素地址,而后者表示指向整个数组。数组名(b)和&数组名(&b)尽管他们值一样,但在一些地方产生结果不同。
  • 代码里的字节大小:所有代码都在64位下编译运行的,所以pb指针所占的字节数是8个字节,(若在32位下,则是4个字节)数组有10个元素且为整型,该环境下int型占4个字节,则b数组的大小是40个字节.
  • 由图对b的大小进行计算结果也可以看出,b,&b,&b[0]的值是一样的,但是数组名是首元素地址&数组名代表表整个数组,这里有个特别注意的点:sizeof(数组名),这里的数组名不再是首元素地址,而是表示整个数组,所以表示的是整个数组所占字节的大小,计算得40,且这个必须为数组名,不可以添加任何东西,这是一个特例,而sizeof(&数组名)是地址的大小,和指针一样8个字节由下可以验证:

代码如下

#include<stdio.h>
int main()
{
		int a[] = { 1,2,3,4 };
		printf("a=%d\n", sizeof(a));//16,数组整大小 
		printf("a + 0=%d\n", sizeof(a + 0));// 4/8,首元素地址
		printf("*a=%d\n", sizeof(*a));// 4/8,a是首元素地址,*a首元素的字节大小
		printf("a + 1=%d\n", sizeof(a + 1));// 4/8       
		printf("a[1]=%d\n", sizeof(a[1]));// 4/8 第二个元素大小
		printf("&a=%d\n", sizeof(&a));// 4/8,地址的大小就是四个或八个字节
		printf("*&a=%d\n", sizeof(*&a));// 16,&a是数组的地址,数组的地址解引用,即数组的大小,和a效果一样
		printf("&a + 1=%d\n", sizeof(&a + 1));// 4,数组地址的大小即4/8
		printf("&a[0]=%d\n", sizeof(&a[0]));// 4/8,第一个元素地址
		printf("&a[0] + 1=%d\n", sizeof(&a[0] + 1));  //第二个元素地址						
	
	
	return 0;
}

结果如下 

 1d0ab90e2fbc42d69d5e91452877bdbe.png


由结果图对pb的一系列解释:

  • pb:其指向的应该是整个数组,以数组首元素地址的值表示,所以pb等价于&b,sizeof(pb)是个地址,8个字节(sizeof(&b))
  • *pb:对于pb,加*是找这个指针的指向,因为这个指针应该有10个指向,加上*则是首元素地址(*pb),等价于b,所以sizeof(*pb),40个字节,再如同下方首元素地址+1,是第二个元素的地址。注意:*(pb+1)与其不同
  • **pb:对首元素地址再解引用为数组元素值
  • *pb+1:在首元素地址处再+1,为第二个元素地址,即b[1]的地址(图中有)
  • *(*pb+1):由*pb可推得是b[1]
  • 对于二维数组,可以将每一行当作整体看作一个元素,使二维变一维,若取b[0][0],我们需要先找出你要去那一行,以*pb为例,*pb表示找第一行,再要找第一行的地址,则用一维的方式找元素就行,如,将(*pb)看作一维数组,再解引用*(*pb),得出的就是1,后面会用一个二维数组继续进行验证
  • pb+1:这里要将b看作二维数组,第一行是b这个数组里的10个数,第二行未初始化,pb没有解引用又进行加1,而pb指向含10个元素的数组,则要跳一个b数组的大小,即跳到数组的下一行,由上面代码结果得出,b[0]的地址与(pb+1)的地址刚好差40(在16进制中640-618==40),即10个int大小
  • *(pb+1):如上述,在第二行解引用,是第二行的首元素地址,相当于b,所以sizeof(*(pb+1))大小是整个数组的大小,40字节,注意:内存是一维的而不是二维的,所以跳跃10个元素后,pb的地址与b[0]的地址刚好差40,因为刚好跳到了b[10]的下一个位置。(本文为了方便,而看作二维)
  • **(pb+1):则是首元素的值,而b数组只定义了10个元素,没有对这个位置进行初始化,所以这个位置的值是一个垃圾值
  • 由上文*(pb+1)是第二行的首元素地址,相当于sizeof(b[1])(要将b数组看作二维,b[1]是第二行的数组名,后面有代码与结果对二维的进行验证),表示那一行的大小,所以是40个字节,而pb+1是这个b的下一行整个数组的位置,相当于sizeof(&b[1]),地址是8个字节。
  • 由此也可得int(*pb)[n],这里的n代表了,他要跨越几个字节,下面进行验证

代码如下

#include<stdio.h>
int main()
{
	int b[10] = { 21,12,23,24,25,26,27,28,29,10 };
	int(*pb)[3] = &b;

	printf("&pb的地址%p\t", &pb);
	printf("b[0]的地址%p\t", &b[0]);
	printf("b[1]的地址%p\n", &b[1]);
	printf("b[2]的地址%p\n", &b[2]);
	printf("b[3]的地址%p\n", &b[3]);

	printf("pb大小=%d\t",sizeof(pb));
	printf("*pb大小=%d\n",sizeof(*pb));

	printf("pb+1=%p\n", pb + 1);
	printf("&pb+1=%p\n", &pb + 1);
	printf("*(pb+1)=%p\n", *(pb + 1));
	printf("**(pb + 1)=%d\n", **(pb + 1));
	printf(" *(*(pb + 1)+1)=%d\n", *(*(pb + 1) + 1));
	printf("*(*(pb + 1)+2)=%d\n", *(*(pb + 1) + 2));
	printf("*pb+1=%p\t", *pb + 1);
	printf("*(*pb+1)=%d\n", *(*pb + 1));
	return 0;
}

代码结果 

5709c4ae346c4a73a5236ba3ebb1755e.png

 

代码解释: 

  • **(pb+1):pb+1,因为int(*pb)[3] = &b,所以一次跳3个元素,将这个过程,看作对b数组的分割,pb+1存放地址以b[3]的地址作整个地址(pb+1代表指针的内容,即指向的地址),*(pb+1),取出首地址,即b[3]的地址,**(pb+1)为b[3]
  • &pb+1是指针自己的地址去跳跃,跳8个字节
  • 其他的结果也是一样的去推,不再做解释
  • 总结:pb+i,i由指针决定,int(*pb)[n],n为多少就跳跃多少位置


1.4指针转为数组的写法 

一维数组与指针 

代码如下: 

#include<stdio.h>
int main()
{
	printf("一维数组与指针的转换\n");
	int a[10] = { 5,9,7,8,1,23,12,4,45,12};
	int* p= &a;
	int(* pa)[1] = &a;
	
	printf("指针写法\n\n");
	for (int i = 0; i < 10; i++)
	{
		//两种方法均可
		printf("p[%d]=%d  ", i, p[i]);
		printf("第%d个元素为=%d\n\n", i, *(p+i));
	}

	printf("数组指针的写法\n\n");
	for (int i = 0; i < 10; i++)
	{
		printf("pa[%d]=%-4d   ", i, *pa[i]);
		printf("pa[%d]=%-4d   ", i, (*pa)[i]);
		printf("第%d个元素为=%d  ", i, *(*pa+i));
		printf("第%d个元素为=%d\n", i, **pa+i);//错误写法
	}
	return 0;
}

运行结果:

2b913c9a61a14d3c80509e9d08b846a1.png

 

结果分析:

  • 在普通指针中: *(p+i)等价于p[i],等于a[i]
  • 在数组指针中:由于要进行一次解引用,所以 *pa[i]才是a[i]
  • **pa+i是每次循环将b[0]进行+i,没有产生地址的跳跃

二维数组与指针

        二维数组要先找到哪一行,即要* 。(前面说的很清楚,可以自己将代码复制,进行验证)

代码如下

#include<stdio.h>
int main()
{
	int b[3][5] = { {3,2,5,117,9},
			{12,16,11,22,112},
			{66,23,56,21,18} };
	int(*pb)[5] = &b;
	//int* pb = &b;//错误写法
	printf("b[0][0]=%p  ", &b[0][0]);
	printf("b[0][1]=%p  ", &b[0][1]);
	printf("b[1][0]=%p\n", &b[1][0]);

	//printf("pb[0]=%p  ", pb[0]);
	//printf("pb=%p  ", pb);
	//printf("*pb=%p  ", *pb);//
	//printf("**pb=%d\n", **pb);

	printf("pb[0][3]=%d  ", *(pb[0] + 3));
	printf("pb[2][3]=%d  ", *(*(pb+2)+3));//注意下标

	printf("\n\n");
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("p[%d][%d]=%d\t",i,j,pb[i][j]);

			//printf("pb[%d][%d] = %p\t", i, j, *(pb + i) + j);//错误写法,输出为各个b的地址

			//正确写法
			//printf("pb[%d][%d] = %d\t", i,j,*(pb[i]+j));
			//printf("pb[%d][%d] = %d\t", i,j,*((*pb+i)+j));
		}
		printf("\n");
	}

	return 0;
}

 结果如下

290ef85e07e94865b964af3db7ae5d04.png 

 不过,你甚至可以这么写int(*pb)[3][5] = &b;,然后用***pb取出3和其他数,若你想让你的代码看起来更加复杂,大可一试。

二维数组的大小

 代码如下

#include<stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));  // 48
	printf("%d\n", sizeof(a[0][0]));// 4
	printf("%d\n", sizeof(a[0]));// 16 a[0]相当于第一行的数组名,数组名首元素地址,计算第一行的大小
	printf("%d\n", sizeof(a[0] + 1));//4  没有&,即首元素地址加1,第一行第二个的地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4 是第一行第二个元素大小
	printf("%d\n", sizeof(a + 1));//4 a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以是首元素地址
	//即a是第一行地址,则本题是第二行地址
	printf("%d\n", sizeof(*(a + 1)));// 16 即第二行的大小
	printf("%d\n", sizeof(&a[0] + 1));//4 第二行地址  第一行地址加1
	printf("%d\n", sizeof(*(&a[0] + 1)));//16  第二行大小
	printf("%d\n", sizeof(*a));//a是首元素地址,即本题计算第一行大小
	printf("%d\n", sizeof(a[3]));//16 不会去访问数组内的大小

	return 0;
}

 代码结果

6c719618345f49eab58b5bd590857acf.png  

  •  注意:只有sizeof(数组名)才是整个数组的大小,如:sizeof(a)是3*4*4,48个字节,而sizeof(a[3]),虽然那个位置没有被初始化,但能够访问,代表取那一行整个数组的地址,4*4,16个字节(这时可以将a[3]单纯的看作一个一维数组名)

 

1.5指针数组 

代码如下

#include<stdio.h>
int main()
{
	int a1[5] = { 112,31,432,21,13 };
	int a2[5] = { 21,13,1231,23,21 };
	int a3[5] = { 123,42,345,7,1 };
	//int* p[3] = { a1,a2,a3 };//定义了一个名称为p的指针数组,该数组有三个元素,
	int* p[3] = { &a1,a2,a3 };//定义了一个名称为p的指针数组,该数组有三个元素,
	//每个元素的类型为int*,即每个元素都是基本类型为int的指针

	printf("a1地址%p\t", a1);
	printf("a2地址%p\n", a2);
	printf("\n\n");

	printf("*p=%p\t", *p);
	printf("**p=%d\n", **p);
	printf("*p+1=%p\t", *p + 1);
	printf("*(*p + 1)=%d\n", *(*p + 1));//a1的第二个元素

	printf("*p + 1字节=%d\n", sizeof(*p + 1));//第一行地址,即首元素地址+1,即第二个元素地址,为8个字节
	printf("p大小是%d\n",sizeof(p));
	printf("p[0]大小是%d\n",sizeof(p[0]));
	printf("&p大小是%d\n",sizeof(&p));
	printf("*p大小是%d\n",sizeof(*p));

	printf("*(p+1)=%p\t", *(p + 1));
	printf("p+1=%p\n", p + 1);

	printf("\n\n");
	printf("*p[0]=%d\t\t", *p[0]);
	printf("*p[1]=%d\t", *p[1]);
	printf("*p[0]+1=%d\n", *p[0] + 1);
	printf("p[0]=%p\t", p[0]);
	printf("p[1]=%p ", p[1]);

	printf("\n\n");
	for (int i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//都是对的写法
			//printf("%-4d  ", *(p[i] + j));
			//printf("%-4d  ", p[i][j]);
			printf("%-4d  ", *(*(p+i)+j));//注意与数组指针的差别:printf("pb[%d][%d] = %d\t", i,j,*((*pb+i)+j));
		}printf("\n");
	}

	return 0;
}

 运行结果

06084a07c9fe4a28ac7265bf1dfa53ef.png

 

结果分析: 

  • int* p[3] = { a1,a2,a3 };,[]的优先级高于*,则该语句表示一个数组,数组有3个元素,数组元素类型是int*,所以int* p[3] = { &a1,a2,a3 };也是一样的。
  •  *(*(p+i)+j):由于这是一个数组,所以p是p这个数组的首元素地址,p+i则是这个数组的第i个元素的地址,*(p+i)是第i个元素的值,是指针数组存放的第i个数组的地址,用这个数组的首元素地址来表示,*(p+i)+j,则是在某个数组的首地址的基础下加j,是这个数组的第j个元素的地址,**(*(p+i)+j)对地址解引用,就是这个数组的第j个元素的值

1.6二维数组与指针

二维数组与指针的转换

代码如下

#include<stdio.h>
int main()
{
	
	int b[3][5] = { {3,2,5,117,9},
			{12,16,11,22,112},
			{66,23,56,21,18} };
	printf("b[0][0]=%p  ", &b[0][0]);
	printf("b[0][1]=%p  ", &b[0][1]);
	printf("b[1][0]=%p\n", &b[1][0]);
	printf("b[1][1]=%p  ", &b[1][1]);

	printf("b=%p\t",b);
	printf("&b=%p\n",&b);

	printf("*b=%p\t", *b);
	printf("**b=%d\n", **b);

	printf("&b[0]=%p\t",&b[0]);
	printf("b[0]=%p\t",b[0]);
	printf("*b[0]=%d\n\n",*b[0]);

	printf("&b[1]=%p\t", &b[1]);
	printf("b[1]=%p\n", b[1]);

	printf("b+1=%p\t", b + 1);
	printf("&b+1=%p\n", &b + 1);//地址与b[0]相差60个字节,即整个数组+1

	//printf("&(b+1)=%p\t", &(b + 1));//错误&必须作用于一个左值,而(b+1)是一个表达式,不是一个合法的左值
	printf(" *(b + 1)=%p\t", *(b + 1));
	printf(" **(b + 1)=%d\t", **(b + 1));
	printf("*(b+1)+1=%p\t", *(b + 1) + 1);
	printf("*(*(b + 1) + 1)=%d\n",*(*(b + 1) + 1));
	printf("*b+1=%p\t", *b + 1);
	printf("*((*b + 1)+2)=%d\t", *((*b + 1)+2));

	return 0;
}

代码结果 

8bfe98b1b0594004bdd13236d7681775.png

 

结果分析 

  • 在二维数组中,第一行就类似于一维数组的第一个元素,b表示首元素地址,*b表示默认取第一行的地址,&b是整个数组的地址,**b表示首元素地址的内容b[0][0]
  • b[0]相当于*b,取第一行地址,*b[0]相当于**b
  • b+1,在第一行位置上再跳一行,是第二行的地址,*(b+1)默认取第二行的首地址,**(b+1)就是12
  • *b+1,取第一行地址,即b[0][0]的地址,再+2是b[0][2]的地址,再用*,就是117
  • *(b+1)+1,第二行首地址+1,是b[1][1]的地址,则*(*(b+1)+1)是b[1][1],值为16
  • 第一行地址+1,则是第二行的地址,(*&b+1),即第二行地址,*(*&b+1)第二行首元素地址,**(*&b+1)第二行第一个元素的那个整数值。
  • *&b功能抵消,其实就是b

总结:

  • 在数组中&数组名,如&b,就是整个数组的地址以首元素地址表示,数组名就是首元素地址
  • 一维数组中,&b表示整个数组的地址,*&b表示首元素地址,**&b第一个元素的那个整数值,等价于*b
  • 二维数组中,&b表示整个数组的地址,也是第一行的地址,也是第一行第一个元素的地址,&b[1]是整个第二行的地址,b[1]+1是第二行第二个元素的地址,若*&b是默认找第一行地址,**&b是首元素地址,***&b才是第一行第一个元素的那个整数值
  • 二维数组里我们首先需要明确自己要哪一行所以首先要b+i,找到第i行的地址,用第i行首元素地址来表示,*(b+i)得到第i行首元素地址,*(b+i)+j得到第i行第j个元素的地址,*(*(b+i)+j)得到第i行第j个元素。

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值