还剩13页未读,继续阅读
本资源只提供10页预览,全部文档请下载后查看!喜欢就下载吧,查找使用更方便
文本内容:
第章束定5在程序中我们靠各种名字操纵程序对象,以此编制有声有色的程序上一章因名、值分离我们引出存储对象概念,但只有当名字和存储对象结合在一起才构成程序对象,所以,名字不等于程序对象,它只有通过束定(Binding,我国有译绑定,定连,联编的)才能成为程序对象例如,一个变量声明了未分配存储,此时该变量名没有束定,只有在运行时分配存储才成为定义的程序对象发明名字是高级程序语言重大的进步,名字能为程序员提供可见可识的语义但机器只把名字看成符号串,它没有识别名字表达的语义的能力,它只知道束定,把sin(x)与它的函数体束定起来sin(x)才能实施它的语义“对x求正弦”这样人们希望的语义才成为现实本章学习束定和各种束定机制,声明、声明的种类和作用域等有关基本概念本章还讨论由于嵌套块结构语言带来较为深入的几个问题;不同束定机制对语言释义的差异;名字的作用域与程序对象生命期匹配问题;束定机制与语言翻译器的关系名字与束定
5.1如果一个名字只指称一个程序对象,这本来是没有什么值得讨论的问题,多数程序设计语言动态创建程序对象时一个名字可以在不同时候代表不同的程序对象如嵌套程序外块、内块可以有相同的名字一个程序对象可以有好几个名字,如引用变量名,指针名极端情况下程序对象可以没有名字(无名类型),名字可以没有对象(悬挂指针)所以块结构、参数传递、递归、指针、引用、别名等机制使名字空间大为复杂化了它打破了“一个程序对象对应一个名字”的简单概念把名字和存储对象联系起来叫束定,更概括一些,束定是将名字(标识符)和可束定体联系起来所谓可束定体(bindable)是能反映(操作)语义的存储块,如常量、变量的存储体、函数体、过程体、类型、异常指明束定一般由程序员在程序正文的声明中作出,如int a,*p;标识符a,p束定为int类型的变量、指针然而真正束定是编译器或解释器完成的它给a,P分配了存储对象并填上名字一地址对照表,某个地址上的存储对象就“是”该名字的对应物反过来说也行可以把束定看作是完成名字指向存储对象指针的过程,但它和指针不同,首先它是编译(或解释)器做的为每一个名字分配其语法要求的存储,也可以跨越时间,编译时占个位置,运行时再分配(实现束定)再者,翻译器可以自动递引用束定而不能自动递引用指针它是跨越程序世界和机器世界的概念在一个程序的生命期期间,一旦束定不再改变叫静态束定,反之,一个名字束定多个存储对象(不同时间)叫动态束定还有一种介乎动静之间叫块结构束定静态束定一般在运行之前完成(编译时做一部分,连接时做一部分,装载后确立(elaboration)时再做完),也叫早束定程序运行时动态完成叫晚束定一个程序设计语言采用什么类型机制和程序运行机制决定了它以早束定还是以晚束定实现我们现在分析已有语言的束定机制⑵cf实数
3000000.0END;bf布尔变量BEGINzf整常量1Q-过程抽象如有递归END.
4.2词法作用域与动态作用域
5.我们把程序正文给出的嵌套声明作用域叫词法作用域Lexical scope嵌套块是词法子辈,被嵌套块是父辈,它们的作用域相互复盖,以最近原则束定Pascal是词法作用域的语言例5-15以词法作用域解释本程序结果PROGRAM A;VAR x,y:Integer;FUNCTION Bd:Integer:Integer;BEGIN B:=x+d END;FUNCTION Cd:Integer:Real;VAR x:Real;BEGIN x:=
5.1;C:=x+Bd+l END;BEGIN x:=3;y:=0Writein CyEND.读者如跟踪其打印结果C0=
9.1当x=3,y=0o词法作用域一般以上章介绍的运行时堆栈和堆栈帧实现编译后的执行代码和全局量先入运行时堆栈成第一个堆栈帧,如图5-5所示当程序执行进入子程序或块时,它的参数,局部变量另辟堆栈帧存放.本例为函数C的d,xo当然还要包括子程序的返回地址动态父辈,并且成动态链和指示词法父辈的指针静态链静态链以全名束定实现,它提供了词法作用域描述Z-----------------X DL块A丁程序代码•~SL:静态链x=3y=0DL:动态链-----------------堆栈帧CL-d=0x=
5.
4.3词法作用域规则和动态作用域规则所产生的计算结果只有在非常简单的情况下是一致的,而例5-16LISP动态作用域defun Ax,y〃定义一个函数名为Aprintc Cy〃具有两个参数x,y〃函数体是打印函数printc;defun B它打印函数C应用到y上的结果值d〃定义一个函数B+d x〃只有一个参数d〃函数体是d和x相加的返回值,注意,defun Cx未定义,它是全局参数引用d〃定义一个函数Clet x5,1〃只有一个参数d+xB+d1〃设置局部参数x的值为
5.1〃函数体是把局部的d加1,后结果值应用其A30返回值再加局部的x,得C的返回值〃把以上定义的函数的A应用于实参是3,0其引用过程是的调用A30=printc C0=printc+
5.1B+01//x=3,y=0=printc+
5.1Bl//x=
5.1,d=0=printc+
5.1+
15.1=printc+
5.
16.1//d=l,x就近取值二
5.1=printc
11.2//例5-15是
9.1多数情况下不同以下例子是和例5-15完全一样的程序,只是所用作用域规则不同这两种作用域的束定机制当有标识符多次定义时结果不同Pascal,C,Ada按词法作用域将标识符换成全名,只有递归情况下,无法换成全名,才按动态作用域释义所以它们两者都用作用域与生命期匹配的问题
5.
4.4名字的作用域和它所代表的程序对象的生命期并不是总是匹配的最突出的例子是函数或过程调用中的引用参数传递(VAR参数)调用期间形参的名字与实参表示的程序对象束定这个程序对象的生命期要比形参名字在其整个作用域的执行期要长因为被调用的块要先出口,自然比调用块寿命短就过程或函数而言对象的生命期比名字作用域要长这种情况还不止形、实参调用Pascal和C中用指针动态分配的链表和树结构也有这种情况因为这些指针是结构的成分可不在堆中分配每当产生一新堆栈帧的结点时,指向它的指针在其父结点的成分内填上地址就可以了而指向该结构的头指针是在堆中分配的,只要不除配它的寿命持久还可以把它拷贝到外块寿命更长的指针上C语言的局部静态变量也正是利用这种不匹配,达到数据保护的目的静态量顾名思义和全局量一样,静态分配到全局数据区而它的名字的作用域仅限于该局部块每当执行进入到该局部块,这个静态变量的名字束定到静态分配的存储上,此时是可访问的每当出了该局部块,名字失去定义而又没有其它名字和它束定任何局部块都不能访问它数据安全保存再次进入重新束定…类似的FORTRAN的公共语句静态分配的公共数据区,其中的程序对象的生命期,也比过程或函数模块内公用区中变量的名字作用域长意思和C的静态变量相近,但它必须共享,不安全,不如C的独享束定机制与语言翻译器
5.5我们已经看到束定机制不同对程序语义有影响,语言翻译器是采用编译型还是解释型对实施束定时采用什么束定机制也有影响,本节讨论这个问题编译器和解释器最大的不同是解释器一面翻译一面执行,因此它的可用信息比编译器在运行前编译要多多了根据运行上下文解释器可以知道当前待分配的数据的数量和大小,特别是存放大量数据的数组编译器于运行之先就要分配存储,它不得不每次都开辟较大的数组以放下最大可能尺寸的数据若程序数据大小差异很大且变化难以估计,往往耗费大量空间类型语言的类型声明给数据大小提供了准确信息,所以编译器倾向于类型语言数据束定解决了,命令(语句)一般是比较容易的一个程序块从进口到出口逐条增减堆栈分配的指针,而无类型语言正因如此不宜编译(不等于说不行)语法分析、符号表都可以照做,只是存储管理要困难一些,要动态管理解释器采用动态作用域的模式实现非常自然,符号表在执行时是存在的分配存储对象并将其地址和标识符束定的工作也在执行中做因此,对标识符引用的解释总是按执行上下文中的最当前的束定一般说来,程序的执行路径要取决于输入和条件(语句)求值情况,是难以预测的因此,动态束定会因每次运行情况不同,同一名字和不同存储对象束定这样,就会引起对全局引用作出不同的解释,而且出了错极难追踪编译器中则正好相反,符号表在编译结束后销毁(除非保留作排错而用),所有存储对象都必须在编译时完成束定对于那些一定要在执行中间分配的局部量,编译器要决定它的地址,即相对于当时的堆栈框架的首地址,以便分配到正确位置上,这样,对于有多意的名字编译器必须保存一个与分配域堆栈平行的束定堆栈,直到程序执行时为止,每当翻译一个子程序块,编译器就把局部束定压入堆栈,每当该子程序块编译完即可全部弹出栈构成一个新堆栈帧用于解释符号引用的束定总是在栈顶这种翻译模式正好实现词法作用域解释局部量的束定总是在最小封装块中定义词法作用域是实现编译器的一种方法,编译器可以决定如何嵌套,谁是父块谁是子块,但不能知道执行顺序词法作用域的优点也是它的缺点要事先知道如何束定,因而排错比较容易,再一个是执行效率高正因为词法作用域和动态作用域对程序语义有影响,作用域应该成为程序设计语言形式化的一部分本来一个程序设计语言的设计与编译实现还是解释实现没有什么关系许多语言开发早期是解释型的以后由于运行效率难以容忍改成编译型,BASIC,LISP,Prolog都有这种经历但正如上所述,语义有微妙差异,两种翻译器不完全等价小结
5.6•束定是将程序中代表程序对象的标识符和实现程序对象的存储对象联系起来的工作标识符只有通过束定才能成为程序对象•束定联系标识符和存储对象类似指针,但不是指针,它跨越编译一装载一运行阶段才完成它必须由系统自动递引用•静态束定(早束定)在运行前完成一旦束定不再改变动态束定(晚束定)在运行期间实施,一个名字可动态束定到另一存储对象上•符号表即实施束定最主要手段,它列出全名、类型、它所对应的被束定体的首地址无类型语言将值类型附记在存储对象后编译完成符号表消失无类型动态束定的变量运行中类型也是可变的•可以独立地与名字束定的存储对象叫可束定体•声明就是束定,声明有定义、顺序声明、并行声明、递归声明四类•静态束定语言一个标识符可声明多次,定义只能一次动态束定语言一个标识符可以束定多次,因而它们表征的程序对象不同•声明的有效范围叫作用域一个标识符在其作用域内用简单名即可引用叫可见,否则叫掩蔽块结构语言都采用最近声明有效原则被掩蔽的标识符要用作用域分辨符•块声明是带有声明的声明,它使部分声明隐藏•环境是束定集合的有效范围声明指明新的束定集合,它在原有环境内指定,声明确立构成新环境•当一个标识符在一个程序中有多次束定时,相互复盖的作用域造成释义混乱,可以按词法作用域理解并进行束定,也可以按动态作用域理解和进行束定但两者的释义结是不同的•递归过程(无论是递归函数还是递归数据结构)只能按动态作用域束定和释义嵌套块结构语言按词法作用域束定和释义把标识符和全名分开是块结构语言解决名字冲突的办法•标识符和它所代表的程序对象的生命期不一定匹配在块结构和嵌套块结构的语言中这是常见的c语言的静态存储类就利用了它的优点•语言的翻译器不因解释型和编译型对语言释义有所不同但束定的词法作用域和动态作用域对语义确有影响编译型倾向有类型、嵌套结构语言并按词法作用域解释型倾向于动态作用域习题
5.1程序对象、标识符、名字、存储对象它们各自的含义并有什么关系?答程序对象是名字和存储对象的结合体;标识符和名字都是用以代表程序对象的,标识符是一字符串用于命名程序中某个(语法)元素名字也是字符串用以表示程序中的实体以使人们从语义角度使用它一般情况下,名字对应一简单标识符有时代表多个构件的组合存储对象是机器内部程序对象的具体实现关系一个名字可对应几个程序对象,一个程序对象也可对应多个名字,这些是由不同的束定机制来决定的
5.2符号表有什么作用?怎样加入新名字?,m n f nat类型变量
5.3程序对象的语义如何体现?
5.4无类型语言的变量是如何声明的?
(4)处环境:j TNat类型变量的参数
5.5何谓束定?束定和指针、引用有何不同?答束定是将名字标识符和可束定体联系起来,所谓可束定体是能反映出语义的存储块如常量、变量的存储体、函数体、过程体、类型和异常或者束定是将程序中代表对象的标识符和实现程序对象的存储对象联系起来的动作指针是程序可以令其指向任何程序实体,引用是常指针不同
①束定是编译或解释器做的,为每一个名字分配其语法要求的存储,也以跨越时间,编译时占个位置,运行时再分配实现束定
②翻译器可以自动递引用束定而不能自动递引用指针
5.6何谓动态、静态、块结构束定,比较它们的同异
5.7用束定能代替赋值吗?为什么?
5.8在一个块中各声明的标识符其生命期一样长吗?程序语言的编译解释器怎么知道?
5.9有以下C程序片段int x,y,zfunl intj,k,1;int m,n,x;fun3int y,z,k;•••main•••它是嵌套块程序吗?如何嵌套,用框形图绘出示意,标以A,B,C…为块名a.定义了多少名字作用域,指出每个名字作用域的起止b.main,funl,fun3中各能访问什么变量c.哪些变量作用域相互复盖?d.变量j是全局量还是局部量,为什么?e.在哪个块中能同时使用k,x两个变量,若有两外以上它们的使用意义一样吗?
5.10何谓名字冲突?不冲突不行吗?
5.11指出以下Pascal程序各标号处的束定环境PROGRAM A;CONST x=999;TYPE Nat=
0..x;VAR m,n:Nat;1FUNCTION fn:Nat:NatBEGINEND;PROCEDURE wj:Nat;2CONST n=6;BEGINEND;BEGINEND.1处环境x一整常量999Nat.类型O...maxint叫n fnat类型变量2处环境为x一整常量3处环境x f整常量999叫n rnat类型变量j fnat类型变量
(5)处环境x一整常量n—常量6xf整常量999m fnat类型变量度w->过程抽象(如有递归)ff函数抽象(如有调用)
5.12何谓束定出现?应用出现?函数抽象的F(xT)的参数x是什么出现?
5.13试述块声明的作用?C++的类声明是块声明吗?
5.14为什么递归定义只能用动态作用域?Nat->类型变量答动态作用域是指新的存储块(堆栈帧)一直保留对参m,nfnat类型变量数和局部量的束定,直至该程序块出口才释放的堆栈帧,递f一函数抽象归定义的函数在每次递归调用本函数时都要作新的存储分配w-过程抽象形成新一层堆栈帧,所以只能用动态作用域
5.15C语言的联合、Pascal和Ada的变体记录、FORTRAN的等价语句,一个存储对象与多个标识符束定,束定的是一个程序对象吗?如果不是它们的作用域复盖怎么没有区分问题呢?
5.16试举一例说明常量非恒值的用途怎样避免常量非恒值
6.17C++中声明也是语句,可以出现在任何语句出现的地方它如何实现束定?动态/静态?
5.18对于非并发语言并行声明有什么作用?
5.19用FORTRAN能仿真作出C的静态变量吗?如果行,试述仿真要点如果不行说明原因
5.20试述标识符的作用域和它所代表的程序对象寿命不一的根本原因各种束定机制
5.2除无类型语言而外,一般常见语言(Algol,FORTRAN,COBOL,C,Pascal,Ada)都是类型语言,每个程序对象均与类型相关,类型在声明(或缺省声明)中给出静态束定
5.
2.1较老的非结构化语言一般采用静态束定在编译时建立一个符号表,最简单的情况,该表用运行时内存类型名字地势存储对象(首地址)real length---array[
1..4]of integerage(首地址)三个域类型,名字,地址束定即可实现,见图5-1用虚箭头表示束定编译根据类型为名字分配适合大小的存储对象,按相对地址算出被束定对象的首地址,填入表,然而,运行符时号只表有存储对象,符号表在执行代码中是没有的,多数语言此时均销毁所以说,它跨越时间编译时开始,运行开始时(动态)到程序结束运行时(静态)结束静态束定运行前完成,一旦束定不再改变(符号表已销毁)静态束定也可以实现多重束定,如下例例5-1FORTRAN的等价语句DIMENSION Pl
(3)图5T符号表和束定EQUIVALENCE(Pl,P2),(Pl
(2),P3)DIMESION语句声明了一三元素实型数组(P隐含声明实型)Pl
(3)分配存储后实现Pl束定紧接着EQUIVALENCE又指明束定名字的意思是P2就是数组Pl,P3是P1数组的第二元素如图5-2所示ipl!p2lp3图5-2多重束定其他语言的多重束定是COBOL REDEFINES子句Pascal无标签域的变体记录C联合类型动态束定
5.
2.2动态束定是程序对象标识符在运行中分配存储并束定,而且可在运行之中改变束定程序中的声明到运行时才束定显然要根据运行值判定无类型语言解释执行,动态束定很自然(见
5.
2.4)类型语言在运行中解释类型并束定变量效率较低方法是一样的FORTH是类型语言,它完全动态束定不过为了提高效率它同时有编译器和解释器,相互切换使用编译器不完全生成目标码,只生成解释起来更快一些的中间码FORTH的程序对象是有类型的字词(word)o在统一的字典中处理字典是一个大的堆栈系统的、预定义的、以前定义过的字都压在栈底,新的应用压在栈顶新、老字都可以使用每当处理一项声明,字典创建一个项存储用户定义的字每个项分四个域名字域放名字,第一字节是名字长度;链接域使该项成为可查找的数据结构(实为指向以前项的指针);代码域相当于类型域,标识名字是哪类程序对象(函数、变量、常量、用户定义的类型);参数域或称体存放字的特定含义,如常量就放纯值,变量放存储对象,函数则放可解释的代码代码域实则是指向运行例程的指针,这个例程就束定为该名字的语义,代码域定义了该类型对象的解释方法最初的类型仅有“函数”、“变量”,用户可不断增加,每当声明一新的类型符,则给出两段代码,一个是为该类型对象分配足够的空间并初始化,另一段是该类型的执行例程指向这个新类型的指针成了该类型唯一的标识符,也成了今后声明该类型对象代码域中的内容现举例说明如下例5-2FORTH的动态束定(表示编译开始,后为类型声明符)(编译动作将类型02by3array声明符装入字典项)(在字典项中存入2义3维数)1create(为六个短整数分配12个字节)22,3(运行时动作指令,取下标)312allot(函数调用,检查下标)4does(如果不越界)5rangecheck(函数调用,计算线性下标值)6if(给出数组基地址和位移)71inearsub切换成解释执行,数据类型定义毕)8then9;(声明并分配名为box的数组变量)10(给box(l,2)赋值10)CREATE起到第3行和DOES起到第8行即为上述两段代码112by3array box按类型声明符2by3array的束定,将编译后的1-8行压入字典堆栈的项中解释执行时,按2by3array指针找到编译块,运行CREATE分配box的存储对象(在该项的顶端),接着解释执行第12行,运行does检查(1,2)是否越界若未越界计算下标准确值,在该地址下赋值10如果第12行以后用户把box定义为另一类型变量也可以,因为类型名束定于指向另一编译块的指针,再出现box则按最新释义当见到用户的FORGET命令则将至此定义的新字全部撤消,栈顶指针退下一项如无FORGET命令,栈越积越大,可重用的字越多块结构束定
5.
2.3块结构束定使名字和反映操作语义的代码联系,更为复杂它和FORTH动态束定不同的是,新程序块中的名字可以和外块相同即重新定义,但出了该块还要恢复该名字和原来束定详细的实现机制见
5.4节在此处我们先介绍一个有趣的问题,即常量束定会因块结构动态运行机制,变成常量非恒值程序中为常量给出一个名字方便程序维护因为若常量有了改动只需改定义常量名的那一次,程序中出现数十次都用名字代替了不用改有的语言还扩充了常量表达式例5-3Ada的常量束定P1:constant FLOAT:=
3.1416;N:INTEGER:=5;M:INTEGER;MAX_LENGTH:constant INTEGER:=N*100;MAX_INDEX:constant INTEGER:=MAX_LENGTHT;最后两行都是要计算的常量表达式它们通常在装入内存后运行前计算结果值束定于常量名Pl,MAX_LENGTH,MAXJNDEX都成了只读变量(由编译加指令保护),而符号对照表运行时已撤消,这是正常情况如果把这组常量声明放在过程PROC内而调用PROC的主子程序MAIN内又把N定义为一般变量且N和PROC都在一循环体内,而循环次数不知,每循环一次读一次N,由于采用堆栈式管理,每调用一次装入一次PROC并计算一次常量表达式(确立时,简单常量编译时已赋值不必计算)此外,由于PROC中的N和procedure MAINis•••N:INTEGER;••♦loopGET(N);PROC;when(AB)exit;end loop;•••end MAINMAIN中的N同名,按作用域规则它们束定于同一存储对象,出了PROC还原显然,因‘全局量N取决于输入,常量非恒值有时这种非恒值常量还非常有用
2.4无类型语言的束定
5.无类型语言更加依赖束定,因为一个变量名可完全动态地束定到任何类型的值或操作集上完全动态束定只可用于解释类型语言,或像LISP那样只有简单、统一的类型结构,这种语言类型直接和存储对象结合,而不通过名字,并和对象一起存在内存符号表中就没有类型这个域了,如图5-3所示符号表运行时内存存储对象类型标签图5-3APL的束定由于无类型,一个名字即使在一个块内,只要程序员发出命令即可束定到另一个存储对象上,:array of4number与存储对象大小无关APL、SNOBOL就是这种无类型语言,只有按当时束定解释它的意义无类型语言“变量”是不用声明的它只有定义(简单声明),即将标识符束定到表达式的结果值上,第一次引用则此标识符即为该值类型的“变量”APL甚至连赋值概念也没有,程序员显式操纵束定,达到计算并改变值的效果如下例例5-4APL计算税金的程序V TAXCALC
[1],ENTER GROSSPAY,
[2]GROSS-□〃输入什么值GROSS就是什么类型,口表示终端输入
[3]一LESS X GROSS18000〃条件表达式为真转LESS标号句
[4]TAX-.25XGROSS〃表达式结果值束定到TAX
[5]-DI SPLAY〃转到标号DI SPLAY句
[6]LESS:TAX-.22XGROSS
[7]DISPLAY:THE TAXIS$,
①TAX
[8]V即为束定,把表达式结果值所据的存储单元束定到TAX一相当于goto,‘义’指示后面表达式为条件,‘口’为终端输入,’
①显示TAX的十进制值引号中的字符串是照录显示出来的程序员可把标识符束定在任何表达式上,不同类型值都可以声明
5.3声明指明了本程序用到的所有程序对象实质上,它给出预想的束定集合(实现世界),即每个标识符和什么样的存储对象束定声明的作用,一方面供翻译器处理时所需信息,一方面为人们阅读便于调试对于类型语言,一般有显式声明部分或声明语句强类型语言每个标识符都要显式声明对于其它类型语言允许隐含声明例如,FORTRAN的标识符第一个字符在(I..N)范围为整型,其余为实型,无类型语言无显式声明部分,直接给出束定(通过表达式)或定义或隐含由上下文给出束定即使是强类型语言为使程序清晰也有隐含声明例5-5Ada的显式和隐式声明Ada是静态强类型语言,它的声明有典型性,且最丰富,它的显式数.声明子莪型.声明有:声明基本声明=对象—声明包一声明类属.声明I类型一声明类属一设例一声I子程庠.声明明延迟一常量—I任务—声明声明I异常.声明|换名—声明Ada的隐式声明包括块的名字、循环名字、语句标号(且为可选)以及循环控制变量有些操作也可以直接出现不用声明,如预定义运算符,派生子程序等声明的确立产生事实上的束定,下文讨论中为了方便不强调确立过程声明就有了静态束定声明的种类
5.
3.1这里讨论的不是声明的程序对象有哪些类别,而是声明本身的类别,有•定义•顺序声明•并行声明•递归声明我们详细说明如下
(1)定义定义为标识符束定提供完整信息,使标识符可束定于确定的存储对象上定义就是声明,而声明不完全等于定义例如,C语言的外部变量声明,Ada的带有(with)子句声明都是给编译提供信息的所以,一般说来,一个标识符可以声明多次而定义只能一次否则产生名字冲突在这个意义上,定义是简单的声明具有完整定义的声明为完全声明,否则为不完全声明,不完全声明在相互递归的类型定义中是常见的,例如,二叉树的每个结点由结点值和左、右两指针组成,是先定义指向结点的指针类型,(此时结点是什么不知道),还是先定义结点(以记录类型表达)的类型(此时指针成份未定义)?所以,只能先说一半再说整个,如例5-6实现上不会有困难读者想想为什么?有些语言把定义仅限于用己知信息定义程序中需用的信息,而声明可创建新信息例5-6ML的类型定义与声明ML定义两个类型type book=string*int〃书名和版本号type author=string*int〃作者名和出版年号book,author两个名字都束定于string和int组成的结构存储对象上这是不安全的,编译后两标识符去掉后容易出错ML还有新类型声明datatype book=bk ofstring*intdatatype author=au ofstring*int它创建了两个新类型,除束定于string*int的存储对象上外另有标记au,bk则与原有的类型都不同了前者按结构等价,后者按名等价Pascal,Ada,C++则认为每个类型构造子,如record...end,array[]of...,都引入新类型,定义只把标识符束定于类型如果把变量定义的概念仅限于用已有变量定义新变量,则许多语言为防止别名的混乱没有变量定义,只有引入新变量的变量声明,只有Ada有变量定义,即它的换名声明P0PULA:INTEGER renamePOPULATION STATE;将以国名为下标的人口数组元素换名为POPULA与此相反,函数式语言没有变量概念,当然也没有变量声明,只有和值束定的参数变元即只有定义,无类型动态语言更是如此,全靠定义例5-7ML的值定义val count=refO_ref是引用相当于传统语言分配算符new,即将标识符count束定于数0,因而引入了该变y uo例5-8ML的函数定义和值定义val even=fnnint=n mod2=0函册型构函fe体函数定义值定义以上是标识符even的值定义因有val将标识符even束定于函数,其变元n为整数,体的表达式返回真值,则even的值为Integerf Truth_Value以上还说明ML的函数抽象even是第一类值可用作值束定如果定义为函数也是可以的fun evennint=n mod2=0函数抽象函数体I〈束定》I但值定义更好用,因为二》右边可以是任何值表达式2顺序声明与并行声明程序讲究的就是次序,所以声明一开始都有一个隐含约定声明是顺序的,即后声明的声明符declarator可立即使用刚声明的声明符如果把声明符集写作D则有顺序声明DI;D2声明确立次序先D1后D2分号表示因此,D1可以影响到D2的声明例5-9Ada程序包声明package MANAGERis一声明程序包规格说明type PASSWORDis private;一声明私有类型未定义NULL_PASSWORD:coustant PASSWORD;一立即用私有类型声明变量function GETreturn PASSWORD;一返回私有类型函数function IS_VALIDP:in PASSWORDreturn BOOLEAN;Privatetype PASSWORDis range
0..700;一定义私有类型NULL_PASSWORD:constant PASSWORD:=0;―此时才定义end MANAGER;尽管PASSWORD在第2行声明时未定义,但第3,4,5行就立即用它声明其它对象end MANAGER,表示程序包结束,以上声明都限于本包,如果第3行像第8行那样赋了初值,本声明就出错了并行声明不怕次序调换ML中就有并行声明,其一般形式是D1|D2或DI andD2两个子声明DI,D2是独立的,即它们确立相互无影响,确立先后不会改变声明的意图,因此,就不能立即声明立即用了例5-10ML的并行声明val pi=
3.14159and sin=fnxreal=・,・and cos=fn xreal二〉…是合法的,加上and tan=fn xreal=sin x/cos x就不合法了,因为在确立tan时它也许最先对于讲究副作用、次序的命令式语言是不会采用并行声明的除非同一程序描述并行的子程序部分而函数式、逻辑式因排除副作用,故可采用3递归声明递归声明是标识符以自身束定的声明,一般形式是D=…D…〃D是包含标识符D的声明符或D1二…D2…〃D1是间接递归或称相互递归D2二・・・D1・・・递归声明通常限于类型、过程、函数、值定义至少到目前还没有扩大到更大的方面,如类、模块、程序包、类属、异常等有的语言采用自动递归,有的语言则由程序员显式指明递归,后者当有重名时程序员有主动性例5Tl Pascal和ML的递归FUNCTION eofVARfTextBoolean;BEGINeof=eoff ORft=*END;这个Pascal程序原意为用标准函数eof检索正文文件f的内容,当该文件以‘*结束时检索结束如不递归调用怎么也到不了但本程序自动递归调用自己定义的eof,而不会调用标准的eof其中有ft,所以什么也查不到ML可显式指明递归val recpower=fnx:real,n:int=if n=0then
1.0else ifn0then
1.0/power x,-n elsex*power x,n-l其中rec是显式指明符声明的作用域
5.
3.2在程序正文中声明有效的范围称为作用域scope作用域由所在程序块起止符标识声明自出了该声明符的句子(已产生束定),即开始生效,直至所在块的终止符,所以早期语言声明部分均在块的起始处即使在块的起始处,简单的、并行的、顺序的和递归的声明作用域都略有差别简单声明的作用域,从本声明结束至块末顺序声明,每一子声明结束即起作用并行声明,所有子声明结束才起作用递归声明,只要遇见与标识符相同的声明符,则被声明标识符即起作用
(1)嵌套块与可见性声明局部于表达式则称块表达式文件、主程序、函数或过程中的声明局限于该程序单元,早期语言Algol60首先提出块结构,即在程序单元中另立可再作局部声明的块于是产生嵌套块或称块结构程序可见性(Visiability)为用简单标识符即可引用该程序对象这对任何无嵌套声明结构是显而易见的如果是嵌套块则有名字冲突,如下例例5-12Pascal的嵌套声明名字冲突PROGRAM A;VAR x,y:Integer;FUNCTION Bd:Integer:Integer;ABEGIN B:=x+d END;BFUNCTION Cd:Ingeter:Real;VAR x:Real;CBEGIN x:=
5.1;C:=x+Bd+l END;BEGIN x:=3;y:=0;writeinCy END.图5-4Pascal的嵌套声明名字冲突这个典型的Pascal程序A,内嵌B块、C块A中声明的x作用域被B域复盖,因故B中未声明,第二行的x引用的是A.x,而C中的两处x都是C.x而不是A.x,因为块结构约定就近声明优先原则,在C中指的是C域中的x,要引用A.x必须加前缀xxx.这就不是简单名了所以,虽在A.x的作用域内但不可见(被掩蔽),必须加前缀名才可见C和C++用作用域分辨符
(2)标识符和名字由于一个标识符在嵌套块结构程序中可以多次声明或定义,而束定只能将名字和存储对象结合一次,故而把标识符和内部名字分开,这样,编译器按标识符所在的作用域全部换上内部名字,束定就唯一了,不致产生语义混乱例如,上段例子A域中的标识符一律变成名字A.x,A.y,B域中的名字为B.d,C域中为C.x,C.d每个(带前缀的)全名的作用域为所在的最小封包声明的块o全名只用于束定,即符号表中登记的是全名,运行时照样撤消尽管如此,概念上分清是十分必要的,今后本书把程序正文上的名字叫标识符,它翻译为全名后入符号表,与存储对象束定块声明
5.
3.3程序均由模块组成,我们暂且把嵌套块和并存块的声明作用域,以及由此而引起的程序对象生存期问题放在下节讨论这里先说程序单元中定义的块Algol60最早引入的分程序实则是块命令BEGIN D;C END;即把一个语句(命令)扩大到可有自己声明集D,命令集C的局部块,前文我们已经介绍过了并且把这个局部性的思想用于表达式(表达式中有局部声明)即块表达式,这里进一步问,声明集也是一个块,能不能再带有声明呢?回答是肯定的块声明是含有局部声明的声明,局部声明确立产生的束定仅用于块声明事实上有此需要,现代语言ML、Ada、C++都扩充了块声明,请看下例例5-13ML的块声明localfun maltiple(nint,dint)=(n modd=0)infun leap(yint)=(multiple(y,4)andalso notmultiple(y,1000))orelse multiple(y,400)end本例声明一求闰年函数leap,它给出函数定义,只要输入整数年即可判断是否闰年函数体中用到multiple函数,它在local…in之间定义(局部声明)块外的用户只能见到leap,而maltiple是见不到的,后者只为本块服务所以它是声明的声明前述例5-9是Ada块声明的例子,该例定义的Password类型、常量和两函数包外用户都可见,唯独私有类型PASSWORD本身的定义不可见(放在private中)它是其它声明的辅助声明、读者就会看到块声明的好处控制外部的可见性,使程序更清晰、简明修改私有类型定义对外无影响束定的作用域与释义
5.4前面我们介绍了束定的概念、机制及类别,声明即为预计的束定集合等一般概念束定作用域复盖是造成语言释义混乱的根源,本节我们研究这个问题束定与环境
5.41声明是给出预期的束定,而束定总是在以前束定过的标识符的基础上进行,即承认已有束定我们把以前的束定集的作用域范围称为环境(environment),当然环境还包括系统预定义的字面量,关键字,特殊符号(从符号学意义上的早期束定)一旦对某标识符作出束定,它也成为新环境的一部分环境用以解释程序中的一切活动,例如有声明const c=7;将标识符C与常整型存储对象束定,其中放值7完成束定(确立)后,c就是常量程序对象7的指称再如var cCharc就是字符变量的程序对象的指称,两个c束定不同是它所在的环境不同,当然同一环境下,同一标识符不能有两次束定(请注意,同一程序同一标识符可声明多次为什么?)环境是束定集合(实现世界),也可以说,环境是有效声明的集合(程序世界)以下是Pascal环境示例例5-14Pascal程序的环境
(1)处环境为PROGRAM P;z一整常量;cCONST z=0;一字符变量Q-VAR cChar;过程抽象PROCEDURE Q;CONST c=
3.0e6;VAR bBoolean;
(2)处环境为BEGIN。
个人认证
优秀文档
获得点赞 0