第七章-程序结构PPT课件_第1页
第七章-程序结构PPT课件_第2页
第七章-程序结构PPT课件_第3页
第七章-程序结构PPT课件_第4页
第七章-程序结构PPT课件_第5页
已阅读5页,还剩85页未读 继续免费阅读

下载本文档

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

文档简介

1、第七章 程序结构 Chapter 7 Program Structure,程序结构: 使程序得以运行的框架组织便是程序结构,对程序结构的研究,是为了更好地表达算法思想,使其符合编译逻辑,又具有更好的可读性和可维护性 学习目标: 1. 从简单的函数层层调用,初步理解+程序结构 2. 学习合理组织程序的规则与经验,掌握扩展程序规模的基本方法,第七章内容,函数组织( Function Organization ) 头文件 ( Header Files ) 全局数据 ( Global Data ) 静态数据 ( Static Data ) 作用域与生命期 ( Scopes void f2(); voi

2、d f3(); /函数定义 int main() f1(); f2(); f3(); void f1() ,void p(); void f1(),void f1(); int main() void f1(),void g1(); void g1(),图7-3 一个文件拆分成三个程序文件,7.2 头文件,原始头文件:作为共同开发的项目,为了共享彼此的过程资源(函数),将全体函数声明放在一个共用的头文件中 界面头文件:界定模块可用资源(函数,数据,类型等)(可由一个或几个头文件组合,其实现由他人提供),或提供他人使用的模块资源它是由软件工程师分发的、以规范项目开发为目的的资源文件 做法:练习划

3、分函数组,模仿学习构造头文件,并注意头文件的应含内容,7.2.1 原始头文件,函数声明只能有惟一的一次,为了方便,可以将一个程序中所有要用到的函数专门做成一个程序文件,加在每个程序文件的开始处,也就是将那些函数声明做成一个文件后缀为.h的文件 (称之为头文件),用指令#include的方式加在程序文件的开始处。 如在前面的多文件结构中可以先做一个abc.h的头文件,其内容为:,/ abc.h void f1 ( ); void f2 ( ); void f3 ( ); void g1 ( ); void g2 ( ); void p ( ); void h ( );,原始头文件 (包含图中的一

4、切函数声明),头文件的使用:使函数调用免于声明,/ a1.cpp #include”abc.h” void f1() if() p(); g1(); else g2(); h(); ,头文件的使用:使函数调用免于声明,/ a2.cpp #include”abc.h” int main() f1(); f2(); f3(); /- void f3() f1(); /- void p() f3(); /-,头文件的使用:使函数调用免于声明,/ a3.cpp #include”abc.h” void h() void f2() g1(); g2(); /- void g1() void g2() ,

5、7.2.2 界面头文件,上述的规定和使用头文件的方法是很原始的,头文件更重要的作用是在设计阶段规定界面,也就是通过头文件可以明白地看出,某个程序文件提供了什么服务,这种头文件称为用户界面。,界面头文件,/ a1.h a1.cpp提供的资源 void f1(); / a2.h a2.cpp提供的资源 void p(); / a3.h a3.cpp提供的资源 void g1(); void g2(); void f2(); void h();,/ a1.cpp #include”a2.h” #include”a3.h” void f1() if() p(); g1(); else g2(); h(

6、); ,使用界面头文件,/ a2.cpp #include”a1.h” #include”a3.h” static void f3(); int main() f1(); f2(); f3(); void f3() f1(); void p() f3(); ,使用界面头文件,/ a3.cpp #include”a3.h” void h() void f2() g1(); g2(); void g1() void g2() ,使用界面头文件,7.2.3 头文件的内容,头文件的作用是给源程序提供可以使用的外部资源一览表,它不仅仅包括函数声明,它基本上还包括:,全局数据声明, 如 extern in

