Einfache und zusammengesetzte Datentypen


Strukturierte Programmierung

Der Begriff "Strukturierte Programmierung" wurde hier und da schon fallengelassen, ohne zu erklären, was überhaupt damit gemeint ist. Wir werden uns hier auch noch nicht richtig damit auseinandersetzen, da QBASIC nur begrenzt die entsprechenden Möglichkeiten aufweist. Aber trotzdem hier schon ein kurzes Wort dazu.

Wir unterscheiden heute im Groben drei "Niveaus" oder "Stile" der Programmierung.

Wobei vielfach darauf hingewiesen wird, dass es eigentlich noch eine Zwischenebene zwischen der zweiten und dritten gibt, nämlich die "objektverarbeitende Programmierung".

Beim Anstieg von einem Niveau auf's nächste geht's darum, komplexere Programme schreiben zu können. Vielleicht hat Sie dieser Kurs (oder ein anderer) schon ermutigt und motiviert, einmal ein richtig grosses Programm zu schreiben; eins, das nicht nur zwanzig oder hundert Zeilen hat, sondern, sagen wir zweitausend. Und selbst das ist noch ein vergleichsweise kleines Programm; es gibt Programme, die haben zehn Millionen Zeilen und mehr! Aber schon bei ein, zweitausend Zeilen BASIC werden Sie festgestellt haben, dass es immer schwerer wird, ein fehlerfreies Programm zu bekommen, je mehr Zeilen es hat. Da müssen Sie Stunden und Tage nach einem Fehler suchen. "Warum springt das verflixte Programm jetzt in diese Zeile?" werden Sie sich oftmals gefragt haben. Oder: "Warum hat jetzt die Variable sum_abc1 null?" Insofern ist es zwar theoretisch möglich, auch ein 100.000-Zeilen-Programm in BASIC zu schreiben, aber man wird sehr, sehr, sehr lange Zeit dafür brauchen.

Schon früh in der Computerentwicklung hat man gemerkt, dass man sehr viel Zeit sparen kann, wenn man Mittel an der Hand hat, das Programm strukturieren zu können. Kommentare sind ein solches Mittel. Sie werden vermutlich auch jetzt noch viel zu wenig kommentieren. Ein Kommentar, der in 3 Minuten geschrieben ist, spart im Mittel eine halbe bis eine Stunde Entwicklungszeit ein!

Solche Strukturierungsmittel wie Kommentare, GOTO-freie Programmierung, If-Blöcke usw. sind es, die die Produktivität eines Programmierers vervielfachen können, wenn er damit umgehen kann. Sie sind es auch, die einen einfachen Programmierer von einem erfahrenen Informatiker unterscheidet. Der Informatiker ist (auch) Fachmann für Programmstrukturierung, nicht nur für praktische Programmierung

Ein Tipp an dieser Stelle: Wenn Sie vorhaben sollten, ein Programm von tausend Zeilen oder mehr zu erstellen, dann warten Sie damit noch ein bisschen. Sie können das zwar jetzt schon, aber Sie vertun dabei viel Zeit. Mit den in den folgenden Kapiteln eingeführten Mitteln der strukturierten Programmierung sind Sie viel schneller und haben Sie viel mehr Freude daran.

Deklarieren

Wir haben inzwischen gelernt, dass der Computer für alle Variablen Speicherplatz benötigt. Wir haben uns aber noch nicht darum gekümmert, wie der Computer erfährt, wieviel Speicherplatz er eigentlich braucht. Am Anfang unserer Programmierversuche wäre die Sorge auch störend gewesen. BASIC ist eine Sprache, die extra für Anfänger entworfen wurde. Und daher nimmt es dem Lernenden erstmal diese Sorge ab. Man schreibt einfach eine Zuweisung hin "x=1+1" und BASIC weiss: "x" ist eine Zahlvariable. Und reserviert die benötigte Anzahl von Bytes. Falls es auf 'x$="1+1"' trifft, dann weiss es: "x" ist eine Stringvariable. Usw.

