Info 2, Tizenegyedik heti előadás: ruby tömbök mégegyszer, hogyan kezeli a ruby a memóriát

Utolsó módosítás: 2009. április 23.

A rubyban a szögletes zárójelek, ha önmagukban állnak, egy új tömböt hoznak létre. Például a [] kifejezés értéke egy újonnan létrejött tömb, ami jelenleg üres. Ez a tömb különbözik az összes eddig létrehozott tömbtől, tehát ha módosítjuk az elemeit, az a korábbi tömbök elemeit nem módosítja, és viszont.

Ha a szögletes zárójelek egy kifejezés után állnak, és annak a kifejezésnek az értéke egy tömb, akkor a kapott kifejezés a tömb egyik elemét olvassa ki illetve változtatja meg. Ha például az s változó jelenlegi értéke egy tömb, akkor s[0] ennek a tömbnek az első elemét adja vissza, s[0] = 42 pedig módosítja ezt az elemet.

Egy tömb a ruby-ban írható (mutable) érték, tehát a tartalmát meg lehet változtatni. Ha valahol egy tömb értéket tárolunk, például egy lokális változó értékeként, vagy egy tömb elemeként, stb, akkor ez az érték lényegében csak rámutat a tömbre, ha később ezen tömb tartalmát (méretét vagy elemeit) megváltoztatjuk, akkor azon a helyen keresztül, ahol a tömböt tároltuk, szintén az új tartalmat fogjuk látni. eltárolhatjuk így, ekkor ezeken keresztül Így például az a = [0, 0]; b = a; b[0] = 5; utasítások végrehajtása után az a[0] kifejezés eredménye 5. Egy olyan utasítás tehát, mint pl. b[0] = 5; nem változtatja meg a b változó értékét, az továbbra is ugyanaz a tömb lesz.

Másrészt viszont egy olyan utasítás, mint a b = 9; vagy akár b = [8, 2]; megváltoztatja a b értékét, és a régi értéket eldobja; de annak a tömbnek a tartalmát, ami a b korábbi értéke volt, nem változtatja meg. Így ha egy másik változó értéke továbbra is ugyanez a tömb volt, akkor azon keresztül a régi tömböt érhetjük el. Például induljunk ki az előző példából: az a = [0, 0]; b = a; b[0] = 1; utasítások után az a és a b tartalma ugyanaz a b tömb, amelyet a p függvény így ír ki: [1, 0]. Ha most a b = [5, 2]; utasítást hajtjuk végre, akkor a értéke továbbra is a régi tömb, amely így íródik ki: [1, 0]. (A p függvény kimenetéből csak egy tömb tartalma derül ki, az nem, hogy két kiírt tömb ugyanaz a tömb-e, vagy két különböző tömb ugyanazzal a mérettel és azonos kinézetű elemekkel.)

Ráadásul annak a változónak a tartalmát is megváltoztathatjuk, amibe eleve eltároltuk a tömböt: a = [0, 0]; b = a; után kiadhatjuk az a = 5; utasítást, de a kételemű tömb megmarad, mivel egy másik változó, a b hivatkozik rá. Emlékezzünk rá, hogy C nyelben egy tömböt egy tömb változó deklarációja hozott létre, és ha ez a változó megszűnt (mert kiléptünk annak a függvénynek vagy blokknak a futásából, amiben deklaráltuk a tömböt), akkor a tömb is megszűnik, és az esetlegesen a tömb elemeikre mutatókon keresztül már nem szabad az elemeire hivatkozni. Ezzel ellentétben a ruby-ban a tömb akkor jön létre, ha a szögletes zárójeles kifejezéssel létre hozzuk, és később nem szűnik meg létezni. Ennek a legfontosabb alkalmazása, hogy a ruby-ban egy függvényből könnyen visszaadhatunk (visszatérési értékként) egy új tömböt, amit a függvény futása közben hoztunk létre.

A ruby-ban tehát a tömbök (és a sztringek és még néhány másik fajta érték) módosítható tartalmú. Ezzel szemben a számok, a logikai (igaz-hamis) értékek, és még néhány egyéb típusnak nem lehet megváltoztatni a tartalmát, így ha például azt írjuk, hogy a = 4; b = a, akkor mindegy, hogy arra gondolunk, hogy a két változó értéke ugyanaz a szám objektum, vagy két különböző szám objektum azonos tartalommal, mivel a szám tartalmát semmilyen módon nem lehet megváltoztatni, tehát b-vel bármit csinálunk, attól a értéke nem lehet 4 helyett 5.

Nézzünk alkalmazott példákat erre.

meret = 4
matrix = []
(0 ... meret).each {|i|
        sor = []
        (0 ... meret).each {|j|
                sor[j] = i * meret + j
        }
        matrix[i] = sor
}
print "matrix = "
p matrix
print "matrix[2] = "
p matrix[2]
print "matrix[2][0] = "
p matrix[2][0]

Kimenet:

matrix = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]]
matrix[2] = [8, 9, 10, 11]
matrix[2][0] = 8

Az első részt írhatjuk így is:

matrix = []
(0 ... meret).each {|i|
        sor = []
        matrix[i] = sor
        (0 ... meret).each {|j|
                sor[j] = i * meret + j
        }
}

Vagy így.

matrix = []
(0 ... meret).each {|i|
        matrix[i] = []
        (0 ... meret).each {|j|
                matrix[i][j] = i * meret + j
        }
}

Vagy akár így.

matrix = []
(0 ... meret).each {|i|
        matrix[i] = []
}
(0 ... meret).each {|i|
        (0 ... meret).each {|j|
                matrix[i][j] = i * meret + j
        }
}

Azt nem írhatnánk csak (hibát jelezne), hogy

matrix = []
(0 ... meret).each {|i|
        (0 ... meret).each {|j|
                matrix[i][j] = i * meret + j
        }
}
mivel a matrix[i] értékét nem állítottuk be sehol.

Ha pedig ezt írnánk, akkor hibás kimenetet kapunk, mivel a matrix tömb összes eleme ugyanaz a tömb.

matrix = []
sor = []
(0 ... meret).each {|i|
        (0 ... meret).each {|j|
                sor[j] = i * meret + j
        }
        matrix[i] = sor
}

Valóban, lefuttatva a kimenet ez lesz:

matrix = [[12, 13, 14, 15], [12, 13, 14, 15], [12, 13, 14, 15], [12, 13, 14, 15]]
matrix[2] = [12, 13, 14, 15]
matrix[2][0] = 12