函数是C程序的基本模块可将一些功能相对独立的或经常课件_第1页
函数是C程序的基本模块可将一些功能相对独立的或经常课件_第2页
函数是C程序的基本模块可将一些功能相对独立的或经常课件_第3页
函数是C程序的基本模块可将一些功能相对独立的或经常课件_第4页
函数是C程序的基本模块可将一些功能相对独立的或经常课件_第5页
已阅读5页,还剩87页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

函数是C++程序的基本模块。可将一些功能相对独立的或经常使用的操作或运算抽象出来,定义为函数。使用时只要考虑其功能和使用接口即可。在面向对象的程序设计中,类中所封装的操作是用函数进行描述的,因此函数在C++程序中具有非常重要的意义。要掌握函数的使用,必须理解函数调用时的内部实现机制,以及与此相关的内存分配机制、变量生命期和作用域。本章还将介绍关于函数重载的概念,介绍递归算法、内联函数、默认参数函数以及多文件组织、编译预处理、工程文件的概念和运行库函数。第四章函数

第四章函数

4.1函数的定义与调用

4.5作用域与存储类型

4.4函数调用机制

4.3全局变量和局部变量

4.2函数的参数传递,返回值及函数原型说明

4.10编译预处理4.9头文件与多文件结构4.8C++的系统库函数4.7函数的一些高级议题

4.6函数的递归调用

4.1

函数的定义与调用4.1.1

函数概述4.1.2

函数的定义4.1.3

函数的调用

4.1.1函数概述main()fun2()fun1()fun3()fun1_1()fun2_1()fun2_2()图4.1函数调用层次关系4.1.1函数概述函数按是否带有参数,分为:

无参函数和有参函数4.1.1结束

函数按其是否系统预定义分为两类,一类是编译系统预定义的,称为库函数或标准函数,如一些常用的数学计算函数、字符串处理函数、图形处理函数、标准输入输出函数等。这些库函数都按功能分类,集中说明在不同的头文件中。用户只需在自己的程序中包含某个头文件,就可直接使用该文件中定义的函数。另一类是用户自定义函数,用户可以根据需要将某个具有相对独立功能的程序定义为函数。4.1.2函数的定义1.无参函数2.有参函数有参函数有参函数的定义格式为《数据类型》函数名(参数类型1形式参数1《,参数类型2形式参数2,…》{函数体}例:下面函数的功能是返回两个整数中较大一个的值max(inta,intb){return(a>=b?a:b);}定义函数时可能会涉及若干个变量,究竟哪些变量应当作为函数的参数?哪些应当定义在函数体内?这有一个原则:作为一个相对独立的模块,函数在使用时完全可以被看成“黑匣子”,除了输入输出外,其他部分可不必关心。从函数的定义看出,函数头正是用来反映函数的功能和使用接口,它所定义的是“做什么”,在这部分必须明确“黑匣子”的输入输出部分,输出就是函数的返回值,输入就是参数。因此,只有那些功能上起自变量作用的变量才必须作为参数定义在参数表中;函数体中具体描述“如何做”,因此除参数之外的为实现算法所需用的变量应当定义在函数体内。C++中不允许函数的嵌套定义,即在一个函数中定义另一个函数。提示4.1.3

函数的调用在C++中,除了主函数外,其他任何函数都不能单独作为程序运行。任何函数功能的实现都是通过被主函数直接或间接调用进行的。所谓函数调用,就是使程序转去执行函数体。无参函数的调用格式为:函数名()有参函数的调用格式为:函数名(实际参数表)其中实际参数简称实参,用来将实际参数的值传递给形参,因此可以是常量、具有值的变量或表达式。4.2

函数的参数传递、返回值及

函数原型说明

4.2.1函数的参数传递及传值调用

4.2.3函数原型说明4.2.2

函数返回值

函数调用首先要进行参数传递,参数传递的方向是由实参传递给形参。传递过程是,先计算实参表达式的值,再将该值传递给对应的形参变量。一般情况下,实参和形参的个数和排列顺序应一一对应,并且对应参数应类型匹配(赋值兼容),即实参的类型可以转化为形参类型。而对应参数的参数名则不要求相同。某些特殊情况下也允许参数不对应,这将在函数高级议题中讨论。按照参数形式的不同,C++有两种调用方式:传值调用和引用调用。顾名思义,传值调用传递的是实参的值,本章主要介绍传值调用。关于引用调用,将在第五章类与对象中介绍。

4.2.1函数的参数传递及传值调用

4.2.1函数的参数传递及传值调用

调用power(4.6,3)函数power(4.6,3)return97.336

主程序后续语句n=3x=4.6c=‘a’【例4.2】

说明实参和形参对应关系的示例。#include<iostream.h>#include<math.h>floatpower(floatx,intn){//求x的n次幂floatpow=1;while(n--)pow*=x;returnpow;}voidmain(){intn=3;floatx=4.6;charc='a';cout<<"power("<<x<<','<<n<<")="<<power(x,n)<<endl;cout<<"power("<<c<<','<<n<<")="<<power(c,n)<<endl;cout<<"power("<<n<<','<<x<<")="<<power(n,x)<<endl;}

4.2.1函数的参数传递及传值调用

调用power(3,4.6)函数power(3,4.6)return81主程序后续语句n=3x=4.6c=‘a’【例4.2】

说明实参和形参对应关系的示例。#include<iostream.h>#include<math.h>floatpower(floatx,intn){//求x的n次幂floatpow=1;while(n--)pow*=x;returnpow;}voidmain(){intn=3;floatx=4.6;charc='a';cout<<"power("<<x<<','<<n<<")="<<power(x,n)<<endl;cout<<"power("<<c<<','<<n<<")="<<power(c,n)<<endl;cout<<"power("<<n<<','<<x<<")="<<power(n,x)<<endl;}4.2.2函数返回值return语句的一般格式为:

return表达式;函数的计算结果通过该语句传递回主调函数。【例4.3】设计函数,根据三角形的三边长求面积。如果不能构成三角形,给出提示信息。分析:函数为计算三角形面积,一般三角形返回面积值,若不能构成三角形则返回-1。设计一个主函数完成函数测试。根据返回值情况输出相应结果。程序见下页:4.2.2函数返回值#include<iostream.h>#include<math.h>floatTriangleArea(floata,floatb,floatc){if((a+b<=c)||(a+c<=b)||(b+c<=a))return-1;floats;s=(a+b+c)/2;

returnsqrt(s*(s-a)*(s-b)*(s-c));}voidmain(){floata,b,c,area;cout<<"输入三角形三边a,b,c:"<<endl;cin>>a>>b>>c;

area=TriangleArea(a,b,c);if(area==-1)cout<<'('<<a<<','<<b<<','<<c<<')'<<"不能构成三角形!"<<endl;else cout<<"三角形("<<a<<','<<b<<','<<c<<")面积为:"<<area<<endl;}4.2.3

