Arrays, grössere Programme und Bugs


Was sind Arrays?

Arrays sind eine besondere Form von Speichern. Wir haben bisher zwei Typen von Speichern/Variablen kennengelernt: Zahlvariablen und Stringvariablen. Jetzt kommt ein dritter hinzu. Was fehlt denn?

Nun, jetzt, da unsere Programmieraufgaben langsam grösser werden, kann es passieren, dass wir nicht nur ein, zwei oder drei Variablen in unserem Programm brauchen, sondern vielleicht ein Dutzend oder mehr. Und manchmal wäre es gut, wenn diese Speicher nicht alle individuelle Namen tragen würden, sondern durchnumeriert wären. A1, A2, A3 usw. Zum Beispiel könnte es für manche Probleme nützlich sein, eine Grösse zu zehn verschiedenen Zeitpunkten abzuspeichern. Soweit so schön.

Aber wir können ja die Variablen dann A1, A2, A3 benennen. Das schon. Aber wenn wir nun zum Beispiel alle zehn Variablen auf null setzen wollen, müssen wir zehn LET-Anweisungen schreiben. Da hilft kein Weg drumrum:

10 LET A1=0
20 LET A2=0
30 LET A3=0
usw.

Bei zehn geht's ja noch, aber bei hundert wird das Nonsense. Man sollte die Variablen alle über ihre Nummer ansprechen können. Nehmen wir an, die Nummer steht im Zähler I, dann müsste man sagen können: LET AI=0. Statt LET A1=0.

Genau das kann der C16 auch. Nur heisst es nicht LET AI=0. Sondern, damit man Nummer und Variablennamen auseinanderhalten kann: LET A(I)=0. Zwischen den Klammern steht die Nummer, der s.g. Index. A besteht dann aus mehreren Einzelvariablen. Insgesamt heisst das dann ein Array oder - zu deutsch - ein Feld oder eine Feldvariable.

Wenn wir nun die FOR-Schleifen aus dem Vorkapitel verwenden, dann können wir mit Feldern ganz elegant umgehen:

10 FOR I=0 TO 99
20 LET A(I)=0
30 NEXT I

Auf diese Art und Weise haben wir auf einen Schlag hundert Variablen initialisiert. Aber warum zählt der Knilch von Null ab? Erstens zählen Programmierer immer von Null ab. Mich hat einmal jemand gefragt, warum sie das tun. Ich wollte ihm das ganz lässig mit einem Satz hinwerfen und habe mich dabei ziemlich blamiert. Man kann das schon erklären, aber es ist nicht so ganz einfach. Es hat hauptsächlich damit zu tun, dass man bei Zählern, die von Null ab zählen, besser den Zähler in 10er-Gruppen oder 4er-Gruppen o.ä. einteilen kann, und zwar mit dem Modulooperator. Aber das sei hier nur am Rande erwähnt. Es gibt auch noch ein Zweitens: Computer zählen meistens von Null ab. So auch der C16. Egal wie wir es machen und wollen, wenn wir mit einem Array arbeiten, gibt es das Element Null.

Jetzt fehlt hier noch was. Im Gegensatz zu einzelnen Variablen muss der C16 wissen, wie gross das Array denn sein soll, dass wir im Programm erwähnen. Beim ersten Auftreten eines A()-Ausdrucks muss er also über die Grösse von A() informiert sein. Und das machen wir mit einem DIM-Befehl. Der DIM-Befehl muss vor dem ersten Auftreten von A() im Programm einmal erscheinen. DIM A(100) reserviert ein Array von 100 Zahlspeichern. Man nennt dieses Erklären, dass man nun einen bestimmten Speicher haben will, Deklarieren von Variablen.

Und nun Vorsicht mit dem Nullzählen! Wollen wir die Elemente X(1) bis X(10), so dürfen wir nicht DIM X(10) deklarieren, sonst haben wir ein Element zu wenig! Denn dann ist X(0) bis X(9) deklariert - der C16 zählt immer von Null ab! Wir müssen also DIM X(11) deklarieren. Und lassen das nullte Element eben unbenutzt.

Beispiel für die Verwendung von Arrays: Ein Statistikrechner

Das nächste Programm ist ein bisschen grösser und stellt einen kleinen Statistikrechner dar. Es deklariert ein Feld X(), und lässt es durch den Benutzer füllen. Anschliessend rechnet es Minimum, Maximum, Mittelwert und Streuung der Zahlen aus, zeichnet eine Häufigkeitsverteilung als Balkendiagramm auf den Bildschirm und gibt daneben die vier Ergebnisse aus.


