试问以下代码执行后,b 和 c 的值分别是什么?
a = [1,2,3]
b = Hash.new []
c = Hash.new 0
a.each { |x| b[x.to_s] << x }
a.each { |x| c[x.to_s] += x }
你是否会觉得是这样:
b = {"1"=>[1], "2"=>[2], "3"=>[3]}
c = {"1"=>1, "2"=>2, "3"=>3}
然而实际结果却是:
b = {}
c = {"1"=>1, "2"=>2, "3"=>3}
为什么 b 还是空的?刚 push 的值哪去了?
刚 push 的值在这:
b['0'] #=> [1, 2, 3]
b['1'] #=> [1, 2, 3]
b 的默认值被修改了,换句话说,那些 push 操作只是修改了默认值而已,并没有给 b 增加新元素。
那么 c 为什么又被修改了呢?
对此我的解释是 Hash 的默认值是放在一个不属于 b 和 c 的空间里的,类似于 js 里的 prototype,每次访问不存在的 key 就返回 prototype 里的值。push 操作是直接对原数据进行更改不生成 copy,所以 b 的空间里仍然没有值,反而是默认值被修改了。而数值是简单类型,运算时会产生 copy,这个 copy 就放在 c 的空间里,所以 c 有值,并且 c 的默认值没有被修改。
String 类型生不生成 copy 呢?这取决于你用什么运算符。String 同时提供了<<和+=运算符,前者不提供 copy,后者提供。
如果我想让数组生成 copy 怎么办呢?Hash.new 也有方案支持需要 copy 的数组——block。
若要 b 不为空,第一段代码第二行可改成
b = Hash.new { |hash, key| hash[key] = [] }
这样 b 就能预期地按简单类型——不修改默认值,并且非空那样运作了。 官方文档:http://ruby-doc.org/core-2.3.0/Hash.html#method-c-new