函数原型说明

函数原型是一条以分号结束的语句,实际上就是所定义函数的函数头,形如:《函数返回值类型》函数名(《形参表》)

语法上对程序文件中函数的排列次序是没有固定要求的,只要满足先定义后使用即可。但从结构化程序设计的角度,通常是先调用后定义。使用函数原型,则既符合由粗到精的思维方式,又满足了语法要求。

其中形参表可以逐个列出每个参数的类型和参数名,也可以列出每个形参的类型,参数名可省略,各形参之间以逗号分隔。函数原型和所定义的函数必须在返回值类型、函数名、形参个数和类型及次序等方面完全对应一致,否则将导致编译错误。

下面是一个使用结构化程序设计思想开发的企业管理报表程序的框架。它使用了函数原型说明。#include<iostream.h>voidmenu_print();voidaccount_report();voidengineering_report();voidmarketing_report();voidmain(){intchoice;do{

menu_print(); cin>>choice;}while(choice<=0||choice>=4);switch(choice){case1:account_report();break;case2:engineering_report();break;case3:marketing_report();break;}}voidmenu_print(){cout<<”系统功能:”<<endl;cout<<”1 财务报表”<<endl;cout<<”2 工程报表”<<endl;cout<<”3 市场报表”<<endl;cout<<”选择业务序号:”;}voidaccount_report(){//生成财务报表}voidengineering_report(){//生成工程报表}voidmarketing_report(){//生成市场报表;}voidmain(){ cout<<setw(10)<<'m'<<setw(20)<<"m*m“ <<setw(20)<<"m*m*m"<<endl; for(intm=11;m<1000;m++)

if(palindrome(m)&&palindrome(m*m) &&palindrome(m*m*m))

cout<<setw(10)<<m<<setw(20)<<m*m <<setw(20)<<m*m*<<endl;}boolpalindrome(intn){ intdigit[10]; intm=n,i=0; do{ digit[i]=n%10;n/=10;i++; }while(n>0); for(intj=0;j<i;j++)n=n*10+digit[j]; return(n==m);}4.2.3

