《C语言程序设计教程(修订本)第5章模块设计.ppt》由会员分享,可在线阅读,更多相关《C语言程序设计教程(修订本)第5章模块设计.ppt(39页珍藏版)》请在三一办公上搜索。
1、第5章 模块设计,5.1 模块的实现函数5.2 模块间的参数传递5.3 模块的递归调用5.4 程序举例5.5 编译预处理,5.1 模块的实现函数,5.1.1 函数的概念 在C语言中,函数分为以下两种。(1)标准库函数 这种函数用户不必定义,但可直接使用。例如scanf(),printf(),fabs(),sqrt(),exp(),sin(),cos()等都是C语言中常用的库函数。(2)用户自己定义的函数 这种函数用以解决用户的专门问题,一般由用户自己编写。,返回目录,例5.1 从键盘输入两个正整数m与n(mn),求 的值(即求)。其C程序如下:#include stdio.hmain()/*主
2、函数*/int m,n;int p();/*说明本函数中要调用的函数p()是整型*/scanf(%d,%d,&m,&n);if(mn)printf(%dn,p(m)/p(mn);else printf(mn!n);int p(k)/*计算阶乘值的函数*/int k;int s,i;s1;for(i1;ik;ii1)ss*i;return(s);,下面对函数作几点说明:(1)一个完整的C程序可以由若干个函数组成,其中必须有一个且只能有一个主函数main()。(2)一个完整C程序中的所有函数可以放在一个文件中,也可以放在多个文件中。在编译命令行中键入各个函数所在的文件名(各文件名之间用空格分隔)。
3、在主函数中用#include 语句将各函数所在的文件包含进来。(3)C语言中的函数没有从属关系,各函数之间互相独立,可以互相调用。,5.1.2 函数的定义 在C语言中,函数定义的一般形式为 类型标识符 函数名(形参表列)形参类型说明 说明部分 语句部分,在定义C函数时要注意以下几点。(1)函数类型标识符同变量类型说明符,它表示返回的函数值类型。(2)如果省略函数的类型标识符,则默认为是int型。(3)C语言允许定义空函数。如dummy(),(4)函数中返回语句的形式为return(表达式);或return 表达式;(5)如果“形参表列”中有多个形式参数,则它们之间要用“,”分隔。(6)C语言允
4、许在形参表中直接对形参的类型进行说明。,5.1.3 函数的调用函数调用的一般形式为函数名(实参表列)(1)函数调用可以出现在表达式中(有函数值返回);也可以单独作为一个语句(无函数值返回)。(2)实参表中的各实参可以是表达式,但它们的类型和个数应与函数中的形参一一对应。(3)在调用函数中,通常要对被调用函数的返回值类型进行说明(一般在调用函数的函数体中的说明部分),包括函数类型、函数名和一对圆括号。,例5.2 下列程序的功能是计算输出一个圆台两底面积之和。#include stdio.hmain()double r1,r2;double q();printf(input r1,r2:);sca
5、nf(%lf,%lf,&r1,&r2);printf(s%fn,q(r1,r2);double q(x,y)double x,y;double s;s3.1415926*(x*xy*y);return(s);,但C语言规定,在以下几种情况下可以不在调用函数中对被调用函数作类型说明:被调用函数为整型或字符型,自动按整型处理。被调用函数的定义出现在调用函数之前。在调用函数之前已经由别的函数(它可能也要调用该被调用函数)对被调用函数作了类型说明。(4)C语言虽不允许嵌套定义函数,但可以嵌套调用函数。,5.2 模块间的参数传递,5.2.1 形参与实参的结合方式1地址结合 所谓地址结合,是指在一个模块调
6、用另一个模块时,并不是将调用模块中的实参值直接传送给被调用模块中的形参,而只是将存放实参的地址传送给形参。2数值结合 所谓数值结合,是指调用模块中的实参地址与被调用模块中的形参地址是互相独立的,在一个模块调用另一个模块时,直接将实参值传送给形参被存放在形参地址中。,返回目录,例5.3 分析下列C程序:void swap(x,y)int x,y;int t;tx;xy;yt;return;#include stdio.hmain()int x,y;scanf(x%d,y%d,&x,&y);swap(x,y);printf(x%d,y%dn,x,y);,在这个程序中共有两个函数。在主函数中分别为整
7、型变量x与y输入数据,然后调用函数swap(x,y)。而函数swap(x,y)的功能是实现变量x值与y值的交换。但在实际运行该程序时,如果从键盘输入x6,y8则输出的结果为x6,y8 即没有达到交换的目的。这是因为在主函数中调用函数swap()时,只是将实参x和y的值分别传递给了swap()函数中的形参x和y,但由于主函数中的实参x和y与函数swap()中的形参x和y在计算机中的存储地址是不同的,因此,在函数swap()中虽然交换了形参x与y的值,但实参x与y的值实际上没有改变,即它们没有被交换。,由此可以看出,在形参与实参为数值结合的情况下,实参与形参在计算机内存中的存储地址不是同一个,因此
8、,即使在被调用函数中改变了形参值,调用函数中的实参值也是不会被改变的。,5.2.2 局部变量与全局变量1局部变量 在函数内部定义的变量称为局部变量。函数内部定义的变量只在该函数范围内有效,因此,不同函数中的局部变量可以重名,互不混淆。2全局变量 在函数外定义的变量称为全局变量。,除非十分必要,一般不提倡使用全局变量,其原因有以下几点:由于全局变量属于程序中的所有函数,因此,在程序的执行过程中,全局变量都需要占用存储空间,即使实际正在执行的函数中根本用不着这些全局变量,它们也要占用存储空间。在函数中使用全局变量后,要求在所有调用该函数的调用程序中都要使用这些全局变量,从而会降低函数的通用性。在函
9、数中使用全局变量后,使各函数模块之间的互相影响比较大,从而使函数模块的“内聚性”差,而与其他模块的“耦合性”强。在函数中使用全局变量后,会降低程序的清晰性,可读性差。,5.2.3 动态存储变量与静态存储变量1用户程序的存储分配一般来说,用户程序在计算机中的存储分配如图5.1所示。,图5.1 用户程序在计算机中的存储分配,其中:程序区用于存放程序;静态存储区是在程序开始执行时就分配的固定存储单元,如全局变量;动态存储区是在函数调用过程中进行动态分配的存储单元,如函数形参、自动变量、函数调用时的现场保护和返回地址等。,2变量的存储类型 数据类型:如整型(int)、实型(float)、字符型(cha
10、r)、双精度型(double)等。数据的存储类型:分为自动类型(auto)、静态类型(static)、寄存器类型(register)、外部类型(extern)。,例5.4 计算并输出15的阶乘值。其C程序如下:int fac(n)int n;static int f1;ff*n;return(f);#include stdio.hmain()int i;for(i1;i5;ii1)printf(%d!%dn,i,fac(i);,下面对静态存储变量作几点说明:形参不能定义成静态类型。对局部静态变量赋初值是在编译时进行的,在调用时不再赋初值;而对自动变量赋初值是在调用时进行的,每次调用将重新赋初值
11、。定义局部静态变量时若不赋初值,则在编译时将自动赋初值0;但在定义自动变量时若不赋初值,则其初值为随机值。若无多大必要,尽量不用局部静态变量。,3外部变量 全局变量如果在文件开头定义,则在整个文件范围内的所有函数都可以使用该变量。一般来说,全局变量有以下几种用法:在同一文件中,为了使全局变量定义点之前的函数中也能使用该全局变量,则应在函数中用extern加以说明。使一个文件中的函数也能用另一个文件中的全局变量。利用静态外部变量,使全局变量只能被本文件中的函数引用。,5.2.4 内部函数与外部函数 在C语言中,函数可以分为内部函数与外部函数。只能被本文件中其他函数调用的函数称为内部函数,内部函数
12、又称为静态函数。定义内部函数的形式如下:static 类型标识符 函数名(形参表)定义外部函数的形式如下:extern 类型标识符 函数名(形参表),5.3 模块的递归调用,人们在解决一些复杂问题时,为了降低问题的复杂程度(如问题的规模等),一般总是将问题逐层分解,最后归结为一些最简单的问题。,返回目录,例5.5 编写一个C函数,对于输入的参数n,依次打印输出自然数1到n。这是一个很简单的问题,实际上不用递归就能解决,其C函数如下:#include stdio.hwrt(int n)int k;for(k1;kn;k)printf(%dn,k);return;,解决这个问题还可以用以下的递归函
13、数来实现:#include stdio.hwrt1(int n)if(n!0)wrt1(n1);printf(%dn,n);return;,自己调用自己的过程称为递归调用过程。在C语言中,自己调用自己的函数称为递归函数。递归分为直接递归与间接递归两种。所谓直接递归,是指直接调用函数本身。,5.4 程序举例,例5.6 编写一个函数,其功能是判断给定的正整数是否是素数,若是素数则返回函数值1,否则返回函数值0。其C函数如下:#include math.hsushu(int n)int k,i,flag;ksqrt(double)n);i2;flag0;while(ik)&(flag0)if(n%i
14、0)flag1;ii1;return(!flag);,返回目录,例5.8 Hanoi塔问题。相传古代印度有一座Bramah庙,庙中有三根插在黄铜板上的宝石柱,在其中的一根柱子上放了64个金盘子,大盘在下,小盘在上,称为Hanoi塔。有一天,庙里的和尚们想把这些盘子从一根柱子上移到另一根柱子上,规定每次只允许移动一个盘子,并且,在移动过程中都不允许出现大盘子压在小盘子上面的现象,但在移动盘子的过程中可以利用三根柱子中的任何一根。为了使问题具有普遍性,假设圆盘数为n,按直径从小到大依次编号为1,2,n;三根柱子的名称分别为X,Y,Z。开始时,n个圆盘按从大到小的顺序(即下面放大圆盘,上面放小圆盘)
15、放在X柱子上,现在要将X柱子上的n个圆盘移到Z柱子上,其移动的原则如上所述。这个问题称为n阶Hanoi塔问题。,可以写出C程序如下:#include stdio.hmain()int n;char xX,yY,zZ;void hanoi();printf(input n);scanf(%d,&n);hanoi(n,x,y,z);void hanoi(n,x,y,z)int n;char x,y,z;void move();,if(n1)move(x,n,z);else hanoi(n1,x,z,y);move(x,n,z);hanoi(n1,y,x,z);return;void move(x,
16、n,z)int n;char x,z;printf(%c(%d)%cn,x,n,z);,5.5 编译预处理,编译预处理功能是C语言的一个重要特点。所谓编译预处理,是指C语言编译系统首先对程序模块中的编译预处理命令进行处理。C语言提供的编译预处理命令主要有以下3种:(1)宏定义;(2)文件包含命令;(3)条件编译命令。编译预处理命令一般是在函数体的外面。正确使用编译预处理命令,可以编写出易于调试、易于移植的程序模块。,返回目录,5.5.1 文件包含命令 文件包含是指一个源文件可以将另一个指定的源文件包括进来。文件包含命令的一般形式为#include 文件名或#include 文件名 其功能是将指
17、定文件中的全部内容读到该命令所在的位置后一起被编译。,在使用文件包含命令时,要注意以下几个问题:(1)当#include命令指定的文件中的内容改变时,包含这个文件的所有源文件都应该重新进行编译处理;(2)一个#include命令只能指定一个被包含文件,如果需要包含多个文件,则要用多个#include命令实现;(3)被包含的文件应该是源文件,不能是经编译后的目标文件;(4)文件包含可以嵌套使用,即被包含的文件中还可以使用#include命令;(5)由#include命令所指定的文件中可以有任何语言成分,因此,通常可以将经常使用的、具有公用性质的符号常量、带参数的宏定义以及外部变量等集中起来放在这
18、种文件中,以尽量避免一些重复操作。,5.5.2 条件编译命令 C语言的编译预处理程序提供了条件编译能力,以便使同一个源程序在不同的编译条件下能够产生不同的目标代码文件。1#ifdef,#else,#endif其一般形式为#ifdef 标识符 程序段1#else 程序段2#endif 其作用是,如果“标识符”已经定义过(一般是指用#define命令定义),则程序段1参加编译,而程序段2不参加编译;否则程序段2参加编译,而程序段1不参加编译。,2#ifndef,#else,#endif其一般形式为#ifndef 标识符 程序段1#else 程序段2#endif 其作用是,如果标识符没有定义过,则程序段1参加编译,而程序段2不参加编译;否则程序段2参加编译,而程序段1不参加编译。,3#if,#else,#endif其一般形式为#if 常量表达式 程序段1#else 程序段2#endif 其作用是,如果常量表达式的值为“真”(值非0),则程序段1参加编译,而程序段2不参加编译;否则程序段2参加编译,而程序段1不参加编译。,4#undef其一般形式为#undef 标识符其作用是,将已经定义的标识符变为未定义。,