變數遮蔽
在程式設計中,變數遮蔽(英語:variable shadowing,或稱變數隱藏)指的是:當新變數與舊有變數同名,此名稱暫時不再可用於存取舊有變數。
通常此新變數會被定義於一作用域(決策塊、方法或內部類)之中。 以程式語言在解析識別碼(即名稱,而不只是變數)的角度來看,這就是名稱遮蔽,而我們稱此舊有的變數被新變數遮蔽(be shadowed by)掉,而新變數遮蔽了(mask)舊有的變數。因為被遮蔽的名稱,在後續使用上可能不便於判定它指向哪個變數,所以此設計有時會迷惑使用者。判定方法端看該程式語言如何進行名稱解析。
ALGOL 是最早使用變數遮蔽此一設計的語言之一:它是第一個使用程式塊來建立作用域的語言。此設計也被其所衍生出的語言認可並採用,其中就包含了 C、 C++ 和 Java。
C# 則打破了這種傳承,而允許在內部類與外部類之間、方法與其包含的類之間執行變數遮蔽;但不允許在 if 塊與其內的方法之間、或switch塊中的 case 陳述式之間進行變數遮蔽。
有些語言有更多種遮蔽變數的模式,例如 Kotlin 可以用函數內的變數遮蔽傳入的參數,以及內部代碼塊的變數遮蔽外部代碼塊的變數。而Java不允許這兩種模式。但是,此二語言都可以用傳遞給函數/方法的參數來遮蔽類字段(Class Field)[1]。Rust 則允許相同代碼塊中,透過重複聲明的方式來遮蔽變數。[2]
有些語言則是完全不允許變數遮蔽,譬如 CoffeeScript 。 [3]
範例
編輯Lua
編輯以下 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
編輯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
C++
編輯#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;
}
Java
編輯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 引入了以代碼塊為作用域的 let
與 const
關鍵字,從而允許變數遮蔽。[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();
參見
編輯參考資料
編輯- ^ From Java to Kotlin and Back Again. [2021-10-04]. (原始內容存檔於2020-11-28).
- ^ 2.0 2.1 Scope and Shadowing - Rust By Example. doc.rust-lang.org. [2022-12-04]. (原始內容存檔於2022-12-08).
- ^ Please introduce explicit shadowing · Issue #2697 · jashkenas/Coffeescript. GitHub. [2021-10-04]. (原始內容存檔於2021-10-04).
- ^ Variable Shadowing in JavaScript. GeeksforGeeks. 2021-03-05 [2022-12-04]. (原始內容存檔於2022-12-04) (美國英語).