destructive method
指的是那些會改變物件(method 的 receiver)方法,也就是這方法是拿同一塊記憶體來用。non-destructive method
則相反,需要重新要一塊記憶體,而不會去改原本那塊記憶體的內容。
關於記憶體位置跟object_id的關係,昨天的文章 Ruby 記憶體管理 GC vs. ObjectSpace有詳細介紹囉
這邊就稍微介紹 object id
x = 2
x.object_id #=> 5
x += 4
x #=> 6
x.object_id #=> 13
任何物件都有屬於自己的object id,從上面的程式碼可以看到 x 原本是2,object_id 是5,加了4之後 object_id也變了。因為在Ruby 數字跟Symbol都是不可改變的,也就是你不能改變x的值,然後x的object_id還是一樣的。不過 Ruby 的 Array, Hash, String 則都是可以改變的。
Time.object_id # class 也有 object_id
# => 70328999205860
首先來看看什麼情況下String的object_id會變,何時又不變
string = '道瓊今天暴跌'
p "string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"
string += '一千點QQ'
p "string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"
output:
"string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189815321160"
"string: 道瓊今天暴跌一千點QQ"
"後來 string 的 object_id是 70189815320560"
可以看到+=
之後,string
的 object_id 變了。
所以string的.+
方法是 non-destructive method
string = '道瓊今天暴跌'
p "string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"
string << '一千點QQ'
p "string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"
output:
"string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189815810980"
"string: 道瓊今天暴跌一千點QQ"
"後來 string 的 object_id是 70189815810980"
object_id 沒變誒
所以 <<
是destructive method
string = '道瓊今天暴跌'
p "本來 string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"
# string 必須傳入,Ruby 不像 JS,會去方法宣告式外面找變數
def dj_down_point(points, string)
string += "#{points}點QQ"
p "方法中 string: #{string}"
p "方法中 string 的 object_id是 #{string.object_id}"
end
dj_down_point(1000, string)
p "後來 string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"
output:
"本來 string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189766779080"
"方法中 string: 道瓊今天暴跌1000點QQ"
"方法中 string 的 object_id是 70189766770560"
"後來 string: 道瓊今天暴跌"
"後來 string 的 object_id是 70189766779080"
可以看到方法裡面string 的object_id跟外面的不同,因為 .+
新建一個物件,導致後來的string
並不如我們預期成為組完字串的樣子。
string = '道瓊今天暴跌'
p "本來 string: #{string}"
p "本來 string 的 object_id是 #{string.object_id}"
def dj_down_point(points, string)
string << "#{points}點QQ"
p "方法中 string: #{string}"
p "方法中 string 的 object_id是 #{string.object_id}"
end
dj_down_point(1000, string)
p "後來 string: #{string}"
p "後來 string 的 object_id是 #{string.object_id}"
output:
"本來 string: 道瓊今天暴跌"
"本來 string 的 object_id是 70189766749660"
"方法中 string: 道瓊今天暴跌1000點QQ"
"方法中 string 的 object_id是 70189766749660"
"後來 string: 道瓊今天暴跌1000點QQ"
"後來 string 的 object_id是 70189766749660"
=> true
object_id都是一樣,這才是我們希望組成的字串
如果沒有注意到object_id的變化,在使用方法可能產生意想不到的bug。
array = [4, 2, 3, 1, 5]
puts "原本的 array = #{array}"
puts "原本的 Object_id = #{array.object_id}"
array_after = array.sort
puts "後來的 array = #{array}"
puts "後來的 array_after = #{array_after}"
puts "後來的 Object_id = #{array.object_id}"
Output:
原本的 array = [4, 2, 3, 1, 5]
原本的 Object_id = 70189817907240
後來的 array = [4, 2, 3, 1, 5]
後來的 array_after = [1, 2, 3, 4, 5]
後來的 Object_id = 70189817907240
array並沒有改變
array = [4, 2, 3, 1, 5]
puts "原本的 array = #{array}"
puts "原本的 Object_id = #{array.object_id}"
array_after = array.sort!
puts "後來的 array = #{array}"
puts "後來的 array_after = #{array_after}"
puts "後來的 Object_id = #{array.object_id}"
Output:
原本的 array = [4, 2, 3, 1, 5]
原本的 Object_id = 70189817878380
後來的 array = [1, 2, 3, 4, 5]
後來的 array_after = [1, 2, 3, 4, 5]
後來的 Object_id = 70189817878380
所以array內容被改變了,Object_id 還是 同一塊記憶體
=> sort! 是 destructive method
從api 文件可以看得出來
https://docs.ruby-lang.org/en/2.6.0/Array.html#method-i-sort
sort → new_ary
sort {|a, b| block} → new_ary
sort! → ary
sort! {|a, b| block} → ary
所以使用sort!,你可以不把回傳值接起來。
還有其他方法,像是Hash的 merge
是non-destructive method
而merge!
是destructive method
Ruby很貼心的在 destructive method
加上驚嘆號!
,提醒你這是會改變原本的物件的。
昨天的文章有提到,為了更有效率的使用記憶體,盡量重複使用同一個物件。所以為了程式執行的效率,不妨試著使用一些destructive method
,尤其在迴圈之中,效能累績起來也是可觀的。
Yes! First week finish!