2016年5月19日 星期四

給新手的C++教學 (上冊) - 10. 變數的作用範圍 (Scope)

回到「給新手的C++教學 (上冊)」

上一章

不知你在之前的章節中做練習時,電腦是否莫名其妙地說你寫錯呢?
舉個例子
還記得第6章的練習題嗎?
題目是這樣的:
「輸入一個整數n,然後輸入n個數字
最後把這n個數字的順序顛倒,然後輸出」
當你學完陣列之後,馬上就興高采烈地寫下了程式碼:

#include<cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    int a[n];
    int i=0;
    while(i<n)
    {
        scanf("%d",&a[i]);
        i=i+1;
    }
    int i=n-1;
    while(i>=0)
    {
        printf("%d ",a[i]);
        i=i-1;
    }
    printf("\n");
}

然後,電腦就說你寫錯了
然後,電腦就說你寫錯了XD
哦~原來我前面已經宣告了一個名字為「i」的變數了,不能再宣告一個名字一模一樣 (同樣是「i」) 的變數
p.s. 雖然之前的章節完全沒有提到,這個命名不能重複的規則請讀者務必了解,仔細想想應該就會發現這個規則合情合理,否則最後會分不清楚「i」到底是指哪一個變數

如果你還是搞不懂發生了甚麼事,將上面的程式碼和第6章提供的程式碼解答比對一下,看看有甚麼差別吧~

現階段,這種問題是很好解決的
以目前的知識,可以想到的方法有2種:

1. 重複利用「i」這個變數 (也就是第6章採用的方法):

#include<cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    int a[n];
    int i=0;
    while(i<n)
    {
        scanf("%d",&a[i]);
        i=i+1;
    }
    i=n-1;
    while(i>=0)
    {
        printf("%d ",a[i]);
        i=i-1;
    }
    printf("\n");
}

2. 用不同的名稱宣告一個新的變數 (例如將新變數取名為「j」):

#include<cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    int a[n];
    int i=0;
    while(i<n)
    {
        scanf("%d",&a[i]);
        i=i+1;
    }
    int j=n-1;
    while(j>=0)
    {
        printf("%d ",a[j]);
        j=j-1;
    }
    printf("\n");
}


你可能覺得這樣沒有問題
但是,當你的程式稍微複雜一點的時候
你會很容易搞不清楚,對於特定的一個變數,現在到底有沒有在「使用中」 (假如你使用了方法1.)
或者記不住每一個變數的用途 (假如你使用了方法2.)

難道我們就是沒辦法「用相同的名稱宣告一個新的變數」嗎?

可以的!
我們只要設定好每一個變數的「作用範圍 (Scope)」(或者說「有效範圍」)
也就是說,對於每一個變數,我們可以設定程式碼的哪幾行可以使用它

方法是使用「大括號」框出「作用範圍」
這樣一來,在這個「大括號」裡面宣告的變數,都不能在「大括號外面」使用

事實上,連「if」和「while」的大括號,都有這個功能
廣泛地講,只要出現「大括號」,都會發揮同樣的作用

這樣說你可能還沒有畫面,我們來看看要怎麼利用這個特性來解決上面的問題:

#include<cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    int a[n];
    {
        int i=0;
        while(i<n)
        {
            scanf("%d",&a[i]);
            i=i+1;
        }
    }
    {
        int i=n-1;
        while(i>=0)
        {
            printf("%d ",a[i]);
            i=i-1;
        }
    }
    printf("\n");
}

可以看到,我們宣告了兩個名稱相同的變數「i」,但是是在不同的大括號裡面宣告,因此它們互不影響

另外,在這兩個大括號外面也無法使用這兩個「i」
甚麼意思呢?
我們來做個測試:

#include<cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    int a[n];
    {
        int i=0;
        while(i<n)
        {
            scanf("%d",&a[i]);
            i=i+1;
        }
    }
    {
        int i=n-1;
        while(i>=0)
        {
            printf("%d ",a[i]);
            i=i-1;
        }
    }
    printf("%d\n",i);
    printf("\n");
}

