可以用数组名作函数参数,此时实参与形参都应用数组名(或指针变量)。
例2:有一个一维数组score,内放10个学生成绩,求平均成绩。
float average(float array[10]){
int i;
float aver,sum=array[0];
for(i=1; i<10; i++)sum=sum+array[i];
aver=sum/10;
return aver;
}
main(){
float score[10],aver;
int i;
printf("input 10 scores:/n");
for(i=0; i<10; i++)scanf("%f",&score[i]);
printf("/n");
aver=average(score);//数组名作为函数参数
printf("average score is %5.2f",aver);
}
说明:
l 用数组名称作函数参数,应该在主调函数和被调函数分别定义数组,本例中array是形参数组名,score是实参数组名,分别在其所在的函数中定义,不能只在一方定义。
l 实参数组与形参数组类型应该保持一致(这里都为float型),如不一致,结果将出错。
l 在被调用函数中声明了形参数组的大小为10,但在实际上,指定其大小是不起任何作用的,因为C编译器对形参数组大小不做检查,只是检查实参数组的首地址传给形参数组。因此,score[n]和array[n]指的是同一单元。
l 形参数组也可以不指定大小,在定义数组时在数组名后面跟一个空的方括号,有时为了在被调用函数中处理数组元素的需要,可以另设一个参数,传递需要处理的数组元素的个数,上例可以改写为下面的形式:
float average(float array[], int n){
int i;
float aver,sum=array[0];
for(i=1; i<n; i++)sum=sum+array[i];
aver=sum/n;
return aver;
}
main(){
float score_1[5]={98.5,97,91.5,60,55};
float score_2[10]={67.5,89.5,99,69.5,77,89.5,76.5,54,60,99,5};
printf("the average of class A is %6.2f/n", average(score_1, 5));
printf("the average of class B is %6.2f/n", average(score_2, 10));
}
可以看出,两次调用average函数时,需要处理的数组元素是不同的,在第一次调用时用一个实参5传递给形参n,表示求前面5个学生的平均分数。第二次调用时,求10个学生平均分。
l 最后应该说明一点:用数组名作为函数实参的时,不是吧数组元素的值传递给形参,而是把实参数组的起始地址传递给形参数组,这样两个数组就共占同一段内存单元。见下图:
起始地址1000 |
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 |
b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
假如a的起始地址为1000,则b数组的起始地址也是1000,显然a和b同占一段内存单元,a[0]和b[0]同占一个内存单元……。由此,我们可以看到,形参数组中各个元素的值如果发生变化会使实参数组元素的值同时发生变化,从上图是很容易理解的。这一点与变量做函数参数的情况是不同的,务必注意!在程序设计中可以有意识地利用这一点,改变实参数组元素(如排序)。
最后,再给出一个例子:用选择排序法对数组中,10个整数按有小到大排序。所谓选择排序就是先将10个数中最小的数与a[0]对换;再将a[1]到a[9]中最小的数与a[1]对换……,每比较一轮,我们可以找出一个未经排序的数中最小的一个。共比较9论。
a[0] a[1] a[2] a[3] a[4]
3 6 1 9 4
1 6 3 9 4
1 3 6 9 4
1 3 4 9 6
1 3 4 6 9
我们可以看到在执行函数调用语句sort(a,10);之前和之后,a数组各元素的值是不一样的。原来是无序的,执行sort(a,10)之后是有序的。这也就是所谓的形参数组改变也可以使实参数组随之改变。
现在,我们已经知道了,当用数组名作函数参数时,如果形参数组中各元素的值发生了变化,实参数组元素的值也随之变化。那么这是为什么呢?如果你学过指针,这个问题就很容易来回答。
我们先看数组元素做实参时的情况。如果已经定义了一个函数,其原型为:
Void swap(int x, int y);
假设函数的作用是将两个形参(x,y)的值进行交换,现在我们这样调用这个函数:
Swap(a[1], a[2]);
用数组元素a[1], a[2]做实参的情况与变量做实参时一样,是“值传递”方式,我们将a[1], a[2]的值单向传递给x,y。当x和y的值改变时a[1], a[2]的值并不改变。
我们现再看用数组名作函数参数的情况。前面已经介绍,实参数组名代表该数组首元素的地址。而形参是用来接收从实参传递过来的数组首元素的地址。因此形参应该是一个指针变量。实际上,C编译器都是将形参数组名作为指针变量来处理的。例如:
我们定义一个被调函数f(int arr[], int n);,实际上,编译器是把arr按照指针变量来处理的,这相当于将函数f的首地址写成:f(int *arr,int n)。以上两种写法是等价的。
实际上,我们经常用这种方法调用一个函数来改变实参数组的值。
这里,给出一个表格,用于比较变量名作为函数参数和数组名作为函数参数。
实参类型 | 变量名 | 数组名 |
要求形参的类型 | 变量名 | 数组名或指针变量 |
传递的信息 | 变量的值 | 实参数组首元素的地址 |
通过函数调用能否改变实参的值 | 不能 | 能 |
注意:
l 在用数组名做函数实参的时候,既然实际上相应的形参是指针变量,为什么还允许使用形参数组的形式呢?
这是因为在C语言中,用下标法和指针法都可以访问一个数组,而且用下标法比较直观,便于理解。因此许多人愿意用数组名做形参,以便与实参数组对应。但从应用的角度讲,用户可以认为有一个形参数组,从实参数组那里得到起始地址,因此形参数组与实参数组共占一段内存单元,在调用函数期间,如果改变了形参数组的值,也就改变了实参数组的值。当然,在主调函数中我们可以使用这些改变了的值。实际上,对C语言比较熟悉的专业人士,往往比较喜欢用指针变量做形参。
l 再强调一点:实参数组名代表一个固定的地址,或者说是一个指针型常量,而形参数组并不代表一个固定的地址值。作为指针变量,在函数调用开始时,它的值等于实参数组起始地址,但在函数执行期间,它可以再被赋值。例如:
f(arr[], int n){
printf("%d/n", *arr); //输出array[0]的值
arr=arr+3;
printf("%d/n", *arr); //输出array[3]的值
}