程式设计中,变量遮蔽(英语:variable shadowing,或称变量隐藏)指的是:当新变量与旧有变量同名,此名称暂时不再可用于存取旧有变量。

通常此新变量会被定义于一作用域(决策块、方法或内部类)之中。 以编程语言在解析标识符(即名称,而不只是变量)的角度来看,这就是名称遮蔽,而我们称此旧有的变量被新变量遮蔽(be shadowed by)掉,而新变量遮蔽了(mask)旧有的变量。因为被遮蔽的名称,在后续使用上可能不便于判定它指向哪个变量,所以此设计有时会迷惑用户。判定方法端看该编程语言如何进行名称解析

ALGOL 是最早使用变量遮蔽此一设计的语言之一:它是第一个使用程序块来建立作用域的语言。此设计也被其所派生出的语言认可并采用,其中就包含了 CC++Java

C# 则打破了这种传承,而允许在内部类与外部类之间、方法与其包含的类之间执行变量遮蔽;但不允许在 if 块与其内的方法之间、或switch块中的 case 语句之间进行变量遮蔽。

有些语言有更多种遮蔽变量的模式,例如 Kotlin 可以用函数内的变量遮蔽传入的参数,以及内部代码块的变量遮蔽外部代码块的变量。而Java不允许这两种模式。但是,此二语言都可以用传递给函数/方法的参数来遮蔽类字段(Class Field)[1]。Rust 则允许相同代码块中,透过重复声明的方式来遮蔽变量。[2]

有些语言则是完全不允许变量遮蔽,譬如 CoffeeScript[3]

示例

编辑

以下 Lua 代码在多个代码块中,示范如何遮蔽变量。

v = 1 -- 一個全域變數

do
  local v = v + 1 -- 一個遮蔽了變數 v 的新區域變數
  print(v) -- 印出 2

  do
    local v = v * 2 -- 另一個新的區域變數,遮蔽了區塊外部的變數 v
    print(v) -- 印出 4
  end

  print(v) -- 印出 2
end

print(v) -- 印出 1

Python

编辑

以下 Python 代码提供了另一个变量遮蔽的例子:

x = 0

def outer():
    x = 1

    def inner():
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)

# 印出結果:
# inner: 2
# outer: 1
# global: 0

由于 Python 中,变量不必声明即可赋值,故 Python 3 中引入关键字 nonlocal,用于避免变量遮蔽,而得以对非区域变量赋值:

x = 0

def outer():
    x = 1

    def inner():
        nonlocal x
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)

# 印出結果:
# inner: 2
# outer: 2
# global: 0

而关键字 global 的功能则是避免变量遮蔽,兼赋值给全局变量:

x = 0

def outer():
    x = 1

    def inner():
        global x
        x = 2
        print("inner:", x)

    inner()
    print("outer:", x)

outer()
print("global:", x)

# 印出內容:
# inner: 2
# outer: 1
# global: 2

Rust 语言尤其不同,其允许在同一个代码块中多次宣告同名变量:后宣告者遮蔽先宣告者。[2]

fn main() {
    let x = 0;
    
    {
        let x = 1;
        println!("Inner x: {}", x); // 印出 1
    }
    
    println!("Outer x: {}", x); // 印出 0
    
    // 同作用域的遮蔽
    let x = "Rust";
    println!("Outer x: {}", x);  // 印出 'Rust'
}

//# Inner x: 1
//# Outer x: 0
//# Outer x: Rust
#include <iostream>

int main()
{
  int x = 42;
  int sum = 0;

  for (int i = 0; i < 10; i++) {
    int x = i;
    std::cout << "x: " << x << '\n'; // 印出 i 的值,從 0 到 9
    sum += x;
  }

  std::cout << "sum: " << sum << '\n';
  std::cout << "x:   " << x   << '\n'; // 印出 42

  return 0;
}
public class Shadow {
    private int myIntVar = 0;

    public void shadowTheVar() {
        // 因為此變數名稱與上文的物件名稱相同,
        // 所以在此方法中,它遮蔽了上述變數
        int myIntVar = 5;

        // 若只使用 'myIntVar' 來指涉變數,會指向此方法中定義的變數
        // (遮蔽了另一個同名的變數)
        System.out.println(myIntVar); // 印出 5

        // 若我們想要存取此 class 中被定義的、現正被遮蔽的 myIntVar,
        // 我們須以如下方式指涉它:
        System.out.println(this.myIntVar); // 印出 0
    }

    public static void main(String[] args){
        new Shadow().shadowTheVar();
    }
}

JavaScript

编辑

ECMAScript 6 引入了以代码块为作用域的 letconst 关键字,从而允许变量遮蔽。[4]

function myFunc() {
    let my_var = 'test';
    if (true) {
        let my_var = 'new test';
        console.log(my_var); // new test
    }
    console.log(my_var); // test
}
myFunc();

参见

编辑

参考资料

编辑
  1. ^ From Java to Kotlin and Back Again. [2021-10-04]. (原始内容存档于2020-11-28). 
  2. ^ 2.0 2.1 Scope and Shadowing - Rust By Example. doc.rust-lang.org. [2022-12-04]. (原始内容存档于2022-12-08). 
  3. ^ Please introduce explicit shadowing · Issue #2697 · jashkenas/Coffeescript. GitHub. [2021-10-04]. (原始内容存档于2021-10-04). 
  4. ^ Variable Shadowing in JavaScript. GeeksforGeeks. 2021-03-05 [2022-12-04]. (原始内容存档于2022-12-04) (美国英语).