2010-101《C语言程序设计实用教程》Powerpoint制作:耿祥义张跃平第7章函数的结构与调用2010-102主要内容及难点2010-103概述一个C程序就是由若干个函数所构成。因此掌握怎样编写一个函数,才能掌握如何编写C程序。本章详细讲解函数的基本结构和函数的调用。本章不再叙述VC++6.0编译、链接、运行的过程。对于本章例子中的的C程序,在用VC++6.0时,要建立相应的工程,并将源文件加到工程中。2010-1047.1函数概述1.函数与程序的结构一个C程序(VC++6.0中称作一个工程)是由若干个函数所构成,这些函数可以在一个源文件中,也可以分布在若干个源文件中。2.main函数的重要性一个C程序必须有且仅有一个main函数,操作系统从main函数开始执行C程序,因此,一个C程序的各个源文件中有且仅有一个源文件包含有main函数。3.函数的调用原则操作系统通过执行main函数开始运行一个C程序。main函数可以调用C程序中的其他函数来完成程序的任务,其他函数也可以互相调用,但其他函数(非main函数)不能调用main函数(main函数只能由操作系统来调用)。2010-1057.2有关函数的重要术语函数头只有函数的类型、名称和名称之后的一对小括号以及其中的参数列表。需要注意的是,函数头中参数的类型不可省略,例如intadd(intx,inty,intz)是正确的函数头,而intadd(intx,y,z)是错误的函数头。7.2.1函数头2010-1067.2.2函数原型按着ANSIC标准,在源文件的预处理指令的后面、函数定义之前声明函数的原型,以确保该函数在当前源文件中有效。声明函数的原型就是函数头用分号结尾,例如声明一个无参数函数的原型intspeak();声明一个有参数函数的原型intadd(int,int);intadd(inta,intb);函数的原型中允许省略参数的名字,上述声明都是正确的。以上声明都是add函数的原型,二者是等价的。通过声明函数的原型(在源文件的预处理指令的后面、函数定义之前)可以让该函数在当前源文件中有效。当前源文件中的函数就可以调用这个函数。2010-1077.2.3函数定义(原型实现)函数定义也称原型实现。按着ANSIC标准,必须按着函数的原型给出函数的实现。当按着函数原型实现一个函数时,也称给出函数定义。函数定义(函数实现)包括有两部分:函数头和函数体。◆声明一个有参数函数的原型intadd(inta,intb);◆根据此add函数的原型给出的add函数的定义。intadd(intx,inty)//函数头{//函数体开始printf(两个整数和);returnx+y;}//函数体结束1.函数头:函数头要保持和函数的原型中的函数头一致。2.函数体:函数头之后的一对大括号以及之间的内容是函数体。由两部分构成:声明变量部分和语句部分(1)声明变量:在函数体中声明的变量被称做局部变量,仅在该函数内有效.(2)语句:函数体中的语句可以操作局部变量形成解决问题的算法。例子1(example7_1.c、circle.c)main函数调用getCircleArea函数得到圆和梯形的面积。2010-1087.3无参与有参函数的定义按着ANSIC标准,当在一个源文件定义一个函数时,需要按着源文件中给出的函数原型声明来实现该函数,其关键就是给出函数体,函数体中的语句可以操作局部变量形成解决问题的算法。2010-1097.3.1无参函数的定义对于无参函数,只需按着函数原型给出定义即可,例如对于函数原型声明:doublecry();下列是一个合法的函数定义(实现):doublecry(){printf(%f,3.24);return100.98;}2010-10107.3.2有参函数的定义对于有参数的函数,尽管允许函数原型中省略参数的名字,但是,定义该函数时,函数头中务必要写上参数的名字,不允许省略;如果函数原型中没有省略参数的名字,定义该函数时,函数头中的参数名字既可以和函数原型中的保持一致也可以另外命名。例如,对于函数原型:intadd(int,int);或intadd(intx,inty);下列是一个合法函数定义(实现):intadd(inta,intb){intsum;sum=a+b;returna+b;}2010-10117.4函数的类型与return语句函数的类型可以是C语言中的基本类型、void型以及以后学习的指针类型。本节讲解函数类型是基本型和void型的情况。2010-10127.4.1基本类型_1函数类型是基本类型(char、short、int、long、float或double)时就决定了函数需要返回一个基本类型的数据,即调用这样的函数不仅能执行函数体中的代码,而且还可以得到函数的返回值。1.return语句的作用当函数是基本类型时,在函数定义时,函数体中必须包含有return语句(return语句是由关键字构成的语句)。return语句的语法格式如下:return表达式;或return(表达式);return语句有两个作用:◆返回一个值给函数的调用者。◆结束当前函数的执行。例子2(example7_2.c、sum.c)main负责调用contimueSum函数。2010-10137.4.1基本类型_22.返回值的精度类型按精度从低到高排列:charshortintlongfloatdouble(1)从低到高的转换当return语句返回的值的级别低于函数的类型的级别时,系统将return语句返回的值进行类型转换运算、提升为函数类型所指定的级别后再最终返回,即提高返回值的精度。(2)从高到低的转换当return语句返回的值的级别高于函数的类型的级别时,系统将return语句返回的值进行类型转换运算后再最终返回,即降低返回值的精度。例子3(example7_3.c)main负责调用contimueSum函数。2010-10147.4.2void型如果一个函数不需要返回任何数据,那么按着ANSIC标准,该函数的类型应当是void型。调用者调用void函数仅仅是执行函数体中的代码,不会得到函数的返回值。当函数是void型时,在函数定义时,函数体中的最后一条语句不必是return语句。注程序可根据需要,随时在函数体中执行return语句来结束函数的执行。例子4(example7_4.c,out.c,reverse.c)函数outGraph函数输出A,B,C,D,E字母组成的图案,reverseNumber函数负责倒置一个正整数,比如,将123倒置为321。2010-10157.5函数的调用与参数传值函数调用的基本原则是main函数可以调用C程序中的其他函数,其他函数也可以互相调用,但其他函数(非main函数)不能调用main函数。2010-10167.5.1调用无参数函数由于无参数函数没有参数,因此,调用时不必向参数传递值。无参数函数的调用格式是:函数名()如果无参数函数没有返回值,调用该函数仅仅是执行函数体中的代码。如果无参数函数有返回值,那么调用getNumber函数不仅执行函数体中的代码,而且调用者会得到此函数的返回值。2010-10177.5.2调用有参数函数_1对于有参数函数,函数定义时必须给出参数的名字。参数属于局部变量,有效范围是整个函数体内,因此在调用有参数的函数时,必须向参数专递值。1.形参与实参(1)形参函数的参数也称作形式参数,简称形参。形参的特点:◆名字不必和原型声明中的一致◆形参一定是变量,不能是表达式◆在函数体中编写代码时,默认形参是有值的,即调用者将来传递过来的值。因此,除非特别需要,函数体一般不对形参进行初始化。◆形参的名字要互不相同,而且不能和函数体中的局部变量同名。以上的第1点和第3点也是把函数的参数称作形式参数的原因。(2)实参实参是负责向形参传递值的一个表达式,即调用函数时将实参的值传递给形参。实参可以是一个复杂的表达式,也可以是一个变量或一个常量构成的简单表达式。2010-10187.5.2调用有参数函数_22.传值特点调用函数时,对于函数中的参数,需要将某个实参的值传递给参数。传递的特点是“复制”机制,也就是说,函数中参数变量的值是调用者指定的值的拷贝(相当于对参数变量实施了一次赋值操作)。例如,如果向函数的int型参数x传递一个int型变量y中的值,那么参数x得到的值是y的值的拷贝。因此,如果函数在函数体中改变参数变量的值,不会影响向参数传递值的变量的值,反之亦然。3.调用格式有参数函数的调用格式是:函数名(实参列表)在调用过程中避免“实参不足错误”、“实参过多警告”。4.传值与精度当实参的值的级别低于形参类型的级别时,系统将实参的值进行类型转换运算、提升为形参类型所指定的级别后再传递给形参,即提高传值的精度。当实参的值的级别高于形参类型的级别时,系统将实参的值进行类型转换运算、降低为形参类型所指定的级别后再传递给形参,即降低传值的精度。例子5(example7_5.c,f.cg.c)f函数负责计算等差数列的和,g函数负责计算等比数列的和,主函数负责调用两个函数,并负责向函数的参数传递值。2010-10197.6函数封装代码的思想函数封装代码的思想,其核心内容是以下三点◆有利于代码的复用◆便于系统的维护◆“黑盒”式调用例子6(example7_6.c,gongYue.c,gongBei.c)函数intgongYue(int,int)负责返回两个参数指定的正整数的最大公约数函数intgongBei(int,int)负责返回两个参数指定的正整数的最小公倍数函数intgongBei(int,int)调用了函数intgongYue(int,int)。例子7(example7_7.c,guess.c)主函数可以反复调用guess函数达到重复玩猜数游戏的目的。2010-10207.7函数的递归调用函数可以间接或直接调用自身。间接递归是一个函数调用了另外一个函数,而后者又调用了前者。直接递归是指一个函数直接调用了自身,即一个函数在执行函数体的代码中包含有调用自身的代码。7.7.1递归调用的发生如果函数调用了自身,那么一旦程序调用该函数,将出现不断地调用该函数的执行流程,即出现了函数的递归调用。因此需要在适当的条件下结束这种递归过程,否则程序将进入一个“死”循环过程,即进入一个无法结束的执行流程。2010-10217.7.2递归的应用_1)2(3)1(11nanann对于代数中的数列:在main函数中调用函数f,调用时实参的值是3。intmain(){intnumber=f(3);printf(%d\n,number);return0;}具有上述特点的函数的代码如下:intf(intm){intresult;if(m==1)result=1;elseif(m1)result=f(m-1)+3;returnresult;}2010-10227.7.2递归的应用_2递归调用的特点:通过前面的分析,不难看出递归调用的时间顺序和返回值的时间顺序刚好相反,即最后被调用的函数是第一个给出返回值的,而最先调用的函数是最后一个给出返回值的。递归调用属于“先进后出”。例子8(example7_8.c)使用函数递归计算了6的阶乘。例子9(example7_9.c)使用函数递归输出Fibonacci数列第41项,并显示用时.2010-10237.7.3递归的缺点递归的优点可能很明显,那就是比较容易解决递归求解问题,但缺点也是显而易见的。递归付出的是空间和时间的代价,尤其是高次递归。比如,例子9中的fibonacci函数就属于高次递归。如果执行fibonacci(50),那么付出的空间和时间代价都是非常大的,对于fibonacci(200),个人PC机几乎无法完成。fibonacci(4)的递归次序和返回值次序如图7.13所示。假设计算机执行一条指令的最快时间为p(单位可以是任何单位),那么调用fibonacci(n)花费的时间大约是p×2n,也就是说,