指派

在 Ruby 中,指派使用 =(等號)字元。此範例將數字五指派給區域變數 v

v = 5

指派會建立區域變數,如果之前未曾參照過該變數。

指派表達式的結果永遠都是指派的數值,包括 指派方法

區域變數名稱

區域變數名稱必須以小寫美國標準資訊交換碼 (US-ASCII) 字母或八位元組設定的字元開頭。區域變數通常與 US-ASCII 相容,因為鍵盤上都有輸入這些變數的按鍵。

(Ruby 程式必須以與 US-ASCII 相容的字元集撰寫。在這些字元集中,如果設定八位元組,則表示延伸字元。Ruby 允許區域變數包含這些字元。)

區域變數名稱可以包含字母、數字、_(底線或低線)或設定第八位元組的字元。

區域變數範圍

一旦區域變數名稱已指定給範圍中名稱的所有用途,則這些名稱將被視為區域變數。

以下是範例

1.times do
  a = 1
  puts "local variables in the block: #{local_variables.join ", "}"
end

puts "no local variables outside the block" if local_variables.empty?

這會列印

local variables in the block: a
no local variables outside the block

由於區塊會建立新的範圍,因此在其中建立的任何區域變數都不會外洩到周圍的範圍。

在外部範圍中定義的變數會顯示在內部範圍中

a = 0

1.times do
  puts "local variables: #{local_variables.join ", "}"
end

這會列印

local variables: a

您可以透過在區塊引數中列出 ; 之後的變數,將區塊中的變數與外部範圍隔離。請參閱 呼叫方法 文件中的區塊區域變數文件,以取得範例。

另請參閱 Kernel#local_variables,但請注意,for 迴圈不會像區塊一樣建立新的範圍。

區域變數和方法

在 Ruby 中,區域變數名稱和方法名稱幾乎相同。如果您尚未指定這些模糊名稱中的任何一個,Ruby 會假設您想要呼叫方法。一旦您指定名稱,Ruby 會假設您想要參照區域變數。

當解析器遇到指定時會建立區域變數,而不是在指定發生時

a = 0 if false # does not assign to a

p local_variables # prints [:a]

p a # prints nil

方法名稱和區域變數名稱的相似性可能會導致混淆的程式碼,例如

def big_calculation
  42 # pretend this takes a long time
end

big_calculation = big_calculation()

現在,任何參照 big_calculation 的內容都被視為區域變數,並將被快取。若要呼叫方法,請使用 self.big_calculation

您可以透過使用空引數括號(如上所示)或使用明確的接收器(例如 self)來強制呼叫方法。如果方法的可見性不是公開的,或接收器是文字 self,使用明確的接收器可能會引發 NameError

另一個常見的混淆情況是使用修飾詞 if

p a if a = 0.zero?

您會收到 NameError,而不是印出「true」,「未定義的 local 變數或方法『a』」。由於 Ruby 會先解析 if 左邊的 a,且尚未看到對 a 的指定,因此它假設您想要呼叫一個方法。然後,Ruby 會看到對 a 的指定,並假設您正在參照一個 local 變數。

混淆來自於表達式的無序執行。首先,指定 local 變數,然後您嘗試呼叫一個不存在的方法。

Local 變數和 eval

使用 eval 來評估 Ruby 程式碼,將允許存取在相同範圍內定義的 local 變數,即使 local 變數是在呼叫 eval 之後才定義的。然而,在呼叫 eval 內部定義的 local 變數,不會反映在周圍的範圍內。在呼叫 eval 內部,在周圍的範圍內定義的 local 變數和在呼叫 eval 內部定義的 local 變數,都可以存取。然而,您將無法存取在相同範圍內先前或後續呼叫 eval 中定義的 local 變數。將每個 eval 呼叫視為一個獨立的巢狀範圍。範例

def m
  eval "bar = 1"
  lvs = eval "baz = 2; ary = [local_variables, foo, baz]; x = 2; ary"
  eval "quux = 3"
  foo = 1
  lvs << local_variables
end

m
# => [[:baz, :ary, :x, :lvs, :foo], nil, 2, [:lvs, :foo]]

實例變數

實例變數在同一個物件的所有方法中共享。

實例變數必須以 @(「at」符號或商業 at)開頭。否則,實例變數名稱遵循與 local 變數名稱相同的規則。由於實例變數以 @ 開頭,因此第二個字元可以是大寫字母。

以下是實例變數使用範例

class C
  def initialize(value)
    @instance_variable = value
  end

  def value
    @instance_variable
  end
end

object1 = C.new "some value"
object2 = C.new "other value"

p object1.value # prints "some value"
p object2.value # prints "other value"

未初始化的實例變數值為 nil。如果您在啟用警告的情況下執行 Ruby,在存取未初始化的實例變數時,您將會收到警告。

value 方法可以存取由 initialize 方法設定的值,但僅限於同一個物件。

Class 變數

Class 變數在類別、其子類別及其實例之間共享。

類別變數必須以 @@(兩個「at」符號)開頭。名稱的其餘部分遵循與實例變數相同的規則。

以下是範例

class A
  @@class_variable = 0

  def value
    @@class_variable
  end

  def update
    @@class_variable = @@class_variable + 1
  end
end

class B < A
  def update
    @@class_variable = @@class_variable + 2
  end
end

a = A.new
b = B.new

puts "A value: #{a.value}"
puts "B value: #{b.value}"

這會列印