函数原型说明mm*mm*m*m111211331101102011030301111123211367631

运行结果:(1)代码区(Codearea):存放程序代码,即程序中各个函数的代码块;(2)全局数据区(Dataarea):存放全局数据和静态数据;分配该区时内存全部清零。(3)栈区(Stackarea):存放局部变量,如函数中的变量等;分配栈区时内存不处理。(4)堆区(Heaparea):存放与指针相关的动态数据。分配堆区时内存不处理。参见第七章。4.3.1变量的存储机制与C++的内存布局

4.3.2全局变量在所有函数之外定义的变量称为全局变量。全局变量在编译时建立在全局数据区,在未给出初始化值时系统自动初始化为全0。全局变量可定义在程序开头,也可定义在中间位置,该全局变量在定义处之后的任何位置都是可以访问的,称为可见的。请看下例:4.3.2全局变量打印200调用func()函数func()200*2=400打印400n=100n=100*2=200【例4.5】多个函数使用全局变量的例子。#include<iostream.h>intn=100;voidfunc(){ n*=2;}voidmain(){ n*=2; cout<<n<<endl; func(); cout<<n<<endl;}4.3.3局部变量

定义在函数内或块内的变量称为局部变量。程序中使用的绝大多数变量都是局部变量。局部变量在程序运行到它所在的块时建立在栈中,该块执行完毕局部变量占有的空间即被释放。局部变量在定义时可加修饰词auto,但通常省略。局部变量在定义时若未初始化,其值为随机数。4.3.3局部变量打印main()中的t=3.5调用fun()函数fun()打印fun()中的t=5

打印main()中的t=3.5t=3.5t=5【例4.9】使用局部变量的例子。#include<iostream.h>voidfun(){autointt=5;//fun()中的局部变量,auto可省略cout<<"fun()中的t="<<t<<endl;}voidmain(){floatt=3.5;//main()函数中的局部变量cout<<"main()中的t="<<t<<endl;fun();cout<<"main()中的t="<<t<<endl;}4.4函数调用机制

局部变量占用的内存是在程序执行过程中“动态”地建立和释放的。这种“动态”是通过栈由系统自动管理进行的。当任何一个函数调用发生时,系统都要作以下工作:(1)建立栈空间;(6)恢复现场:取主调函数运行状态及返回地址,释放栈空间;(7)继续主调函数后续语句。(5)释放被调函数中局部变量占用的栈空间;(4)执行被调函数函数体;(3)为被调函数中的局部变量分配空间,完成参数传递;(2)保护现场:主调函数运行状态和返回地址入栈;4.4函数调用机制voidfun1(int,int);voidfun2(float);voidmain(){intx=1;y=2;fun1(x,y);}voidfun1(inta,intb){floatx=3;fun2(x);}voidfun2(floaty){intx;…}x栈顶栈底y3fun2()fun1()运行状态及返回地址x3b2a1fun1()main()运行状态及返回地址y2x1main()操作系统运行状态及返回地址此图例说明在程序执行过程中怎样通过栈“动态”地建立和释放局部变量占用的内存的

4.5作用域与存储类型4.5.1

作用域4.5.2变量的存储类型4.5.3外部存储类型与静态存储类型

4.5.4生命期与可见性

4.5.1作用域

1块作用域

3文件作用域

2函数原型作用域

作用域指标识符能够被使用的范围。只有在作用域内标识符才可以被访问(称为可见)。本节只讨论局部域和文件域(全局域),其中局部域包括块域和函数原型域。任何标识符作用域的起始点均为标识符说明处。下面分别介绍:参和函数体中定义的局部变量,作用域都在该函数内,也称作函数域。块域块指一对大括号括起来的程序段。块中定义的标识符,作用域在块内。复合语句是一个块。函数也是一个块。复合语句中定义的标识符,作用域仅在该复合语句中。函数中定义的标识符,包括形块域a=3b=535a=3b=5a=5b=3【例4.7】输入两数,将两数按从大到小的顺序保存,并输出结果。结果栈t=3#include<iostream.h>voidmain(){inta,b; //具有函数域

cout<<"输入两整数:"<<endl;

cin>>a>>b;

cout<<“a="<<a<<'\t'<<"b="<<b<<endl;

