用函数合理组织程序.ppt_第1页
用函数合理组织程序.ppt_第2页
用函数合理组织程序.ppt_第3页
用函数合理组织程序.ppt_第4页
用函数合理组织程序.ppt_第5页
免费预览已结束,剩余26页可下载查看

下载本文档

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

文档简介

1、6.1 模块化带来的好处,从软件工程的角度上说,降低程序复杂性的有效方法是合理的模块化和局部化。在设计一个复杂的程序时,往往把整个程序划分为若干个功能较为单一的模块,分别予以实现,再把所有的模块像搭积木一样搭起来,这种在程序设计中分而治之的策略,被称为模块化程序设计方法。在C+中,这些模块就是一个个的函数,函数也是C+构造程序的重要的基本单位。 语句是构成程序的最基本单位,函数也是由语句构成的,从程序设计的角度上说,函数(一系列语句)常被当成一个整体来看,这就降低了程序的复杂度。,6.1.1 函数的调用过程,在前面的学习中大家已经熟悉了main()函数,一个C+程序里包含一个主函数(即main

2、函数)和若干个其它函数,main()处于最顶层,其他函数作为其下层模块,main()函数调用其它函数,其它函数之间也可以互相调用。图6.1为一个C+程序的模块结构图,程序的执行过程为ABCDEFGHIJKMN。 任何一个C+程序都是从main()函数开始执行,而且是只执行main()函数,从main()的前花括号开始,到main()的后花括号为止,在此过程中,如果碰到函数调用语句(如中的“函数1;”),便暂时中断main()的执行,将程序流程转到被调用函数(对应中的B),执行完被调用函数或遇到return语句则返回main()函数(对应中的H),继续执行,一个函数也可以调用其他函数(如中函数1

3、调用函数3),这时的调用过程与main()函数调用其他函数过程类似,常称为函数嵌套。,6.1.2 抽象和封装,面向过程的程序设计是基于功能分析的,最关心的是如何实现一个模块的功能以及如何使用这个模块,至于模块内部的结构,对其他模块来说是不重要的,完全可隐藏的。对函数而言,这个道理同样适用,在第2章主函数小节中提到了函数由函数头和函数体两部分组成,而函数头定义了函数和调用它的函数之间的接口。在C+程序中,函数可以看成一个封装体,将一系列相关的、实现某一功能的代码封装起来,并提供了一个使用方法(程序中常称接口),通过该接口可以在程序的任何地方使用这些代码完成特定功能,至于函数是如何编写的,可能并不

4、是用户关心的重点,用户真正关心的是这个函数如何使用。,6.1.3 实现一个函数,实现一个函数有3个步骤:定义、声明与调用,拿电影来做比喻,定义等价于电影的拍摄,声明等价于电影院得到放映许可,调用是电影院放电影,电影院可以自行拍摄,也可以拿别的单位拍的电影来放,在程序中,这意味着可以自己定义函数,也可以使用诸如标准库或第三方库提供的函数,但在使用前,都要进行声明,通知编译器函数的存在,以获得函数的使用许可,才能进行调用,声明后,程序可以多次调用函数,等同于电影院在获得放映许可后,可以多次放映影片。见示例代码61:,6.2 函数定义,函数定义由函数头和函数体两部分组成,其基本形式为: 返回类型 函

5、数名(参数列表) 函数体 函数定义通过这一结构告诉编译器要进行的操作。 最小的函数 - 无返回值、无参数 void MyFunction1(void) ,6.2.1 函数头,第一行“返回值类型 函数名(参数列表)”称为函数头,定义了函数和调用它的函数之间的接口: (1)函数名 上级函数通过函数名实现对函数的调用,函数名是一个符合C+语法要求的标识符,定义函数名与定义变量名的规则是一样的,但应尽量避免用下划线开头,因为编译器常常定义一些下划线开头的变量或函数。函数名应尽可能反映函数的功能,做到“望文知义”。 (2)参数列表 0个或多个变量,用于向函数传送数值或从函数带回数值,每个参数都应采取“类

6、型 变量名”形式,参数列表中的参数称为形式参数,简称形参。编译器并不会在函数定义时为这些参数分配内存空间,只有在函数调用时,向函数传递了实参后,这些参数才称为程序实体,形参相当于剧本中的角色,而实参是演员,在中,函数定义中的x和y是剧本角色,而变量num1和num2是演员,num1扮演了x的角色,num2扮演了y的角色。如果参数表列中参数个数为0,我们称之为无参函数,无参函数可以定义为: 返回类型 函数名( ) 或 返回类型 函数名(void) (3)返回类型 指定函数用return返回的函数值的类型,如果函数没有返回值,返回类型应为void。C+对返回值的类型有一定限制,不能是数组,但可以是

