36,程序运行结果如图,4-6,所示图,4-6,交换两个变量的值的错误演示,,,程序的目的是要输出,a,和,b,交换后的值,即输出“,a=8,”和“,b=5,”,但实际输出结果却是“,a=5,”和“,b=8,”在,swap,函数中,虽然形参,x,和,y,的值交换了,但由于实参与形参之间是单向传递的,形参值的改变不会影响实参的值,所以实参,a,和,b,还是保持其原有的值不变37,C++,中的参数类型检查通常是针对有参函数而言的在调用函数时,编译器会自动检查实参与形参的类型是否相同或赋值兼容如果二者都不满足,那么编译器就会报告一个错误,编译就无法进行例如:,,void add(int x,int y) //,定义两个整数相加的函数,,{,,return (x+y); //,函数返回两个整数相加后的值,,},,…,,int z; //,定义一个整型变量用以接收,add,函数的返回值,,z=add(2,3); //,传入类型,类型相同(即,2,、,3,和,x,、,y,都是整型),,z=add(5.4,6.7); //double,型转换为,int,型,,5.4,变为,5,赋给,x,,,6.7,变为,6,赋给,y,,,4.3.4,参数类型检查,,,38,一般情况下,形参的值是靠实参通过数据传递(值传递)而获得的。
但是,当多次调用同一函数,而其中又有一个或几个参数,它们传递进来的实参值多次都是相同时,就可以在函数定义或声明时给形参一个默认值默认实参值其实就是形参的默认值指定默认实参的方法类似于初始化一个变量例如:,,void add(int x,int y=6);,,上述,add,函数的声明中,形参,x,是一个普通的参数,而形参,y,则是一个具有默认实参的参数,这个默认实参就是,6,如果在调用,add,函数时没有给形参,y,传递实参值,则形参,y,的值取默认值,6,由于实参与形参的结合顺序是从左至右进行的,因此指定默认值的参数必须放在形参列表中的最右端,否则会出错例如:,,void fun1(int a,int b=2,int c=3); //,正确,,void fun2(int a=1,int b=2,int c); //,不正确,,void fun3(int a=1,int b,int c=3); //,不正确,,,4.3.5,使用默认实参,,,39,既可以在函数声明中指定默认值,也可以在函数定义中指定默认实参通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。
此外,在一个文件中,一个参数只能被指定一次默认实参但可以在多次声明中依次向前指定其他参数的默认值例如,在头文件,method.h,中声明一个名为,f,的函数int f(int x,int y,int z=10);,,在源文件,method.cpp,中定义这个函数include "method.h" //,包含头文件,method.h,,int f(int x,int y,int z=10) /*,错误,在同一文件中形参,z,被重复指定为默认实参,应去掉“,=10”*/,,{,,,…,,},,在源文件,main.cpp,中使用函数,f,include"method.h" //,包含头文件,method.h,,int f(int x,int y=12,int z); //,正确,重新声明函数,f,时,向前指定,y,的默认实参,12,,注意:,如果在函数定义的形参列表中指定默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才有效40,指定了默认实参的函数虽然并不普遍,但在多数情况下仍然是适用的调用有默认参数的函数时,可以省略有默认值的实参对于在定义或声明中带有默认实参的函数,如果在调用时没有给相应的形参传递实参值,则使用默认实参作为该形参的值。
例如:,,void output_message(int num1=10,double num2=1.2345),,//,指定参数,num1,和,num2,的默认实参,,{,,cout <
此时,至于在函数定义中给不给出默认值,要根据具体的编译系统而言,因为不同的编译系统有不同的处理规则2,)一个函数不能既作为重载函数,又作为有默认参数的函数,否则容易出现二义性,使系统无法执行因为当调用函数时如果少写一个参数,系统无法判定是利用重载函数还是利用有默认参数的函数42,#include ,,using namespace std;,,int max(int ,int ,int =0); //,函数声明,并指定了一个默认实参,,int main(),,{,,int a,b,c;,,cout <<"please input three integers:" <>a >>b >>c;,,cout <<"max(a,b)=" <
43,程序运行结果如图,4-7,所示图,4-7,带有默认参数的函数实现,,,,44,1,)函数的嵌套调用,,C++,中的所有函数都是平行的,即在定义函数时是互相独立的一个函数定义内部包含另一个函数定义,称为嵌套定义C++,规定不能嵌套定义函数,但可以嵌套调用函数所谓嵌套调用,是指在调用一个函数的过程中又调用另一个函数,其示意图如图,4-8,所示图,4-8,嵌套调用,,,4.3.7,函数的嵌套调用和递归调用,45,(,1,)执行,main,函数的开头部分2,)遇到调用,fun1,函数的语句,转去执行,fun1,函数3,)执行,fun1,函数的开头部分4,)遇到调用,fun2,函数的语句,转去执行,fun2,函数5,)执行,fun2,的函数体,如果再无其他嵌套的函数,则完成,fun2,函数的全部操作6,)返回调用,fun2,函数处,即返回,fun1,函数7,)继续执行,fun1,函数尚未执行的部分,直到,fun1,函数结束8,)返回,main,函数中原来调用,fun1,函数处9,)继续执行,main,函数的剩余部分直到结束嵌套调用在,C++,编程中是经常使用的需要注意的是,在程序中实现函数嵌套调用时,如果被调用函数是调用在前,定义在后,那么就需要对每一个被调用的函数作声明。
下面举例说明函数的嵌套调用图,4-7,表示的是两层嵌套(连同,main,函数共,3,层函数)的执行过程示意图,具体过程描述如下46,【,例,4-7】,编写程序,求两个数,a,,,b,的最小公倍数和最大公约数利用欧几里得算法(又称辗转相除法)实现的过程如下1,)求,a,除以,b,的余数,r,2,)如果余数,r,为,0,,则,b,是最大公约数,算法结束;否则执行下一步3,)将除数作为新的被除数,余数作为新的除数,即执行“,a=b;b=r;,”,然后转到步骤(,2,)47,具体程序如下include ,,using namespace std;,,int gcd(int a,int b); //,求最大公约数函数原型声明,,int lcm(int a,int b); //,求最小公倍数函数原型声明,,void main(),,{,,int a,b;,,cout <<"please enter two integers:" <>a >>b;,,cout <<"the greatest common divisor is:"<
图,4-9,函数的嵌套调用实例,1,,,49,【,例,4-8】,用弦截法求方程,f,(,x,),=,x,3,-8,x,2,+12,x,+16=0,的根结合数学知识,其解决方法如下1,)确定求值区间输入两个不同的点,x,1,,,x,2,,直到,f,(,x,1,)和,f,(,x,2,)异号为止因为一旦,f,(,x,1,)和,f,(,x,2,)异号,则(,x,1,,,x,2,)区间内必有一根但应注意,x,1,,,x,2,的值不要相差太大,以保(,x,1,,,x,2,)区间内只有一根2,)连接(,x,1,,,f,(,x,1,))和(,x,2,,,f,(,x,2,)),,两点,此线(即弦)交,x,轴于,x,,如图,4-10,所示其中,,x,的坐标可由下式求出x=,,,再从,x,求出,f,(,x,)图,4-10,弦截法示意图,,,50,(,3,)若,f,(,x,)与,f,(,x,1,)同号,则根必在(,x,1,,,x,2,)区间内,此时将,x,作为新的,x,1,如果,f,(,x,)与,f,(,x,2,)同号,则表示根在(,x,1,,,x,2,)区间内,将,x,作为新的,x,2,重复步骤(,2,)和步骤(,3,),直到,,为止。
此时认为,f,(,x,),,其中的,,为一个很小的正数,例如,10,-6,),,这就是弦截法的算法,在程序中分别用以下几个函数来实现各部分的功能:,,(,1,)用,f,(,x,)代表,x,的函数:,x,3,-8,x,2,+12,x,+16,2,)用函数,xpoint,(,x,1,,,x,2,)来求(,x,1,,,f,(,x,1,))和(,x,2,,,f,(,x,2,))两点的连线与,x,轴的交点,x,的坐标3,)用函数,root,(,x,1,,,x,2,)来求(,x,1,,,x,2,)区间的实根显然,执行,root,函数的过程中要用到函数,xpoint,,而执行,xpoint,函数的过程中要用到,f,函数51,根据以上算法,可编写出如下程序include ,,#include ,,#include ,,using namespace std;,,double f(double);,,double xpoint(double,double);,,double root(double,double);,,void main(),,{,,double x1,x2,x,f1,f2;,,do //,输入两个不同点,x1,、,x2,,直到,f,(,x1,)和,f,(,x2,)异号为止,,{,,cout <<"please input x1,x2:" <>x1 >>x2;,,f1=f(x1);,,f2=f(x2);,,},,while( (f1*f2) >0);,,x=root(x1,x2);,,cout <0),,{,,y1=y;,,x1=x;,,},,else,,x2=x;,,},,while(fabs(y)>=1e-6);,,return x;,,},,53,程序运行结果如图,4-11,所示。
图,4-11,用弦截法求方程根的结果,,,注意:,main,函数可以调用其他函数,各函数间也可以互相调用,但不能调用,main,函数54,2,)函数的递归调用,,直接或间接调用自己的函数称为递归函数直接调用自身的函数称为直接递归调用,如在执行函数,fun1,的过程中,又要调用,fun1,函数,如图,4-12,所示间接调用自身的函数称为间接递归调用,如在执行函数,fun1,的过程中要调用函数,,,,,,,,,图,4-12,函数的直接递归调用,,图,4-13,函数的间接递归调用,,,fun1(),,{,,,……,,,fun1();,,,……,,},,fun1() fun2(),,{ {,,…… ……,,fun2(); fun1(),,…… ……,,} },,55,下面看一个简单的递归调用例子,——,用递归方法求,n,!的值我们知道,5,!,=5*4,!,也就是说,想要求,5,!的值必须知道,4,!的值,而,4,!,=4*3,!,同理,要求,4,!的值又必须知道,3,!的值,而,3,!,=3*2,!,,……,以此类推,只要知道了,1,!(值为,1,),反推回去就求得了,5,!。
这就是递归思想用流程图表示该过程如图,4-14,所示图,4-14,递归调用求,5,!的值示意图,,,56,由图,4-14,可知,求解过程分为两个阶段:第,1,阶段(即①,~,④)是回推,即将,5,!表示为,4,!的函数,而,4,!仍然不知道,还要回推到,3,!,……,直到,1,!此时,1,!已知,不必再向前推了然后开始第,2,阶段(⑤,~,⑧),采用递推方法,从已知的,1,!推算出,2,!(值为,2,),从,2,!再推算出,3,!(值为,6,),……,一直推算出,5,!(值为,120,)为止显然,一个递归问题可以分为回推和递推两个阶段注意:,如果要求递归过程不是无限制地进行下去,那么就必须具有一个结束递归过程的条件通常使用,if,语句来控制有了上面的基础,就不难写出求,n,!的程序代码了例,4-8】,用递归方法求,n,!其中:,,,,非法,,n<0,,n!= 1 n=0,或,n=1,,n*(n-1)! n>1,,,,,57,编写程序如下include ,,using namespace std;,,long factorial(int); //,函数声明,形参为一个整型变量,,int main(),,{,,int n; //n,为需要求阶乘的整数,,long y;,,cout <<"please input an integer:" <>n;,,y=factorial(n); //,调用递归函数,factorial,,将返回值赋给,y,,cout <1,时,进行递归调用,,return fac; //,将,fac,的值作为函数返回值,,},,58,程序运行结果如图,4-15,所示。
图,4-15 【,例,4-8】,程序运行结果,,,59,另一个递归调用的例子是求两个数的最大公约数例,4-9】,用递归函数求两个数的最大公约数结合,【,例,4-6】,中介绍的欧几里得算法,求两个数的最大公约数的过程可以递归地描述为如下的式子b a%b=0,,gcd(a,b)=,,gcd(b,a%b) a%b,≠,0,,,,60,编写程序如下include ,,using namespace std;,,int gcd(int a,int b); //,递归函数,gcd,原型声明,,void main(),,{,,int a,b,g;,,cout <<"please enter two integers:" <>a >>b;,,g=gcd(a,b); //,调用递归函数并将返回值赋给变量,g,,cout <<"the greatest common divisor is:" <
图,4-16,用递归函数求两个数的最大公约数的结果,,由以上两个例题可以看出,当使用递归方法解决问题时,需要满足如下两个条件1,)应能减小问题的规模2,)应能确定终结条件还需说明的是,在实现递归时,需要大量的额外开销,执行效率较低但随着计算机性能的快速提升,大家首先考虑的往往不再是效率问题,而是程序的可读性问题用递归方法来处理问题,符合人们的思路,程序容易理解因此,建议读者优先考虑用递归方法编程62,通常的函数调用是将程序执行顺序转到被调用函数去执行,待被调用函数执行完毕后,再返回调用函数继续执行,见图,4-7,这种转移操作会消耗一定的时间,尤其是当某些函数体代码不是很大,而又需要频繁调用时,就大大降低了程序的执行效率为了解决这个问题,,C++,引入了内联函数C++,规定,如果在函数的定义或声明前加上关键字,inline,,就称为内联函数内联函数的执行机理,是在编译时将所调用函数的函数体代码直接嵌入主调函数中例如,对于,add,函数,如果其定义为:,,int add(int x,int y),,{,,return(x+y);,,},,在,main,函数中声明为:,,inline int add(int x,int y); //,在函数声明前加上关键字,inline,,,add,函数成为内联函数,,函数调用为:,,int c=add(3,5);,,则程序编译后,实际的代码如下。
int c=3+5;,,,,即在编译后用,add,函数体的代码(“,x+y,”的返回值“,return(x+y);,”)代替“,add(3,5),”,同时用实参代替形参这样,代码“,int c=add(3,5);,”就被置换成“,int c=3 + 5;,”注意:,可以在声明函数和定义函数的同时写,inline,,也可以只在其中一处声明,inline,,效果相同,都能按,内联函数处理4.3.8,内联函数,63,#include ,,using namespace std;,,inline int min(int ,int ,int); //,声明,min,为内联函数,注意左端有关键字,inline,,int main(),,{,,int a,b,c,m;,,cout <<"please input three integers:" <