if(b>=a){ intt;

//具有块域

t=a;

a=b;b=t;//交换a,b的值

}cout<<"a="<<a<<'\t'<<"b="<<b<<endl;}【例4.8】设计函数完成两数交换,用主函数进行测试,看结果如何。#include<iostream.h>voids);voidmain(){inta,b;//a,b作用域为main()cout<<"输入两整数:"<<endl;cin>>a>>b;cout<<"调用前:实参a="<<a<<','<<"b="<<b<<endl;swap(a,b);//传值cout<<"调用后:实参a="<<a<<','<<"b="<<b<<endl;}voidsa,intb){//a,b作用域为swap()cout<<"调用中…"<<endl;cout<<"交换前:形参a=“<<a<<','<<"b="<<b<<endl;intt;t=a;a=b;b=t; //交换swap()中的a,b的值cout<<"交换后:形参a="<<a<<','<<"b="<<b<<endl;}

局部变量具有局部作用域使得程序在不同块中可以使用同名变量。这些同名变量各自在自己的作用域中可见,在其它地方不可见。块作用域由VC++平台运行,结果如下:输入两整数:35调用前:实参a=3,b=5调用中…交换前:形参a=3,b=5交换后:形参a=5,b=3调用后:实参a=3,b=5

交换失败

对于块中嵌套其它块的情况,如果嵌套块中有同名局部变量,服从局部优先原则,即在内层块中屏蔽外层块中的同名变量,换句话说,内层块中局部变量的作用域为内层块;外层块中局部变量的作用域为外层除去包含同名变量的内层块部分。

如果块内定义的局部变量与全局变量同名,块内仍然局部变量优先,但与块作用域不同的是,在块内可以通过域运算符“::”访问同名的全局变量。全局n=100

200300内i=500内j=600内n=500+600=11001100500600100200+300=500500

500200300外部i=200外部j=300【例4.9】

显示同名变量可见性。intn=100;#include<iostream.h>voidmain(){inti=200,j=300;cout<<n<<'\t'<<i<<'\t'<<j<<endl;{ //内部块inti=500,j=600,n;n=i+j;cout<<n<<'\t'<<i<<'\t'<<j<<endl;//输出局部变量ncout<<::n<<endl;//输出全局变量n}n=i+j; //修改全局变量cout<<n<<'\t'<<i<<'\t'<<j<<endl;}函数原型作用域

函数原型不是定义函数,在作函数原型声明时,其中的形参作用域只在原型声明中,即作用域结束于右括号。正是由于形参不能被程序的其他地方引用,所以通常只要声明形参个数和类型,形参名可省略。3文件作用域

文件作用域也称全局作用域。定义在所有函数之外的标识符,具有文件作用域,作用域为从定义处到整个源文件结束。文件中定义的全局变量和函数都具有文件作用域。如果某个文件中说明了具有文件作用域的标识符,该文件又被另一个文件包含,则该标识符的作用域延伸到新的文件中。如cin和cout是在头文件iostream.h中说明的具有文件作用域的标识符,它们的作用域也延伸到嵌入iostream.h的文件中。存储类型决定了变量的生命期,变量生命期指从获得空间到空间释放之间的时期。4.5.2变量的存储类型

存储类型的说明符有四个:auto,register,static和extern。前两者称为自动类型,后两者分别为静态和外部类型。本节重点掌握static和extern这两种类型的使用和区别。具体说,区分局部变量和静态局部变量,全局变量和静态全局变量。auto:前面提到的局部变量都是自动类型。其空间分配于块始,空间释放于块终,且由系统自动进行。自动变量保存在栈中,且是在程序运行过程中获得和释放空间,未初始化时值为随机数。4.5.2变量的存储类型

register:为提高程序运行效率,可以将某些变量保存在寄存器中,即说明为寄存器变量,但不提倡使用。static:静态变量。根据被修饰变量的位置不同,分为局部(内部)静态变量和全局(外部)静态变量。所有静态变量均存放在全局数据区,编译时获得存储空间,未初始化时自动全0,且只初始化一次。局部静态变量的作用域为块域,但生命期为整个文件。即当块结束时,局部静态变量空间仍然保持,直到整个程序文件结束时该局部静态变量空间才释放,生命期结束。局部静态变量

【例4.10】自动变量与局部静态变量的区别。(演示)#include<iostream.h>st(){staticintt=100;//局部静态变量t++;returnt;}at(){intt=100;//自动变量t++;returnt;}voidmain(){inti;for(i=0;i<5;i++)cout<<at()<<'\t';cout<<endl;for(i=0;i<5;i++)cout<<st()<<'\t';cout<<endl;}4.5.2变量的存储类型

