第8章函数本章的学习重点◆函数的分类◆函数的定义◆函数的调用与声明◆函数的实参与形参◆局部变量和全局变量◆函数的嵌套调用8.1函数的定义函数是C语言的基本组成单位,它是模块化程序设计的主要构成单元。模块单元是指实现某一功能的程序段,通常以函数形式实现。8.1.1函数的分类1.按照定义类型划分:主函数、库函数和用户自定义函数。主函数:主函数的调用名称为main(),它是C语言中最主要的函数,在C语言中具有唯一性。库函数:库函数通常由开发编译系统的人员编写,并加以封装后嵌入到C语言编译系统中。用户自定义函数:用户自定义函数是由用户定义,用于完成某些特定功能的程序段,通常也将用户自定义函数称为子函数。2.按照是否能够返回值划分按照能不能返回数值分为返回值类型函数和无返回类型函数。8.1.1函数的分类返回值类型函数:返回值类型函数在定义时应指定返回值的类型。例如:intmax(){程序段}无返回类型函数:无返回类型函数在定义时应在函数名前面标注void说明符,以说明该函数为无返回类型。3.按照函数是否带参数划分按照函数是否带有参数分为有参函数和无参函数。有参函数:函数定义和调用时时含有输入参数的函数。无参函数:函数定义和调用时都不带参数的函数类型。8.1.2函数的定义函数定义的一般表达形式为:类型说明符函数名(形参表列){程序体}函数构成的程序结构如下:函数名1(参数表列){程序体1}8.1.2函数的定义1.无返回值函数定义无返回值类型的函数定义使用void作函数类型说明符。例如:voidtest(){printf(“Thisisafunctiontest\n”);}2.返回值函数定义带有返回值的函数定义时类型说明符可以使用除void以外的任何数据类型。函数使用return语句返回数值。例如:01intSum()02{03inti=0,sum=0;04while(i10)05{06sum=sum+i++;07}08returnsum;09}8.1.2函数的定义3.带参数函数的定义有时候使用函数时需要用到函数体外的一些变量或数值,这时候,就需要用到函数参数来传递到函数体内。例如:01intmax(inta,intb)02{03if(ab)04returna;05else06returnb;07}函数max判断两个整型数值a和b的大小,并返回较大的一个。8.2函数的调用与声明函数定义后,通过被调用来执行。通常情况下,在调用函数时,应先对函数进行声明。8.2.1函数的调用函数在定义之后,可以由它本身或其他函数体调用。函数调用的一般表达形式为:函数名(实参表列)其中函数名就是函数定义的函数名称,在这里,函数称为被调函数。范例8.1SubFuncCalcMaxVal.c设计一个返回int类型的函数max,功能是计算主调函数中两个变量的大小,返回较大的一个,当两个数相同时,打印数据相同的信息。范例分析:1.子函数max的定义2.函数头3.形参8.2.1函数的调用4.函数调用语句5.程序的入口6.程序的执行自定义函数可以在程序中被多次调用,使用不同的实参,返回结果也有所不同。由于函数具有很好的模块管理功能以及支持反复调用功能,因此函数被广泛用于程序中实际问题的解决。实训8.1——计算数学分段函数数学分段函数P(m,n)由两部分组成,当mn时,P(m,n)的值为,当m=n时,P(m,n)的值为0。编写程序,键盘输入m和n的值,使用子函数计算阶乘,调用该函数,实现对数学函数的计算。1.需求分析:需求1:计算分段数学函数的值需求2:设计程序子函数factorial,用于计算阶乘。2.技术应用对于需求1,使用if…else分支语句实现分段数学函数的计算。对于需求2,设计名为factorial的子函数,该函数含有一个形参,返回值类型为double,避免因为返回值数值过大导致数值溢出。源程序:SubFuncCalcPartsFunction.c!!()!(,)0mmnnmnPmnmn!!()!mnmn(,)Pmn8.2.2函数的声明1.函数声明的一般形式函数声明应该放在调用函数处的前面,其一般表达形式为:类型标识符函数名(类型标识符1形参名1,类型标识符2形参名2,……);也可以省略形参名,只保留类型标识符:类型标识符函数名(类型标识符1,类型标识符2,……);例如:intmin(inta,intb);或者intmin(int,int);8.2.2函数的声明2.函数声明的位置函数声明的作用是告诉编译系统有一个已经定义好的子函数可以调用,因此,通常将函数声明放在函数的头部,而将函数定义放在调用函数位置之后。例如:intcallback_test(inta,intb);//函数声明voidmain()//主函数{……a=callback_test(x,y);//函数调用语句……}intcallback_test(inta,intb)//函数的定义{函数体}8.2.2函数的声明范例8.2CalcOverTurnSeqNum.c计算数列M=1-2+3-…+(n-1)-n。设计一个函数,命名为OverTurnSeqNum,函数的功能是计算M的值,其中n由形参传入。3.多文件系统的函数声明当需要调用的函数与本身的函数不在一个文件中时,必须使用函数声明,以保证程序编译时能够找到该函数,并使程序正确运行。实训8.2——近似计算圆周率pi编写程序,利用公式计算圆周率pi,定义子函数计算pi,并将该子函数保存于单独的文件中,在主函数中输出计算所得的pi的值。输入n的值以确定pi的精度。1.需求分析:需求1:计算的值需求2:通过调用数学库函数sqrt计算pi的值需求3:输入n的值确定pi的精度需求4:定义的子函数在一个单独的文件中需求5:声明子函数,并定义在declare.h文件。2.技术应用对需求1,定义子函数Calc_pi(doublen),计算公式的值。对需求2,子函数Calc_pi(doublen)中调用sqrt函数。对需求3,主函数中输入n的值,以确定pi的精度。对需求4,将子函数定义在单独的文件中,命名为Calc_pi.c。对需求5,新建文件,命名为declare.h,将子函数Calc_pi(doublen)的声明放到改文件中,在主函数所属文件中包含该头文件。222211111...623n8.2.3函数的参数1.函数的形参形参仅出现在被调函数中,类似于函数内定义的变量,在函数体内任何位置都可以使用。但形参只能用于本函数体内,不能用于其他函数。2.函数的实参实参仅出现在主调函数中。通常,实参是能够计算为数值的变量或表达式,也可以是指针、数组名和函数等。3.函数调用时的实参与形参关系函数调用时,主调函数将实参的值传递给被调函数的形参,从而实现主调函数向被调函数的数据传送。8.2.3函数的参数函数调用时实参和形参之间的值传递过程如下图:范例8.3ExchangeTwoValueInSubFunc.c设计一个函数,命名为ExchangeValue,函数的作用是交换由主调函数输入的两个变量的值,并在主调函数中输出函数调用前后两个变量的值,函数定义类型为void类型。形参inttest(inta,intb){函数体}被调函数值传递x=100,y=50;Reslut=test(x,y);主调函数实参8.2.3函数的参数范例8.3ExchangeValue()函数调用时m和n以及a和b的内存结构变化图:10ExchangeValue()调用前mExchangeValue()初始调用20n10a20b分配内存给a和b10ExchangeValue()a和b数值交换完毕mExchangeValue()调用结束20n2010ab10m20naba和b内存空间被释放程序执行流程内存数据变化8.3局部变量与全局变量局部变量的作用域仅限在函数内部,即变量的定义和使用都只能在函数内进行。如下面程序中所示:01chartest1(charc)02{03inta,b;04……05}06inttest2(intm,intn)07{08intx,y;09……10}11voidmain()12{13charw=’A’,v;14intf1=10,f2=12,res=0;15v=test1(w);16res=test2(f1,f2);17{18intnext=0;19next=v+res;20}21……22}形参c,变量a和b的作用域,仅用于函数test1()内部。形参m和n,变量x和y的作用域,仅用于函数test2()内部。变量w、vf1、f2和res的作用域,仅用于函数main()内部。变量next的作用域8.3局部变量与全局变量范例8.4PartVariable.c定义一个函数intcal(intm,intn),在函数内部定义内部变量mn和nm,计算m和n之间的差。8.3.2全局变量要引用其他文件的全局变量,需要对全局变量进行声明,声明全局变量的一般表达形式为:extern类型标识符变量名1.全局变量的定义和声明全局变量可以定义在程序的前面,也可以定义在程序中间,当定义在程序前面时,可以不对其声明而直接使用,但当定义在程序中间时,必须进行变量的声明。范例8.4OutVariable.c函数使用全局变量时,应该将该全局变量的定义放在函数前面,否则,就要先声明该全局变量,然后才能在函数内使用。在一个自定义函数内对两个全局变量进行自增运算,其中一个全局变量在函数后面定义,使用前先声明该变量。8.3.2全局变量2.全局变量的作用域全局变量可以被任何函数使用,在每个函数中修改这类变量都会引起该变量值的变化,并对其他函数使用该变量产生影响。范例8.5OutVariableControl.c交换两个变量的值在C语言中经常用到,但在范例8.3中并没有在子函数中实现变量值的交换。设计一个子函数exchange(),在这个函数内交换两个全局变量的值。3.全局变量的命名当全局变量与局部变量的名字相同时,在局部变量作用域内,全局变量将不起作用。8.4函数的嵌套调用和递归C语言允许在一个函数中调用另一个函数。这种方式称为函数的嵌套调用。如图所示为函数嵌套调用示意图。范例8.6ReCallbackFunc.c判断三个数中的最大值有很多方法,使用嵌套函数调用也是其中一种方法,设计两个函数,实现求三个数的最大值,并使用嵌套函数调用实现。voidmain(){调用函数test1()}test1(){调用函数test2()}test2(){函数体}程序主入口1234实训8.3——汉诺塔程序设计汉诺塔游戏又称为圆盘游戏,玩法是有三个柱子A、B和C,其中柱子A上按由大到小穿插着n个中间含孔的圆盘,要求借助柱子B,将这n个圆盘移动到柱子C上,每次只能移动一个盘子,并且任何时候都不能出现大盘在上,小盘在下的情况,编写程序,实现移动n个盘子的汉诺塔移动方法程序设计。1.需求分析:需求1:移动一个盘子时的移动方法需求2:移动n个盘子时的移动方法2.技术应用对于需求1,当仅需要移动一个盘子时,就是要将该盘子从A移动到C,方法为AC。对于需求2,当有n个盘子需要移动时,先考虑如何将上面n-1个盘子移动到B,如果有方法能够将上面n-1个盘子借住C移动到B,则最下面一个盘子可以按照需求1的方法将盘子移动到C,然后再借助同样的方法将B上的n-1个盘子借住A移动到C。源代码:Hanoi.cABC8.5数组作函数参数范例8.7CheckArrayCompnent.c有一个字符数组,其中各元素可能是字母,也可能是数字或其他特殊字符,设计一个子函数,分析数组中各元素的值,当是字母时打印出该字母,否则输出非字母信息。范例8.8FuncParamWithArrayName.c函数参数传递时,实参到形参的传递只能是单向的值传递。同时,由于参数传递的单向性,一般情况在函数中无法修改实参的值。但是,当使用数