10 REM STATISTIKRECHNER
20 REM -----------------------------------------------------
30 DIM X(100)
35 DIM XG(10)
40 REM HAUPTPROGRAMM
50 REM .......
55 DO 
60 GOSUB 500:REM FUELLE X()
70 IF (N>0) THEN GOSUB 800:REM WERTE X() AUS
80 REM WERTE STEHEN IN MEAN, STD, XMIN, XMAX
90 IF (N>0) THEN GOSUB 1500:REM ZEICHNE BALKENDIAGR
100 INPUT A$
110 LOOP UNTIL A$<>"J"
120 SCNCLR
130 END
500 :
510 REM X() FUELLEN
520 REM .........
530 SCNCLR
540 COLOR 4,1,0:COLOR 1,3,4:COLOR 2,1,0
550 CHAR ,0,0,""
560 PRINT "===================================="
570 PRINT "      STATISTIKRECHNER"
580 PRINT "===================================="
590 REM DIESE UEBERSCHRIFT KOENNEN SIE MIT HILFE DER
600 REM GRAFIKZEICHEN NOCH SCHOENER MACHEN.
610 PRINT:PRINT
620 COLOR 1,6,4
635 LET N=0:REM ANZAHL DER DATEN
640 LET A=0
650 DO WHILE (A<>-999)
640 GOSUB 2000:REM EINGABE AUFFORDERN
650 LET X(N)=A
660 LET N=N+1
670 IF (N>99) THEN GOSUB 2200:RETURN:REM 
MELDUNG: MAX 100 ZAHLEN
680 LOOP
690 RETURN
799 :
800 REM AUSWERTUNG
810 REM .............
820 LET SUM=0
830 LET SQSUM=0
840 FOR I=0 TO N-1
850 SUM=SUM+X(I)
860 SQSUM=SQSUM+X(I)!2 ! steht für Pfeil nach oben (Potenzzeichen)
870 IF I=0 THEN IMIN=0:XMIN=X(I)
880 IF I=0 THEN IMAX=0:XMAX=X(I)
890 IF (X(I)XMAX) THEN XMAX=X(I):IMAX=I
910 NEXT I
920 MEAN=SUM/N
930 STD=-1
940 IF N>1 THEN STD=SQR(1/N*SQSUM-MEAN!2)
950 RETURN
1499 :
1500 REM BALKENDIAGRAMM
1510 REM ..........
1520 SCNCLR
1530 COLOR 1,3,4
1540 CHAR ,10,0,"HAEUFIGKEITSDIAGRAMM"
1550 GOSUB 2100:REM GRUPPIERE DATEN NACH XG()
1560 GOSUB 2200:REM SUCHE MAXIMUM IN XG()
1570 LET DY=XGMAX/20
1580 FOR I=0 TO 9
1590 COLOR 1,4,4
1600 LET ICOL=I*2+5
1610 LET IROW2=25
1620 LET IROW1=24-INT(XG(I)/DY+0.5)
1630 FOR J=IROW1 TO IROW2
1640 CHAR ,ICOL,J,CHR$(166)
1650 NEXT J
1660 NEXT I
1670 REM AUSGABE ACHSENSCHRIFT
1680 CHAR ,5,25,STR$(XMIN)
1690 CHAR ,23,25,STR$(XMAX)
1695 LET XMID=(XMAX-XMIN)/2
1700 CHAR ,14,25,STR$(XMID)
1710 REM AUSGABE DER STATISTIK
1720 LET ICOL=30:LET IROW1=5
1730 CHAR ,ICOL,IROW1,"MEAN:"+STR$(MEAN)
1750 CHAR ,ICOL,IROW1+1,"STREU:"+STR$(STD)
1760 CHAR ,ICOL,IROW1+2,"MIN:"+STR$(IMIN)
1770 CHAR ,ICOL,IROW1+3,"MAX:"+STR$(IMAX)
1775 REM EINGABE: WEITERMACHEN?
1780 CHAR ,ICOL-10,IROW1+5,"NOCH EINE STATISTIK? (J/N)"
1790 RETURN
1999 :
2000 REM EINGABE AUFFORDERN
2010 CHAR ,0,5,"GEGEN SIE ZAHL NR "+STR$(N)+" EIN"
2020 CHAR ,0,6,"(ENDE: -999)"
2030 CHAR ,0,7,"":INPUT A
2040 REM EINGABEBEREICH LOESCHEN
2050 FOR I=5 TO 7:CHAR ,0,I,
"_______________________________":NEXT I
2060 RETURN
2100 REM GRUPPIERE DATEN
2110 FOR I=0 TO 9
2120 LET XG(I)=0
2130 NEXT I
2140 LET DX=(XMAX-XMIN)/10
2150 FOR I=0 TO N-1
2160 LET IG=(X(I)-XMIN)/DX
2170 LET XG(IG)=XG(IG)+1
2180 NEXT I
2190 RETURN
2200 REM SUCHE MAXIMUM IN XG
2210 FOR I=0 TO 9 
2220 IF I=0 THEN IMAX=I:XGMAX=XG(I)
2230 IF XG(I)>XGMAX THEN IMAX:=I:XGMAX=XG(I)
2240 NEXT I
2250 RETURN

