数组的传参(基于指针)

一维数组名与一维数组地址的区别

 

1.一维数组名的含义

  • 假设我们先创建一个数组a,a就是这个数组的名称,它代表着a这个数组首元素的地址 
#include<stdio.h>
int main()
{
    int a[10] = { 0 };
    printf("%p\n", a);
    printf("%p", &a[0]);
    return 0;
}

  • 可以看到我们打印出a和a[0]的地址是同一个值,证明了a这个数组名代表的是数组首元素的地址

2.一维数组地址的含义

  • 既然a代表了数组首元素的地址,那我们是不是会想,那a这个一维数组的地址是不是也是和首元素的地址相同呢?
  • 我们可以让编译器帮我们一下
  • 或许你想写出下面这样的代码来判断一维数组的地址与首元素地址是否不同,并且得出了相同的结果
#include<stdio.h>
int main()
{
	int a[10] = { 0 };
	int* p = &a;
	printf("%p\n", p);
	printf("%p", &a[0]);
	return 0;
}

  • 可以看到,我创建了一个指针变量p存放数组a的地址,然后打印出p和&a[0],结果仍然相同,这是否说明这二者是一致的呢?
  • 我得非常遗憾的告诉你,这样不能证明二者的地址一致,因为一个指针的误用。
  • int*p=&a;这一条语句编译器没有报错,但是你想要存放一个数组的地址的话,是不能这么使用的
  • 指针p前面的类型为int,代表它要存放的是一个整型的地址,而a是一个整型数组,用存放一个整型地址的指针去存放一个整型数组的地址,很明显是错误的。
  • 那么应该如何存储一个数组的地址呢?
  • 通过 int (*p)[10]=&a; 这一条语句就可以存储数组a的地址,我们来仔细说说为什么要这么写
  • 首先我们知道*与[]的优先级是[]在前,*在后,为了避免p先于[]结合,我们将*p用括号括起来,表明p是一个指针变量,然后后面跟着的[10]代表了这个指针变量是指向一个数组,再结合前面的int,代表着p是存放一个能存放10个整型的数组的地址,换句话说,p存放着一个数组的地址,而这个数组里面每个元素的类型是int。
  • 有了这些知识,我们就能储存一个数组的地址了,存放这个数组地址的指针变量p,我们称它为数组指针。
  • 再来看一段代码运行的结果。
#include<stdio.h>
int main()
{
	int a[10] = { 0 };
	int (*p)[10] = &a;
	printf("%p\n", a);
	printf("%p", p);
	return 0;
}

  • 我们看到,这两个地址仍然一致,那这说明了一维数组地址与首元素地址相同吗?
  • 其实,它们还是有点不一样
  • 再来看看下面的运行结果。
#include<stdio.h>
int main()
{
	int a[10] = { 0 };
	int (*p)[10] = &a;
	printf("%p\n", a);
	printf("%p\n", a+1);
	printf("%p\n", p);
	printf("%p", p + 1);
	return 0;
}

  • 我们可以发现,a+1与p+1的结果是不一样的,a+1比a的地址大4,说明让a加1走过了一个整形的大小
  • 而p与p+1差了28,这是十六进制的28,转化为十进制就是40,这40不就是a这个数组的大小吗
  • 这说明了一维数组地址与一维数组名含义的不同,虽然刚开始的地址都为首元素的地址,但是能访问的大小不同,让a+1一次访问4个字节,也就是1个int类型,而让p+1一次会访问40个字节,走过了10个 int类型,也就是走过了一个a数组的大小
  • 可能你说为什么非要引进来数组指针这个东西,这是为了后面二维数组的传参作铺垫,要是想要最简单的理解,我们可以这么写代码
#include<stdio.h>
int main()
{
	int a[10] = { 0 };
	int (*p)[10] = &a;
	printf("%p\n", a);
	printf("%p\n", a + 1);
	printf("%p\n", &a);
	printf("%p", &a + 1);
	return 0;
}

  • 可以看到&a+1与&a的差值也为40,a+1与a地址的差值仍然为4,说明了二者的不同,也佐证了我们前面数组指针的使用是没有错误的。
  • 总结一下,&a与a虽然值一样,但意义却不同,&a代表的是数组的地址,而不是数组首元素的地址,数组的地址+1,会跳过一个数组的大小,所以&a+1与&a的差值是40

二维数组名的含义

  • 说完了一维数组名的含义,我们再来讲讲二维数组名的含义,至于二维数组的地址的含义,暂且不谈。
  • 一维数组名代表的首元素的地址,那么二维数组呢?既然都是数组,是不是一样的呢,没错,二维数组名代表的也是其首元素的地址,但是这个首元素和一维数组的首元素有些不同。
  • 我们可以将二维数组看成一堆一维数组的排列,二维数组的每一行单独拿出来都是一个一维数组,这里说的二维数组首元素的地址其实就是将二维数组的第一行单独拿出来的一维数组的地址
  • 刚刚我们了解到,一维数组的地址可以用数组指针存储,所以现在我们也能用数组指针存放我们的二维数组首元素的地址。
  • 下面一段代码能加深你对其的理解