7、t n; extern int a; 函数声明, 如void fn(); 类型声明, 如class A; 全局常量定义,如const float pi=3.14; 内联函数定义,如inline void fn() ; 模板声明和定义,如template class A; 名空间定义,如namespace N 类型定义,如enum COLOR; class A; 预编译指令,如#include 注释,如/2003年5月7日创建,由于头文件可能出现在一个程序的若干个源程序文件中,所以将一些实体定义放在头文件中是不明智的,因为一种定义体在一个程序中只能出现一次,如:,#include int mai

8、n() std:cout“hello world!n”; 然后,在源文件a.cpp中,写上 #include”abc.h” 编译运行,虽然能得到结果,但是这从根本上背离了头文件在C+程序结构体系中的作用!,头文件一定不能包括: 全局数据定义,如int a; 函数定义,如void fn();,7.3 全局数据,全局数据:使若干个模块在程序范围内共享(读与写)数据,是若干程序文件沟通数据的一种形式 意义:模块的独立性由数据的封闭性来支持全局数据破坏了数据的封闭性,因而对小程序简单而对规范化程序则不登大雅之堂 做法:练习函数之间用参数传递数据的常规形式,尽量避免使用全局数据,7.3.1 全局数据访问

9、,全局数据就是在任何函数的外部声明或定义的,在程序范围内可以访问的数据。 对于大多数函数都要访问某个数据的情形时,可以将它设置为全局的,就可以免于参数传递。,例如:对于矩阵的输入、处理和输出,vector a; / global Data void input ( ); void transpose ( ); void print ( ); int main ( ) input ( ); / using a transpose ( ); / using a print ( ); / using a ,7.3.2 消除全局数据,全局数据破坏了模块结构的独立性,也破坏了抽象数据结构的封闭性。程序是

