接下来我们继续学习函数吧!
5.2 函数参数及其传递方式
5.2.1 函数的参数
形参和实参的使用要注意以下几点:
(1)形参只有在函数调用的过程中才占用内存的存储单元。在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
(2)实参可以是常量、变量、表达式、函数返回值等。无论实参是何种形式的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。
(3)实参和形参在数量、类型、顺序上应相同或赋值兼容,否则会发生“类型不匹配”的错误。
(4)函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向传送给实参。因此在函数调用过程中,形参的值发生改变,而实参的值不会改变。
5.2.2 函数参数的传递方式
使用函数参数可以传递两种不同的数据,即传递值和传递变量的地址。前一种方式称为值传递,后一种方式称为地址传递。
1. 值传递
形参变量是普通变量(非指针变量)时,参数的传递即为值传递。
【例】分析下面程序中参数传递过程相关变量的变化情况。
#include<stdio.h>
void main()
{
void swap(int a, int b); /* swap()函数的声明 */
int x = 2, y = 3;
printf("调用swap前,变量x,y的值:%d, %d\n", x, y);
swap(x, y); /* swap()函数的调用,x,y为实参 */
printf("调用swaphou,变量x,y的值:%d, %d\n", x, y);
}
void swap(int a, int b) /* swap()函数的定义,a,b为形参 */
{
int t;
t = a; a = b; b = t; /* 交换形参变量a,b的值 */
printf("调用swap中,变量a,b的值:%d, %d\n", x, y);
}
程序执行结果如下图5-1所示。从结果可以看出,函数调用中形参的值进行了交换,而实参的值没有改变,仍保持原值。
2. 地址传递
当形参变量是指针或数组类型时,实参和形参之间是地址传递。调用函数时,要求为形参传递变量的地址或数组的首地址,或者说,实参应是主调函数中变量的地址或数组的首地址。
使用地址传递时,形参有两种具体形式:一种是指针形参;另一种是数组形参。数组形参时,编译程序把数组名也作为指针变量处理。也就是说,这两种形式的形参没有本质区别,仅仅是书写形式上的不同。
(1)指针作函数的参数
指针作为函数参数时,实参和形参都为指针类型。进行函数调用时,将实参地址传递给形参指针变量。在被调用函数的内部,通过形参指针间接访问主调函数中的变量。
【例】指针作函数参数。
#include<stdio.h>
void main()
{
void swap(int *a, int *b);
int x = 2, y = 3;
int *p1, *p2;
p1 = &x;
p2 = &y;
printf("调用swap前,变量x,y的值:%d, %d\n", x, y);
swap(p1, p2); /* 调用swap()函数,p1,p2为实参 */
printf("调用swap后,变量x,y的值:%d, %d\n", x, y);
}
void swap(int *a, *b) /* 定义swap()函数,a,b为指针形参 */
{
int t;
t = *a; *a = *b; *b = t; /* 交换形参变量a,b所指向变量的值 */
}
程序执行结果如下图5-2所示。
(2)数组作函数的参数
数组作为函数的参数,其本质与指针作为参数是一样的。当函数的形参为数组时,实参应是数组名,将实参数组的首地址传给形参,形参数组与实参数组共用一块内存空间,即主调函数中数组的空间。
【例】设计一个函数,对整型数组的前n个元素从小到大排序,在主函数中调用该函数对数组中的数据排序。
#include<stdio.h>
void selsort(int x[], int n) /* 定义函数selsort(),选择法排序 */
{
int i, j, k, temp;
for(i = 0; i < n; i++)
{
k = i;
for(j = i + 1; j < n; j++)
{
if(x[k] > x[j]) k = j;
}
if(k != i)
{
temp = x[k]; x[k] = x[i]; x[i] = temp;
}
}
}
void printa(int *x, int n) /* 定义函数printa(),输出数组数据 */
{
int i;
for(i = 0; i < n; i++)
{
printf("%4d", x[i]);
}
printf("\n");
}
void main()
{
int a[10] = {66, 77, 45, 76, 89, 54, 32, 67, 78, 88};
printa(a, 10); /* 调用函数printa() */
selsort(a, 10); /* 调用函数selsort() */
printa(a, 10); /* 调用函数printa() */
}
程序执行结果如下图5-3所示。
就参数传递而言,地址传递与值传递没有区别,都是将实参的值(普通值或地址值)赋值给形参。但从数据访问来看,由于地址传递的是主调函数中变量的地址,通过该地址可以间接访问主调函数中的变量;而值传递是将常量、变量或表达式的值传递给形参,与主调函数之间没有访问变量的通道,不能访问主调函数中的变量。因此,有人认为值传递是单向传递,而地址传递是双向传递,这是从被调函数是否能访问主调函数中的变量而言的。
5.3 函数的嵌套调用和递归调用
5.3.1 函数的嵌套调用
C语言中函数的定义都是相互平行、相互独立的,即定义一个函数时,该函数体内不能包含另一个函数的定义,即函数不能嵌套定义。
在C语言中,函数不可以嵌套定义,但可以嵌套调用。
【例】用牛顿迭代法求解一元三次方程在附近的近似解,精确到。
(注:牛顿迭代法详细讲解见“ 马同学高等数学 ”的博客:https://blog.csdn.net/ccnt_2012/article/details/81837154)
分析:
用牛顿迭代法求解方程在α附近近似解方法如下:
(1)在α附近取一点作为根的第0次近似值。
(2)过点作曲线的切线与轴的交点的横坐标作为方程的第1次近似解。
切线方程为:
令,得切线与轴交点的横坐标:
(3)过点作曲线的切线与轴的交点的横坐标作为方程的第2次近似解:
(4)推广到一般:
逐次逼近方程的解,直到为止。
程序如下:
#include<stdio.h>
#include<math.h>
float f(float x); /* f()函数声明 */
float tang(float x); /* tang()函数声明 */
float root(float x); /* root()函数声明 */
void main()
{
float x0 = 4.0, x1, y; /* 取4为根的第0次近似值 */
do{ /* 牛顿法求方程根 */
x1 = root(x0); /* root()函数调用 */
y = f(x1);
x0 = x1;
}while(fabs(y) >= le-5);
printf("a root of equation is %8.4f\n", x1);
}
float f(float x) /* f()函数定义 */
{
float y;
y = x * x * x - 2 * x * x - 4 * x - 7;
return(y);
}
float tang(float x) /* tang()函数定义 */
{
float y;
y = 3 * x * x - 4 * x - 4;
return(y);
}
float root(float x) /* root()函数定义 */
{
float y;
y = x - f(x) / tang(x);
return(y);
}
程序运行结果如下图5-4所示。
5.3.2 函数的递归调用
C语言允许函数直接或间接地调用它自身,这种调用形式称为递归调用。
合理的递归调用应该是有限的,是在一定条件下能终止的递归调用,即要有递归终止条件。
【例】用递归函数计算。
分析:求可以表示为递归公式:
程序如下:
#include<stdio.h>
long fac(int n); /* fac()函数声明 */
void main()
{
int n;
long y;
printf("input a integer number:");
scanf("%d", &n);
y = fac(n); /* fac()函数调用 */
printf("\n%d! = ld\n", n, y);
}
long fac(int c) /* fac()函数定义,采用递归方法实现 */
{
long f;
if(n == 0 || n == 1)
{
f = 1;
}
else
{
f = fac(n - 1) * n; /* fac()函数定义中又调用fac()函数 */
}
return(f);
}
程序运行结果如下图5-5所示。
函数部分未完待续……欢迎大家提出意见或建议,一起进步!