Eigene Methoden schreiben
Wir haben gesehen, dass Schleifen und Iteratoren uns die Möglichkeit bieten, dasselbe immer und immer wieder zu tun. Manchmal will zwar man dasselbe öfter machen, aber an unterschiedlichen Stellen im Programm. Stellen wir uns vor, wir würden einen Fragebogen für einen Psychologie-Studenten schreiben. Bei den Studenten, die ich kannte, und den Fragebögen, die sie mir gegeben haben, würde dies etwa so aussehen:
puts 'Hallo und danke, dass du dir die Zeit nimmst'
puts 'und mich bei diesem Experiment unterstützt.'
puts 'Diese Umfrage geht um deine Meinung über '
puts 'italienisches Essen. Denke einfach nur an '
puts 'italienisches Essen und versuche, ganz ehrlich'
puts 'zu antworten, entweder mit "ja" oder "nein".'
puts 'Meine Umfrage hat nicht mit Bettnässen zu tun.'
puts
# Wir stellen ein paar Fragen, ignorieren aber die Antworten.
richtig = false
while (not richtig)
puts 'Magst du Spaghetti Bolognese?'
antwort = gets.chomp.downcase
if (antwort=='ja' or antwort=='nein')
richtig=true
else
puts 'Bitte antworte mit "ja" oder "nein"'
end
end
richtig = false
while (not richtig)
puts 'Magst du Maccaroni mit Käse?'
antwort = gets.chomp.downcase
if (antwort=='ja' or antwort=='nein')
richtig=true
else
puts 'Bitte antworte mit "ja" oder "nein"'
end
end
# Diesmal merken wir uns die Antwort!
richtig = false
while (not richtig)
puts 'Nässt du dein Bett?'
antwort = gets.chomp.downcase
if (antwort=='ja' or antwort=='nein')
richtig=true
if (antwort=='ja')
bettnaesser=true
else
bettnaesser=false
end
else
puts 'Bitte antworte mit "ja" oder "nein"'
end
end
richtig = false
while (not richtig)
puts 'Magst du Penne arrabiata?'
antwort = gets.chomp.downcase
if (antwort=='ja' or antwort=='nein')
richtig=true
else
puts 'Bitte antworte mit "ja" oder "nein"'
end
end
richtig = false
while (not richtig)
puts 'Magst du Calzone?'
antwort = gets.chomp.downcase
if (antwort=='ja' or antwort=='nein')
richtig=true
else
puts 'Bitte antworte mit "ja" oder "nein"'
end
end
# und hier kommen noch weitere Fragen...
puts
puts 'Auswertung'
puts 'Danke, dass du dir Zeit für die Umfage genommen hast.'
puts 'Tatsächlich hat die Untersuchung aber nichts mit '
puts 'italienischem Essen zu tun. Es ging ums Bettnässen.'
puts 'Alle anderen Fragen sollten nur die Aufmerksamkeit '
puts 'ablenken, um ehrliche Antworten zu erhalten.'
puts 'Vielen Dank nochmals.'
puts
puts bettnaesser
Hallo und danke, dass du dir die Zeit nimmst und mich bei diesem Experiment unterstützt. Diese Umfrage geht um deine Meinung über italienisches Essen. Denke einfach nur an italienisches Essen und versuche, ganz ehrlich zu antworten, entweder mit "ja" oder "nein". Meine Umfrage hat nicht mit Bettnässen zu tun. Magst du Spaghetti Bolognese? ja Magst du Maccaroni mit Käse? nein Nässt du dein Bett? nein Magst du Penne arrabiata? ja Magst du Calzone? nein Auflösung Danke, dass du dir Zeit für die Umfage genommen hast. Tatsächlich hat die Untersuchung aber nichts mit italienischem Essen zu tun. Es ging ums Bettnässen. Alle anderen Fragen sollten nur die Aufmerksamkeit ablenken, um ehrliche Antworten zu erhalten. Vielen Dank nochmals. false
Dies war ein ziemlich langes Program, mit vielen Wiederholungen. (Alle diese Teile Code über das italienische Essen waren identisch und die Frage übers Bettnässen war nur ein bisschen anders.) Wiederholung ist eine schlechte Angewohnheit. Andererseits können wir dies nicht in eine große Schleife oder Iterator packen, da wir ja manchmal etwas zwischen den Fragen auch machen wollen. In solchen Situationen braucht man Methoden. Und die schreibt man so:
def bellen puts 'wau ' end
Wie?... unser Programm hat gar nicht gebellt. Und warum nicht? Weil wir ihm nicht gesagt haben, dass es bellen soll. Wir haben ihm gesagt, wie man bellt, jedoch nie, dass es bellen soll. Versuchen wir es noch einmal:
def bellen puts 'wau ' end
bellen bellen puts 'wuff wuff wuff' bellen bellen wau wau wuff wuff wuff wau wau
Ah, viel besser. (Info am Rande: Wir haben es in der Mitte mit einem englischen Hund zu tun, der sagt nämlich "wuff".)
Wir haben also eine Methode bellen definiert. (Methodennamen beginnen mit einem kleinbuchstaben, genau wie Variablennamen. Es gibt ein paar Ausnahmen wie + und ==.) Aber müssen Methoden nicht immer zu Objekten gehören? Ja, ganeu, das stimmt. Und in diesem fall hier, wie auch bei puts und gets gehört die Methode zu dem Objekt,d as das ganze Programm darstellt. Im nächsten Kapitel werden wir kennenlernen, wie man Methoden mit anderen Objekten assoziiert.
Aber erst kümmern wir uns um ...
Methoden-Parameter
Sicher hast du schon gemerkt,
dass manche Methoden (wie gets, to_s, reverse...)
auf Objekten einfach aufgerufen werden. A
ndere Methoden (wie +, -, puts...) benötigen Parameter,
damit das Objekt weiß, was es genau tun soll.
Du würdest je auch nicht wissen, was zu tun ist, wenn du nur 5 + bekommst, oder?
Damit wird der 5 gesagt, dass addiert werden soll, doch es wird nicht gesagt, was addiert wird.
Um unserer bellen-Methode einen Parameter zu übergeben
(der angibt, wie oft gebellt werden soll), schreiben wir dies:
def bellen num
puts 'wau '*num
end
bellen 5
bellen 3
puts 'wuff wuff wuff'
bellen 6
bellen 4
bellen # hier sollte ein Fehler gemeldet werden,
# da der Parameter fehlt
wau wau wau wau wau wau wau wau wuff wuff wuff wau wau wau wau wau wau wau wau wau wau bellen2.rb:10:in `bellen': wrong number of arguments (0 for 1) (ArgumentError) from bellen2.rb:10
num ist in der Methode bellen eine Variable, die auf den Wert zeigt, der als Parameter übergeben wird. Ich sag's noch einmal, weil es recht verwirrend ist: num ist in der Methode bellen eine Variable, die auf den Wert zeigt, der als Parameter übergeben wird. Wenn ich also bellen 3 aufrufe, dann ist der Parameter 3 und die Variable num zeigt drauf.
Wie du siehst, ist der Parameter jetzt erforderlich. Womit soll schließlich 'wau' multipliziert werden, wenn du ihm diese Information nicht sagst? deine armer Computer weiß es einfach nicht.
Wenn man Objekte in Ruby vergleicht mit Nomen im Deutschen, so sind die Methoden wie die Verben und man kann die Parameter als Adverben ansehen (z.B. hat uns in bellen der Parameter ja auch gesagt wie es zu tun sei) oder manchmal als direkte Objekte im Satz (beispielsweise in puts, wo der Parameter angibt, was ge-puts-t werden soll.
Lokale Variablen
Im folgenden Programm sind zwei Variablen:
def verdoppeln zahl ergebnis=zahl*2 puts zahl.to_s+' verdoppeln ergibt '+ergebnis.to_s end verdoppeln 44
44 verdoppeln ergibt 88
Die Variablen heißen zahl und ergebnis. Beide leben innerhalb der Methode verdoppeln. Diese Variablen (und alle anderen, die du bisher gesehen hast) heißen lokale Variablen. Dies bedeutet, dass sie innerhalb der Methode leben und diese nicht verlassen können. Wenn du das versuchst, wirst du einen Fehler erhalten:
def verdoppeln zahl ergebnis=zahl*2 puts zahl.to_s+' verdoppeln ergibt '+ergebnis.to_s end verdoppeln 44 puts ergebnis.to_s
44 verdoppeln ergibt 88 doubleThis2.rb:7: undefined local variable or method `ergebnis' for main:Object (NameError)
Undefininierte lokale Variable... in der Tat haben wir zwar die lokale Variable ergebnis definiert, aber nicht lokal dort, wo wir versuchen, sie zu verwenden; sie ist lokal in der Methode.
Das mag unpraktisch erscheinen, ist aber der Erfahrung nach sehr praktisch! Einserseits bedeutet es, dass man außerhalb einer Methode keinen Zugriff auf die lokalen Variablen der Methode hat, heißt es auch, dass die Methoden keinen Zugriff auf deine lokalen Variablen haben, und sie somit nicht zerstören können:
def kaputt var var=nil puts 'haha, ich habe deine Variable kaputt gemacht' end var='meine Variable ist vor dir sicher!!!' kaputt var puts var
haha, ich habe deine Variable kaputt gemacht meine Variable ist vor dir sicher!!!
In diesem Programm gibt es nun zwei Variablen, die var heißen: eine innerhalb der Methode kaputt und eine außerhalb. Beim Aufruf kaputt var haben wir den String von einer Variable in die andere übergeben, dass sie beide auf denselben Inhalt zeigten. Danach hat die Methode kaputt seine lokale Variable var auf nil gesetzt, was aber nichts zu tun hatte mit unserer Variablen var außerhalb der Methode.
Rückgabewerte
Du hast sicherlich schon festgestellt, dass manche Methode etwas zurückgeben, wenn man sie aufruft. Zum Beispiel gibt gets einen String zurück (nämlich den, den du eingegeben hast) und die +-Methode in 5+3 (was ja eigentlich 5.+(3) ist) gibt 8 zurück. Arithmetische Methoden für Zahlen geben Zahlen, arithmetische Methoden für Strings geben Strings zurück.
Es ist wichtig, sich den Unterschied zu verdeutlichen zwischen einer Methode, die einen Wert zurückgibt, wenn man sie aufruft und einer Methode, die Information auf dem Bildschirm ausgibt. Halt dir vor Augen, dass 5+3 zwar 8 zurückgibt, aber nicht am Bildschirm ausgibt!
Was gibt also puts zurück? Das hat uns bisher nicht interessiert, aber schauen wir das mal genauer an:
wert = puts 'Rückgabe von puts:' puts wert
Rückgabe von puts: nil
Also, erstens einmal gibt puts nil zurück. Obwohl wir es nicht getestet haben, gibt auch das zweite puts nil zurück; puts gibt immer nil zurück. Jede Methode gibt etwas zurück, auch wenn es nur nil ist.
Mach hier mal eine kleine Pause, in der du rausfindest, was die Methode bellen zurück gibt.
Überrascht? So funktioniert es: zurückgegeben wird der Wert der letzten Zeile der Methode. Bei der Methode bellen gibt es also den Wert von puts 'wau ' zurück, was nil ist, weil puts immer nil liefert. Wenn wir wollen, dass diese Methode den String 'Gelbes U-Boot' zurückgibt, so müssen wir dies als letzte Zeile schreiben.
def bellen num puts 'wau '*num 'gelbes U-Boot' end x = bellen 5 puts x
wau wau wau wau wau gelbes U-Boot
Versuchen wir das alles man mit unserer psycholigischen Umfrage, aber diesmal mit einer Methode, die die Fragerei für uns übernimmt. Die Methode braucht also die Fragestellung als Parameter und soll true zurückgeben, wenn mit 'ja' geantwortet wurde, und false, wenn mit 'nein geantwortet wurde. (Auch wenn wir diese Antworten meist ignorieren werden ist es kein schlechter Ansatz, wenn die Methode die Antwort mal zurückgibt. Außerdem können wir die Methode auf diese Weise auch für die Bettnässer-Frage verwenden.) Ich erlaube mir ein paar Abkürzungen bei der Begrüßung und der Auswertung, aber du weißt ja schon, worum es geht, und so ist es leichter zu lesen:
def fragen fragestellung
richtig = false
while (not richtig)
puts fragestellung
antwort = gets.chomp.downcase
if (antwort=='ja' or antwort=='nein')
richtig=true
if (antwort=='ja')
antwort=true
else
antwort=false
end
else
puts 'Bitte antworte mit "ja" oder "nein"'
end
end
antwort # der Rückgabewert!!!
end
puts 'Hallo und danke, dass du ...'
puts
fragen 'Magst du Spaghetti Bolognese?' # ignorieren!
fragen 'Magst du Maccaroni mit Käse?'
bettnaesser = fragen 'Nässt du dein Bett?' # Antwort merken!
fragen 'Magst du Penne arrabiata?'
fragen 'Magst du Calzone?'
fragen 'Magst du Pizza Hawaii?'
fragen 'Magst du Vitello Tonnato?'
fragen 'Magst du Tira-mi-su?'
puts
puts 'Auswertung'
puts 'Danke, ...'
puts
puts bettnaesser
Hallo und danke, dass du ... Magst du Spaghetti Bolognese? ja Magst du Maccaroni mit Käse? nein Nässt du dein Bett? nein Magst du Penne arrabiata? ja Magst du Calzone? nein Magst du Pizza Hawaii? ja Magst du Vitello Tonnato? ja Magst du Tira-mi-su? ja Auswertung Danke, ... false
Gar nicht schlecht, hm? Wir haben ein zusätzliche Fragen hinzugefügt (und das ist jetzt wirklich einfach) und doch ist unser Programm kürzer! Das ist ein großer fortschritt - und der Traum jedes faulen Programmierers.
Noch ein großes Beispiel
Ich denke mal, ein weiteres Beispiel für Methoden ist an dieser Stelle hilfreich. Wir nennen die Methode deutscheZahl. Sie nimmt eine Zahl (z.B. 22) und gibt das deutsche Wort dafür zurück (in diesem Fall zweiundzwanzig. Erstmal wollen wir nur Zahlen zwischen 0 und 100 betrachten.
(Hinweis: hier werden zwei neue Tricks verwendet:
mit dem Schlüsselwort return kann man eine Methode frühzeitig verlassen
und elsif bietet eine weitere Möglichkeit der Verzweigung.
Ich denke, im Kontext wird die Bedeutung klar.)
def deutscheZahl zahl
# Nur Zahlen 0-100.
if zahl < 0
return 'Bitte gibt eine Zahl 0 oder größer ein.'
end
if zahl > 100
return 'Bitte gibt eine Zahl 100 oder kleiner ein.'
end
zahlString = '' # das wird unser Ergebnis werden
# "links" ist der Anteil von 'zahl' den wir noch verarbeiten müssen
# "schreiben" ist der Anteil, den wir jetzt schon rausschreiben können
links = zahl
schreiben = links/100 # Wie viele Hunderter müssen noch geschrieben werden
links = links - schreiben*100 # Diese Hunderter abziehen.
if schreiben > 0
return 'einhundert'
end
schreiben = links%10 # wie viele Einer müssen wir noch schreiben?
if links/10 %10 !=1 # Sonderfall: Teenager!!!
links -=schreiben # diese Einer abziehen!
if schreiben > 0
if schreiben == 1
if links>0
zahlString = zahlString + 'ein'
else
zahlString = zahlString + 'eins'
end
elsif schreiben == 2
zahlString = zahlString + 'zwei'
elsif schreiben == 3
zahlString = zahlString + 'drei'
elsif schreiben == 4
zahlString = zahlString + 'vier'
elsif schreiben == 5
zahlString = zahlString + 'fünf'
elsif schreiben == 6
zahlString = zahlString + 'sechs'
elsif schreiben == 7
zahlString = zahlString + 'sieben'
elsif schreiben == 8
zahlString = zahlString + 'acht'
elsif schreiben == 9
zahlString = zahlString + 'neun'
end
if links>0
zahlString +='und';
end
end
end
schreiben = links/10 # wie viele Zehner müssen geschrieben werden?
links = links - schreiben*10 # Diese Zehner abziehen.
if schreiben > 0
if schreiben == 1 # Uh-oh...
# Sonderfall für die 'Teenager' 11-19
if links == 0
zahlString = zahlString + 'zehn'
elsif links == 1
zahlString = zahlString + 'elf'
elsif links == 2
zahlString = zahlString + 'zwölf'
elsif links == 3
zahlString = zahlString + 'dreizehn'
elsif links == 4
zahlString = zahlString + 'vierzehn'
elsif links == 5
zahlString = zahlString + 'fünfzehn'
elsif links == 6
zahlString = zahlString + 'sechzehn'
elsif links == 7
zahlString = zahlString + 'siebzehn'
elsif links == 8
zahlString = zahlString + 'achtzehn'
elsif links == 9
zahlString = zahlString + 'neunzehn'
end
# In diesem Fall haben wir auch schon die Einer beachtet
# und wir müssen nichts mehr schreiben
links = 0
elsif schreiben == 2
zahlString = zahlString + 'zwanzig'
elsif schreiben == 3
zahlString = zahlString + 'dreißig'
elsif schreiben == 4
zahlString = zahlString + 'vierzig'
elsif schreiben == 5
zahlString = zahlString + 'fünfzig'
elsif schreiben == 6
zahlString = zahlString + 'sechzig'
elsif schreiben == 7
zahlString = zahlString + 'siebzig'
elsif schreiben == 8
zahlString = zahlString + 'achzig'
elsif schreiben == 9
zahlString = zahlString + 'neunzig'
end
end
if zahlString == ''
# zahlString kann nur leer sein,
# wenn der Parameter zahl gleich 0 ist.
return 'null'
end
# Nun haben wir den zahlString erstellt
# und müssen ihn nur noch zurück geben.
zahlString
end
puts deutscheZahl( 0)
puts deutscheZahl( 1)
puts deutscheZahl( 9)
puts deutscheZahl( 10)
puts deutscheZahl( 11)
puts deutscheZahl( 17)
puts deutscheZahl( 32)
puts deutscheZahl( 51)
puts deutscheZahl( 88)
puts deutscheZahl( 99)
puts deutscheZahl(100)
puts deutscheZahl(555)
null eins neun zehn elf siebzehn zweiunddreißig einundfünfzig achtundachzig neunundneunzig einhundert Bitte gibt eine Zahl 100 oder kleiner ein.
Da gibt's natürlich ein paar Stellen, die ich in diesem Programm nicht mag.
Erst einmal gibt's zu viel Wiederholung.
Außerdem werden Zahlen größer als Hundert nicht behandelt.
Und drittens, gibt es zu viele Spezialfälle, zu viele returns.
Wir verwenden mal ein paar Arrays und räumen etwas auf:
def deutscheZahl zahl
# negative Zahlen: 'minus' und die positive Zahl
if zahl < 0
return 'minus '+ (deutscheZahl (-1)*zahl)
# Dies nennt man "Rekursion".
# Die Methode ruft sich selber mit anderen Parametern wieder auf.
# So kann man verschiedene Fälle eleganter von einander trennen.
end
if zahl == 0
return 'null'
end
if zahl == 1
return 'eins'
end
zahlString = '' # das wird unser Ergebnis werden
# merken wir uns die deutschen Worte in Arrays,
# dann brauchen wir keine lange Fallunterscheidung
einer = ['ein', 'zwei', 'drei', 'vier', 'fünf',
'sechs', 'sieben', 'acht', 'neun']
zehner = ['zehn', 'zwanzig', 'dreißig', 'vierzig', 'fünfzig',
'sechzig', 'siebzig', 'achzig', 'neunzig']
# besonderie Namen zwischen 11 und 19
teenager = ['elf', 'zwölf', 'dreizehn', 'vierzehn', 'fünfzehn',
'sechzehn', 'siebzehn', 'achtzehn', 'neunzehn']
# angenommen die Zahl ist 1000 oder kleiner...
if zahl <1000
h = zahl/100 #hunderter
z = zahl/10%10 #zehner
e = zahl%10 #einer
if h>0
zahlString = zahlString + einer[h-1]+'hundert'
end
if z==1 && e>0 #teenager
zahlString = zahlString + teenager[e-1]
else
if e>0
zahlString = zahlString + einer[e-1]
if z>1
zahlString = zahlString +'und'
end
end
if z>0
zahlString = zahlString +zehner[z-1]
end
end
return zahlString
end
# nun haben wir nur Zahlen, die größer gleich 1000 sind...
# wir betrachten immer Blöcke von drei Ziffern (also eine Zahl kleiner 1000)
# und fügen ihnen eine Stellen-Wertigkeit an
wert = ['', 'tausend', 'millionen', 'milliarden', 'billionen'] # das muss reichen.
w=0
while zahl>0
z = zahl%1000
if z>0
zahlString = (deutscheZahl z) + wert[w] + zahlString
# wieder Rekursion!
end
w = w+1
zahl = zahl/1000
end
return zahlString
end
#Ausprobieren:
puts deutscheZahl(-17)
puts deutscheZahl( 51)
puts deutscheZahl(34555)
puts deutscheZahl(3455500000)
puts deutscheZahl(-439587000555)
puts deutscheZahl(-10000000034)
minus siebzehn einundfünfzig vierunddreißigtausendfünfhundertfünfundfünfzig dreimilliardenvierhundertfünfundfünfzigmillionenfünfhunderttausend minus vierhundertneununddreißigmilliardenfünfhundertsiebenundachzigmillionenfünfhundertfünfundfünfzig minus zehnmilliardenvierunddreißig
Ahhh... sehr viel schöner. Das Programm ist deutlich kompakter, weshalb ich so viele Kommentare eingebaut habe. Es funktioniert auch für größere und negative Zahlen... wenn es auch Schönheitsfehler gibt: ein paar Leerzeichen oder Bindestriche würden die Lesbarkeit erhöhen und probier doch mal eine Million aus! Aber das kannst du ja jetzt selber machen...
Selber ausprobieren
- Erweitere die Methode
deutscheZahlso, dass sie auch Tausender verarbeiten kann und z.B. 'eintausend' zurückgibt, statt 'zehnhundert'. - Nun sollst du die Methode
deutscheZahlweiter verfeinern, dass sie auch Millionen, Milliarden und Billionen zurückgeben kann.
Herzlichen Glückwunsch! Jetzt bist du ein echter Programmierer! Du hast alles gelernt, was du brauchst, um große Programme von Anfang an zu schreiben. Wenn du selber Ideen hast für Programme, die du gerne schreiben würdest, solltest du jetzt damit anfangen!
Es ist natürlich ein langsamer Prozess, wenn man alles von Anfang an neu schreibt. Und warum sollte man Code schreiben, den andere schon geschrieben haben? Willst du, dass dein Programm E-Mails versenden kann? Möchtest du Dateien auf deinem Computer speichern und laden? Wie sieht es aus mit Webseiten für ein Tutorial, in dem der Code automatisch getestet wird? ;) Ruby bietet viele verschiedene Arten von Objekten, die uns unterstützen, bessere Anwendungen schneller zu schreiben. Schauen wir uns diese Klassen an...