Bei Arrays verhielt es sich allerdings anders. Diese muss man deklarieren. Das heisst, man muss dem Computer sagen, dass man nun die Variable x benutzen will und welche Grösse x haben soll, z.B. DIM x(100).

Da ein Array in Wirklichkeit zusammengesetzt ist aus verschiedenen einzelnen Zahlvariablen, gehören Arrays zu den s.g. "zusammengesetzten Datentypen". Zusammengesetzte Datentypen muss man auch in BASIC deklarieren.

Automatisches und manuelles Deklarieren

Nun gibt es verschiedene Gründe, warum diese "Deklarationsautomatik" bei einfachen Datentypen für grössete Programme Nachteile bringen kann:

  1. Schreibfehler wirken sich z.T. verheerend aus. Wahrscheinlich ist Ihnen das auch schon passiert: Sie wollen die Variable XCOORD verwenden, und schreiben stattdessen an irgendeiner Stelle XCORD. BASIC denkt sich: "Hups! Eine neue Variable!", deklariert sie und initialisiert sie mit null, d.h. gibt ihr den Anfangswert null. Inmitten einer komplizierten Formel fällt der Schreibfehler aber nicht auf. Man stellt nur fest, dass das Formelergebnis nicht stimmt. Dass da plötzlich eine ganz neue Variable auftauchen könnte, darauf kommt man nicht. Und man rechnet und rechnet und grübelt und grübelt und ist am Schluss ziemlich verzweifelt.
  2. Geschwindigkeit. Das ist heute kein so grosses Problem mehr wie früher, aber es spielt schon noch eine Rolle. Wenn BASIC automatisch deklariert, dann wird jede Zahlvariable als Flieskommazahl deklariert. Mit dem Ergebnis,d ass jede Rechenoperation eine Fliesskommaoperation ist. Früher waren diese zehn- oder zwanzigmal langsamer als Ganzzahloperationen. Die Ganzzahlrechnungen hatte der Prozessor ja eingebaut, die Fliesskommaoperationen nicht. Heute ist das anders, aber trotzdem sind Ganzzahloperationen noch schneller. Also wird man interessiert daran sein, BASIC zu sagen: "x ist eine Ganzzahl, keine Fliesskommazahl!". Am interessantesten ist dies bei FOR-Schleifen. Es macht überhaupt keinen Sinn, FOR I=0 TO 99 mit I als Fliesskommazahl durchzuführen. Überlassen wir das Deklarieren aber vollständig BASIC, dann macht es genau das.
  3. Ungenaue Vergleiche: Wenn x und y Fliesskommazahlen sind und wir schreiben IF (x=y) THEN... hin und wir gehen davon aus, dass in x und y nur Ganzzahlen sind, dann kann das in die Hose gehen. Das sieht man am deutlichsten bei x=(((((x/2)*2)/2)*2)/2)*2. Sind wir mit x=1 gestartet, ist nun x=0.999999 oder x=1.00001. Wir haben Rundungsfehler. Und wenn wir nun den o.g. Vergleich durchführen, arbeitet dieser nicht so, wie wir uns das gedacht haben. Auch diesen Fehler werden wir ziemlich lange nicht entdecken!
  4. Kleines Argument, aber es sei hier erwähnt: Speicherplatz. Nehmen wir an, wir wollen eine Karte, eine "Map" mit 200 x 100 Punkten erstellen. Jeder Punkt soll eine von 16 Farben haben. Wenn wir einfach nur DIM map(200,100) deklarieren, hagelt es eine Fehlermeldung. Warum? In QBASIC kann man Arrays nur bis max. 64K Grösse benutzen. Eine einfache Fliesskommazahl, die QBASIC für automatisch deklarierte Zahlvariablen hernimmt, benötigt 4 Bytes. 4 x 200 x 100 = 80000 > 64K. Wir bräuchten aber nur Ganzzahlen, sogar eigentlich nur ein Byte pro Punkt. Nur 1 Byte für eine Ganzzahl herzunehmen, das sieht QBASIC nicht vor, aber 2 Byte geht. Dieser Datentyp heisst "INTEGER". Deklarieren wir DIM x(200,100) as INTEGER, dann geht's!

