FIGYELEM! Ez a dokumentum kizárólag az ELTE IK hallgatók számára oktatási célra készült! Félkész munka, dolgozunk rajta! Terjeszteni, felhasználni máshol a szerzők engedélye nélkül tilos! |
|
Kifejezések
Mindezidáig játszi könnyedséggel kezeltük a Ruby kifejezéseit. Végtére is a=b+c egy elég általános dolog. Egy egész rakás Ruby programot írhatunk e fejezet elolvasása nélkül.
De nem lenne olyan mókás. ;-)
Az első különbség a Ruby-ban, hogy minden, ami ésszerűen egy értéket adna vissza, az meg is teszi - szinte minden kifejezés. Mit jelent ez a gyakorlatban?
Néhány nyilvánvaló dolgot, mint például az utasítások láncba fűzését.
a = b = c = 0
|
» 0
|
[ 3, 1, 7, 0 ].sort.reverse
|
» [7, 3, 1, 0]
|
|
Kevésbé egyértelmű, hogy minden, ami normálisan Java-ban, vagy C-ben utasítás, az a Ruby-ban kifejezés. Például, mind az if , mind a case utasítás az utolsó futtatott kifejezés értékét adják vissza.
songType = if song.mp3Type == MP3::Jazz
|
|
if song.written < Date.new(1935, 1, 1)
|
|
Song::TradJazz
|
|
else
|
|
Song::Jazz
|
|
end
|
|
else
|
|
Song::Other
|
|
end
|
|
|
|
|
|
rating = case votesCast
|
|
when 0...10 then Rating::SkipThisOne
|
|
when 10...50 then Rating::CouldDoBetter
|
|
else Rating::Rave
|
|
end
|
|
|
Operátor kifejezések
A Ruby-ban megtalálhatók az alap operátorok (+,-,*,/, és így tovább), valamint tartogat néhány meglepetést. Az operátorok, és teljes precedenciájuk lásd később.
A Ruby-ban sok operátor valójában metódushívás. Amikor a*b+c -t írunk, tulajdonképp megkérjük az a objektumot, hogy futtassa le a * metódust a b paraméterrel. Ezután a számítások eredményeként létrejövő objektumot megkérjük, hogy futtassa le a + metódust c paraméterrel. Ez ekvivalens azzal, hogy:
Mivel minden objektum, és mivel átdefiniálhatjuk a példánymetódusokat, mindig átdefiniálhatjuk az alap aritmetikát, ha nem tetszenek a kapott válaszok.
class Fixnum
|
|
alias oldPlus +
|
|
def +(other)
|
|
oldPlus(other).succ
|
|
end
|
|
end
|
|
|
|
1 + 2
|
» 4
|
a = 3
|
|
a += 4
|
» 8
|
|
Még hasznosabb maga a tény, hogy az általunk írt osztályok ugyanúgy szerepelhetnek operátor kifejezésekben, mint a beépített objektumok. Például, szeretnénk lefuttatni néhány másodpercnyi részletet a dal közepéből. Használhatnánk a [] operátort a lejátszott szám meghatározásához.
class Song
|
|
def [](fromTime, toTime)
|
|
result = Song.new(self.title + " [extract]",
|
|
self.artist,
|
|
toTime - fromTime)
|
|
result.setStartTime(fromTime)
|
|
result
|
|
end
|
|
end
|
|
|
Ez a kódtöredék kiterjeszti a Song osztályt a [] metódussal, amely két paramétert kap (egy kezdő, és egy végpontot). Visszaad egy új dalt az adott intervallumra levágott számból. Ezután lejátszhatjuk ezt a bemutató részletet:
Vegyes kifejezések
Az olyan egyértelmű operátor kifejezések, és metódushívások, a kevésbé egyértelmű utasítás kifejezések (mint például a már látott if , vagy case ) mellett a Ruby rendelkezik még néhány dologgal, amit kifejezésekben használhatunk.
Parancs kifejezések
Ha ´ jelek közé zárunk egy string-et, vagy a kötetlen %-kal prefixelt formát használjuk, akkor az alapvetően az adott operációs rendszeren értelmezett parancsként fog végrehajtódni. A kifejezés értéke a parancs által az alapértelmezett kimenete. A sortörés jelek nem kerülnek eltávolításra.
`date`
|
» "Sun Jun 9 00:08:26 CDT 2002\n"
|
`dir`.split[34]
|
» "lib_singleton.tip"
|
%x{echo "Hello there"}
|
» "Hello there\n"
|
|
Ezekben a parancs kifejezésekben használhatjuk a kiterjesztett kifejezéseket, és minden megszakító utasítást is.
for i in 0..3
|
|
status = `dbmanager status id=#{i}`
|
|
# ...
|
|
end
|
|
|
A parancs kilépési státuszát a $? globális változó tárolja.
A '`' lazasága
A parancs kifejezésének leírásában azt mondtuk, hogy alapvetően parancsként fog végrehajtódni. Valójában string-ként átadódik a Kernel::` metódusnak. Ezt pedig akár felül is definiálhatjuk.
alias oldBackquote `
|
|
def `(cmd)
|
|
result = oldBackquote(cmd)
|
|
if $? != 0
|
|
raise "Command #{cmd} failed"
|
|
end
|
|
result
|
|
end
|
|
|
|
print `date`
|
|
print `data`
|
|
|
Eredménye:
Sun Jun 9 00:08:26 CDT 2002
|
|
prog.rb:3: command not found: data
|
|
prog.rb:5:in ``': Command data failed (RuntimeError)
|
|
from prog.rb:10
|
|
|
Értékadás
Majd minden eddig megtekintett példa használt értékadást. Nos, nagyjából itt az ideje, hogy ejtsünk rólá néhány szót.
Egy értékadó utasítás egy - a bal oldalon szereplő - attribútum, vagy változó értékét (lvalue) állítja át úgy, hogy megegyezzen a jobb oldalon álló értékkel (rvalue). Ezután visszaadja az értéket az értékeadó kifejezés eredményeként. Következmény: az értékadásokat láncba fűzhetjük, valamint használhatunk értékadást viszonylag váratlan helyen is.
a = b = 1 + 2 + 3
|
|
|
|
a
|
» 6
|
b
|
» 6
|
|
|
a = (b = 1 + 2) + 3
|
|
|
|
a
|
» 6
|
b
|
» 3
|
|
|
File.open(name = gets.chomp)
|
|
|
Ruby-ban az értékadásoknak két alapvető formája van. Az első egy objektum referenciát rendel egy változóhoz, vagy konstanshoz. Az értékadás e formája bele van drótozva a nyelvbe.
instrument = "piano"
|
|
MIDDLE_A = 440
|
|
|
Az értékadás második formájában egy objektum attribútuma, vagy elem referenciája szerepel a bal oldalon.
aSong.duration = 234
|
|
instrument["ano"] = "ccolo"
|
|
|
Ezek a formák speciálisak, mert a bal oldalon metódushívások szerepelnek, ami azt jelenti, hogy akár felül is definiálhatjuk őket.
Már láttuk, hogy definiálunk egy írható objektum attribútumot. Egyszerűen definiálunk egy metódust, melynek nevének végén egy egyenlőségjel szerepel. A metódus paraméterként az értékadás jobb oldalán szereplő értéket kapja.
class Song
|
|
def duration=(newDuration)
|
|
@duration = newDuration
|
|
end
|
|
end
|
|
|
Nincs okunk arra, hogy ezeket az attribútum állító metódusokat összhangban tartsuk a belső példányváltozókkal, vagy hogy minden attribútum olvasóhoz adjunk attribútum író metódust is (vagy fordítva).
class Amplifier
|
|
def volume=(newVolume)
|
|
self.leftChannel = self.rightChannel = newVolume
|
|
end
|
|
# ...
|
|
end
|
|
|
Miért írtunk self.leftChannel -t a példában? Van itt egy kis trükk az írható attribútumokkal. Általábon az egy osztályon szereplő metódusok hívhatják az ugyanazon osztályon - vagy ősosztályon - belül található metódusokat funkcionális formában (azaz a self objektumot adva meg fogadóként). Ez azonban nem működik az attribútum írókkal. A Ruby meglátja az értékadást, és úgy dönt, hogy a bal oldalon szereplő névnek egy lokális változónak kell lennie, nem pedig egy attribútum íróra vonatkozó metódushívásnak.
class BrokenAmplifier
|
|
attr_accessor :leftChannel, :rightChannel
|
|
|
|
def volume=(vol)
|
|
leftChannel = self.rightChannel = vol
|
|
end
|
|
end
|
|
|
|
ba = BrokenAmplifier.new
|
|
ba.leftChannel = ba.rightChannel = 99
|
|
ba.volume = 5
|
|
|
|
ba.leftChannel
|
» 99
|
ba.rightChannel
|
» 5
|
|
Elfelejtettük kitenni a self -et az leftChannel értékadásánál, tehát a Ruby egy új, a volume= metódusban található lokális változóban tárolta el az értéket. Az objektum attribútuma azonban nem kerül frissítésre.
Párhuzamos értékadás
Programozási kurzusunk első hete alatt (vagy a második szemeszter, ha laza suliról van szó :)), biztosan írnunk kellett olyan programot, amely két változóban szereplő értéket cserél meg.
int a = 1;
|
|
int b = 2;
|
|
int temp;
|
|
|
|
temp = a;
|
|
a = b;
|
|
b = temp;
|
|
|
Ezt Rubyban sokkal egyszerűbben is megtehetjük:
A Ruby értékadások hatékonyak párhuzamosan is, tehát a hozzárendelt értékeket nem értinti maga az értékadás. A jobb oldalon szereplő értékek abban a sorrendben értékelődnek ki, amelyben megjelennek, még mielőtt a bal oldalon szereplő attribútumokbon bármilyen értékadást végrehajtanánk. Egy másik példát is megtekintünk. a második sor az a , b , és c változókhoz rendeli az x , x+=1 , x+=1 értékeket.
x = 0
|
» 0
|
a, b, c = x, (x += 1), (x += 1)
|
» [0, 1, 2]
|
|
Amikor egy értékadás bal oldalán több, mint egy érték szerepel, akkor az értékadás viszatérési értéke a jobb oldalon szereplő értékek alkotta tömb. Ha egy értékadás bal oldalán több érték szerepel, mint a jobb oldalon, akkor a többletet jelentő baloldai értékek nil értéket kapnak. Ha egy párhuzamos értékadásban több jobb oldali érték szerepel, mint bal oldali, akkor a fölösleges értékeket figyelmen kívül hagyjuk. Ha az értékadás bal oldalán csak egy érték szerepel, a jobb oldalon pedig több, akkor azok egy tömbként értékelődnek ki.
A Ruby párhuzamos értékadás operátorát használva kiterjeszthetünk, vagy épp korlátozhatunk tömböket. Ha az utolsó bal oldali érték egy *-gal kezdődik, akkor minden maradék jobb oldali értéket - egy tömbbe gyűjtve - kapja értékül. Hasonlóképp, ha az utolsó jobb oldali utolsó térkép egy tömb, és egy *-gal kezdődik, akkor azt helyben kibontja elemeire. (Ez nem szükséges, ha a jobb oldalon egyedül egy tömb szerepel. Ekkor ez automatikusan megtörténik.)
a = [1, 2, 3, 4]
|
|
b, c = a
|
» b == 1, c == 2
|
b, *c = a
|
» b == 1, c == [2, 3, 4]
|
b, c = 99, a
|
» b == 99, c == [1, 2, 3, 4]
|
b, *c = 99, a
|
» b == 99, c == [[1, 2, 3, 4]]
|
b, c = 99, *a
|
» b == 99, c == 1
|
b, *c = 99, *a
|
» b == 99, c == [1, 2, 3, 4]
|
|
Beágyazott (nested) értékadások
A párhuzamos értékadásoknak van még egy kihangsúlyozandó előnye. A bal oldalon szerepelhet zárójelezett kifejezés. A Ruby úgy kezeli ezeket, mintha beágyazott értékadó utasítások lennének. Kitömöríti a kapcsolódó jobb oldali értéket, majd hozzárendeli azt a zárójelezett kifejezésekhez, mielőtt folytatná az értékadás magasabb szintű végrehajtását.
b, (c, d), e = 1,2,3,4
|
» b == 1, c == 2, d == nil, e == 3
|
b, (c, d), e = [1,2,3,4]
|
» b == 1, c == 2, d == nil, e == 3
|
b, (c, d), e = 1,[2,3],4
|
» b == 1, c == 2, d == 3, e == 4
|
b, (c, d), e = 1,[2,3,4],5
|
» b == 1, c == 2, d == 3, e == 5
|
b, (c,*d), e = 1,[2,3,4],5
|
» b == 1, c == 2, d == [3, 4], e == 5
|
|
Az értékadás más formái
Sok más nyelvhez hasonlóan a Ruby-ban is megtalálható egy szintaktikus rövidítés: a=a+2 írható a+=2 formában is.
A Ruby a második formát visszaalakítja az elsőre. Azaz a saját osztályainkban metódusként definiált operátoraink a vártnak megfelelően működnek.
class Bowdlerize
|
|
|
|
def initialize(aString)
|
|
@value = aString.gsub(/[aeiou]/, '*')
|
|
end
|
|
|
|
def +(other)
|
|
Bowdlerize.new(self.to_s + other.to_s)
|
|
end
|
|
|
|
def to_s
|
|
@value
|
|
end
|
|
end
|
|
|
|
a = Bowdlerize.new("damn ")
|
» d*mn
|
a += "shame"
|
» d*mn sh*m*
|
|
Feltételes végrehajtás
A Ruby-ban több különböző feltételes végrehajtást támogató mechanizmus is található. Nagyrészük ismerős lesz, de egy részük elsőre furcsának tűnhet. Mielőtt fejest ugranánk ezekbe, töltsünk kis időt a boolean (logikai) kifejezésekkel.
Logikai kifejezések
A Ruby igen egyszerű igazság-fogalommal rendelkezik. Minden érték, ami nem nil , vagy a konstans false , az igaz. Megfigyelhetjük, hogy a könyvtárakban található rutinok igen következetesen használják ezt. Például az IO#gets , ami egy file következő sorát adja vissza, nil ad a file végénél, ami lehetővé teszi a következő ciklus megírását:
while line = gets
|
|
# sor feldolgozása
|
|
end
|
|
|
Habár, itt van egy kis csapda a C, C++, és Perl programozók számára. A 0 (nulla) szám nem hamisként értékelődik. Még csak nem is egy nulla hosszúságú string-ként. Ezt a megszokást nehéz lehet levetkőzni...
Defined?, And, Or, és Not
A Ruby támogatja az összes normális operátort, valamint bemutat egy újat is, amelynek neve: defined? .
Mind az and, mind az &&, akkor ad igaz értéket, ha mindkét operandusa igaz. Csak akkor értékelik ki a második operandust, ha az első igaz. Az egyetlen különbség a kettő között a precedenciában található: and alacsonyabb szintű, mint a &&.
Hasonlóképp, mind az or, mind az || igaz értéket ad vissza, ha legalább az egyik operandus igaz. Csak akkor értékeli ki a második paramétert, ha az első hamis. A különbség: or alacsonyabb precedenciájú, mint az ||.
Hogy érdekesebbé tegyük a dolgot, az and és az or egymással megegyező precedenciájú, ellenben az && magasabb precedenciájú, mint az ||.
A not, és a ! operátorok az operandusok ellentettjét adják vissza (hamis, ha az operandus igaz, valamint igaz, ha az operandus hamis). És igen, a not és a ! csak a precedenciában különbözik.
A defined? operátor nil -t ad vissza, ha az argumentuma (ami akár egy kifejezés lehet) nem értelmezett, egyébként pedig visszaadja az argumentum leírását. Ha az argumentum a yield , akkor a yield string-et adja vissza, ha létezik az aktuális környezethez kapcsolt blokk.
defined? 1
|
» "expression"
|
defined? dummy
|
» nil
|
defined? printf
|
» "method"
|
defined? String
|
» "constant"
|
defined? $&
|
» nil
|
defined? $_
|
» "global-variable"
|
defined? Math::PI
|
» "constant"
|
defined? ( c,d = 1,2 )
|
» "assignment"
|
defined? 42.abs
|
» "method"
|
|
A logikai operátorokhoz kapcsolódóan, a Ruby objektumok támogatják az ==, ===, <=>, =~, eql? , és equal? operátorokkal való összehasonlítást. Mind - kivéve az <=> operátort - az Object osztályban van definiálva, ám gyakran felülírjuk őket, az adott szemantikának megfelelően. Például, az Array osztály átdefiniálja az == operátort, tehát két tömb akkor egyenlő, ha ugyanannyi elemük van, és a kapcsolódó elemek megegyeznek.
Általános összehasonlító operátorok
Operátor |
Jelentés |
== |
Érték szerinti egyenlőség-vizsgálat. |
=== |
Egy "case" utasítás "when" kifejezésében egyenlőség-vizsgálat. |
<=> |
Általános összehasonlítás. -1-et, 0-át, +1-et ad vissza, attól függően, hogy a fogadója kisebb, egyenlő, vagy nagyobb, mint az argumentuma. |
<, <=, >=, > |
A kisebb, kisebb-egyenlő, nagyobb-egyenlő, nagyobb operátorok. |
=~ |
Reguláris kifejezés mintaillesztés. |
eql? |
Igaz, ha mind a fogadó, mind az argumentum megegyező típusú. Például 1 == 1.0 igaz, de 1.eql?(1.0) hamis. |
equal? |
Igaz, ha a fogadónak, és az argumentumnak megegyezik az objektum azonosítója (object id) |
 |
|
|
Az == -nek és az =~ -nek létezik negált formája, azaz != , !~ . Habár ezeket a Ruby a program olvasásakor átkonvertálja, tehát a!=b ekvivalens !(a==b) -vel, valamint a!~b ekvivalens !(a=~b) -vel. Ez azt jelenti, hogy ha írunk egy az == , és az =~ operátorokat felüldefiniáló osztályt, akkor egyből kapjuk a megfelelő != , !~ operátorokat is, nem kell őket definiálni.
A Ruby intervallumokat használhatjuk logikai kifejezésekként. Egy exp1..exp2 intervallum hamis lesz, amíg exp1 igazzá nem válik. Ezután igazként értékelődik ki, amíg exp2 igazzá nem válik. Ezután az intervallum újraindul, és kezdődik minden előlről.
Végül, használhatunk egy reguláris kifejezést logikai kifejezésként. A Ruby átfogalmazza $_ = ~/re/ formára.
Az If, és az Unless kifejezések
Egy Ruby if kifejezés nagyon hasonló más nyelvek if utasításaihoz.
if aSong.artist == "Gillespie" then
|
|
handle = "Dizzy"
|
|
elsif aSong.artist == "Parker" then
|
|
handle = "Bird"
|
|
else
|
|
handle = "unknown"
|
|
end
|
|
|
Ha az if utasításainkat több sorba rendezzük, a then kulcsszót el is hagyhatjuk.
if aSong.artist == "Gillespie"
|
|
handle = "Dizzy"
|
|
elsif aSong.artist == "Parker"
|
|
handle = "Bird"
|
|
else
|
|
handle = "unknown"
|
|
end
|
|
|
Habár, ha szorosabban rendezzük a kódot, a then kulcsszót muszáj kitenni, hogy elkülönítsük a logiaki kifejezést az őt követő utasítástól.
if aSong.artist == "Gillespie" then handle = "Dizzy"
|
|
elsif aSong.artist == "Parker" then handle = "Bird"
|
|
else handle = "unknown"
|
|
end
|
|
|
Lehet 0, vagy több elsif kifejezésünk, és egy opcionális else kifejezésünk.
Mint azt már említettük, az if valójában egy kifejezés, nem pedig utasítás - értéket ad vissza. Nem kell feltétlen felhasználnunk ezt az értéket, ám néha hasznos lehet.
handle = if aSong.artist == "Gillespie" then
|
|
"Dizzy"
|
|
elsif aSong.artist == "Parker" then
|
|
"Bird"
|
|
else
|
|
"unknown"
|
|
end
|
|
|
A Ruby-ban megtalálható az if negált formája is. Ez pedig az unless .
unless aSong.duration > 180 then
|
|
cost = .25
|
|
else
|
|
cost = .35
|
|
end
|
|
|
Végül, a C rajongóknak, a Ruby támogatja a C formájú feltételes kifejezéseket is.
cost = aSong.duration > 180 ? .35 : .25
|
|
|
A feltételes kifejezés vagy a kettőspont előtti, vagy az utáni értéket adja vissza, attól függően a kérdőjel előtti logikai kifejezés igaz, vagy hamis voltától. Ebben az esetben, ha a szám hosszabb, mint 3 perc, a kifejezés .35-öt ad vissza, rövidebb számok esetén pedig .25-öt. Bármelyik is az eredmény, azt a cost változó tárolja.
If és unless módosítók
A Ruby tartalmazza a Perl egyik elegáns funkcióját. Az utasítás módosítók lehetővé teszik a feltételes állítások normál utasítások mögé helyezését.
mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/
|
|
puts "a = #{a}" if fDebug
|
|
print total unless total == 0
|
|
|
Egy if módosítót megelőző kifejezés csak akkor értékelődik ki, ha a feltétel igaz. Az unless pont ellenkezőleg működik.
while gets
|
|
next if /^#/ # megjegyzések átugrása
|
|
parseLine unless /^$/ # Ne elemezzünk üres sorokat
|
|
end
|
|
|
Mivel az if egy kifejezés, írhatunk nagyon homályos utasításokat is, mint például:
if artist == "John Coltrane"
|
|
artist = "'Trane"
|
|
end unless nicknames == "no"
|
|
|
Ez az út az őrület kapujához vezet.
Case kifejezések
A Ruby case kifejezése nagyon erőteljes eszköz: egy többágú if .
case inputLine
|
|
|
|
when "debug"
|
|
dumpDebugInfo
|
|
dumpSymbols
|
|
|
|
|
|
when /p\s+(\w+)/
|
|
dumpVariable($1)
|
|
|
|
|
|
when "quit", "exit"
|
|
exit
|
|
|
|
|
|
else
|
|
print "Illegal command: #{inputLine}"
|
|
end
|
|
|
Mint az if utasításoknál, a case is értéket ad vissza, mégpedig az utolsó kiértékelt kifejezését, és szintén szükséges a then kulcsszó kiírása, ha a kifejezéseket a feltételekkel egy sorba írjuk.
kind = case year
|
|
when 1850..1889 then "Blues"
|
|
when 1890..1909 then "Ragtime"
|
|
when 1910..1929 then "New Orleans Jazz"
|
|
when 1930..1939 then "Swing"
|
|
when 1940..1950 then "Bebop"
|
|
else "Jazz"
|
|
end
|
|
|
A case működése során összehasonlítja a célkifejezést (a case kulcsszó után találhatót) minden when kulcsszó után található kifejezéssel. Ez az összehasonlítás az === operátor használatával történik. Mindaddig, amíg egy osztály értelmes === operátorral rendelkezik - és minden beépített osztály rendelkezik ilyennel -, az adott osztály objektumai használhatók ilyen esetszétválasztós kifejezésekben.
Például a reguláris kifejezések az === operátort egyszerű mintaillesztéssel definiálják.
case line
|
|
when /title=(.*)/
|
|
puts "Title is #$1"
|
|
when /track=(.*)/
|
|
puts "Track is #$1"
|
|
when /artist=(.*)/
|
|
puts "Artist is #$1"
|
|
end
|
|
|
A Ruby osztályok a Class osztály példányai, amely definiál egy === operátort annak ellenőrzésére, hogy az argumentum az osztály, vagy annak valamely ősosztályának példánya-e? Tehát (mellőzve a többszörös öröklődés előnyeit) tesztelhetjük az osztályok objektumait:
case shape
|
|
when Square, Rectangle
|
|
# ...
|
|
when Circle
|
|
# ...
|
|
when Triangle
|
|
# ...
|
|
else
|
|
# ...
|
|
end
|
|
|
Ciklusok
El ne áruljuk senkinek, de a Ruby beépítettet ciklus-szerkezetei nagyon primitívek.
A while 0, vagy több alkalommal futtatja le a törzsében található kódot, mindaddig, amíg a feltétele igaz. Például, a következő kifejezés addig olvas, amíg az bement el nem fogy.
Ennek van egy negált formája is, amely addig futtatja törzsét, amíg a feltétel igaz nem lesz.
until playList.duration > 60
|
|
playList.add(songList.pop)
|
|
end
|
|
|
Az if -hez és unless -hez hasonlóan, mindkét ciklus lehet utasítás módosító is.
a *= 2 while a < 100
|
|
a -= 10 until a < 100
|
|
|
A logikai kifejezéseknél említettük, hogy az intervallumokat használhatjuk egyfajta ugrándozásra is, igazat adva vissza, amikor egy esemény igazzá válik, és mindaddig igazat visszaadni, amíg egy másik esemény igazzá nem válik. Ezt általában ciklusokban is használhatjuk. A következő példában egy szöveget tartalmazó file-ból beolvassuk az első 10 számot, mégpedig betűvel leírva (első, második, ...), de csak azokat írjuk ki, amelyekben megtalálható a harmadik, negyedik, ötödik.
file = File.open("ordinal")
|
|
while file.gets
|
|
print if /third/ .. /fifth/
|
|
end
|
|
|
Eredménye:
Egy logikai kifejezésben használt intervallum elemei önmaguk is lehetnek kifejezések. Ezek minden alkalommal kiértékelődnek, amikor a nagy logikai kifejezés kiértékelődik. Például a következő program kihasználja, hogy a $. változó az aktuális bemeneti sor számát tárolja.
file = File.open("ordinal")
|
|
while file.gets
|
|
print if ($. == 1) || /nyol/ .. ($. == 3) || /kil/
|
|
end
|
|
|
Eredménye:
első
|
|
második
|
|
harmadik
|
|
nyolcadik
|
|
kilencedik
|
|
|
Van egy kis szépséghibája a while , uncle utasítások módosítására használatának. Ha egy begin/end blokkot módosítanak: a kód legalább egyszer végrehajtódik, a logikai kifejezés értékétől függetlenül.
print "Hello\n" while false
|
|
begin
|
|
print "Goodbye\n"
|
|
end while false
|
|
|
Eredmény:
Iterátorok (bejárók)
Ha elolvassuk az előző fejezet elejét, kicsit elbátortalanodhatunk. A Ruby beépítettet ciklus-szerkezetei nagyon primitívek . Azért ne csüggedjünk, van jó hír is. A Ruby nem igényel semmiféle bonyolult beépített ciklusokra, mert minden mókás dolgot a Ruby iterátorokkal valósít meg.
Például, a Ruby-ban nincs for ciklus - legalábbis nem olyan, amit Java-ban, C-ben, vagy C++-ban találunk. Ehelyett a Ruby olyan beépített osztályokban található metódusokat használ, amelyek ezzel ekvivalens, de kisebb hibavalószínűségű működést garantálnak.
Nézzünk meg néhány példát.
3.times do
|
|
print "Ho! "
|
|
end
|
|
|
Eredménye:
Így könnyű elkerülni a szakállas, és eggyel-elcsúszok hibákat - ez a ciklus egymás után háromszor kerül végrehajtásra. A times -zal kapcsolatban: az egészek speciális intervallumokon belül is tudnak ciklusokat létrehozni, a downto , az upto , és a step használatával.Például a hagyományos 0-tól 9-ig tartó for ciklus (valami i=0; i < 10; i++ féle) a következőképp írható:
0.upto(9) do |x|
|
|
print x, " "
|
|
end
|
|
|
Eredménye:
Egy 0-tól 12-ig hármassával léptető ciklus a következőképp néz ki:
0.step(12, 3) {|x| print x, " " }
|
|
|
Eredménye:
Hasonlóképp, egy tömb elemein (vagy más tárolók elemein) való lépkedés az each metódus használatával igen egyszerű:
[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }
|
|
|
Eredménye:
Azaz, ha egy osztály rendelkezik each metódussal, az Enumerable (felsorolható) modulban található további metódusok is elérhetővé válnak. Például, a File osztály rendelkezik each metódussal, amely a file minden egyes sorát visszaadja lépésenként. Az Enumerable modulban található grep metódus használatával végigjárhatjuk csak azokat a sorokat, amelyek megfelelnek bizonyos feltételeknek.
File.open("ordinal").grep /n$/ do |line|
|
|
print line
|
|
end
|
|
|
Eredménye:
negyedik
|
|
nyolcadik
|
|
kilencedik
|
|
|
És végül, és valószínűleg utóljára, a Ruby legegyszerűbb ciklusa következik. Ez pedig a loop .
A loop örökké hívogatja a kapcsolódó blokkot (vagy amíg meg nem szakítjuk a ciklust, de ahhoz, hogy megtudjuk, hogyan kell, tovább kell olvasni. :))
For... in
Korábban azt mondtuk, hogy a Ruby beépített primitív ciklusai a while , és az until . Akkor mit is csinál a for dolog? Nos, ő olyan, mint egy szintaktikus kockacukor. Amikor azt írjuk:
for aSong in songList
|
|
aSong.play
|
|
end
|
|
|
A Ruby ezt így érti:
songList.each do |aSong|
|
|
aSong.play
|
|
end
|
|
|
A for és az each közötti egyetlen különbség a törzsükben definiált lokális változók láthatóságában van.
A for használható minden olyan objektumon, amely rendelkezik each metódussal, mint például egy tömb, vagy egy intervallum.
for i in ['fee', 'fi', 'fo', 'fum']
|
|
print i, " "
|
|
end
|
|
|
|
for i in 1..3
|
|
print i, " "
|
|
end
|
|
|
|
for i in File.open("ordinal").find_all { |l| l =~ /d$/}
|
|
print i.chomp, " "
|
|
end
|
|
|
Eredménye:
fee fi fo fum 1 2 3 second third
|
|
|
Mindaddig, amíg az osztályunk rendelkezik each metódussal, addig a for ciklussal bejárhatjuk.
class Periods
|
|
def each
|
|
yield "Classical"
|
|
yield "Jazz"
|
|
yield "Rock"
|
|
end
|
|
end
|
|
|
|
periods = Periods.new
|
|
for genre in periods
|
|
print genre, " "
|
|
end
|
|
|
Eredménye:
Break, redo és next
A ciklusvezérlés tartalmaz break , redo , és next utasításokat, amelyek lehető teszik a normális ciklikus viselkedéstől való eltérést.
A break megszakítja, és azonnal bezárja a az adott ciklust, a program pedig a ciklus blokkját követő első utasítással folytatódik. A redo újrakezdi a ciklust a feltétel újrakiértékelése, vagy a következő elem generálása nélkül. A next a ciklus végére ugrik, és kezdi a következő elem feldolgozását.
while gets
|
|
next if /^\s*#/
|
# megjegyzések átugrása
|
break if /^END/
|
# végén megállás
|
redo if gsub!(/`(.*?)`/) { eval($1) }
|
|
|
# sor feldolgozása
|
end
|
|
|
Ezek a kulcsszavak iterátor alapú ciklusoknál is használhatók:
i=0
|
|
loop do
|
|
i += 1
|
|
next if i < 3
|
|
print i
|
|
break if i > 4
|
|
end
|
|
|
Eredménye:
Retry
A redo utasítás a ciklus aktuális menetének megismétlését hajtja végre. Néha azonban szükség van a teljes ciklus megismétlésére, a legelejétől. Erre való a retry utasítás, amely bármilyen iterátor ciklust újrakezd.
for i in 1..100
|
|
print "Now at #{i}. Restart? "
|
|
retry if gets =~ /^y/i
|
|
end
|
|
|
Interaktív futtatás esetén a következőt láthatjuk.
Now at 1. Restart? n
|
|
Now at 2. Restart? y
|
|
Now at 1. Restart? n
|
|
. . .
|
|
|
A retry újra kiértékeli az iterátor összes argumentumát az újraindítás előtt. Az online Ruby dokumentáció a következő csináld-magad amíg ciklust hozza példának.
def doUntil(cond)
|
|
yield
|
|
retry unless cond
|
|
end
|
|
|
|
i = 0
|
|
doUntil(i > 3) {
|
|
print i, " "
|
|
i += 1
|
|
}
|
|
|
Eredménye:
Változók láthatósága, és a ciklusok
A while , az until , és a for ciklusok beépített ciklusok, és nem tartalmaznak semmi újdonságot a láthatósággal kapcsolatban - az előzőleg létezett lokális változik használhatók a ciklus törzsében, és minden új lokális változó elérhető utánna is.
Az iterátorok -mint a loop , és az each - által használt blokkok már kicsit másak. Általában az ezen blokkokon belül létrehozott változók nem érhetők el a blokkon kívül.
[ 1, 2, 3 ].each do |x|
|
|
y = x + 1
|
|
end
|
|
|
|
[ x, y ]
|
|
|
Eredménye:
prog.rb:4: undefined local variable or method `x'
|
|
for #<Object:0x401c2ce0> (NameError)
|
|
|
Habár, ha a blokk futásának ideje alatt már létezik egy lokális változó az adott blokkban szereplő névvel, akkor a már létező lokális változót használja a blokk. Azaz a blokk vége után az értéke elérhető marad. Ahogy a következő példa is mutatja, ez érvényes a blokkon belüli normális változókra, és a blokk paraméterére is.
x = nil
|
|
y = nil
|
|
[ 1, 2, 3 ].each do |x|
|
|
y = x + 1
|
|
end
|
|
|
|
[ x, y ]
|
» [3, 4]
|
|
|