再看變數範圍


先前談過,在Ruby中有五種變數,包括區域變數、全域變數、實例變數、類別變數與常數,先前曾討論過一些範圍(Scope)的問題,在學到模組之後,這邊要正名為區域變數、全域變數、實例變數、類別模組變數與常數。以下對變數作個總整理。

$$開頭的全域變數是最容易理解的,因為整個程式中隨處可見。

以小寫字母開頭的區域變數的範圍也許是其次容易理解的,在Ruby中,非常明確的,區域變數就真的只是區域變數,無論它出現在哪個區域,可見範圍就僅止於該區域,不會跨入內嵌的子區域。如果出現在類別區域,就是在類別區域中可見,不會在方法區域中可見。如果出現在模組區域中,就只在模組區域中可見,不會在類別或方法中可見。例如:
>> class Some
>>     x = 10
>>     def some
>>         puts x
>>     end
>> end
=> nil
>> Some.new.some
NameError: undefined local variable or method `x' for #<Some:0x42df98>
        from (irb):23:in `some'
        from (irb):26
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> module M1
>>     y = 20
>>     class Some
>>         puts y
>>     end
>> end

NameError: undefined local variable or method `y' for M1::Some:Class
        from (irb):30:in `<class:Some>'
        from (irb):29:in `<module:M1>'
        from (irb):27
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>


如果類別或模組中定義了常數,則類別或模組,或其中的方法都可取用常數(方法中不能設定常數)。常數若定義在類別中,則可以透過「類別名稱::常數」的方式取得。常數若定義在模組中,則可以透過「模組名稱::常數」的方式取得。事實上,類別名稱或模組都必須以大寫名稱作開頭,而大寫名稱在Ruby中是定義為常數,所以類別名稱與模組名稱,其實就是個常數。例如:
>> class Some
>>     CONT_SOME = 10
>> end
=> 10
>> Some::CONT_SOME
=> 10
>> module M1
>>     CONT_M1 = 20
>> end
=> 20
>> M1::CONT_M1
=> 20
>> module M2
>>     CONT_M2 = 30
>>     class Other
>>         CONT_OTHER = 40
>>     end
>> end
=> 40
>> M2::CONT_M2
=> 30
>> M2::Other
=> M2::Other
>> M2::Other::CONT_OTHER
=> 40
>>


因此,模組也常被用來作為類別、公用方法或公用常數的名稱空間,而與特定類別相關的公用常數與公用方法則定義在特定類別中。

如果類別或模組外定義了常數,則類別或模組內可以直接取用常數,在類別或模組中若設定了同名常數,則是在類別或模組中定義了自己的常數。例如:
>> X = 10
=> 10
>> class Some
>>     puts X
>> end
10
=> nil
>> class Other
>>     X = 20
>>     puts X
>> end
20
=> nil
>> X
=> 10
>>


關於@@開頭的變數,類別變數 中作過深入的討論,實際上,@@開頭的變數,也可以定義在模組中,姑且稱之為模組變數,屬於模組擁有,模組變數必須定義模組方法來取得。例如:
>> module M1
>>     @@x = 10
>>     def self.x
>>         @@x
>>     end
>> end
=> nil
>> M1.x
=> 10
>>


實際上,@@開頭的變數,範圍貫穿模組或類別以及當中定義的方法(無論是實例方法或是類別方法),而且在Object類別中,若有@@變數與內部類別的@@類別變數同名,則外部類別的@@變數會覆蓋內部類別的@@變數(在Object中定義@@變數,幾乎就等同全域變數了)。因此要小心使用,你可以回顧 類別變數 中看過的幾個例子。

以@開頭的變數為實例變數,屬於個別物件擁有,可視範圍僅限於物件之內,要取得實例變數,必須為該物件定義實例方法。

@開頭的變數,並不是只能出現在實例方法中,如果@開頭的變數出現在類別或模組本體,那它就是屬於類別或模組(因為它們也是物件),出現在類別或模組方法中時,表示要存取的就是該類別或模組擁有的實例變數。例如:
>> class Some
>>     @a = 10
>>     def self.a
>>         @a
>>     end
>>     def a
>>         @a
>>     end
>>     def a=(v)
>>         @a = v
>>     end
>> end
=> nil
>> s = Some.new
=> #<Some:0x2603a60>
>> Some.a
=> 10
>> s.a = 20
=> 20
>> Some.a
=> 10
>> s.a
=> 20
>>


出現在Some本體的@a,是屬於Some類別擁有的實例變數,因此必須透過類別方法Some.a取得。出現在實例方法中的@a,則屬於Some產生的s實例擁有,因此必須透過s.a實例方法存取。

那麼,@@開頭的變數與@開頭的變數有何不同?來看看這個例子:
>> class Some
>>     @@a = 10
>> end
=> 10
>> def Some.a
>>     @@a
>> end
=> nil
>> Some.a
NameError: uninitialized class variable @@a in Object
        from (irb):5:in `a'
        from (irb):8
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>


這是在 類別變數 討論過的情況,直譯器看到@@a時,會往外看類別或模組邊界在哪(而不是看到Some.a中的Some),結果看到的邊界是Object,但Object中並沒有定義@@a,因此發生錯誤。

如果是以下呢?
>> class Some
>>     @a = 10
>> end
=> 10
>> def Some.a
>>     @a
>> end
=> nil
>> Some.a
=> 10
>>


直譯器看到@a時,會看看是透過哪個實例呼叫,結果是Some,因此取得的是Some中定義的@a

@開頭的變數一定是在實例上,只是這個實例可能是類別、模組或依類別建構出來的實例;@@開頭的變數,可記憶為比@擁有更大可視範圍的意涵,其範圍貫穿模組或類別以及當中定義的方法(無論是實例方法或是類別方法),並需留意Object中@@變數幾乎等同全域變數的情況