资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,在结构化程序设计中,函数是将任务进行模块划分的基本单位。一个函数实现一项功能。,在面向对象,程序设计中,,函数是对数据的一项操作,也是实现一项功能。,第三章 函数,要掌握函数的使用,必须理解函数调用时的内部实现机制,以及与此相关的内存分配机制、变量生命期和作用域。,本章还将介绍关于函数重载的概念,介绍递归算法、内联函数、默认参数函数以及多文件组织、编译预处理、工程文件的概念和运行库函数。,1,第三章 函数,3.1,函数的定义与调用,3.5,作用域与标识符的可见性,3.4,函数调用机制,3.3,全局变量和局部变量,3.2,函数的参数传递,返回值及函数声明,3.10,编译预处理,3.9,头文件与多文件结构,3.6,存储类型与标识符的生命期,3.8,函数的一些高级议题,3.7,函数的递归调用,2,3.1,函数的定义与调用,3.1.1,函数概述,3.1.2,函数的定义,3.1.3,函数的调用,3,3.1.1,函数概述,函数是,C+,程序的基本组成模块。,通过函数,可以把一个复杂任务分解成为若干个易于解决的小任务。充分体现逐步细化的设计思想。,组成,C+,程序的若干函数中,有一个称为,main(),(,Winmain(),)函数,是程序执行的入口,它可以调用其他函数,但不可以被调用。而其他,一般函数既可以调用也可以被调用,。,函数概念的引入:,入口函数:,4,3.1.1,函数概述,main(),fun2(),fun1(),fun3(),funa(),funb(),func(),图,3.1,函数调用层次关系,5,3.1.1,函数概述,3.1.1,结束,库函数和自定义函数:,库函数,或,标准函数,,是由编译系统预定义的,如一些常用的数学计算函数、字符串处理函数、图形处理函数、标准输入输出函数等。,库函数都按功能分类,集中说明在不同的头文件中,。用户只需在自己的程序中包含某个头文件,就可直接使用该文件中定义的函数。,用户根据需要将某个具有相对独立功能的程序定义为函数,称,自定义函数,。,6,3.1.2,函数的定义,无参函数定义格式为:,数据类型,函数名,(void),函数体,说明:,数据类型指函数返回值类型,可以是任一种数据类型,默认为返回整型值(但新标准要求写明,不用默认方式)。没有返回值应将返回值类型定义为,void,。,函数名采用合法标识符表示。,对无参函数,参数括号中的,void,通常省略,但括号不能省略。,函数体由一系列语句组成。函数体可以为空,称为空函数。,1,无参函数,7,3.1.2,函数的定义,/,打印一个表头,void,TableHead(),cout*endl;,cout*example *endl;,cout*endl;,int,input(),/,输入满足要求的数据,int,n;,cout,输入一个大于,5,的整数:,n;,while,(n=b?a:b);,有参函数的参数表中列出所有,形式参数,的类型和参数名称。各参数即使类型相同也必须分别加以说明。,形式参数简称形参,,只能是变量名,,不允许是常量或表达式。,void,delay(,long,n),int,i;,for,(i=0;ichoice;,while,(choice=4);,switch,(choice),case,1:,account_report();,break;,case,2:,engineering_report();,break;,case,3:,marketing_report();,break;,return,0;,19,void,menu_print(),cout”,系统功能:”,endl;,cout”1,财务报表”,endl;,cout”2,工程报表”,endl;,cout”3,市场报表”,endl;,cout”,选择业务序号:”,;,void,account_report(),/,生成财务报表,void,engineering_report(),/,生成工程报表,void,marketing_report(),/,生成市场报表;,20,3.2.3,函数声明,【,例,3.4,】,输出所有满足下列条件的正整数,m,:,10m1000,且,m,、,m,2,、,m,3,均为回文数。,分析:,回文指左右对称的序列。如,121,、,353,等就是回文数。判断整数是否回文数用函数实现,其思想是将该数各位拆开后反向组成新的整数,如果该整数与原数相等则为回文数。,m m*m m*m*m,11 121 1331,101 10201 1030301,111 12321 1367631,运行结果:,21,3,.,3,全局变量和局部变量,3,3,1,变量的存储机制与,C+,的内存布局,3,3,2,全局变量,3,3,3,局部变量,22,3.3.1,变量的存储机制与,C+,的内存布局,自由存储区,(,动态数据,),操作系统为一个,C+,程序的运行所分配的内存分为四个区域,如图,3.3,所示:,栈区(函数局部数据),全局数据区,(,全局、静态,),代码区(程序代码),(,main(),函数局部数据),自由存储区,(,动态数据,),23,存储区域说明:,(,1,)代码区(,Code area,):存放程序代码,即程序中各个函数的代码块;,(,2,)全局数据区(,Data area,):存放全局数据和静态数据;,分配该区时内存全部清零,,结果变量的所有字节,等效初始化为全,。,(,3,)栈区(,Stack area,):存放局部变量,如函数中的变量等;,分配栈区时不处理内存,,即变量取随机值。,(,4,),自由存储区(,Free store area,):,存放与指针相关的动态数据。,分配,自由存储,区时不处理内存,。参见第七章。,3.3.1,变量的存储机制与,C+,的内存布局,24,3,.3.2,全局变量,在所有函数之外定义的变量称为,全局变量,。,全局变量存放在全局数据区,因编译器自动将该区清为全,0,,如果用户在定义时不显式给出初始化值,则,等效初始化为全,0,。,全局变量可定义在程序开头,也可定义在中间位置,该全局变量,在定义处之后,的任何位置都是可以访问的,称为,可见的,。,【,例,3.5】,多个函数使用全局变量的例子。,全局变量引入:,25,3.3.3,局部变量,定义在函数内或块内的变量称为,局部变量,。,程序中使用的绝大多数变量都是局部变量。,局部变量在程序运行到它所在的块时建立在栈中,该块执行完毕局部变量占有的空间即被释放。故亦称为,自动变量,。,局部变量在定义时可加修饰词,auto,,,但通常省略。局部变量在定义时若未初始化,其值为,随机数,。,局部变量引入:,【,例,3.6】,使用局部变量的例子。,26,3.4,函数调用机制,局部变量占用的内存是在程序执行过程中,“,动态,”,地建立和释放的。这种,“,动态,”,是通过栈由系统,自动,管理进行的。,(,1,)建立栈空间;,(,6,)恢复现场:取主调函数运行状态及返回地址,释放栈空间;,(,7,)继续主调函数后续语句。,(,5,)释放被调函数中局部变量占用的栈空间;,(,4,)执行被调函数函数体;,(,3,)为被调函数中的局部变量分配空间,完成参数传递;,(,2,)保护现场:主调函数运行状态和返回地址入栈;,调用过程:,27,3.4,函数调用机制,void,fun1,(int,int,);,void,fun2(,float,);,int,main(),int x=1;y=2;,fun1(x,y);,return,o;,void,fun1(,int,a,int,b),float,x=3;,fun2(x);,void,fun2(,float,y),int,x;,x,栈顶,栈底,y,3,fun2(),fun1(),运行状态及返回地址,x,3,b,2,a,1,fun1(),main(),运行状态及返回地址,y,2,x,1,main(),操作系统运行状态及返回地址,此图例说明,在程序执行过程中怎样通过栈“,动态,”地建立和释放局部变量占用的内存的,28,3.5,作用域与标识符的可见性,3,文件域,2,函数声明域,作用域:,指标识符能够被使用的范围。只有在作用域内标识符才可以被访问(称为可见)。,本节重点讨论,局部域,和文件域(全局域),其中局部域包括,块域,和,函数声明域,。任何,标识符,作用域的起始点均为,标识符说明,处。,下面分别介绍,:,1,块域,29,函数中定义的标识符,包括形参和函数体中定义的局部变量,作用域都在该函数内,也称作,函数域,。,块域,块,指一对大括号括起来的程序段。块中定义的标识符,作用域在块内。,复合语句是一个块。,函数也是一个块。,复合语句中定义的标识符,,作用域仅在该复合语句中。,【,例,3.7】,输入两数,按从大到小的顺序保存。,块的引入:,30,块域,由,VC+,运行,结果如下:,输入两整数:,3 5,调用前:实参,a=3,b=5,调用中,交换前:形参,a=3,b=5,交换后:形参,a=5,b=3,调用后:实参,a=3,b=5,交换失败,局部变量具有局部作用域使得程序在不同块中可以使用同名变量。这些同名变量各自在自己的作用域中可见,在其它地方不可见。,【,例,3.8】,设计函数完成两数交换,用主函数进行测试。,操作系统运行状态及返回地址,main(),3,a,5,b,main(),运行状态及返回地址,swap(),3,a,5,b,3,t,3,5,31,块域,对于块中,嵌套,其它块的情况,如果嵌套块中有同名局部变量,服从局部优先原则,即在内层块中,屏蔽,外层块中的同名变量,换句话说,内层块中局部变量的作用域为内层块;外层块中局部变量的作用域为外层除去包含同名变量的内层块部分。,如果块内定义的局部变量与全局变量同名,块内仍然局部变量优先,但与块作用域不同的是,在块内可以通过域运算符“,:”,访问同名的全局变量。,【,例,3.9】,显示同名变量可见性。,32,函数声明域,函数声明不是定义函数,在作函数声明时,其中的形参作用域只在声明中,即,作用域结束于右括号,。正是由于形参不能被程序的其他地方引用,所以通常只要声明形参个数和类型,,形参名可省略,。,33,3,文件域,文件域,也称全局域。定义在所有函数之外的标识符作用域为从定义处到整个源文件结束,即文件域。文件中定义的全局变量和函数的作用域为文件域。,如果某个文件中说明了作用域为文件域的标识符,该文件又被另一个文件包含,则该标识符的作用域延伸到新的文件中。如,cin,和,cout,是在头文件,iostream,中说明的具有文件作用域的标识符,它们的作用域也延伸到嵌入,iostream,的文件中。,34,存储类型(,storage class,)决定标识符的存储区域,即编译系统在不同区域为不同存储类型的标识符分配空间。由于存储区域不同,标识符的生命期也不同。所谓,生命期,,指的是,标识符从获得空间到空间释放之间的期间,,标识符只有在生存期中、并且在其自己的作用域中才能被访问。,3.6,存储类型与标识符的生命期,3.6.1,存储类型,3.,3.2,生命期,35,自动变量为用,auto,说明的变量,通常,auto,缺省。局部变量都是自动变量,生命期开始于块的执行,结束于块的结束,其原因是自动变量的空间分配在栈中,块开始执行时系统自动分配空间,块执行结束时系统自动释放空间。故自动变量的生命期和作用域是一致的。,3.6.1,存储类型,为提高程序运行效率,可以将某些变量保存在寄存器中,即用,register,说明为寄存器变量,,但不提倡使用,。,C+,中关于存储类型的说明符(,storage class specifier,)有四个:,auto,、,register,、,static,和,extern,。其中用,auto,和,register,修饰的称为自动存储类型,用,static,修饰的称为静态存储类型,用,extern,修饰的称为外部存储类型。,1,自动存储类型,36,static,说明的变量称为静态变量。根据定义的位置不同,还分为局部静态变量和全局静态变量,也称内部静态变量和外部静态变量。静态变量均存储在全局数据区,如果程序未显式给出初始化值,则,等效初始化为全,0,;静态变量占有的空间要到整个程序执行结束才释放,故静态变量具有整个程序执行期间的生命期。,3.6.1,存储类型,局部静态变量是定义在块中的静态变量,编译系统在全局数据区为其开辟空间并保存数据,该空间一直到整个程序结束才释放。局部静态变量具有局部作用域,但却具有整个程序执行期间的生命期。,如果显式给出初始化值,则在该块第一次执行时完成,且只进行一次。,2,静态存储类型,【,例,3.10】,自动变量与局部静态变量的区别,37,3,.6,.1,存储类型,3,外部存储类型,一个,C+,程序可以由多个源程序文件组成。多文件程序系统可以通过,外部存储类型,的变量和函数来,共享,某些数据和操作。,在一个程序文件中定义的全局变量和函数缺省为外部的,即其作用域可以延伸到程序的其他文件中。其他文件如果要使用这个文件中定义的全局变量和函数,应该在使用前用“,extern,”,作外部声明。外部声明通常放在文件的开头(,函数,总是,省略,extern,)。,外部变量声明不同于全局变量定义,变量定义时编译器为其分配存储空间,而变量声明则表示该全局变量已在其他地方定义过,编译系统不再分配存储空间。,外部的全局变量或函数加上,static,修饰,就成为静态全局变量或静态函数。静态的全局变量和函数作用域限制在本文件,其他文件即使使用外部声明也无法使用该全局变量或函数。,【,例,3.11】,外部存储类型的例子,38,3.6.2,生命期,1.,静态生命期,静态生命期,(,Static extent,或,Static storage duration,)指的是标识符从程序开始运行时就存在,具有存储空间,到程序运行结束时消亡,释放存储空间。具有静态生命期的标识符存放在全局数据区,如全局变量、静态全局变量、静态局部变量。具有静态生命期的标识符在未被用户初始化的情况下,系统会,等效,将其初始化为全,0,。,函数驻留在代码区,也具有静态生命期。所有具有文件作用域的标识符都具有静态生命期。,39,3.6.2,生命期,2.,局部生命期,在函数内部或块中定义的标识符具有,局部生命期,(,Automatic extent,或,Automatic storage duration,),其生命期开始于执行到该函数或块的标识符定义处,结束于该函数或块的结束处。具有局部生命期的标识符存放在栈区。具有局部生命期的标识符如果未被初始化,其内容是随机的,不可引用。,具有局部生命期的标识符必定具有局部作用域;但反之不然,静态局部变量具有局部作用域,但却具有静态生命期。,40,3.6.2,生命期,具有,动态生命期,(,dynamic extent,或,dynamic storage duration,)的标识符存放在自由存储区,由特定的函数调用或运算来创建和释放,如用,new,运算符(或调用,malloc(),函数)为变量分配存储空间时,变量的生命期开始,而用,delete,运算符(或调用,free(),函数)释放空间或程序结束时,变量生命期结束。关于,new,运算符和,delete,运算符将在第七章中介绍。,3.,动态生命期,41,3.7,函数的递归调用,递归是一种描述问题的方法,或称算法。递归的思想可以简单地描述为“,自己调用自己,”。例如用如下方法定义阶乘:,可以看出是用阶乘定义阶乘,这种自己定义自己的方法称为递归定义。,递归的引入:,递归的分类,在函数调用中,有这样两种情况,一种是在函数,A,的定义中有调用函数,A,的语句,即自己调用自己;另一种是函数,A,的定义中出现调用函数,B,的语句,而函数,B,的定义中也出现调用函数,A,的语句,即相互调用。前者称,直接递归,,后者称,间接递归,。本节只介绍直接递归。,42,递归定义的阶乘函数:,fac(,int,n),int,y;,if,(,n=0|n=1,)y=1;,else,y=n*fac(n-1);,return,y;,只要设计主函数调用阶乘函数,即可实现计算阶乘。,递归函数必须定义递归,终止条件,(,Stopping condition,),避免,无穷递归,(,Infinite Recursion,)。,3.7,函数的递归调用,【,例,3.12】,求,4,!,运行结果:,4 3 2 1 1 2 6 24,4!=24,43,3.7,函数的递归调用,探讨:,计算是先右后左。请看下一条输出语句:,cout,n4!=,fac(4),n3!=,fac(3)endl;,先算,fac(3),后算,fac(4),先右后左。但输出还是从左到右:,3 21126,432112624,4,!,=24,3,!,=6,说明:,cout,n4!=,fac(4)endl;,执行时是先算函数值,然后再从左到右输出各表达式的值。所以有两行输出,而不是第一行插在第二行赋值号与,24,之间。,44,3.7,函数的递归调用,递归过程的分析:,递归函数的执行分为,“,递推,”,和,“,回归,”,两个过程,这两个过程由递归终止条件控制,即,逐层递推,,直至,递归终止条件,,然后,逐层回归,。每次调用发生时都首先判断递归终止条件。,递归调用同普通的函数调用一样,每当调用发生时,在栈中分配单元保存返回地址以及参数和局部变量;而与普通的函数调用不同的是,由于递推的过程是一个逐层调用的过程,因此存在一个逐层连续的参数入栈过程,直至遇到递归终止条件时,才开始回归,这时才逐层释放栈空间,返回到上一层,直至最后返回到主调函数。,45,3.7,函数的递归调用,现场与返回地址,第一层,fac(4),域,4,n,4,*,6,24,y,现场与返回地址,第二层,fac(3),域,3,n,3,*,2,6,y,现场与返回地址,第三层,fac(2),域,2,n,2,*,1,2,y,现场与返回地址,第四层,fac(1),域,n,y,1,1,1,2,6,fac(,4,),返回值,=,24,main(),域,注意:,右图中存,“,fac(,1,),返回值,”,等的存储单元是,无名临时局部变量,,其生命期在表达式,y=n*fac(n-1),中。,因为被调函数的返回语句格式为:,return,表达式,;,为返回表达式的值,必须有一个,无名临时局部变量,来,承载,这个值。,然后由主调函数中包含调用的表达式语句从该临时变量中取得值,表达式语句执行后该临时变量撤销。,24,46,3.7,函数的递归调用,从以上几例可以看出,递归算法一般不需要借助循环,但通过不断,递推,和,回归,的过程实现了其他算法用循环完成的功能。因此,递归的,终止条件,非常重要,否则将会无休止地递归下去,陷入死循环状态。,【,例,3.14】,输入一个整数,用递归算法将整数倒序输出,【,例,3.13】,汉诺塔问题,47,3.7,函数的递归调用,【,例,3.13】,【,例,3.1,3,】,汉诺塔问题。,有,A,、,B,、,C,三根柱子,,A,柱上有,n,个大小不等的盘子,大盘在下,小盘在上。要求将所有盘子由,A,柱搬动到,C,柱上,每次只能搬动一个盘子,搬动过程中可以借助任何一根柱子,但必须满足大盘在下,小盘在上。打印出搬动的步骤。,A,柱,B,柱,C,柱,48,分析:,1 A,柱只有一个盘子的情况:,A,柱,C,柱;,2 A,柱有两个盘子的情况:小盘,A,柱,B,柱,大盘,A,柱,C,柱,小盘,B,柱,C,柱。,3 A,柱有,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,个盘子的问题,,,,类推下去,一直到,最后成为搬动一个盘子的问题,。,这是一个典型的递归问题,递归结束于只搬动一个盘子。,3.7,函数的递归调用,【,例,3.13】,49,3.7,函数的递归调用,【,例,3.13】,算法:,1 n-1,个盘子,A,柱,B,柱,借助于,C,柱;,2,第,n,个盘子,A,柱,C,柱;,3 n-1,个盘子,B,柱,C,柱,借助于,A,柱;,其中步骤,1,和步骤,3,继续递归下去,直至搬动一个盘子为止。由此,可以定义两个函数,:,一个是递归函数,命名为,hanoi(,int,n,char,source,char,temp,char,target),,实现将,n,个盘子从源柱,source,借助中间柱,temp,搬到目标柱,target,;,另一个命名为,move(,char,source,char,target),,用来输出搬动一个盘子的提示信息。,50,void,move(,char,source,char,target),couttargetendl;,void,hanoi(,int,n,char,source,char,temp,char,target),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,个盘子搬到目标柱,int,main(),int,n;,cout,输入盘子数:,n;,hanoi(n,A,B,C);,return,0;,51,3.7,函数的递归调用,【,例,3.13】,banoi(2,A,C,B),A,C,banoi(3,A,B,C),banoi(1,A,B,C),banoi(1,B,C,A),C,B,B,A,A,C,B,C,banoi(1,A,B,C),A,B,banoi(1,C,A,B),A,C,banoi(2,B,A,C),汉诺塔程序执行框图,输入盘子数:,3,52,图,3.10,递归求解斐波那契数列调用树,同其他算法相比,用递归算法编制的程序,非常简洁易读,,但缺点是增加了内存的开销,在递推的过程中会,占用大量栈空间,,且连续的调用返回操作,占用较多,CPU,时间,。因此是否选择使用递归算法取决于所解决的问题及应用的场合。,3.7,函数的递归调用,【,例,3.15】,采用递推法求解,Fibonacii,数列,53,3.8,函数的一些高级议题,3.8.1,函数重载,3.8.2,缺省参数,3.8.3,内联函数,54,3.8.1,函数重载,重载的引入:,在,C+,中,如果需要定义几个功能相似,而参数类型不同的函数,那么这样的几个函数可以使用相同的函数名,这就是,函数重载,。,例:,求和函数对应不同的参数类型可以定义如下几个重载函数:,int,sum(,int,a,int,b),double,sum(,double,a,double,b),float,sum,(float,a,float,b,float,c),55,3.8.1,函数重载,重载匹配:,当某个函数中调用到重载函数时,编译器会根据实参的类型去对应地调用相应的函数。匹配过程按如下步骤进行:,(,1,)如果有严格匹配的函数,就调用该函数;,(,2,)参数内部转换后如果匹配,调用该函数;,(,3,)通过用户定义的转换寻求匹配。,因此在定义重载函数时必须保证参数类型不同,仅仅返回值类型不同是不行的。函数重载的好处在于,可以用相同的函数名来定义一组功能相同或类似的函数,程序的可读性增强。,【,例,3.16】,重载函数的应用,56,3.8.2,默认参数,默认参数的引入:,默认参数指在定义函数时为形参指定默认值(缺省值),。这样的函数在调用时,对于默认参数,可以给出实参值,也可以不给出参数值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按默认值进行调用。,默认参数的函数调用:,默认实参并不一定是常量表达式,可以是任意表达式,甚至可以通过函数调用给出。如果默认实参是任意表达式,则函数每次被调用时该表达式被重新求值。但表达式必须有意义。,【,例,3.17】,默认参数,57,3.8.2,默认参数,使用要点:,默认参数可以有多个,但所有默认参数必须放在参数表的右侧,即先定义所有的非默认参数,再定义默认参数。这是因为在函数调用时,,参数自左向右逐个匹配,,当实参和形参个数不一致时只有这样才不会产生二义性。,在同一个作用域中一个参数只能被指定一次默认值,不可以在声明和定义中同时指定默认值,即使默认值一样也不行。,int,fun2(,int,int,=10,int,=20);,/,函数声明中给出缺省值。参数名也可省略,void,fun1(),int,fun2(,int,a,int,b,int,c),/,定义中不再给出缺省值,习惯上,缺省参数在公共头文件包含的函数声明中指定,否则缺省实参只能用于包含该函数定义的文件中的函数调用。,58,3.8.3,内联函数,内联函数的引入:,当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。,当函数,功能简单,,使用,频率,很,高,,,为了提高效率,直接将函数的代码嵌入到程序中。但这个办法有缺点,一是相同代码重复书写,二是程序可读性往往没有使用函数的好。,为了协调好效率和可读性之间的矛盾,,,C+,提供了另一种方法,即定义,内联函数,,方法是在定义函数时用修饰词,inline,。,59,3.8.3,内联函数,请看如下程序段,读入一行字符串,逐个判断是否为数字字符:,inline,int,IsNumber(,char,ch),return,ch=0,int,main(),char,ch;,while,(cin.get(ch),ch!=n),if,(IsNumber(ch)cout,是数字字符,endl;,else,cout,不是数字字符,endl;,return,0;,因使用频度很高,说明为内联函数。,60,3.8.3,内联函数,内联函数的调用机制与一般函数不同,编译器在编译过程中遇到,inline,时,为该函数建立一段代码,而后在每次调用时,直接,将该段代码,嵌入,到调用函数中,从而,将函数调用方式变为顺序执行方式,,这一过程称为内联函数的扩展或内联。,因,inline,指示符对编译器而言只是一个建议,编译器也可以选择忽略该建议。,61,3.9,头文件与多文件结构(选读),3.9.1,头文件,标准库头文件:,考虑标识符在其他文件中的可见性。使用头文件是很有效的方法。如:,#include,using namespace,std;,其中,iostream,是,在,标准名字空间域,std,中定义的头文件。对应的传统方式的文件名为,,头文件以“,.h”,为后缀。,系统定义的头文件,中定义了一些常用的公用标识符和函数,用户只要将头文件包含进自己的文件,就可使头文件中定义的标识符在用户文件中变得可见,也就可以直接使用头文件中定义的标识符和函数。,62,3.9.1,头文件,自定义头文件:,除了系统定义的头文件外,用户还可以,自定义头文件,。对于具有外部存储类型的标识符,可以在其他任何一个源程序文件中经声明后引用,因此用户完全可以将一些具有外部存储类型的标识符的声明放在一个头文件中。具体地说,头文件中可以包括:用户构造的数据类型(如枚举类型),外部变量,外部函数、常量和内联函数等具有一定通用性或常用的量,而一般性的变量和函数定义不宜放在头文件中。,63,3.9.2,多文件结构,在开发较大程序时,通常将其分解为多个源程序文件,每个较小的程序用一个源程序文件建立。程序经过建立、编译、连接,成为一个完整的可执行程序。,多文件结构通过工程进行管理,在工程中建立若干用户定义的头文件,.h,和源程序文件,.cpp,。头文件中定义用户自定义的数据类型,所有的程序实现则放在不同的源程序文件中,。编译时每个源程序文件单独编译,如果源程序文件中有编译预处理指令,则首先经过编译预处理生成临时文件存放在内存,之后对临时文件进行编译生成目标文件,.obj,,编译后临时文件撤销。所有的目标文件经连接器连接最终生成一个完整的可执行文件,.exe,。,图,3.11,是一个多文件系统的开发过程。,64,3.9.2,多文件结构,编译,预编译,编译,预编译,预编译,编译,图,3.11 C+,程序开发过程,file1.h,file1.cpp,file2.h,file2.cpp,filen.h,filen.cpp,临时文件,1,临时文件,2,临时文件,n,file1.obj,file2.obj,filen.obj,Filename.exe,.lib,C+,标准类库,连接,运行,65,3.10,编译预处理,(,选读,),3.10.1,宏定义指令,3.10.2,文件包含指令,3.10.3,条件编译指令,66,3.10.1,宏定义指令,1,不带参宏定义,用来产生与一个字符串对应的常量字符串,格式为:,#define,宏名 常量串,预处理后文件中凡出现该字符串处均用其对应的常量串代替。替换过程称为宏替换或宏展开。例如,如果使用指令,#define,PI 3.1415926,则程序中可以使用标识符,PI,,编译预处理后产生一个中间文件,文件中所有,PI,被替换为,3.1415926,。,宏替换只是字符串和标识符之间的简单替换,预处理本身不做任何数据类型和合法性检查,也不分配内存单元,。,67,3.10.1,宏定义指令,2,带参数的宏定义,带参宏定义的形式很象定义一个函数,格式为:,#define,宏名(形参表)表达式串,例如作如下宏定义:,#define,S(a,b)(a)*(b)/2,程序中可使用,S(a,b),,预处理后产生中间文件,其中,S(a,b),被替换成,(a)*(b)/2,。注意,宏定义形参通常要用括号括起来,否则容易导致逻辑错误。例如,如果定义:,#define,S(a,b)a*b/2,那么程序中的,S(3+5,4+2),就会被宏展开为,3+5*4+2/2,,不符合定义的真正的意图。,带参宏定义形式上象定义函数,但它与函数的本质不同,宏定义仍然只是产生字符串替代,不存在分配内存和参数传递。,68,3.10.2,文件包含指令,文件包含,用,#include,指令,,预处理后将指令中指明的源程序文件嵌入到当前源程序文件的指令位置处。格式为:,#include,或,#include,文件名,第一种,方式称为,标准方式,,预处理器将在,include,子目录下搜索由文件名所指明的文件。这种方式适用于嵌入,C+,提供的头文件,因为这些头文件一般都存在,C+,系统目录的,include,子目录下。而,第二种方式,编译器将首先在当前文件所在目录下搜索,如果找不到再按标准方式搜索。这种方式适用于嵌入用户自己建立的头文件。,69,一个被包含的头文件中还可以有,#include,指令,即,include,指令可以嵌套,,但是,如果同一个头文件在同一个源程序文件中被,重复包含,,就会出现,标识符重复定义的错误,。例如:头文件,f2.h,中包含了,f1.h,,如果文件,f3.cpp,中既包含,f1.h,,又包含,f2.h,,那么编译将提示错误,原因是,f1.h,被包含了两次,那么其中定义的标识符在,f3.cpp,中就被重复定义。避免重复包含可以用,条件编译指令,。,3.10.2,文件包含指令,70,3.10.3,条件编译指令,1,用,宏名,作为编译的条件,格式为:,#ifdef,#else,#endif,2,表达式的值,作为编译条件,格式为:,#if,#else,#endif,当希望在不同条件下编译程序的不同部分。这种情况就要使用条件编译指令。,其中程序段可以是程序也可以是编译预处理指令。可以通过在该指令前面安排宏定义来控制编译不同的程序段。,71,例:在调试程序时常常要输出调试信息,而调试完后不需要输出这些信息,则可以把输出调试信息的语句用条件编译指令括起来。形式如下:,#ifdef,DEBUG,cout,a=,at,x=,x=b?a:b);,int,main(),float,x,y;,cout,输入两个实数:,xy;,coutx,和,y,中较大数为,max(x,y)endl;,return,0,;,74,3.2.1,函数的参数传递及传值调用,【,例,3.2】,调用,power(4.6,3),函数,power(4.6,3),return,97.336,主程序后续语句,n=3,x=4.6,c=,a,【,例,3.,2,】,实参和形参对应关系的示例。,float,power(,float,x,int n),/,求,x,的,n,次幂,float,p=1;,while,(n-)p*=x;,return,p;,int,main(),int,n=3;,float,x=4.6;,char,c=a;,coutpower(x,n)=power(x,n)endl;,coutpower(c,n)=power(c,n)endl;,coutpower(n,x)=power(n,x)endl;,return,0;,75,3.2.1,函数的参数传递及传值调用,【,例,3.2】,调用,power(,a,3),函数,power(97,3),return,912673,主程序后续语句,n=3,x=4.6,c=,a,【,例,3,2,】,实参和形参对应关系,。,float,power(,float,x,int,n),/,求,x,的,n,次幂,float,p=1;,while,(n-)p*=x;,return,p;,int,main(),int,n=3;,float,x=4.6;,char,c=a;,coutpower(x,n)=power(x,n)endl;,coutpower(c,n)=power(c,n)endl;,coutpower(n,x)=power(n,x)endl;,return,0;,76,3.2.1,函数的参数传递及传值调用,【,例,3.2】,调用,power(3,4.6),函数,power(3,4),return,81,主程序后续语句,n=3,x=4.6,c=,a,【,例,3,2,】,实参和形参对应关系。,float,power(float x,int n),/,求,x,的,n,次幂,float,p=1;,while,(n-)p*=x;,return,p;,int,main(),int,n=3;,float,x=4.6;,char,c=a;,coutpower(x,n)=power(x,n)endl;,coutpower(c,n)=power(c,n)endl;,coutpower(n,x)=power(n,x)endl;,return,0;,77,【,例,3.3】,程序:,float,TriangleArea,(,float,a,float,b,float,c),if,(a+b=c)|(a+c=b)|(b+c=a),return-1,;,float,s;,s=(a+b+c)/2;,return,sqrt(s*(s-a)*(s-b)*(s-c),;,int,main(),float,a,b,c,area;,cout,输入三角形三边,a,b,c:abc;,area=TriangleArea(a,b,c);,if,(area=-1)cout(a,b,c,),不能构成三角形!,endl;,else,cout,三角形,(a,b,c,),面积为:,areaendl;,return,0;,78,bool,palindrome(,int,);,/,函数声明,int,main(),int,m;,coutsetw(10)msetw(20)m*m,“,setw(20)m*m*mendl;,for,(m=11;m1000;m
展开阅读全文