赋值¶ ↑
在 Ruby 中,赋值使用 =
(等号)字符。此示例将数字 5 赋值给局部变量 v
v = 5
赋值语句会在变量之前没有被引用时创建一个局部变量。
赋值表达式的结果总是被赋值的值,包括 赋值方法。
局部变量名¶ ↑
局部变量名必须以小写 US-ASCII 字母或八位设置为 1 的字符开头。通常局部变量是 US-ASCII 兼容的,因为所有键盘上都有输入这些字符的键。
(Ruby 程序必须用 US-ASCII 兼容的字符集编写。在这样的字符集中,如果八位设置为 1,则表示扩展字符。Ruby 允许局部变量包含这样的字符。)
局部变量名可以包含字母、数字、_
(下划线)或八位设置为 1 的字符。
局部变量作用域¶ ↑
一旦局部变量名被赋值,在该作用域的剩余部分中,该名称的所有使用都被视为局部变量。
以下是一个例子
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
)来强制调用方法。使用显式接收者可能会引发 NameError
,如果方法的可见性不是公共的,或者接收者是字面量 self
。
另一个常见的混淆情况是使用修饰符 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”符号或商业符号)开头。否则,实例变量名称遵循与局部变量名称相同的规则。由于实例变量以 @
开头,因此第二个字符可以是大写字母。
以下是一个实例变量使用示例
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
您可以以这种方式使用以下运算符:+
、-
、*
、/
、%
、**
、&
、|
、^
、<<
、>>
还有||=
和&&=
。前者在值是nil
或false
时进行赋值,而后者在值不是nil
或false
时进行赋值。
以下是一个例子
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]}