0%

0-12 變數的作用範圍

余柏序學長的教學文

作用範圍

變數是會失效的!如果有認真寫題目的人,應該會發現這一點。在之前的迴圈、陣列等也有提過區域變數與全域變數。不要將陣列宣告在 main 函式內,而是宣告於全域的其中一個理由就是:如果你的程式裡面需要用到函式,那這個寫在 main 函數外的函式就沒有辦法使用這個陣列裡的資訊了!那麼,現在就來看看,變數的作用範圍,大概是怎麼一回事吧!

最懶的判斷法

一個變數的作用範圍,自其宣告開始,到其所屬的右大括號為止

比如:

1
2
3
4
5
6
7
int main(){
int a;
for(int b=0;b<n;b++){
int c = 5;
cout << c << '\n';
}
}

這個 a 的作用範圍,就是 main 函式結尾,而 b 與 c 的範圍,則到 for 迴圈結束為止。

有大括號的東西,例如 if、for、while、函式…… 都適用這個規則。並且不會因為省略大括號而有改變,也就是說,即使這樣:

1
if(9 > 5) int b = 6;

因為 if 只有一行而省略大括號,但變數 b 的作用範圍仍然只在這個 if 中。

1
2
if(9 > 5) int b = 6;
cout << b << '\n';

這樣是會 CE 的,因為在 cout 那行,這個 b 已經失效,是找不到 b 的。

更多範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
int a = 1;

void f(){
int g = 7;
}

int main(){
int b = 2;
if(a > b){
int c = 3;
}
for(int i=0;i<a;i++){
int d = 4;
}
while(a--){
int e = 5;
}
f();
}

以上各變數的作用範圍,分別是哪裡呢?

答案:

  • a : 3 ~ end
  • b : 10 ~ 21
  • c : 12 ~ 13
  • d : 15 ~ 16
  • e : 18 ~ 19
  • f 是函式名稱,不是變數
  • g : 6 ~ 7

這樣,對變數的作用範圍,有大致的了解了嗎?

接下來,我們就來看看,這樣會造成什麼問題。

重複宣告

一般來說,我們無法宣告具有相同名稱之變數,例如:

1
2
int a = 5;
int a = 3;

這是一段會 CE 的程式碼。

但若作用範圍不同,則可以順利編譯執行。這會導致程式的執行結果與心中所想的不同,而且不太容易 debug。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<bits/stdc++.h>
using namespace std;
int a = 1;

void print(){
cout << a << '\n'; // 1
}

int main(){
cout << a << '\n'; // 1
int a = 2;
cout << a << '\n'; // 2
for(int i=0;i<3;i++){
int a = 3;
cout << a << '\n'; // 3
}
cout << a << '\n'; // 2
print(); // go out of main function
}

輸出:

1
2
3
4
5
6
7
1
2
3
3
3
2
1

這段程式碼當中,總共出現了三種不同的 a,但因為他們作用範圍不同,因此不會有編譯錯誤。

大範圍的變數與小範圍的同時出現時,會優先以小的為主。因此在 11 行的 a 宣告後,雖然第 3 行的 a 仍在作用範圍,但在 11 行的 a 於 main 函式結尾失效前,都會暫時被覆蓋。在此期間,只要程式碼有提到 a,編譯器一律不會當成第 3 行的 a。

應盡量避免這樣的狀況發生,可於編譯指令中加上 -Wshadow 以自動偵測是否有變數同名。但仍建議平常在練習時養成變數命名的好習慣,勿隨意命名變數。

若只是用來代表目前迴圈跑到哪裡的 i, j, k 變數等,我會於迴圈的一開始宣告。這樣,兩個不同迴圈中的 i 才不會互相形成干擾。

練習

檢查自己過去的程式碼,有沒有重複命名的現象?是否曾因為重複命名而產生過 bug ?