在程序设计过程中,参数传递是经常会遇到的情况。在C语言中,函数的参数传递的方式通常有两种,一种是传值的方式;另一种是传地址的方式。本节主要介绍传值调用和传地址调用。
在函数调用时,一般情况下,调用函数和被调用函数之间会有参数传递。调用函数后面括号里面的参数是 实际参数 ,被调用函数中的参数是 形式参数 。传值调用是建立参数的一个副本并把值传递给形式参数,在被调用函数中修改形式参数的值,并不会影响到调用函数实际参数的值。
【例2-13】 编写一个函数,求两个整数的最大公约数。
【分析】 通过传值调用的方式,把实际参数的值传递给形式参数,其实形式参数是实际参数的一个副本(拷贝)。其程序实现如下。
#include <stdio.h> /* 包含输入输出函数*/ int GCD(int m,int n); /* 求两个整数的最大公约数的函数声明*/ void main() { int a,b,v; printf(" 请输入两个整数:"); scanf("%d,%d",&a,&b); v=GCD(a,b); /* 调用求两个数中的较大者的函数*/ printf("%d 和%d 的最大公约数为:%d\n",a,b,v); } int GCD(int m,int n) /* 求两个整数的最大公约数,并返回公约数*/ { int r; r=m; do { m=n; n=r; r=m%n; }while(r); return n; }
程序的输出结果如图2-42所示。
假设输入两个数15和25,在主函数中,将15和25分别赋值给实际参数a和b,通过语句s=GCD(a,b)调用实现函数GCD(int x,int y)也就是所谓的被调用函数,将15和25分别传递给被调用函数的形式参数m和n。然后求m和n的最大公约数,通过语句return n;将最大公约数5返回给主函数,即被调用函数,因此输出结果为5。
上述函数参数传递属于参数的单向传递,即a和b可以把值分别传递给m和n,而不可以把m和n传递给a和b。在传值调用中,实际参数和形式参数分别占用不同的内存单元,形式参数是实际参数的一个副本,实际参数和形式参数的值的改变都不会相互受到影响,如图2-43所示。这就像有一张身份证原件,它的复印件就是个副本,复印件的丢失不会影响到身份证原件的存在,身份证原件的丢失也不会影响到复印件的存在。
图2-42 求两个整数的最大公约数运行结果
图2-43 参数传递过程
图2-44 形式参数改变后的情况
在调用函数时,形式参数被分配存储单元,并把15和25传递给形式参数,在函数调用结束,形式参数被分配的存储单元被释放,形式参数不复存在,而主函数中的实际参数仍然存在,并且其值不会受到影响。在被调用函数中,如果改变形式参数的值,假设把m和n的值分别改变为20和35,a和b的值不会改变,如图2-44所示。
C语言通过指针(地址)实现传地址调用。在函数调用过程中,如果需要在被调用函数中修改参数值,则需要把实际参数的地址传递给形式参数,通过修改该地址的内容改变形式参数的值,以达到修改调用函数中实际参数的目的。
【例2-14】 编写一个求两个整数较大者和较小者的函数,要求用传地址方式实现。
【分析】 通过传地址调用的方式,把两个实际参数传递给形式参数。在被调用函数中,先比较两个形式参数值的大小,如果前者小于后者,则交换两个参数值,其中,前者为大,后者为小。传地址调用时,在调用函数和被调用函数中,对参数的操作其实都是在对同一块内存操作,实际参数和形式参数共用同一块内存。其程序实现如下。
#include <stdio.h> /* 包含输入输出函数*/ void Swap(int *x,int *y); /* 函数声明*/ void main() { int a,b; printf(" 请输入两个整数:\n"); scanf("%d,%d",&a,&b); if(a<b) Swap(&a,&b); /* 两个数中如果前者小,则交换两个值,使其较大的保存在a 中较小保存在b 中*/ printf(" 在两个整数%d 和%d 中,较大者为:%d, 较小者为:%d\n",a,b,a,b); } void Swap(int *x,int *y) /* 交换两个数,较大的保存在*x 中,较小的保存在*y 中*/ { int z; z=*x; *x=*y; *y=z; }
程序的运行结果如图2-45所示。
图2-45 传地址方式求两个整数的较大者和较小者的程序运行结果
在主函数中,如果a<b,则调用Swap(&a,&b)函数交换两个数。其中,实际参数是变量的地址,就是把地址传递给被调用函数Swap(int*x,int*y)中的形式参数x和y,x和y是指针变量,即指针x和y指向变量a和b。这样,交换*x和*y的值就是交换a和b的值。函数调用时,实际参数和形式参数的变化情况如图2-46所示。
如果要修改多个参数的值并返回给调用函数,该怎么呢?这就需要将数组名作为参数传递给被调用函数。数组名作为参数传递时,传递的是整个数组。数组名是数组的首地址,如果把数组名作为实际参数,在函数调用时,会把数组的首地址传递给形式参数。这样形式参数就可以根据数组的首地址访问整个数组并对其操作,这是因为整个数组元素的地址是连续的。
下面是一个数组名作为参数传递的例子。
图2-46 实际参数和形式参数的变化情况
【例2-15】 编写函数,要求将数组中的n个元素的值分别减去20。
【分析】 数组名作为参数传递给被调用函数,实际上是把数组的起始地址传递给形式参数。因为数组在内存中存储的连续性,可以利用数组下标和指针访问数组中的每一个元素,这样在被调用函数中就可以对整个数组进行操作,无须将每一个数据元素作为参数传递给被调用函数。将数组名作为参数传递,调用函数和被调用函数都是对占同一块内存单元的数组进行操作。其程序实现如下。
#include <stdio.h> /* 包含输入输出函数*/ #define N 10 void SubArray1(int *x,int n); /* 数组名作为参数的函数原型*/ void SubArray2(int *aPtr,int n); /* 指针作为参数的函数原型*/ void main() { int a[N]={51,52,53,54,55,56,57,58,59,60}; int i; printf(" 原来数组中的元素为:\n"); for(i=0;i<N;i++) printf("%4d",a[i]); printf("\n"); printf(" 数组中的元素第一次减去20 之后为:\n"); SubArray1(a,N); /* 调用SubArray1 :数组名作为参数的函数*/ for(i=0;i<N;i++) printf("%4d",a[i]); printf("\n"); printf(" 数组中的元素第二次减去20 之后为:\n"); SubArray2(a,N); /* 调用SubArray2 :指针作为参数的函数*/ for(i=0;i<N;i++) printf("%4d",a[i]); printf("\n"); } void SubArray1(int b[],int n) /* 数组名作为参数,将数组中的元素减去20*/ { int i; for(i=0;i<n;i++) b[i]=b[i]-20; } void SubArray2(int *aPtr,int n) /* 指针作为参数,将数组中的元素减去20*/ { int i; for(i=0;i<n;i++) *(aPtr+i)=*(aPtr+i)-20; }
程序运行结果如图2-47所示。
该函数以两种方式实现了函数调用,即数组名作为形式参数和指针作为形式参数。在许多情况下,数组和指针效果是一样的。
在没有调用函数SubArray1(int b[],int n)之前,数组a在内存中的情况如图2-48所示。数组元素被保存在一个联系的存储单元中,数组名a指向数组的第一个元素。在调用函数SubArray1(int b[],int n)后,参数传递给数组名b,b也指向数组a的第一个元素,然后对所有元素减去20,如图2-49所示。调用函数SubArray2(int*aPtr,int n)后,参数传递给指针变量aPtr,aPtr也指向了数组a,并再一次把数组元素减去20,如图2-50所示。
图2-47 数组中的元素减去20后的程序运行结果
图2-48 未调用函数前
图2-49 第一次数组元素被减去20后
图2-50 第二次数组元素被减去20后
注意
在传值调用中,参数传递是单向传递,只能由实际参数传递给形式参数,而不能把形式参数反向传递给实际参数。而在传地址调用中,对形式参数的操作,即是对实际参数的操作,它们拥有同一块内存单元,属于双向传递。