7、其他任何类型,如整型、浮点型、指针,甚至是结构和共用体等。,6.2.2 函数体,花括号中的语句称为函数体,一个函数的功能,通过函数体中的语句来完成,函数体指明了函数要进行的操作及操作顺序。 程序执行到函数体中的return语句返回,在函数体中可以有多个return语句,但函数只能有一个出口,换句话说,只执行一条返回语句,返回语句的基本形式为: return 表达式; 表达式的类型应当与函数头中指定的返回类型一致,否则,编译器会根据函数头中指定的返回类型对表达式进行转换。 返回主要起如下作用: (1)撤销函数调用时为参数和变量分配的栈内存空间; (2)向调用函数(上级)返回最多一个值(表达式的值

8、); (3)将程序流程从当前函数返回上级函数。 代码6.1中,add函数的定义如下: int add (int x, int y) int z = x + y; return z; ,6.2.3 函数定义补充说明,当函数的返回类型是void时,表明函数不向上级函数返回任何值,这时可以用一个空的“return;”语句,将程序流程返回,撤销函数调用时为参数和变量分配的栈内存空间,空的“return;”语句位于函数末尾时,该语句可以省略,用函数体的后花括号实现函数的返回即可。 通常用返回类型为void的函数执行某些操作,见代码62 。 int main() void print();/函数声明 pr

9、int();/函数调用 return 0; void print()/函数定义,void表示没有返回值 int n; coutn; cout你输入的数是:nendl; ,6.3 函数声明,函数声明,也称函数原型。函数声明,用以通知编译器函数的存在,以获得函数的使用许可,惟其如此,才能在程序中对函数进行调用。,6.3.1 为什么要进行函数声明,函数声明描述了函数和编译器间的接口,想要调用一个函数,必须在调用函数中必须对被调用函数进行说明。 在代码61中,main()函数中的“int add(int x,int y);”用于在main()函数内声明add函数,使其在main()函数内可用,同时告诉

10、编译器,add函数接收两个int型的输入参数,如果程序没有提供这样的参数,编译器便会指出错误,或对传入的其他类型参数进行隐式转换,在add函数完成计算后,将把返回值放置到指定位置(可能是CPU寄存器,也可能是某个内存单元),然后上级函数(代码61中为main()函数)从这个位置取得返回值,add函数的声明指出了返回值类型为int,编译器借此知道应检索多少内存字节并对这些字节作出解释。图6.2形象化地说明了代码61中的函数声明的作用和返回值机制。,6.3.2 如何声明一个函数,函数声明类似于函数定义,不过没有实现代码,函数说明的一般形式如下: 返回类型 函数名(参数表列); 函数声明是一个语句,

11、所以要以分号结束,在书写函数声明时,只要把函数头复制下来,并在末尾添加分号即可。 语句结尾处有无分号常常可用来区分是函数声明还是函数定义。 如代码6.1中的“int add(int x, int y);”,不过,函数声明只要与函数定义一致,能提供给编译器足够的信息即可,因此,C+中的函数声明不要求提供变量名,add函数的声明可以写成: int add(int, int); 从中编译器得知:函数名为add,接收两个int型的参数,返回值类型为int,这些信息已经足够,“int add(int x,int y);”中的变量名x和y仅仅起到增强程序可读性的作用,而且,这个变量名也可以与函数定义中的形

12、参不同,也就是说,将声明语句写成下列形式丝毫不会影响程序的编译和运行。 int add(int A, int B);,6.3.3 分割程序文件,一些程序常常由许多文件组成,为了方便管理,常常将函数定义在cpp文件中,而将函数的声明语句放在与cpp文件同名的头文件(h文件)中,这样就可以通过编译预处理 #include 或#include “xxx.h” 实现函数的声明,这种方法在大型程序文件的组织中十分有用,见代码6.3。 #include 用于C+系统提供的头文件,这些头文件一般位于C+系统目录下的include子目录下,而 #include “xxx.h” 适用于用户自己建立的头文件,预处