i=0t=100123451011011011011014.5.2变量的存储类型

i=0t=10012101345102103104105#include<iostream.h>st(){staticintt=100;//局部静态变量t++;returnt;}at(){intt=100;//自动变量t++;returnt;}voidmain(){inti;for(i=0;i<5;i++)cout<<at()<<'\t';cout<<endl;for(i=0;i<5;i++)cout<<st()<<'\t';cout<<endl;}全局静态变量全局静态变量是指用static修饰的全局变量。有关内容在下节静态存储类型中介绍。4.5.3

外部存储类型与静态存储类型1.

外部存储类型2.

静态存储类型一个C++程序可以由多个源程序文件组成,编译系统将这若干个文件连接在一起,产生可执行程序。外部存储类型和静态存储类型确定了变量和函数在多文件程序中的联络关系。

1外部存储类型

外部存储类型包括外部变量和外部函数。在由多个源程序文件组成的程序中,如果一个文件要使用另一个文件中定义的全局变量或函数,这些源程序文件之间通过外部类型的变量和函数进行沟通。在一个文件中定义的全局变量和函数都缺省为外部的,即其作用域可以延伸到程序的其他文件中。但其他文件如果要使用这个文件中定义的全局变量和函数,必须在使用前用“extern”作外部声明,外部声明通常放在文件的开头。变量定义时编译器为其分配存储空间,而变量声明指明该全局变量已在其他地方说明过,编译系统不再分配存储空间,直接使用变量定义时所分配的空间。函数声明缺省为外部的,因此修饰词extern通常省略。

1外部存储类型

【例4.11】外部存储类型的例子。假定程序包含两个源程序文件Ex4_11_1.cpp和Ex4_11_2.cpp,程序结构如下:/*Ex4_11_1.cpp,由main()组成*/#include<iostream.h>voidfun2();//外部函数声明,等价于externvoidfun2();intn; //全局变量定义voidmain(){n=1;fun2(); //fun2()定义在文件Ex4_11_2.cpp中cout<<″n=″<<n<<endl;}/*Ex4_11_2.cpp,由fun2()组成*/externintn;//外部变量声明,n定义在文件Ex4_11_1.cpp中voidfun2(){//fun2()被文件Ex4_11_1.cpp中的函数调用n=3;}运行结果:n=32静态存储类型

静态存储类型包括静态全局变量和静态函数。在定义全局变量或函数时加说明符static,就成为静态变量或静态函数。静态存储类型的作用域与外部存储类型相反,一旦定义为静态存储类型,就限制该变量或函数只能在定义它的文件中使用。静态全局变量在编译时分配存储空间,如果定义时不指定初值,则编译系统将其初始化为全0。一个全局变量和一个静态全局变量在使用上是不同的,其他文件通过外部变量声明可以使用一个全局变量,但却无法使用静态全局变量,静态全局变量只能被定义它的文件所独享。函数与静态函数之间的区别是相同的。4.5.4

生命期与可见性1.生命期2.可见性

1

生命期

(1)静态生命期

(2)局部生命期

(3)动态生命期

生命期(Lifetime)也叫生存期。生命期与存储区域相关,存储区域分为代码区、静态数据区、栈区和堆区,相应地,生命期分为静态生命期、局部生命期和动态生命期。(1)静态生命期

静态生命期指的是标识符从程序开始运行时存在,即具有存储空间,到程序运行结束时消亡,即释放存储空间。具有静态生命期的标识符存放在静态数据区,属于静态存储类型,如全局变量、静态全局变量、静态局部变量。具有静态生命期的标识符在未被用户初始化的情况下,系统会自动将其初始化为全0。

函数驻留在代码区,也具有静态生命期。所有具有文件作用域的标识符都具有静态生命期。(2)局部生命期

在函数内部或块中定义的标识符具有局部生命期,其生命期开始于执行到该函数或块的标识符声明处,结束于该函数或块的结束处。具有静态生命期的标识符存放在栈区。具有局部生命期的标识符如果未被初始化,其内容是随机的,不可用。具有局部生命期的标识符必定具有局部作用域;但反之不然,静态局部变量具有局部作用域,但却具有静态生命期。(3)动态生命期

具有动态生命期的标识符由特定的函数调用或运算来创建和释放,如调用malloc()或用new运算符为变量分配存储空间时,变量的生命期开始,而调用free()或用delete运算符释放空间或程序结束时,变量生命期结束。具有动态生命期的变量存放在堆区。关于new运算和delete运算将在指针一章中介绍。可见性