A value: 0
B value: 0

繼續使用相同的範例,我們可以使用任一類別的物件進行更新,並且值是共享的

puts "update A"
a.update

puts "A value: #{a.value}"
puts "B value: #{b.value}"

puts "update B"
b.update

puts "A value: #{a.value}"
puts "B value: #{b.value}"

puts "update A"
a.update

puts "A value: #{a.value}"
puts "B value: #{b.value}"

這會列印

update A
A value: 1
B value: 1
update B
A value: 3
B value: 3
update A
A value: 4
B value: 4

存取未初始化的類別變數將引發 NameError 例外。

請注意,類別有實例變數,因為類別是物件,所以請盡量不要混淆類別和實例變數。

全域變數

全域變數可以在任何地方存取。

全域變數以 $(美元符號)開頭。名稱的其餘部分遵循與實例變數相同的規則。

以下是範例

$global = 0

class C
  puts "in a class: #{$global}"

  def my_method
    puts "in a method: #{$global}"

    $global = $global + 1
    $other_global = 3
  end
end

C.new.my_method

puts "at top-level, $global: #{$global}, $other_global: #{$other_global}"

這會列印

in a class: 0
in a method: 0
at top-level, $global: 1, $other_global: 3

未初始化的全域變數的值為 nil

Ruby 有些特殊全域變數的行為會根據不同的情況而不同,例如正規表達式比對變數,或是在指定時會產生副作用的變數。請參閱 全域變數文件 以取得詳細資料。

指定方法

您可以定義會像指定一樣運作的方法,例如

class C
  def value=(value)
    @value = value
  end
end

c = C.new
c.value = 42

使用指定方法可以讓您的程式看起來更漂亮。在指定到實例變數時,大多數人會使用 Module#attr_accessor

class C
  attr_accessor :value
end

在使用方法指定時,您必須永遠有一個接收器。如果您沒有接收器,Ruby 會假設您正在指定到一個區域變數

class C
  attr_accessor :value

  def my_method
    value = 42

    puts "local_variables: #{local_variables.join ", "}"
    puts "@value: #{@value.inspect}"
  end
end

C.new.my_method

這會列印

local_variables: value
@value: nil

若要使用指定方法,您必須設定接收器

class C
  attr_accessor :value

  def my_method
    self.value = 42

    puts "local_variables: #{local_variables.join ", "}"
    puts "@value: #{@value.inspect}"
  end
end

C.new.my_method

這會列印

local_variables:
@value: 42

請注意,指定方法所傳回的值會被忽略,因為指定表達式的結果永遠都是指定值。

縮寫指定

您可以混合使用多個運算子與指定。若要將 1 加到一個物件,您可以寫

a = 1

a += 2

p a # prints 3

這等於

a = 1

a = a + 2

p a # prints 3

您可以使用以下運算子:+-*/%**&|^<<>>

還有 ||=&&=。前者會在值為 nilfalse 時進行指定,而後者會在值不是 nilfalse 時進行指定。

以下是範例

a ||= 0
a &&= 1

p a # prints 1

請注意,這兩個運算子的行為比較像 a || a = 0,而不是 a = a || 0

隱含 Array 指定

您可以在指定時列出多個值來隱含建立一個陣列

a = 1, 2, 3

p a # prints [1, 2, 3]

這會隱含建立一個 Array

您可以在指定時使用 * 或「展開」運算子,或解開一個 Array。這類似於多重指定

a = *[1, 2, 3]

p a # prints [1, 2, 3]

b = *1

p b # prints [1]

您可以在指定作業的右方任何地方使用 splat

a = 1, *[2, 3]

p a # prints [1, 2, 3]

多重指定

您可以在右方指定多個值給多個變數

a, b = 1, 2

p a: a, b: b # prints {:a=>1, :b=>2}

在以下區段中,任何使用「變數」的地方,指定方法、實例、類別或全域也會運作

def value=(value)
  p assigned: value
end

self.value, $global = 1, 2 # prints {:assigned=>1}

p $global # prints 2

您可以使用多重指定來交換兩個值

old_value = 1

new_value, old_value = old_value, 2

p new_value: new_value, old_value: old_value
# prints {:new_value=>1, :old_value=>2}

如果指定作業的右方有比左方變數更多的值,額外值會被忽略

a, b = 1, 2, 3

p a: a, b: b # prints {:a=>1, :b=>2}

您可以在指定作業的右方使用 * 來收集額外值。

a, *b = 1, 2, 3

p a: a, b: b # prints {:a=>1, :b=>[2, 3]}

* 可以出現在左方的任何地方

*a, b = 1, 2, 3

p a: a, b: b # prints {:a=>[1, 2], :b=>3}

但是您只能在指定作業中使用一個 *

陣列 分解

就像在 方法參數 中的 陣列 分解,您可以在指定作業時使用括號分解 陣列

(a, b) = [1, 2]

p a: a, b: b # prints {:a=>1, :b=>2}

您可以在較大的多重指定中分解 陣列

a, (b, c) = 1, [2, 3]

p a: a, b: b, c: c # prints {:a=>1, :b=>2, :c=>3}

由於每個分解都被視為其自己的多重指定,因此您可以在分解中使用 * 來收集參數

a, (b, *c), *d = 1, [2, 3, 4], 5, 6

p a: a, b: b, c: c, d: d
# prints {:a=>1, :b=>2, :c=>[3, 4], :d=>[5, 6]}