13、理器接收到该指令后,会首先在当前文件所在目录中进行搜寻,如果找不到,再到C+系统头文件中寻找。,6.4 函数调用,在前面的代码中,读者已经知道了如何调用一个函数,函数定义和函数声明的目的都是为了函数调用,唯有函数调用才是利用函数实现某个功能的过程,函数调用的基本形式为: 函数名(实参列表); 对于无参函数,其调用形式为: 函数名(); 函数调用由函数名和函数调用运算符()组成,()内有0个或多个逗号分隔的参数(称为实参)。每一个参数是一个表达式,且参数的个数与参数的类型要与被调函数定义的参数(称为形参)个数和类型匹配,首先计算参数表达式的值,并将此值传递给形参。如果函数调用后有返回值,调用表达

14、式可以用在表达式中,如中的 int numTotal=add(num1,num2);,而无参函数的调用必须是一个单独的语句,如 print();。 函数调用的主要作用是: (1)用实参向形参传递数据; (2)为获得数据的参数和函数中声明的变量分配临时存储空间; (3)中断当前正在运行的上级调用函数,将程序流程转到被调用函数的入口处。,6.4.1 形参和实参,形参和实参,类似与剧本角色和演员的关系,同一个角色可以由不同的演员来扮演,只有在演员扮演的过程中,角色才是鲜活、有意义的。前面的章节已经提到,在函数定义时,并不会为参数列表中的参数分配内存空间,只有在函数调用时,才为形参分配内存空间,并用实

15、参的值为其赋值,在执行到函数结束时,程序会撤销调用过程中为参数和中间变量分配的内存空间。代码64演示了形参和实参的关系: int main() int n = 2; cout 地址: 程序运行结果:地址:0013FF7C 数值:2 地址:0013FF2C 数值:2(注意:print函数中的参数n和main函数中的参数n占据不同的内存地址),6.4.2 参数类型转换,调用函数时,如果实参类型与形参类型不匹配,编译器会对实参自动进行类型转换(隐式转换),形参的类型,取决于函数的声明语句,见代码65。 int main() double m = 12;/声明一个double型变量m void pri

16、nt(int);/函数声明 print(m);/函数调用时,实参m隐式转换为int型传递给形参 return 0; void print(int n)/函数定义 cout数值:nendl; 程序运行结果:数值:2,6.4.3 值传递,很多C+教科书在讲述函数传值调用时都会举下面这个例子,见代码66 。 int main() void change(int, int);/函数声明 int x = 2, y = 3; cout 交换前:x= x ,y= y endl; change(x, y);/传值调用 cout 交换后:x= x ,y= y endl; return 0; void chang

