一、数组名的理解
在之前我们我们使用指针访问数组的时候,使用到了这样一段代码:
int arr[10] = { 0 };
int* pa = &arr[0];
这里我们使用 &arr[0] 的方式拿到了数组第一个元素的地址,但是其实数组名本来就是地址,而且 是数组首元素的地址,我们来做个测试。
//演示1.1
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* pa = &arr[0];
int* pb = &arr;
printf("%p\n", pa);
printf("%p\n", pb);
return 0;
}
\
我们发现数组名和数组首元素的地址打印出的结果一模一样,数组名就是数组首元素(第一个元素)的地址。 这时候我们可能会有疑问,如果数组名是数组首元素的地址,那下面的代码要怎么理解呢?
//演示1.2
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));
return 0;
}
如果数组名是数组首元素的地址,那么这里的结果应该是4或8,也就是一个地址的大小。但我们已经知道结果是40,是10个整型的大小。这是为什么呢?
其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:
• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的,下面我们会详细解释)。
除此之外,任何地方使用数组名,数组名都表示首元素的地址。
接下来让我们看看下面这段代码:
//演示1.3
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n",&arr[0]);
printf(" arr = %p\n",arr);
printf(" &arr = %p\n",&arr);
return 0;
}
从这段代码看,&arr[0]、arr、&arr的打印结果完全一样,那么它们区别在哪里呢?
我们再看下面这段代码:
//演示1.4
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf(" &arr[0] = %p\n",&arr[0]);
printf("&arr[0] + 1 = %p\n",&arr[0] + 1);
printf(" arr + 1 = %p\n",arr + 1);
printf(" &arr + 1 = %p\n",&arr + 1);
return 0;
}
它们的结果已经不一样了,可以看到,相比于arr[0],
&arr[0]+1和arr+1,跳过了4个字节,说明它们代表的是数组首元素的地址,+1后跳过一个元素,也就是跳过一个整型;
&arr+1,跳过了40个字节,说明它代表的是整个数组的地址,+1后跳过一个arr数组的大小。
二、使用指针访问数组
有了前面知识的理解,再结合数组的特点,我们就可以很方便的使用数组来访问数组了。
//演示2.1
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* parr = arr;
//输入
for (i = 0; i < sz; i++)
{
scanf("%d", parr + i);
//也可以写成 scanf("%d", arr + i);
}
//输出
for (i = 0; i < sz; i++)
{
printf("%d ", *(parr + i));
}
return 0;
}
数组名arr是数组首元素的地址,可以赋值给parr,也就是说数组名arr和整型指针parr在这里是等价的。那么我们可以使用arr[i]来访问数组中的元素,是不是也可以使用parr[i]来实现这样的操作呢?
//演示2.2
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* parr = arr;
for (i = 0; i < sz; i++)
{
scanf("%d", parr + i);
}
for (i = 0; i < sz; i++)
{
printf("%d ", parr[i]);
}
return 0;
}
输入的数据被很好的打印了出来,所以说parr[i]是等价于arr[i]的,实质上,arr[i]和parr[i]都会被计算机理解为*(parr+i)。
三、一维数组传参的本质
我们已经了解两个知识,在主函数中通过sizeof相除来求数组中元素的个数,以及数组可以作为参数传递给函数。
那么我们可不可以把一个数组传递给主函数外的某个函数,然后在这个函数内通过sizeof相除的方法求出数组元素的个数呢?我们来试一下:
//演示3.1
#include <stdio.h>
void test(int pa[])
{
int sz2 = sizeof(pa)/sizeof(pa[0]);
printf("sz2 = %d\n", sz2);
}
int main()
{
int arr[6] = { 0 };
int sz1 = sizeof(arr)/sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}
我们发现这种做法行不通。它的原因涉及到一维数组传参的本质,我们接下来来探讨一下这个问题。
我们知道,数组名代表的是数组首元素的地址,那么我们在将数组名作为参数直接传递给函数时,传过去的也就应该是数组首元素的地址。
所以在演示3.1中test函数计算sz2时,sizeof(pa)的值不会是arr数组的大小,而是arr数组首元素地址的大小,也就是4或8个字节,所以sz2结果的结果是1或2(在x64环境下)。
通过上面的例子,我们知道了一维数组在传参时实际传递的是一个地址,所以函数的形参部分理论上应该用一个指针来接受传来的参数。但是实际运用中呢,我们常常就写作数组的形式了,因为这样并不错,而且也更易于理解。