3

我正在玩if块内的赋值操作,发现了下面的结果,这让我很惊讶:

C:\>irb --simple-prompt
if false
x = 10
end
#=> nil
p x
nil
x.object_id
#=> 4
#=> nil
p y
NameError: undefined local variable or method `y' for main:Object
        from (irb):5
        from C:/Ruby193/bin/irb:12:in `<main>'

在上面的代码中,您可以看到x已经创建了局部变量,即使它只是在 falsyif块中分配。我试图查看它的内容,xp x迫使我相信赋值没有完成,但x变量存在。x.object_id也证明是这样的。

现在我的问题是,x即使if故意永久关闭块入口点,该局部变量是如何创建的?

我希望 的输出p x与 的输出相似p y。但相反,我得到了一个令人惊讶的答案p x

有人可以向我解释这个概念是如何工作的吗?

编辑

不,这是另一个测试。这不是只有local变量的情况。instanceclass变量也发生了同样的情况。见下文:

class Foo
  def show
    @X = 10 if false
    p @X,"hi",@X.object_id
  end
end
#=> nil
Foo.new.show
nil
"hi"
4
#=> [nil, "hi", 4]

class Foo
  def self.show
    @@X = 10 if false
    p @@X,"hi",@@X.object_id
  end
end
#=> nil
Foo.show
nil
"hi"
4
#=> [nil, "hi", 4]

成功案例:

class Foo
  def self.show
    @@X = 10 if true
    p @@X,"hi",@@X.object_id
  end
end
#=> nil
Foo.show
10
"hi"
4
#=> [10, "hi", 4]
4

3 回答 3

8

在 Ruby 中,局部变量由解析器在第一次遇到赋值时定义,然后从那时起就在作用域内。

这是一个小演示:

foo # NameError: undefined local variable or method `foo' for main:Object

if false
  foo = 42
end

foo # => nil

如您所见,局部变量确实存在于第 7 行,即使第 4 行的赋值从未执行过。然而,它被解析了,这就是局部变量foo存在的原因。但是因为从未执行过赋值,所以变量未初始化,因此计算结果为niland not 42

在 Ruby 中,大多数未初始化甚至不存在的变量的计算结果为nil. 这适用于局部变量、实例变量和全局变量:

defined? foo       #=> nil
local_variables    #=> []
if false
  foo = 42
end
defined? foo       #=> 'local-variable'
local_variables    #=> [:foo]
foo                #=> nil
foo.nil?           #=> true

defined? @bar      #=> nil
instance_variables #=> []
@bar               #=> nil
@bar.nil?          #=> true
# warning: instance variable @bar not initialized

defined? $baz      #=> nil
$baz               #=> nil
# warning: global variable `$baz' not initialized
$baz.nil?          #=> true
# warning: global variable `$baz' not initialized

但是,对于类层次结构变量和常量,情况并非如此:

defined? @@wah     #=> nil
@@wah
# NameError: uninitialized class variable @@wah in Object

defined? QUUX      #=> nil
QUUX
# NameError: uninitialized constant Object::QUUX

这是一条红鲱鱼:

defined? fnord     #=> nil
local_variables    #=> []
fnord
# NameError: undefined local variable or method `fnord' for main:Object

在这里出现错误的原因不是未初始化的局部变量不计算为nil,而是fnord模棱两可:它可能是发送到默认接收器的无参数消息(即等效于self.fnord()访问局部变量fnord

为了消除歧义,您需要添加一个接收者或一个参数列表(即使是空的)来告诉 Ruby 这是一个消息发送:

self.fnord
# NoMethodError: undefined method `fnord' for main:Object
fnord()
# NoMethodError: undefined method `fnord' for main:Object

或者确保解析器而不是评估器)在使用之前解析(执行)赋值,告诉 Ruby 它是一个局部变量:

if false
  fnord = 42
end
fnord              #=> nil

而且,当然,nil是一个对象(它是 class 的唯一实例NilClass),因此有一个object_id方法。

于 2013-03-03T13:37:51.053 回答
3

Ruby 总是解析你的所有代码。它不会将 false 视为不解析内部内容的标志,它会对其进行评估并看到不应执行内部代码

于 2013-03-03T08:31:07.560 回答
1

Ruby 有局部变量“提升”。如果您在方法中的任何地方都对局部变量进行了赋值,那么该变量在方法中的任何地方都存在,甚至在赋值之前,即使赋值从未实际执行。在分配变量之前,它的值为nil

编辑:

上面说的不太对。Ruby 确实有一种变量提升的形式,它会在局部变量赋值存在但未执行时定义一个局部变量。但是,在上述方法中发生赋值的点处不会发现该变量。

于 2013-03-03T07:55:22.963 回答