17、e(int n, int m)/函数定义 int temp; temp = n; n = m; m = temp; 输出结果: 交换前:x = 2, y = 3; 交换后:x = 2, y = 3;,6.4.4 指针传递,要想被调函数改变调用函数中的变量值,应使函数中的操作直接作用在调用函数的变量上,要达到目的,一个有效途径是使用指针传递,先看代码67 。 int main() void change(int *, int *);/函数声明,形参为指针类型 int x = 2, y = 3; cout 交换前:x= x ,y= y endl; change( 输出结果: 交换前:x = 2,

18、y = 3; 交换后:x = 3, y = 2;,6.4.5 引用传递,在第4章中已经提及,对变量的引用相当于变量的别名,以“int ,6.4.6 对3种传递的补充(函数返回值),3种传递方式不仅仅是参数传递,函数返回某个值也有值传递、指针传递和引用传递3种方式。 代码6.9是使用范例。 返回值为指针和引用类型时,要注意函数内定义或声明的变量为自动(临时)变量,这些变量在函数结束后会被自动撤销,其占用的内存也会被系统收回;也就是说此时返回的指针和引用都是无效的。例如: int * minus(int m, int n) int k = 0;int * z = 函数执行完毕后,指针z和函数中的变

19、量被撤销,可能导致程序崩溃。,6.4.7 缺省参数调用,缺省参数调用的基本思想是在声明语句中预先初始化一些参数的值,在调用语句中相应的参数可以缺省。 缺省参数调用的规则 (1)定义时,缺省参数必须按照从右向左顺序; (2)调用时,如某一参数缺省,其后的参数也必须缺省。,6.4.8 inline函数,函数的引入可以减少程序的代码量,使得函数程序可以在代码间共享。但函数调用需要一些时空开销,在调用函数时,要中断调用函数,将执行流程转移到被调函数中,待被调函数执行完毕后,返回调用函数。因此,在调用函数前,应保护被调函数现场,记录程序当前位置,以方便从被调函数中返回,恢复现场,并从原来的位置处继续执行

20、。 对于较长的函数,这种开销可以忽略不计,但对于一些很短的、频繁调用的函数体,这种开销就不能不能忽视。举例来说,代码6.3中的print函数定义如下: void print(int x) coutxendl; ,6.5 递归(略),除了main()函数外,C+函数可以调用自身,这称为递归,是一种通用的编程技术,可以有效降低问题的复杂度,为解决某些问题提供了极大的方便,首先来看一个用以求阶乘的函数,对于一个整数n,其阶乘n!定义为: n!=n*(n-1)*(n-2)*3*2*1 0的阶乘为1,读者可以从阶乘定义中总结出以下规律,对n0而言: n!=n*(n-1)! 直观上,可以用循环语句计算n的

21、阶乘,如下列代码所示: int Calc(int n) int res=1; for(int i=1; i=n; i+) res*=I; return res; ,6.6 函数的重载,自然语言中,一个词可以有不同的含义,即该词被重载了,人们可以根据上下文判断该词的意义,在C+程序,可以将语义、功能相似的几个函数用同一个名字来表示,这样便于记忆,提高了函数的易用性,称为函数重载。函数重载的示例见代码613 。,6.6.1 何时使用函数重载,将不相关的函数都用同样的名字来命名不是个明智的选择,将降低程序的可读性,使程序难以理解,因此,函数重载必须用在合适的场合。 函数名相同,最重要的一点是函数要完

22、成的任务一样(至少是类似),比如,都用来输出信息,或都用来和某个硬件打交道等,避免函数名字相同,但功能完全不同的情形。其次,形式参数的类型应不同,对于形参类型相同,只有形参个数不同的场合,就无需定义两个函数,采用前面提及的缺省参数调用机制,只要定义一个函数即可。 不过,如果形参类型不同,缺省参数调用便不再适用,此时,应使用函数重载。,6.6.2 如何实现函数重载,当调用多个同名的重载函数时,要求能够唯一地确定应执行哪一个函数,这是通过函数参数的个数和类型来区分的。所以,重载的函数要求参数个数或者参数类型上不同,否则会出现编译错误。 C+中不允许几个函数名相同、形参个数和类型也相同,仅仅是返回值

23、不同的情形,否则,程序编译时会出现函数重复定义的错误,此时,编译器无法根据返回值不同决定具体要调用的函数。,6.6.3 陷阱:隐式转换导致重载函数出现二义性,调用重载的函数时,如果实参类型与形参类型不匹配,编译器会对实参自动进行类型转换。如果转换后仍然不能匹配到重载的函数,则会产生一个编译错误,见代码614。,6.7 C+如何使用内存,C+有3种管理数据内存的方式:自动存储(栈存储)、静态存储和动态存储(堆存储),不同的方式下,内存的分配形式,存在时间的长短都不同,其中,动态存储方式已经在第4章中进行了介绍,下面对自动存储和静态存储进行说明。,6.7.1 自动存储(栈存储),对于在函数的形参、

24、内部声明的变量及结构变量等,编译器将在函数执行时为形参自动分配存储空间,在执行到变量和结构变量等的声明语句时为其自动分配存储空间,因此称其为自动变量(automatic variable),有的教科书也称其为局部变量,在函数执行完毕返回时,这些变量将被撤销,对应的内存空间将被释放。 事实上,自动变量的生存期只局限于它所在的代码块。所谓代码块,是包含在花括号对中的一段代码,函数只是代码块的一种,比较下面两段代码:,6.7.2 静态存储(编译器预分配),每个C+程序对应着一块静态数据区(也称全局数据区)。在源程序编译时,编译器就为某些程序实体(某些变量及所有的常量)预分配存储地址和内存空间,程序一开始执行,这些程序实体就被创建,一直到程序结束才被撤销,并释放对应的内存空间,因此称其为“永久存储”。静态存储的程序实体的数目在程序运行过程中是不变的,因此无须使用特殊的机制(如堆、栈)来管理它们,编译器将分配固定的内存块来存储所有的静态存储实体。 常量已经在第2章进行了详细的介绍,下面对静态存储的变量进行讨论,根据用法不同,静态存储的变量可分为“全局变量”和“静态变量”。,6.8 作用域与可见域,在函数一节,讨论形参变量时曾经提到:形参变

温馨提示

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

评论

0/150

提交评论