跳到內容

賦值

賦值表達式會將一個值賦予一個具名的識別符(通常是變數)。賦值運算子是等號(=)。

賦值的目標可以是

# Assigns to a local variable
local = 1

# Assigns to an instance variable
@instance = 2

# Assigns to a class variable
@@class = 3

# Assigns to a constant
CONST = 4

# Assigns to a setter method
foo.method = 5
foo[0] = 6

方法作為賦值目標

結尾為等號(=)的方法稱為 setter 方法。它可以作為賦值的目標。賦值運算子的語義會作為語法糖應用於方法呼叫。

呼叫 setter 方法需要明確的接收者。無接收者的語法 x = y 永遠會被解析為賦值給一個區域變數,而不是呼叫方法 x=。即使加上括號也不會強制方法呼叫,如同讀取區域變數時一樣。

以下範例顯示以典型方法表示法和賦值運算子呼叫 setter 方法的兩種方式。這兩種賦值表達式是等效的。

class Thing
  def name=(value); end
end

thing = Thing.new

thing.name=("John")
thing.name = "John"

以下範例顯示以典型方法表示法和索引賦值運算子呼叫索引賦值方法的兩種方式。這兩種賦值表達式是等效的。

class List
  def []=(key, value); end
end

list = List.new

list.[]=(2, 3)
list[2] = 3

組合賦值

組合賦值是賦值運算子和另一個運算子的組合。它適用於除了常數以外的任何目標型別。

有一些包含 = 字元的語法糖可用

local += 1  # same as: local = local + 1

這假設相應的目標 local 是可賦值的,無論是作為變數還是透過各自的 getter 和 setter 方法。

= 運算子語法糖也適用於 setter 和索引賦值方法。請注意,||&& 會使用 []? 方法來檢查鍵是否存在。

person.age += 1 # same as: person.age = person.age + 1

person.name ||= "John" # same as: person.name || (person.name = "John")
person.name &&= "John" # same as: person.name && (person.name = "John")

objects[1] += 2 # same as: objects[1] = objects[1] + 2

objects[1] ||= 2 # same as: objects[1]? || (objects[1] = 2)
objects[1] &&= 2 # same as: objects[1]? && (objects[1] = 2)

鏈式賦值

可以使用鏈式賦值將相同的值賦予多個目標。它適用於除了常數以外的任何目標型別。

a = b = c = 123

# Now a, b and c have the same value:
a # => 123
b # => 123
c # => 123

多重賦值

您可以透過以逗號(,)分隔表達式來同時宣告/賦值多個變數。它適用於除了常數以外的任何目標型別。

name, age = "Crystal", 1

# The above is the same as this:
temp1 = "Crystal"
temp2 = 1
name = temp1
age = temp2

請注意,由於表達式會被賦值給暫時變數,因此可以在單行中交換變數的內容

a = 1
b = 2
a, b = b, a
a # => 2
b # => 1

多重賦值也適用於結尾為 = 的方法

person.name, person.age = "John", 32

# Same as:
temp1 = "John"
temp2 = 32
person.name = temp1
person.age = temp2

也適用於索引賦值[]=

objects[1], objects[2] = 3, 4

# Same as:
temp1 = 3
temp2 = 4
objects[1] = temp1
objects[2] = temp2

一對多賦值

如果右側只包含一個表達式,則會為左側的每個變數索引該型別,如下所示

name, age, source = "Crystal, 123, GitHub".split(", ")

# The above is the same as this:
temp = "Crystal, 123, GitHub".split(", ")
name = temp[0]
age = temp[1]
source = temp[2]

此外,如果提供了strict_multi_assign 旗標,則元素數量必須與目標數量相符,且右側必須是Indexable

name, age, source = "Crystal, 123, GitHub".split(", ")

# The above is the same as this:
temp = "Crystal, 123, GitHub".split(", ")
if temp.size != 3 # number of targets
  raise IndexError.new("Multiple assignment count mismatch")
end
name = temp[0]
age = temp[1]
source = temp[2]

a, b = {0 => "x", 1 => "y"} # Error: right-hand side of one-to-many assignment must be an Indexable, not Hash(Int32, String)

展開賦值

賦值的左側可以包含一個展開,它會收集任何未賦予其他目標的值。如果右側有一個表達式,則會使用範圍索引

head, *rest = [1, 2, 3, 4, 5]

# Same as:
temp = [1, 2, 3, 4, 5]
head = temp[0]
rest = temp[1..]

負索引用於展開後的目標

*rest, tail1, tail2 = [1, 2, 3, 4, 5]

# Same as:
temp = [1, 2, 3, 4, 5]
rest = temp[..-3]
tail1 = temp[-2]
tail2 = temp[-1]

如果表達式沒有足夠的元素,且展開出現在目標的中間,則會引發IndexError

a, b, *c, d, e, f = [1, 2, 3, 4]

# Same as:
temp = [1, 2, 3, 4]
if temp.size < 5 # number of non-splat assignment targets
  raise IndexError.new("Multiple assignment count mismatch")
end
# note that the following assignments would incorrectly not raise if the above check is absent
a = temp[0]
b = temp[1]
c = temp[2..-4]
d = temp[-3]
e = temp[-2]
f = temp[-1]

右側表達式必須是Indexable。即使沒有 strict_multi_assign 旗標(請參閱上方的一對多賦值),也會進行大小檢查和 Indexable 檢查。

如果有多個值,則會形成一個Tuple

*a, b, c = 3, 4, 5, 6, 7

# Same as:
temp1 = {3, 4, 5}
temp2 = 6
temp3 = 7
a = temp1
b = temp2
c = temp3

底線

底線可以出現在任何賦值的左側。將值賦予它沒有任何效果,且無法從底線讀取

_ = 1     # no effect
_ = "123" # no effect
puts _    # Error: can't read from _

當右側傳回的一些值不重要時,在多重賦值中很有用

before, _, after = "main.cr".partition(".")

# The above is the same as this:
temp = "main.cr".partition(".")
before = temp[0]
_ = temp[1] # this line has no effect
after = temp[2]

*_ 的賦值會被完全捨棄,因此可以使用多重賦值來有效率地擷取值的第一個和最後一個元素,而無需為中間的元素建立中間物件

first, *_, last = "127.0.0.1".split(".")

# Same as:
temp = "127.0.0.1".split(".")
if temp.size < 2
  raise IndexError.new("Multiple assignment count mismatch")
end
first = temp[0]
last = temp[-1]