C 函数的所有参数均以 “传值调用” 方式进行传递,这意味着函数将获得参数值的一份拷贝。这样函数就可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。
既然调用函数时,函数只会得到参数的一份拷贝,那么在函数中处理这个参数,不会影响原来的参数值,也就是传递给它的参数值。
我们举个例子,奇偶检验的例子:
用函数实现这么一个功能,就是检验一个整数中的1的位的个数是偶数还是奇数?
/*
**对值进行偶校验
*/
int even_parity(int value, int n_bits)
{
int parity = 0;
/*
**计数值的值为1的位的个数
*/
while(n_bits > 0)
{
parity += value & 1;
value >> 1;
n_bits -= 1;
}
//如果parity对2取余数为0,则返回TRUE
return (parity % 2) == 0;
}
这个例子说明了标量函数参数的传值调用行为。函数检查第一个参数是否满足偶校验,也就是它的二进制位模式中1的个数是否为偶数。
函数的第二个参数指定第一个参数中的有效位的数目。函数一次一位地对第1个参数值进行移位,所以每个位迟早都会出现在最右边的那个位置。所有的位逐个加在一起,所以在循环结束之后,我们就得到第1个参数值的位模式中1的个数。最后对这个数进行测试,看看它是否为偶数,如果是,返回TURE。
该函数对第一个参数值进行奇偶校验,函数处理的是第一个参数的拷贝,我们也确实感受到了处理的是参数值,确实是传值调用,但是这个例子并不能明显的或者没有体现出函数处理的是实际传递给函数的参数值的拷贝。
因此,我们再次举一个例子,说明这个问题。
写一个函数,用来交换调用程序中的两个整数值。
//交换调用程序中的两个整数
void swap(int x, int y)
{
int temp; //中间值
temp = x;
x = y;
y = temp;
}
这个函数如果在程序中调用,可以放心地告诉呢,没有任何作用。
也就是说,并起不到交换两个参数值的效果。
原因也很简单,就是函数调用时,函数获得的是实际参数值的一份拷贝,并不会对传递给函数的实际参数值进行修改。
那么传说中的传址调用是什么情况呢?
C 的规则很简单:
所有参数都是传值调用。但是,如果被传递的参数是一个数组名,并且在函数中使用下标引用该数组的参数,那么在函数中对数组元素进行修改实际上修改的是调用程序中的数组元素。函数将访问调用程序的数组元素,数组并不会被复制。这个行为被称为“传址调用”。
上面所说的调用程序的意思是调用这个函数的程序,而不是函数本身。
数组参数的这种行为似乎与传值调用规则相悖。但是,此处其实并无矛盾之处。数组名的值实际上是一个指针,传递给函数的就是这个指针的一份拷贝。下标引用实际上是间接访问的另一种形式,它可以对指针执行间接访问操作,访问指针指向的内存位置。参数(指针)实际上是一份拷贝,但在这份拷贝上执行间接访问操作所访问的是原先的数组。
此处我们记住两个规则:
1. 传递给函数的标量参数是传值调用的;
2. 传递给函数的数组参数在行为上就像它们是传址调用的那样。
下面举个例子,这个例子有效地进行了整数之间的交换:
//交换调用程序中的两个整数
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
在程序中调用这个函数,就可以有效的交换调用程序中的两个整数。
因为函数期望接受的参数是指针,所以我们应该按照下面的方式调用它:
swap(&a, &b);
一个例子不过瘾,那么再举一个例子:
这个例子是让编写一个函数,把一个数组中的所有元素设置为0:
//把一个数组的所有元素设置为 0
void clear_array(int array[], int n_elements)
{
//从数组的最后一个元素开始,逐个清除数组中的所有元素
while(n_elements > 0) //n_elements是数组中的元素的个数
{
array[--n_elements] = 0;
}
}
调用该函数时,传递给函数的第一个参数是一个数组名,该数组名相当于一个指针,实际上传递给函数的就是这个指针(数组名)的一份拷贝,下标引用实际上是间接访问的另一种形式,他可以对指针(地址)执行间接访问操作,访问指针指向的内存位置。这样就会改变调用程序中的数组值。
这个例子同时说明了另外一个特征。在声明数组参数时,不指定它的长度是合法的,因为函数并不为数组元素分配内存。间接访问操作将访问调用程序中的数组元素。这样,一个单独的函数可以访问任意长度的数组。