块 (编程)

在计算机编程中，块（block）或代码块是将源代码组织在一起的词法结构。块构成自一个或多个和语句。编程语言允许创建块，包括嵌入其他块之内的块，就叫做块结构编程语言。块和子程序是结构化编程的基础，结构化所强调的控制结构可以用块来形成的。

在编程中块的功能，是确使成组的语句被当作如同就是一个语句，限定在一个块中声明的对象如变量、过程和函数的词法作用域，使得它们不冲突于在其他地方用到的同名者。在块结构编程语言中，在块外部的对象名字在块内部是可见的，除非它们被声明了相同名字的对象所遮掩。

历史
块结构的想法是在1950年代开发最初的期间发展出来的，并形式化于ALGOL 60报告中。ALGOL 58介入了“复合”（compound）语句的概念，它只与控制流程有关。在“ALGOL 60报告”中，介入了块和作用域的概念。最终在“修订报告”中，复合语句被定义为：包围在语句括号begin和end之间的成序列的语句，形成一个复合语句。块被定义为：成序列的声明，跟随着成序列的语句，并被包围在begin和end之间，形成一个块；所有声明以这种方式出现在一个块中，并只在这个块中有效。块与复合语句的主要差异是不能从块外跳转到块内的标签。

语法
块在不同语言家族中使用不同的语法：
 ALGOL语言家族，ALGOL 60及其后继者比如Simula，使用关键字begin和end来界定复合语句和块。ALGOL 68成为了，偏好使用与begin和end等价的圆括号(和)。
Pascal语言家族，在Pascal中块就是程序、过程和函数的主体，它细分为六节，前五节中任何一个都可以空无，在最后的语句部份使用begin和end来界定复合语句。
C语言家族，C承袭了BCPL，使用花括号{和}来界定复合语句或块。
 Lisp语言家族，Lisp 1.5使用具有语法关键字prog的S-表达式表示块，而Maclisp和Scheme使用let形式的S-表达式来表示块，S-表达式是圆括号(和)包围的前缀表示法。
 Smalltalk语言家族，Smalltalk-80和Self使用方括号[和]来界定块。
此外，复合语句界定还可以采用：
 越位规则，源于ISWIM使用缩进表示块结构，采用越位规则的语言有occam、Python和Nim等。
建立控制结构，除了对所控制的语句序列，采用复合语句或匿名块之外，还可以采用其他语法机制：
 在ALGOL 68中，条件和迭代语句，使用块首保留字的反写保留字来终止，比如：if ~ then ~ elif ~ then ~ else ~ fi，case ~ in ~ out ~ esac和for ~ while ~ do ~ od。继承此风格的有：1974年Dijkstra的守卫命令语言和Bourne shell等。
 一些结构化编程语言，如FORTRAN 77、Modula-2、Ada和Visual Basic等，对控制结构加结束关键字，比如：IF ~ THEN ~ ELSIF ~ THEN ~ ELSE ~ END。

限制
受ALGOL影响的一些语言支持块，但有着各自的限制：
C家族语言，在块和复合语句之中不仅支持嵌套入复合语句，还支持嵌入带有声明的匿名块，但不允许声明。
Pascal家族语言，在语句部份的复合语句之中，不允许存在带有声明的匿名块，只支持复合语句，用来在if、while、repeat等控制语句内组合语句序列。

基本语义
块的语义是双重的。首先，它向编程者提供了建立任意大和复杂的结构，并把它当作一个单元的一种途径。其次，它确使编程者能限制变量的作用域，有时可以限制已经被声明了的其他对象的作用域。

在早期语言比如FORTRAN和BASIC中，没有语句块或控制结构。直到1978年标准化FORTRAN 77之前，都没有“块状IF”语句，要实现按条件选择，必须诉诸GOTO语句。例如下述FORTRAN代码片段，从雇员工资中分别扣除超出正税阈值部分的税款，和超出附加税阈值部分的附加税款：

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代码相对应的Pascal代码片段：
{ 语言：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;
与上述FORTRAN代码相比，上例中出现在初始化中的那些缺省值，通过复合语句即不带声明的块结构，被分别放置作出有关判断的地方。此外，处理附加税代码不再嵌入到处理正税代码之中，去除了附加税阈值要大于正税阈值，才能处理附加税的隐含条件。使用块结构，能明晰编程者的意图，使代码的结构更加密切反映出编程者的思考；再凭借某种风格的缩进增进可读性，可使代码更加容易理解和修改。

在早期语言中，在子例程中变量的作用域遍及整个子例程。假想在一个Fortran子例程中，完成了与管理者有关的任务，这里可能用到叫做IEMPNO的一个整数变量，指示作为管理者的雇员的社会安全号码（SSN）；后来在这个子例程的维护工作中，又增加与下属们有关的任务，此时编程者可能不经意间使用同名变量IEMPNO，指示了作为这个管理者的下属的雇员的SSN，这就会导致一个难于跟踪的缺陷。

块结构使得编程者能够容易地将作用域控制到细微级别。例如完成有关雇员任务的Scheme代码片段：
;; 语言：R5RS标准Scheme
(let ((empno (ssn-of employee-name)))
  (when (is-manager? empno) ;; when已列入R7RS-small标准
    (let ((employee-list (underlings-of empno)))
      (display
        ;; format是SRFI-28和SRFI-48规定的字符串格式化过程
        (format "~a has ~a employees working under him:~%"
          employee-name (length employee-list)))
      (for-each
        (lambda (empno)
          (display
            (format "Name: ~a, role: ~a~%"
              (name-of empno) (role-of empno))))
        employee-list))))
这里在外层通过绑定宏let将管理者的SSN绑定到了局部变量empno，在其形成的块的作用域中列出管理者的雇员名字和他的下属数目；随后通过for-each高阶函数，将他所有下属的SSN逐个绑定到匿名函数lambda的形式参数empno上，执行此匿名函数列出这个下属的名字和角色；这个形式参数的作用域是此匿名函数的主体，它与其外层的局部变量，标识符重名但不相互影响。在实践中，出于清晰性的考虑，编程者更可能选取明显不同的变量名字，但是即使名字选取存在重复，也难以在不经意间介入一个缺陷。在基于S-表达式的语言中，经常见到大量的嵌套圆括号，故而其代码必须采用良好的缩进。

提升
在一些情境下，在一个块中的代码的求值，如同这个代码实际上是写在块的顶部或块的外部一样。这经常通俗的叫做“提升”（hoisting），这包括了：
 循环不变代码外提，是将在循环内不变的代码在循环之前求值的编译器优化。
 变量提升，是JavaScript的作用域规则，在这里变量有函数作用域，并且表现得如同它们被声明（而非定义）在函数的顶部一样。

参见
 基本块
 作用域
 控制流程
 
 闭包 (计算机科学)

引用

程序架构