可见性从另一个角度说明标识符的有效性,可见性与作用域具有一定的一致性。标识符的作用域包含可见范围,可见范围不会超过作用域。可见性在理解同名标识符的作用域嵌套时十分直观。对于外层块与内层块定义了同名标识符的,在外层作用域中,内层所定义的标识符是不可见的,即外层引用的是外层所定义的标识符;同样,在内层作用域中,外层的标识符将被内层的同名标识符屏蔽,变得不可见,即外层中同名标识符的可见范围为作用域中挖去内层块的范围。图4.6显示下面程序段中变量的作用域与可见性。可见性

下面的程序段和图示显示作用域与可见性。

intm=1;floatx;{floatm=3.5;X=5.5;}m++;

intm,floatx作用域intm可见floatm不可见x可见floatm作用域floatm可见intm不可见x可见4.6

函数的递归调用

递归是一种描述问题的方法,或称算法。递归的思想可以简单地描述为“自己调用自己”。例如用如下方法定义阶乘:可以看出是用阶乘定义阶乘,这种自己定义自己的方法称为递归定义。在函数调用中,有这样两种情况,一种是在函数A的定义中有调用函数A的语句,即自己调用自己;另一种是函数A的定义中出现调用函数B的语句,而函数B的定义中也出现调用函数A的语句,即相互调用。前者称直接递归,后者称间接递归。本节只介绍直接递归。递归函数必须定义递归终止条件(Stoppingcondition),避免无穷递归(InfiniteRecursion)。递归定义的阶乘算法用函数描述为:fac(intn){if(n==0||n==1)return1;elsereturnn*fac(n-1);}只要设计主函数调用阶乘函数,即可实现计算阶乘。4.6

函数的递归调用【例4.12】求4!#include<iostream.h>intfac(intn){ inty;cout<<n<<'\t'; if(n==0||n==1)y=1;elsey=n*fac(n-1); cout<<y<<'\t'; returny;}voidmain(){ cout<<"\n4!="<<fac(4)<<endl;}n=4cout<<4;y=4*fac(3);fac(4)=cout<<2;y=2*fac(1);

n=2cout<<1;y=1;cout<<1;return1;

n=1n=3cout<<3;y=3*fac(2);cout<<24;return24;cout<<6;return6;cout<<2;return2;24递归函数的执行分为“递推”和“回归”两个过程,这两个过程由递归终止条件控制,即逐层递推,直至递归终止条件,然后逐层回归。每次调用发生时都首先判断递归终止条件。递归调用同普通的函数调用一样,每当调用发生时,在栈中分配单元保存返回地址以及参数和局部变量;而与普通的函数调用不同的是,由于递推的过程是一个逐层调用的过程,因此存在一个逐层连续的参数入栈过程,直至遇到递归终止条件时,才开始回归,这时才逐层释放栈空间,返回到上一层,直至最后返回到主调函数。4.6函数的递归调用4.6

函数的递归调用【例4.13】

汉诺塔问题。有A、B、C三根柱子,A柱上有n个大小不等的盘子,大盘在下,小盘在上。要求将所有盘子由A柱搬动到C柱上,每次只能搬动一个盘子,搬动过程中可以借助任何一根柱子,但必须满足大盘在下,小盘在上。打印出搬动的步骤。

A柱B柱C柱分析:1A柱只有一个盘子的情况:A柱C柱;2A柱有两个盘子的情况:小盘A柱B柱,大盘A柱C柱,小盘B柱C柱。3A柱有n个盘子的情况:将此问题看成上面n-1个盘子和最下面第n个盘子的情况。n-1个盘子A柱B柱,第n个盘子A柱C柱,n-1个盘子B柱C柱。问题转化成搬动n-1个盘子的问题,同样,将n-1个盘子看成上面n-2个盘子和下面第n-1个盘子的情况,进一步转化为搬动n-2个盘子的问题,……,类推下去,一直到最后成为搬动一个盘子的问题。这是一个典型的递归问题,递归结束于只搬动一个盘子。4.6函数的递归调用4.6

