Ruby 关于"a=a+1"的思考

phun · 2015年06月05日 · 最后由 hooopo 回复于 2015年06月18日 · 2908 次阅读

a = a + 1

如上,在 irb 上中就执行上面的代码,那么结果是什么呢?

  • 首先,a 被赋值为 nil。

  • 然后,报错:NoMethodError: undefined method `+' for nil:NilClass

而我期望的是它会报NameError: undefined local variable or method `a' for main:Object这样的错,然后停止对 a 赋值。

我没有看过 ruby 代码,猜想当代码执行到那一句时,先是解释"a=",没有 a 这个变量,于是乎创建一个 a 并赋值 nil,然后在"a+1"时 a 就是 nil。

上面的想法可以通过执行

b = b.nil?

来验证,会返回 true。

那么它带来的问题是当我在一个类中,想对一个方法的返回值进行处理然后赋值给同名的变量时,就会报错了。

有点儿绕口,看代码:

class C
  def a
    1
  end

  def add_wrong
    a = a + 1
  end

  def add_right
    a = self.a + 1
  end
end

c = C.new
c.a          #=> 1
c.add_wrong  #=> NoMethodError: undefined method `+' for nil:NilClass
c.add_right  #=> 2

摘自:http://fuhao.im/2014/05/08/ruby-tips.html.

同名变量是@a 吧,如果你确定 a 是 C 的实例变量的话。

NoMethodError 之后程序就停止执行了,还考虑后面干嘛呢?

先解释 a+1 吧 想不报错,用 a ||= 0 保证初始化了

这是左值右值的问题,define lvalue, evaluate rvalue 么?纯属瞎猜

def add_wrong
    a = a + 1
end

这个方法中的左边的 a 是个变量,覆盖了 a 方法,所以现在的 a 是个 nil,怎么去 +1 呢,而你下面的 self.a 这样是调用当前 scope object 的 a 方法,也就是你的类的 instance method a

这么解释:局部变量只要解释器看到它赋值了就可以,并不在乎赋值是否真实发生。

如:a=1 if false

这个时候 a 并没有真的被赋值,所以其值为 nil

同理,后来的例子中,解释器看到 a 的赋值,a 就被认为是局部变量了。

先右值 直接

a + 1

也是错的,ruby 里没有赋值的都是 nil,不需要等 a=才会给 a 赋值 nil

#7 楼 @azhao a+1 返回的错误是:undefined local variable or method `a' for main:Object. 说明还没有 a 这个变量,a.nil?同样

#6 楼 @mingyuan0715 嗯,我想说的是在执行”a=a+1"时,a 先被赋值为 nil,add_wrong 就是这个陷阱的真实情况了。

#3 楼 @chaucerling 先解释 a+1 的话就应该报 undefined local variable or method `a' for main:Object 的错呀

#2 楼 @rei 嘿嘿,就是好奇 ruby 的实现

#1 楼 @davidwei a 就是一个局部变量,同名只因为用着方便。

局部变量的声明会被置顶, ruby 里没有单独的变量声明过程,所以用 js 举个例子,

var a = a + 1;

实际上是

var a;
a = a + 1;

在程序代码执行之前,a 就已经存在了 不信反汇编一下

code = <<EOF
a = a + 1
EOF

puts RubyVM::InstructionSequence.compile(code).disasm # =>

=begin
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@3] s1)
[ 2] a          
0000 trace            1                                               (   1)
0002 getlocal_OP__WC__0 2
0004 putobject_OP_INT2FIX_O_1_C_ 
0005 opt_plus         <callinfo!mid:+, argc:1, ARGS_SKIP>
0007 dup              
0008 setlocal_OP__WC__0 2
0010 leave            
=end

a 已经被放进了 local table,而程序直接就用 getlocal 去获取局部变量 a 了,也就是代码真正被执行之前,局部变量 a 已经被声明了

也就是说不用真的执行赋值,只要 Ruby 解析器能看到某个名字出现在赋值符号的左边就够了,就会创建一个这个名字的变量,未赋值时值为 nil。

http://rednaxelafx.iteye.com/blog/361770

需要 登录 后方可回复, 如果你还没有账号请 注册新账号