Dieses Programm soll mehr als nur die Verwendung von Arrays zeigen. Weiteres gleich weiter unten. Aber zunächst mal zu den Arrays: Es werden zwei benutzt, X() und XG() und sie werden gleich am Anfang mittels eines DIM-Befehls deklariert. Man sollte die DIM-Befehle alle an den Anfang und beieinander schreiben, damit man den Überblick darüber behält, welche Felder man schon deklariert hat und welche nicht.

Die Benutzung ist nicht weiters schwierig. Sie sehen, dass die einzelnen Elementvariablen der Arrays nie anders als in einer Schleife angesprochen werden. Z.B. Zeile 650 oder 850. Spätestens jetzt müsste klar sein, dass die Aufgabe ohne Arrays gar nicht gelöst hätte werden können, denn erstens wäre es unmöglich gewesen, die ganzen Schleifen "aufzuwickeln" und explizit jeden Schleifendurchgang hinzuschreiben und zweitens ist beim Schreiben des Programms noch gar nicht klar, wie lang die Schleifen eigentlich sein werden, d.h. mit wieviel Elementen überhaupt gerechnet wird.

Ein paar Empfehlungen für's Programmieren

Nun noch ein paar Erklärungen zum Programm selbst. Ich vermute, hätten Sie das Programm als Anfänger selbst geschrieben, Sie hätten es etwas anders gemacht. Folgende Merkmale werden Sie entdecken:

Tatsächlich ist der GOTO-Befehl ein Merkmal sehr einfacher Programmierung, wie wir sie am Anfang kennengelernt haben. Bei längeren Programmen macht ein unüberlegtes Verwenden von GOTO's das Programm unkontrollierbar. Hier ein abschreckendes Beispiel, um deutlich zu machen, was ich meine:

10 PRINT "GEBEN SIE EINE ZAHL EIN (1 BIS 10)"
20 INPUT A
30 IF A<3 THEN GOTO 100
100 LET I=0
110 LET I=I+1
120 IF I<5 OR A>2 THEN GOTO 150
130 GOTO 110
140 LET I=I+A
150 IF I<10 THEN GOTO 100
160 GOTO 10

Wissen Sie, was dieses Programm macht? Ich nicht. (Und ich habe es geschrieben!) Und das Programm ist noch nicht einmal lang. Ergo: Solange man keine besseren Befehle wie GOSUB und Schleifen hat, ist GOTO für kurze Programme OK. Aber der C16 hat GOSUB und Schleifen und daher kann man bis auf ein paar wenige Ausnahmen darauf verzichten. Das sollte kein Dogma sein: Wenn Ihnen partout nicht einfällt, wie Sie ein Problem ohne die einmalige Verwendung eines GOTO's lösen können, dann nehmen Sie das GOTO. Drei GOTO's in einem Programm sind keine Katastrophe. Aber zehn können es schon sein.

Es gibt eine Situation, die GOTO's erforderlich zu machen scheint. Wenn man zwei Programmteile hat, bei denen in Abhängigkeit einer IF-Anweisung entweder der eine oder der andere ausgeführt werden soll. Schönes Beispiel sind die Zeilen 2220 und 2230. Falls die Bedingung zutrifft, werden die beiden durch Doppelpunkt getrennten Befehle hinter THEN ausgeführt, sonst nicht. Was ist, wenn es nun nicht nur zwei kurze Befehle sind, sondern zehn? Dann ist es wohl schlecht, sie in einer Riesenzeile alle per Doppelpunkt hintereinanderzuhängen. Bisher sind wir dann immer hinter dem IF mit GOTO in einen separaten Programmteil gesprungen und am Ende wieder zurück. Nun, das machen wir dann immer noch so, aber nun machen wir es nicht mehr mit GOTO, sondern viel übersichtlicher mit einem eigenen kleinen Unterprogramm und GOSUB.