函数的递归调用算法可以描述为:1n-1个盘子A柱B柱,借助于C柱;2第n个盘子A柱C柱;3n-1个盘子B柱C柱,借助于A柱;其中步骤1和步骤3继续递归下去,直至搬动一个盘子为止。由此,可以定义两个函数,一个是递归函数,命名为hanoi(intn,charsource,chartemp,chartarget),实现将n个盘子从源柱source借助中间柱temp搬到目标柱target;另一个命名为move(charsource,chartarget),用来输出搬动一个盘子的提示信息。程序如下:#include<iostream.h>voidmove(charsource,chartarget){cout<<source<<"->"<<target<<endl;}voidhanoi(intn,charsource,chartemp,chartarget){if(n==1)move(source,target);else{

hanoi(n-1,source,target,temp);//将n-1个盘子搬到中间柱 move(source,target); //将最后一个盘子搬到目标柱

hanoi(n-1,temp,source,target);//将n-1个盘子搬到目标柱}}voidmain(){ intn; cout<<"输入盘子数:"<<endl; cin>>n; hanoi(n,'A','B','C');}【例4.14】

输入一个整数,用递归算法将整数倒序输出。分析:在递归过程的递推步骤中用求余运算将整数的各个位分离,并打印出来。#include<iostream.h>voidbackward(intn){ cout<<n%10; if(n<10)return; elsebackward(n/10);}voidmain(){ intn;

cout<<"输入整数:"<<endl; cin>>n; cout<<"原整数:"<<n<<endl<<"反向数:"; backward(n); cout<<endl;}4.6

函数的递归调用n=247cout<<7;backward(24);n=2cout<<2;return;n=24cout<<4;backward(2);backward(247)

return;

return;

cout<<endl;

求余总是取当前整数的最右一位,所以先输出余数后递归可实现倒序输出。如果先递归后输出余数,则是在回归的过程中输出,实现的就是正序输出。从以上几例可以看出,递归算法一般不需要借助循环,但通过不断递推和回归的过程实现了其他算法用循环完成的功能。因此,递归的终止条件非常重要,否则将会无休止地递归下去,陷入死循环状态。【例4.15】在【例3.11】中采用递推法求解Fibonacii数列,本例用递归法求解。本例的递归调用过程参见图4.11。#include<iostream.h>#include<iomanip.h>intfib(intn){ if(n==0)return0; elseif(n==1)return1; elsereturnfib(n-1)+fib(n-2);}voidmain(){ for(inti=0;i<=19;i++){

//将19改为69,可以看出计算到后面越来越缓慢。 if(i%5==0)cout<<endl; cout<<setw(6)<<fib(i); } cout<<endl;}4.6

函数的递归调用图4.11递归求解斐波那契数列调用树同其他算法相比,用递归算法编制的程序非常简洁易读,但缺点是增加了内存的开销,在递推的过程中会占用大量栈空间,且连续的调用返回操作占用较多CPU时间。因此是否选择使用递归算法取决于所解决的问题及应用的场合。4.6

函数的递归调用4.7函数的一些高级议题4.7.1函数重载

4.7.2缺省变元

4.7.3内联函数

4.7.1函数重载在C++中,如果需要定义几个功能相似,而参数类型不同的函数,那么这样的几个函数可以使用相同的函数名,这就是函数重载。例如求和函数,对应不同的参数类型,可以定义如下几个重载函数:sum(inta,intb)//不写返回类型,返回整型doublesum(doublea,doubleb)floatsum(floata,floatb,floatc)当某个函数中调用到重载函数时,编译器会根据实参的类型去对应地调用相应的函数。匹配过程按如下步骤进行:(1)如果有严格匹配的函数,就调用该函数;(2)参数内部转换后如果匹配,调用该函数;(3)通过用户定义的转换寻求匹配。因此在定义重载函数时必须保证参数类型不同,仅仅返回值类型不同是不行的。函数重载的好处在于,可以用相同的函数名来定义一组功能相同或类似的函数,程序的可读性增强。4.7.1函数重载3+5=调用sum(3,5)函数sum(3,5)return82.2+5.6=调用sum(2.2,5.6)函数doublesum(2.2,5.6)return7.83.5+4+8=调用sum(3.5,4,8)函数floatsum(3.5,4,8)return15.5结束87.815.5【例4.16】

重载函数的应用。#include<iostream.h>sum(inta,intb){ returna+b;}Doublesum(doublea,doubleb){ returna+b;}floatsum(floata,floatb,floatc){ returna+b+c;}voidmain(){ cout<<"3+5="<<sum(3,5) <<endl; cout<<"2.2+5.6=“ <<sum(2.2,5.6)<<endl; cout<<"3.5+4+8=“ <<sum(3.5,4,8)<<endl;}

