
三章节过程式程序设计语言.ppt
94页第三章 过程式程序设计语言基本观点: 计算实现的模型如果按冯·诺依曼原理强制改变内存中的值叫命令(或译指令、强制Imperative式)的由于强制改变值,程序状态的变化没有一定规则,程序大了就很难查错,很难调试,不易证明其正确 组织程序的范型即:算法过程+数据结构(计算控制+计算对象)3.1 计算对象表示—值与类型3.2 计算对象实现—存储3.3 计算对象连接—束定3.4 计算组织---程序控制3.5 计算组织---函数与过程3.6 计算组织---抽象与封装类型是计算机可能实现的结构和约定对客观世界差异的刻划同一类型的外延,即同一结构表示所有可能的值构成一个域分类原则:同样表示结构,同样语义解释,同样的操作 同类型值运算结果:同类型无符号整数: 二进制解释的值整数:符号 值 3.1 计算对象-值与类型0 10 0 0 1 1 11 11 0 1 0 0 0 1 0 1 0 1 00 0 0 0 1 0 1 0 1 0 1 1 浮点数: 符号 阶码 尾数程序语言中:基元(primitive)类型:整型/实(定,浮)/字符/真值/枚举结构(structured)类型:元组/数组/记录(结构)/表/串3.1.1 字面量、变量、常量• 名字操纵值, 名: 字面量 从名字即知类型 字面值不变 变量 符号名要声明类型 值可变 常量 符号名要声明类型 值不变• 基元值的名字是地址的别名, 地址值在计算机中不恒定• 操纵地址的名字是指针(地址变量) float *p, x = 37.32; p=&x; (p)203C(x)117F117F37.32续•名值可分导致 x = x + 1; 按名 按名取值: 引用reference 存—值 左值 右值 int & Rint = Svar Rint 是引用类型的变量,即左值变量必须给一右值,因 而成Svar别名 y = x ++ ; x 既是右值也是左值3.1.2 值是头等程序对象程序语言中的值•字面量(整、实、布尔、字符、枚举、串)•复合量(记录、数组、元组、结构、表、联合、集合、文件)•指针值•变量引用(左值、右值)•函数和过程抽象,数学对象参与运算的权利是一样的,值是计算对象也要按一致性原则:–可出现在表达式中并求值–可作函数返回值–可单独存储 –可以构成复杂的数据结构–可作函数参数3.1.3 类型系统•类型定义 值的集合和值上操作集合(V,Op)•类型系统 一组可直接使用的类型•类型规则•类型检查机制3.1.3 类型系统•静态与动态 静 动 变量 有类型 无类型 动态简洁、灵活 参数 有类型 无类型 静态清晰、死板 值 有类型 有类型•弱/强类型–无类型 LISP , Smalltalk–弱类型 变量有类型。
类型兼容性大, 系统不作检查–强制类型 隐式类型强制(转换),自动截尾, 补零显式 类型强制 PL/1–伪强类型 静态均有类型且作检查,由于不严,导出等价准则 Pascal–强类型 类型有严格定义, 均作检查 Ada•类型等价 按结构等价 type A is array (range 1.. 100) of INTEGER; type B is array (range 1..100) of INTEGER; OA1, 0A2: A; OB1, OB2: B; OC1: array (range 1.. 100) of INTEGER; OD1, OD2: array (range 1..100) of INTEGER; OE1: A; OA1,OA2,OB1,OB2,OC1,OD1,OD2,OE1均等价 续 按名等价 OA1, OA2 是同一类型(都用A声明) OA1, OB1, OC1是不同类型(类型名为A,B, 无) OD1, OD2 是同一类型(同时声明, 虽无名) OD1, OC1 是不同类型(两次声明) OA1, OE1 是同一类型(虽两次声明, 但同名)• 类型完整性准则 涉及值的类型中不能随意限定操作, 力求没有第二类的值续3.1.4 类型兼容•不同类型值混合运算, 人为定出计算级别,由低 层升格为高层, 结果值是高层的•隐式转换 弱类型 I := R; 显式转换 强类型 I := Integer(R);•强类型按名判定,不同类型名则不兼容只有子类型不同名可以兼容•显式和隐式混合type BASE is INTEGER; subtype SON_TYPE is BASE range 1..1000; --子类型type DIVERSE is new BASE range 1..1000; --派生类型A, B: BASE; C, D: SON_TYPE; E: DIVERSE; …A:= B+C, --合法,结果为B类型赋给AA:= C+E; --不合法A:= C + SON_TYPE(E); --合法,有显式强制A:= E ; --不合法,两个类型E:= B+BASE(E); --不合法续3.2 3.2 计算对象的实现计算对象的实现- - 存存 储储 存储对象是程序对象在计算机中的实现存储对象是程序对象在计算机中的实现 程序对象不一一对应为存储对象程序对象不一一对应为存储对象 x:=0; x,0是两程序对象是两程序对象 只有一个存储对象只有一个存储对象x加指令清零加指令清零 初值常量也不作为单独存储对象初值常量也不作为单独存储对象3.2.1 3.2.1 程序变量的时空特性程序变量的时空特性引用和指针引用和指针P指针是地址变量指针是地址变量 *P是是P所指的内容所指的内容, 也有左值和右值也有左值和右值 *P左值是左值是P所指地址值,即所指地址值,即P的值的值 *P右值是所指地址内的内容值右值是所指地址内的内容值引用是常指针是变量的别名引用是常指针是变量的别名, 但实现是不一样的但实现是不一样的递引用递引用 dereferencedereference通过指针变量引用变量的值为递引用通过指针变量引用变量的值为递引用*P1右值即递引用右值即递引用有些语言显式递引用算符如有些语言显式递引用算符如FORTH的的@1 13 1 13 VARIABLE xx (VARIABLE xx (声明变量声明变量xxxx并赋初值并赋初值13)13)2 0 2 0 VARIABLE Y (VARIABLE Y (声明变量声明变量Y Y并赋初值并赋初值0) 0) 3 3 xx @ 2 * Y ! (xx @ 2 * Y ! (相当于相当于Y=xx*2)Y=xx*2)如果只写如果只写xx 2 * xx 2 * 则为将则为将xxxx的地址乘以的地址乘以2 2放在放在Y Y之中之中3.2.1 变量的时态变量的时态•分配分配/未分配未分配/除分配除分配–分配分配: 为程序对象创建为程序对象创建—存储对象存储对象 编译时分配叫静态分配编译时分配叫静态分配 allocate 运行时分配叫动态分配如声明指针运行时分配叫动态分配如声明指针p, 执执new才分配才分配–未分配未分配: 声明了未分配运行时分配声明了未分配运行时分配–除分配除分配: 取消存储对象取消存储对象(程序对象程序对象) delete操作显式操作显式–自动除配自动除配: 无用单元收集无用单元收集Garbage collection动态语言有,静态可有动态语言有,静态可有Ada可没有可没有C续续43? ? a b c d e f 声明和定义:定义必然声明;反之不然声明和定义:定义必然声明;反之不然 声明的两个作用声明的两个作用 :给出对象:给出对象, 该对象的时间有效性该对象的时间有效性 出了声明的作用域该对象失去定义。
在声明的作用出了声明的作用域该对象失去定义在声明的作用 域内显式删除也失去定义域内显式删除也失去定义•定义定义/ /未定义未定义/ /失去定义失去定义 只只要要分分配配存存储储对对象象必必然然有有残残值值, 无无意意义义即即未未定定义义 赋值或初值变量得以定义赋值或初值变量得以定义a,b:分配且有定义分配且有定义c,d:分配未定义或失去定义分配未定义或失去定义e,f:未分配或除配未分配或除配3.2.2 存储模型•基元类型值基元类型值 仅除数组仅除数组•记录、构造、表记录、构造、表 不可更新其中一元素不可更新其中一元素•函数抽象函数抽象, ——ML重过程重过程•变量引用变量引用可存储值可存储值StorableStorable::指最小的可直接访问的可存储单元中的值指最小的可直接访问的可存储单元中的值Pascal可存储值可存储值:集合不选择更新某一元素是可存储值,集合不选择更新某一元素是可存储值,Pascal, C ,,Ada数组可选择更新数组可选择更新, 不是可存储值不是可存储值引用非可存储引用非可存储(C++可存储可存储), 过程和函数名也非可存储过程和函数名也非可存储ML几乎都是可存储值几乎都是可存储值, 也带来毛病:每次更新结构数据都要重也带来毛病:每次更新结构数据都要重来。
它们是:来它们是:( if exp then sin else cos ) (x) 得得sin(x)||cos(x)可存储值可存储值 存储对象的生存期存储对象的生存期•全局变量全局变量 和引用程序寿命一样长和引用程序寿命一样长•局部变量局部变量 和程序中的一个模块寿命一样长和程序中的一个模块寿命一样长•持久变量持久变量 比程序寿命长除非显式撤销比程序寿命长除非显式撤销 文件变量文件变量•瞬间变量(瞬间变量(transient)持久变量的逆持久变量的逆每个存储对象都有创建每个存储对象都有创建(生生), 可用可用(活着活着),撤销,撤销(死死)的生命期按生命期长短分:的生命期按生命期长短分:静态存储对象静态存储对象 编译时分配存储对象编译时分配存储对象, 近代语言类属对象直到装入后近代语言类属对象直到装入后确立确立(elaboration)之时才定下存储对象叫静态分配之时才定下存储对象叫静态分配 一旦执行不再改动其存储,直至所在存储单元无效叫一旦执行不再改动其存储,直至所在存储单元无效叫静态静态(Static)存储对象存储对象 全局变量均为隐式的静态对象全局变量均为隐式的静态对象, COBOL,BASIC全全静静态态,,ALGOL,,C是是显显示示声声明明静静态态,,Pascal除除全全局局,,Ada 不能。
不能 C语语言言的的静静态态变变量量是是既既私私有有又又不不随随所所在在声声明明块块中中消消逝逝, 全全局局于于所所在在文文件件auto是是静静态态分分配配动动态态装装入入不不叫叫静静态态对对象象Extern是静态对象是静态对象externstaticauto动态存储对象动态存储对象 把寿命特长的(如文件,全局量)排出来归到栈底的某一组,把寿命特短的(如循环控制变量)另立嵌套组,这个问题也就解决块5块66块1块2块3块45466546寿寿命命动态存储对象动态存储对象 二叉树其大小由输入值定在运行中确立内存开二叉树其大小由输入值定在运行中确立内存开辟堆(辟堆(heap)随生成随堆放动态存储对象指针(即随生成随堆放动态存储对象指针(即堆变量)所指程序对象用堆存放堆变量)所指程序对象用堆存放 问题:多次重分,内存成了小洞问题:多次重分,内存成了小洞 解解决决办办法法::按按寿寿命命分分组组寿寿命命最最长长的的放放在在较较低低((按按其所在块生命期)其所在块生命期)重复使用重复使用无法再分无法再分堆栈帧管理堆栈帧管理 由动态堆栈联想到一般嵌套式语言可按动态堆栈式管由动态堆栈联想到一般嵌套式语言可按动态堆栈式管理理, 多数变量和所在块寿命一样长多数变量和所在块寿命一样长(语言称之为自动变量语言称之为自动变量)• 动态堆栈式存储动态堆栈式存储 按按程程序序动动态态执执行行, 以以动动态态堆堆栈栈管管理理局局部部数数据据和和动动态态生生成数据成数据 运运行行时时堆堆栈栈Run-time stack其其底底压压入入程程序序代代码码和和全全局局,,静静态态量量。
每每执执行行到到调调用用时时生生成成一一个个堆堆栈栈帧帧(frame)记记录录该该块块数数据据信信息息, 每每当当返返回回则则局局部部量量自自动动撤撤销销对对于于递递归归块块的的局部量可多次生成多次消除局部量可多次生成多次消除 动动态态链链描描述述调调用用父父辈辈地地址址, 返返回回地地址址是是继继续续执执行行的的下下一地址 静静态态链链描描述述词词法法父父辈辈lexical parent块块地地址址, 按按该该块块复复制制局部变量局部变量参数参数 返回地址返回地址动态链动态链静态链静态链返回值返回值局部变量局部变量程序代码程序代码全局静态存储全局静态存储首先调用块首先调用块堆栈帧堆栈帧第二调用块第二调用块堆栈帧堆栈帧最新最新调用块调用块堆栈帧堆栈帧临时变量空间临时变量空间栈顶栈顶…………堆栈帧堆栈帧组织组织运行时堆栈运行时堆栈续续调调用用块块首首地地址址本本帧帧词词法法父父辈辈• 举例举例 求整数连乘积之递归程序求整数连乘积之递归程序: function product (jj:: Integer):: Integer;; var kk:: Integer; begin if jj <= 0 then product:=1 else begin readln (kk);; product:=kk * product(jj-1) end end;; Product函数函数目标代码目标代码jj:2return :?kk:25jj:1return :?kk:7jj:0return :?kk:?临时存储临时存储DLSL:SL:静态链静态链DL:DL:静态链静态链最初调用时堆栈帧最初调用时堆栈帧第一次调用时堆栈帧第一次调用时堆栈帧第一次调用时堆栈帧第一次调用时堆栈帧 栈顶栈顶SL动态堆存储动态堆存储•忽略死对象忽略死对象 不超过一页浪费不超过一页浪费, 若寿命差不多浪费不若寿命差不多浪费不大大•保持一个自由表保持一个自由表(链表链表)8个字节头说明数据个字节头说明数据•按类型长度保持多个表减少识别域开销按类型长度保持多个表减少识别域开销(Ada) 动动态态堆堆栈栈缺缺点点::开开始始帧帧不不知知有有多多大大,要要求求存存储储对对象象比创建它的块寿命长。
比创建它的块寿命长 指指针针和和显显式式动动态态分分配配依依然然不不少少了了堆堆按按帧帧也也设设heap• 各种语言堆分配各种语言堆分配 FORTH LISP C Pascal FORTH LISP C Pascal AdaAda分配分配 Here cons Here cons mallocmalloc new new new new除配除配 无回收无回收 有回收有回收 显式除显式除 显式除显式除 有回收有回收• 死堆对象处理死堆对象处理 死堆对象也叫无用单元死堆对象也叫无用单元garbage垃圾)垃圾)3.2.33.2.3 悬挂引用悬挂引用Dangling ReferenceDangling Reference• 当当堆堆式式管管理理同同时时提提供供显显式式除除配配命命令令KILL时时;;堆堆栈栈式式管管理理外外块块指指针针指指向向内内块块对对象象时时; 函函数数抽抽象象作作为为第第一一类类值值时时,都会产生都会产生悬挂引用悬挂引用• 解决办法解决办法 Pascal把把指指针针限限制制为为堆堆对对象象且且不不用用dispose,,不不提提供供地地址运算。
操作数组不能按指针寻址址运算操作数组不能按指针寻址, 快速索引快速索引 C语言比较自由,悬挂引用留给程序员语言比较自由,悬挂引用留给程序员 局局部部函函数数作作为为返返回回值值产产生生的的悬悬挂挂指指针针 ML, Mirada完完全全作作为为堆堆变变量量且且无无KILLAlgol 68是是引引用用((常常指指针针)), 不赋比局部量寿命更长的值不赋比局部量寿命更长的值例例 悬挂引用悬挂引用( (C)C)intint * dangle ( * dangle (intint ** ** pppppp) { //) { //参数是指针的指针参数是指针的指针 intint p=5 p=5;; intint m=21 m=21;; * *pppppp=&p=&p;; // //传回的指向传回的指向p p的指针的指针 return & mreturn & m;;} //} //返回返回m m的地址的地址main( ) {main( ) { intint k =17 k =17;; intint * pm * pm,, * *pkpk=&k=&k;; // //pkpk指向指向k k pm = dangle (& pm = dangle (&pkpk) );; // //返回时返回时pmpm,,PkPk均指向已均指向已 ////失去定义的指针函数的失去定义的指针函数的 } // } //局部量,即局部量,即p p和和m m3.3 3.3 计算对象的连接计算对象的连接--- --- 束定束定BindingBinding名字操纵程序对象。
名字和存储对象联系起来才成为程序对象名字操纵程序对象名字和存储对象联系起来才成为程序对象把声明名字(地址)和存储对象或语义实体连接起来叫束定把声明名字(地址)和存储对象或语义实体连接起来叫束定束定束定 绑定绑定 定连定连 连编(编译中连接模块)连编(编译中连接模块)名字与束定名字与束定 一名可束定到多个对象一名可束定到多个对象 一对象可以束定到多个名字一对象可以束定到多个名字 在一个程序的声明期内一旦束定不再改变叫静态(早)束定在一个程序的声明期内一旦束定不再改变叫静态(早)束定 运行中一个名字束定到(多个)对象叫动态(晚)束定运行中一个名字束定到(多个)对象叫动态(晚)束定3.3.1 3.3.1 静态束定静态束定编译按数据类型为名字分配合适大小的存储对象编译按数据类型为名字分配合适大小的存储对象, , 该对象首地址填该对象首地址填入符号表即为静态束定入符号表即为静态束定, , 编译后变量表销毁编译后变量表销毁, , 故静态束定不能变故静态束定不能变符号表符号表 运行时内存运行时内存类型类型 名字名字 束定束定 存储对象存储对象real length (地址地址)array[1..4] of integer age (地址地址)多多重重束束定定 FORTRAN等等价价语语句句, 以内部引用实现。
以内部引用实现Real P2, P3Dimension P1(3)Equivalence (P1,P2),(P1(2),P3) P1 P2 P3其它语言多重束定是其它语言多重束定是: COBOL REDEFINES Pascal 变体记录变体记录 C 联合联合3.3.2 动态束定•运行时将名(字)束定于其体(得 其语义操作)•解释型语言一般保留名字表运行中束定•FORTH语言动态束定它是编译─ 解释型•每当生成word出一字项, 有两片代码 一为初始化和该名应执行的代码(编 译完成)•新字体中用到旧字,则运行中束定•解释执行另一片代码时,动态束定编译块•FORGET命令可以撤销当前字定义名字域(字)链接域类型域 参数域(体)...WORDWORD项项 项 指前一字指针 执行堆栈 例例 FORTH FORTH 的动态束定的动态束定0 0 :: 2 2by3array ('by3array ('::' '表示编译开始,表示编译开始, 后为类型声明符后为类型声明符) )1 1 create (create (编译动作:编译动作: 将类型声明符装入字典项将类型声明符装入字典项) )2 22 2,,3 (3 (在字典项中存入在字典项中存入 2×3 2×3维数维数) )3 12 3 12 allot (allot (为六个短整数分配为六个短整数分配1212个字节个字节) )4 4 does> (does> (运行时动作指令,运行时动作指令, 取下标取下标) )5 5 rangecheckrangecheck ( (函数调用,函数调用, 检查下标检查下标) )6 6 if (if (如果不越界如果不越界) )7 7 linearsublinearsub ( (函数调用,函数调用, 计算线性下标值计算线性下标值) )8 8 then (then (给出数组基地址和位移给出数组基地址和位移) )9 9 ;; (' (';;' '切换成解释执行,切换成解释执行, 数据类型定义毕数据类型定义毕) )10 10 11 211 2by3array box (by3array box (声明并分配名为声明并分配名为boxbox的数组变量的数组变量) )12 10 1 2 12 10 1 2 box (box (给给box(1box(1,,2)2)赋值赋值10)10)3.3.3 3.3.3 无类型语言束定无类型语言束定名字名字 束定束定length age 符号表符号表 运行时内存存储对象:类型标签运行时内存存储对象:类型标签scalar numberarray of 4 number不同时刻可以将一名字束定到不同存储对象上。
不同时刻可以将一名字束定到不同存储对象上APL语言可显式操纵束定:束定符号语言可显式操纵束定:束定符号‘← ← ’’APLAPL是无类型解释执行语言,以下是计算税金的程序:是无类型解释执行语言,以下是计算税金的程序: TAXCALCTAXCALC [1] 'ENTER GROSS PAY' [1] 'ENTER GROSS PAY' [2] GROSS ← □ // [2] GROSS ← □ //输入什么值输入什么值GROSSGROSS就是就是 什么类型什么类型,□,□表示终端输入表示终端输入 [3] → [3] →LESS × GROSS < 18000 //LESS × GROSS < 18000 //条件表达式为真转条件表达式为真转 LESSLESS标号句标号句 [4] [4] TAX ← .25 ×GROSS //TAX ← .25 ×GROSS //表达式结果值束定到表达式结果值束定到TAXTAX [5] →DISPLAY // [5] →DISPLAY //转到标号转到标号DISPLAYDISPLAY句句 [6] [6] LESS : TAX ←.22 × GROSSLESS : TAX ←.22 × GROSS [7] DISPLAY: 'THE TAX IS $' [7] DISPLAY: 'THE TAX IS $',, Ф TAXФ TAX [8] [8] 3.3.4 3.3.4 声明声明declarationdeclaration• 声明指明本程序用到的所有程序对象给出所有束定集合声明指明本程序用到的所有程序对象给出所有束定集合 声明给翻译必要信息声明给翻译必要信息 • 声明的确立产生事实上的束定声明的确立产生事实上的束定• 声明有显式的和隐式的声明有显式的和隐式的 例例 : Ada的显式声明:的显式声明:基本声明基本声明::=对象对象 |数数 |类型类型 |子类型子类型 |包包 |子程序子程序 |任务任务 |类属类属 |异常异常 |类属设例类属设例 |换名换名 |延迟延迟Ada的隐式声明:的隐式声明:块名字,循环名字,语句标号,循环控制变量,预定义运算符块名字,循环名字,语句标号,循环控制变量,预定义运算符声明种类声明种类•定义定义 —为对象名提供完整的束定信息。
声明可多次定义只次为对象名提供完整的束定信息声明可多次定义只次 —定义是从已有信息定义新信息定义是从已有信息定义新信息, Ada称之为称之为rename—函数式语言函数式语言, 无类型语言只有值定义无类型语言只有值定义, 没有变量声明没有变量声明 val even = fn(n::int) => ( n mod 2 = 0 ) 函数型构函数型构 函数体函数体 《束定》《束定》 函数定义函数定义 值定义值定义 fun even (n: int) = ( n mod 2 =0) 函数抽象函数抽象 体体•顺序声明的顺序声明的Ada例子例子package MANAGER is --package MANAGER is --声明程序包规格说明声明程序包规格说明 type PASSWORD is privatetype PASSWORD is private;; -- --声明私有类型未定义声明私有类型未定义 NULL_PASSWORD:coustantNULL_PASSWORD:coustant PASSWORD PASSWORD;;----立即立即用私有类型声明变量用私有类型声明变量 function GET return PASSWORDfunction GET return PASSWORD;; -- --返回私有类型函数返回私有类型函数 function IS_VALID(P: in PASSWORD) return BOOLEANfunction IS_VALID(P: in PASSWORD) return BOOLEAN;; PrivatePrivate type PASSWORD is range 0..700 type PASSWORD is range 0..700;; -- --定义私有类型定义私有类型 NULL_PASSWORD:constant PASSWORD:=0 NULL_PASSWORD:constant PASSWORD:=0 ;; -- --此时才定义此时才定义end MANAGERend MANAGER;;•顺序与并行声明顺序与并行声明 — 声明是顺序的声明是顺序的, 声明后立即有用声明后立即有用, 次序是重要的次序是重要的 D1;;D2;;D3 // D2中可利用中可利用D1的声明,的声明,D3可利用可利用D1,, //D2的声明(见的声明(见Ada例)例) —并行声明并行声明D1||D2它们一般相互独立它们一般相互独立, 确立次序不影响语义确立次序不影响语义 ML的例子:的例子: Val Pi = 3.14159 and sin = fn (x : real ) => (...) and cos = fn (x : real ) => (...) and tan = fn(x : real ) => sin(x)/con(x) 非法非法 并行声明仅当声明语句结束,各并行子声明同时生效并行声明仅当声明语句结束,各并行子声明同时生效 •递归声明递归声明 用声明定义自己的声明用声明定义自己的声明 D= …D… 直接递归。
名字一样就是递归直接递归名字一样就是递归 D1= … D2… 间接递归,或相互递归间接递归,或相互递归 D2= … D1… 指明递归(指明递归(ML)) val rec power = fn ( x : real,,n : int ) => if n =0 then 1.0 else if n < 0 then 1.0/power (x,,-n) else x * power (x,,n-1)如如果果没没有有rec两两次次power应应用用出出现现则则调调其其它它地地方方定定义义的的power而不是本身而不是本身声明作用域声明作用域•嵌套块与可见性嵌套块与可见性•标识符和全名标识符和全名作用起始作用起始 顺序顺序 每一子声明结束即起作用每一子声明结束即起作用 并行并行 所有子声明结束才起作用所有子声明结束才起作用 递归递归 只要遇见相同被声明符只要遇见相同被声明符, 就起作用就起作用可见性可见性Visiability指简单标识符可引用该对象。
嵌套有指简单标识符可引用该对象嵌套有复盖名字冲突复盖名字冲突, 同样名字出现在嵌套块中同样名字出现在嵌套块中, 按最近声明按最近声明解释被覆盖的只能加块名前缀(称全名),如解释被覆盖的只能加块名前缀(称全名),如outer.x才可使用才可使用C++用用‘::’作用域分辨符作用域分辨符 正因为有冲突所以内部束定时只能束定全名正因为有冲突所以内部束定时只能束定全名, A.x,,B.y块声明块声明• 把一个语句扩大到块,封闭的块有局部声明把一个语句扩大到块,封闭的块有局部声明 Begin DBegin D;; C end; C end; D是声明集,是声明集,C是语句集说明作用域只限于此块是语句集说明作用域只限于此块• 块声明:含有局部声明的声明块声明:含有局部声明的声明, 子声明的束定用于声明子声明的束定用于声明 ML: local fun multiple (n:int,d:int) = (n mod d = 0) //函数说明函数说明 in fun leap (y:int) = (multiple (y,4) //用于另一函数定义用于另一函数定义 andalso not multiple (y,1000)) orelse multiple (y,400) end3.3.5 3.3.5 束定作用域与释义束定作用域与释义束定与环境束定与环境 在束定集的作用范围内称环境在束定集的作用范围内称环境(environment)。
词法作用域词法作用域 程程序序正正文文给给出出的的嵌嵌套套声声明明的的作作用用域域称称词词法法作作用用域域, 是是词词法法子子 集和覆盖父辈集和覆盖父辈 PROGRAM APROGRAM A;; VAR x VAR x,,y:Integery:Integer;; FUNCTION B(d:Integer): Integer FUNCTION B(d:Integer): Integer;; BEGIN b:=x+d END BEGIN b:=x+d END;; FUNCTION C(d:Integer):Real FUNCTION C(d:Integer):Real;; VAR x:Real VAR x:Real;; BEGIN x:= 5.1 BEGIN x:= 5.1;; C:=x+B(d+1) END C:=x+B(d+1) END;; BEGIN x:= 3 BEGIN x:= 3;;y := 0 y := 0 WritelnWriteln (C(y)) (C(y)) END. END. 当(当(x=3,y=0)x=3,y=0)时时 C(0)=9.1 C(0)=9.1 • 动态作用域和递归动态作用域和递归 — 递归每展开一层束定一次,且保留至递归终止。
运行中动递归每展开一层束定一次,且保留至递归终止运行中动 态束定因声明作用域大小不同到底多大静态不得而知因声明作用域大小不同到底多大静态不得而知 — 词法作用域和动态作用域求值差异词法作用域和动态作用域求值差异我们沿用上例以我们沿用上例以LISP求求C((0):): ( (defundefun A A (x (x,,y) y) ( (printcprintc( C y))) //( C y))) //以最近束定释义以最近束定释义 ( (defundefun B (d) B (d) (+d x)) (+d x)) ( (defundefun C (d) C (d) (let (x'5.1)) (let (x'5.1)) (+x(B (+d 1)))) (+x(B (+d 1)))) (A 3 0) =( (A 3 0) =(printcprintc (C 0) //x=3 (C 0) //x=3,, y=0 y=0 =(printc(+5.1(B(+ 0 1)))) //x=5.1 =(printc(+5.1(B(+ 0 1)))) //x=5.1,, d=0 d=0 = (printc(+5.1 (B(1)))) = (printc(+5.1 (B(1)))) =(printc(+5.1(+1 5.1))) //d=1 =(printc(+5.1(+1 5.1))) //d=1,, x x就近取值就近取值=5.1=5.1 = =printc(+5.1 6.1))printc(+5.1 6.1)) =( =(printcprintc 11.2) // 11.2) //上例是上例是9.19.1作用域与生命期匹配作用域与生命期匹配• 形参与实参结合形参与实参结合(束定束定)实参(程序对象)寿命比该函数实参(程序对象)寿命比该函数/过程长过程长• 静态静态(static)对象声明其寿命比所在块长。
对象声明其寿命比所在块长• 指针实现的记录或结构的树或链其第一头指针在堆中分配,寿指针实现的记录或结构的树或链其第一头指针在堆中分配,寿 命比堆栈帧内束定的变量长命比堆栈帧内束定的变量长• 文件变量的寿命一般比程序中对象长文件变量的寿命一般比程序中对象长束定机制与语言翻译器束定机制与语言翻译器• 类型语言类型语言──编译高效,符号表不存在局部束定要保留编译高效,符号表不存在局部束定要保留• 无类型语言无类型语言──解释方便,符号表存在解释方便,符号表存在• 词法作用域高效词法作用域高效, 排错易,但要事先知道如何束定排错易,但要事先知道如何束定• 动态作用域灵活,方便,低效有递归不可少动态作用域灵活,方便,低效有递归不可少3.4 3.4 计算组织计算组织- -程序控制程序控制•冯·诺依曼机器模型变量的时空特性对程序中求值的次序是十分敏感的•表达式的求值次序是最低层的程序控制,在它的上层是四类控制:顺序控制、选择控制、重复(迭代)、函数或过程调用•再上一层是对程序模块的控制包括一个程序的各模块组织以及它们与环境的相互关系•并发控制也是一类控制,它可以在语句级,特征块和模块级实施并发控制, 3.4.1 一般概述 •语句级控制由于GOTO危害导致结构化程序。
•1966年Boehm和Jacopini回答了这个问题:任何流程图的计算逻辑都可以用顺序组、 条件选择组、迭代组三种程序结构实现•保留GOTO的积极作用限制GOTO的副效应, 把它们改头换面变为比较安全的顺序控制器(sequencer) •命令式语言只要有赋值语句V∶=EXP,简单的逻辑条件IF(e)和GOTO语句就可以编出一切计算程序(输入/出除外)顺序控制顺序控制•S1; S2 进一步扩展可为: S1; S2; …;Sn•AdaAda语句较全:语句较全: 简单_语句::=空_语句 |赋值_语句 | 过程调用_语句 |goto_语句 | 入口调用_语句 |出口_语句 | 返回_语句 |引发_语句 | 夭折_语句 |延迟_语句 | 代码_语句 其中空_语句,赋值_语句 ,延迟语句,代码_语句不影响控制和转移,exit(出口)语句,raise(引发)语句,abort(夭折)语句,return(返回)语句都是顺序控制器。
条件选择控制条件选择控制• if(e)无结构Algol60改为if…then…else结构, 退化是if…then• 悬挂else if E1 then if E2 then S1 else S2 E1为‘真’‘假’均可执行S2 解决if E1 then begin if E2 then S1 else S2 end Pascal,Algol,C if E1 then begin if E2 then S1 end else S2 if E1 then if E2 then S1 endif else S2 endif Fortran-77,Ada if E1 then if E2 then S1 else S2 endif endif └───就近匹配───┘ •嵌套if和case e是同一表达式仅值不同 可改换ease Ada 的case语句 IF exp1 THEN ST1 ELSEIF exp2 THEN ST2 ELSEIF exp... ... ELSE SF3 ENDIF case Exp is switch (exp) when v1=> S1; case v1: S1; when v2=> S2; break; ... case v2: S2; when vm|vn=>Sn; break; when others => St; default: St; end case; next; 执行一个Si跳到end case 没有break要顺序执行,直到next e1e2e3S1S2S3 C C以条件表达式实现选择控制以条件表达式实现选择控制 a=b 迭代控制迭代控制迭代一般用于处理同构的、聚集数据结构的重复操作,诸如数组、表、文件 •显式迭代(循环),显示迭代有以下几种: 无限循环·· 有限循环的三种形式: while_do do_until do_while_doFORTRAN 初始 上界 增量DO L I=EXP1, EXP2, EXP3Adafor BACKDAY in reverse DAY 'RANGE loop 在循环控制变量和表达式约定上几十年来一直在演变 C采用迭代元素(Iteration element)iterate (<控制元素>)<迭代域> = iterate (; ; 可以有以下六种写法: [1] 先赋sum初值进入正规for 循环: sum=0; sum += current ->value; for (current=list; current != NULL; current =current -> next) sum += current->value //循环控制变量current就用表list [2] sum在循环内赋初值: for (current=list, sum=0; current != NULL; current =current -> next) sum += current->value [3] 循环控制变量可以在for以外赋初值, 增量在体内赋值: current = list, sum=0; for ( ; current != NULL; ) sum += current->value, current = current->next; //用','号连接的两语句成为一个命令表达式[4] 循环条件为空,用条件表达式完成: current = list, sum = 0; for(; ; ) if (current == NULL) break; else sum += current->value, current->next; //不提倡此种风格[5] 完全不用循环体,全部在循环条件中完成 for (current = list, sum = 0; (current = current->next)!= NULL; sum += current->value); //效率最高. 但list不能为NULL表 [6] 逻辑混乱,也能正确计算: current = list, sum = 0; for(;current=current->next;sum += current->value) if (current == NULL) break; //最坏的风格,list也不能为NULL表3.4.2 异常处理异常处理 程序无法执行下去,也就是出现了异常(exception)情况。 在早期语言的程序中,出现了这种情况就中断程序的执行,交由操作系统的运行程序处理现在向用户开放,Ada,C++,Java 定义用户定义 : <异常名>:exception; 系统定义的异常(Ada): · CONSTRAINT_ERROR 凡取值超出指定范围叫约束异常 · NUMBER_ERROR 数值运算结果值实现已不能表达 叫数值异常 · STORAGE_ERROR 动态存储分配不够时,叫存储异常 异常定义与处理段• TASKING_ERROR 任务通信期间发生的异常叫任务异常 PROGRAM_ERROR 除上四种而外程序中发生的一切异常, 都叫程序异常.如子程序未确立就调用等 • 预定义的可以显式也可以隐式引发,而用户定义的异常必须 显式引发• 引发语句: raise [<异常名>];• 引发语句可出现在任何可执行语句所在的地方一旦引发,则本程序块挂起转而执行本块异常处理段中同名的异常处理程序每个程序块都可以在该块末尾设立异常处理段: declare: <正常程序声明>; [<异常声明>]; begin: <正常程序代码>; [<引发语句>];exception: when<异常名> => <处理代码> --异常处理段 when。 --一般再次引发原异常(缺省名字) [<引发语句>]end;Java的异常处理语句,try_catch_finally: try{ //可能有异常语句组 //用throw 显式抛出异常 //也可以隐式自动抛出异常 } catch(<异常类型-1> <异常对象名>){ //异常处理段-1 //catch(捕捉)try 中抛出的异常对象 //不匹配则跳过本catch 不执行 } catch (<异常类型-2> <异常对象名>){ //异常对象段-2 //匹配执行后跳过所有catch } …… finally {//善后处理段 //只要进入try块,本段必须执行 //C++无此机制,其余同 }· Ada的嵌套异常及异常传播的 with TEXT_IO, MATH_FUNCTIONS; use TEXT_IO, MATH_FUNCTIONS; procedure ONE_CIRCLE (DIAM:Float) is RADIUS, CIRCUMFERENCE, AREA:Float; begin CIRCUMFERENCE:=DIAM*22.0/7.0; RADIUS:=DIAM/2.0; AREA:=RADIUS*RADIUS*22.0/7.0; PRINT_CIRCLE (DIAM, RADIUS, CIRCUMFERENCE, AREA); [7] exception when NUMBER_ERROR => raise; end ONE_CIRCLE; procedure CIRCLES is DIAMETER:Float; MAXIMUM: constant Float:=1.0e6; [0]TOO_BIG_ERROR: exception; PUT ("please enter an float diameter."); begin [1]loop begin GET (DIAMETER); if DIAMETER > MAXIMUM then [2] raise TOO_BIG_ERROR; else [3] exit when DIAMETER <=0; end if; [8] ONE_CIRCLES(DIAMETER); PUT("Please enter another diameter(0 toquit):"); [4] exception when TOO_BIG_ERROR=> PUT ("Diameter too large ! please reenter."); [5]end ;[6]end loop; PUT (“processing finished.”); PUT New_line; end CIRCLE;3 3.5.5 函数和过程函数和过程•命令式语言中子程序有两种形式:函数(必须返回值)也叫函数,过程(实施一组动作)也叫子例程subroutine。 它们是程序的第一次分割,这种分割的好处:–实施的功能单一,便于调试;–相对独立,便于多人分工完成,且时间不受约束;–相对封闭,人们易于控制,是分解复杂性的有力措施•子程序和主程序联系的接口特别重要在这个界面上要指出该例程的数据特征, 即输入什么输出什么而整个子程序体是完成从输入到输出的实现手段–界面指出“做什么?”,而子程序体回答“怎么做”–80年代程序完成第二次分割: 将子程序定义(即界面)和子程序体显式的分开, 成为相对独立的规格说明(Specification)和体(body)3.5.13.5.1 函数和过程抽象函数和过程抽象•函数抽象是用一个简单的名字抽象代表一个函数函数由函数型构(Signature)和函数体(body)组成函数计算的目的是求值函数体等同于一个复合的表达式函数抽象是对表达式的抽象•过程抽象是用一个简单的名字抽象代表一个计算过程过程由过程型构和过程体组成过程调用的目的是执行一组命令过程抽象是对命令(即语句)集的抽象•函数由函数型构和函数体组成形式是: function FUNC (fp1,fp2,...):returntype;//函数型构 B; //函数体,可包括任何声明和语句 其中fp1,fp2,…为形式参数,也叫形式变元(argument)returntype 为函数返回值的类型· 函数引用是应用函数的唯一手段,它在同名的函数名之下给出实 在参数(实在变元): FUNC (ap1,ap2,...);• 各种语言函数定义(a) FORTRAN INTEGER FUNCTION FACT(N) //前缀指明返回类型 INTEGER N,I,F //参数类型在此声明 F = 1 DO 10 I = 2,N F = F*1 10 CONTINUE FACT = F //必须至少定义函数名一次 RETURN //至少有一返回语句 END(b) Pascal FUNCTION fact (n:Integer) :Integer; //参数类型在变元表中定义, BEGIN //后缀指明返回类型 fact ∶= 1; IF n = 1 OR n = 0 THEN Return ELSE Fact ∶= n*fact(n-1); //也要定义函数名 END(c) C int fact (n){ //前缀指明返回类型 int n; //参数在体中声名类型 int i, f; //ANSI C改参数原型 f = 1; if(n>1) for (i = 2; i<= n; i++) f * = i; return (f); } //返回表达式f的结果值(d) Ada function FACT (N: POSITIVE) return POSITIVE is begin //参数类型在表中定义, if N = 1 then //后缀指明返回类型 return(1); else return (N*FACT(N-1)); //返回表达式值 endif; end FACT;(e) ML fun fact (n :int) = //函数定义,参数类型表中定义 if n = 1 then 1 else n*fact (n-1) val fact = fn(n:int) => //用函数定义值 if n = 1 then 1 else n*fact (n-1)续• 多重入口和指定返回FORTRAN 的多重入口示例 SUBROUTINE DEG(R,THETA,X,Y) C = 3.14159/180.0 THETA = C * THETA ENTRY RAD (R,THETA,X,Y) X = R * COS(THETA) Y = R * SIN(THETA) RETURN END若THETA是度数值时,则调用语句为: CALL DEG (R,THETA,X,Y) //入口在子程序顶部若THETA是弧度值时,则: CALL RAD(R,THERA,X,Y) //入口在子程序中FORTRAN的指定返回SUBROUTINE RM(X,Y,*,*,*) . . .RETURN2 //返回语句标号80 . . .RETURN1 //返回语句标号70 . . .RETURN3 //返回语句标号120 . . .END CALL RM(A,B,70,80,120) 形-实参数表中元素个数,次序,类型应一致。 早期语言都严格遵此准则近代语言提供了较多的灵活表示法 Ada引入缺省参数,实参个数可少于形参个数;指明参数结合不考虑次序;Ada引入参数模式in,out,inout指明只读,只写,读写参数 C语言允许任意多个参数的调用如内定义函数printf()调用时可以写任意个输出,只是第一参数中的格式个数与参数个数对应• 过程定义与调用 过程子程序定义形式 procedure PROC (fp1,fp2,...) //过程型构 B; //子程序体包含局声明 对应的过程调用是: PROC (ap1, ap2, ...); C语言一切过程,包括主程序都是函数过程它以void(无值)关键字代替函数类型指明符,实施子程序过程语义 •引用或调用的形式• 无参过程-- 函数和过程的参数表均可为空有的语言保留(),有的只有一个名字一般无参过程也要更新过程内部的值函数过程还会返回不同的值全局量在函数中有效改变了全局量两次调用结果值当然不一样这就是函数的副作用(side effect) -- 有副作用的函数 C 在很大程度上利用函数副作用,例如,当需要跳过空白时写: while ((c = getch()) == ''); -- Ada中常用的随机数: function RANDOM return FLOAT range 0.0…1.0;引用时, 若FIELD已声明为常量: RESULT ∶= RANDOM * FIELD;RANDOM若无副作用RESULT值不可能改变。 3.5.2 参数机制语言中第一类对象均可作函数/过程参数 由于变量的时空特性,传递的形-实参数可以有许多不同的实现结合的办法,即参数机制传值调用(call-by-value)[1]实参表达式先求值[2]将值复制给对应的形参形参和实参有同样大小的存储[3]过程运行后一般不再将改变了的形参值复制回实参 Pascal中的传值调用 PROCEDURE test1(J2,A2:Integer;P2:list) BEGINWriteln(J2,A2,P2↑.value);J2∶= J2 + 1;P2∶= P2↑.next;Writeln(J2,A2,P2↑.value) END. 调用程序有: test1(J1,A1[J1],P1↑.next);第一次打印为: 1 30 %第二次打印为: 2 30 $ 传名调用(call-by-name)传名在过程/函数中加工的就是实参已分配的值,因此不需付出双倍存储代价但传名过程的虚实结合是将程序体中所有形参出现的地方均以实参变元名置换这样出现几次算几次效率是低的传名调用程序示例由于Pascal无传名机制,此处作一点扩充:PROCEDURE test2(NAME J2,A2:Integer;NAME P2:List); 函数体同test1执行同样调用:test2(J1,A1[J1],P1↑.next);名结合后打印: 1 30 %执行后结果是: 2 45 $引用调用引用调用(call_by_reference)引用参数实现时, 编译器在子程序堆栈中设许多中间指针, 将形参名束定于此指针,而该指针的值是实参变量的地址(在主程序堆栈框架内),在子程序中每次对形参变量的存取都是自动地递引用到实参存储对象上。 引用调用的Pascal示例: PROCEDURE test3(VAR J2,A2:Integer;VAR P2:List); 函数体同test1相应的调用程序是:test3(J1,A1[J1],P1↑.next); 第一次打印是: 1 30 %第二次打印是: 2 30 $引用调用图示参数模式与返回调用(call-by-return)显式指明参数传递模式,可以为编译实现提供信息 fun_name(x,y:Real; VAR s,q:Integer)...x,y传值实现,它只读s,q引用实现,可读/写Ada只规定参数模式in,out,inout,传递方向的模式(mode)由编译选择实现方式: proc_name (X,Y:in Real;S:inout Integer;Q:outInteger)…in模式可不写出(缺省) 函数只能有in的模式,过程都有,且出现次序不受限制x,y因在子程序中只读,传值实现可保证不受破坏s读/写用引用实现,而q是只写参数,传值和引用都不能保证”只”写实现返回调用机制有两种办法:其一是复制另一种办法是引用实现增加”只写”保护 值-返回调用(call-by-value-and-return)是对by-reference的改进,因多进程竞争数据资源时多重引用(束定)易于引起混乱P2返回值由P2定 返回值由P1定 正常顺序执行对于并发多任务宜只读──只写 值与返回调用机制是把值调用和返回调用组合起来,实现调用程序双向通道,这对于有多个存储器的多处理器系统和网络分布式系统值调用极度安全在子程序执行期间因不是束定, 形参变量的值不会中途改变, 复制回去和拷贝进来处可设断点检查P1P2P1P2P1P2 指针参数(call_by_point)•指针作为参数其实现方式一般是复制机制,它复制的是地址(指针内容)•注意和引用调用之同异例:指针Pascal引用版: 交换两变量的内容PROCEDURE swap1( VAR a,b:Integer); VAR t:Integer;BEGIN T ∶= a; a ∶= b; b ∶= tEND;调用程序片断: j = 3; k = 5; swap1(j,k); //结果j = 5, k = 3J =>3K=>5Caller-frame<= a<= t<= b3<=========<========= Swapl-frame指针版:变换两变量的内容 TYPE int_ptr = ↑Integer; VAR jp, kp:int_ptr; PROCEDURE swap2 (a,b:int_ptr); VAR t:Integer; BEGIN t∶= a↑; a↑∶= b↑; b↑∶= t END;相应调用程序片断: NEW (jp); jp↑= 3; NEW ( (kp); kp↑∶= 5; Swap2(jp,kp);C语言的指针参数传递 void swap3(int *a, int*b) { int t; t = *a; *a = *b; *b = t; }形参是两指针, 实参不用指针的版本: main(){ int j = 3; k = 5; //声明并初始化两整数 swap3(&j,&k); //类型匹配吗?}3.63.6 抽象与封装抽象与封装• 函数和过程是封装的程序实体,它有数据和操作,规格说明(型构)和过程体,一体使于人们控制复杂性• Pascal统一的嵌套结构不造于大型程序数据2sub1--sub10数据4sub22--sub28数据1sub11--sub15数据3sub16--sub21main续• 将相关的数据和操作封装成大模块(若干类型,若干过程/函数) 结构上形成包package或模块 Modula•包是可分别编译。 随时连接软件资源,是解决复杂系统的有力手段•包的规格说明和包体显式分开语义上正好是“作什麽”,“怎麽做”3.6.1 模块和包• 规格说明和体在表示结构上的分离有利于修改,维护• 封装实现数据隐藏,有利于安全• 规格说明是程序包的抽象,有利于复杂系统简化• 模块模块((包包))封装数据与操作,它有可控界面,外界不封装数据与操作,它有可控界面,外界不能操纵私有数据引出公有能操纵私有数据引出公有((publicpublic包的使用者可见)包的使用者可见)、私、私有有((privateprivate本包所有操作可访问,包外不可见)本包所有操作可访问,包外不可见)、保护、保护((protected,protected,包外不可见,但本包的子包可见)包外不可见,但本包的子包可见)概念概念• 包只是包只是以封装手段以封装手段, ,可可有有/ /可没有可没有逻辑逻辑语语义义----只有数据无操作,数据块只有数据无操作,数据块BLOCK DATABLOCK DATA((FORTRANFORTRAN))----只有操作无共享数据如函数包,数只有操作无共享数据如函数包,数学学库库----有数据有操作有数据有操作,,一口对外可模拟自动机一口对外可模拟自动机----有数据有操作有数据有操作,,模拟客观世界对象模拟客观世界对象——增加程序表达能力增加程序表达能力----封装的包可实现复杂的数据类型封装的包可实现复杂的数据类型ADTADT Ada 的复数程序包 package COMPLEX is type NUMBER is record REAL_PART:FLOAT; IMAG_PART:FLOAT; end record; function "+"(A,B: in NUMBER) return NUMBER; function "-"(A,B: in NUMBER) return NUMBER; function "*"(A,B: in NUMBER) return NUMBER; end COMPLEX; package body COMPLEX is <声明私有数据/操作> <实现规格说明中定义的每一个过程/函数> <实现本包私有过程/函数> endCOMPLEX; 有了这个程序包我们可以编出复数应用程序: with COMPLEX; use COMPLEX; procedure MAIN is COMP_1: NUMBER ∶= (1.0,2.0);--1+2i COMP_2: NUMBER : =(3.0,4.0); -- 3+4i W, X,Z: NUMBER; begin W ∶= COMP_1 + COMP_2; --W = 4+6i X ∶= COMP_2 - COMP_1; --X = 2+6i Z ∶= COMP_1 * COMP_2; --Z = -5+10i end MAIN;3.6.2 3.6.2 抽象数据类型抽象数据类型 •数据抽象数据抽象数据抽象是抽象数据类型的方法学数据抽象是抽象数据类型的方法学定义一组数据集定义一组数据集V V,,以及其上的操作集以及其上的操作集OpOp,,构成构成ADTADT((A Abstract Data Type)) T=( V , Op )T=( V , Op )----什么和怎么做分开什么和怎么做分开(规格说明和体规格说明和体)--实现数据隐藏(体中声明的数据和操作外界不可见)实现数据隐藏(体中声明的数据和操作外界不可见)--分别开发分别编译(可做大程序)分别开发分别编译(可做大程序)--简化复杂性便于调试(利用抽象实现分治)简化复杂性便于调试(利用抽象实现分治)--构造新类型,计算直观方便(面向对象的基础)构造新类型,计算直观方便(面向对象的基础)•构造新类型构造新类型Swith COMPLEY use COMPLEY;Procedure MAIN is C1:NUMBER is =(1.0,2.0); C2:NUMBER is =(3.0,4.0); W ,X, Z :NUMBER;begin W:=C1+C2; X :=C2-C1; Z :=C1*C2;end MAIN-*+NumberLEXCOMP• 构造函数和析构函数变量—类型 V : integer;以类型指明程序对象变量—抽象数据类型, 声明时要指明如何构造C:NUMBER:=(1.0,2.0) 较简单可用赋初值办法,复杂在运行中由构造函数(constructor)完成 C = ADT_Name(<构造参数>) {<构造操作即函数体>}构造函数(constructor) 和ADT同名不再使用的程序对象,用析构函数(destructor)显式删除一般形式是:ADT_Name() {…} 和ADT同名 ~ ADT_Name( ){…}• C C语言以文件实现抽象数据类型语言以文件实现抽象数据类型 C C语言语言4 4种文件种文件 --头文件头文件““modules.h” modules.h” 定义宏和类型声明定义宏和类型声明 --主模块主模块““name.c” name.c” 给出新类型的数据和操作定义给出新类型的数据和操作定义 --其它模块其它模块““other.c” other.c” 实现头文件中声明实现头文件中声明 --通过通过makefilemakefile指明各文件关系指明各文件关系 make SOMETYPEmake SOMETYPE SOMETYPE SOMETYPE::name.o other.o //name.o other.o //连续两模块的目标码连续两模块的目标码 文件取名文件取名SOMETYPESOMETYPE CC_o SOMETYPE name.o other.o // CC_o SOMETYPE name.o other.o //编译并连成为编译并连成为 SOMETYPESOMETYPE的目标文件的目标文件 name.o : name.c modules.h //name.o : name.c modules.h //两源文件连成为两源文件连成为name.oname.o CC_o name.o name.c // CC_o name.o name.c //源文件编译后形成目标文件源文件编译后形成目标文件 other.o : other.c modules.hother.o : other.c modules.h CC_o other.o other.c //SOMETYPE CC_o other.o other.c //SOMETYPE 如预定义的了如预定义的了3.6.33.6.3 类属类属 函数是表达式集的抽象、过程是命令集的抽象,类属(generic)是声明集的抽象。 即声明的变量、类型、子程序都是参数化的 Ada类属子程序: generic type ELEMENT is pravite; --类属类型参数 procedure EXCHANGE (FIRST, SECOND:in out ELEMENT); TEMP:ELEMENT; --程序中用类属形参 begin TEMP ∶= FIRST; FIRST ∶= SECOND; SECOND ∶= FIRST; end EXCHANGE; 关键字procedure换成package即为类属包 参数化类型与实际类型结合由类属设例指明:procedure INT_EXCHANGE is new EXCHANGE (Integer);实在的Integer与ELEMENT匹配, 即在procedure中任何出现ELEMENT的地方以Integer代,等于有了整数的 EXCHANGE 如同样板,可以写出多个过程: procedure CHAR_EXCHANGE is new EXCHANGE ( Character); procedure SMALLINT_EXCHANGE is new EXCHANGE (MY_INT);类属定义一般形式类属定义一般形式 generic <类属值> | <类属变量> | <类属类型> | < 类属子程序> package GENERIC_PKG is --用类属参数的包 . . . end GENERIC_PKG; 类属实例化是在有了实在参数以后作实例声明: package INS_PKG is new GENERIC_PKG(<实参变元>); 类属形实参数的个数、 次序要匹配。 •类属参数类属参数 原则上声明中的所有程序对象都可以参数化,其实现机原则上声明中的所有程序对象都可以参数化,其实现机制如同子程序中参数结合,制如同子程序中参数结合, 类属值一般是复制机制,类属值一般是复制机制, 类属类属对象对象( (变量变量) )、类型、子程序用引用机制类型、子程序用引用机制 取决于实现取决于实现类属声明是类属声明是 in in 模式模式变量变量 可换值可换值 inoutinout模式模式变量变量 可换变量可换变量类属类型类属类型private private 私有私有 可以和任何类型匹配可以和任何类型匹配 (< >) (< >) 枚举枚举 可以和任何枚举类型匹配可以和任何枚举类型匹配 range < > range < > 整整 可以和任何整型匹配可以和任何整型匹配 delta < > delta < > 定点定点 可以和任何定点类型匹配可以和任何定点类型匹配 digits < > digits < > 浮点浮点 可以和任何浮点类型匹配可以和任何浮点类型匹配类属子程序类属子程序with procedure( 被分数据按数组类型设计,它的元素类型,按升序还是降序,数组下标,都参数化: generic type ITEM is private --数组元素类型参数化 type SEQUENCE is array (Integer range < > ) of ITEM; --数组类型参数化ITEM马上就用了 with function PRECEDES (x,y:ITEM ) return Boolean; --参数化操作未定升降序 package SORTING is procedure SORT (S: in SEQUENCE); procedure MERGE (S1, S2: in SEQUENCE; S: out SEQUENCE); end SORTING;有两个实在函数: function "<= " (X,Y: Float) return Boolean; function ">=" (X,Y: Float) return Boolean; 那么就可以作以下设例声明: type FLPAT_SEQUENCE is array (Integer range < >) of Float; package ASCENDING is new SORTING(Float,FLOAT_SEQUENCE,"<="); package DESCENDING is new SORTING(Float,FLOAT_SEQUENCE,">="); 这样, 就有了一个升序, 一个降序的两个程序包。 续•类属类属动态动态实现问题实现问题类属程序是抽象程序可以编译,但不可执行,类属程序是抽象程序可以编译,但不可执行,每次执行的是实例程序每次执行的是实例程序其编译执行如同其编译执行如同C C语言语言之宏替换,也就是参数静态束定于实参,强类之宏替换,也就是参数静态束定于实参,强类型语言可以做到型语言可以做到类属程序只有一个类属程序只有一个,,运行时动态束定就不需要运行时动态束定就不需要显式显式设例设例 因此因此,,动态设例的程序表达能力更动态设例的程序表达能力更强强,程序易于扩充程序易于扩充。
