数组表达式在 C 中是特殊的。
6.3.2.1 左值、数组和函数指示符
...
3 除非它是 sizeof 运算符、_Alignof 运算符或
一元 & 运算符,或者是用于初始化数组的字符串文字,具有
type ‘‘array of type’’ 被转换为类型为 ‘‘pointer to type’’ 的表达式,它指向
到数组对象的初始元素并且不是左值。如果数组对象有
注册存储类,行为未定义。
C 2011 online draft
当你打电话时
set_array(b);
表达式 b从“int的四元素数组”类型转换为“指向int的指针”(int *),表达式的值为b[0]的地址。因此,set_array 接收的是 指针 值 (&b[0]),而不是数组。
6.7.6.3 函数声明符(包括原型)
...
7 将参数声明为“类型数组”应调整为“限定指针”
type'',其中类型限定符(如果有)是在 [ 和 ] 的
数组类型推导。如果关键字static 也出现在[ 和] 的
数组类型推导,然后对函数的每次调用,对应的值
实际参数应提供对数组的第一个元素的访问,其中至少有
由大小表达式指定的元素。
同上。
基本上,在函数定义中,任何声明为T a[N] 或T a[] 的参数都应解释为T *a - IOW,该参数被视为指针,而不是数组。你的函数定义
void set_array(int array[4]){
array[0] = 22;
}
被视为已写入
void set_array(int *array){
array[0] = 22;
}
所以数组在 C 中是通过引用传递的,但实际上不是通过引用传递的。真正发生的是指向数组第一个元素的指针按值传递 . set_array 中的 array 参数指定内存中与 b 不同的对象,因此您对 array 所做的任何更改本身对 b 没有影响(IOW,您可以指定array 的一个新值并让它指向不同的对象,但这个赋值根本不影响b)。相反,您正在做的是访问b 到 array 的元素。
图片形式:
+---+
b: | | b[0] <---+
+---+ |
| | b[1] |
+---+ |
| | b[2] |
+---+ |
| | b[3] |
+---+ |
... |
+---+ |
array: | | ---------+
+---+
这样做的一个实际结果是sizeof array 产生指针 类型int * 的大小,而不是数组的大小,这与sizeof b 不同。这意味着您无法使用sizeof array / sizeof array[0] 技巧计算数组中的元素数量。当您将数组作为参数传递给函数时,您还应该将数组的大小(即元素的数量)作为单独的参数传递除非该数组包含明确定义的哨兵值(如字符串中的 0 终止符)。
请记住,表达式a[i] 定义为*(a + i) - 给定起始地址a,偏移量i 元素(不是字节!)该地址并尊重结果。如果a 是数组表达式,则在完成此计算之前首先将其转换为指针表达式。这就是为什么您可以访问b 的元素通过 array - array 只需存储b 的第一个元素的地址。
在 C 中,所有函数参数都是按值传递的——函数定义中的形式参数和函数调用中的实际参数指的是内存中不同的对象,而实际参数的值被复制到形式参数。形式论证的变化不会反映在实际论证中。
我们通过将指针传递给实际参数并写入取消引用的指针来伪造传递引用语义:
void foo( T *ptr )
{
*ptr = new_value(); // write a new value to the thing ptr points to
}
void bar( void )
{
T var;
foo( &var ); // have foo write a new value to var
}
IOW,写信给*ptr 和写信给var 是一样的。写到ptr,OTOH,在foo 之外没有任何影响。
在真正的按引用传递系统(例如 Fortran)中,函数定义中的形式参数和函数调用中的实际参数都指定同一个对象(在“占用内存并且可以存储值”的意义,而不是面向对象的“类的实例”意义),因此在这些系统中,对形式参数的任何更改都会反映在实际参数中(导致关于Hacker Test, "你有没有改变过 4 的值?无意间?用 Fortran 以外的语言?")