5.3 函数调用及执行过程
5.3 函数调用及执行过程
上一节讲解了函数的定义方法和函数的一般设计原则。本讲将介绍如何使用自定义函数,以及在调用函数时的参数传递方法和函数的执行过程。
定义好的函数仅是一段不会被执行的代码,静静地呆着,在执行前不会产生任何作用。函数要起作用的前提是被其它函数调用,并且被执行。前面我们使用过了printf、scanf、getchar、sqrt等函数,这些函数都是别人写好的库函数,在程序中对这些函数的使用称为函数调用。
正确调用函数需要知道函数的功能是什么、给函数“喂进”什么数据、函数完成后会返回什么结果?这是函数调用的三要素。如对scanf和printf的调用如下:
scanf("%d%d%d",&x,&y,&z);
printf("x=%d,y=%d,z=%d\n",x,y,z);
两个函数的调用都是由函数名、一对括号和一系列参数构成的,而且参数之间全部用逗号“,”号分隔,即:
函数名( 实际参数列表 )
“函数名”代表了函数的代码段,“实际参数列表”表示要“喂给”函数的数据,要“喂给”函数多少个数据,每个数据的类型是什么?取决于函数定义时的形式参数的个数及类型。如果函数定义时没有形式参数,则调用时括号内也不要给任何数据。我们发现scanf和printf函数在调用时实际参数个数可多可少,且参数类型也是可变的,这是由于scanf、printf函数使用了可变形式参数的原因,这种情况相当复杂,学习者可自行查阅可变形式参数的定义方法。实际参数可以常量、变量或任何合法的表达式,在实际参数传递给形式参数之前要从右至左先完成计算和传递,被调用函数的实际参数要与其定义时的形式参数个数相同,对应参数的数据类型要相容。
函数分为“函数型”函数和“过程型”函数。前者具有返回值,后者没有返回值,其调用方法也略有不同。“函数型”函数既可以用作表达式中的运算量也可以用作语句,如:
函数名( 实际参数列表 )
或
函数名( 实际参数列表 );
用作语句时,只要在函数调用之后加上分号即可,前面的scanf和printf函数就是按语句使用的例子。
“过程型”函数指的是函数类型为void的函数,这种函数没有返回值,因此不能作为运算量用在表达式中,只能作为语句的调用,即:
函数名(实际参数列表);
两个函数A、B发生关联的方法是函数调用。如果在函数A中调用了函数B,如:
A( )
{
...
B( );
...
}
我们称函数A为主调用函数,函数B则称为被调用函数。这种调用关系表明了函数A的完成要依赖于B的执行结果。
接下来,看一下如何进行函数调用。函数必须先定义后使用,也就是被调用函数必须在主调用函数之前定义才能被调用。所以函数定义时要注意定义的先后顺序。先来看一个例子。
例1:求sum=3!+5!+7!+11!
分析题目,我们如果能写出求阶乘的函数fact,就可以调用函数fact四次累加后求出结果,如:
sum=fact(3)+fact(5)+fact(7)+fact(11);
可见,解题的关键是写出阶乘函数。让我们用IPO分析方法来进行函数的分析和设计。
(1)确定输入:函数要求某个整数的阶乘,因此只用一个输入,如n,即只需要定义一个形式参数。
(2)确定输出:函数要返回n!,所以,也只有一个输出一个变量f,其值表示n!。为了可以求更大的n的阶乘,变量f可用双精度实型(double)或长长整型(long long)表示。
(3)确定处理:可设变量f的初值为1,只需要把1至n的每个整数i依次乘到f中,即重复做f=f*i就能得到n!并放在f中,代码为:
f=1;
for(i=1;i<=n;i++)
f=f*i;
根据(1)(2)可写出函数框架:
double fact(int n)
{
函数体
return f;
}
把(3)中处理的代码替换“函数体”并完善变量定义,求阶乘的函数定义如下:
double fact(int n)
{
double f;
int i;
f=1;
for(i=1;i<=n;i++)
f=f*i;
return f;
}
有了求阶乘函数后,主函数可调用完成指定的功能:
int main( )
{
double sum;
sum=fact(3)+fact(5)+fact(7)+fact(9);
printf("sum=%.0f\n",sum);
return 0;
}
本例只涉及到两个函数,fact是被调用函数,main是主调用函数。所以fact应该在main之前定义,因此,完整的程序如下:
#include<stdio.h>
double fact(int n)
{
....
}
int main( )
{ ...
sum=fact(3)+fact(5)+fact(7)+fact(9);
...
}
如果不按被调用函数、主调用函数的顺序定义函数,即把被调用函数fact定义在主调用函数之后,会发生什么?
#include<stdio.h>
int main( )
{ ...
sum=fact(3)+fact(5)+fact(7)+fact(9);
...
}
double fact(int n)
{
....
}
以上程序将不能通过编译处理,因为在main中调用fact之前还未对fact函数进行定义,打破了“先定义后使用”的规则。解决之道是在调用函数前要对该函数进行函数声明,表明该函数在程序中是有定义的,可以被调用。
声明的格式和定义基本一样,不同的是:(1)函数的声明中的形式参数名可以省略,只保留参数类型即可;(2)函数声明可以与变量定义可以放在一起。如以下几种函数声明方法都是合法的:
(1)double fact(int n);
(2)double fact(int);
(3)double sum,fact(int);
方法(1)最简单,只是把函数定义的头部直接复制后粘贴在声明位置再加分号;方法(2)则省略了形式参数名,只保留每个形式参数数据类型名;方法(3)是把函数声明和变量定义混在一起。在main调用fact之前,先对fact函数进行声明后就可以正常调用了,main程序如下:
int main( )
{
double sum,fact(int); //声明fact函数
sum=fact(3)+fact(5)+fact(7)+fact(9);
...
}
double fact(int n)
{
....
}
我们再看一个函数设计和使用的例子。
例2:写一个用格里戈里公式求π的近似值的函数,要求最后一项的误差的绝对值小于指定的eps值。
先按自己喜欢给函数起一个名字如getPi,然后确定函数的输入和输出。根据题目的意思,输出的结果是由最后一项误差eps决定,因此函数形参只需要一个双精度实数eps。函数执行完后只需要返回一个结果,即PI的近似值,所以函数框架定义为:
double getPi(double eps)
{
double pi;
函数体
return pi;
}
用前一章求PI的关键代码(见4.3)去替换“函数体”部分,进一步完善变量的定义,函数变为:
double getPi(double eps)
{
double pi,item;
int d,flag;
item=1;d=1;flag=1; //可以写为;item=d=flag=1;
while( fabs(item)>=eps )
{
sum=sum+item;
d=d+2;
flag=-flag;
item=flag*1.0/d;
}
pi=4*sum;
return pi;
}
我们可以多次调用getPi输出不同误差下的π的值,如:
int main( )
{
double Pi;
Pi=getPi(0.001);
printf("Pi=%f\n",Pi);
Pi=getPi(0.0001);
printf("Pi=%f\n",Pi);
Pi=getPi(0.00001);
printf("Pi=%f\n",Pi);
return 0;
}
主调用函数和被调用函数的执行过程是怎样的呢?接下来该看一下函数的调用和执行过程了。
以主函数main调用函数getPi求π的近似值为例,过程是这样的:(1)先要保存调用时main中各种变量的值及当前的内存地址(设为A),以便函数执行完后能返回到正确的位置,接着转向getPi的程序段执行;(2)为被调用函数的形式参数自动分配存储空间,同时计算实际参数的值后分别给对应的形式参数赋值,然后开始执行被调用函数体中的语句,直到碰到return语句或到函数结束括号“}”;(3)由return把计算结果返回给主调用函数,无条件转向主调用函数调用点的位置(A),在主调用函数中把函数的计算结果赋给指定的变量Pi,然后继续执行函数调用后面的语句,此时函数名就像一个变量一样被使用。第二次和第三次调用的过程都是一样的。执行顺序示意图如下;
上图,getPi程序段被重复写了三次,只是为了观察调用过程方便而已,实际上是同一段程序代码。
再来看一个复杂的函数调用实例。当函数A调用函数B,函数B又调用函数C,函数C又用函数D,代码执行顺序是什么呢?
程序在执行函数A时,先执行语句1,然后转到B,执行语句3,转到C,接着执行语句5,转到D,执行语句7,转到E,在E 中执行语句9和语句10,然后返回D,执行语句8,返回C,执行语句6,返回B,执行语句4,返回A,执行语句2,结束函数A的调用并返回主调用函数。语句的执行顺序为:语句1->语句3->语句5->语句7->语句9->语句10->语句8->语句6->语句4->语句2。可见语句2是要把后面所函数执行完后才能执行到。
本节讲了函数的调用,以及在调用过程中实际参数与形式参数的传递方法,分析了函数的调用和执行过程。正确理解函数的调用和执行过程是阅读和编写程序的关键。本节就讲到这里,下次再见!