Noch eine Bemerkung zur Maximumsuche Zeile 2200 bis 2250. Hier stehen zwei IF-Anweisungen hintereinander, die hinter dem THEN identische Anweisungsblöcke haben. Warum haben wir die beiden nicht zu einer zusammengefasst? IF I=0 OR XG(I)>XGMAX THEN ...? Das wäre ein richtig netter Bug gewesen. Sie wissen nicht, was ein Bug ist? Dann lesen Sie gleich unten weiter. Jedenfalls hätte es nicht funktioniert und wir hätten den Fehler nicht so schnell gefunden. Das Problem ist, dass wir durch die erste IF-Anweisung XGMAX einen Anfangswert zuweisen. Wenn wir beide IF-Anweisungen zusammenfassen, hat XGMAX zum Zeitpunkt, zu dem der Computer die IF-Bedingung auswertet, gar keinen Anfangswert. Keinen definierten jedenfalls. Wir kennen ihn nicht. Und das kann dann ganz schön in die Hose gehen.

Bugs

Was sind Bugs? Das ist ein englisches Wort und heisst "Wanze". Diese kleinen Tierchen, die man ev. als Bettgenossen in einem nicht allzu reinlichen Hotel hat. Ich kann die Geschichte nicht mehr genau wiedergeben, aber so ungefähr. Die ersten Computer der Geschichte in den Jahren 1945 bis 1955, hatten als elektrische Einheiten zum Speichern von Zahlen (die Programme wurden auf Lochstreifen gestanzt) s.g. elektromechanische Relais. Das waren mechanische Schalter, die elektrisch umgeschaltet werden konnten. Vorausgesetzt, nichts Störendes kam in die Schaltwippe. Die Wanzen, die damals hier und da durch die riesigen Anlagen krabbelten, wussten davon aber nichts, krochen dazwischen, wurden vom Relais erschlagen, das Relais hängte und der Millionen-Dollar-Computer ging wegen der kleinen Wanze nicht mehr. Dann mussten die Techniker solange suchen, bis sie die Wanze in einem der Tausenden von Relais gefunden hatten.

Ich glaube, es war John v. Neumann, der eine recht smarte Mathematikerin eingestellt hatte, die ihm beim Betrieb einer dieser ersten Ungetüme, ENIAC oder EDSAC, half. Als er einmal zu ihr kam und sie mitten in der Maschine herumkroch und er fragte, was sie da tue, antwortete sie trocken: "I'm debugging the machine." (Ich entwanze die Anlage). Damit hatte sie ein neues Wort geboren. "Debugging". So, wie sich mit der Zeit das Wort "Bug" für Fehler im Computer und dann auch in den Programmen (in der "Software") verbreitete, so verbreitete sich für die Tätigkeit der Fehlersuche das Wort "Debugging". Jedesmal, wenn wir also in unserem Programm einen Bug suchen, (und das kann genauso mühsam sein, wie in Tausenden von Relais die geschichtsträchtige Wanze zu suchen), "debuggen" wir. Es gibt bei grösseren Programmiersystemen wie dem C16 sogar extra Funktionalitäten und Programme, um diese Suche zu erleichtern. Das sind dann "Debugger".

Zurück zu den Arrays: Mehr Dimensionen

Man kann ja Arrays mit Tabellen vergleichen. Mit Tabellen, die mehrere Zeilen, aber nur eine Spalte haben. Jedes Element ist eine Zelle der Tabelle. Gibt es auch mehrspaltige Tabellen? Ja, die kann der C16 auch. DIM X(10,5) wäre eine solche mehrspaltige Tabelle mit 10 Zeilen und 5 Spalten. Der Zugriff ist ganz analog zum eindimensionalen Array. Es gibt sogar dreidimensionale Arrays, was dreidimenionalen Tabellen entsprechen würde: DIM X(10,5,8) z.B.. Wieviel Dimensionen der C16 beherrscht, weiss ich gar nicht. Aber es kommt selten vor in der Programmierung, dass man mehr als drei Dimenionen braucht.

Ein Anwendungsbeispiel bringe ich hier nun nicht, da im nächsten Kapitel ein ausführliches Programm unter Verwendung eines zweidimensionalen Arrays kommt.