我理解"常数"在Ruby中按惯例称为常量,但实际上是可变的。然而,我的印象是当他们发生变异时#34;有一个警告:
class Z2
M = [0,1]
end
Z2::M # => [0, 1]
Z2::M = [0,3]
(irb):warning: already initialized constant Z2::M
(irb):warning: previous definition of M was here
但是我发现事实并非如此:
a = Z2::M
a[1] = 2
Z2::M # => [0,2] and no warning
这是"警告"系统?我推断一个常量的赋值会复制它,但我想这不是真的,因为看起来常量和变量指向同一个对象?这是否意味着所有所谓的"常数"是否需要冻结以防止它们在没有警告的情况下被更改?
答案 0 :(得分:5)
缺少猴子修补内核#警告(请参阅https://stackoverflow.com/a/662436/1301972)以引发异常,您将无法阻止重新分配常量本身。在惯用的Ruby代码中,这通常不是一个实用的问题,其中一个期望能够执行重新打开类的操作,即使类名也是常量。
Ruby常量实际上不是不可变的,你不能freeze变量。但是,当某些内容尝试修改常量引用的冻结对象的内容时,可以获取异常。
冻结阵列非常简单:
CONSTANT_ONE = %w[one two three].freeze
但是存储在此Array中的字符串实际上是对String对象的引用。因此,虽然您无法修改此数组,但您仍然可以(例如)修改索引0引用的String对象。要解决此问题,您不仅要冻结数组,还要冻结它所拥有的对象。例如:
CONSTANT = %w[one two three].map(&:freeze).freeze
CONSTANT[2] << 'four'
# RuntimeError: can't modify frozen String
CONSTANT << 'five'
# RuntimeError: can't modify frozen Array
由于冻结递归引用可能有点笨拙,所以最好知道there's a gem for that。您可以使用ice_nine来深度冻结大多数对象:
require 'ice_nine'
require 'ice_nine/core_ext/object'
OTHER_CONST = %w[a b c]
OTHER_CONST.deep_freeze
OTHER_CONST << 'd'
# RuntimeError: can't modify frozen Array
OTHER_CONST[2] = 'z'
# RuntimeError: can't modify frozen Array
另一个需要考虑的选择是在将常量的值分配给另一个变量时调用Object#dup,例如类初始值设定项中的实例变量,以确保不会意外地改变常量的引用。例如:
class Foo
CONSTANT = 'foo'
attr_accessor :variable
def initialize
@variable = CONSTANT.dup
end
end
foo = Foo.new
foo.variable << 'bar'
#=> "foobar"
Foo::CONSTANT
#=> "foo"
答案 1 :(得分:1)
没有差距,因为你没有改变常数。事实上,Ruby常量只是带有额外警告的变量。
常量,就像每个变量一样,只是指向内存中对象的指针。执行M = [0,3]
时,您正在创建一个新数组,并将常量重新指向此新对象,从而触发警告。
但是,当您运行M[0] = 1
时,您只是修改引用的对象,但是您不会更改常量,因为它仍然指向同一个对象。
要实现的重要一点是Ruby中的所有类都只是内存中的对象,用常量引用,所以当你这样做时:
class Z2
end
它相当于(如果Z2未定义或未指向类对象):
Z2 = Class.new
自然类是一个非常动态的对象,因为我们不断添加方法等等 - 我们绝对不希望这会触发任何警告。
答案 2 :(得分:0)
如果你Z2::M[1] = 2
,你也不会收到消息。我认为缺少警告因为您要更改Array
本身而不是参考Z2::M
。
如果M
是一个整数,例如:
class Z2
M = 1
end
a = Z2::M
a = 2
a # => 2
Z2::M # => 1
要从常量修改Array
而不修改您可以执行的原始操作:
class Z2
M = [0,1]
end
a = Z2::M.dup
a[0] = 1
a # => [1,1]
Z2::M # => [0,1]