资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,单击此处编辑母版标题样式,单击此处编辑母版文本样式,二级,三级,四级,五级,*,第5章 函数与运算符的重载,5.1 三次方程求根程序的设计,5.2 函数的说明与使用,5.3 函数的嵌套与递归,5.4 函数与运算符的重载,5.5 函数与,C+,5.6,1,5.1 三次方程求根程序的设计,-,计算三次方程,x,3,+px+q=0,的一个实根的公式为,x,r,=,为了从系数,p,、,q,计算实根,x,r,把公式的计算分解为下面几步:,1,)令实数,x,r,A+B;,2,)令实数,A,B,分别为实数,R,S,的立方根:,3,)令,R=-q/2+a,S=-q/2,a;(5.2),4,)令,a=sqrt(q/2)*(q/2)+(q/3)*(q/3)*(q/3);,实际的计算过程为:,用,(4),计算得到,a;,用,(3),计算得到,R,和,S,;求出,R,和,S,的立方根,A,和,B;,最后得到实根,x,r,。,2,5.1,三次方程求根程序的设计,计算立方根的迭代公式,Float cuberoot,(,float x,),/,精确到小数点后,6,位,float root,croot,;,const float eps,1e,6,;,croot,x,;,do,root,croot,;,croot,(,2*root,x/,(,root*root,),/3,;,while,(,fabs,(,croot,root,),eps,);,return,(,croot,);,3,5.1,三次方程求根程序的设计,#include,iostream.h,/program5-1,#include,math.h,float cuberoot,(,float,);,void main,(,void,),float p,q,xr,;,cout,Input parameters p,q:,;,cin,p,q;,float a,sqrt(q/2)*(q/2)+(q/3)*(q/3)*(q/3);,xr,cuberoot,(,q,2,a,),cuberoot,(,q,2,a,);,cout,endl,The real root of the equation is,xr,;,float cuberoot,(,float x,),4,5.1,三次方程求根程序的设计,课本,p129,显示了不使用函数的程序。其中,croot,的立方根的运算进行了两次,所以该计算程序要重复两次,当程序较长,或计算次数更多时,采用“子程序”的方案可以大大缩短程序的长度。,特别是当程序比较复杂时,可以使得程序显得清晰,在,program5_1,中,,main,()中不涉及计算立方根的细节,显得简洁,而在,cuberoot,()中只解决一个浮点数的立方根的计算,也很清楚。,另外,还可以把立方根的计算与,C+,语言中的运算符和标准函数对应起来,当在程序中对于,cuberoot,(,y,)给出了定义之后,就可以在主函数或其它用户定义的函数中,像运算符或标准函数那样使用了,如,cuberoot,(,x,)的使用与,a+b,,,sin,(,x,)的使用没有什么区别。,5,5.2,函数的说明与使用,-参看书,p130,的5.2节,C+,程序允许两种函数说明语句的形式,我们把它们分别称为函数原型(或函数声明)和函数定义。,1,函数原型,函数原型(亦称函数声明)用来指出函数的名称,类型和参数,其格式为:,属性说明,类型,函数名,(,参数表,);,int add(int a,int b);inline void swap(float void print(char*),;,6,5.2.1,函数的说明,属性说明:可缺省,一般可以是下面的关键字之一:,inline,,,static,,,virtual,friend,等。,inline,表示该函数为内联函数;,static,表示该函数为静态函数;,virtual,表示该函数为虚函数;,friend,表示该函数为某类,(class),的友元函数。,类型:指函数的返回类型。,函数名:一个标识符。,参数表:它可能为空,,void,或,类型,参数名,,,类型,参数名,的形式。,main(),,,print(void),,,cuberoot(float x),,,add(int a,int b),7,5.2.1,函数的说明,2,函数定义,函数定义与函数原型的主要区别是它还包括函数体,其格式为:,属性说明,类型,函数名,(,参数表,),函数体,属性说明,返回类型,函数名与函数原型一致,参数表中不可省略参数名。,函数体,:,由和括起来的复合语句即程序块。,program 5_1,的最后,12,行就是一个函数定义,。,8,函数的分类方法,1,从,使用角度,分类,2,从,函数形式,分类,9,(1),从使用角度分类,从,使用角度,划分,可将函数分为:,系统预定义,的标准库函数(如,,sin,abs,等),以及由,用户自定义,的函数。,程序中可直接使用(调用)系统预定义的标准库函数,但要求在调用前使用编译预处理指令,include,将对应的头文件包含进来。,由用户自定义的函数与系统预定义的标准库函数的不同点在于,,自定义函数的,函数名,、,参数个数,、函数,返回值类型,以及函数所实现的,功能,等都完全由用户程序来规定(指定),。,10,(2),从函数形式分类,从,函数形式,划分,可分为无参函数与有参函数两类。对,无参函数,来说,调用它们时不需要提供实际参数;而对,有参函数,进行调用时,必须提供所需个数的且具有相匹配数据类型的实际参数。,11,无参函数的定义,。,无参函数定义,的一般形式,(),.,通常用于,实现某种固定功能,。例如:,void printStar().,就自定义了一个叫做,printStar,的无参函数,比如可用它来实现打印出一行共10个“*”的固定功能。,12,有参函数定义,的一般形式,(),.,通过调用处提供的,不同实参值,来,计算,出其,对应的函数值,、或,实现某种与传递过来的那些不同值有关的某种功能,。例如:,void printStar(int k).,就自定义了一个叫做,printStar,的具有1个,int,型形参,k,的函数,比如可用它来实现打印出一行共,k,个“*”的自定义功能。,13,5.2.2,函数的调用,函数调用是已定义函数的一次实际运行,与某类型的一个变量和后文中某类的一个对象类似,函数调用是函数定义的一个“实例”。,在,C+,程序中,除,main,函数外,其它任一函数的执行都是,通过在,main,函数中直接或间接地调用该函数而引发,的。调用一个函数就是,去执行该函数之函数体,的过程。,对无参函数进行调用,的一般形式为:,(),例如:,printStar();,14,对有参函数进行调用,的一般形式为:,(),例如:,printStar(26);,其中,函数说明中的参数称为,形式参数(形参,),函数调用中的参数称为,实际参数(实参,)。,实参表,在,参数个数,、,参数顺序,、以及,参数类型,等方面要与被调函数的,形参表,之间有一个,一一对应,的,相互匹配,关系。编译器将根据参数的顺序,来逐一实现实参与对应形参的“结合”,而后执行一遍函数体(而完成本次的函数调用)。,15,计算机对,函数进行调用,的执行顺序,以,Program5-1,为例,(1),根据调用语句中的函数名,(cuberoot),在整个程序中搜索同名函数定义;,(2),对实参数的参数个数,类型,顺序进行核对,判定是否与函数定义中的形参表对应一致,在上例中只有一个浮点型参数;,(3),根据参数的类型(值参数或引用参数)进行值参数的值传递或引用参数的换名,在上例中即是要把实参表达式的值计算出来赋给形参,x,;,(4),运行函数体代码;,(5),返回调用点,并返回所要求的函数值,即返回计算结果,croot,的值。,16,关于,函数原型的一点说明,当,函数调用从书写顺序上先于函数定义,时,,C+,要求必须事先列出这一函数的函数原型。,(1)无参函数,定义的函数原型,();,(2)有参函数,定义的函数原型,();,注意,与函数定义的一般形式相比,相当于用分号代换了函数体,而成为其相应的函数原型。,17,5.2.3,函数的返回,函数的返回完成两项任务:,(1),把运行控制从函数体返回到函数调用点。,在,program5-1,中就是在计算,cuberoot(-q/2+a),之后再返回到语句,xr=cuberoot()+cuberoot(),的计算过程中。,(2),根据返回值要求,返回所需要的数据值。,18,5.2.3,函数的返回,函数的返回值有下面几种情形:,1.,返回,void,类型,如果函数无值返回,应说明为,void,类型。未作类型说明的函数,系统认为是,int,类型函数,应返回一整型值。,2,返回数值类型,最常见的函数是返回一个数值的函数。,例如:,int add,(,int a,,,int b,);,当函数要返回的数值不止一个时,情况比较复杂,一般它可以以结构或类的形式,也可以以结构,数组或对象指针类型方式实现,这样的实例在后面的章节可以见到。,3,返回引用类型,值返回方式是,C,和,Pascal,语言中唯一的返回方式,,C+,语言提供的引用返回概念是一种很强的功能,当函数定义中把该函数说明为某类型的引用类型时,该函数调用后返回的不单是值,而是包含返回值的变量(或对象)。由于返回引用与引用类型有关,所以这样的实例将在下节介绍。,19,函数应用实例,1.实例1-无须给出函数,f,的原型,设,f(x)=(x*x+x+1)/2-5.5。,(1),求,z=(f(2.5)+2*f(6)/f(4.3),,并显示结果,z。,(2),对任意输入的一个实数,a,,求出,f(a),并显示。,程序执行后的交互结果如下:,z=4.90618,Input a=,10,f(a)=50,20,程序编制:,#,include,double f(double x),double y;,y=(x*x+x+1)/2-5.5;,return(y);,/,对非,void,类型的函数,必须有一个,/return,语句,由它返回函数值,21,void main(),double z,a;,z=(f(2.5)+2*f(6)/f(4.3);/,调用自定义函数,f,coutz=zendl;,couta;,coutf(a)=f(a)endl;/,算出,f(a),并输出,22,点评:,1),main,中共出现了4次对自定义函数,f,的调用。,2),编写,f,函数时的3点注意:,(1),f,函数体内的3行也可用如下的一行来代替,return(x*x+x+1)/2-5.5);,return,句括号内表达式的值,即为整个函数的返回值。,(2),return,句也可使用另一格式,即可以不括起表达式:,return(x*x+x+1)/2-5.5;/OK!,23,(3),三种不正确的用法,f=(x*x+x+1)/2-5.5;,不可给函数名,f,赋值。,return(f);,返回值类型应该是,double,,而非指针类型(函数名相当于一个指针)。,f(x)=(x*x+x+1)/2-5.5;,赋值号左端非变量(也即,f(x),非左值)。,24,3),关于,return,语句,return,语句(称为返回语句)具有如下三种使用格式:,return;,return;,return();,第一种,格式的,return,用于立即从被调函数中返回,当函数,类型为,void,时,应使用这种格式的返回语句。,当函数,类型为非,void,型时,应使用,第二或第三种,格式的,return,语句,此两种格式的语句效果完全相同(可将第二种格式看成是第三种格式的省略形式),系统此时都将计算出表达式的值,并“携带”该值立即从被调函数中返回。,25,2.,实例2-,main,在前而被调函数,f,在后时,必须先列出函数,f,的原型,设,f(x)=(x*x+x+1)/2-5.5。,(1),求,z=(f(2.5)+2*f(6)/f(4.3),,并显示结果,z。,(2),对任意输入的一个实数,a,,求出,f(a),并显示。,程序执行后的交互结果如下:,z=4.90618,Input a=,10,f(a)=50,26,程序编制:,#,include,double f(double x);/,函数,f,的原型,void main(),/,同上,从略,double f(double x)/,被调函数,f,的具体定义,/同上,从略,27,3.实例3-无参函数,实现固定功能,编无参函数,,void printStar(),,,它负责,完成固定功能,:在同一行连续显示60个“*”。并编制主函数,main,,对该函数实现调用,使程序执行后的显示结果如下:,the result of first call to printStar():,*,the result of second call to printStar():,*,28,程序编制:,#,include,void printStar()/,自定义无参函数,printStar,for(int i=1;i=60;i+)/,显示60个“*”,cout*;,coutendl;,return;,29,void main(),coutthe result of first call to printStar():endl;,printStar();/,对,printStar,的第一次调用,coutthe result of second call to printStar():endl;,printStar();/,对,printStar,的第二次调用,30,点评:,(1),将,printStar,定为,void,类型,,是因为,不需要它返回任何值,。,(2),按语句方式调用,printStar,(,而不可作为表达式因子,如3+,printStar()/2,等)。,(3),printStar,函数中的,return,用于立即从被调函数中返回到主调函数处(跳转到调用语句的下一语句处)去执行,当函数类型为,void,时,应使用这种无返回值的,return,语句。实际上,由于本函数是,在执行完函数体内的所有语句后才返回,,此种情况下的,return,语句可以缺省,。,31,4.实例4-一参函数,编一参函数,,void printStar(int k),,,它负责,显示出,k,行“*”,来,其中每行均显示连续的8个“*”。并编制主函数,main,,对该函数实现调用,使程序执行后的显示结果样式如下:,k=?,3,*,*,*,32,程序编制:,#,include,void printStar(int k),/k,为形参,由调用处的实参提供实际值,for(int i=1;i=k;i+),/,显示出,k,行,cout*endl;,return;,/,该,return,语句可以缺省,void main(),int k;,/,欲显示的行数,k,coutk;,printStar(k);,/,函数调用,带去输入的实参,k,33,5.,实例5-二参函数,编二参函数,,void printStar(int k,int n),,,它负责,显示出,k,行“*”,来,且,每行,均,显示,连续的,n,个“*”,。并编制主函数,对该函数实现调用,使程序执行后的显示结果样式如下:,k,n=?,4 15,*,*,*,*,34,程序编制:,#,include,void printStar(int k,int n),/,负责显示出,k,行*来,且每行均显示连续的,n,个*,for(int i=1;i=k;i+)/,循环,k,次,显示出,k,行“*”,for(int j=1;j=n;j+)/,循环,n,次,显示出1行的,n,个“*”,cout*;,coutendl;,/,注意,函数末右花括号前缺省了一个,return,语句,35,void main(),int k,n;/,显示出,k,行,每行显示,n,个“*”,coutkn;,printStar(k,n);,/,以输入的,k,与,n,为实参去调用,printStar,36,6.,实例6-无参函数,全局变量,编无参函数,,void printStar(),,,并,结合使用全局变量,k,与,n,,,使每调用一次该函数,总,显示出,k,行“*”,来,且,每行,均,显示,连续的,n,个“*”,,并编制主函数,对该函数实现调用,使程序执行后的显示结果样式如下:,k,n=?,3 20,*,*,*,37,程序编制:,#,include,int k,n;/int,型,全局变量,k,n,/*,在,所有函数之外,(即在不属于任一函数定义的外面),说明的变量为,全局变量,,其作用域即有效区域为整个文件(或具有多个文件的整个程序,详细请参看本章的5.7及5.8节)。,*/,38,void printStar()/,无参函数,printStar,/其中所用的,k,与,n,都是全局变量,for(int i=1;i=k;i+),/显示出,k,行“*”,for(int j=1;j=n;j+),/显示,n,个“*”,cout*;,coutendl;,39,void main(),coutkn;/,输入全局变量,k,,全局变量,n,的值,coutthe result of call to printStar():endl;,printStar();/,调用无参函数,printStar,40,5.,2.4,函数的参数,C+,语言允许函数无参,有一个或多个参数,而且还支持不定个数参数的函数。,无参函数:用,void,或空表示无参。,一个或多个参数:多数函数有一个或多个确定的个数、确定的类型和顺序的参数。,在,C+,语言中,不同的函数是根据函数名和参数表二者来区分的,只有二者完全一致才是同一函数。,(,3,)不定个数参数:有些应用问题中参数个数是变化的。,例如设计一个电话计费函数,为了计算通话费,不同的通话类型(如市话,长途,数据与通讯,,BP,机等)有不同数目的参数。,处理参数个数不定的情形,可有不同的途径:,例如指针,,C+,提供的某些库函数,无名参数,可缺省参数等。,41,无名参数,-,C+,语言,允许参数表中包含无名参数,主要是为了区分函数,,例如:,int f,(,int a,,,int b,),return a,b*b,;,int f,(,int a,,,int b,,,int,),return a*a,b,;,两个不同的函数同名,但由于第二个函数包含一无名参数,使得在调用时能够被区分,,f,(,x,,,y,)是第一个函数的调用,,f,(,x,,,y,,,0,)是第二个函数的调用。,42,可缺省参数(参数默认值),-,允许在,函数定义处,为其中,最后面的连续若干个参数,设置,默认值,(也称,缺省值,),从而为调用处提供了方便(若调用处缺省了某个或某些实参的情况下,系统将自动使用那些在函数定义处给定的参数默认值)。,下面是一个示例性程序,在定义函数,func,时为它的最后面的连续3个参数(也即它的所有参数)都设置了默认值。若,调用处缺省,了某个或某些,实参,的情况下,系统将,自动使用,这些给定的参数,默认值,。,43,#include,void func(int a,=11,int b,=22,int c,=33,),/为参数,a、b、c,设置了默认值11、,22,与33,couta=a,b=b,c=cendl;,44,void main(),func();,/,调用时,缺省了3个实参,,将使用,/定义处给定的那3个相对应的参数默认值,func(55);,/,调用时,缺省了后2个实参,,将使用,/定义处给定的那后2个对应参数默认值,func(77,99);,/,调用时,缺省了最后1个实参,,将使用,/定义处给定的那最后1个参数默认值,func(8,88,888);,/,调用时,没缺省任一个实参,,系统将,/不使用定义处给定的任一个参数默认值,45,程序执行后的显示结果如下:,a=11,b=22,c=33,a=55,b=22,c=33,a=77,b=99,c=33,a=8,b=88,c=888,46,注意,只能为函数,最后面的连续若干个,参数设置默认值,且在,调用处,也只能缺省,后面的连续若干个,实参。,void func(int a,int b=2,int c=3);/OK!,void func(int a=1,int b,int c=3);/ERROR!,对第一个函数说明,采用如下的调用语句:,func(1,22,333);/OK!,调用时给出所有实参,func();/ERROR!,参数,a,没有默认值,func(10,20);/OK!,参数,c,默认为3,func(5,9);,/ERROR!,调用处也只能缺省后面的连续若干个实参,47,5.,2.5,值调用和引用调用,C+,语言在进行函数调用时,对参数的处理有两种方式,赋值型和引用型,即值调用方式和引用调用方式。前者是普通的形式,在,C,语言中只有这种方式;,C+,语言中增加了引用调用形式,这种形式与,pascal,语言中的变量参数调用方式相似。,1 赋值调用,2 引用调用,48,赋值调用,-,赋值形参:在函数定义的参数中,除了被说明为引用(,&,)的参数之外,其余所有类型的形参都属于赋值形参。,过程:,在执行函数调用时,在检查函数名及参数表之后,首先为值参数分配内存,然后计算各对应的实参表达式,并把计算的值赋给刚刚创建的参数变量,进而开始函数体的运行。,凡是赋值形参,在函数的每次调用时,,都必须为每一个赋值形参创建一个新的参数变量。,实参表达式:另一方面,函数调用语句中,与赋值形参相对应的实参可以是指定类型的常量、变量或表达式。在执行函数调用时应把该表达式的值计算出来,作为初值赋给刚刚为赋值形参创建的参数变量。这是赋值调用方式名称的由来。,为赋值形参创建的参数变量是局限于函数体运行的局部变量,它作为该形参的一个实例,参加函数体程序块的这次运行,,一旦运行完毕,这个参数变量就被撤消,。,49,引用调用,函数定义的参数表中,名字前加上符号的参数为引用形参。,例如,void swap,(,int,a,,,int,b,);,引用形参在调用过程中的参数传递机制不同于赋值形参。其要点是:,(,1,)函数的调用语句中对应于引用形参的实参必须是同一类型的变量,非变量的表达式则不允许。,(,2,)参数传递的内容不是实参的值,而是地址,其实际的效果是令对应的引用形参在调用过程中,作为一个变量名指向作为实参的这个变量,与赋值形参的不同在这里体现出来,在引用调用过程中并不创建新的参数变量!,(,3,)在函数体程序块的运行中,引用形参的每次出现,由于它现在已经是指向实参变量,因此相当于全用实参变量所代替。即起到了所谓的“换名”的作用。,(,4,)在函数体程序运行结束,控制转回调用点时,该引用形参与实参变量的对应关系也就终止了。但是在调用过程中对于这个实参变量的所有处理和操作的结果,却保留下来。这一点也是区别于赋值调用的。,50,引用调用,设计函数在下面两种情形时,,需要改变某些变量的值(上述函数,swap,就是一例);,对于占内存较多的数据参数,为了不另建新的参数变量以节省内存,建议采用引用参数。,在后一种情况,为了保证实参不在函数中被修改,可在形参说明中加上,const,说明,例如:,complex add,(,const complex,a,,,const complex,b,);,而对于赋值形参,则无此必要。,51,实例,void printStar(int k,int n);,/,它所用的两个参数均为,赋值参数,。,void s,/,它所用的两个参数均为,引用参数,。,int myFunc(int a,float,/,它所用的第一个参数为赋值参数,/另一个为引用参数。,语法要求:调用函数时,,引用参数对应的实参必须是指定类型的变量,。,52,对函数进行调用的执行过程(步骤),:,(1),将对应,实参表达式,的,值,赋给,赋值,形参,(若参数为赋值参数的话);,(2),用,实参变量,替换,相应的,形参,(若参数为引用参数的话);,(3),按各形参的“当前值”(或已被,“赋值”,,或已被,“换名”,)去,执行,一遍,函数体,并,返回,调用处。,53,注意:,(1),通过,赋值参数,来传值的方式是一种,“单向传值”,方式,它只可向被调函数的形参“传入”值,而不可通过该形参“传出”值。,(2),通过,引用参数,来传值的方式是一种,“双向传值”,方式,它不仅可向被调函数的形参“传入”值,而且还可通过该形参“传出”值。,54,实例,通过下面的小例子来理解赋值参数与引用参数的使用区别。,#,include,void f1(int /,函数原型,/前两个为引用参数,可“双向传值”,/后两个为赋值参数,55,void main(),int i=1,j=2,k=77,n=88;,cout-In main,befor calling f1-endl;,couti,j,k,n=i j k nendlendl;,f1(i,j,k,n);,/,注意,调用后实参变量,i、j,的值进行了改变,/而,k,与,n,的值并不改变,cout-In main,after calling f1-endl;,couti,j,k,n=i j k nendl;,56,void f1(int&a,int&b,int c,int d),/*,前两个为引用参数,后两个为赋值参数。对,引用参数,而言,调用时,将用对应实参变量来替换它们。即是说,,被调函数,中对,形参值,的使用与改变,就是对,主调函数,中调用语句处所对应,实参变量值,的直接使用与改变(“双向传值”),*/,cout-Enter f1-endl;,couta,b,c,d=a b c dendlendl;,int tmp;,tmp=a;a=b;b=tmp;,/,交换,a,与,b,的值,tmp=c;c=d;d=tmp;,/,交换,c,与,d,的值,cout-In the end of f1-endl;,couta,b,c,d=a b c dendlendl;,57,程序执行后的显示结果如下:,-,In main,befor calling f1-,i,j,k,n=1 2 77 88,-Enter f1-,a,b,c,d=1 2 77 88,-In the end of f1-,a,b,c,d=2 1 88 77,-In main,after calling f1-,i,j,k,n=2 1 77 88,58,5.2.6,内联函数 -,可在一般的函数说明前冠以关键字,inline,,称这样的函数为,内联函数,。按如下的方式来说明:,inline (),59,在编译过程中,凡内联函数,,系统均把它的执行代码插入到该函数的每个调用点处(以取代那一函数调用),,从而使程序执行过程中,每次对该函数调用时不需控制转移,可,节省执行时间,;但由于每个调用点处均出现那一函数的执行代码拷贝,相对来说使用内联函数后,会扩大其代码空间,。,60,使用内联函数的简单实例,#,include,inline int max(int x,int y)/,内联函数,max,return(xy?x:y);,61,void main(void),int a,b;,coutab;,coutmax(a,b)=max(a,b)endl;,/,对内联函数,max,的调用,62,程序执行后的显示结果如下:,Input a,b:123 456,max(a,b)=456,使用内联函数时应注意:,1.,内联函数的函数体一般讲不宜过大,以1-5行为宜。,2.,凡在类体中定义的成员函数(见第7章)均隐含为内联函数。,63,第五章前部分结束,64,此课件下载可自行编辑修改,供参考!,感谢您的支持,我们努力做得更好!,
展开阅读全文