Gründe genug, sich anzugewöhnen, bei grösseren Programmen auf die automatische Deklaration zu verzichten. Variablen explizit zu deklarieren, auch das ist ein Element der strukturierten Programmierung.

Einfache Datentypen

In QBASIC unterscheiden wir folgende einfache Datentypen:

Typspezifizierer

Mit dem "speziellen Zeichen" hat es folgendes auf sich: Man kann in QBASIC den Datentypus auch ohne manuelle Deklaration zuweisen, indem man dem Variablennamen das entsprechende "spezielle Zeichen" (Typspezifizierer) anhängt. Wir kennen das schon von den Strings mit dem $-Zeichen. Das geht aber mit den anderen Typspezifizierern genauso. Beispiel:

'Kurzes Programm
sum&=0
FOR i%=0 TO 32766
  sum&=sum&+i%
  PRINT sum&
NEXT i%

Die letzten Zeilen der Ausgabe lauten:

 32755         536461390
 32756         536494146
 32757         536526903
 32758         536559661
 32759         536592420
 32760         536625180
 32761         536657941
 32762         536690703
 32763         536723466
 32764         536756230
 32765         536788995
 32766         536821761
 

Wenn Sie die FOR-Schleifengrenze auf 32767 erhöhen, erhalten Sie die Fehlermeldung "Überlauf". Die Sie übrigens erst dann wieder wegkriegen, wenn Sie im Menü auf Ausführen->Neustart gehen. Ein Integer kann eben die Zahl 32767 nicht speichern. Hingegen kann sum, das als LONG spezifiziert ist, problemlos 536 Millionen und noch ein paar Zerquetschte speichern.

Manuelles Deklarieren einfacher Datentypen

Der schöne und empfehlenswertere Weg ist allerdings die manuelle Deklaration. Die Syntax dazu ist: DIM <Variable> AS <Typ> . Wobei anstelle von <Typ> einer der fünf einfachen Datentypen INTEGER, LONG, SINGLE, DOUBLE oder STRING zu setzen ist.

Unser kurzes Programm mit manueller Deklaration:

'Kurzes Programm
DIM i AS INTEGER
DIM sum AS LONG
sum=0
FOR i=0 TO 32766
  sum=sum+i
  PRINT sum
NEXT i

Diese Form macht das Programm auch lesbarer, da die Variablennamen nicht durch Sonderzeichen verunziert werden.

Allerdings hat man damit die "Unsitte" (so ganz eine Unsitte ist das auch wieder nicht; wir kommen noch drauf zurück,) der automatischen Deklaration immer noch nicht abgeschaltet. Wir haben nur einige Variablen manuell deklariert. Wir können weiterhin einfach mitten im Programmtext aus Versehen oder mit Absicht neue Variablennamen erwähnen und BASIC beschwert sich nicht. Bei QBASIC können wir das leider überhaupt nicht abschalten. Bei anderen BASIC-Varianten (z.B. Visual Basic oder Visual Basic for Application (VBA)) geht das aber. Die Anweisung gibt man ganz am Anfang des Programms und sie heisst: "OPTION EXPLICIT". Das nur so nebenbei.

Noch ein Wort zum "loose typing"

