函数参数调用有两种方式:传值调用和传址调用。
C的规则很简单:所有参数都是传值调用。
但是,如果被传递的参数是一个数组名,并且在函数中使用下标引用该数组的参数,那么在函数中对数组元素进行修改实际上修改的是调用程序中的数组元素。函数将访问调用程序的数组元素,数组并不会被复制。这个行为被称为传址调用。
数组参数的这种行为似乎与传值调用规则相悖。但是,此处其实并无矛盾之处——数组名的值实际上是一个指针,传递给函数的就是这个指针的一份拷贝。下标引用实际上是间接访问的另一种形式,它可以对指针执行间接访问操作,访问指针指向的内存位置。参数(指针)实际上是一份拷贝,但在这份拷贝上执行间接访问操作所访问的是原先的数组。
程序:奇偶校验
/*
** 对值进行偶校验。
*/
int even_parity( int value, int n_bits )
{
int parity = 0;
/*
** 计数值中值为1的位的个数。
*/
while( n_bits > 0 ){
parity += value & 1;
value >>= 1;
n_bits -= 1;
}
/*
** 如果计数器的最低位是0,返回TRUE(表示1的位数为偶数个)。
*/
return ( parity % 2 ) == 0;
}
个函数的有趣特性是在它的执行过程中,它会破坏这两个参数的值。但这并无妨,因为参数是通过传值调用的,函数所使用的值是实际参数的一份拷贝。破坏这份拷贝并不会影响原先的值。这个特性可以在下面交换两个数的函数看到。
程序:整数交换:无效的版本
void swap( int x, int y )
{
int temp;
temp = x;
x = y;
y = temp;
}
为了访问调用程序的值,必须向函数传递指向你希望修改的变量的指针。接着函数必须对指针使用间接访问操作,修改需要修改的变量。
程序:整数交换:有效的版本
/*
** 交换调用程序中的两个整数。
*/
void swap( int *x, int *y )
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
因为函数期望接受的参数是指针,所以我们应该按照下面的方式调用它:
swap (&a, &b);
程序:将一个数组设置为零
/*
** 把一个数组的所有元素都设置为零。
*/
void clear_array( int array[], int n_elements )
{
/*
** 从数组最后一个元素开始,逐个清除数组中的所有元素。注意前缀自增避免了越出数组边界的可能性。
*/
while( n_elements > 0 )
array[ --n_elements ] = 0;
}
n_elements是一个标量参数,所以它是传值调用的。在函数中修改它的值并不会影响调用程序中的对应参数。另一方面,函数确实把调用程序的数组的所有元素设置为0。数组参数的值是一个指针,下标引用实际上是对这个指针执行间接访问操作。
这个例子同时说明了另外一个特性。在声明数组参数时不指定它的长度是合法的,因为函数并不为数组元素分配内存。间接访问操作将访问调用程序中的数组元素。这样,一个单独的函数可以访问任意长度的数组。但是,函数并没有办法判断数组参数的长度,所以函数如果需要这个值,它必须作为参数显式地传递给函数。
参考