#include<stdio.h>
int main()
{ 
	int i = 0;
	int j = 0;
	int a[3][5] = { { 0,1,2,3,4 } ,{5,6,7,8,9},{1,3,5,7,9} };
	int (*p)[5] = a;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(a + i) + j));
		}
		printf("\n");
	}
	return 0;
}

  • 我们看到代码的第七行,int (*p)[5],方括号中写的是5,代表将二维数组的一行抽出来后一维数组的大小,我们创建了一个数组指针存储a,我们把数组a的第一行单独拿出来叫做a[0],我们其实是存储了a[0]这个数组的地址
  • 我们再看到第12行,a+i中i代表着移动的行数,而a代表着a[0]这个一维数组的地址,移动1次就到了a[1]的地址,就是二维数组第1行(行数从0开始)单独抽出来的一维数组的地址,我们再对其进行解引用操作,
  • *(a+i)就能表示a[i]这个数组名,也就是第i行首元素的地址,再对其进行j的便宜,我们就找到了第i行第j列元素的地址,再进行解引用操作,就能取到a这个数组的第i行第j列元素,与a[i][j]等价;

指针数组与数组指针

1.指针数组

  • 上面提到了数组指针,现在我们再来说说指针数组,并分析一下二者的区别,避免使用错误
  • 数组指针,指针在后面,数组是修饰指针的,说明是一个指向数组的指针,指针数组,指针修饰数组,说明是存放指针的数组
  • 上文中我们用int (*p)[10]创建了一个数组指针,为了避免p先与[]结合,我们在*p与[]使用了(),但如果不使用(),直接写成int *p[10]; 这将会创建一个指针数组,p先于[]结合,表明p是一个数组,再看前面的int *,表明p数组中的元素是int *,即存放整型地址的指针。
  • 那指针数组该如何使用呢?很简单,我们用它存放许多的地址
#include<stdio.h>
int main()
{ 
	int i = 0;
	int a[5] = { 0,1,2,3,4};
	int* p[5] = { &a[0],&a[1],&a[2],&a[3],&a[4] };
	for (i = 0; i < 5; i++)
	{
		printf("%d ",*p[i]);
	}
	return 0;
}

  • 不过我们通常不会这么使用,这里仅仅为了举例。

2.存放指针数组(地址)的数组指针

  • 现在难度再升级一下,我们现在定义一个指针数组 int*d[10],我们想把这个指针数组的地址存放
  • p=&d;
  • 这个p应该如何定义?
  • int *(*p)[10]; 利用这个语句,即可创建一个存放指针数组地址的数组指针p
  • 首先,我们先想,指针数组是一个数组,我们要存放一个数组的地址,肯定得要一个数组指针,我们可以先写成这样 (*p)[10],表示一个指向数组的指针,之后我们还差前面的类型,这个类型是由原本数组中存储的变量的类型决定的
  • 我们要存的是指针数组,指针数组中储存的是指针,在这里为int *类型
  • 所以我们再前面加上类型 int *
  • 最终就成了 int* (*p)[10];

3.存放数组指针的指针数组

  • 最后我们想想,如果我们想要存放许多个数组指针,应该怎么办呢?
  • 我们应该创建一个数组存储数组指针,既然存储的是指针,所以我们创建的是指针数组
  • int (*p[10])[5];
  • 这样我们就创建了一个能存放数组指针的指针数组
  • 这看上去有些复杂,我们可以先把p[10]去掉,看剩下的部分int (*)[5]
  • 这部分是不是十分的眼熟,这代表一个存放数组地址的指针,也就是数组指针,其中数组中每个元素的类型为int
  • 在加上p[10],int (*p[10])[5] 就代表了一个能存储10个数组指针的指针数组,其中每个数组指针都指向一个数组,每个数组有5个元素,每个元素是int类型

一维数组传参

  • 有了前面的知识的铺垫,最后的数组传参就是手到擒来了
  • 看下面几个传参方式

  • 上面5种传参方式,哪一种是正确的?答案是全部正确。
  1. a是一个一维数组,用一维数组接收,可以不写大小,正确。
  2. a是一个一维数组,用一维数组接收,写上大小,正确。
  3. a代表数组首元素的地址,用一个int类型的指针接收,正确。
  4. b是一个指针数组,用一个指针数组接收,正确。
  5. b是一个指针数组,数组中存放的也是指针,b代表了数组首元素的地址,而首元素也是一个指针,即传过去了一个指针的地址,用二级指针接收,正确。

二维数组传参

  • 再来看看几种二维数组的传参

  • 哪几种是正确的呢?答案是1,3,6是正确的
  1. a是二维数组,用二维数组接收,正确
  2. 用于接收的二维数组行和列的大小均未给出,错误
  3. 用于接收的二维数组给出了列的大小,正确
  4. 用一个int 类型的指针接收二维数组首元素的地址,错误
  5. 用一个指针数组接收二维数组首元素的地址,错误、
  6. 用一个数组指针接收二维数组首元素的地址,正确
  7. 用一个二级指针接收二维数组首元素的地址,错误

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dhdw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值