1、什么时候数组与指针相同?
数组的声明就是数组,指针的声明就是指针,两者不能混淆。但在使用数组(在语句或表达式中引用)时,数组总是可以写成指针的形式,两者可以互换。
根据C语言的标准说明:“作为函数定义的形式参数”(也就是只限于这种情况)时,“数组下标表达式总是可以改写为带偏移量的指针表达式。”
事实上,一个数组作为参数传递给一个函数(表达式)时,它最终是会被转化为一个指向该数组第一个元素的指针。当参数传递是一个指针时,毋庸置疑,最终还是一个指针。
============================================================================
2、数组传参能被当作指针使用,内部是如何实现的?
比如有一个数组 a[i] 被当作参数传递,那么 a[i]
在编译时总是被编译器改写成*(a+i)
的形式。
编译器自动把下标值的步长调整到数组元素的大小。如果整型数的长度是4个字节,那么 a[i+1] 和 a[i] 在内存中的距离就是4 (而不是1)。对起始地址执行加法操作之前,编译器会负责计算每次增加的步长。这就是为什么指针总是有类型限制,每个指针只能指向一种类型的原因所在——因为编译器需要知道对指针进行解除引用操作时应该取几个字节,以及每个下标的步长应取几个字节。
(解引用: *
解引用操作符 例如某指针变量p, 解引用就是*p, 也就是p所指向的变量 )
就比如说有这么一个例子,如果我们声明:
int a[10] , *p, i ;
要访问变量 a[i],可以有以下几组访问方式
数组访问:
for(i = 0; i < 10; i++)
{
a[i] = 0;
}
指针备选方案1:
p = a;
for(i = 0; i < 10; i++)
{
p[i] = 0;
}
指针备选方案2:
p = a;
for(i = 0; i < 10; i++)
{
*(p+i) =0;
}
指针备选方案3:
p = a;
for(i = 0; i < 10; i++)
{
*p++ = 0;
}
上面的四种方案,其实现的效果完全一样。
============================================================================
3、为什么C语言把数组形参当作指针?
把作为形参的数组和指针等同起来是出于效率原因的考虑。在C语言中,所有非数组形式的数据实参均以传值形式(对实参作一份拷贝并传递给调用的函数,函数不能修改作为实参的实际变量的值,而只能修改传递给它的那份拷贝)调用。然而,如果要拷贝整个数组,无论在时间上还是在内存空间上的开销都可能是非常大的。
而且因为,在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。
============================================================================
4、请用实例证明:所有属于函数实参的数组在编译时被编译器改写为指针
证明思想是根据:不能给数组名赋值,但是可以给指针赋值。(或者说:赋值时只能给可以修改的东西赋值。)
证明代码:
//定义数组,然后直接通过主函数对数组名进行赋值。
#include <stdio.h>
int array[100], array2[100];
int main()
{
printf("测试程序\n");
array[1] = 3;
*array = 3;
array = array2;
printf("%d , %d",array[1],*array);
return 0;
}
运行结果:
结论:数组在主函数里无法被赋值,数组就是数组。
辅助证明一:
//通过把数组传入外部函数,看其能否被赋值
#include <stdio.h>
int array[100], array2[100];
void fun1(int ptr[]) //参数为数组
{
ptr[1] = 3;
*ptr = 3;
printf("%d , %d\n",ptr[1],*ptr);
ptr = array2;
printf("%d , %d",ptr[1],*ptr);
}
int main()
{
printf("测试程序\n");
fun1(&array[0]);
return 0;
}
运行结果::编译通过,成功运行。
辅助证明二:
#include <stdio.h>
int array[100], array2[100];
void fun1(int *ptr) //参数为指针
{
ptr[1] = 3;
*ptr = 3;
printf("%d , %d\n",ptr[1],*ptr);
ptr = array2;
printf("%d , %d",ptr[1],*ptr);
}
int main()
{
printf("测试程序\n");
fun1(&array[0]);
return 0;
}
运行结果:成功运行。
因此得出,在函数内部对数组参数的任何引用都将产生一个对指针的引用,所有属于函数实参的数组在编译时被编译器改写为指针。
============================================================================
5、数组和指针可交换性的总结
这里全部摘抄自《C专家编程》
警告:在你阅读并理解前面的章节之前不要阅读这一节的内容,因为它可能会使你的脑
力永久退化。
(插播:C专家编程英文版 密码:d05t)
1.用 a[i]这样的形式对数组进行访问总是被编译器 “改写” 或解释为像*(a+1)这样的指针访问。
2.指针始终就是指针。它绝不可以改写成数组。你可以用下标形式访问指针,一般都是指针作为函数参数时,而且你知道实际传递给函数的是一个数组。(PS:形参为指针,实参为数组,这时才“能”用下标形式访问)
3.在特定的上下文中,也就是它作为函数的参数(也只有这种情况),一个数组的声明可以看作是一个指针。作为函数参数的数组(就是在一个函数调用中)始终会被编译器修改成为指向数组第一个元素的指针。
4.因此,当把一个数组定义为函数的参数时,可以选择把它定义为数组,也可以定义指针。不管选择哪种方法,在函数内部事实上获得的都是一个指针。
5.在其他所有情况中,定义和声明必须匹配。如果定义了一个数组,在其他文件对它进行声明时也必须把它声明为数组,指针也是如此。
============================================================================
6、C专家编程中文版
为了尊重作者,这里就不放下载地址了,如果想要的可以偷偷私我 (备注:C专家编程——我想要)。
(PS:有能力的还请多多支持原作👉C专家编程)