一般情况下,函数调用时的实参个数应与形参相同,但为了更方便地使用函数,C++也允许定义具有缺省参数的函数,这种函数调用时实参个数可以与形参不相同。

缺省参数指在定义函数时为形参指定缺省值(默认值)。这样的函数在调用时,对于缺省参数,可以给出实参值,也可以不给出参数值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按缺省值进行调用。缺省参数的函数调用:缺省参数的值或表达方式在编译时确定,即其值通过表达式给出,但表达式必须有意义;缺省值还可以是全局常量、全局变量,甚至可以通过函数调用给出;但不能通过局部变量给出,因为局部变量的值在执行时才有意义。4.7.2缺省变元【例4.20】缺省变元。//文件名:Ex4_20.cpp#include<iostream.h>voiddelay(intloops=5){//延时函数,默认延时5个时间单位 for(;loops>0;loops--);}voidmain(){ delay(3); cout<<“延时3个时间单位"<<endl; delay(); //等同于delay(5) cout<<“延时5个时间单位"<<endl;}4.7.2缺省变元123延时3个时间单位12345延时5个时间单位缺省参数通过表达式给出,所以可以使用函数调用,如:

intfun1(inta=rand());参数a缺省时,可由随机数发生函数当场产生,编译时定的是调什么函数。缺省参数可以有多个,但所有缺省参数必须放在参数表的右侧,即先定义所有的非缺省参数,再定义缺省参数。这是因为在函数调用时,参数自左向右逐个匹配,当实参和形参个数不一致时只有这样才不会产生二义性。

一个参数只能在一个文件被指定一次缺省实参,习惯上,缺省参数在公共头文件包含的函数声明中指定,否则缺省实参只能用于包含该函数定义的文件中的函数调用。例如:intfun2(int,int=10,int=20); //函数原型中给出缺省值。参数名也可省略voidfun1(){…}intfun2(inta,intb,intc){…}//定义中不再给出缺省值4.7.2缺省变元4.7.3内联函数当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。有些情况下,函数本身功能简单,代码很短,但使用频率却很高,程序频繁调用该函数所花费的时间却很多,从而使得程序执行效率降低。为了提高效率,一个解决办法就是不使用函数,直接将函数的代码嵌入到程序中。但这个办法也有缺点,一是相同代码重复书写,二是程序可读性往往没有使用函数的好。为了协调好效率和可读性之间的矛盾,C++提供了另一种方法,即定义内联函数,方法是在定义函数时用修饰词inline。4.7.3内联函数请看如下程序段,读入一行字符串,逐个判断是否为数字字符:#include<iostream.h>inlineIsNumber(charch){returnch>=′0′&&ch<=′9′?1:0;}voidmain(){charch;while(cin.get(ch),ch!=′\n′){if(IsNumber(ch))cout<<″是数字字符″<<endl;elsecout<<″不是数字字符″<<endl;}}因使用频度很高,说明为内联函数。4.8C++的系统库函数C++提供了一个很大的常用函数库,该函数库本身并不是C++语言的组成部分,所有库中的函数用户都可以自己定义,但直接使用库函数能给编程带来很大方便。系统函数库实际上是一系列源程序文件,每个文件中定义了若干常用函数及标识符,具有相同或相似功能的函数和标识符集中放在一个文件中。这些文件均以.h的形式命名,存放在系统目录的include子目录下。例如文件iostream.h中定义了与控制台输入输出和文件输入输出相关对象和成员函数,math.h中定义了大量数学函数,string.h中定义了大量与字符串操作相关的函数。

4.9头文件与多文件结构4.9.1头文件

在将一个程序分解成若干个文件时,需要考虑标识符在其他文件中的可见性。使用头文件是一个很有效的方法。如:#include<iostream.h>其中的iostream.h是系统定义的一个文件,这种以“.h”命名的文件称为头文件,系统定义的头文件中定义了一些常用的公用标识符和函数,用户只要将头文件包含进自己的文件,就可使头文件中定义的标识符在用户文件中变得可见,也就可以直接使用头文件中定义的标识符和函数。除了系统定义的头文件外,用户还可以自定义头文件。对于具有外部存储类型的标识符,可以在其他任何一个源程序文件中经声明后引用,因此用户完全可以将一些具有外部存储类型的标识符的声明放在一个头文件中。具体地说,头文件中可以包括:用户构造的数据类型(如枚举类型),外部变量,外部函数、常量和内联函数等具有一定通用性或常用的量,而一般性的变量和函数定义不宜放

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论