指派

在 Ruby 中,指派使用 =(等號)字元。此範例將數字 5 指派給區域變數 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」,「未定義的區域變數或方法『a』」。由於 Ruby 會先剖析 if 左側的裸 a,且尚未看到指派給 a 的值,因此它假設您想要呼叫一個方法。然後 Ruby 會看到指派給 a 的值,並假設您正在參照一個區域變數。

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

區域變數和 eval

使用 eval 來評估 Ruby 程式碼,將允許存取在相同範圍內定義的區域變數,即使區域變數是在呼叫 eval 之後才定義的。然而,在呼叫 eval 內部定義的區域變數,將不會反映在周圍的範圍內。在呼叫 eval 內部,在周圍的範圍內定義的區域變數和在呼叫 eval 內部定義的區域變數,將會是可以存取的。然而,您將無法存取在相同範圍內之前或之後呼叫 eval 所定義的區域變數。將每個 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」符號或商業符號)開頭。否則實例變數名稱遵循與區域變數名稱相同的規則。由於實例變數以 @ 開頭,因此第二個字元可以是大寫字母。

以下是實例變數使用方式的範例

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]

您可以在指定右側的任何地方展開

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]}