Ruby 二维数组生成小陷阱

hlxwell · 2018年02月09日 · 最后由 hlxwell 回复于 2018年02月10日 · 1420 次阅读

今天想生成一个二维数组,突然突发奇想想出了这个方法 matrix = [[0] * 5] * 10,然后发生奇迹的事情了。

[1] pry(main)> matrix = [[0] * 5] * 10
=> [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
[2] pry(main)> matrix[1][1] = 2
=> 2
[3] pry(main)> matrix
=> [[0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0], [0, 2, 0, 0, 0]]

你会发现每一行的数组其实都是在被复制的,你改动任何一行的数据,其他几行的数据就会一起被改动。

查看 C 源码后发现都是 memcpy 在作祟

ary_memcpy(ary2, 0, t, ptr);
while (t <= len/2) {
    ary_memcpy(ary2, t, t, RARRAY_CONST_PTR(ary2));
    t *= 2;
}
if (t < len) {
    ary_memcpy(ary2, t, len-t, RARRAY_CONST_PTR(ary2));
}

正确方法应该是 matrix = Array.new(10) {Array.new(5, init_value)}

是因为数组是对象,*5 是复制指针而不是复制数据。

msg7086 回复

那么问一下,这个怎么解释

[1] pry(main)> a = [0] * 5
=> [0, 0, 0, 0, 0]
[2] pry(main)> a
=> [0, 0, 0, 0, 0]
[3] pry(main)> a[2] = 4
=> 4
[4] pry(main)> a
=> [0, 0, 4, 0, 0]  <=  按道理这个应该是 [4,4,4,4,4] 因为所有的都是指向同一个地方。

指针被你赋值改掉了呀。而且 4 本来就是不可变对象,你本来就改不了 4 的值。

看下面的例子。

# pry
[1] pry(main)> s = ['0']*4
=> ["0", "0", "0", "0"]
[2] pry(main)> s[0] = '1'
=> "1"
[3] pry(main)> s
=> ["1", "0", "0", "0"]
[4] pry(main)> s[1].replace '2'
=> "2"
[5] pry(main)> s
=> ["1", "2", "2", "2"]
[6] pry(main)> s.map &:object_id
=> [46944791677080, 46944791865940, 46944791865940, 46944791865940]
[7] pry(main)> 

拿你开头的例子来说:

值 [[0 0] [0 0]]
针 [[A A] [A A]]
针 [  B     B  ]
a[0][0] = 2
值 [[2 0] [2 0]]
针 [[C A] [C A]]
针 [  B     B  ]

因为你是把 A 指针换成了 C 指针,所以不会变成 [2 2],但是因为 B 和 B 指向同一个数组,所以会变成 [2 0] [2 0]。

@hlxwell 还记得 C# 的 byVal 和 byRef 么?在 Ruby 里

4, true, 1.3, nil 这些都是 value type,

5..6, "foo", [4,4,4], {a: 3}, Object.new 这些是 reference type ...

luikore 回复

哈哈哈,好久不见了。最近可好。

我觉得看 object_id 还是挺直观的。

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