Dziwne zachowanie zmiennych string przy używaniu gsub, sub

Pisałem mały skrypt w Ruby i napotkałem coś dziwnego:

irb(main):019:0> napis1 = 'dobro' => "dobro" irb(main):020:0> napis2 = napis1 => "dobro" irb(main):021:0> napis2.gsub! 'dobro', 'zlo' => "zlo" irb(main):022:0> napis2 => "zlo" irb(main):023:0> napis1 => "zlo"
Czy ktoś może mi wyjaśnić dlaczego obie zmienne się zmieniają? Używam Ruby w wersji 1.8.7.

Bo obie zmienne wskazują na ten sam obiekt. Tym obiektem jest String, którego poprosiłeś o zmienienie sobie treści.

“Zmienna” to tylko adres do obiektu.

Aha, dzięki. :slight_smile:

Myślałem, że

napis2 = napis1

tworzy drugi obiekt.

Czyli działający przykład wygląda tak:

irb(main):054:0> napis1 = 'dobro' => "dobro" irb(main):055:0> napis2 = String.new napis1 => "dobro" irb(main):056:0> napis2.gsub! 'dobro', 'zlo' => "zlo" irb(main):057:0> napis2 => "zlo" irb(main):058:0> napis1 => "dobro"

porównaj

[code=ruby]> napis1 = “dobro”

napis2 = napis1
napis3 = napis1.clone[/code]
a także http://railsblogger.blogspot.com/2009/03/ruby-dup-vs-clone.html

Użyj metody #clone (http://apidock.com/ruby/Object/clone).

Ok, teraz wszystko jest jasne, dzięki.

class Klass attr_accessor :str end s1 = Klass.new #=> #<Klass:0x401b3a38> s1.str = "Hello" #=> "Hello" s2 = s1.clone #=> #<Klass:0x401b3998 @str="Hello"> s2.str[1,4] = "i" #=> "i" s1.inspect #=> "#<Klass:0x401b3a38 @str=\"Hi\">" s2.inspect #=> "#<Klass:0x401b3998 @str=\"Hi\">"
Może ktoś wytłumaczyć, dlaczego tak się dzieje?

s1.str = "Hello"

Tylko przypisujesz referencję do stringa. String nie jest klonowany.

Google -> deep clone

W rubym każde przypisanie to przypisanie referencji.

Może dokładniej: s1.clone sklonował obiekt s1. Ale zawartością obiektu s1 był adres jakiegoś Stringa. Wartość tego adresu pojawiła się też w obiekcie s2, więc wskazywany String wciąż był ten sam.
To co chciałeś uzyskać nazywa się “głębokim” klonowaniem i zazwyczaj musisz sam pisać takie metody. Możesz albo zdefiniować sobie metodę clone_deep albo nadpisać metodę clone:

def clone klon = super klon.str = str.clone klon end
To głębokie klonowanie ma sens dla tych atrybutów, których wartości są obiektami mogącymi zmieniać swój stan. Integery, booleany, symbole nie mogą, ale cała reszta raczej tak.

Ha, chciałem Ci podesłać wynik ‘ri Object#clone’ ale właśnie widzę, że przykład wziąłeś właśnie stamtąd :wink:
Rozumiem, że o Object#dup i Object#duplicable? też już poczytałeś. Różnica jest malutka: dup nie klonuje stanu “frozen”, a clone tak.