可以發現,在新加的那一行「printf("%d\n",i);」發生錯誤了!
電腦說你這行寫錯了
這是因為,我們新加的那一行程式碼「printf("%d\n",i);」的位置,並不在任何一個「名字叫做『i』的變數」的「作用範圍」之內
因此,對於「printf("%d\n",i);」來說,你根本沒有宣告過「i」這個變數!

當然,你可以在「printf("%d\n",i);」前面補宣告「i」,讓電腦可以順利讀完你的程式碼:

#include<cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    int a[n];
    {
        int i=0;
        while(i<n)
        {
            scanf("%d",&a[i]);
            i=i+1;
        }
    }
    {
        int i=n-1;
        while(i>=0)
        {
            printf("%d ",a[i]);
            i=i-1;
        }
    }
    int i;
    printf("%d\n",i);
    printf("\n");
}

再次提醒,這個程式碼中的3個「i」,它們的作用範圍都是不同的,因此是互不影響
也就是說,當你修改其中一個「i」的數值,另一個「i」的數值是保持不變
所以,你應該把這3個「i」當作不同的變數,只是名稱相同而已

那麼,如果作用範圍「重疊」了
怎麼辦?
例如,我把程式碼修改成這樣:

#include<cstdio>
int main()
{
    int n;
    scanf("%d",&n);
    int a[n];
    {
        int i=0;
        while(i<n)
        {
            scanf("%d",&a[i]);
            i=i+1;
        }
    }
    int i;
    {
        int i=n-1;
        while(i>=0)
        {
            printf("%d ",a[i]);
            i=i-1;
        }
    }
    printf("%d\n",i);
    printf("\n");
}

可以發現,「第3個『i』(『int i=n-1;』那一行) 的作用範圍」包含在「第2個『i』(『int i;』那一行) 的作用範圍」裡面!
那麼,對於

while(i>=0)
{
    printf("%d ",a[i]);
    i=i-1;
}

這段程式碼
它用到的會是哪一個「i」呢?

答案是「第3個『i』」

這是C++的隱藏規則 (非潛規則,見註5)
當多個相同名稱的變數的作用範圍重疊的時候,會以「最後被宣告」的那一個變數為優先 (也就是「作用範圍最小」的那個變數)

仔細想想,其實規則這樣定可以帶來很多方便
不過,我還是強烈建議,盡量不要讓「作用範圍重疊」的情況發生
因為這種情況常常會造成別種不必要的麻煩,也就是「無法使用外面相同名稱的變數」

不知道你目前覺得本章教的知識有沒有用
但是,我相信
你總有一天會想要用到它


對了,還記得我們提過「for迴圈」嗎?
來介紹一下吧~
我們可能常常要用迴圈做到 (或類似) 下面這件事:

#include<cstdio>
int main()
{
    {
        int i=1;
        while(i<=10)
        {
            printf("i=%d\n",i);
            i=i+1; 
        }
    }
    {
        int i=10;
        while(i>=1)
        {
            printf("i=%d\n",i);
            i=i-1;
        }
    }
    return 0;
}

注意到
我們需要用大括號把「int i=?;」和「while迴圈」框起來,限制變數「i」的作用範圍,才能在兩個不同迴圈使用相同名稱 (都是「i」) 的變數
這可以用「for迴圈」來精簡成這樣:

#include<cstdio>
int main()
{
    for(int i=1;i<=10;i++)
    {
        printf("i=%d\n",i);
    }
    for(int i=10;i>=1;i--)
    {
        printf("i=%d\n",i);
    }
    return 0;
}

哇塞,太讚了吧!連限制作用範圍的大括號都可以省了!
看得出來,你很快就會愛上它!(註12)

下一章

感謝:李旺陽學長
(版權所有 All copyright reserved)

2 則留言:

  1. 補宣告i的那個程式碼會多輸出一個0喔

    還有 想問一下
    {int n=2;} int n; printf("%d",n);
    為什麼補宣告n卻會輸出1
    而如果去掉前面的大括號變成:
    int n; printf("%d",n);
    就會輸出0?

    如果這樣不能補宣告的話為什麼文章中的i就可以

    回覆刪除
    回覆
    1. 還有 如果大括號內的互不影響
      為什麼把前面的大括號去掉後面的數值會改變

      刪除

歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原