C程序的模块化设计ppt课件.ppt
第7章 C程序的模块化设计,本章要点,C程序的模块化结构 内部变量、外部变量、静态(static)变量 变量的存储属性 动态存储分配和释放编译预处理,7.1 C程序的模块化结构,7.3 变量和函数的存储属性,7.4 动态存储分配和释放,第7章 C程序的模块化设计,7.2 内部变量、外部变量和作用域、生存期,7.7 本章小结,7.5 编译预处理,7.6 综合应用举例,7.1 C程序的模块化结构,模块化程序设计方法的思想:,将一个较大的程序按功能分割成一些模块,每个模块都是功能单一、结构清晰、接口简单、容易理解的小任务。对每一个处于底层的小任务编写相应的实现函数,然后通过函数调用的方法将多个这样的函数按功能组合成一个较高层的调用函数,多个这样的调用函数又可成为被调函数,并按功能组织成更高层次的调用函数。,7.1 C程序的模块化结构,模块化程序设计的基本方法:自顶向下(或自底向上)和逐步求精。,C语言的开发工具提供了程序工程开发的能力。工程由多个源程序文件组成。每个源程序文件可以单独编译;然后再连接成一个大的可执行文件。每个源程序文件可以包含一个或多个函数。一个工程只能有一个main主函数。,7.1 C程序的模块化结构,7.1.1 概述,7.1.2 多源文件程序的结构,7.1.3 分割编译,7.1 C程序的模块化结构,7.1.1 概述,函数是C程序的基本组成单位。C编程就是在main函数中给一些函数安排一个执行顺序,而在这些函数中,又安排了另一些函数的执行顺序。,7.1 C程序的模块化结构,7.1.1 概述,main()f1();f2();,f1()g1(),f2()h1();h2();k1(),g1()k1(),h1(),h2(),k1(),图7.1由若干个函数组成的一个程序,1.1 算法、C语言和程序设计,7.1.1 概述,如何组织这些函数?,问题:,1.1 算法、C语言和程序设计,7.1.2 多源文件程序的结构,当一个程序由许多函数构成时,如果所有的函数都放在一个.c程序文件中,势必会造成文件体积庞大,查找函数困难,阅读理解困难,函数取名困难,安排函数的顺序也困难。程序员编程时,往往采用模块化程序设计方法,将一个大的程序分解为功能单一的小模块。,1.1 算法、C语言和程序设计,7.1.2 多源文件程序的结构,将图7.1的所有函数组织在三个程序文件file1.c、file2.c和file3.c中。,/*file1.c*/extern void f1();extern void f2();void main()f1();f2();,/*file2.c*/void g1();void k1();void f1()g1();void g1()k1();void k1(),/*file3.c*/void h1();void h2();extern void k1();void f2()h1();h2();k1();void h1()void h2(),7.1 C程序的模块化结构,7.1.3 分割编译,编译是以源文件为单位进行的。在编译该程序时,可以以源文件为单位分别进行编译,相应产生目标文件,然后再用连接程序将分别编译产生的多个目标文件连接成一个可执行文件,这样的过程称为分割编译。,7.1 C程序的模块化结构,7.1.3 分割编译,编写多源文件程序的步骤基本:编译:必须对程序中的每个源程序文件单独进行编译,编译通过后生成相应的文件名同源程序文件名,扩展名为.obj的目标程序文件。由于有多个源程序文件,因此有多个目标程序文件。连接:连接器把上一步生成的多个目标程序文件和系统的库函数代码结合在一起生成扩展名为.exe的可执行程序文件。运行:运行生成后的可执行文件。,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,7.2.2 静态(static)变量,7.2.3 作用域及生存期,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,1内部变量:也称局部变量,即在函数内部定义的变量。以下变量都是内部变量:在函数内部定义的变量,其作用范围只在该函数内部。函数定义时的形参,其作用范围只在该函数内部。在复合语句中定义的变量,其作用范围只在该复合语句内。,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,内部变量的特点:块作用域:所有内部变量的作用域都是局部的,称为程序块作用域。自动生存期:局部变量具有自动存储期限。当调用函数时,“自动”分配局部变量的存储单元,当函数返回时释放变量所在存储空间。,注意:当再次调用函数时,无法保证变量始终保留原来的值。,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,讨论下面程序的内部变量及其特点:,void main()int a,b;int x10,y10;if(ab)int t;t=a;a=b;b=t;Fun(x);,void Fun(int x)int a,b;,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,2外部变量:也称全局变量,在任何函数之外定义的变量。其特点如下:全局生存期:一直占据所分配的存储空间直至整个程序(工程)运行结束。文件作用域:从变量定义点开始到整个源程序结束处,跟在外部变量声明后的所有函数都可以访问它。还可以通过extern存储类型说明,把作用域扩大到该源程序文件外部变量声明之前的函数或者该工程的其它源程序文件中。,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,分析下列程序不同位置上的变量n及其特点:,int n=5;void fun(void);void main()printf(“1.main:n=%d n”,n);fun();printf(“2.main:n=%d n”,n);void fun(void)n=10;printf(“3.fun:n=%d n”,n);,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,外部变量的作用:可以在不同的函数中传递数据。如例7.3 可以在不同的源程序文件中传递数据,扩大其作用范围。如例7.4,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,外部变量可以在不同的函数中传递数据:例7.3 输入nm的矩阵,将其转置后输出。下面给出实现的所有函数的说明:input():实现矩阵数据的输入。transpose():实现矩阵的转置。print():实现矩阵的输出。由于以上这些函数都用到矩阵数据,而函数不能返回数组,因此,可以将矩阵说明为外部变量。,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,外部变量可以可以在不同的源程序文件中传递数据,扩大其作用范围。如例7.4。当一个程序分成几个源文件来实现时,可以在不同源文件中传递全局变量,但必须有一处是定义外部变量的,而其它源程序文件必须对该外部变量进行声明,称为外部变量的声明。外部变量的声明格式:extern 数据类型 变量名;,注意:全局变量只能定义一次,可多次用extern声明以扩大作用域。,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,外部变量与内部变量可以同名时,内部变量的优先级高于外部变量。例7.5 分析下列程序的运行结果。,#include int n=5;int main()int n=50;printf(“main:n=%d n”,n);fun();void fun(void)printf(“fun:n=%d n”,n);,7.2 内部变量、外部变量和作用域、生存期,7.2.1 内部变量、外部变量,3外部变量的利与弊 在多个函数共享一个变量或者少数几个函数共享大量变量时,外部变量很有用。然而,外部变量破坏了函数的独立性。独立性是重用代码的起码条件,只有完全不存在外部变量的函数才有独立性,因此,尽量少用外部变量,可以通过形式参数在函数之间传递数据的方法消除外部变量。例7.6 消去例7.3的外部变量,改成形式参数传递来 实现。,7.2 内部变量、外部变量和作用域、生存期,7.2.2 静态(static)变量,在模块化程序设计中,为了发挥程序的模块作用,有必要引入一种模块内的全局变量,它区别于前面介绍的局部变量和全局变量。这种变量具有全局寿命和局部可见性的特点;且默认初始化的值为0。,7.2 内部变量、外部变量和作用域、生存期,7.2.2 静态(static)变量,静态变量的定义格式如下:static 数据类型 变量名表;例如,static int x,y;,注意静态全局变量和静态局部变量之分。,7.2 内部变量、外部变量和作用域、生存期,7.2.2 静态(static)变量,静态全局变量:在所有函数之外定义的静态变量。特点:只在定义的文件范围内可见,在其他程序文件中不可见。,例7.7 分析下列程序,由两个源程序文件组成:,/*test.c*/#include extern void f1();extern void f2();void main()f1();f2();f1();f2();,/*test1.c*/#include static int i;void f1(void)printf(“f1:i=%dn”,+i);void f2(void)printf(“f2:i=%dn”,+i);,7.2 内部变量、外部变量和作用域、生存期,7.2.2 静态(static)变量,2静态局部变量:在函数内部定义的静态变量。特点:它只在定义它的函数内部可见;在整个程序运行期间一直占据其所在的存储空间。,7.2 内部变量、外部变量和作用域、生存期,7.2.2 静态(static)变量,例7.8 分析下列程序,注意全局变量、静态局部变量和局部变量的区别。,#include void f1();int n=1;void main()int a=0,b=-10;printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();void f1()static int a=2;int b=5;a+=2,b+=5;n+=12;printf(“f1:a=%d,b=%d,n=%dn”,a,b,n);,运行结果如下:main:a=0,b=-10,n=1f1:a=4,b=10,n=13main:a=0,b=-10,n=13f1:a=6,b=10,n=25,7.2 内部变量、外部变量和作用域、生存期,7.2.3 作用域及生存期,作用域:通常把变量的有效使用范围称为作用域生存期:变量的存在时间称为生存期(存储期)。,7.2 内部变量、外部变量和作用域、生存期,7.2.3 作用域及生存期,1.作用域:作用域规则主要是针对程序文件来说的,即作用域是一个变量在程序文件中的有效区域。C语言的作用域有2种:块作用域:块中定义的变量:其作用域起于定义它的位置,止于块的结束,常见于复合语句中定义的变量;函数内部定义的变量:其作用域从定义处开始一直到该函数结束;函数的形参:其所在的整个函数内都有效。文件作用域:从定义处开始到文件末尾。例如全局变量和全局静态变量。,7.2 内部变量、外部变量和作用域、生存期,7.2.3 作用域及生存期,1.作用域:作用域规则主要是针对程序文件来说的,即作用域是一个变量在程序文件中的有效区域。例1,分析下列程序变量的作用域:,void Fun(int x)int a,b;,void main()int a,b;int x10,y10;if(ab)int t;t=a;a=b;b=t;Fun(x);,7.2 内部变量、外部变量和作用域、生存期,例2,分析下列程序变量的作用域:,#include void f1();int n=1;void main()int a=0,b=-10;printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();void f1()static int a=2;int b=5;a+=2,b+=5;n+=12;printf(“f1:a=%d,b=%d,n=%dn”,a,b,n);,7.2.3 作用域及生存期,7.2 内部变量、外部变量和作用域、生存期,7.2.3 作用域及生存期,2生存期:生存期是指一个实体产生后,存活时间的度量。对于变量,生存期决定了为变量预留和释放内存空间的时间。生存期的实质表现为内存空间的分配和收回。一般而言,操作系统将程序装入内存后,将形成一个随时可以运行的进程空间,该进程空间分为四个区域,如下图所示。,变量存在的区域,7.2 内部变量、外部变量和作用域、生存期,7.2.3 作用域及生存期,变量存在的区域:全局数据区:存放全局变量、静态变量(包括静态全局变量和静态局部变量)。栈区:存放函数数据区,即局部变量。它动态地反映了程序运行时的函数状态。堆区:存放动态变量,供程序员需要时申请和释放,将在7.5节中介绍动态存储分配和回收。,变量在不同的内存区域,将决定了其有不同的生存期。,7.2 内部变量、外部变量和作用域、生存期,7.2.3 作用域及生存期,三种生存期:(1)全局生存期:分配在全局数据区。在整个程序运行期间始终占用同一内存单元,直至整个程序执行完毕。默认初始化为0。(2)自动生存期:分配在栈区。这种变量在所属块被执行时获得内存单元,并在块终止时释放内存单元具有“用之则建,用完则撤”的动态特点。若未初始化,则初值不定。(3)动态生存期:分配在堆区。这种空间的分配必须通过调用malloc函数或calloc函数获得,不需要时可以调用free函数释放内存单元。,7.2 内部变量、外部变量和作用域、生存期,例 分析下列程序变量的生存期:,#include void f1();int n=1;void main()int a=0,b=-10;printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();void f1()static int a=2;int b=5;a+=2,b+=5;n+=12;printf(“f1:a=%d,b=%d,n=%dn”,a,b,n);,7.2.3 作用域及生存期,7.3 变量和函数的存储属性,7.3.1 变量的存储类型,7.3.2 函数的存储属性,7.3 变量和函数的存储属性,7.3.1 变量的存储类型,变量的存储类别有4种:auto:自动型static:静态型register:寄存器型extern:外部型,不同的存储类别有不同的作用域和生存期。,7.3 变量和函数的存储属性,7.3.1 变量的存储类型,完整的变量定义格式如下:,存储类别 数据类型 变量名表;,7.3 变量和函数的存储属性,7.3.1 变量的存储类型,1atuo型auto 数据类型 变量名表;,例如,auto int x,y;auto char ch;,特点:存储在栈区内,具有局部作用域、自动的生存期。是C语言默认的存储类型。,7.3 变量和函数的存储属性,7.3.1 变量的存储类型,2register型register 数据类型 变量名表;,例如,register int x,y;,特点:不占存储空间,而是存储到CPU的寄存器中。该存储类别只对内部变量有效,具有自动生存期、局部作用域。不能定义多个这种类型的变量。,7.3 变量和函数的存储属性,7.3.1 变量的存储类型,3extern型extern 数据类型 变量名表;,特点:这种变量由其它文件定义,定义的文件只是引用。,#include int a;int main(void),bb.c,#include extern int a;int fun1(void),cc.c,#include int fun2(void)extern int a;,aa.c,7.3 变量和函数的存储属性,7.3.1 变量的存储类型,4static型static 数据类型 变量名表;,例如,static int x,y;,特点:static型的变量存储在全局数据区,因此具有全局寿命,且默认初值为0。定义的位置不同,有不同的作用域:在函数内部定义的变量,具有块作用域在函数外部定义的变量,具有文件作用域,7.2 内部变量、外部变量和作用域、生存期,分析下列程序的运行结果:,#include void f1();int n=1;void main()int a=0,b=-10;printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();printf(“main:a=%d,b=%d,n=%dn”,a,b,n);f1();void f1()static int a=2;int b=5;a+=2,b+=5;n+=12;printf(“f1:a=%d,b=%d,n=%dn”,a,b,n);,7.3.1 变量的存储类型,7.3 变量和函数的存储属性,7.3.2 函数的存储属性,由于函数本质上是外部的,定义函数的目的就是供其他函数调用,所以函数的存储类别只有static和extern两类。,7.3 变量和函数的存储属性,7.3.2 函数的存储属性,1内部函数如果一个函数只能被本文件中其他函数调用,不能被工程的其它文件中的函数调用,可以把该函数定义为static型的内部函数。,其定义格式如下:,static 数据类型 函数名(形参列表),7.3 变量和函数的存储属性,7.3.2 函数的存储属性,2外部函数函数一般具有外部特点。如果在定义函数时前面没有加上static限制,则在同一个工程中的不同源文件中都可以调用该函数。,其定义格式如下:,数据类型 函数名(形参列表)说明语句部分执行语句部分,外部函数可以被同一工程中的其它文件调用,但必须在其它文件中进行声明,声明格式如下:,extern 数据类型 函数名(形参列表);,7.4 动态存储分配和释放,7.4.1 申请动态内存,7.4.2 动态内存的释放和重新分配,7.4.3 void指针类型,7.4 动态存储分配和释放,包括动态存储分配:malloc函数和calloc函数;动态存储释放及重新分配:free函数和realloc。在使用这些函数时,应当用“#include”编译预处理命令,把stdlib.h头文件包含到程序文件中。,7.4 动态存储分配和释放,7.4.1 申请动态内存,C语言允许建立内存动态分配区域:需要时随时开辟,不需要时随时释放。数据是临时存放在一个特别自由的存储区,称为堆区。申请动态内存的函数有malloc函数和calloc函数。,7.4 动态存储分配和释放,7.4.1 申请动态内存,1malloc函数函数原型为:,void*malloc(unsigned int size);,说明:该函数的功能是申请分配指定size个字节的存储区。若申请成功,返回一个指向该存储区的首地址;若没有足够的内存空间分配,函数返回空(NULL)。,7.4 动态存储分配和释放,7.4.1 申请动态内存,例如,int*pi;/*申请内存空间,强制数据类型转换*/pi=(int*)malloc(sizeof(int);/*如果申请成功,将123存储于刚分配的存储空间中*/if(pi!=NULL)*pi=123;,注意,由malloc所分配的存储单元无确定的初值。,7.4 动态存储分配和释放,7.4.1 申请动态内存,2calloc函数函数原型为:,void*calloc(unsigned n,unsigned size);,说明:该函数可以给n个同一类型的数据项分配连续的存储空间由calloc所分配的存储单元,系统自动置初值0,例如:char*pc;pc=(char*)calloc(80,sizeof(char);/*pc指向具有80个连续char型的存储区域的首地址*/,7.4 动态存储分配和释放,7.4.2 动态内存的释放和重新分配,用malloc和calloc函数在堆中分配的存储空间,随时可以调用free函数释放,也可以通过realloc函数对动态分配的内存区调整大小。,7.4 动态存储分配和释放,1free函数函数原型为:,void free(void*p);,说明:函数执行后,p指向的存储空间交还给系统,指针变量p就没有确切的指向了。,7.4.2 动态内存的释放和重新分配,7.4 动态存储分配和释放,2realloc函数函数原型为:,void*realloc(void*p,unsigned int size);,说明:该函数的功能是将p所指向的已动态分配存储区的大小改变为size,size可比原来分配的空间大或小。,7.4.2 动态内存的释放和重新分配,7.4 动态存储分配和释放,7.4.3 void指针类型,void*指针变量名;,C99允许使用基类型为void的指针类型,其定义格式如下:,说明:这种类型的指针不指向任何具体的数据。在将它的值赋给另一指针变量时要进行强制类型转换,使之适合于被赋值变量的类型。,7.4 动态存储分配和释放,7.4.3 void指针类型,例7.9 建立动态数组。输入5个学生的成绩,调用一个函数检查其中有无低于60分的成绩,输出这些不合格的成绩。,#include#include void main()void check(int*);int*p1,i;void*p2;p2=malloc(5*sizeof(int);p1=(int*)p2;printf(输入5个学生的成绩:);for(i=0;i5;i+)scanf(%d,p1+i);check(p1);free(p2);,void check(int*p)int i;printf(不合格的成绩有:);for(i=0;i5;i+)if(pi60)printf(%d,pi);printf(n);,7.5 编译预处理,7.5.1 宏定义,7.5.2 文件包含,7.5.3 条件编译,7.5 编译预处理,编译预处理命令指示在对源程序进行编译之前的预处理。编译预处理命令以#号开头,占用单独的一行,命令尾不能有“;”号一般放在程序前面或函数的前面。编译预处理命令有3种宏定义命令文件包含命令条件编译命令,7.5 编译预处理,编译预处理命令指示在对源程序进行编译之前的预处理。编译预处理命令以#号开头,占用单独的一行,命令尾不能有“;”号一般放在程序前面或函数的前面。编译预处理命令有3种宏定义命令文件包含命令条件编译命令,7.5.1 宏定义,1不带参数的宏定义一般形式:#define 宏名 一串字符,宏展开:预编译时,用宏体替换宏名。不作语法检查。,如#define YES 1#define NO 0#define PI 3.1415926#define OUT printf(“Hello,World”);,说明:用指定标识符(宏名)代替字符序列(宏体)。其中,宏名一般大写;一串字符一般不用双引号做定界符。,7.5 编译预处理,7.5.1 宏定义,宏名?宏体?,例#define WIDTH 80#define LENGTH WIDTH+40 var=LENGTH*2;宏展开:var=80+40*2;,例#define PI 3.14159 printf(“2*PI=%fn”,PI*2);宏展开:printf(“2*PI=%fn”,3.14159*2);,例#define WIDTH 80#define LENGTH WIDTH+40 var=LENGTH*2;宏展开:var=80+40*2;,7.5 编译预处理,7.5.1 宏定义,如 if(x=YES)printf(“correct!n”);else if(x=NO)printf(“error!n”);展开后:if(x=1)printf(“correct!n”);else if(x=0)printf(“error!n”);,2带参数的宏定义一般形式:#define 宏名(参数表)含参数的一串字符,例如,z=AREA(10);展开为:z=10*10+5;例如,z=AREA(a+b);展开为:z=a+b*a+b+5;,#define AREA(x)x*x+5,说明:参数表中的各参数用逗号隔开。宏展开时,以宏名后的实际参数替换宏定义中含参数的一串字符中所对应的形参。,7.5 编译预处理,7.5.1 宏定义,带参数的宏。,中间不能有空格,例如,z=AREA(a+b);展开为:z=(a+b)*(a+b)+5;,因此,最好改成如下:#define AREA(x)(x)*(x)+5,7.5 编译预处理,7.5.1 宏定义,#define MAX(x,y)(x)(y)?(x):(y).main()int a,b,c,d,t;.t=MAX(a+b,c+d);宏展开:t=(a+b)(c+d)?(a+b):(c+d);,int max(int x,int y)return(xy?x:y);main()int a,b,c,d,t;.t=max(a+b,c+d);,例 用宏定义和函数实现同样的功能,7.5 编译预处理,7.5.1 宏定义,带参的宏与函数区别,7.5 编译预处理,7.5.1 宏定义,3宏撤销一般形式:#undef 宏名,7.5 编译预处理,7.5.1 宏定义,例,#define PI3.1415926#undef PI,7.5 编译预处理,一般形式:#include“文件名”或#include,7.5.2 文件包含,说明:其作用是把指定文件的内容包含(复制)到当前源文件里。,7.5 编译预处理,一般形式:#include“文件名”或#include,7.5.2 文件包含,说明:这两种使用形式有差别,其差异在于预处理程序定位头文件的方式不同:#include,预处理程序直接到系统指定的include目录中查找所需的文件。#include“文件名”,预处理程序首先在源文件所在目录里查找,如果没找到,再到系统指定的include目录查找。,7.5 编译预处理,条件编译命令有6个,分别是:#if 整型表达式#else#elif整型表达式/*else if的缩写,分支测试*/#endif/*标识if作用范围结束*/#ifdef 标识符/*等价于#if defined标识符*/#ifndef标识符/*等价于#if undefined标识符*/,7.5.3 条件编译,7.5 编译预处理,条件编译的作用是直接取舍程序语句和协调多个头文件。例如,#ifdef NEWC#include#else#include#endif,7.5.3 条件编译,7.6 综合应用举例,例7.12 输入10个学生5门课的成绩,并实现如下功能:计算每个学生的平均分;计算每门课的平均分;找出所有50个分数中最高的分数所对应的学生和课程。计算平均分方差:,其中xi为某学生的平均分。,7.7 本章小结,本章主要包括以下内容:(1)C程序的模块化结构设计(2)内部变量、外部变量和作用域、生存期静态变量有:静态全局变量;静态局部变量(3)变量和函数的存储属性变量的存储属性:auto(默认),register,extern,static函数的存储属性有:static和extern,7.7 本章小结,例如,int a;extern int b;static int c;void f(int d,register int e)auto int g;int h;static int i;extern int j;register int k;,7.7 本章小结,7.7 本章小结,本章主要包括以下内容:(4)动态存储分配和回收(5)编译预处理命令,谢谢学习第7章,