收藏 分享(赏)

函数与预编译.ppt

上传人:fmgc7290 文档编号:5247890 上传时间:2019-02-14 格式:PPT 页数:92 大小:904KB
下载 相关 举报
函数与预编译.ppt_第1页
第1页 / 共92页
函数与预编译.ppt_第2页
第2页 / 共92页
函数与预编译.ppt_第3页
第3页 / 共92页
函数与预编译.ppt_第4页
第4页 / 共92页
函数与预编译.ppt_第5页
第5页 / 共92页
点击查看更多>>
资源描述

1、假如不模块化,读多少行的程序能让你不头疼?,main()当中能放多少行程序?,假如 cout()函数由10行代码替换, 那么你见过的程序会成什么样子?,如果所有代码都在main()当中,团队怎么合作?,如果代码都在一个文件中,怎么团队合作?,模块化思想,模块各司其职 每个模块只负责一件事情,它可以更专心 便于进行单个模块的设计、开发、调试、测试和维护等工作 一个模块一个模块地完成,最后再将它们集成 开发人员各司其职 按模块分配任务,职责明确 并行开发,缩短开发时间 分而治之(Wirth, 1971 ) 信息隐藏(Parnas, 1972),在结构化程序设计中,函数是将任务进行模块划分的基本单位

2、。一个函数实现一项功能。在面向对象程序设计中,函数是对数据的一项操作,也是实现一项功能。,第四章 函数与预编译,要掌握函数的使用,必须理解函数调用时的内部实现机制,以及与此相关的内存分配机制、变量生命期和作用域。,本章还将介绍关于函数重载的概念,介绍递归算法、内联函数、默认参数函数以及多文件组织、编译预处理、工程文件的概念和运行库函数。,第四章 函数与预编译,4.1 函数的定义与调用,4. 5 作用域与标识符的可见性,4.4 函数调用机制,4.3 全局变量和局部变量,4.2 函数的参数传递,返回值及函数声明,4.10 编译预处理,4.9 头文件与多文件结构,4.6 存储类型与标识符的生命期,4

3、.8 函数的一些高级议题,4.7 函数的递归调用,4.1 函数的定义与调用,4.1.1 函数概述,4.1.2 函数的定义,4.1.3 函数的调用,4.1.1 函数概述,函数是C+程序的基本组成模块。,通过函数,可以把一个复杂任务分解成为若干个易于解决的小任务。充分体现逐步细化的设计思想。,组成C+程序的若干函数中,有一个称为main()函数,是程序执行的入口,它可以调用其他函数,但不可以被调用。而其他一般函数既可以调用也可以被调用。,函数概念的引入:,入口函数:,4.1.1 函数概述,4.1.1 函数概述,4.1.1结束,库函数和自定义函数:库函数或标准函数,是由编译系统预定义的,如一些常用的

4、数学计算函数、字符串处理函数、图形处理函数、标准输入输出函数等。库函数都按功能分类,集中说明在不同的头文件中。用户只需在自己的程序中包含某个头文件,就可直接使用该文件中定义的函数。用户根据需要将某个具有相对独立功能的程序定义为函数,称自定义函数。,4.1.2 函数的定义,无参函数定义格式为: 数据类型函数名(void)函数体,说明:数据类型指函数返回值类型,可以是任一种数据类型,默认为返回整型值(但新标准要求写明,不用默认方式)。没有返回值应将返回值类型定义为void。函数名采用合法标识符表示。对无参函数,参数括号中的void通常省略,但括号不能省略。函数体由一系列语句组成。函数体可以为空,称

