|
|||
a=b+c
ziemlicher Standard. Sie
könnten eine ganze Menge Code in Ruby schreiben, ohne etwas von diesem Kapitel
gelesen zu haben.
Aber es würde nicht so viel Spaß machen;-)
.
Einer der ersten Unterschiede von Ruby ist, dass alles, was vernünftig einen
Wert zurückgeben kann, das auch tut: fast alles ist ein Ausdruck. Was bedeutet
das in der Praxis?
Zu den offensichtlichen Dingen gehört die Möglichkeit, Anweisungen zu
verketten.
a = b = c = 0 |
» | 0 |
[ 3, 1, 7, 0 ].sort.reverse |
» | [7, 3, 1, 0] |
if
und
case
zum Beispiel geben beide den Wert des zuletzt ausgeführten
Ausdrucks zurück.
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 |
if
und case
.
a*b+c
schreiben, fordern Sie eigentlich das Objekt, das
a
referenziert, auf, die Methode ``*
'' auszuführen
und übergeben dabei b
als Parameter. Dann fordern Sie das aus
der Berechnung resultierende Objekt auf, ``+
'' auszuführen,
wobei c
der Parameter ist. Das entspricht dem Ausdruck
(a.*(b)).+(c) |
class Fixnum |
||
alias oldPlus + |
||
def +(other) |
||
oldPlus(other).succ |
||
end |
||
end |
||
|
||
1 + 2 |
» | 4 |
a = 3 |
||
a += 4 |
» | 8 |
[]
'' verwenden, um die zu extrahierende Musik
anzugeben.
class Song def [](fromTime, toTime) result = Song.new(self.title + " [extract]", self.artist, toTime - fromTime) result.setStartTime(fromTime) result end end |
Song
um
die Methode ``[]
'', die zwei Parameter verlangt (eine Start-
und eine Endzeit). Sie gibt einen neuen Song mit der im Intervall
angegebenen Musik zurück. Wir könnten dann die Einleitung eines Songs
mit Code wie folgendem spielen:
aSong[0, 0.15].play |
if
und case
), gibt es in Ruby einige Dinge mehr, die
Sie in Ausdrücken verwenden können.
`
) angeben oder den
%x
-Begrenzer benutzen, wird dieser (wenn nicht anders
angegeben) als Befehl des darunterliegenden Betriebssystems ausgeführt.
Der Wert des Ausdrücks ist die Standardausgabe des Befehls. Newlines
werden nicht abgeschnitten, also ist es wahrscheinlich, dass am Wert,
den Sie zurückbekommen, noch ein Newline oder ein Zeilenvorschub hängt.
`date` |
» | "Sun Mar 4 23:23:52 CST 2001\n" |
`dir`.split[34] |
» | "lib_pstore.txi" |
%x{echo "Hello there"} |
» | "Hello there\n" |
for i in 0..3 status = `dbmanager status id=#{i}` # ... end |
$?
können Sie auf den Exitstatus
des Programms zugreifen.
Kernel::`
(ein einzelner Backquote) übergeben. Wenn Sie möchten, können Sie diese
Methode überschreiben.
alias oldBackquote ` def `(cmd) result = oldBackquote(cmd) if $? != 0 raise "Command #{cmd} failed" end result end print `date` print `data` |
Sun Mar 4 23:23:52 CST 2001 prog.rb:3: command not found: data prog.rb:5:in ``': Command data failed (RuntimeError) from prog.rb:10 |
a = b = 1 + 2 + 3 |
||
a |
» | 6 |
b |
» | 6 |
a = (b = 1 + 2) + 3 |
||
a |
» | 6 |
b |
» | 3 |
File.open(name = gets.chomp) |
instrument = "piano" MIDDLE_A = 440 |
aSong.duration = 234 instrument["ano"] = "ccolo" |
class Song def duration=(newDuration) @duration = newDuration end end |
class Amplifier def volume=(newVolume) self.leftChannel = self.rightChannel = newVolume end # ... end |
Sidebar: Accessors innerhalb einer Klasse benutzen | ||||||||||||||||||||||||||||||||||||
Warum haben wir im Beispiel auf Seite 76self.leftChannel
geschrieben? Nun, da ist ein verstecktes Gotcha[Von ``got
you'', in etwa ``hab' ich dich''. Wird oft als Bezeichnung für unerwartete
Eigenschaften einer Sprache verwendet, Anm. d. Übers.] bei
beschreibbaren Attributen.
Normalerweise können Methoden innerhalb einer Klasse andere Methoden
der selben Klasse und ihrer Superklasse in funktionioneller Form (d.h.
mit implizitem Empfänger self ) aufrufen. Bei attribute
writers funktioniert das jedoch nicht. Ruby sieht die Zuweisung
und nimmt an, dass der Name links von ihr eine lokale Variable sein
muss, kein Methodenaufruf.
self. '' vor die Zuweisung zu
leftChannel zu schreiben, speicherte Ruby den neuen Wert in
einer lokalen Variable der Methode volume= ; das Attribut
des Objekts wurde nie aktualisiert. Das Aufstöbern dieses Bugs kann
knifflig werden.
|
int a = 1; int b = 2; int temp; temp = a; a = b; b = temp; |
a, b = b, a |
a
, b
und
c
die Werte der Ausdrücke x
, x+=1
bzw.
x+=1
zu.
x = 0 |
» | 0 |
a, b, c = x, (x += 1), (x += 1) |
» | [0, 1, 2] |
nil
gesetzt. Wenn eine
Mehrfachzuweisung mehr R-Werte als L-Werte hat, werden die
übriggebliebenen R-Werte ignoriert. In Ruby 1.6.2 werden, wenn eine
Zuweisung einen L-Wert und mehrere R-Werte hat, die R-Werte in ein Array
konvertiert, das dem L-Wert zugewiesen wird.
Sie können mit Rubys Parallelzuweisungsoperator Arrays expandieren und
zusammenklappen. Wenn der letzte L-Wert mit einem Stern beginnt, werden
alle übriggebliebenden R-Werte gesammelt und diesem L-Wert als Array
zugewiesen. Gleichfalls können Sie auch dem letzten R-Wert, wenn er ein
Array ist, einen Stern voranstellen, und ihn so in seine einzelnen Werte
zerlegen. (Das ist nicht notwenig, wenn der R-Wert das einzige Ding an
der rechten Seite ist -- in diesem Fall wird das Array automatisch
expandiert).
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] |
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 |
a=a+2
kann als a+=2
geschrieben werden.
Die zweite Form wird intern in die erste umgewandelt. Das bedeutet, dass
Operatoren, die Sie als Methoden in Ihren eigenen Klassen definiert
haben, wie erwartet funktionieren:
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* |
nil
oder die Konstante false
ist, ist wahr. Sie
werden bemerken, dass die Bibliotheksroutinen diese Tatsache konsequent
benutzen.
Die Methode IO#gets
zum Beispiel, die die nächste Zeile einer Datei zurückgibt, gibt am Ende
der Datei nil
zurück und ermöglicht es Ihnen Schleifen wie die
folgende zu schreiben:
while line = gets # process line end |
defined?
ein.
Sowohl ``and
'' als auch &&
'' geben true
zurück, wenn beide Operanden true sind. Sie werten den zweiten Operanden
nur aus, wenn der erste true ist (das wird manchmal
``Kurzschlussevaluierung'' genannt). Der einzige Unterschied der beiden
Formen ist der Vorrang (``and
'' bindet schwächer als
``&&
'').
Gleichermaßen geben sowohl ``or
'' als auch ``||
'' true
zurück, wenn einer der Operanden true ist. Sie werten ihren zweiten
Operanden nur aus, wenn der erste false ist. Wie auch bei
``and
'' ist ihr Vorrang der einzige Unterschied zwischen
``or
'' und ``||
''.
Nur um das Leben interessant zu machen haben ``and
'' und
``or
'' gleichen Vorrang, während ``&&
''
höheren Vorrang als ``||
'' hat.
``not
'' und ``!
''
geben das Gegenteil ihres Operanden zurück (false, wenn der Operand true
ist und true, wenn er false ist). Und ja, ``not
'' und
``!
'' unterscheiden sich nur im Vorrang.
Die Vorrangregeln werden in Tabelle 18.4 auf Seite 221 zusammengefasst.
Der Operator defined?
gibt nil
zurück, wenn sein
Argument (das ein beliebige Ausdruck sein kann) nicht definiert ist,
ansonsten gibt er die Beschreibung dieses Arguments zurück. Wenn das
Argument yield
ist, gibt defined?
den String ``yield''
zurück, wenn ein Codeblock mit dem aktuellen Kontext assoziiert ist.
defined? 1 |
» | "expression" |
defined? dummy |
» | nil |
defined? printf |
» | "method" |
defined? String |
» | "constant" |
defined? $& |
» | "$&" |
defined? $_ |
» | "global-variable" |
defined? Math::PI |
» | "constant" |
defined? ( c,d = 1,2 ) |
» | "assignment" |
defined? 42.abs |
» | "method" |
==
, ===
,
<=>
, =~
, eql?
und equal?
auch
Vergleiche (siehe Tabelle 7.1 auf Seite 81). Alle außer
<=>
sind in der Klasse Object
definiert, werden aber von davon abgeleiteten Klassen oft überschrieben,
um sie mit für die Klasse passenden Bedeutungen zu versehen. Die Klasse
Array
beispielsweise definiert ==
so um,
dass zwei Arrays gleich sind, wenn sie die gleiche Anzahl an Elementen
haben und auch die entsprechenden Elemente gleich sind.
Allgemeine Vergleichsoperatoren
|
==
als auch =~
haben negierte Formen,
!=
und !~
. Diese werden jedoch von Ruby konvertiert,
wenn Ihr Programm gelesen wird.
a!=b
ist äquivalent zu !(a==b)
und a!~b
ist
das gleiche wie !(a=~b)
.
Das bedeuted, dass Sie, wenn Sie eine Klasse schreiben, die ==
oder =~
überschreibt, gleichzeitig !=
und !~
gratis dazubekommen.
Die Kehrseite der Medaille ist, dass Sie !=
und =~
nicht unabhängig von ==
und =~
definieren können.
Sie können Bereiche als boolsche Ausdrücke verwenden. Ein Bereich wie
exp1..exp2
wird als false ausgewertet bis exp1
true
wird. Der Range wird dann als true ausgewertet, bis exp2
true
wird. Ist das geschehen wird der Bereich zurückgesetzt und ist bereit,
nochmals abgefeuert zu werden. Wir zeigen dazu einige Beispiele auf
Seite 84.
Schlussendlich können Sie einen bloßen regulären Ausdruck als boolschen
Ausdruck verwenden. Ruby expandiert ihn zu $_=~/re/
.
if
-Ausdruck in Ruby ist ziemlich gleich zu einer
``if''-Anweisung in anderen Sprachen.
if aSong.artist == "Gillespie" then handle = "Dizzy" elsif aSong.artist == "Parker" then handle = "Bird" else handle = "unknown" end |
if
-Anweisung auf mehrere Zeilen aufspalten,
können Sie das Schlüsselwort then
weglassen.
if aSong.artist == "Gillespie" handle = "Dizzy" elsif aSong.artist == "Parker" handle = "Bird" else handle = "unknown" end |
then
notwendig, um den boolschen Ausdruck von den folgenden Anweisungen zu
trennen.
if aSong.artist == "Gillespie" then handle = "Dizzy" elsif aSong.artist == "Parker" then handle = "Bird" else handle = "unknown" end |
elsif
-Bedingungen und eine optionale
else
-Bedingung verwenden.
Wie wir vorher gesagt haben, ist if
ein Ausdruck und keine
Anweisunge -- es gibt einen Wert zurück. Sie müssen den Wert eines
if
-Ausdrucks nicht verwenden, es kann aber nützlich werden.
handle = if aSong.artist == "Gillespie" then "Dizzy" elsif aSong.artist == "Parker" then "Bird" else "unknown" end |
if
:
unless aSong.duration > 180 then cost = .25 else cost = .35 end |
cost = aSong.duration > 180 ? .35 : .25 |
true
oder
false
ausgewertet wird. In diesem Fall gibt der Ausdruck,
wenn der Song länger als 3 Minuten dauert, .35 zurück. Für kürzere Songs
gibt er .25 zurück. Wie auch immer das Ergebnis ausfällt, es wird
cost
zugewiesen.
mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/ puts "a = #{a}" if fDebug print total unless total == 0 |
if
-Modifikator wird die vorangegangene Anweisung nur
ausgewertet, wenn die Bedingung wahr ist. unless
arbeitet
umgekehrt.
while gets next if /^#/ # Kommentare überspringen parseLine unless /^$/ # Leere Zeilen nicht bearbeiten end |
if
selbst ein Ausdruck ist, kann das zu äußerst obskuren
Anweisungen wie folgende führen:
if artist == "John Coltrane" artist = "'Trane" end unless nicknames == "no" |
case
-Ausdruck ist ein mächtiges Biest: ein mehrfaches
if
auf Steroiden.
case inputLine when "debug" dumpDebugInfo dumpSymbols when /p\s+(\w+)/ dumpVariable($1) when "quit", "exit" exit else print "Illegal command: #{inputLine}" end |
if
, gibt case
den Wert des zuletzt
ausgeführten Ausdrucks zurück. Weiters müssen Sie auch hier
then
setzen, wenn der Ausdruck in der selben Zeile wie die
Bedingung steht.
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 |
case
vergleich das Ziel (der Ausdruck nach dem Schlüsselwort
case
) mit jedem der Vergleichsausdrücke nach den
Schlüsselwörtern when
. Dieser Test wird mit
Vergleich ===
Ziel durchgeführt.
Solange eine Klasse eine sinnvolle Bedeutung für ===
definiert
(und das tun alle eingebauten Klassen), können Objekte dieser Klasse in
case
-Ausdrücken verwendet werden.
Reguläre Ausdrücke zum Beispiel definieren ===
als simple
Mustererkennung.
case line when /title=(.*)/ puts "Title is #$1" when /track=(.*)/ puts "Track is #$1" when /artist=(.*)/ puts "Artist is #$1" end |
Class
, die
===
als Test, ob das Argument eine Instanz der Klasse oder eine
ihrer Superklassen ist, definiert.
Somit können Sie (wenn Sie die Vorteile von Polymorphismus aufgeben und
die Götters des Neuschreibens heraufbeschwören wollen) die Klasse eines
Objekts überprüfen:
case shape when Square, Rectangle # ... when Circle # ... when Triangle # ... else # ... end |
while
-Schleife führt ihren Körper null oder mehrere Male
aus, solange ihre Bedingung wahr ist. Dieses häufige Idiom zum Beispiel
liest die Eingabe so lange, bis sie ausgeschöpft ist.
while gets # ... end |
until playList.duration > 60 playList.add(songList.pop) end |
if
und unless
, können beide Schleifen als
Anweisungsmodifikatoren benutzt werden.
a *= 2 while a < 100 a -= 10 until a < 100 |
true
wird, wenn ein Ereignis eintritt und
solange true
bleibt, bis ein zweites Ereignis eintritt.
Diese Möglichkeit wird normalerweise innerhalb von Schleifen benutzt.
Im folgenden Beispiel lesen wir eine Textdatei ein, die die ersten zehn
Ordinalzahlen (``erstes'', ``zweites'', etc.) enthält, geben die Zeilen
aber erst beginnend mit der einen, die ``drittes'' enthält aus und
enden wieder mit der Zeile, die ``fünftes'' enthält.
file = File.open("ordinal") while file.gets print if /drittes/ .. /fuenftes/ end |
drittes viertes fuenftes |
$.
die Zeilennummer der aktuellen Eingabezeile enthält, um
die Zeilennummern eins bis drei und die zwischen einem Match von
/ach/
und /neu/
auszugeben.
file = File.open("ordinal") while file.gets print if ($. == 1) || /ach/ .. ($. == 3) || /neu/ end |
erstes zweites drittes achtes neuntes |
while
- und if
-Modifikatoren gibt es einen Kniff:
Wenn der Ausdruck, den sie modifizieren ein
begin
/end
Block ist, wird der Code im Block immer
mindestens ein Mal ausgeführt, unabhängig vom Wert des boolschen
Ausdrucks.
print "Hello\n" while false begin print "Goodbye\n" end while false |
Goodbye |
3.times do print "Ho! " end |
Ho! Ho! Ho! |
times
können Sie mit Integers mittels downto
,
upto
und step
Schleifen über Bereiche anlegen.
Eine traditionelle ``for''-Schleife, die von 0 bis 9 läuft (sowas wie
i=0; i < 10; i++
), wird wie folgt geschrieben.
0.upto(9) do |x| print x, " " end |
0 1 2 3 4 5 6 7 8 9 |
0.step(12, 3) {|x| print x, " " } |
0 3 6 9 12 |
each
leicht gemacht.
[ 1, 1, 2, 3, 5 ].each {|val| print val, " " } |
1 1 2 3 5 |
each
, werden die
zusätzlichen Methoden im Modul Enumerable
(ab
Seite 407 dokumentiert und auf den Seiten 104--105 zusammengefasst)
verfügbar. Die Klasse File
zum Beispiel stellt
eine each
-Methode bereit, die jede Zeile einer Datei
retourniert. Wenn wir die Methode grep
aus
Enumerable
benutzen, können wir nur über die
Zeilen, die eine bestimmte Bedingung erfüllen, iterieren.
File.open("ordinal").grep /d$/ do |line| print line end |
second third |
loop
.
loop { # block ... } |
loop
-Iterator ruft den assoziierten Block unendlich Mal
auf (oder zumindest, bis Sie die Schleife verlassen, aber Sie müssen
weiterlesen, um herauszufinden, wie man das machen kann).
while
und until
wären. Was ist dann
dieses ``for
''-Ding? Nun, for
ist ein Klumpen
syntaktischer Zucker. Wenn Sie
for aSong in songList aSong.play end |
songList.each do |aSong| aSong.play end |
for
-Schleife und der
each
-Form ist der Scope von lokalen Variablen, die
im Körper der Schleife definiert werden. Das wird auf Seite 89
besprochen.
Sie können for
benutzen, um über jedes Objekt zu iterieren, das
der Methode each
antwortet, wie zum Beispiel ein
Array
oder ein Range
.
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 |
fee fi fo fum 1 2 3 second third |
each
-Methode definiert,
können Sie eine for
-Schleife benutzen, um sie zu durchlaufen.
class Periods def each yield "Classical" yield "Jazz" yield "Rock" end end periods = Periods.new for genre in periods print genre, " " end |
Classical Jazz Rock |
break
, redo
und
next
erlauben es Ihnen, den normalen Fluss durch eine Schleife
oder einen Iterator zu veränden.
break
beendet die aktuelle Schleife; die Kontrolle wird bei der
Anweisung, die dem Block folgt, wieder aufgenommen. redo
wiederholt den aktuellen Schleifendurchlauf vom Beginn an, aber ohne die
Bedingung neu auszuwerten oder das nächste Element zu holen (in einem
Iterator). next
springt zum Ende der Schleife und startet den
nächsten Schleifendurchlauf.
while gets next if /^\s*#/ # skip comments break if /^END/ # stop at end # substitute stuff in backticks and try again redo if gsub!(/`(.*?)`/) { eval($1) } # process line ... end |
i=0 loop do i += 1 next if i < 3 print i break if i > 4 end |
345 |
redo
veranlasst eine Schleife, die aktuelle
Iteration zu wiederholen. Manchmal aber müssen Sie die Schleife ganz an
ihrem Anfang beginnen lassen. Die Anweisung retry
ist ihre
Eintrittskarte dafür. retry
startet jede Art von Schleife neu.
for i in 1..100 print "Now at #{i}. Restart? " retry if gets =~ /^y/i end |
Now at 1. Restart? n Now at 2. Restart? y Now at 1. Restart? n . . . |
retry
liest jedes Argument des Iterators neu aus, bevor sie ihn
neu startet. Die Onlinedokumentation zu Ruby zeigt folgendes Beispiel
einer selbstgebastelten until-Schleife.
def doUntil(cond) yield retry unless cond end i = 0 doUntil(i > 3) { print i, " " i += 1 } |
0 1 2 3 4 |
while
, until
und for
sind in
die Sprache eingebaut und führen keinen neuen Scope ein; bereits
existente Variablen können in der Schleife benutzt werden und jede neue
lokale Variable ist nachher verfügbar.
Die Blöcke die von Iteratoren (wie loop
oder each
)
benutzt werden, sind etwas anders. Normalerweise kann auf die in diesen
Blöcken erzeugen lokalen Variablen von außerhalb des Blocks nicht
zugegriffen werden.
[ 1, 2, 3 ].each do |x| y = x + 1 end [ x, y ] |
prog.rb:4: undefined local variable or method `x' |
x = nil |
||
y = nil |
||
[ 1, 2, 3 ].each do |x| |
||
y = x + 1 |
||
end |
||
[ x, y ] |
» | [3, 4] |