讲述的知识点包括:1.赋值,2.拷贝,3.clone和dup的区别,4.拷贝中的回调函数,5.rails中的拷贝

赋值

变量之间赋值是指将两个变量指向同一个引用(就是指向同一个内存地址,具有相同的object_id)

#变量之间赋值,两个变量指向同一个引用,他们有共同的object_id
a = "demo"
b = a 
p a.object_id #=>70198266701020
p b.object_id #=>70198266701020

#因为a,b指向同一个对象,改变其中任何一个对象,另外一个对象都会被改变。
b[0] = "x", #b #=>"xemo"
a #=>"xemo" #改变b, a也会改变
p a.object_id #=>70198266701020
p b.object_id #=>70198266701020 #但是两个变量还是指向同一个同一个引用,因为有共同的object_id

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

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

b = "xx"
a #=> "demo"
p a.object_id #=>70198266701020
p b.object_id #=>70198263178200 #a,b为没有关系的两个变量

拷贝

拷贝也分为浅拷贝和深拷贝,浅拷贝涉及到两个方法分别是dup和clone。浅拷贝只拷贝对象的第一层,而对于对象的第二层以及更多的嵌套层不做拷贝,这里的拷贝是指指向新的内存地址(就是拷贝之后的对象和之前对象指向不同的内存地址),对于普通对象而言,只拷贝对象的第一层指向,而对于对象的属性不拷贝,对于数组和hash而言,只拷贝第一层的值,而不对第二层以及更深层次的值。深拷贝是对对象以及对象中的任何嵌套以及依赖对拷贝。

上述语言的专业描述:拷贝的目的是为了得到两个看起来一样但是本质上又不同的对象,这里的本质体现在他们是否指向了同一个存储空间。在Ruby中,对象的定义是一组实例变量外加一个指向其类的引用。如果其某个实例变量又依赖于另外的对象,那么这个时候我们如何来对待这个依赖的对象呢?根据我们处理方式的不同将会得到两种不同的拷贝概念:浅拷贝:对所有的依赖对象不做拷贝,仅仅是引用。深拷贝:对所有的依赖(依赖的依赖)对象都做拷贝。

1.普通字符串的拷贝

#普通的字符串对象,只拷贝第一层
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

2.带有属性的对象

#对象拷贝了第一层(第一层对象的引用指向不同的内存地址),对象的第二层就是对象的属性没有进行拷贝(第二层属性的引用指向相同的内存地址)
class Demo
  attr_accessor :name
end

obj = Demo.new
obj.name = "jayzen"
obj1 = obj.clone
p obj #=>#<Demo:0x007f87292291d0 @name="jayzen">
p obj1 #=>#<Demo:0x007f87292127c8 @name="jayzen">

#没有对对象的属性进行拷贝,属性指向同一个内存地址,改变其中一个也会改变另外一个
obj.name.sub!(/j/, "x")
obj.inspect #=>#<Demo:0x007f87292291d0 @name=/"xayzen/">
obj1.inspect #=>#<Demo:0x007f87292127c8 @name=/"xayzen/">

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

#hash数组也是一个对象,如果进行dup,只是复制了容器本身而非复制其中的元素
#增减数组元素不会互相影响,如果改变了了其中的元素就会互相影响
one = ["hello", "world"]
two = one.dup 
two #["hello", "world"]

one.each{|x| x.sub!("lo", "xx")}
one #["helxx", "world"]
two #["helxx", "world"] #通过sub方法实现了互相影响

one << "88"
one #["hello", "world", "88"]
two #["hello", "world"] #增减元素,不互相影响


#如果采用赋值的形式,只对第一层的数组进行拷贝,不对第二层的数组进行数组进行拷贝,同理hash
a = ["x", ["y", "z"]]
b = a.clone
#因为只拷贝第一层,所以a第一层数组变化不影响b
a[0] = "t"
a #=>["t", ["y", "z"]]
b #=>["x", ["y", "z"]]

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

#hash的例子也是类似

4.用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"]]

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

拷贝中的回调函数

拷贝中存在三个回调函数,分别是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

results matching ""

    No results matching ""