5、为空函数。,1 无参函数,4.1.2 函数的定义,例: 打印一个表头 void TableHead ( ) cout*endl; cout* example *endl; cout*endl; ,2 有参函数,有参函数的定义格式为 数据类型函数名 (参数类型1 形式参数1,参数类型2 形式参数2,函数体,例: 写一函数,返回两个整数中较大一个的值,有参函数的参数表中列出所有形式参数的类型和参数名称。各参数即使类型相同也必须分别加以说明。形式参数简称形参,只能是变量名,不允许是常量或表达式。,int max (int a, int b) return(a=b?a:b); ,问题:定义函数时究竟哪

6、些变量应当作为函数的参数?哪些应当定义在函数体内?,提示,原则:函数在使用时被看成 “黑匣子”,除了输入输出外,其他部分可不必关心。从函数的定义看出,函数头正是用来反映函数的功能和使用接口,它所定义的是“做什么”。即明确了“黑匣子”的输入输出部分,输出就是函数的返回值,输入就是参数。因此,只有那些功能上起自变量作用的变量才必须作为参数定义在参数表中;函数体中具体描述“如何做”,因此除参数之外的为实现算法所需用的变量应当定义在函数体内。C+中不允许函数的嵌套定义,即在一个函数中定义另一个函数。,4.1.3 函数的调用,函数调用:所谓函数调用,就是使程序转去执行函数体。在C+中,除了主函数外,其他

7、任何函数都不能单独作为程序运行。任何函数功能的实现都是通过被主函数直接或间接调用进行的。无参函数的调用格式:函数名( )有参函数的调用格式:函数名(实际参数表) 其中实际参数简称实参,用来将实际参数的值传递给形参,因此可以是常量、具有值的变量或表达式。,【例4.1】 输入两个实数,输出其中较大的数,4.2 函数的参数传递、返回值及 函数声明,4.21 函数的参数传递及传值调用,4.23 函数声明,4.22 函数返回值,参数传递:函数调用首先要进行参数传递,参数传递的方向是由实参传递给形参。传递过程是,先计算实参表达式的值,再将该值传递给对应的形参变量。一般情况下,实参和形参的个数和排列顺序应一

8、一对应,并且对应参数应类型匹配(赋值兼容),即实参的类型可以转化为形参类型。而对应参数的参数名则不要求相同。,4.2.1 函数的参数传递及传值调用,传值调用和引用调用: 按照参数形式的不同,C+有两种调用方式:传值调用和引用调用。传值调用传递的是实参的值,本章介绍传值调用。,4.2.1 函数的参数传递及传值调用,传值调用: 将实参的值复制给形参,在函数中参加运算的是形参,而实参不会发生任何改变。传值调用起了一种隔离作用。,【例4.2】 实参和形参对应关系的示例。,4.2.2 函数返回值,return语句的格式: return 表达式; 函数的计算结果通过该语句传递回主调函数。,【例4.3】设计

9、函数,根据三角形的三边长求面积。如果不能构成三角形,给出提示信息。 分析:函数为计算三角形面积,一般三角形返回面积值,若不能构成三角形则返回-1。设计一个主函数完成函数测试。根据返回值情况输出相应结果。,4.2.2 函数返回值,函数可以有返回值,也可以没有返回值。对于没有返回值的函数,功能只是完成一定操作,应将返回值类型定义为void ,函数体内可以没有return语句,当需要在程序指定位置退出时,可以在该处放置一个:return ;,讨论:,4.2.2结束,4.2.3 函数声明,函数声明是一条以分号结束的语句: 函数返回值类型函数名 (形参表);,语法上对程序文件中函数的排列次序要求满足先定

10、义后使用。但从结构化程序设计的角度,通常是先调用后定义。使用函数声明,则既符合由粗到精的思维方式,又满足了语法要求。,其中形参表可以逐个列出每个参数的类型和参数名,也可以列出每个形参的类型,参数名可省略,各形参之间以逗号分隔。函数声明和所定义的函数必须在返回值类型、函数名、形参个数和类型及次序等方面完全对应一致,否则将导致编译错误。,函数声明的引入:,函数声明的格式:,下面是一个使用结构化程序设计思想开发的企业管理报表程序的框架。它使用了函数声明。 void menu_print(); void account_report(); void engineering_report(); void

11、 marketing_report(); int main()int choice;do menu_print();cinchoice;while(choice=4);switch(choice)case 1: account_report(); break;case 2: engineering_report(); break;case 3: marketing_report(); break; return 0;,void menu_print() cout”系统功能:”endl;cout”1 财务报表”endl;cout”2 工程报表”endl;cout”3 市场报表”endl;cout

12、”选择业务序号:”; void account_report() /生成财务报表 void engineering_report()/生成工程报表 void marketing_report()/生成市场报表; ,4.2.3 函数声明,【例4.4】 输出所有满足下列条件的正整数m:10m1000且m、m2、m3均为回文数。,分析:回文指左右对称的序列。如121、353等就是回文数。判断整数是否回文数用函数实现,其思想是将该数各位拆开后反向组成新的整数,如果该整数与原数相等则为回文数。,m m*m m*m*m11 121 1331 101 10201 1030301 111 12321 1367

13、631,运行结果:,4.3 全局变量和局部变量,4.4.1 变量的存储机制与C+的内存布局,4.4.2 全局变量,4.4.3 局部变量,4.4.1 变量的存储机制与C+的内存布局,操作系统为一个C+程序的运行所分配的内存分为四个区域,如图4.3 所示:,存储区域说明: (1)代码区(Code area):存放程序代码,即程序中各个函数的代码块; (2)全局数据区(Data area):存放全局数据和静态数据;分配该区时内存全部清零,结果变量的所有字节自动初始化为零。 (3)栈区(Stack area):存放局部变量,如函数中的变量等;分配栈区时不处理内存,即变量取随机值。 (4)自由存储区(F

14、ree store area):存放与指针相关的动态数据。分配自由存储区时不处理内存。参见第七章。,4.4.1 变量的存储机制与C+的内存布局,4.4.2 全局变量,在所有函数之外定义的变量称为全局变量。,全局变量在编译时建立在全局数据区,在未给出初始化值时系统自动初始化为全0。,全局变量可定义在程序开头,也可定义在中间位置,该全局变量在定义处之后的任何位置都是可以访问的,称为可见的。,【例4.5】 多个函数使用全局变量的例子。,全局变量引入:,4.4.3 局部变量,定义在函数内或块内的变量称为局部变量。,程序中使用的绝大多数变量都是局部变量。,局部变量在程序运行到它所在的块时建立在栈中,该块

15、执行完毕局部变量占有的空间即被释放。,局部变量在定义时可加修饰词auto,但通常省略。局部变量在定义时若未初始化,其值为随机数。,局部变量引入:,【例4.6】 使用局部变量的例子。,4.4 函数调用机制,局部变量占用的内存是在程序执行过程中“动态”地建立和释放的。这种“动态”是通过栈由系统自动管理进行的。,(1)建立栈空间;,(6)恢复现场:取主调函数运行状态及返回地址,释放栈空间;,(7)继续主调函数后续语句。,(5)释放被调函数中局部变量占用的栈空间;,(4)执行被调函数函数体;,(3)为被调函数中的局部变量分配空间,完成参数传递;,(2)保护现场:主调函数运行状态和返回地址入栈;,调用过

16、程:,4.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; ,此图例说明在程序执行过程中怎样通过栈“动态”地建立和释放局部变量占用的内存的,4.5 作用域与标识符的可见性,3 文件作用域,2 函数声明作用域,作用域:指标识符能够被使用的范围。只有在作用域内标识符才可以被访问(称为可见)。,本节重点讨论局部域和文件域(全局域),其中局

17、部域包括块域和函数声明域。任何标识符作用域的起始点均为标识符说明处。,下面分别介绍:,1 块作用域,函数中定义的标识符,包括形参和函数体中定义的局部变量,作用域都在该函数内,也称作函数域。,块域,块指一对大括号括起来的程序段。块中定义的标识符,作用域在块内。,复合语句是一个块。,函数也是一个块。,复合语句中定义的标识符,,作用域仅在该复合语句中。,【例4.7】 输入两数,按从大到小的顺序保存。,块的引入:,块域,由VC+平台运行,结果如下: 输入两整数: 3 5 调用前:实参a=3,b=5 调用中 交换前:形参a=3,b=5 交换后:形参a=5,b=3 调用后:实参a=3,b=5 交换失败,局

18、部变量具有局部作用域使得程序在不同块中可以使用同名变量。这些同名变量各自在自己的作用域中可见,在其它地方不可见。,【例4.8】设计函数完成两数交换,用主函数进行测试。,块域,对于块中嵌套其它块的情况,如果嵌套块中有同名局部变量,服从局部优先原则,即在内层块中屏蔽外层块中的同名变量,换句话说,内层块中局部变量的作用域为内层块;外层块中局部变量的作用域为外层除去包含同名变量的内层块部分。,如果块内定义的局部变量与全局变量同名,块内仍然局部变量优先,但与块作用域不同的是,在块内可以通过域运算符“:”访问同名的全局变量。,【例4.9】 显示同名变量可见性。,函数声明作用域,函数声明不是定义函数,在作函

19、数声明时,其中的形参作用域只在声明中,即作用域结束于右括号。正是由于形参不能被程序的其他地方引用,所以通常只要声明形参个数和类型,形参名可省略。,3 文件作用域,文件作用域也称全局作用域。定义在所有函数之外的标识符,具有文件作用域,作用域为从定义处到整个源文件结束。文件中定义的全局变量和函数都具有文件作用域。 如果某个文件中说明了具有文件作用域的标识符,该文件又被另一个文件包含,则该标识符的作用域延伸到新的文件中。如cin和cout是在头文件iostream中说明的具有文件作用域的标识符,它们的作用域也延伸到嵌入iostream的文件中。,存储类型(storage class)决定标识符的存储

20、区域,即编译系统在不同区域为不同存储类型的标识符分配空间。由于存储区域不同,标识符的生命期也不同。所谓生命期,指的是标识符从获得空间到空间释放之间的期间,标识符只有在生存期中、并且在其自己的作用域中才能被访问。,4.6 存储类型与标识符的生命期,4.6.1 存储类型,4.4.2 生命期,自动变量为用auto说明的变量,通常auto缺省。局部变量都是自动变量,生命期开始于块的执行,结束于块的结束,其原因是自动变量的空间分配在栈中,块开始执行时系统自动分配空间,块执行结束时系统自动释放空间。故自动变量的生命期和作用域是一致的。,4.6.1 存储类型,为提高程序运行效率,可以将某些变量保存在寄存器中

21、,即用register说明为寄存器变量,但不提倡使用。,C+中关于存储类型的说明符(storage class specifier)有四个:auto、register、static和extern。其中用auto和register修饰的称为自动存储类型,用static修饰的称为静态存储类型,用extern修饰的称为外部存储类型。,1 自动存储类型,static说明的变量称为静态变量。根据定义的位置不同,还分为局部静态变量和全局静态变量,也称内部静态变量和外部静态变量。静态变量均存储在全局数据区,如果程序未显式给出初始化值,系统自动初始化为全0,且初始化只进行一次;静态变量占有的空间要到整个程序执

22、行结束才释放,故静态变量具有全局生命期。,4.6.1 存储类型,局部静态变量是定义在块中的静态变量,当块第一次被执行时,编译系统在全局数据区为其开辟空间并保存数据,该空间一直到整个程序结束才释放。局部静态变量具有局部作用域,但却具有全局生命期。,2 静态存储类型,【例4.10】 自动变量与局部静态变量的区别,4.6.1 存储类型,3 外部存储类型,一个C+程序可以由多个源程序文件组成。多文件程序系统可以通过外部存储类型的变量和函数来共享某些数据和操作。,在一个程序文件中定义的全局变量和函数缺省为外部的,即其作用域可以延伸到程序的其他文件中。其他文件如果要使用这个文件中定义的全局变量和函数,应该

23、在使用前用“extern”作外部声明。外部声明通常放在文件的开头(函数总是省略extern)。,外部变量声明不同于全局变量定义,变量定义时编译器为其分配存储空间,而变量声明则表示该全局变量已在其他地方定义过,编译系统不再分配存储空间。,外部的全局变量或函数加上static修饰,就成为静态全局变量或静态函数。静态的全局变量和函数作用域限制在本文件,其他文件即使使用外部声明也无法使用该全局变量或函数。,【例4.11】外部存储类型的例子,4.6.2 生命期,1. 静态生命期,静态生命期(Static extent或Static storage duration)指的是标识符从程序开始运行时就存在,具

24、有存储空间,到程序运行结束时消亡,释放存储空间。具有静态生命期的标识符存放在全局数据区,如全局变量、静态全局变量、静态局部变量。具有静态生命期的标识符在未被用户初始化的情况下,系统会自动将其初始化为0。函数驻留在代码区,也具有静态生命期。所有具有文件作用域的标识符都具有静态生命期。,4.6.2 生命期,2. 局部生命期,在函数内部或块中定义的标识符具有局部生命期(Automatic extent或Automatic storage duration),其生命期开始于执行到该函数或块的标识符定义处,结束于该函数或块的结束处。具有局部生命期的标识符存放在栈区。具有局部生命期的标识符如果未被初始化,

25、其内容是随机的,不可引用。具有局部生命期的标识符必定具有局部作用域;但反之不然,静态局部变量具有局部作用域,但却具有静态生命期。,4.6.2 生命期,具有动态生命期(dynamic extent或dynamic storage duration)的标识符存放在自由存储区,由特定的函数调用或运算来创建和释放,如用new运算符(或调用malloc()函数)为变量分配存储空间时,变量的生命期开始,而用delete运算符(或调用free()函数)释放空间或程序结束时,变量生命期结束。关于new运算符和delete运算符将在第七章中介绍。,4. 动态生命期,4.7 函数的递归调用,递归是一种描述问题的方

26、法,或称算法。递归的思想可以简单地描述为“自己调用自己”。例如用如下方法定义阶乘:,可以看出是用阶乘定义阶乘,这种自己定义自己的方法称为递归定义。,递归的引入:,递归定义的阶乘函数: fac(int n) if (n=0|n=1) return 1; else return n*fac(n-1); 只要设计主函数调用阶乘函数,即可实现计算阶乘。,4.7 函数的递归调用,【例4.12】 求4!,运行结果: 4 3 2 1 1 2 6 24 4!=24 说明: cout”n4!=”fac(4)endl; 执行时是先算函数值,然后再从左到右输出各表达式的值。 所以有两行输出,而不是第一行插在第二行赋

27、值号与24之间。,4.7 函数的递归调用,探讨: 计算是先右后左。请看下一条输出语句: cout”n4!=”fac(4) ”n3!=”fac(3)endl; 先算fac(3), 后算fac(4), 先右后左。但输出还是从左到右: 3 2 1 1 2 6 4 3 2 1 1 2 6 24 4!=24 3!=6,递归的分类:在函数调用中,有这样两种情况,一种是在函数A的定义中有调用函数A的语句,即自己调用自己;另一种是函数A的定义中出现调用函数B的语句,而函数B的定义中也出现调用函数A的语句,即相互调用。前者称直接递归,后者称间接递归。本节只介绍直接递归。递归函数必须定义递归终止条件(Stoppi

28、ng condition),避免无穷递归(Infinite Recursion)。,递归函数的执行分为“递推”和“回归”两个过程,这两个过程由递归终止条件控制,即逐层递推,直至递归终止条件,然后逐层回归。每次调用发生时都首先判断递归终止条件。递归调用同普通的函数调用一样,每当调用发生时,在栈中分配单元保存返回地址以及参数和局部变量;而与普通的函数调用不同的是,由于递推的过程是一个逐层调用的过程,因此存在一个逐层连续的参数入栈过程,直至遇到递归终止条件时,才开始回归,这时才逐层释放栈空间,返回到上一层,直至最后返回到主调函数。,4.7 函数的递归调用,递归过程的分析:,4.7 函数的递归调用,从

29、以上几例可以看出,递归算法一般不需要借助循环,但通过不断递推和回归的过程实现了其他算法用循环完成的功能。因此,递归的终止条件非常重要,否则将会无休止地递归下去,陷入死循环状态。,【例4.14】 输入一个整数,用递归算法将整数倒序输出,【例4.13】 汉诺塔问题,图4.10 递归求解斐波那契数列调用树,同其他算法相比,用递归算法编制的程序非常简洁易读,但缺点是增加了内存的开销,在递推的过程中会占用大量栈空间,且连续的调用返回操作占用较多CPU时间。因此是否选择使用递归算法取决于所解决的问题及应用的场合。,4.7 函数的递归调用,【例4.15】采用递推法求解Fibonacii数列,4.8 函数的一

30、些高级议题,4.8.1 函数重载,4.8.2 缺省参数,4.8.3 内联函数,4.8.1 函数重载,重载的引入: 在C+中,如果需要定义几个功能相似,而参数类型不同的函数,那么这样的几个函数可以使用相同的函数名,这就是函数重载。 例: 求和函数对应不同的参数类型可以定义如下几个重载函数: int sum(int a,int b) double sum(double a,double b) float sum(float a,float b,float c),4.8.1 函数重载,重载匹配:当某个函数中调用到重载函数时,编译器会根据实参的类型去对应地调用相应的函数。匹配过程按如下步骤进行: (1

31、)如果有严格匹配的函数,就调用该函数; (2)参数内部转换后如果匹配,调用该函数; (3)通过用户定义的转换寻求匹配。 因此在定义重载函数时必须保证参数类型不同,仅仅返回值类型不同是不行的。函数重载的好处在于,可以用相同的函数名来定义一组功能相同或类似的函数,程序的可读性增强。,【例4.16】 重载函数的应用,4.8.2 缺省参数,缺省参数的引入: 缺省参数指在定义函数时为形参指定缺省值(默认值)。这样的函数在调用时,对于缺省参数,可以给出实参值,也可以不给出参数值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按缺省值进行调用。,缺省参数的函数调用:缺省实参并不一定是常量表达式,

32、可以是任意表达式,甚至可以通过函数调用给出。如果缺省实参是任意表达式,则函数每次被调用时该表达式被重新求值。但表达式必须有意义;,【例4.16】 缺省参数,4.8.2 缺省参数,使用要点: 缺省参数可以有多个,但所有缺省参数必须放在参数表的右侧,即先定义所有的非缺省参数,再定义缺省参数。这是因为在函数调用时,参数自左向右逐个匹配,当实参和形参个数不一致时只有这样才不会产生二义性。,在同一个作用域中一个参数只能被指定一次缺省值,不可以在声明和定义中同时指定缺省值,即使缺省值一样也不行。 int fun2 (int, int =10, int =20); /函数声明中给出缺省值。参数名也可省略 v

33、oid fun1() int fun2(int a, int b, int c) /定义中不再给出缺省值 习惯上,缺省参数在公共头文件包含的函数声明中指定,否则缺省实参只能用于包含该函数定义的文件中的函数调用。,4.8.3 内联函数,内联函数的引入: 当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。 当函数功能简单,使用频率很高,为了提高效率,直接将函数的代码嵌入到程序中。但这个办法有缺点,一是相同代码重复书写,二是程序可读性往往没有使用函数的好。 为了协调好效率和可读性之间的矛盾,C+提供了另一种方法,即定义内联函数,方

34、法是在定义函数时用修饰词inline。,4.8.3 内联函数,请看如下程序段,读入一行字符串,逐个判断是否为数字字符: inline IsNumber(char ch) return ch=0 因使用频度很高,说明为内联函数。,4.9 头文件与多文件结构 (选读),4.9.1 头文件,标准库头文件: 考虑标识符在其他文件中的可见性。使用头文件是很有效的方法。如: #include using namespace std; 其中的iostream是在标准名字空间域std中定义的头文件。对应的传统方式的文件名为,头文件以“.h”为后缀。系统定义的头文件中定义了一些常用的公用标识符和函数,用户只要将

35、头文件包含进自己的文件,就可使头文件中定义的标识符在用户文件中变得可见,也就可以直接使用头文件中定义的标识符和函数。,4.9.1 头文件,自定义头文件: 除了系统定义的头文件外,用户还可以自定义头文件。对于具有外部存储类型的标识符,可以在其他任何一个源程序文件中经声明后引用,因此用户完全可以将一些具有外部存储类型的标识符的声明放在一个头文件中。具体地说,头文件中可以包括:用户构造的数据类型(如枚举类型),外部变量,外部函数、常量和内联函数等具有一定通用性或常用的量,而一般性的变量和函数定义不宜放在头文件中。,4.9.2 多文件结构,在开发较大程序时,通常将其分解为多个源程序文件,每个较小的程序

36、用一个源程序文件建立。程序经过建立、编译、连接,成为一个完整的可执行程序。多文件结构通过工程进行管理,在工程中建立若干用户定义的头文件.h和源程序文件.cpp。头文件中定义用户自定义的数据类型,所有的程序实现则放在不同的源程序文件中。编译时每个源程序文件单独编译,如果源程序文件中有编译预处理指令,则首先经过编译预处理生成临时文件存放在内存,之后对临时文件进行编译生成目标文件.obj,编译后临时文件撤销。所有的目标文件经连接器连接最终生成一个完整的可执行文件.exe。图4.11是一个多文件系统的开发过程。,4.9.2 多文件结构,编译,预编译,编译,预编译,预编译,编译,图4.11 C+程序开发

37、过程,4.10 编译预处理(选读),4.10.1 宏定义指令,4.10.2 文件包含指令,4.10.3 条件编译指令,4.10.1 宏定义指令,1 不带参宏定义 用来产生与一个字符串对应的常量字符串,格式为: #define 宏名 常量串 预处理后文件中凡出现该字符串处均用其对应的常量串代替。替换过程称为宏替换或宏展开。例如,如果使用指令 #define PI 4.1415926 则程序中可以使用标识符PI,编译预处理后产生一个中间文件,文件中所有PI被替换为4.1415926。 宏替换只是字符串和标识符之间的简单替换,预处理本身不做任何数据类型和合法性检查,也不分配内存单元。,4.10.1

38、宏定义指令,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,不符合定义的真正的意图。 带参宏定义形式上象定义函数,但它与函数的本质不同,宏定义仍然只是产生字符串替代,不存在分配内存和参数传

39、递。,4.10.2 文件包含指令,文件包含用#include指令,预处理后将指令中指明的源程序文件嵌入到当前源程序文件的指令位置处。格式为: #include 或 #include 文件名 第一种方式称为标准方式,预处理器将在include子目录下搜索由文件名所指明的文件。这种方式适用于嵌入C+提供的头文件,因为这些头文件一般都存在C+系统目录的include子目录下。而第二种方式编译器将首先在当前文件所在目录下搜索,如果找不到再按标准方式搜索。这种方式适用于嵌入用户自己建立的头文件。,一个被包含的头文件中还可以有#include指令,即include指令可以嵌套,但是,如果同一个头文件在同一

40、个源程序文件中被重复包含,就会出现标识符重复定义的错误。例如:头文件f2.h中包含了f1.h,如果文件f4.cpp中既包含f1.h,又包含f2.h,那么编译将提示错误,原因是f1.h被包含了两次,那么其中定义的标识符在f4.cpp中就被重复定义。避免重复包含可以用条件编译指令。,4.10.2 文件包含指令,4.10.3 条件编译指令,1 用宏名作为编译的条件 格式为: #ifdef#else #endif,2 表达式的值作为编译条件 格式为: #if #else #endif,当希望在不同条件下编译程序的不同部分。这种情况就要使用条件编译指令。,其中程序段可以是程序也可以是编译预处理指令。可以

41、通过在该指令前面安排宏定义来控制编译不同的程序段。,例:在调试程序时常常要输出调试信息,而调试完后不需要输出这些信息,则可以把输出调试信息的语句用条件编译指令括起来。形式如下: #ifdef DEBUG couta=atx=xendl; #endif 在程序调试期间,在该条件编译指令前增加宏定义: #define DEBUG 调试好后,删除DEBUG宏定义,将源程序重新编译一次。,条件编译指令包括:#if、#else、#ifdef、#ifndef、#endif、#undef等。 #ifndef与#ifdef作用一样,只是选择的条件相反。 #undef指令用来取消#define指令所定义的符号,

42、这样可以根据需要打开和关闭符号。,第四章 函数,再见,谢谢!,4.1.3 函数的调用【例4.1】,main( )函数,调用max(2.5,4.7 ),函数max(2.5,4.7 ),return 4.7,主程序后续语句,【例4.1】 输入两个实数,输出其中较大的数。其中求两个实数中的较大数用函数完成。 程序如下: #include using namespace std; float max(float a,float b)return(a=b?a:b); int main()float x,y;coutxy;coutx“和“y“中较大数为“max(x,y)endl;return 0; ,4.

43、2.1 函数的参数传递及传值调用【例4.2】,调用power(4.6,3 ),函数power(4.6,3 ),return97.336,主程序后续语句,【例4.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; cout“power(“x,n“)=“power(x,n)endl; cout“power(“c,n“)=“power(c,n)endl; cout“power(“n,x“)=

44、“power(n,x)endl; return 0;,4.2.1 函数的参数传递及传值调用【例4.2】,调用power(a,3 ),函数power(97,3 ),return912673,主程序后续语句,【例4.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; cout“power(“x,n“)=“power(x,n)endl; cout“power(“c,n“)=“power(c,n)e

45、ndl; cout“power(“n,x“)=“power(n,x)endl; return 0;,4.2.1 函数的参数传递及传值调用【例4.2】,调用power(3,4.6 ),函数power(3,4),return81,主程序后续语句,【例4.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; cout“power (“x,n“)=“power(x,n)endl; cout“power

46、 (“c,n“)=“power(c,n)endl; cout“power (“n,x“)=“power(n,x)endl; return 0;,【例4.3】程序: float TriangleArea(float a, float b, float c)if (a+babc;area=TriangleArea(a,b,c);if(area=-1) cout(a,b, c)“不能构成三角形!“endl;else cout“三角形(“a,b,c“)面积为:“areaendl;return 0;,bool palindrome(int); /函数声明 int main()int m;coutsetw

47、(10)msetw(20)“m*m“setw(20)“m*m*m“endl;for(m=11;m1000;m+)if(palindrome(m),【例4.4】 输出回文数,bool palindrome(int n) /判断回文数int digit10;int m=n,i=0;dodigiti=n%10; n/=10;i+;while(n0);for(intj=0;ji;j+) n=n*10+digitj;return (n=m);,【例4.4】 输出回文数,4.4.2 全局变量【例4.5】,打印 200,调用 func( ),函数 func( ),200*2 =400,打印 400,n=10

48、0,n=100*2 =200,【例4.5】 多个函数使用全局变量的例子。int n=100; void func()n*=2; int main()n*=2;coutnendl;func();coutnendl; return 0;,4.4.3 局部变量,打印main()中的t=4.5,调用 fun( ),函数 fun( ),打印fun()中的t=5,打印main()中的t=4.5,t = 5,【例4.6】 使用局部变量的例子。void fun() auto int t=5; / fun()中的局部变量,auto可省略 cout“fun()中的t=“tendl; int main() float t=4.5; /main()函数中的局部变量 cout“main()中的t=“tendl; fun(); cout“main()中的t=“tendl; return 0;,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 中等教育 > 小学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报