Das, was wir "automatisch Deklarieren" nannte, nennt man in der Fachsprache "loose typing", also lose Tpyisierung. Was gleichzeitig dann auch "loose declaration" heisst. "Loose typing" ist keineswegs auf dem Rückzug. Vielmehr unterscheidet man heute zwischen Skriptsprachen und Compilersprachen. Skripte sind in der Regel kurze kleine Programme, um Systemverwaltung, Anwendungsverwaltung oder Webverwaltung zu erleichtern und zu automatisieren. Ein Techniker oder Verwalter, der solche Aufgaben zu erledigen hat, hat andere Sorgen, als sich den Typ von Schleifenvariablen zu überlegen. Daher erlauben solche Skiptsprachen das "loose typing". Compilersprachen hingegen werden meist für grössere Entwicklungsprojekte mit vielen tausend Zeilen benutzt. Hier ist loose typing fast überall verboten (z.B. Java, C, C++, C#, Delphi usw.). BASIC ist so ein Zwitter mitten drin. Es gibt BASIC-Skriptsprachen, in jedem modernen Windows-Betriebssystem ist Visual BASIC Script (VBS) eingebaut. Und es gibt BASIC als Compilersprache, z.B. in Form von Gambas, Visual Basic, Freebasic, Purebasic usw. Hier sollte man auf eine strenge Deklarations- und Typisierungspraxis achten.

Strukturvariablen

Nun kommen wir zu einem äusserst wichtigen neuen Gestaltungsmittel: Strukturvariablen. Es ist deshalb so wichtig, weil es nicht nur in der struktierierten Programmierung eine grosse Rolle spielt, sondern sozusagen die Keimzelle der objektorientierten Programmierung in sich birgt. Das sei hier gleich vorweggenommen.

Was Strukturvariablen sind, ist zum Glück ganz einfach erklärt: Mehrere einfache Variablen werden zu einer "Obervariablen" zusammengefasst. Z.B. bilden Name, Strasse, Hausnummer, Postleitzahl und Wohnort zusammen eine "Adresse". Es wäre nun schön, wenn wir einen Datentyp "Adresse" zur Verfügung hätten, in dem all diese Angaben gespeichert werden könnten. Mittels Strukturvariablen kein Problem. In QBASIC gibt es dazu das Schlüsselwort TYPE. Am Besten zuerst ein Beispiel:

TYPE adresstyp
  NAME1 AS STRING * 40
  strasse AS STRING * 40
  hausnr AS INTEGER
  plz AS LONG
  wohnort AS STRING * 40
END TYPE

DIM adressen(100) AS adresstyp
DIM i AS INTEGER
DIM answer AS STRING

i=0
DO 
  PRINT "Adresse Nr.";i
  INPUT "Name: ";adressen(i).name1
  INPUT "Strasse: ";adressen(i).strasse  
  INPUT "hausnr: ";adressen(i).hausnr
  INPUT "Postleitzahl: ";adressen(i).plz
  INPUT "Wohnort: ";adressen(i).wohnort
  INPUT "Noch eine Adresse? ";answer  
LOOP UNTIL answer="no"
  

Zunächst wird mit TYPE der neue Datentyp definiert. Der Name des Datentyps ist "adresstyp". Merke: Nicht "Adresse". Man sollte immer zwischen den Datentypen und den Variablen selbst gut unterscheiden!.

Dann kommt in den nächsten Zeilen eine Aufzählung der Datentypen, die wir in "adresstyp" reinpacken wollen. Die Syntax ist eigentlich identisch zu der einer DIM-Deklaration, nur ohne das Schlüsselwort DIM. Gleich in der ersten Zeile begegnen wir allerdings einigem Ungewohnten. Erstens: Warum heisst es "name1" und nicht "name"? Und zweitens: Was hat das "* 40" zu besagen?

Nun, NAME ist eine QBASIC-Anweisung und kann daher nicht für eigene Namen verwendet werden. Beim String besteht das Problem, dass wir - im Gegensatz zur Deklaration ausserhalb von TYPES - angeben müssen, wieviel Bytes denn für den String reserviert werden sollen. Ausserhalb von TYPE müssen wir das nicht, da dann QBASIC von selbst mehr Speicherplatz bereitstellen kann, wenn der String länger wird. Innerhalb von TYPES geht das nicht und wir müssen daher QBASIC gleich von Anfang an sagen, wie lange der String maximal werden soll.

Ansonsten dürfte alles klar sein. Für die Hausnummer reicht ein Integer, da sie selten grösser als 32766 sind. (Sie werden lachen: In Frankreich gibt's tatsächlich auch grössere Hausnummern, da für Häuser, die weit ausserhalb von Ortschaften stehen, oft die Distanz in Metern vom Ortskern als Hausnummer verwendet wird...)

Für die Postleitzahl brauchen wir seit der Postreform einen LONG. Beim Wohnort nehmen wir auch 40 Zeichen, dass solche Namen wie "Villingen-Schwenningen" auch ihren Platz finden.

Wir schliessen die Liste mit dem Schlüsselwort "END TYPE" ab. Damit ist unser neuer Datentyp fertig. Ab jetzt können wir ihn genauso verwenden wie die vordefinierten einfachen Datentypen auch. Im Beispiel deklarieren wir gleich ein ganzes Array davon. Der Zugriff auf die einzelnen Elemente der Struktur erfolgt mit der Angabe eines Punktes und dann des Elementnamens. Also: <Obervariable>.<Element>

Übung:

Modifizieren Sie das obige kleine Progrämmchen so, dass es die Adressdaten aus einer Textdatei ausliest, in der die Angaben einer Adresse jeweils durch Tabs oder Leerzeichen getrennt in einer Zeile stehen.

Komplexere Datenstrukturen

Mithilfe von Strukturvariablen lassen sich ziemlich komplexe Datenstrukturen aufbauen und verwenden. Man kann nämlich

als Elemente von Strukturvariablen verwenden.

Dies sei an einem Beispiel erläutert, das (hoffentlich) selbsterklärend ist:

TYPE punkttyp
  x AS INTEGER
  y AS INTEGER
  z AS INTEGER
END TYPE

TYPE linientyp
  startpunkt AS punkttyp
  zielpunkt AS punkttyp
END TYPE

TYPE gittertyp
  linie(100) AS linientyp
END TYPE

Angenommen, wir wollen eine Wirtschaftssimulation schreiben. Zentrale Akteure in dieser Simulation sind Firmen.Jeder Firma kann mit einer anderen Firma interagieren. Jede Firma besteht in sich aber wiederum aus Angestellten und Maschinen. Die Maschinen stellen Produkte her. Es gibt Produkte verschiedener Typen. Die Angestellten wiederum treten auch wieder als Käufer der Produkte auf. Maschinen sind übrigens auch Produkte. Und zu all diesen aufgezählten Objekten: Firmen, Angestellten, Maschinen, Käufern, Produkten sind ihre Eigenschaften festzuhalten. Bei Produkten mag das noch einfach sein: Sie haben vielleicht nur einen Preis. Bei Maschinen ist das schon schwieriger: Welche Produkttypen stellt die Maschine her? Welche kann sie herstellen? Wieviel Produkte pro Stundeund Produkttyp werden hergestellt? Wieviel der Maschine nutzt sich dabei ab? Und was ist ihr Preis? Bei den Personen ist das noch vielfältiger: Welche Produkte wollen sie kaufen? Welchen Preis bei welchem Produkt akzeptieren sie? Bei welcher Firma arbeiten sie? An welcher Maschine arbeiten sie? Was verdienen sie dort? Und schliesslich die Firma: Welche Angestellte arbeiten in ihr? Welche Maschinen gehören dazu? Welche andere Firma stellt die Produkte her, die auch diese Firma herstellt? Wie gross sind ihre Einnahmen? Ihre Ausgaben?

Übung:

Überlegen Sie sich einmal eine passende Datenstruktur mittels TYPE für diese Simulation! So dass ich als Programmierer jederzeit z.B. die Frage beantworten kann "An welchen Maschinen welcher Firmen arbeiten die Personen, die gerade Produkte mit einem Preis kleiner als x gekauft haben?"

Denken Sie auch einmal drüber nach, wie sie die Datenstruktur bilden würden, wenn Sie TYPE nicht zur Verfügung hätten!