![]() |
![]() |
In den ersten Kapiteln haben wir die grundlegenden Befehle PRINT, LET und GOTO kennengelernt. Prinzipiell können wir mit diesen Befehlen fast alles programmieren. Viele Dinge, die wir in den folgenden Kapiteln programmiert haben, wären aber allein mit diesen Befehlen aber sehr unkomfortabel, sehr, sehr unkomfortabel sogar, praktisch eigentlich doch unmöglich. Andere Dinge, wie die Sonderzeichen oder Input mit Prompt sind die Sahne auf dem Kaffee, aber im Notfall auch entbehrlich.
In diesem Kapitel kommen wir zu einem unscheinbar anmutenden Befehl, den wir bisher vermutlich noch gar nicht vermisst haben. Er heisst GOSUB. Aber er gehört keineswegs zur notfalls entbehrlichen Sahne, sondern eher zu den Dingen, ohne die die meisten Prorgrammieraufgaben nur noch theoretisch, nicht mehr jedoch praktisch lösbar wären.
GOSUB funktioniert zunächst wie GOTO: GOSUB 200 springt in Zeile 200. Aber es ist etwas komfortabler als GOTO. Es merkt sich nämlich die Absprungstelle. Es gibt nun einen zweiten Befehl namens RETURN. Wenn das Programm an ein RETURN kommt, dann springt es wieder zurück zu dieser Absprungstelle. Ein Beispiel:
10 PRINT "Hier ist Zeile 10" 20 GOSUB 100 30 PRINT "Hier ist Zeile 30" 40 END 100 PRINT "Hier ist Zeile 100" 110 RETURN
Das Programm startet bei Zeile 10, springt in Zeile 20 nach Zeile 100, in Zeile 110 dahin zurück, von wo es gekommen war, nämlich nach Zeile 20, führt dann Zeile 30 aus und endet in Zeile 40.
Diese Programm hat eine erkennbare Struktur: Es besteht aus einem Hauptprogramm in den Zeilen 10 bis 40 und einem Unterprogramm in den Zeilen 100 und 110. Mit GOSUB kann man Unterprogramme bauen.
Aber wozu braucht man Unterprogramme? Zum Erledigen von Unteraufgaben. Teilaufgaben. Der Witz besteht darin, dass man diese Teilaufgaben von jeder Stelle des Hauptprogramms aus erledigen lassen kann. Schauen wir uns wieder ein Beispiel an.
Wir begegneten im String-Kapitel dem Wunsch, einen String rechtsbündig ausgeben zu können. Dies ist so eine Teilaufgabe oder Unteraufgabe. Sie muss oft und an vielen Stellen eines entsprechenden Buchhaltungs- oder Tabellierungsprogramms ausgeführt werden. Deshalb nennt man solche Unterprogramme auch oft Routinen. Schauen wir zunächst, wie wir diese Rechtsbündig-Routine programmieren und dann, wie wir sie einsetzen können.
Nochmals zur Erklärung, was "rechtsbündig" eigentlich bedeutet: Man möchte eine Reihe von Zahlen untereinander so ausgeben, dass ihre Enden übereinander stehen.
123 45 6ist eine rechtsbündige Ausgabe.
123 45 6
ist eine linksbündige Ausgabe. Für Zahlen sind natürlich meist nur rechtsbündige Ausgaben praktisch, da hier die zusammengehörigen Stellen untereinander stehen. Wenn wir aber mit dem PRINT-Befehl einfach untereinander Zahlen ausgeben, erscheinen sie linksbündig. Da müssen wir uns etwas einfallen lassen.
Man kann gedanklich die Aufgaben auf vielerlei Weise lösen.
Realisieren lässt sich auf dem C16 zunächst nur die dritte Variante, weil die ersten beiden verlangen, dass wir einzelne Zeichen im String verändern, was nicht so einfach geht. Das hatten wir schon beim Mastermind-Spiel.
10 LET S=LEN(A$) 20 LET IMAX=W-S //Stelle fest, wieviel Zeichen noch fehlen 30 LET I=0 //Zeile 30 bis 80: Baue den Verlängerungsstring B$ mit W-S Zeichen. 40 LET B$="" 50 IF IMAX<=0 THEN GOTO 100 //Wenn keine Zeichen fehlen, dann überspringe den nächsten Teil 60 LET B$=B$+"_" 70 LET I=I+1 80 IF I<IMAX THEN GOTO 60 100 LET A$=B$+A$
Die Texte ab // sind Kommentare, die nicht abgetippt werden dürfen.
So, das war der harte Teil. Wenn Sie nicht jedes Detail verstanden haben: Macht überhaupt nichts. Der Witz an Routinen ist ja, dass man sie nicht verstehen muss, um sie benutzen zu können.
Bis jetzt ist das äusserlich ja ein ganz normales kleines Programm, ohne Ein- und Ausgabe. Wie wird es zu einem Unterprogramm? Ganz einfach: Man fügt am Ende noch ein RETURN ein:
... 110 RETURN
Aber nun ist das ja auch doof: Wir starten ja direkt ins Unterprogramm, ohne A$ und W besetzt zu haben!
Auch kein Problem. Wir können ja eine Zeile 5 mit einem GOTO-Befehl einfügen, der uns ins Hauptprogramm befördert. Es steht nirgendwo geschrieben, dass Hauptprogramme immer am Anfang stehen müssen. Das Hauptprogramm selbst können wir ja z.B. ab Zeile 1000 laufen lassen. Dann gibt es vorher sogar noch Platz für ein paar weitere Unterprogramme.
Im Hauptprogramm selbst setzen wir W, lassen den Benutzer einen String eingeben, machen ihn rechtsbündig, geben ihn aus und lassen den Benutzer den nächsten String eingeben.
5 GOTO 1000 10 LET S=LEN(A$) 20 LET IMAX=W-S //Stelle fest, wieviel Zeichen noch fehlen 30 LET I=0 //Zeile 30 bis 80: Baue den Verlängerungsstring B$ mit W-S Zeichen. 40 LET B$="" 50 IF IMAX<=0 THEN GOTO 100 //Wenn keine Zeichen fehlen, dann überspringe den nächsten Teil 60 LET B$=B$+"_" 70 LET I=I+1 80 IF I<IMAX THEN GOTO 60 100 LET A$=B$+A$ 110 RETURN 1000 LET W=20 1010 SCNCLR 1020 INPUT "GEBEN SIE EINEN STRING EIN (Q=Quit)";A$ 1030 GOSUB 10 1040 PRINT A$ 1050 IF A$<>"Q" THEN GOTO 1020
So schaut das Programm gut aus, oder nicht? Wenn wir es ausprobieren, funktioniert unser Rechtsbündig-Unterprogramm auch ganz hervorragend. Aber wir kommen aus dem Programm nicht raus! Die Eingabe von "Q" bewirkt nichts, nicht das kleinste Zipfelchen. Woran liegt das?
Hier begegnen wir einem Grundproblem der Programmierung schlechthin: Diesselbe Variable wird in verschiedenen Programmteilen überschrieben. In unserem Fall ist es A$. A$ wird sozusagen zweimal verwendet. Einmal in unserer String-Eingabe und -Verarbeitung und einmal am Ende zur Kontrolle, ob das Programm verlassen werden soll. Auf den ersten Blick passiert zwischen Teile 1020 und Zeile 1050 nicht viel mit A$. Aber dieser Blick ist nun, da wir Unterprogramme kennen, extrem trügerisch! Und ob etwas mit A$ passiert...
Wie können wir so etwas verhindern? Die gute Nachricht: Wir werden im zweiten Teil eine Programmiertechnik kennenlernen, mit deren Hilfe so etwas gar nicht mehr auftreten kann. Die schlechte Nachricht: Bis dahin gibt es keine gute Lösung des Problems - nur unsere Disziplin im Benennen von Variablen kann das Schlimmste verhindern. Nennen wir z.B. in diesem Fall die Eingabevariable HA$, dann steht das "H" für "Hauptprogramm". Unser Programm sähe dann so aus:
5 GOTO 1000 10 LET S=LEN(A$) 20 LET IMAX=W-S //Stelle fest, wieviel Zeichen noch fehlen 30 LET I=0 //Zeile 30 bis 80: Baue den Verlängerungsstring B$ mit W-S Zeichen. 40 LET B$="" 50 IF IMAX<=0 THEN GOTO 100 //Wenn keine Zeichen fehlen, dann überspringe den nächsten Teil 60 LET B$=B$+"_" 70 LET I=I+1 80 IF I<IMAX THEN GOTO 60 100 LET A$=B$+A$ 110 RETURN 1000 LET W=20 1010 SCNCLR 1020 INPUT "GEBEN SIE EINEN STRING EIN (Q=Quit)";HA$ 1030 LET A$=HA$ 1040 GOSUB 10 1050 PRINT A$ 1060 IF HA$<>"Q" THEN GOTO 1020
Sie können das Programm ganz leicht erweitern, indem Sie die Eingabe am unteren Bildschirmende vornehmen lassen und die Tabelle mittels CHAR ,X,Y,A$-Befehl in der oberen Bildschirmhälfte aufbauen. Und meinetwegen auch gleich noch Summe und Mittelwert ausgeben. Dann haben wir schon ein kleines Statistik-Programm beieinander!
Es gibt einen Befehl, hätten wir ihn am Anfang erklärt, er wäre uns total widersinnig erschienen: REM. REM bewirkt, dass das, was dahinter folgt, einfach nur abgespeichert wird, aber ansonsten nichts bewirkt. Wirklich gar nichts. Kein Rechnen, keine Bildschirmausgabe, kein Speichern, nichts. Warum das? Nun, wenn wir ein Programm mit vielen Zeilen schreiben und die Programme komplexer werden, dann können wir sie nicht mehr rein aus den Programmzeilen heraus verstehen. Wir müssen wenigstens den einzelnen Programmteilen Namen geben können, besonders den Routinen. So dass wir als Programmierer sehen: "Ah ja! Zeile 10 bis 110 ist die Rechtsbündig-Routine"! Und das geht mit einem Kommentar. Der C16 und erst recht Ihr PC bietet genug Programmspeicher, um auch noch solche Kommentare in den Speicher mitaufzunehmen.
Eine Kommentarzeile sieht so aus:
10 REM ICH BIN EIN KOMMENTAR
Kommentieren wir also unser Programm ein bisschen:
4 REM SPRUNG INS HAUPTPROGRAMM 5 GOTO 1000 8 REM RECHTSBUENDIG-ROUTINE 9 REM ------------------------- 10 LET S=LEN(A$) 20 LET IMAX=W-S 30 LET I=0 40 LET B$="" 50 IF IMAX<=0 THEN GOTO 100 60 LET B$=B$+"_" 70 LET I=I+1 80 IF I<IMAX THEN GOTO 60 100 LET A$=B$+A$ 110 RETURN 1000 REM HAUPTPROGRAMM 1001 REM --------------------- 1005 LET W=20 1010 SCNCLR 1020 INPUT "GEBEN SIE EINEN STRING EIN (Q=Quit)";HA$ 1030 LET A$=HA$ 1040 GOSUB 10 1050 PRINT A$ 1060 IF HA$<>"Q" THEN GOTO 1020
So sieht doch das Programm gleich viel lesbarer aus. Vielleicht werden Sie die Frage stellen: Ist das denn nötig, die Kommentare ins Programm selbst zu schreiben? Ich kann mir das Programm doch ausdrucken und dann die Kommentare von Hand dazufügen!
Können Sie machen. Aber wenn Sie das Programm verändern, werdedn Sie immer neue Ausdrucke anfertigen und da müssen Sie dann sämtliche Kommentare immer neu reinschreiben. Ausserdem: Lassen Sie das Programm ein paar Monate liegen. Wenn Sie es dann mal wieder in den Speicher laden, wissen Sie dann, wo der aktuelle Papierausdruck dafür noch ist? Nein, es hat schon ziemliche Vorteile, den Kommentar in elektronischer Form und untrennbar mit dem Programm zusammen abzuspeichern.
Wir hatten schon einige Kapitel zuvor als Übungsaufgabe, ein Honorarabrechnungsprogramm zu schreiben. Und das geht nur dann vernünftig, wenn wir die Ergebnisse - Stundenzahl, Mehrwertsteuer, Nettobetrag, Bruttobetrag - rechtsbündig auf dem Bildschirm ausgeben. Das Programm müsste so aussehen:
1010 PRINT "HONORARABRECHNUNG" 1020 LET STUNDSATZ=30 1030 LET MWST=0.16 1040 INPUT "ANZAHL STUNDEN (ENDE=99)", NSTUND 1050 IF NSTUND=99 THEN END 1060 LET A$=STR$(NSTUND) >>Mache A$ rechtsbündig 1070 LET NST$=A$ 1080 LET A$=STR$(NSTUND*STUNDSATZ) >>Mache A$ rechtsbündig 1090 LET NETTO$=A$ 1100 LET A$=STR$(VAL(NETTO$)*MWST) >> Mache A$ rechtsbündig 1110 LET MW$=A$ 1120 LET A$=STR$(VAL(NETTO$)+VAL(MW$)) >> Mache A4 rechtsbündig 1130 LET BRUTT$=A$ 1140 PRINT "ERGEBNIS" 1150 PRINT "-------------" 1160 PRINT "STUNDEN: ";TAB(20);NST$ 1170 PRINT "NETTO: ";TAB(20);NETTO$ 1180 PRINT "MWST:" ; TAB(20);MW$ 1190 PRINT "BRUTTO:";TAB(20);BRUTT$ 1200 GOTO 1040
Wie man sieht, habe ich in den Programmcode Textzeilen eingefügt, an den Stellen, an denen eine Unteraufgabe zu erledigen ist. Wie bauen wir die Unterprogramme nun oben ein? Nun, mit Ihren bisherigen Kenntnissen (vor diesem Kapitel) hätten Sie das so gelöst: Sie hätten einfach den Code an der jeweiligen Stelle immer wieder eingefügt, wo er gebraucht wird. Den nachfolgenden Code hätten Sie nach hinten verschoben. In dieser Versuchung steht man als Programmierer immer. Dass man, statt die Teilaufgabe ordentlich und allgemeingültig zu lösen, eine spezifische Lösung mitten in das Hauptprogramm "reinbastelt". Ergebnis:
Diese ganzen Probleme löst der kleine GOSUB-Befehl elegant und mit einem Schlag:
4 REM SPRUNG INS HAUPTPROGRAMM 5 GOTO 1000 8 REM RECHTSBUENDIG-ROUTINE 9 REM ------------------------- 10 LET S=LEN(A$) 20 LET IMAX=W-S 30 LET I=0 40 LET B$="" 50 IF IMAX<=0 THEN GOTO 100 60 LET B$=B$+"_" 70 LET I=I+1 80 IF I<IMAX THEN GOTO 60 100 LET A$=B$+A$ 110 RETURN 1000 REM HAUPTPROGRAMM 1001 REM ------------------- 1005 LET W=20 1010 PRINT "HONORARABRECHNUNG" 1020 LET STUNDSATZ=30 1030 LET MWST=0.16 1040 INPUT "ANZAHL STUNDEN (ENDE=99)", NSTUND 1050 IF NSTUND=99 THEN END 1060 LET A$=STR$(NSTUND) 1064 REM A$ RECHTSBUENDIG 1065 GOSUB 10 1070 LET NST$=A$ 1080 LET A$=STR$(NSTUND*STUNDSATZ) 1084 REM A$ RECHTSBUENDIG 1085 GOSUB 10 1090 LET NETTO$=A$ 1100 LET A$=STR$(VAL(NETTO$)*MWST) 1104 REM A$ RECHTSBUENDIG 1105 GOSUB 10 1110 LET MW$=A$ 1120 LET A$=STR$(VAL(NETTO$)+VAL(MW$)) 1124 REM A$ RECHTSBUENDIG 1125 GOSUB 10 1130 LET BRUTT$=A$ 1140 PRINT "ERGEBNIS" 1150 PRINT "-------------" 1160 PRINT "STUNDEN: ";TAB(20);NST$ 1170 PRINT "NETTO: ";TAB(20);NETTO$ 1180 PRINT "MWST:" ; TAB(20);MW$ 1190 PRINT "BRUTTO:";TAB(20);BRUTT$ 1200 GOTO 1040
Das Tolle daran: Jedesmal, wenn wir bei der Bearbeitung des Hauptprogramms mal wieder "Rechtsbündig machen" brauchen: Einfach GOSUB 10, das war's. Unsere Programme werden dadurch sehr mächtig. Und auch besser lesbar. Jemand, der sich für die Unterprogramme nicht interessiert, der braucht sie auch nicht zu lesen. Der kann sich auf das Lesen des Hauptprogramms beschränken und es dadurch viel schneller verstehen.
Der C16 bietet die Möglichkeit, mehrere Anweisungen in eine Ziele zu packen. Z.B.
10 INPUT "GEBEN SIE ZWEI ZAHLEN EIN",A:INPUT B: IF A<B THEN PRINT "A IST KLEINER ALS B":IF A>B THEN PRINT "A IST GROESSER ALS B":IF A=B THEN PRINT "A IST GLEICH B":GOTO 10
Man fügt also die zweite Anweisung hinter die erste, getrennt durch einen Doppelpunkt.
Mit diesem Doppelpunkt kann man - wie im obigen Beispiel zu sehen - viel Unsinn anrichten. Normalerweise werden Programme total unübersichtlich und schwer zu editieren, wenn man mehrere Anweisungen in eine Zeile packt. Es gibt nur ganz wenige Ausnahme. Die eine sind mehrere gleichartige Zuweisungen. Z.B. 10 LET X=1:LET Y=10
Hier fördert es sogar die Übersicht, wenn man die beiden Zuweisungen in eine Zeile schreibt, da man sofort sieht, dass es sich um die gleiche Sache handelt: Z.B. Eigenschaften einer Position oder einer geometrischen Figur o.ä.
Die zweite sind Kommentare. Es kann sinnvoll sein, die Kommentare hinter die Anweisungen zu schreiben: 1010 GOSUB 10:REM RECHTSBUENDIG
Hier kann man ev. sogar noch die Zuweisung des Arguments mit in die Zeile packen:
1010 LET A$="ABC___":GOSUB 10:REM RECHTSBUENDIG
Zunächst sieht das ungewohnt aus, aber mit der Zeit wird man diese Trilogie vor dem inneren Auge als einen eigenen "Befehl" der Form RECHTSBUENDIG(A$) lesen. Das macht Sinn.
Übungsaufgabe 1: Manchmal ist es notwendig, Strings in Zahlen umzuwandeln. Dazu haben wir die VAL()-Funktion. Diese hat aber den Nachteil, dass sie Null zurückgibt, egal, ob im Argumentstring etwas stand, was gar keine Zahl war oder tatsächlich eine Null. Das heisst, man kann eine Routine gut gebrauchen, die den String A$ darauf prüft, ob es sich dabei um eine Zahl handelt. Schreiben Sie so eine Routine und bauen Sie sie in das Honorarabrechnungsprogramm ein, indem Sie die Stundenzahl nicht als NSTUND-Zahl, sondern als String entgegennehmen.
Übungsaufgabe 2: Beim Mastermind-Spiel waren wir mit der Teilaufgabe konfrontiert, ein Zeichen an einer ganz bestimmten Stelle eines Strings zu verändern. Mit Hilfe der Befehle LEFT$ und RIGHT$ muss man dazu den String um das zu ersetzende Zeichen herum neu zusammensetzen. Ziemlich umständlich. Schreiben Sie ein Routine und bauen Sie diese in das Mastermind-Programm ein. Vergessen Sie die Möglichkeit von Mehrfachanweisungen nicht!
Übungsaufgabe 3:Im Mondlandespiel muss man immer wieder eine Raumfähre auf den Bildschirm zeichnen. Auch das ist eine Teilaufgabe für sich: Eine Routine, die eine Mondlandefähre an die Position X,Y auf den Bildschirm zeichnet. Und eine Routine, die die Mondlandefähre an der Stelle X,Y wieder löscht. Schreiben Sie diese Routinen und bauen Sie diese in das Spiel ein. Sie werden sehen, dass das Programm plötzlich viel einfacher und übersichtlicher wird!