块 (编程)
在计算机编程中,块(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语言家族中,块使用花括号来界定:{
和}
。 - s-表达式,圆括号包围的前缀表示法,它具有语法关键字比如
lambda
或let
,如在Lisp语言家族中那样。
此外还有:
- 越位规则,采用缩进表示块结构,如occam和Python那样。
- 在ALGOL 68和此后1974年Edsger W. Dijkstra的守卫命令语言中,条件和迭代代码块可以选择使用块保留字的反写来终止:比如:
if ~ then ~ elif ~ else ~ fi
,case ~ in ~ out ~ esac
和for ~ while ~ do ~ od
。继承此风格而对控制结构加结束关键字的语言还有Fortran 77、Modula-2、Ada、Bourne shell和Visual Basic等。
限制
一些支持带有声明的块的语言不完全支持所有声明,例如C及其很多派生语言不允许在块内的函数定义(嵌套函数)。不同于祖先ALGOL,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. . Communications of the ACM (New York, NY, USA: ACM). 1958, 1 (12): 8–22. doi:10.1145/377924.594925.
- Backus, J. W.; Bauer, F. L.; Green, J.; Katz, C.; McCarthy, J.; Perlis, A. J.; Rutishauser, H.; Samelson, K.; Vauquois, B.; Wegstein, J. H.; van Wijngaarden, A.; Woodger, M. Naur, Peter , 编. 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).