10、各个独立模块的聚集,全局数据牵扯了各个模块,使其无法独立。 可以通过参数传递的方法消去全局数据。如:,消去全局数据:前一个过程的输出作为后一个过程的输入,typedef vector Mat ; Mat input ( ) ; Mat transpose ( const Mat ,7.3.3 一次定义原则,全局数据有全局变量、全局常量、全局对象等,它们都有指针、引用、数组和其他形式。全局数据在程序存储结构中置身于全局数据区的位置。 全局数据区的整个区域在程序启动时,初始化为0.,在多文件结构的程序中,像单文件那样的全局数据定义形式存在着问题,因为在不同源程序文件中定义同名全局数据,就是多次构建

11、全局数据实体,它意味着各个程序文件实际上使用的是各不相同的实体,如:,/ 多文件结构中全局数据的冲突 /= #include /- int n=8; void f(); /- int main() std:coutnn; f(); /=,8,/= #include /- int n; void f() std:coutnn; /=,0,程序中的两个函数显示一个全局整型变量,却出现了不一致,原因是全局数据也应像函数那样,多次声明,而只能有一次定义。 全局数据的声明形式是在全局数据定义形式前加关键字extern,如:,在多个程序文件组成的程序中共享数据,要遵守一次定义规则,/ item1.cpp

12、#include using namespace std ; int n = 8 ; / define void f ( ) ; int main ( ) coutn”n”; f ( ) ; ,/ item2.cpp #include using namespace std ; extern int n ; / declare void f ( ) coutn”n”; ,还可以将各程序文件中的全局数据声明放在头文件中实现,如: /= / f0705.h /= extern int n; void f(); /=,/ f0705.cpp /= #includef0705.h #include /

13、- int n = 8; /- int main() std:coutnn; f(); /=,/ f07051.cpp /=#includef0705.h #include /- void f() std:coutnn; /=,7.3.4 全局变量,全局常量也是放在全局数据区,只供读取,不许修改,不会扰乱模块关系。不允许在同一个程序中反复定义全局常量,但可以在不同的程序文件中重复定义。,全局常量可能被滥用,如求矩阵的和,其数据放在ab.txt中,每组数据前两个整数表示行和列,后面则是矩阵的元素,计算和然后输出。 在程序中,为了求若干个矩阵的和,先定义两个最大行列数的二维数组,然后反复重用,由于

14、数组的下标只允许为常量,因此,设置全局变量,带来的后果是,占用了不必要的空间。若行列放宽后,则必须要改程序,带来了函数划分时所引入的参数传递困难,程序如下:,/= #include #include #include using namespace std; /- const int row=100; const int col=100; int main() ifstream in(ab.txt); int marowcol; int mbrowcol; int mcrowcol;,for(int r,c; inrc; ) for(int i=0; imaij; for(int i=0; i

15、mbij; for(int i=0; ir; +i) for(int j=0; jc; +j) mcij = maij + mbij; for(int i=0; ir; +i) for(int j=0; jc; +j) coutsetw(4)mcij; coutendl; ,该程序可以完全不用全局常量,如下列程序可以表示了一种便于函数划分、增加函数独立性的常规方法,结构更加合理,不滥用空间,不滥用全局数据,运行结果与前面程序一致.,#include #include #include #include using namespace std; /- typedef vector Mat; vo

16、id input(istream /-,void input(istream ,7.4 静态数据,静态全局数据:在一个程序文件中共享的数据注意:全局数据则在多个程序文件中共享数据 静态局部数据:在屡次调用的同一个函数中共享的数据,7.4.1 静态全局数据,在过程化程序设计中,为了使程序文件发挥模块的作用,有必要定义一种模块的局部量,它区别于其他程序文件,称之为静态全局数据(也称全局静态数据)。,函数的模块性在于只与其输入/输出联系,定义的变量在程序来说是局部变量,但在该函数内部来说,却是全局的。程序文件的模块性在于只与其定义的全局函数联系,只与包含文件联系,其定义的静态全局数据对于该程序文件内

17、部来说是全局的,但对于整个程序来说却是局部的。,在程序工程中添加一个程序文件,等于添加了一个程序文件模块,意味着其他地方要使用该程序文件所定义的某些函数,在该程序文件中包含了某些头文件,意味着该程序文件要使用其他地方的函数。如根据图7-3的程序文件划分,其中 a2.cpp可以得到图7-4:,a1.h a3.h,程序文件模块 a2.cpp,a2.h,图7-4 程序文件模块,f1,f2,p,程序如下: #includeabc.h /- int main() f1(); f2(); f3(); /- void f3() f1(); /- void p() f3(); /-,程序中,有的函数是为文件中

18、的其他函数服务的,并不对外提供服务,这些函数应声明为静态,表示局部于程序文件。同样有的变量只是为本文件服务,也不是全局数据,应标以static,这些函数和变量称为静态全局数据和静态全局变量,它只在本文件范围内可见,在其他程序文件中不可见。,没有静态全局常量,前述的全局常量可以看做是文件变量,每个程序文件都可以通过”const int a=3;”的形式来定义惟一的文件域全局常量,即静态全局常量,这就是为什么可以将全局常量写入头文件的原因。,7.4.2 静态局部数据,函数中的变量为局部变量,在定义局部变量前面加上static,就成了静态局部变量,静态局部变量驻留在全局数据区,默认初始化值为0,而且

19、不会受函数的调用和返回的影响,函数第一次被调用时,静态局部变量被建立,此后该变量一直存在,直到程序运行结束。,静态局部变量在函数内部定义,但却驻留在全局数据区,所以,从可见性来说,它与局部变量一致,从生命期来说,它与全局变量一致。 下面的程序演示了全局变量、静态局部变量和局部变量的区别:,#include using namespace std; /- void func(); int n=1; /- int main() int a=0, b=-10; couta=a, b=b, n=nendl; func(); couta=a, b=b, n=nendl; func(); /- void

20、func() static int a=2; int b=5; a+=2, b+=5; n+=12; couta=a, b=b, n=nendl; /=,a =0,b=-10,n=1 a =4,b=10,n=13 a =0,b=-10,n=13 a =6,b=10,n=25,由于静态局部变量是局部的,所以若把该变量的指针作为函数返回值到处传播是不妥的,而且也丧失了模块的独立性,因为任何依附于某个块的操作都必须依赖于某个块而不能独立。,7.5 作用域与生命期,作用域:有很多种,变化最多的是局部作用域作用域遵守就近原则,它总是取用最贴近的名字,除非名字加前缀,则指特定区域的名字 生命期:实体一旦产

21、生(定义)后,存活时间的度量 作用域与生命期:作用域是编程规范,用于编译时的语法检查,生命期是程序运行中的实体存活度量,体现运行程序的内在规律名字访问遵守作用域规则,而作用域以实体存活为前提,7.5.1 作用域,C+的作用域有全局作用域、文件作用域、函数作用域、函数原型作用域、类作用域和局部作用域。 作用域规则主要是针对程序文件而言,所以全局作用域就是文件作用域。 函数作用域即不管名称在函数的什么地方声明,总是可以在函数的任何位置先使用该名称。只有标号是函数作用域的,设置标号即对标号名称进行声明,常以”go to 标号;”的形式出现。,函数原型作用域,是表明函数声明时的形参与上下文无关,如:

22、double width; /其中的width并未重复定义 void area(double width,double length); length =0; /错:length无定义 正因为如此,函数声明中形参可以省略。因为它与上下文没有任何关系。 类作用域与名空间机制类似。,局部作用域指在函数内部的动作序列描述中,依据各语句块甚至整个函数体范围内所定义的数据应遵循的数据访问规则。在局部作用域中,语句块往往是嵌套的,所以变量在其作用域并非一定可见,如果遇到更贴近的变量定义,则另一个外层同名定义将被暂时屏蔽。如:,/ 嵌套的局部作用域 /= #include using namespace s

23、td; /- void fn(int y); int j=8; / j为全局作用域 /- int main() int x=1; fn(x); /- / x作用域结束 void fn(int y) / y作用域开始 if(int i=1) / if语句块,i作用域开始 i=2*i; else i=100; / if语句块结束,则i作用域结束 int x=1; / x作用域开始 if(x y) coutxendl; else coutyendl; /- / x作用域结束 switch(int i=2) / switch语句块,i作用域开始 case 1: coutiendl; / switch语

24、句块结束,则i作用域结束 i = 3; / error int sum = 0; / sum作用域开始 for(int i=0; i10; +i) / i作用域开始 sum += i; / i作用域结束 int j=3; / fn函数块中,int j作用域开始 char ch; / fn函数块中,char ch作用域开始 double j; / 本块中,double j作用域开始 j=5; / 虽赋整数于j,但仍然指double j,非int j :j=6; / 全局变量通过:操作可见,但局部int j不可见 ch=A; / 只要本块中没有定义ch变量,则外块ch可见 / double j作用

25、域结束 j=6.0; / int j可见 /= / j,ch,y,sum作用域结束,7.5.2 生命期,生命期指一个实体产生后,存活时间的度量。 静态局部数据的生命期并不与局部作用域一致,它的生命期一直延续到程序运行结束。,另有一种动态生命期,是由new申请到内存空间后,该空间实体开始有效,一直到delete语句释放该内存空间。在动态生命期中,其有效的堆空间实体可能被跨函数地访问,因此,其作用域是整个程序范围的。如:,/ 动态生命期 #include /- int* fn() int* ap = new int; / ap所指向的内存空间开始有效,可以访问了 return ap; /- int

26、 main() int* bp=fn(); *bp = 15; std:cout*bpn; delete bp; / bp所指即ap所指,此处使该内存空间无效,7.6 名空间,名空间:解决名字冲突的方法所有名字都有空间归属,在一定的空间中,名字是不允许冲突的引用一个名字时,加上空间归属的前缀,就可以唯一确定该名字所对应的实体 无前缀名字:很多时候,名字都是无前缀的,这是因为事先已经指定了默认名字空间如果默认名空间在两个以上,则必须注意名字冲突的可能性,C+的名称理解,肯定不能仅仅用名称的作用域规则来规范,名空间机制才是真正全面发挥作用的名称认定和作用域规则,它规定,一个名称必须在使用的域中明确

27、声称其使用的“空间名”,才能在域中默认的使用该名称,就像使用 using namespace std; 一直在默认使用std标准库名一样。,7.6.1 名空间的概念,C+的名空间机制,就是为了支持大规模程序的逻辑设计,排解名字冲突而产生的。 程序设计语言的描述可以对应整个世界,世界上有许多重名,在不同的场合表达不同的意思,在程序里面,就是通过名空间来区分的。 因为程序是跨文件的,所以对应的名空间也是跨文件的。,7.6.2 名空间的组织,名空间的定义通过下列形式: namespace name /名称声明或定义 因为名空间总是凌驾于其他程序文件之上的,它是其他程序文件中的所有外部名称使用的规范描

28、述,所以一般总是将名空间的定义放在头文件中。,7.6.3 组织模块,一个模块的输入,是指其他模块提供的服务,模块的输出是提供给其他模块使用的服务。 可以将包括许多程序文件在内的更大模块的名称集合,组织在一个头文件中,原先放在头文件中的各个声明,都可以放在名空间中而成为整个程序的模块结构层次中的一个。,如有一个程序,由其函数调用关系可以将其划分为三个模块,模块1由文件1组成,模块2由文件2、3、4组成,模块3由文件5、6组成。如图7-5所示:,图 7-5 函数调用关系,模块2,模块3,由图7-5能得到下图的程序文件组模块,共有三个模块:,模块1,模块2,模块3,在软件设计人物完成后,便可以根据图

29、例所示的三个模块为软件资源,再加上C+的系统资源,编写代码。,7.6.4 数据名冲突,名空间解决模块名冲突问题远远不够,因为在模块中还有各种数据的往来,难免存在着交叉。 数据的名空间规定更复杂,因为它们不但比模块数量更多,而且访问数据的权限并不是对每个程序员都一样的,常用的方法是使用“数据封装”来解决数据的名空间问题,也就是对象化编程的基本思想,在后面的类机制会详细讲述。,7.6.5 名空间的用法,当自己要定义一个函数与标准库的名称发生冲突的时候,就不能简单的将标准库的所有名称一并默认,如定义一个: int abs(int a) return a0?a:-a; 这个时候,就可以选择一个版本使用

30、,如:,/ 解决名字冲突 /= #include / using namespace std; /- int abs(int a) return a0 ? a : -a; /- int main() int a = abs(-5); int b = std:abs(-5); std:coutastd:endlbstd:endl; /=,如果标准库中的某个名称用的很频繁,又可能会遇到标准库的名称与自己定义的名字冲突,就可以局部默认名空间的名称使用,如上述程序可以改写为:,/ 局部名空间默认 /= #include using std:cout; using std:endl; /- int ab

31、s(int a) return a0 ? a : -a; /- int main() int a = abs(-5); int b = std:abs(-5); coutaendlbendl; /=,7.7 预编译,预编译:+在正式编译前的准备工作,它包括文件的加盟、代码的取舍、字符的替代等操作 文件加盟由include指令引导,代码取舍由ifdef或ifndef引导,字符替代功能在逐渐退化,因为+语言本身完全可以胜任这项工作,常用的预编译指令: 包含指令#include 条件指令#if、#elif、#else、#endif、#ifdef、#ifndef 定义指令#define、#undef,7.7.1 #include指令,include指令指示预编译将包含的头文件的内容附加到程序文件中,以参加编译。 如果头文件是C+系统提供的,则用尖括号把头文件括起来,

温馨提示

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

评论

0/150

提交评论