数组名的理解
一般情况
我们知道,对于这一条语句:&arr[0],指的是取出数组首元素的地址,而实际上,在正常情况下,数组名指的也是数组首元素地址,观察下面这一段代码:
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int* p1 = &arr[0];//p1中存放着arr数组首元素的地址
int* p2 = arr;//p2中存放的是什么呢?
printf("%p\n", p1);
printf("%p\n", p2);
return 0;
}
结果如下:
我们可以发现,两个指针指向的地址是相同的,说明:
一般情况下,数组名就是数组首元素的地址
两个特殊情况
数组名在两个特殊情况下不再是数组首元素地址,而是整个数组的地址,这里我们首先需要区分数组首元素地址和整个数组地址的区别,
观察下面代码:
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%p\n", arr);//数组名表示数组首元素地址,打印其值
printf("%p\n", &arr);//&数组名取出的即是整个数组的地址,打印出其值并观察
return 0;
}
结果如下:
我们发现,两个地址的值竟然是一样的,这说明数组首元素与整个数组在值上是相同的,那么二者又有什么不同呢?
再观察下面这段代码:
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%p\n", arr);//打印首元素地址作为参考
printf("%p\n", arr + 1);//数组名表示数组首元素地址,+1跳过一个int类型的字节空间,指向下一个元素
printf("%p\n", &arr + 1);//&数组名取出的即是整个数组的地址,+1跳过该指针类型所指向内容类型的字节空间,那么是多少呢?
return 0;
}
在X86环境下打印,结果如下:
我们发现,数组首元素地址+1确实跳过的是一个int类型大小的字节空间,而整个数组地址+1,却跳过了40个字节空间,也就是10个int类型大小的空间,而这正是整个数组的空间大小,这说明,当数组名为整个数组的地址时,存放该地址的指针解引用时的访问权限就是整个数组的大小
明白了这一点,我们就需要知道,在什么样的特殊情况下,数组名就是整个数组的地址呢?
&数组名
大家可能注意到,在上面的例子中,我使用了&arr,也就是 &数组名 的形式,通过结果大家也能发现此时数组名arr表示的就是整个数组的地址,该地址与数组首元素地址是有区别的
sizeof(数组名)
当数组名单独放在sizeof内部的时候,数组名表示的也是整个数组,表示整个数组的大小,例如:
int main()
{
int arr[] = { 0,1,2,3,4 };
size_t sz = sizeof(arr);//求数组所占空间大小
printf("%zd\n", sz);
return 0;
}
结果如下:
除了上述两种情况以外,其余情况下的数组名表示的都是首元素地址
使用指针访问数组
使用指针访问数组主要有两种形式,一种是使用指针加减整数的方式访问数组元素,在上一篇有例子提及,这里不再过多阐述,这里主要介绍第二种访问方法
我们知道,数组名表示的就是数组地址,那我们是否可以使用一个存放着数组首元素地址的指针来代替数组名呢?
观察下面的例子:
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int* p = &arr[0];//指针中存放数组首元素的地址
int i = 0;
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", p[i])//使用指针直接代替数组名进行数组访问
}
return 0;
}
观察结果:
可以发现,当我们直接使用存放了数组首元素地址的指针来代替数组名来进行数组访问时,结果完全正确,这也侧面证明了非特殊情况下数组名就是首元素的地址
二级指针
我们已经知道,指针是用来存放地址的,而指针也是一个变量,自然也需要向内存申请空间,那么自然指针也有自己的地址,而指针的地址就需要二级指针来存放,例如:
int main()
{
int a = 10;
int* p = &a;
int** pp = &p;//pp指针中存放p指针的地址
printf("%d %d", *p, **pp);//两个解引用符号来获得a中的内容
return 0;
}
打印结果如下:
多级指针的定义与使用与二级指针同理,只需要增加*的个数即可,关于二级指针的使用举例可参考该详解系列的下一篇内容