Ruby的赋值和拷贝

1 赋值
2 拷贝
3 clone和dup的区别
4 拷贝中的回调函数
5 rails中的拷贝

1 赋值
赋值是指向同一个引用,两个是同一个对象。

a = "demo"
b = a
p a.object_id #=>70198266701020
p b.object_id #=>70198266701020

#因为a,b指向同一个对象,改变其中任何一个对象,另外一个对象都会被改变。

b[0] = "x"
b #=>"xemo"
#改变b, a也会改变
a #=>"xemo"
p a.object_id #=>70198266701020
p b.object_id #=>70198266701020
Ruby的赋值和拷贝_第1张图片
两个对象相同的对象,指向同一内存地址

如果对b重新进行赋值,那么a,b为不同的对象。

a = "demo"
b = a
p a.object_id #=>70198266701020
p b.object_id #=>70198266701020

b = "xx"
a #=> "demo"
#a,b为没有关系的两个对象
p a.object_id #=>70198266701020
p b.object_id #=>70198263178200
Ruby的赋值和拷贝_第2张图片
重新赋值之后变为两个不同的对象

2 拷贝
拷贝也分为浅拷贝和深拷贝,浅拷贝涉及到两个方法分别是dup和clone。浅拷贝只拷贝对象的第一层,而对于对象的第二层以及更多的嵌套层不做拷贝,这里的拷贝是指指向新的内存空间,对于普通对象而言,只拷贝对象的第一层指向,而对于对象的属性不拷贝,对于数组和hash而言,只拷贝第一层的值,而不对第二层以及更深层次的值。深拷贝是对对象以及对象中的任何嵌套以及依赖对拷贝。
上述语言的专业描述:
拷贝的目的是为了得到两个看起来一样但是本质上又不同的对象,这里的本质体现在他们是否指向了同一个存储空间。
在Ruby中,对象的定义是一组实例变量外加一个指向其类的引用。如果其某个实例变量又依赖于另外的对象,那么这个时候我们如何来对待这个依赖的对象呢?
根据我们处理方式的不同将会得到两种不同的拷贝概念
浅拷贝:对所有的依赖对象不做拷贝,仅仅是引用
深拷贝:对所有的依赖(依赖的依赖)对象都做拷贝

针对普通字符串对象:

#普通的字符串对象,只拷贝第一层
a = "demo"
b = a.clone

#a,b是两个不同的对象
p a.object_id #=>70109624896300
p b.object_id #=>70109629296760

#普通的字符串对象,只有一层,其实做到了全部拷贝,改变a对象的值,不影响对象b的值
a[0] = "x"
a #=>"xemo"
p a.object_id #=>70109624896300
b #=>"demo"
p b.object_id #=>70109629296760

针对带有属性的对象

#对对象拷贝了第一层,对对象的第二层就是对象的属性没有进行拷贝
class Demo
  attr_accessor :name
end

obj = Demo.new
obj.name = "jayzen"
obj1 = obj.clone
p obj #=>#
p obj1 #=>#

#没有对对象的属性进行拷贝,属性指向同一个内存地址,改变其中一个也会改变另外一个
obj.name.sub!("j", "x")
p obj #=>#
p obj1 #=>#
Ruby的赋值和拷贝_第3张图片
在第一层不互相影响,第二层的属性值指向同一内存地址,会同时发生改变

针对嵌套的数组对象和嵌套的hash对象

#同样,只对第一层的数组进行拷贝,不对第二层的数组进行数组进行拷贝,同理hash

a = ["x", ["y", "z"]]
b = a.clone
#因为只拷贝第一层,所以a第一层数组变化不影响b
a[0] = "t"
a #=>["t", ["y", "z"]]
b #=>["t", ["y", "z"]]

#第二层数组没有进行拷贝,a第二层数组变化会影响b
a[1][0] = "t"
a #=>["x", ["t", "z"]]
b #=>["x", ["t", "z"]]

#hash的例子也是类似

用Marshal实现深拷贝,即是对于对象的任何一层数组都进行了拷贝

a=["x", ["y", "z"]]
b=Marshal.load(Marshal.dump(a))

a[1][0]="t"
a #=>["x", ["t", "z"]]
#因为进行了深拷贝,a的二层数组值改变之后b并没有改变
b #=>["x", ["y", "z"]]

3 clone和dup的区别
记住这两个的区别只要记住哪个比较特殊就好(和记住proc和lambda的区别类似),其中dup不会复制对象扩展的任何模块,dup不会记住被复制对象的freeze状态。

#dup不会复制扩展对象的任何模块
module Demo
  def test
    puts "this is the demo"
  end
end

class Test
end

obj = Test.new
obj.extend Demo

obj1 = obj.clone
obj1.test #=>"this is the demo"

obj2 = obj.dup
ob2.test #=>"error"

#dup不会记住被复制对象的freeze状态
str="demo"
str.freeze

str.clone.frozen? #=>true
str.dup.frozen? #=>false

4 拷贝中的回调函数
拷贝中存在三个回调函数,分别是
initialize_copy
initialize_clone
initialize_dup
上面两个方法ruby2.4中core中没有,在扩展库中的set类有,但是可以直接使用,不用require。
如果同时存在initialize_copy,initialize_clone两个方法,clone方法执行initialize_clone方法,如果要执行initialize_copy方法,需要在initialize_clone方法中执行super方法。
如果同时存在initialize_copy,initialize_dup两个方法,dup方法执行initialize_dup方法,如果要执行initialize_copy方法,需要在initialize_dup方法中执行super方法。
如果只有initialize_copy,没有其他两个方法,则执行该方法。

class Demo
  def initialize_copy(other)
    #__callee__返回的所在方法体的方法名
    puts __callee__
  end
end

Demo.new.dup #initialize_copy

你可能感兴趣的:(Ruby的赋值和拷贝)