赋值

在 Ruby 中,赋值使用 =(等号)字符。此示例将数字五赋值给局部变量 v

v = 5

如果之前未引用变量,赋值会创建一个局部变量。

赋值表达式的结果始终是赋值值,包括 赋值方法

局部变量名称

局部变量名称必须以小写美国标准信息交换码字母或八位元组字符开头。局部变量通常与美国标准信息交换码兼容,因为所有键盘上都有键入这些字符的键。

(Ruby 程序必须以与美国标准信息交换码兼容的字符集编写。在这些字符集中,如果设置了八位元组,则表示扩展字符。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?

您不会打印“true”,而是会收到NameError,“未定义的局部变量或方法‘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”符号或商业 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

分配时,您可以使用 * 或“splat”运算符,或解包一个 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}

但在一个分配中,您只能使用一个 *

Array 分解

就像在 方法参数 中的 Array 分解一样,您可以在分配期间使用括号分解一个 Array

(a, b) = [1, 2]

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

您可以在更大的多重分配中分解一个 Array

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