Viele, die schon gut programmieren und sich diese Einführung anschauen, werden es unmöglich finden, dass hier das Prinzip der Funktionen erst im so ca. 25. Kapitel kommt. Das Prinzip wird meist als so elementar betrachtet, dass es in den meisten anderen Einführungen gleich in der ersten Hälfte des ersten Kapitels gebracht wird.
Nun, dass diese Einführung nicht ist wie andere, darauf wird schon im ersten Kapitel verwiesen. Eine Besonderheit mag dabei die Nutzung des folgenden Prinzips sein: Leiden ist ein guter Lehrmeister. Den Sinn und den Verwendungszweck eines Werkzeugs lernt man dann besonders gut, wenn man es vermisst. Der Schweiss ist es, der in Erinnerung bleibt ;-)))
Im folgenden wird es um eine Variante zum GOSUB-Befehl gehen. Wir haben ja schon gelernt, dass es ein sehr wichtiges Prinzip ist, sein grosses Programmierproblem in lauter kleine, möglichst allgemeingültige Programmierprobleme zu zerlegen. Das ist eine der grössten Künste des Programmierens und noch dazu eine, die sehr Spass macht, wenn man sie beherrscht. Denn dann kann man all die kleinen Lösungen, die man in den Monaten und Jahren programmiert hat, immer wieder benutzen. Es entstehen immer mehr kleine Unterprogramme, die z.B. Schlüsselwörter in Strings suchen, sie suchen und ersetzen, sortieren, Teilstrings auf ganz bestimmte Art und Weisen in Zahlen umwandeln, Strings formatieren usw.. Genauso mit Grafik, Dateien u.v.m.
Wenn Sie versucht haben, so ausführlich Unterprogramme zu nutzen, werden Sie allerdings immer wieder auf ein grosses und ärgerliches Hindernis gestossen sein: Die Variablen. Wenn man sich angewöhnt, wie in der Informatik üblich, für Schleifen z.B. die Variablen I, J und K herzunehmen - in Unterprogrammen geht das nicht. Denn meist sind die gleichen Variablen ja schon im Hauptprogramm verwendet worden. Also muss man dann im Unterprogramm I1, J1 und K1 oder sowas nehmen. Aber dann ruft das Unterprogramm noch ein Unterprogramm auf. Man denkt dabei nicht daran, dass dieses ja auch I1, J1 usw. verwendet - und schon kracht's. Und man begreift erst mal gar nicht, warum. Denn es kommt ja keine Fehlermeldung.
In der Informatik nennt man so etwas eine "Name Space Pollution", eine Namensraumverschmutzung. Bisher wird das Problem der Namensraumverschmutzung so gross gewesen sein, dass eine sinnvolle Wiederverwendung von Unterprogrammen nur dann möglich war, wenn man bei jedem Programmierprojekt an alle Unterprogramme Hand anlegt und ihre Variablennamen mühsam so hinbiegt, dass sie sich in der aktuell verwendeten Kombination nicht überschneiden - was für ein Umstand!
Das wird jetzt besser. Und dazu gibt es in QBASIC SUB's. SUB's sind Unterprogramme, innerhalb der Variablennamen ihre eigene Bedeutung haben. Eine Variable I innerhalb des SUB hallo1 ist eine andere Variable, als das I innerhalb des SUB hallo2 und alle beide sind andere Variablen, als das I innerhalb des Hauptprogramms. Und das gilt für alle anderen Variablen genauso. Ein Variablenname ist also jetzt nicht mehr eins zu eins mit einem Speicherplatz verknüpft, sondern er verweist auf verschiedene Speicher, je nachdem, in welchen SUB oder Hauptprogramm wir uns befinden. Wozu das gut ist, brauche ich nun gewiss nicht mehr zu erklären! Wir sagen zu so etwas auch: Der Raum innerhalb eines SUB's ist ein Namensraum. Und wir lernen jetzt, dass QBASIC offenbar mehrere Namensräume verwalten kann.
Allgemein nennen wir solche Unterprogramme mit eigenen Namensräumen Funktionen oder Methoden oder (wie schon früher gelernt) Routinen. Wir werden den Terminus "Funktion" beibehalten, für SUBs und auch für die gleich noch einzuführenden FUNCTIONs. "Funktionen" im informatischen Sinn haben also nicht unbedingt einen Rückgabewert wie die Sinusfunktion oder Wurzelfunktion.
Schreiben Sie einmal in ein neues Programm folgende Anweisungen:
SUB hallo1
Sobald Sie die Zeile mit einem ENTER abschliessen, ergänzt QBASIC von selbst eine weitere Zeile:
SUB hallo1 END SUB
Überhaupt übernimmt QBASIC jetzt ziemlich energisch die Regie, was geschrieben werden darf und was nicht. Wenn Sie versuchen, vor das SUB eine Zeile einzufügen, macht QBASIC sofort daraus einen Kommentar. Wenn Sie versuchen, nach dem END SUB noch etwas einzufügen, dann protestiert QBASIC und verbietet Ihnen das glatt. Was ist denn da los?
Stillschweigend hat QBASIC hier ein neues Fenster innerhalb der Entwicklungsumgebung aufgemacht. Sie sehen das, wenn Sie F2 drücken oder auf Ansicht->SUBs gehen. Da sehen Sie eine Liste der Routinen, aus denen das Programm besteht. Und Sie sehen, dass das schon zwei sind: hallo1 und "Unbenannt", das Hauptprogramm, das den Dateinamen trägt. Wenn Sie "Unbenannt" auswählen, dann landen Sie wieder im Hauptprogramm und damit vor einem leeren Bildschirm. Hier ist alles so wie es schon immer war.
Das Konzept ist also, für jedes Unterprogramm, jedes SUB ein eigenes Fenster aufzumachen. Daher das Schreibverbot: Vor und nach dem SUB machen im SUB-Fenster Anweisungen keinen Sinn.
Übrigens ist diese Art, SUB's im eigenen Fenster zu verwalten, eine Spezialität von QBASIC. In anderen BASIC-Versionen und Entwicklungsumgebungen schreibt man die Funktionen ins selbe Fenster wie das Hauptprogramm.
Wir füllen nun mal unser neues SUB hallo1 mit Inhalt. Und das Hauptprogramm auch:
Hauptprogramm: --------------- DIM i AS INTEGER FOR i = 0 TO 3 PRINT "main", i PRINT "----------------------" hallo1 NEXT i Sub's: ---------------- SUB hallo1 DIM i AS INTEGER FOR i = 0 TO 3 PRINT "hallo1", i NEXT i END SUB
Hier sehen wir, wie ein SUB aufgerufen wird. Nämlich einfach mit seinem Namen. Ohne irgendein Schlüsselwort davor. Als wenn das SUB ein eigener Befehl von QBASIC wäre. Und genauso sind Funktionen (so nennt man SUB's im allgemeinen) auch gedacht: Sie sollen den Befehlssatz einer Programmiersprache erweitern. Durch den eigenen Namensraum ist das jetzt auch möglich: Man kann einen SUB aufrufen, ohne sich darum zu kümmern, was innerhalb des SUB's passiert. Aber ist das auch so? Schauen wir uns die Ausgabe an:
Die Ausgabe:
main 0 ------------------ hallo1 0 hallo1 1 hallo1 2 hallo1 3 main 1 ------------------ hallo1 0 hallo1 1 hallo1 2 hallo1 3 main 2 ------------------ hallo1 0 hallo1 1 hallo1 2 hallo1 3 main 3 ------------------ hallo1 0 hallo1 1 hallo1 2 hallo1 3
Hat funktioniert. Wir haben im Hauptprogramm die Schleifenvariable I verwendet, ohne uns darum zu kümmern, dass I in hallo1 auch verwendet wird. Aber das macht nichts, da in hallo1 I eine andere Variable darstellt. Der Wert vom I des Hauptprogramms bleibt dabei erhalten: Nach dem ersten Aufruf von hallo1 fährt das Hauptprogramm ganz brav mit I=1 fort.
Schreiben Sie das gleiche Testprogramm mit GOSUB's und vergleichen Sie das Ergebnis!
Ab jetzt empfehle ich Ihnen, kein GOSUB mehr benutzen. Auch innerhalb von SUB's nicht. Wenn Sie sich das Problem von Namensraumkonflikten unbedingt weiter ans Bein binden wollen, können Sie natürlich weiter daran festhalten, aber da selbst das kleinste Unterprogramm meistens Variablen verwendet, werden Sie dann auch weiterhin viel Ärger haben...
Nun haben wir aber ein Problem. Innerhalb des SUB's können wir Variablen anderer SUB's oder des Hauptprogramms nicht sehen. Nehmen wir an, wir wollen dem SUB vom Hauptprogramm mitteilen: "Vertausche die Reihenfolge der Zeichen des folgenden Strings!" Wie "geben" wir dem SUB den String?
Dazu gibt es Argumente. Die schreibt man in Klammern hinter den SUB-Namen:
SUB vertausch (s AS STRING) DIM slen AS INTEGER, i AS INTEGER, s1 AS STRING slen = LEN(s) FOR i = 1 TO slen s1 = s1 + MID$(s, slen - i + 1, 1) NEXT i PRINT s1 END SUB
Diese Funktion nimmt den übergebenen String s, vertauscht die Reihenfolge der Zeichen und gibt das Resultat auf dem Bildschirm aus. Aufrufen können wir sie im Hauptprogramm mit
vertauschalso z.B.
vertausch "qbasic"
So ist unsere Funktion "vertausch" allerdings noch nicht schön. Wer braucht schon eine Funktion, die die Zeichenreihenfolge vertauscht und dann sofort das Resultat auf dem Bildschirm ausgibt? Meistens werden wir den invertierten String irgendwie weiterverwenden wollen. Aber dazu müsste das SUB in der Lage sein, das Resultat wieder an uns zurückzugeben.
Kein Problem. Dazu tauschen wir das Schlüsselwort SUB gegen FUNCTION aus. Und verpassen dem Funktionsnamen selbst einen Datentyp, wie wenn es eine normale Variable wäre. Da in diesem Fall die Funktion einen String zurückgeben soll, ist die Funktion vom Typ String:
FUNCTION vertausch(s AS STRING) AS STRING (...) END FUNCTION
Jetzt ist nur noch die Frage, wie der Rückgabewert definiert wird. Nun, logisch wäre es, das ganz genauso wie bei einer Variablen zu machen:
' So geht's in QBASIC leider nicht FUNCTION vertausch(s AS STRING) AS STRING (...) vertausch=... END FUNCTION
Hier ist QBASIC unkonsequent und lässt nur eine Typdeklaration mittels der Spezifizierer ("Spezialzeichen") zu:
' So geht's in QBASIC FUNCTION vertausch$(s AS STRING) (...) vertausch$=... END FUNCTION
In neueren BASIC-Versionen funktioniert aber auch die obere, logischere Syntax.
Im konkreten Beispiel:
FUNCTION vertausch$(s AS STRING) DIM slen AS INTEGER, i AS INTEGER, s1 AS STRING slen = LEN(s) FOR i = 1 TO slen s1 = s1 + MID$(s, slen - i + 1, 1) NEXT i vertausch$=s1 END FUNCTION
Im Hauptprogramm verwenden wir die Funktion genauso wie eine der eingebauten Funktionen (sin() oder asc()), allerdings ohne die Klammern um die Argumente. Das mag QBASIC nicht:
Hauptprogramm: --------------- DIM s AS STRING s = vertausch$("QBASIC") PRINT s
Schauen Sie sich einmal das Hauptprogramm an! Ist das nicht prima lesbar und verständlich? Jeder weiss, was dieses Hauptprogramm macht! Und das ist das Geheimnis, warum es in der strukturierten Programmierung möglich wird, riesige Programme zu erstellen: Weil dank der Strukturierung in Funktionen und dank der Reduzierung der Menge an Variablennamen, mit der hantiert wird, die Programme übersichtlich und lesbar bleiben. In unserem Hauptprogramm hätten wir sogar auf das s verzichten können:
Hauptprogramm: --------------- PRINT vertausch$("QBASIC")
Allerdings will QBASIC bei FUNCTION's Klammern um die Argumente herum haben. Auch da ist kein so richtig einleuchtendes Prinzip erkennbar. QBASIC will, dass SUB's ohne Klammern um die Argumente aufgerufen werden und FUNCTION's mit Klammern, obwohl beides Funktionen sind, nur einmal mit und einmal ohne Rückgabewert. (Das ist ein typisches Beispiel für mangelnde informatische Stringenz in älteren BASIC-Implementierungen, die bei komplexeren Problemen die Gefahr hervorruft, dass die Programme "zerfasern", weil abstraktere, schönere Lösungen nicht möglich sind.)
SUB testsub(x AS integer) x=1 END SUB 'Hauptprogramm x=0 CALL testsub(x) PRINT x
Was wird ausgegeben? Eine Eins. Es ist also so, dass Argumente von der Funktion (dem SUB) nicht nur gelesen, sondern auch geschrieben werden können. Und Änderungen bleiben in der aufrufenden Funktion wirksam. Mit anderen Worten: Argumente sind Variablen, die Aufrufer und Funktion gemeinsam verwalten. Man nennt solche Argumente "By Reference"-Argumente.
Eigentlich sind "By Reference"-Argumente nicht so gut, da sie das Prinzip der getrennten Namensräume zwischen den Funktionen aufweichen und daher wieder Fehleranfälligkeit mit sich bringen. Da aber QBASIC einige Beschränkungen im Bereich "Strukturierte Programmierung" aufweist, sind wir in diesem Fall ganz froh um dieses "Feature".
Noch ein Beispiel: Sollen Hauptprogramm und das SUB testsub() die Variable n gemeinsam verwalten, dann übergibt man sie einfach testsub() mit: SUB testsub(x as integer, n as integer). Dabei ist x das eigentliche Argument und n eben nur die gemeinsame Variable, die z.B. einen Programmstatus oder ein Limit enthalten kann. Will man zudem noch einen Wert zurückgeben, dann kann man entweder daraus eine FUNCTION machen (bei nur einem Rückgabewert empfehlenswert) oder der SUB noch ein drittes Argument mit auf den Weg geben SUB testsub(x as integer, n as integer, returnvalue as integer).
Jedes SUB sollte gleich hinter der Kopfzeile einen Kommentar mit folgenden Angaben enthalten:
Ihnen wird wahrscheinlich schon aufgefallen sein, dass QBASIC eigenmächtig auch im Hauptprogramm aktiv wird, sobald sie das Programm speichern: Es fügt Zeilen der Form
DECLARE Subname (Argumente)
hinzu. Warum macht es das? Nun, das hat technische Gründe. Der Intepreter muss von Anfang an, also schon bei der ersten intepretierten Programmzeile, wissen, welche Funktionen es im Programm gibt und wie sie aufgerufen werden. Dazu dienen die DECLARE's. Der Interpreter startet also mit den DECLARE's, erst dann geht er zur ersten Zeile des Hauptprogramms. Für Sie als Programmierer haben die DECLARE's eine kleine Falle parat: Falls die DECLARE's schon eingefügt wurden und Sie im Nachhinein einen Funktionskopf noch einmal ändern, dann meckert QBASIC: Falsche Anzahl der Argumente (oder etwas ähnliches). Sie müssen die Änderung immer an zwei Stellen durchführen, bei der Deklaration, also dem DECLARE, und bei der Definition im SUB-Fenster.
%%1