块 (编程)
在计算机编程中,块(block)或代码块是将源代码组织在一起的词法结构。块构成自一个或多个声明和语句。编程语言允许创建块,包括嵌入其他块之内的块,就叫做块结构编程语言。块和子程序是结构化编程的基础,结构化所强调的控制结构是用块来形成的。
在编程中块的功能是确使成组的语句被当作如同就是一个语句,限定在一个块中声明的对象如变量、过程和函数的词法作用域,使得它们不冲突于在其他地方用到的同名者。在块结构编程语言中,在块外部的对象名字在块内部是可见的,除非它们被声明了相同名字的对象所遮掩。
历史
块结构的想法是在1950年代开发第一个Autocode期间发展出来的,并形式化于ALGOL 58和ALGOL 60报告中。Algol 58介入了“复合”(compound)语句的概念,它只与控制流程有关[1]。随后的描述ALGOL 60的语法和语义的“修订报告”介入了块和块作用域的概念,这里的块构成自“一序列的声明,跟随着一序列的语句,并被包围在begin
和end
之间...”在其中“所有声明以这种方式出现在一个块中,并只在这个块中有效。”[2]
语法
块在不同语言中使用不同的语法。两个广大的家族是:
- ALGOL语言家族,ALGOL 60及其后继者如Pascal语言家族,使用关键字
begin
和end
或等价者来界定块。ALGOL 68使用圆括号:(
和)
。C语言家族承袭了BCPL,使用花括号来界定块:{
和}
。 - Lisp语言家族,使用具有语法关键字如
prog
[3]或let
[4]的S-表达式来表示块,S-表达式是圆括号包围的前缀表示法。
此外还有:
- 越位规则,源于ISWIM使用缩进表示块结构,采用越位规则的语言有occam、Python和Genie等。
- 在ALGOL 68和此后1974年Edsger W. Dijkstra的守卫命令语言中,条件和迭代代码块可选的使用块保留字反写来终止:比如:
if ~ then ~ elif ~ then ~ else ~ fi
,case ~ in ~ out ~ esac
和for ~ while ~ do ~ od
。继承此风格的有Bourne shell等。类似的,有的结构化编程语言如FORTRAN 77、Modula-2、Ada和Visual Basic等,对控制结构加结束关键字,比如:IF ~ THEN ~ ELSIF ~ THEN ~ ELSE ~ END
。
限制
受ALGOL影响的一些语言,支持带有声明的块,但不完全支持所有声明,例如C及其很多派生语言,不允许在块内的函数定义(嵌套函数)。Pascal家族语言,在现存块的begin
和end
内部,不支持使用带有自己声明的块,只支持复合语句,用来确使语句序列在if
、while
、repeat
和其他控制语句内被组合在一起。
基本语义
块的语义是双重的。首先,它向编程者提供了建立任意大和复杂的结构并把它当作一个单元的一种途径。其次,它确使编程者能限制变量的作用范围,有时可以限制已经被声明了的其他对象的作用范围。
在原初的语言比如早期的FORTRAN和BASIC中,有一些内建的语句类型,很少或没有以有结构的方式扩展它们的方法。例如,直到1978年标准化FORTRAN 77之前,都没有“块状IF
”语句,所以要写一个遵循标准的代码来实现简单的决定,编程者必须诉诸GOTO
语句:
C 语言: ANSI标准FORTRAN 66 C 初始化要计算的值 PAYSTX = .FALSE. PAYSST = .FALSE. TAX = 0.0 SUPTAX = 0.0 C 如果雇员挣钱少于税金阈值则跃过税款扣除 IF (WAGES .LE. TAXTHR) GOTO 100 PAYSTX = .TRUE. TAX = (WAGES - TAXTHR) * BASCRT C 如果雇员挣钱少于附加税阈值则跃过附加税扣除 IF (WAGES .LE. SUPTHR) GOTO 100 PAYSST = .TRUE. SUPTAX = (WAGES - SUPTHR) * SUPRAT 100 TAXED = WAGES - TAX - SUPTAX
甚至这个非常简要的FORTRAN片段,符合FORTRAN 66标准,却不容易看出这个程序的结构,因为结构不反映在语言中。没有仔细的研究的话,不容易看出执行给定语句所处的环境。
块允许编程者把一组语句当作一个单元,在这种风格的程序中,上例中出现在初始化中的那些缺省值,通过块结构,被分别放置在接近于作出决定的地方:
{ 语言: Jensen和Wirth Pascal } if wages > tax_threshold then begin paystax := true; tax := (wages - tax_threshold) * tax_rate end else begin paystax := false; tax := 0 end; if wages > supertax_threshold then begin pays_supertax := true; supertax := (wages - supertax_threshold) * supertax_rate end else begin pays_supertax := false; supertax := 0 end; taxed := wages - tax - supertax;
上述的Pascal片段,符合ISO 7185标准化的1974年版规定,使用块明晰了编程者的意图,代码的结构更加密切的反映出编程者的思考。使用块结构,依靠缩进支持其可读性,使其更加容易理解和修改。
在原初的语言中,变量有着宽广的作用范围。例如,一个叫做IEMPNO
的整数变量可以用在一个Fortran子例程的某部份中,指示一个雇员的社会安全号码(ssn),但是在相同子例程的维护工作中,一个编程者可能偶然的使用了相同的变量IEMPNO
,用于了不同的用途,这可能导致一个难于跟踪的缺陷。块结构使得编程者易于控制作用范围到细微级别。
;; 语言: R5RS标准Scheme (let ((empno (ssn-of employee-name))) (while (is-manager empno) (let ((employees (length (underlings-of empno)))) (printf "~a has ~a employees working under him:~%" employee-name employees) (for-each (lambda(empno) ;; 在这个lambda表达式之内变量empno指称一个下属的ssn。 ;; 在外部的表达式中变量empno指称的管理者的ssn,被遮蔽了。 (printf "Name: ~a, role: ~a~%" (name-of empno) (role-of empno))) (underlings-of empno)))))
在上述Scheme片段中,empno
被用来标识管理者和他的下属二者自己分别的ssn,但是因为下属ssn被声明于内部的块之中,它与包含管理者ssn的同名变量不相互影响。在实践中,出于清晰性的考虑,编程者更可能选择明显不同的变量名字,但是即使选择重名也难于不经意的介入一个缺陷。
提升
在一些情境下,在一个块中的代码的求值,如同这个代码实际上是写在块的顶部或块的外部一样。这经常通俗的叫做“提升”(hoisting),这包括了:
- 循环不变代码外提,是将在循环内不变的代码在循环之前求值的编译器优化。
- 变量提升,是JavaScript的辖域规则,在这里变量有函数辖域,并且表现得如同它们被声明(而非定义)在函数的顶部一样。
参见
引用
- ^ Perlis, A. J.; Samelson, K. Preliminary report: international algebraic language. Communications of the ACM (New York, NY, USA: ACM). 1958, 1 (12): 8–22. doi:10.1145/377924.594925.
- ^ John Backus; Friedrich L. Bauer; J. Green; C. Katz; John McCarthy; Alan Jay Perlis; Heinz Rutishauser; K. Samelson; B. Vauquois; J. H. Wegstein; A. van Wijngaarden; M. Woodger. Peter Naur , 编. 3 (5). New York, NY, USA: ACM: 299–314. May 1960 [2009-10-27]. ISSN 0001-0782. doi:10.1145/367236.367262. (原始内容存档于2007-06-25).
- ^ John McCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, Michael I. Levin. (PDF) 2nd. MIT Press. 1985 [1962] [2021-09-23]. ISBN 0-262-13011-4. (原始内容 (PDF)存档于2021-03-02).
The LISP 1.5 program feature allows the user to write an Algol-like program containing LISP statements to be executed. ……The program form has the structure - (
PROG
, list of program variables, sequence of statements and atomic symbols...) An atomic symbol in the list is the location marker for the statement that follows. - ^ Kent M. Pitman. . 1983, 2007 [2021-10-14]. (原始内容存档于2021-12-21).
LET
is used to bind some variables to some objects, and then to evaluate some forms (those which make up the body) in the context of those bindings. ……LET*
Same asLET
but does bindings in sequence instead of in parallel.