Informatik für Ingenieure und Naturwissenschaftler : eine anschauliche Einführung in das Programmieren mit C und Java 3540262431, 9783540262435

Das Lehrbuch lehnt sich an die erfolgreiche Mathematik für Ingenieure" desselben Autors an.Es führt zunächst in die

541 25 2MB

German Pages 369 Year 2006

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Informatik für Ingenieure und Naturwissenschaftler : eine anschauliche Einführung in das Programmieren mit C und Java
 3540262431, 9783540262435

Citation preview

Springer-Lehrbuch

Thomas Rießinger

Informatik für Ingenieure und Naturwissenschaftler Eine anschauliche Einführung in das Programmieren mit C und Java

Mit 64 Abbildungen

123

Thomas Rießinger Fachhochschule Frankfurt Fachbereich Informatik Kleiststr. 3 60318 Frankfurt am Main Deutschland [email protected]

Bibliografische Information der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.

ISBN-10 3-540-26243-1 Springer Berlin Heidelberg New York ISBN-13 978-3-540-26243-5 Springer Berlin Heidelberg New York Dieses Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere die der Übersetzung, des Nachdrucks, des Vortrags, der Entnahme von Abbildungen und Tabellen, der Funksendung, der Mikroverfilmung oder der Vervielfältigung auf anderen Wegen und der Speicherung in Datenverarbeitungsanlagen, bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Eine Vervielfältigung dieses Werkes oder von Teilen dieses Werkes ist auch im Einzelfall nur in den Grenzen der gesetzlichen Bestimmungen des Urheberrechtsgesetzes der Bundesrepublik Deutschland vom 9. September 1965 in der jeweils geltenden Fassung zulässig. Sie ist grundsätzlich vergütungspflichtig. Zuwiderhandlungen unterliegen den Strafbestimmungen des Urheberrechtsgesetzes. Springer ist ein Unternehmen von Springer Science+Business Media springer.de © Springer-Verlag Berlin Heidelberg 2006 Printed in Germany Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, daß solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Sollte in diesem Werk direkt oder indirekt auf Gesetze, Vorschriften oder Richtlinien (z. B. DIN, VDI, VDE) Bezug genommen oder aus ihnen zitiert worden sein, so kann der Verlag keine Gewähr für die Richtigkeit, Vollständigkeit oder Aktualität übernehmen. Es empfiehlt sich, gegebenenfalls für die eigenen Arbeiten die vollständigen Vorschriften oder Richtlinien in der jeweils gültigen Fassung hinzuziehen. Satz: Digitale Druckvorlage des Autors Herstellung: LE-TEX Jelonek, Schmidt & Vöckler GbR, Leipzig Umschlaggestaltung: design & production GmbH, Heidelberg Gedruckt auf säurefreiem Papier

7/3142/YL - 5 4 3 2 1 0

Vorwort Vergessen musst du das, was bisher du gelernt.“ ” Meister Yoda, Jedi-Ritter

Nicht jeder ist zum Jedi-Ritter geboren, die meisten Menschen ergreifen allt¨ aglichere Berufe. Wenn man genauer hinsieht, dann ist das auch sehr verst¨ andlich, denn sowohl das Berufsbild als auch der Ausbildungsgang der Jedi-Ritter machen doch einen etwas eigenartigen Eindruck. Was treibt so ein Ritter den ganzen Tag? Man kann schließlich nicht immer nur mit dem Lichtschwert herumfuchteln, allen m¨oglichen Leuten w¨ unschen, dass die Macht mit ihnen sein m¨ oge, und ansonsten bedeutungsschwere S¨ atze mit einer seltsamen Satzstellung murmeln - ganz zu schweigen von der Frage, nach welchem Tarif ein Jedi-Ritter wohl bezahlt werden mag. Und auch u ¨ber die Ausbildung eines Jedi weiß man wenig. Soll er wirklich alles vergessen, was er je gelernt hat, einschließlich seiner Sprache und seiner Tischmanieren? Ich will es nicht hoffen, zumal die Jedi-Ausbildung sehr lange dauert und man somit viel Gelegenheit hat, vieles zu vergessen. Ihre Lage ist dagegen viel angenehmer. Als angehender Ingenieur oder Naturwissenschaftler haben Sie eine deutlichere Vorstellung von Ihrem zuk¨ unftigen Beruf, als sie ein angehender Jedi-Ritter haben kann, wenn man auch nicht ausschließen darf, dass Sie in ferner Zukunft einmal ein Lichtschwert konstruieren werden. Und was Ihre Ausbildung betrifft, so ist ihr Aufbau ziemlich klar, und Sie k¨ onnen Ihrer Studienordnung entnehmen, was darin vorkommen ¨ wird - sicher keine Schwertk¨ampfe und keine Ubungen in der Anwendung der geheimnisvollen Macht, daf¨ ur eher handfeste Dinge wie Mathematik, Physik und eben auch Informatik. Trotzdem gibt es die eine oder andere Gemeinsamkeit zwischen Ihnen und einem auszubildenden Jedi. So wenig auch u ¨ber die Ausbildung der Jedi bekannt ist: dass sie sich immer und immer wieder in Geduld und Konzentration u ussen, ist unbestritten. Das ist kein Nachteil, weder f¨ ur einen Jedi noch ¨ben m¨ f¨ ur einen Nicht-Jedi, denn ob man es nun mit der dunklen Seite der Macht aufnehmen muss oder mit einem schlichten C-Compiler - f¨ ur beides braucht man viel Geduld und bei beidem muss man sich nicht wenig konzentrieren. Gerade wenn es um Informatik geht, kann ich Ihnen diese beiden Jedi-Eigenschaften sehr empfehlen. Sie werden in diesem Buch zun¨ achst mit einigen allgemei-

VI

Vorwort

nen Grundlagen der Informatik Bekanntschaft machen und dabei feststellen, dass auch das nicht ganz ohne Konzentration und Geduld funktioniert. So richtig geht es aber erst nach den Grundlagen los, sobald Sie beginnen, Ihre ersten Programme zu schreiben. Programmieren kann man nicht einfach so im Vorbeigehen, sozusagen durch Fingerschnippen, sondern nur, indem man sich erstens mit den Eigenarten der jeweiligen Programmiersprache vertraut macht und zweitens in aller Ruhe versucht, sein Problem mithilfe eines Programms zu l¨osen. Das wird nicht immer sofort gut gehen, das wird sogar ziemlich h¨aufig erst mal schief gehen: machen Sie sich nichts daraus, das ist v¨ollig normal und passiert jedem. Genau das ist der Grund f¨ ur die Gemeinsamkeiten zwischen angehenden Jedi-Rittern und Ihnen. Die Geduld brauchen Sie zwar aus anderen Gr¨ unden, Ihre Konzentration richtet sich auf andere Probleme, aber dringend n¨ otig sind sie beide, sowohl f¨ ur den Jedi als auch f¨ ur Sie. Ich werde Ihnen hier zwei Programmiersprachen zeigen, die prozedurale Sprache C und die objektorientierte Sprache Java. Voraussetzen werde ich so gut wie gar nichts, abgesehen von den Grundrechenarten und Ihrer Bereitschaft, sich an diesen beiden Sprachen zu versuchen. Aber wie lernt man denn nun Programmieren, egal in welcher Sprache? Ganz einfach: durch Programmieren. Nat¨ urlich k¨ onnen Sie nicht einfach so damit anfangen, sondern m¨ ussen erst einmal lesen, wie man das macht, aber Lesen alleine wird nicht ausreichen. Um sich an das Programmieren und an die eingesetzten Programmiersprachen zu gew¨ ohnen, bleibt Ihnen nichts anderes u brig als zu u ben, nicht nur einmal oder zweimal, sondern oft und ¨ ¨ immer wieder - die Rolle von Geduld und Konzentration sollte man dabei nicht ¨ untersch¨atzen. Deshalb enth¨ alt das Buch auch zu jedem Abschnitt Ubungsaufgaben, an denen Sie das, was Sie gelernt haben, ausprobieren k¨ onnen. Und was sollen Sie machen, wenn Sie mit einer Aufgabe trotz allem nicht zurechtkommen? Nicht weiter schlimm, das kann passieren, auch daf¨ ur ist vorgesorgt. Die L¨osungen der eher theoretischen Aufgaben habe ich am Ende des Buches aufgeschrieben, damit Sie sie nachlesen und mit Ihren eigenen L¨ osungen vergleichen k¨ onnen. Bei den Programmieraufgaben erschien mir das aber weniger sinnvoll, denn auch Programme, mit denen Sie selbst vielleicht Probleme hatten, sollten Ihnen in einer Form zur Verf¨ ugung stehen, die das Arbeiten mit den Programmen erlaubt. Haben Sie gesteigerte Lust, Programmtexte abzutippen? Nein? Dachte ich mir. Deshalb finden Sie die L¨ osungen aller Programmieraufgaben unter der im L¨osungsteil angegebenen Webadresse, von wo Sie sie herunterladen k¨ onnen. F¨ ur den Moment habe ich wohl genug geredet, also beenden wir die Ansprache und fangen einfach an.

Frankfurt, Juni 2005

Thomas Rießinger

Inhaltsverzeichnis

1

Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 Einf¨ uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.1 Das EVA-Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.2 Unterschiede zwischen maschineller und manueller Datenverarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.3 Aufgabenbereiche des Computers . . . . . . . . . . . . . . . . . . . . 1.1.4 Computertypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.5 Informatik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Wie alles begann . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 Der Abakus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.2 Mechanische Rechenmaschinen . . . . . . . . . . . . . . . . . . . . . . 1.2.3 Die Lochkartenmaschine von Hollerith . . . . . . . . . . . . . . . 1.2.4 Die Analytische Maschine von Babbage . . . . . . . . . . . . . . 1.2.5 Elektromechanische und elektronische Rechner . . . . . . . . 1.2.6 Programmiersprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Rechneraufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.1 Der Aufbau eines Taschenrechners . . . . . . . . . . . . . . . . . . . 1.3.2 Die Architektur eines von Neumann-Rechners . . . . . . . . . 1.3.3 Arbeitsspeicher und Festplattenspeicher . . . . . . . . . . . . . . 1.4 Bin¨ are Arithmetik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.1 Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.2 Bin¨arzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.3 Umrechnungsverfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.4 Addition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.5 Subtraktion und Zweierkomplement . . . . . . . . . . . . . . . . . 1.4.6 Multiplikation und Division . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.7 Computerorientierte Umrechnungsverfahren . . . . . . . . . . 1.4.8 Hexadezimalzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5 Logische Schaltungen und Addierer . . . . . . . . . . . . . . . . . . . . . . . . 1.5.1 Die UND-Schaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5.2 Die ODER-Schaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 2 2 5 6 8 10 11 11 12 14 17 18 22 25 25 28 31 36 37 38 41 47 49 54 56 59 61 62 64

VIII

Inhaltsverzeichnis

1.5.3 1.5.4 1.5.5 1.5.6

Die NICHT-Schaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Halbaddierer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Volladdierer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Negative Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

66 66 70 76

2

Strukturierte Programmierung mit C . . . . . . . . . . . . . . . . . . . . . . 81 2.1 Einf¨ uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 2.1.1 Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 2.1.2 Programmiersprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 2.1.3 Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 2.2 Erste C-Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 2.2.1 Die Entwicklung von C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 2.2.2 Ein erstes Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 2.2.3 Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 2.2.4 Eingabe von der Tastatur . . . . . . . . . . . . . . . . . . . . . . . . . . 99 2.2.5 Arithmetik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 2.2.6 Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 2.3 Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 2.3.1 Sequenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 2.3.2 Auswahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 2.3.3 Wiederholung als nicht abweisende Schleife . . . . . . . . . . . 127 2.3.4 Wiederholung als abweisende Schleife . . . . . . . . . . . . . . . . 133 2.3.5 Wiederholung als Z¨ahlschleife . . . . . . . . . . . . . . . . . . . . . . . 136 2.3.6 Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 2.3.7 continue und break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 2.4 Zeiger und dynamische Datenstrukturen . . . . . . . . . . . . . . . . . . . . 152 2.4.1 Zeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 2.4.2 Noch einmal Felder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 2.4.3 Verkettete Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 2.5 Funktionen und Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 2.5.1 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 2.5.2 Vordefinierte Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 2.5.3 call by value und call by reference . . . . . . . . . . . . . . . . . . . 187 2.5.4 Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 2.6 Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195

3

Objektorientierte Programmierung mit Java . . . . . . . . . . . . . . . 201 3.1 Strukturierte Programmierung mit Java . . . . . . . . . . . . . . . . . . . . 203 3.1.1 Die Entwicklung von Java . . . . . . . . . . . . . . . . . . . . . . . . . . 203 3.1.2 Compiler und Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 3.1.3 Erste Schritte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 3.1.4 Standardeingabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 3.1.5 Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 3.2 Klassen, Objekte und Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 3.2.1 Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224

Inhaltsverzeichnis

3.3

3.4

3.5

3.6

3.7

4

IX

3.2.2 Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 3.2.3 Konstruktoren und set/get-Methoden . . . . . . . . . . . . . . . . 232 ¨ 3.2.4 Uberladen von Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 3.2.5 Felder und Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 3.2.6 H¨ ullenklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 3.2.7 Zeiger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 3.2.8 Die Klasse String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 3.2.9 Methodenaufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 3.2.10 Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 3.3.1 Erweitern einer Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 3.3.2 Konstruktoren und Zuweisungen . . . . . . . . . . . . . . . . . . . . 259 3.3.3 Zugriffsattribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 ¨ 3.3.4 Uberschreiben von Methoden und Polymorphie . . . . . . . . 263 3.3.5 Erweitern von Subklassen . . . . . . . . . . . . . . . . . . . . . . . . . . 268 3.3.6 Die Klasse Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 3.3.7 Innere Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270 Abstrakte Klassen und Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . 276 3.4.1 Abstrakte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 3.4.2 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283 3.4.3 Adapterklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 3.4.4 Anonyme Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 Ausnahmebehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 3.5.1 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 3.5.2 Ausl¨ osen von Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 296 3.5.3 Abfangen von Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 299 3.5.4 Selbstdefinierte Exceptionklassen . . . . . . . . . . . . . . . . . . . . 303 3.5.5 Finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 3.5.6 Exceptionklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306 Dateien und Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308 3.6.1 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 3.6.2 Textdateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 3.6.3 Datendateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 3.6.4 Objektdateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 3.6.5 Die Standardstreams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321 Ein wenig u ¨ber GUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324 3.7.1 Ereignisbasierte Programmierung . . . . . . . . . . . . . . . . . . . . 324 3.7.2 Ein erstes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 3.7.3 Schaltfl¨achen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332

L¨ osungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339 4.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339 4.2 Strukturierte Programmierung mit C . . . . . . . . . . . . . . . . . . . . . . 347 4.3 Objektorientierte Programmierung mit Java . . . . . . . . . . . . . . . . 353

X

Inhaltsverzeichnis

Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355 Sachverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357

1 Grundlagen Schwer zu sehen; in st¨ andiger Bewegung ist die ” Zukunft.“ Meister Yoda, Jedi-Ritter

Wenn Sie sich mit Informatik besch¨aftigen wollen oder es sogar m¨ ussen, weil der Studienplan es nun mal vorsieht, dann sollten Sie eine Vorstellung davon haben, was so ein Computer kann und was er nicht kann, und Sie sollten ihn weder u ¨ber- noch untersch¨atzen. Beides habe ich allerdings immer wieder erlebt. Das vielleicht interessanteste Beispiel hat mir vor einigen Jahren eine Informatikstudentin des ersten Semesters geliefert, die sich mit der Aufgabe plagte, die bekannte und beliebte p, q-Formel zur L¨ osung quadratischer Gleichungen in der Programmiersprache Pascal zu programmieren. Nun will ich Ihnen auf der ersten Seite dieses Buchs nicht gleich mit Formeln auf die Nerven fallen und nur sagen, dass man bei dieser Formel aus den beiden gegebenen Werten p und q die beiden L¨osungen einer quadratischen Gleichung ausrechnen kann. Das zu programmieren hat die Studentin dann auch versucht; ihr Programm sorgte daf¨ ur, dass der Benutzer die Zahlen p und q eingab, das war in Ordnung. Und dann ließ sie den armen Benutzer auch gleich noch die L¨osung eingeben und war der Meinung, das Problem w¨ are jetzt gel¨ ost. Vielleicht haben Sie schon einmal das Wort Softwarekrise“ geh¨ ort, da” mit bezeichnet man eine Phase etwa in den sechziger Jahren des zwanzigsten Jahrhunderts, in der die Informatiker merkten, dass planloses Vor-sichhin-Programmieren ziemlich schnell ins Chaos f¨ uhrt und man etwas koordinierter vorgehen sollte. Diese Softwarekrise fand ihre endg¨ ultige L¨ osung in dem dreizeiligen Programm meiner Studentin, denn wenn man das gew¨ unschte Ergebnis gleich selbst eingibt, dann kann das Programm selbst nichts mehr falsch machen... Aber wieder im Ernst: indem sie die M¨ oglichkeiten des Computers gleichzeitig untersch¨atzte und u atzte, hatte sie einen ¨bersch¨ doppelten Anf¨ angerfehler gemacht, den ich Ihnen gerne ersparen m¨ ochte. Die ¨ Ubersch¨ atzung lag darin, dass ihr nicht klar war, wie genau man dem Rechner mitteilen muss, was er im Einzelnen zu tun hat - wenn Sie dem Computer kein genaues Rechenverfahren angeben, wird er entweder gar nichts oder nur Unsinn ausrechnen. Und weil die geplagte Studentin anscheinend ihrem eigenen Optimismus dann doch nicht mehr so traute, ging sie gleich zu einer folgenschweren Untersch¨atzung u ¨ber: Man kann ja u ¨ber Computer denken,

2

1 Grundlagen

was man will, aber wenn man ihnen die Ergebnisse selbst eingeben m¨ usste, ¨ k¨onnte man sich auch den Strom sparen, mit dem man sie betreibt. Ubrigens hatte sie dann auch noch vergessen, die Ergebnisse am Bildschirm auszugeben, aber ich will hier nicht kleinlich werden. So etwas findet man h¨ aufiger, vom Studenten des ersten Semesters bis hin zum Chefprogrammierer einer großen Firma. Der Grund f¨ ur solche Fehleinsch¨atzungen liegt wohl oft darin, dass die Leute nichts u ¨ber das Innenleben des Computers wissen und keine Vorstellung davon haben, wie dieses seltsame Ding funktioniert. Damit Ihnen nicht das Gleiche passiert, will ich in diesem Kapitel u uber ¨ber genau solche Fragen reden. Zuerst sollten wir uns dar¨ verst¨andigen, was man eigentlich mit Computern anstellt und wie man davon ausgehend den Begriff Informatik verstehen kann. Dann werde ich Ihnen ein wenig dar¨ uber erz¨ahlen, wie sich die Datenverarbeitung im Allgemeinen und die Rechner im Besonderen im Lauf der Zeit entwickelt haben. Daraus werden sich dann schon erste Folgerungen dar¨ uber ergeben, wie ein Rechner u ¨blicherweise aufgebaut ist. Was nun im Inneren des Rechners passiert, wie er wirklich rechnet und mit seinen Daten umgeht, das werde ich Ihnen nach ein paar Bemerkungen zum Aufbau eines Rechners erz¨ ahlen. Diese Informationen u ¨ber die interne Wirkungsweise finden dann ihre Anwendung in der Konstruktion eines so genannten Volladdierers, der Ihnen zeigen wird, welche Schaltungen man vornehmen muss, um Additionen durchf¨ uhren zu k¨ onnen. Genug der Vorrede, fangen wir an.

1.1 Einfu ¨ hrung Welche Unterschiede zwischen von Menschen durchgef¨ uhrter und maschinell erledigter Datenverarbeitung bestehen, macht man sich am besten an einem Beispiel klar. 1.1.1 Das EVA-Prinzip Vermutlich hat jeder von Ihnen schon einmal irgendwo gegen Bezahlung gearbeitet und Sie wissen, dass Ihr Lohn nicht einfach so bar auf die Hand ausgezahlt wird, obwohl das im Hinblick auf die Steuern sicher gewisse Vorteile h¨atte. Nat¨ urlich m¨ ussen Ihre Gehaltszahlungen u ¨ber die Lohnbuchhaltung erledigt werden, und die hat in jedem Unternehmen mehr oder weniger die gleichen Abl¨aufe. Gehen wir einmal davon aus, dass Ihr Arbeitgeber eher altmodisch orientiert ist und dieses Teufelszeug namens Computer nicht in seiner Firma duldet. Trotzdem wird man die Daten Ihrer Arbeitszeiten irgendwie erfassen m¨ ussen; das kann zum Beispiel geschehen, indem man die Stempelkarten der abgelaufenen Woche durcharbeitet, auf denen Sie und Ihre Kollegen die jeweiligen t¨ aglichen Zeiten angegeben haben. Falls es keine Zeiterfassung dieser Art gibt, wird irgendjemand die Anwesenheitslisten durchgehen m¨ ussen, um Ihre An- und Abwesenheitsdaten festzustellen, denn Sie

1.1 Einf¨ uhrung

3

k¨onnten ja w¨ahrend der Arbeitszeit auch im Freibad gewesen sein. Damit aus diesen Daten am Ende klingende M¨ unze erzeugt werden kann, wird dann der zust¨andige Sachbearbeiter die Anzahl Ihrer Anwesenheitsstunden in seinen Tischrechner eingeben. All diese Aktivit¨ aten sortiere ich unter dem Oberbegriff Eingabe ein: die vorhandenen Daten werden gesammelt und in den Tischrechner eingegeben, gemacht wird damit erst mal gar nichts. Vom puren Eingeben bekommen Sie aber noch keinen Lohn und das Finanzamt keine Steuern. Deshalb werden jetzt die eingegebenen Daten von Hand verarbeitet, da die Unternehmensleitung immer noch keinen Computer anschaffen wollte. Das muss man k¨onnen, nicht jeder kennt sich in den Regeln und Vorschriften der Lohnbuchhaltung aus, die unter Umst¨ anden auch noch durch spezielle Firmenregelungen erg¨anzt werden und nat¨ urlich auch zu den Vorstellungen des Finanzamtes passen m¨ ussen. Der Sachbearbeiter, vermutlich ein gelernter Lohnbuchhalter, berechnet also den Bruttolohn aus ¨ den eingegebenen Arbeitsstunden, dem Stundenlohn und eventuellen Uberstundenzuschl¨agen und geht dann zur Nettoberechnung u uge ¨ber, um die Abz¨ auszurechnen. Davon gibt es, wie Sie wahrscheinlich schon schmerzlich erfahren haben, eine ganze Menge, und zwar in Form von Steuern und Sozialversicherungsbeitr¨agen. Vielleicht haben Sie auch noch verm¨ ogenswirksame Leistungen oder gar eine Lohnpf¨andung am Hals, und auch diese sonstigen Abz¨ uge wird der Lohnbuchhalter ausrechnen, vermutlich mit seinem Tischrechner. Der kl¨agliche Rest, der nach dem Abzug der Abz¨ uge vom Bruttolohn u brigbleibt, ist dann Ihr Nettolohn, der hoffentlich noch ausreicht, um die ¨ Fahrtkosten zur Arbeit zu decken. Das alles kann der Lohnbuchhalter aber nicht aus der hohlen Hand erledigen. Er braucht dazu Informationen, die er in irgendwelchen Handb¨ uchern oder a¨hnlichen Unterlagen abgelegt hat, wie beispielsweise die Lohnsteuers¨atze, die Sozialversicherungss¨ atze oder auch Daten aus der Personalkartei u ber Ihren Familienstand und die Anzahl Ihrer Kinder. Die Aktivit¨ aten ¨ unseres Buchhalters bezeichnet man als Verarbeitung. Sobald nun alles verarbeitet ist, muss man mit den ermittelten Ergebnisse auch noch etwas anfangen, sonst k¨ onnte man sich die ganze Rechnerei sparen. Damit Sie Ihr Geld bekommen, muss eine Kassenanweisung geschrieben werden, f¨ ur Ihre Lohnmitteilung muss ein entsprechender Beleg an den Postausgang gehen, auch das Finanzamt wird etwas u ¨ber den Vorgang wissen wollen und die Sozialversicherungskassen sicher auch. Mit anderen Worten: die Ergebnisse der Verarbeitung werden auf verschiedenen Medien ausgegeben, und deshalb nennt man diesen Schritt des Prozesses auch die Ausgabe. Und somit habe ich auch schon ein kleines Schema erstellt, mit dem ich die manuelle Datenverarbeitung beschreiben kann, n¨ amlich Eingabe → Verarbeitung → Ausgabe. Die Situation ver¨adert sich, wenn der Chef sich endlich einen Ruck gibt und den einen oder anderen Computer einschließlich der n¨ otigen Programme anschafft - vornehm ausgedr¨ uckt spricht man allerdings nicht von Programmen, sondern von Software. Wie sieht nun die Lohnbuchhaltung aus? Die An-

4

1 Grundlagen

wesenheitsdaten der Mitarbeiter werden jetzt vermutlich maschinell erfasst, zum Beispiel von Eingabestationen am Eingangstor zum Firmengel¨ ande, und automatisch an einen zentralen Computer weitergegeben. Das kann vollautomatisch geschehen, sofern die Eingabestationen mit dem Zentralrechner irgendwie verbunden sind; in diesem Fall spricht man von Vernetzung. Es kann aber auch noch menschliche Hilfe gebraucht werden, indem ein Mitarbeiter die Daten von der Eingabestation holt und dann zu Fuß zum Computer tr¨ agt - das h¨angt von der eingesetzten Technik ab. In jedem Fall ist der erste Schritt der Lohnbuchhaltung die Eingabe der n¨ otigen Daten. Sind die Daten erst einmal im Computer angekommen, muss er mit ihnen arbeiten, also die n¨ otigen Verarbeitungsschritte vornehmen. Die Regeln und Vorschriften, nach denen der Lohnbuchhalter in der guten alten Zeit gearbeitet hat, stecken jetzt in den Anweisungen des verwendeten Programms, in der Software, und dieses Programm arbeitet mit zwei verschiedenen Datenarten: erstens mit den Daten, die aus der Zeiterfassung in den Rechner u ¨bertragen wurden, und zweitens mit den zus¨atzlichen Daten, die auch der Lohnbuchhalter gebraucht hat, n¨ amlich ¨ mit den Steuers¨atzen und Ahnlichem mehr. Da ein Computer schwerlich in den Steuertabellen nachsehen kann, sind diese Daten auf der Festplatte des Rechners abgespeichert, sodass er jederzeit auf sie zugreifen kann. W¨ ahrend der eigentlichen Berechnung, in der wieder Bruttolohn, Abz¨ uge und Nettolohn ausgerechnet werden, kommen sowohl Ihre Anwesenheitsdaten als auch die f¨ ur Ihren Fall n¨otigen auf der Festplatte vorr¨ atigen Daten in den aktuellen Arbeitsspeicher des Rechners, damit er, wenn er nun schon mal Rechner heißt, auch alles Gew¨ unschte ausrechnen kann. Die Rolle des Tischrechners, den noch der Lohnbuchhalter verwendet hatte, spielt dabei das Rechenwerk des Computers. Sie werden zugeben, dass die Aktivit¨aten des letzten Absatzes sicher die Bezeichnung Verarbeitung verdienen. Der n¨achste Schritt ist wenig u ¨berraschend. Die berechneten Ergebnisse m¨ ussen wieder den verschiedenen beteiligten Stellen zur Verf¨ ugung gestellt werden, indem man Ausdrucke macht, Bildschirmausgaben ansieht oder u ¨ber eine Netzwerkverbindung direkt die Ergebnisse an einen anderen Rechner weitergibt. Also ergibt sich als letzter Schritt der computergest¨ utzten Datenverarbeitung wieder die Ausgabe. Sehen Sie den prinzipiellen Unterschied zwischen der manuellen und der maschinellen Datenverarbeitung, wie ich sie hier beschrieben habe? Nein? Kein Wunder, es ist ja auch keiner da. Beide funktionieren nach dem gleichen Prinzip: Zuerst werden Daten eingegeben, dann werden sie nach bestimmten Regeln und Methoden verarbeitet, wobei unter Umst¨ anden noch weitere Daten herangezogen werden, und schließlich werden die Ergebnisse ausgegeben. Das ist ein so grundlegendes Prinzip der gesamten Datenverarbeitung, dass es einen eigenen Namen bekommen hat. Man nennt es das EVA-Prinzip und fasst damit die Anfangsbuchstaben der drei Schl¨ usselw¨ orter Eingabe, Verarbeitung und Ausgabe in einer eing¨ angigen Abk¨ urzung zusammen. Da aber in der Informatik alles m¨oglichst auf Englisch formuliert sein muss, gibt es auch eine englische Variante des EVA-Prinzips. Sie m¨ ussen nur bedenken, dass Eingabe

1.1 Einf¨ uhrung

5

Input, Verarbeitung Process und Ausgabe Output heißt, und schon haben Sie alles zusammen, um auch die Abk¨ urzung IPO-Prinzip zu verstehen.

EVA-Prinzip Die Datenverarbeitung verl¨auft in aller Regel nach dem EVA-Prinzip, das heißt nach dem Prinzip Eingabe → Verarbeitung → Ausgabe, oder in englischer Sprache Input → Process → Output, weshalb man auch vom IPOPrinzip spricht. Kann es Ihnen also egal sein, ob Sie Ihre Datenverarbeitung maschinell oder manuell erledigen lassen, wenn doch das Prinzip immer das gleiche ist? Sicher nicht, denn auch bei gleichen Prinzipien gibt es doch in der Ausf¨ uhrung recht deutliche Unterschiede. 1.1.2 Unterschiede zwischen maschineller und manueller Datenverarbeitung Was jedem vielleicht zuerst einf¨allt, ist die unterschiedliche Geschwindigkeit. Das merken Sie schon, wenn Sie eine einfache Multiplikation schriftlich ausf¨ uhren oder mit Hilfe eines Taschenrechners und dabei die Zeit messen. Noch viel deutlicher wird das nat¨ urlich, wenn es um die Verarbeitung großer Datenmengen geht, also zum Beispiel um die Lohnbuchhaltung in einem großen Unternehmen. Die Routinearbeit des Datensuchens und Berechnens der Ergebnisse l¨asst sich mit einem Rechner sehr schnell durchf¨ uhren, von Hand w¨ are es fast eine Lebensaufgabe. Aber Vorsicht: der Geschwindigkeitsvorteil bezieht sich vor allem auf Routineaufgaben, sobald es um sehr spezielle Aufgaben geht, die ein gewisses Maß an Kreativit¨ at erfordern, kann es ein Mensch immer noch oft mit einem Computer aufnehmen. Etwas anders sieht es aus bei der Zuverl¨ assigkeit. Man muss leider zugeben, daß Computer in aller Regel deutlich zuverl¨ assiger sind als Menschen, auch wenn sie gelegentlich abst¨ urzen - aber das soll auch schon bei Menschen vorgekommen sein, vor allem am Wochenende. Sie k¨ onnen den Computer die immer gleichen Aufgaben so oft durchf¨ uhren lassen wie Sie wollen, er wird keine Erm¨ udungserscheinungen zeigen, keine Fl¨ uchtigkeitsfehler machen oder Sie durch bewusste Schlamperei ¨argern; er macht einfach immer das, was Sie ihm gesagt haben. Genau darin liegt allerdings auch ein Problem: wenn Sie der Maschine die falschen Regeln mitgeteilt, sie also falsch programmiert haben, dann wird sie nat¨ urlich auch treu und zuverl¨ assig die falschen Ergebnisse liefern. Zwar immer die gleichen, aber eben falsche. Man kann daf¨ ur aber nicht den Computer verantwortlich machen, der hat wie u ¨blich nur getan, was Sie ihm gesagt haben. Die Verantwortung liegt hier eindeutig bei dem Programmierer oder dem Anwender, der ein falsches Programm geschrieben oder falsche Eingabedaten verwendet hat. Aber damit so etwas nicht passiert, haben wir ja Sie und dieses Buch, aus dem Sie lernen sollen, wie man richtige Programme schreibt.

6

1 Grundlagen

Sie d¨ urfen nicht vergessen, dass es beim Computereinsatz auch um wirtschaftliche Fragen geht, und deshalb besteht ein wesentlicher Vorteil der maschinellen Datenverarbeitung in den geringeren Kosten, die sie verursachen. Die einmalige Anschaffung von Rechnern, verbunden mit der n¨ otigen Software, verursacht nat¨ urlich zun¨achst einmal Kosten, aber wenn sie erst mal da sind, dann sparen sie auch einiges ein. Die Maschine ist auf mittlere Sicht nun mal billiger als der menschliche Lohnbuchhalter, denn sie bezieht kein Gehalt und keine Lohnnebenkosten, ganz zu schweigen davon, dass man f¨ ur sie auch keine Kantine braucht. Das ist einerseits ein Vorteil, weil das Unternehmen Geld spart, andererseits aber auch ein Nachteil, weil auf diese Weise Arbeitspl¨atze verloren gehen - nicht immer kann man alles eindeutig bewerten.

Vorteile der maschinellen Datenverarbeitung Wesentliche Vorteile der maschinellen Datenverarbeitung im Vergleich zur manuellen Datenverarbeitung sind • h¨ ohere Geschwindigkeit • h¨ ohere Zuverl¨assigkeit • geringere Kosten Sie sehen also, es ist sinnvoll, bei Aufgaben der Datenverarbeitung auf einen Computer zur¨ uckzugreifen, und daher sollten wir einen Blick auf die Aufgabenfelder werfen, f¨ ur die so ein Rechner eingesetzt werden kann. 1.1.3 Aufgabenbereiche des Computers Wenn man ihn schon als einen Rechner bezeichnet, dann sollte zu seinen grundlegenden Aufgaben sicher das Rechnen geh¨ oren, genauer gesagt die Ausf¨ uhrung von Berechnungen. Das k¨onnen Berechnungen verschiedenster Art sein, beispielsweise die Berechnung des Nettolohns aus dem Bruttolohn und den Randbedingungen, u ¨ber die ich vorhin gesprochen habe. Solche Berechnungen k¨ onnte auch jeder Lohnbuchhalter durchf¨ uhren; man braucht hier den Computer nicht deshalb, weil die Berechnungen so kompliziert sind, dass das kein Mensch mehr hinbekommt, sondern weil zu oft die gleichen recht einfachen Dinge getan werden m¨ ussen. Anders sieht das aus bei bestimmten technischen oder physikalischen Problemen, bei denen keine Massendaten verarbeitet werden, sondern ausgesprochen komplizierte Rechenverfahren abgearbeitet werden m¨ ussen, f¨ ur die ein noch so begabter Mensch mehr Zeit br¨ auchte als ein Leben hergibt. Diese Verfahren lassen sich in einem Programm beschreiben, und der Computer kann mit etwas Gl¨ uck auf Grund seiner hohen Geschwindigkeit die n¨otigen Berechnungen vornehmen. Rechnen ist nicht alles im Leben. Denken Sie wieder einmal an die maschinell durchgef¨ uhrte Lohnbuchhaltung, die am Ende, nachdem alles gerechnet ist, irgendwelche Ergebnisse liefert. Nun kann es ja sein, dass die Steuerpr¨ ufung Ihrer Firma auf den Zahn f¨ uhlen und Ihre Abrechnungen u ufen ¨berpr¨

1.1 Einf¨ uhrung

7

will, und zu diesem Zweck m¨ ussen die Ergebnisse gespeichert sein. Das kann man auf dem Papier machen, aber das kostet eine Unmenge Platz, weshalb man die Speicherung großer Datenmengen oft und gerne maschinell vornimmt, also mit Computerunterst¨ utzung. Auch hier gibt es verschiedene M¨ oglichkeiten. Es kann sich um langfristig anzulegende Daten handeln, die wird man auf einer bestimmten Festplatte ablegen, die Platte in irgendeinen Schrank stellen und hoffen, sie notfalls wieder zu finden. Dazu geh¨ oren zum Beispiel Reproduktionen von alten Manuskripten oder Bildern, die man der Nachwelt erhalten will. Wenn Sie aber von Frankfurt nach Teneriffa fliegen wollen und eine Flugbuchung vorgenommen haben, dann k¨ onnen Sie mit einem gewissen Recht erwarten, dass die Angestellten beim Einchecken nicht erst die richtige Festplatte suchen, solche Daten m¨ ussen nat¨ urlich sofort verf¨ ugbar sein. Man verwendet also Computer auch, um Daten abzuspeichern, und zwar entweder langfristig oder direkt verf¨ ugbar. Vielleicht haben Sie schon darauf gewartet, dass ich endlich u ¨ber das Internet rede. Erwarten Sie nicht zu viel, das Internet wird hier ziemlich selten vorkommen, aber wenn es um die Grundaufgaben eines Computers geht, muss ich es schon mal erw¨ahnen. Es gibt ja nicht nur einen Computer auf der Welt, und wenn man die Leistungen und Informationen dieser Computer mitein¨ ander verbinden will, dann spricht man von Computernetzwerken. Uber das Internet hat man heute einen Riesenverbund von Rechnern zur Verf¨ ugung, auf die eine Riesenmenge von Benutzern zugreifen kann, und das regelt sich nicht von alleine. Um diese Kommunikation zwischen den Computern zu steuern, brauchen Sie selbstverst¨andlich wieder Computer, deren Aufgabe es ist, die ¨ Ubermittlung der gew¨ unschten Informationen von Ort zu Ort zu steuern. Und damit habe ich schon das n¨achste Schl¨ usselwort gefunden: Steuerung und, was fast immer dazugeh¨ ort, Kontrolle. Nicht nur das Internet und die allgemeine Datenkommunikation m¨ ussen gesteuert werden, meine Waschmaschine und mein Auto auch. Auch das u ¨bernehmen oft genug Computer, ob Navigationssysteme oder Waschmaschinensteuerung, ob Autopiloten im Flugzeug oder Steuerungsger¨ate f¨ ur Produktionsroboter, sie alle haben die Aufgabe, Ger¨ate zu steuern und zu kontrollieren. Jetzt sind wir so weit, eine Definition des Begriffs Computer“ geben zu ” k¨onnen, den ich schon so oft benutzt habe. Mein Lexikon gibt beispielsweise die eher einfache Definition, ein Computer sei eine elektronische Datenverarbeitungsanlage, die heute in Wissenschaft, Wirtschaft, Verwaltung und Technik eine entscheidende Rolle spiele. Das ist fein beobachtet, aber doch ein wenig schwammig. Auch den Computer einfach nur als Rechner zu bezeichnen und damit auszusagen, dass man nur mit ihm rechnet, ist zu d¨ unn. Ich verwende deshalb die eben ermittelten Grundfunktionen und definiere einen Computer durch die Aufgaben, f¨ ur die er da ist.

Computer Ein Computer ist ein Ger¨at, das Daten speichert und verarbeitet, das auf

8

1 Grundlagen

dieser Basis Berechnungen durchf¨ uhrt, andere Ger¨ ate steuern kann und das mit anderen Ger¨aten und mit Menschen in Verbindung treten kann. Es wird nichts dar¨ uber gesagt, dass es sich unbedingt um ein elektronisches Ger¨ at handeln muss. Theoretisch kann man sich einen Computer ohne elektrischen Strom auf der Basis fließenden Wassers vorstellen, wenn ich auch zugeben muss, dass man von hydraulischen Computern selten etwas h¨ ort. Wenn ich also von einem Computer rede, dann meine ich nat¨ urlich in aller Regel ein elektronisches Ger¨at, das der obigen Definition gen¨ ugt. 1.1.4 Computertypen Computer gibt es nat¨ urlich in verschiedenen Ausf¨ uhrungen. Sie haben vermutlich einen PC zu Hause stehen, auf dem Sie Ihre Computerspiele laufen lassen und mit dem Sie hin und wieder arbeiten. Aber PCs sind nicht die einzigen Arten von Rechnern, sie stehen eigentlich recht weit unten in der Hierarchie, zumindest wenn man der Gr¨oße nach geht. Zuerst m¨ usste man da wohl den Mainframe-Computer nennen. Dabei handelt es sich um einen Großrechner, der eher als Hintergrundrechner arbeitet, auf den Sie also als Benutzer keinen direkten Zugriff haben, sondern beispielsweise mit Hilfe eines Rechnernetzes u onnen. Dass eine so große ¨ber andere Computer zugreifen k¨ Maschine f¨ ur nur einen Benutzer ziemlich sinnlos w¨ are, d¨ urfte klar sein, und daher ist eine wesentliche Eigenschaft eines Mainframe-Computers auch der sogenannte Mehrbenutzerbetrieb: mehrere Benutzer arbeiten gleichzeitig auf demselben Rechner, haben aber subjektiv das Gef¨ uhl, er w¨ are nur f¨ ur sie da. So etwas ist gerade bei vielen Benutzern nur bei sehr hoher Rechenleistung und bei großer Speicherkapazit¨at zu schaffen, und genau dadurch zeichnen sich Mainframe-Computer auch aus. Wie Sie sich leicht vorstellen k¨ onnen, kann man sie auf Grund ihres hohen Leistungsverm¨ ogens ausgezeichnet f¨ ur die Verarbeitung riesiger Datenmengen einsetzen und f¨ ur ausgesprochen komplexe Berechnungen einsetzen, bei denen ein handels¨ ublicher PC sich leise weinend verabschieden w¨ urde. Ob man die Supercomputer noch zu den besonders guten Mainframes rechnet oder ob sie schon eine h¨ohere Klasse darstellen, sieht jeder ein wenig anders. Uns kann das auch ziemlich egal sein, wichtig ist nur, dass sie die Leistungsf¨ ahigkeit der gew¨ohnlichen Mainframes deutlich u ¨bersteigen, indem sie so genannte Mehrprozessorsysteme einsetzen - kein sch¨ones Wort, aber gar nicht so schlimm. Der Prozessor ist der Teil des Computers, der die eigentlichen Verarbeitungen, die Berechnungen durchf¨ uhrt, und normalerweise hat eben ein Computer auch nur einen Prozessor. Wenn man es nun so einrichtet, dass er gleich mehrere Prozessoren hat, dann spricht man eben von einem Mehrprozessorsystem. Mit mehreren Prozessoren kann man auch mehrere Verarbeitungen gleichzeitig laufen lassen, genauso wie man auf einem Herd mit vier Herdplatten auch gleichzeitig vier T¨ opfe zum Kochen bringen kann. Man spricht daher auch von parallelen Systemen, was nur bedeutet,

1.1 Einf¨ uhrung

9

dass mehrere Dinge parallel, also gleichzeitig geschehen k¨ onnen. Allerdings erfordert eine Verarbeitung dieser Art Programmiertechniken, die weit u ¨ber das hinausgehen, was Sie hier lernen werden. Was man fr¨ uher mittlere Datentechnik nannte, bezeichnet man heute als Midrange-Systeme, aber beides ist ein wenig aus der Mode gekommen. Es handelt sich dabei um Computer, die sicher keinen Mainframe-Rechner darstellen, aber doch immerhin die gesamte Datenverarbeitung eines mittleren Unternehmens erledigen und etliche im ganzen Unternehmen verteilte Arbeitspl¨atze mit Rechenleistung versorgen k¨ onnen. Man k¨ onnte also sagen, ein Midrange-System ist der kleinere Bruder eines Mainframe-Computers. Heute neigt man eher dazu, anstelle eines solchen Systems die n¨ otige Zahl von PCs anzuschaffen und in einem Rechnernetzwerk zusammenzuschalten, wobei man an Stelle der PCs oft auch die leistungsf¨ ahigeren Workstations einsetzt, auf die ich gleich zu sprechen komme. Denn eine Workstation ist schon etwas mehr als ein simpler PC und ist auch entsprechend teurer. Sie ist nichts anderes als ein Computer, der eine hohe Rechenleistung an einem Arbeitsplatz konzentriert, deswegen heißt sie ja auch Workstation. Sie kann eine große Menge von Daten bew¨ altigen und ist meistens auch mit Graphikm¨oglichkeiten versehen, die das Anzeigen bunter Bilder, das Sie von Ihrem PC kennen, doch deutlich u ¨bersteigen. So etwas braucht man dann auch nicht am h¨auslichen Schreibtisch, sondern am Arbeitsplatz in einem Forschungs- und Entwicklungslabor oder wenn es darum geht, ¨ Graphiken in hoher Qualit¨ at herzustellen. Ubrigens sind sie im Gegensatz zum vertrauten PC in der Lage, den oben beschriebenen Mehrbenutzerbetrieb zu unterst¨ utzen. Damit haben wir das Ende der Hierarchie erreicht, und das stellt der gute alte Personal Computer dar, der nicht so heißt, weil er ein Computer f¨ urs Personal ist, sondern weil es sich um einen pers¨ onlichen Computer handelt. Er versammelt in sich die notwendige Rechenleistung, die ein Mitarbeiter an seinem pers¨onlichen Arbeitsplatz braucht, nicht mehr und nicht weniger. Seine interne Ausstattung, die Technik, die er in seinem Innenleben verwendet, ist etwas schlichter als die einer Workstation, aber solange es nur um die Belange eines Mitarbeiters an einem Arbeitsplatz geht, ist nichts gegen ihn einzuwenden. Man sollte auch nicht verschweigen, dass die Grenzen zwischen PCs und Workstations inzwischen einigermaßen fließend geworden sind; ein PC mit umfangreicher Ausstattung und den n¨otigen Zugangsm¨ oglichkeiten zu den gew¨ unschten Rechnernetzen kann durchaus auch das Niveau einer Workstation erreichen.

Computertypen Teilt man die Computer nach ihrer Leistungsf¨ ahigkeit ein, so erh¨ alt man die folgende Hierarchie: • Mainframes und Supercomputer • Midrange-Systeme

10

1 Grundlagen

• Workstations • Personal Computer

1.1.5 Informatik ¨ Uber Computer und ihre Einteilung habe ich jetzt erst mal genug geredet. Sie sollten nicht aus dem Auge verlieren, dass diese Computer nur ein Mittel zum Zweck sind, und der Zweck ist der im EVA-Prinzip beschriebene: die Eingabe, Verarbeitung und Ausgabe von Daten. Da das Ganze mit Hilfe des elektonischen Ger¨ates Computer stattfinden soll, spricht man auch, wie Sie nat¨ urlich wissen, gerne von Elektronischer Datenverarbeitung, abgek¨ urzt EDV. Und was ist jetzt Informatik? Auch da gehen die Definitionen etwas auseinander, zumal in der englischen Sprache das Wort Informatik gar nicht so recht existiert, da sagt man Computer Science“, also die Wissenschaft ” vom Computer. So falsch ist das auch gar nicht, denn schließlich g¨ abe es ohne Computer so etwas wie Informatik u ¨berhaupt nicht, und daher muss die Informatik wohl ziemlich viel mit der Elektronischen Datenverarbeitung zu tun haben. Ich halte mich deshalb an die folgende Definition.

Informatik Informatik ist die Wissenschaft, die sich mit den theoretischen Grundlagen, den Mitteln und Methoden sowie mit der Anwendung der Elektronischen Datenverarbeitung besch¨aftigt, das heißt mit der Informationsverarbeitung unter Einsatz von Computern. Genau das werden wir hier machen, und wir haben ja auch schon in diesem Abschnitt damit angefangen: wir gehen der Frage nach, wie man mit Computern Datenverarbeitung betreibt. Was man unter einem Computer versteht und welche Computertypen man heute unterscheidet, haben wir hier schon gekl¨art. Im n¨achsten Abschnitt werde ich Ihnen berichten, wie es zu der Entwicklung gekommen ist, die zu dem Computer von heute gef¨ uhrt hat. ¨ Ubungen 1.1. Analysieren Sie im Hinblick auf das EVA-Prinzip die T¨ atigkeiten, die ein Mensch bzw. ein Computer durchf¨ uhren muss, um zwei Gleichungen mit zwei Unbekannten zu l¨osen, also zum Beispiel ax + b = e cx − dy = f mit den Unbekannten x und y. Geben Sie dabei genau an, welche Eingaben n¨otig sind, welche Verarbeitungsschritte durchgef¨ uhrt werden und wie die Ausgabe gestaltet werden sollte.

1.2 Wie alles begann

11

1.2. Analysieren Sie die m¨ oglichen Nachteile, die die maschinelle Datenverarbeitung im Vergleich zur manuellen Datenverarbeitung hat. 1.3. Ein Handlungsreisender startet in Frankfurt eine Rundreise, die ihn durch 19 weitere St¨adte und zum Schluss wieder zur¨ uck nach Frankfurt f¨ uhrt. In jeder Stadt will er genau einmal Station machen (außer nat¨ urlich in Frankfurt, wo er sowohl startet als auch ankommt). (a) Zeigen Sie, daß es genau 19 · 18 · 17 · · · 3 · 2 · 1 verschiedene Reiserouten gibt. Man nennt diese Zahl 19! = Fakult¨ at von 19. (b) Der Reisende hat einen Computer zur Verf¨ ugung, der pro Sekunde 100 Millionen verschiedene Routen durchchecken kann. Wie lange braucht er, um alle m¨oglichen Reiserouten durchzugehen und so die k¨ urzeste Route herauszufinden?

1.2 Wie alles begann Es ist schwer zu sagen, wann die Menschen mit der Datenverarbeitung angefangen haben, schon deshalb, weil der Begriff etwas vage ist. Sicher hat der Hirte aus grauer Vorzeit, der f¨ ur jedes seiner Tiere einen Stein beiseite gelegt hat, um auch ohne einen genauen Zahlenbegriff seine Herde abz¨ ahlen zu k¨onnen, schon Daten verarbeitet, und das gar nicht mal schlecht, aber man w¨ urde doch z¨ogern, die Geschichte der Datenverarbeitung mit ihm beginnen zu lassen. Sinnvoller ist es, nach den ersten Hilfsmitteln zu suchen, mit dem der Prozess des Rechnens unterst¨ utzt wurde, und da wird man auch schnell f¨ undig. Die erste wichtige Erfindung in dieser Richtung war wohl das indischarabische Zahlensystem - klingt recht exotisch, aber das sind einfach nur die Zahlen, wie wir sie heute kennen, also das Dezimalsystem einschließlich der Null. Man kannte sie in Indien seit etwa dem sechsten Jahrhundert, in Arabien etwas sp¨ater, und es waren die Araber, die diese praktischen Zahlen dann im neunten Jahrhundert nach Europa einf¨ uhrten. Vermutlich haben Sie zu Ihrer Schulzeit auch das r¨omische Zahlensystem lernen d¨ urfen und k¨ onnen sich ¨ noch an seine Umst¨ andlichkeiten erinnern, da war der Ubergang zum Dezimalsystem sicher eine Wohltat f¨ ur alle Beteiligten. Trotzdem musste man noch immer mit St¨abchen im Sand kratzen oder mit der Feder u ¨bers Pergament, um Rechnungen durchzuf¨ uhren, es fehlte an maschineller Unterst¨ utzung. 1.2.1 Der Abakus Aber nicht lange. In den verschiedensten Weltgegenden, vom alten Rom bis hin zum fernen China wurde ein Rechenger¨ at entwickelt, das in Ostasien auch

12

1 Grundlagen

Abb. 1.1. Einfacher Abakus

heute noch zum g¨angigen Instrumentarium eines Kaufmanns geh¨ ort: der Abakus. Man vermutet, dass er urspr¨ unglich in Babylonien entstand, von dort aus seinen Siegeszug u ¨ber die halbe Welt antrat und etwa seit dem sechsten Jahrhundert in verschiedenen Auspr¨agungen den Leuten beim Rechnen half. Die Grundidee ist einfach genug. Man spannte ein paar Schn¨ ure in einen rechteckigen Kasten und zog eine bestimmte Anzahl Perlen auf diese Schn¨ ure. Im einfachsten Modell waren das neun Perlen pro Schnur. Hatte man also beispielsweise die Konstellation wie in Abbildung 1.1, dann war damit die Zahl 7254 dargestellt, denn die Schn¨ ure, von rechts nach links betrachtet, entsprachen einfach den Einer-, Hunderter-, Tausenderstellen usw., wie wir sie heute noch benutzen. Etwas raffinierter war dann das Modell aus Abbildung 1.2, heute w¨ urde man es vielleicht Turbo-Abakus oder gar Abakus Extended Version nennen. Es realisierte eine Art F¨ unfersystem, indem die unteren Perlen auf einer Schnur genauso zu verstehen waren wie in der einfachen Version, aber die obere Kugel die Zahl F¨ unf symbolisierte. Man konnte also die Vier durch vier Kugeln in der unteren H¨alfte darstellen, die Sechs aber durch eine Kugel unten und eine oben, wobei die Kugeln z¨ ahlten, die bis zur Grenzlinie geschoben waren. Mit so einem Abakus konnte man nicht nur Zahlen gef¨ allig auf ein Brett u ¨bertragen, sondern auch rechnen, sogar sehr schnell rechnen. Fahren Sie einmal nach China und kaufen Sie in irgendeinem kleinen Laden ein; mit etwas Gl¨ uck k¨onnen Sie dann sehen, dass ein ge¨ ubter Abakusbenutzer beim Rechnen eine Geschwindigkeit an den Tag legt, von der Sie als taschenrechnergepr¨ agter ¨ Europ¨aer nur tr¨aumen k¨onnen. Und das geht mit etwas Ubung bei allen vier Grundrechenarten. 1.2.2 Mechanische Rechenmaschinen Sch¨on und gut, aber noch immer nicht das Wahre. Das Ziel, die Rechenvorg¨ange ganz einer Maschine zu u ¨berlassen und sich als Benutzer nur noch

1.2 Wie alles begann

13

Abb. 1.2. Verbesserter Abakus

um die Eingabe k¨ ummern zu m¨ ussen, war noch eine ganze Weile entfernt, erst im siebzehnten Jahrhundert wurde die erste mechanische Rechenmaschine gebaut. Die Verh¨ altnisse waren eher ung¨ unstig f¨ ur solche etwas esoterischen Erfindungen, 1618 hatte der Dreißigj¨ ahrige Krieg angefangen und dreißig lange Jahre w¨ urde man in weiten Teilen Deutschlands, das in unz¨ ahlige ¨ F¨ urstent¨ umer aufgeteilt war, genug mit dem eigenen Uberleben besch¨ aftigt sein. Dennoch hat 1623 ein T¨ ubinger Professor namens Wilhelm Schickard eine zahnradgetriebene Rechenmaschine f¨ ur sechsstellige Additionen, Subtraktionen, Multiplikationen und Divisionen konstruiert und gebaut. F¨ ur die damalige Zeit war das eine technische Meisterleistung, zumal Schickard auch der automatische Zehner¨ ubertrag gelang, der daf¨ ur sorgte, dass bei Additionen wie etwa 9 + 7 auch wirklich eine weitere Stelle, die Zehnerstelle, entsteht und 16 heraus kommt. Er scheint u ¨berhaupt ein heller Kopf gewesen zu sein, denn urspr¨ unglich wurde er in T¨ ubingen angestellt als Professor f¨ ur Hebr¨aisch, Aram¨aisch und sonstige biblische Sprachen, u ¨bernahm aber nach dem Tod des ber¨ uhmten Kepler auch noch ohne Probleme die F¨ acher Astronomie, Mathematik und Geod¨ asie. Seine Maschine hat den Krieg leider ebensowenig u ¨berlebt wir ihr Erfinder, er starb 1635 mit 43 Jahren an der Pest; die Rechenmaschine wurde im Lauf des Krieges zerst¨ ort. Mehr Gl¨ uck hatte da der franz¨ osische Mathematiker und Philosoph Blaise Pascal. Frankreich war zwar - vor allem auf Betreiben des Ministers und Kardinals Richelieu - ausgesprochen aktiv am dreißigj¨ ahrigen Krieg beteiligt, f¨ uhrte ihn aber mit Vorliebe durch Stellvertreter und auf deutschem Boden. So konnte Pascal in aller Ruhe schon mit 19 Jahren 1643 eine mechanische Rechenmaschine entwickeln, die zu achtstelligen Additionen und Subtraktionen in der Lage war und wie die Maschine von Schickard den automatischen Zehner¨ ubertrag beherrschte - nach dem gleichen Grundprinzip, das heute noch in mechanischen Kilometerz¨ahlern verwendet wird. Er hatte die Maschine auf ur seinen Vater, der Zahnradbasis u ¨brigens nicht zum Spaß gebaut, sondern f¨ als Steuerbeamter f¨ ur das Eintreiben der Steuern in einem Bezirk Frankreichs zust¨andig und deshalb dankbar war f¨ ur eine Vereinfachung der t¨ aglichen Re-

14

1 Grundlagen

chenplackerei - bedenken Sie, dass der K¨onig und sein Minister Geld brauchten, sogar viel Geld, um den Krieg am Kochen zu halten. Aus diesem Grund konnte Pascals Maschine auch mit dem nicht durchg¨ angig dezimal gehaltenen franz¨osischen W¨ahrungssystem umgehen, was spezielle Umsetzungen bei den Zahnr¨adern ben¨otigte. Sp¨ ater wurde Pascal ein europaweit bekannter Mathematiker und Physiker, scheint dabei aber etwas unzufrieden gewesen zu sein, denn in seinen sp¨ aten Jahren wandte er sich vor allem der Religion zu und befasste sich mit religionsphilosophischen Fragen. Es wird niemanden wundern, dass sich auch der Universalgelehrte Gottfried Wilhelm Leibniz mit mechanischen Rechenmaschinen besch¨ aftigte und dann 1673 auch eine konstruierte, die f¨ ur alle vier Grundrechenarten zu gebrauchen war. Er fand, dass die langweilige Routinearbeit des Rechnens von Hand des menschlichen Geistes unw¨ urdig war und deshalb einer Maschine u ¨bertragen werden sollte. Allerdings musste er beim konkreten Zusammenbauen feststellen, dass die Verwendung des Dezimalsystems zu recht großen mechanischen Problemen f¨ uhrte, und das veranlasste ihn, u ¨ber einfachere Zahlensysteme nachzudenken. Das Ergebnis k¨ onnen Sie sich vielleicht schon vorstellen: Leibniz war der Erste, der die dualen oder auch bin¨ aren Zahlen, also das Zweiersystem entwickelte, ohne das heutige Rechner nicht mehr vorstellbar sind. Vielleicht w¨ are es auf eine Rechenmaschine mehr oder weniger nicht angekommen, aber der Gedanke, dass eine Maschine einfacher zur Basis zwei als zur Basis zehn rechnen kann, hatte sehr weitreichende Folgen, und er geht auf Leibniz zur¨ uck.

Entwickler der fr¨ uhen mechanischen Rechenmaschinen Die ersten mechanischen Rechenmaschinen wurden entwickelt von: • Wilhelm Schickard, 1623 • Blaise Pascal, 1643 • Gottfried Wilhelm Leibniz, 1673 Ihre Rechenmaschinen basierten auf dem Einsatz von Zahnr¨ adern und erm¨ oglichten den automatischen Zehner¨ ubertrag. Leibniz war außerdem der Sch¨ opfer des Zweiersystems.

1.2.3 Die Lochkartenmaschine von Hollerith Was sie bisher gesehen haben, waren Rechenmaschinen, die dem Benutzer zwar die Durchf¨ uhrung der Grundrechenarten abnehmen konnten, mehr aber auch nicht. Ob man das schon zur Informatik z¨ ahlen will, sei dahingestellt, vielleicht hat es mehr mit dem Ingenieurwesen zu tun. Aber eine andere Entwicklung ist wohl ziemlich sicher auch zur Informatik zu z¨ ahlen. Wir machen also einen kleinen Sprung u ber etwa 200 Jahre und sehen uns die Lochkarten¨ maschinen von Herrmann Hollerith an. Eine Lochkarte ist f¨ ur sich betrachtet noch nichts Besonderes, nur eine kleine Pappkarte, in die L¨ ocher eingestanzt

1.2 Wie alles begann

15

sind. Aber eben in diesen L¨ochern liegt die tiefere Bedeutung der Lochkarte, denn verschiedene Lochkombinationen beinhalten in verschl¨ usselter Form Informationen, seien es nun Daten oder Verfahrensvorschriften zum Sortieren oder zum gesteuerten Zusammenz¨ ahlen von Daten.

Abb. 1.3. Lochkarte (Quelle: http://www.heimcomputer.de/kurios/lochkarte.html)

Ihre erste Anwendung fanden die Lochkarten schon 1728 bei der automatischen Steuerung von Webst¨ uhlen, damals noch als gelochte Holzpl¨ attchen und nicht als Pappkarte. Im fr¨ uhen neunzehnten Jahrhundert wurde diese Technik dann immer mehr verbessert, sodass mit Hilfe automatisch gesteuerter Webst¨ uhle komplizierte Muster gewebt werden konnten, ohne dass man noch gelernte Fachkr¨afte brauchte, sondern nur noch angelernte Hilfsarbeiter - eine gewaltige Rationalisierung f¨ ur die Textilindustrie, sicher, aber das war auf der anderen Seite auch eine der Ursachen, die zur zunehmenden Verelendung der Weber und damit zu den schlesischen Weberaufst¨ anden von 1844 f¨ uhrten. Ein amerikanischer Ingenieur mit dem so gar nicht amerikanischen Namen Herrmann Hollerith ließ sich von den automatisierten Webst¨ uhlen inspirieren und wendete ihr Prinzip auf die Auswertung der Daten der elften amerikanischen Volksz¨ahlung 1890 an. Mit seiner Idee rannte er offene T¨ uren ein, denn die Auswertung der zehnten Volksz¨ ahlung von 1880 hatte sieben Jahre lang gedauert und 500 Leute besch¨ aftigt, weil alle gesammelten Daten manuell ausgewertet werden mussten. Da es sich dabei im Wesentlichen um Sortierund Additionsvorg¨ange an großen Datenmengen handelte, kam Hollerith auf die Idee, zu diesem Zweck Lochkartenmaschinen einzusetzen. Die Daten der Amerikaner wurden nun nicht mehr auf Z¨ ahlbl¨attchen vermerkt, die hinterher die menschlichen Datenverarbeiter zu untersuchen hatten, sondern eben auf Lochkarten, und zwar in Form von klar definierten Lochkombinationen, die einer maschinellen Verarbeitung zug¨ anglich waren. W¨ ahrend man anfangs 240 lochbare Quadrate auf einer Karte unterbrachte und damit 240 Fragen mit Ja oder Nein beantworten konnte, ging man sp¨ ater zu Karten mit 960 m¨ oglichen L¨ochern u ¨ber, die auch die Speicherung von Zahlen oder Zeichenketten zuließen. Das Verarbeitungsprinzip war gar nicht schwer, man musste nur die

16

1 Grundlagen

L¨ocher in elektromechanische Aktivit¨ aten umsetzen: eine Lochkarte passierte einen so genannten Kontaktapparat, wobei zu jeder abzulesenden Stelle ein elektrischer Schaltkreis geh¨ orte. Wies die Stelle ein Loch auf, so schloß sich der Stromkreis und eine bestimmte Verarbeitung konnte angestoßen werden, das heißt die passende Klappe eines Sortierapparates entsprechend ge¨ offnet werden; war kein Loch vorhanden, so floss nat¨ urlich kein Strom und die zugeh¨orige Verarbeitung unterblieb. Es handelte sich also um eine Z¨ ahl- und Sortiermaschine, und genau diese Zwecke waren ja auch bei der Volksz¨ ahlung vordringlich. Der Erfolg war u altigend, die Zahl der Mitarbeiter konn¨berw¨ te von 500 auf 43 gesenkt werden, die Verarbeitungsdauer betrug nur noch vier Wochen anstatt sieben Jahre. Ganz getraut hat man der Sache bei den Beh¨orden aber nicht, weil das Ergebnis den Verantwortlichen nicht passte: man hatte mit deutlich mehr Menschen im Lande gerechnet, und so wurden die Resultate zur¨ uckgehalten, die gesamte Z¨ ahlarbeit noch einmal durchgef¨ uhrt, und erst drei Monate sp¨ater, als sich alles best¨ atigt hatte, wurden die Ergebnisse ver¨offentlicht. Die Statistik passt eben nicht immer zur Politik. Offenbar waren Lochkartenmaschinen sehr sinnvoll und effektiv einsetzbar f¨ ur die routinem¨aßige Verarbeitung großer Datenmengen. Sie wurden zuerst von Holleriths eigener Firma hergestellt, dann von der IBM, in der Holleriths Firma aufgegangen war, und wurden bis weit in die sechziger Jahre des zwanzigsten Jahrhunderts benutzt. Die Lochkarten selbst waren aber noch ein ganzes St¨ uck l¨anger in Gebrauch, was auch ich bezeugen kann. Noch zu Beginn der achtziger Jahre, als ich anfing zu studieren, wurden ganze Programme mit Begeisterung in Lochkarten gestanzt und dann einem Lochkartenleser anvertraut in der Hoffnung, dass das Programm irgendwann im Laufe des Tages zur Ausf¨ uhrung kommen w¨ urde, mehr oder weniger nach Laune des zust¨ andigen Operateurs. Und auch die Studentendaten hat man auf Lochkarten erfasst und dann per Lochkartenleser eingelesen, weshalb es vor zwanzig Jahren auch noch den ehrenwerten Beruf des Lochkartenstanzers gab. Die Hollerithmaschinen waren allerdings keine wirklich programmierbaren Computer, sie waren ausgezeichnet geeignet zum Z¨ ahlen und Sortieren, und weiter waren sie gar nichts. Hollerith selbst lebt allerdings noch heute in den Programmiersprachen FORTRAN und PL1 weiter, in denen sich das so genannte H-Format“ vom ” Anfangsbuchstaben des Namens Hollerith ableitet.

Lochkartenmaschinen Die Lochkartenmaschinen von Herrmann Hollerith dienten vor allem dem schnellen Z¨ ahlen und Sortieren großer Datenmengen und wurden zum ersten Mal sehr erfolgreich bei der amerikanischen Volksz¨ ahlung von 1890 eingesetzt. Maschinen dieses Typs waren bis in die sechziger Jahre des zwanzigsten Jahrhunderts aktiv.

1.2 Wie alles begann

17

1.2.4 Die Analytische Maschine von Babbage Bei der Lochkartenmaschine handelte es sich offenbar um einen Vorl¨ aufer der elektronischen Datenverarbeitung, genau genommen um elektromechanische Datenverarbeitung, u ahlen werde, und ¨ber die ich gleich noch etwas mehr erz¨ daher befinden wir uns jetzt schon im Bereich der Informatik. Noch wichtiger war aber vielleicht ein anderer Vorl¨ aufer, auch wenn seine Maschine nie gebaut wurde, und das war der englische Mathematiker Charles Babbage. Einerseits ein sehr erfolgreicher Mann, er wurde 1828 Professor in Cambridge und brachte es fertig, elf Jahre lang nicht eine einzige Vorlesung zu halten und auch seinen Wohnsitz nicht in Cambridge zu nehmen - Verh¨ altnisse, die einen deutschen Professor von heute mit leisem Neid erf¨ ullen. Andererseits recht erfolglos, weil er das große Projekt seines Lebens, die Konstruktion einer programmgesteuerten Rechenmaschine, also eines Universalrechners, nie verwirklichen konnte, obwohl er dreißig Jahre daran gearbeitet hat. Aber ob die Maschine physisch konstruiert wurde oder nicht, darauf kommt es gar nicht so an, entscheidend ist, dass Babbage als erster Prinzipien entwickelt hat, die man heute noch in der Computerarchitektur findet. Die Maschinen, u ¨ber die ich bisher berichtet habe, waren entweder reine Rechenmaschinen oder eben Lochkartenmaschinen, die zu einem bestimmten Zweck konstruiert waren. Babbage wollte mehr. Er wollte eine Analytische Maschine bauen, die von Fall zu Fall programmiert werden konnte, um an flexiblen Eingabedaten ebenso flexible Berechnungen vornehmen zu k¨ onnen. Falls Sie das an die Funktion eines modernen Computers erinnert, haben Sie v¨ ollig recht, Babbage hatte nichts anderes vor als einen richtigen Computer auf rein mechanischer Basis zu konstruieren. Erstaunlicherweise entsprach nicht nur sein Ziel einem modernen Computerbegriff, sondern er dachte sich auch ein Konstruktionsprinzip aus, das stark heutigen Konstruktionen ¨ ahnelt. Er hatte ein vollautomatisches Rechenwerk f¨ ur die vier Grundrechenarten vorgesehen, das er mit Hilfe von Zahnr¨ adern realisieren wollte, allerdings auf dezimaler Basis und nicht zur Basis zwei. Die zu verarbeitenden Zahlen, die auszugebenden Zahlen und die Zwischenergebnisse sollten in einem separaten Zahlenspeicher gehalten werden, in den immerhin 1000 Zahlen zu je 50 Stellen passten. Es gab ein auf Lochkarten basierendes Eingabeger¨ at und ein Ausgabeger¨ at, letzteres sogar verbunden mit einem Druckwerk, aber vor allem hatte sich Babbage das Prinzip der Steuereinheit ausgedacht, mit der so etwas wie eine Programmsteuerung vorgenommen werden sollte. Im Gegensatz zu den alten Rechenmaschinen sollte die Analytische Maschine also nicht nur die Funktion eines verbesserten Abakus u ¨bernehmen, und im Gegensatz zu Holleriths Lochkartenmaschine ging es nicht nur um Z¨ahlen und Sortieren, sondern Babbage wollte echte flexible Programme zur Verarbeitung seiner Eingabedaten schreiben k¨ onnen, bis hin zu Verzweigungen in den Programmen, die je nach Eingabedaten verschiedene Verarbeitungsm¨oglichkeiten er¨offneten. Dabei war vorgesehen, die Programmierung auf Lochkarten festzuhalten. Die Idee war revolution¨ ar und hat in

18

1 Grundlagen

den heutigen Computern u uhrung mit den da¨berlebt. Leider war die Durchf¨ maligen technischen Mitteln unm¨oglich, viele Jahre nach Babbage hat man aber anhand seiner Pl¨ane mit moderneren technischen Mitteln seine Analytische Maschine nachgebaut und dabei festgestellt, dass seine Konstruktion in jeder Hinsicht funktionsf¨ahig gewesen w¨are; Babbage hatte seine guten Ideen nur ein wenig zu fr¨ uh gehabt. F¨ ur die Idee einer Allzweckmaschine, bei der die Grundrechenarten fest in der Mechanik der Zahnr¨ ader installiert waren, und die eigentlichen Instruktionen, die Programmanweisungen, u ¨ber Lochkarten der Maschine vermittelt wurden und dann durch ein kompliziertes Hebelsystem in mechanische Vorg¨ ange umgesetzt werden konnten - f¨ ur eine so moderne Idee war die Zeit wohl einfach noch nicht reif. Erst als man die reine Mechanik durch die Elektromechanik und dann durch die Elektronik ersetzen konnte, r¨ uckten solche Ideen in die N¨ahe des M¨ oglichen.

Analytische Maschine Die Analytische Maschine von Charles Babbage war, obwohl sie aufgrund mechanischer Probleme nie gebaut wurde, ein Vorl¨ aufer der modernen programmgesteuerten Computer. Ihr Konstruktionsplan sah neben einer Ein- und einer Ausgabevorrichtung ein Rechenwerk, einen Speicher und ein Steuerwerk vor, das die Operationen des Programms koordinieren sollte. Die anfangs gestellte Frage, wie alles begann, haben wir jetzt gekl¨ art: es gab einen Bogen vom Abakus u ¨ber die mechanischen Rechenmaschinen des siebzehnten Jahrhunderts bis hin zu den Ideen von Babbage und Hollerith, die man bereits der Informatik zuordnen konnte. Das war eine lange Entwicklung mit vielen Pausen und Stillst¨anden, und die n¨achsten ernsthaften Fortschritte konnten erst gemacht werden, als man anfing, die Elektrizit¨ at anzuwenden. Zu den eigentlichen Anf¨angen der Datenverarbeitung geh¨ ort das nun nicht mehr, die h¨ oren mit dem Ende des neunzehnten Jahrhunderts auf, aber eine kurze Zusammenfassung sind die Computer, die man im zwanzigsten Jahrhundert baute, allemal wert. 1.2.5 Elektromechanische und elektronische Rechner Den ersten funktionsf¨ahigen Rechenautomaten mit Programmsteuerung hat wohl Konrad Zuse 1941 vorgestellt, mitten im zweiten Weltkrieg, der uns gleich noch einmal begegnen wird. Es dauerte also mehr als 100 Jahre, bis die Ideen von Babbage in die Tat umgesetzt werden konnten, und dann auch noch von einem Konstrukteur, der von Babbage noch nie etwas geh¨ ort hatte. Er hatte schon 1938 einen ersten noch rein mechanischen Rechner mit Programmsteuerung konstruiert, den ZUSE Z1, musste aber die gleiche Erfahrung machen wie Babbage: die Beschr¨ankung auf die Mechanik reduzierte die Funktionsf¨ ahigkeit des Systems, und deshalb machte er gleich anschließend einen Versuch mit einem Ger¨at, das wenigstens teilweise auf elektromagnetischen Relais beruhte. Dieser ZUSE Z2 ist leider genauso wie sein Vorg¨ anger

1.2 Wie alles begann

19

in den Kriegswirren verloren gegangen. Erst als er 1940 von der Deutschen Versuchsanstalt f¨ ur Luftfahrt den Auftrag zum Bau eines weiteren Ger¨ ats erhielt, konnte er sich ungehindert dem Bau des ZUSE Z3 widmen, der dann 1941 tats¨achlich fertiggestellt wurde und vollst¨ andig auf der Relaistechnik beruhte. So ein Relais ist ein elektromechanisches Bauteil, mit dem es m¨ oglich ist, einen Stromkreis mit Hilfe eines anderen Stromkreises zu schließen. Der Z3 konnte die Relaistechnik besonders gut einsetzen, weil man die beiden m¨ oglichen Zust¨ande eines Stromkreises, n¨ amlich offen und geschlossen, nat¨ urlich verbinden kann mit den beiden Ziffern des dualen Zahlensystems, n¨ amlich 0 und 1, und Zuse in seiner Konstruktion das duale Zahlensystem verwendete. Auf diese Weise verband er also die alte Idee von Leibniz mit neuen elektromechanischen Konzepten und schuf einen programmgesteuerten Rechner, der f¨ ur die Durchf¨ uhrung einer Multiplikation oder Division immerhin nur 3 Sekunden brauchte.

Abb. 1.4. ZUSE Z3 (Quelle: http://irb.cs.tu-berlin/∼zuse/de/Rechner Z3.html)

In seinen sp¨ aten Jahren hat er sich u ¨brigens der Malerei zugewendet und war auch in diesem sehr von der Informatik und dem Ingenieurswesen verschiedenen Gebiet erfolgreich. Ich hatte schon erw¨ahnt, dass der zweite Weltkrieg noch einmal vorkommen w¨ urde, und das ist auch kein Wunder. Schon der erste Weltkrieg war, verglichen mit den Kriegen des neunzehnten Jahrhunderts, sehr stark von neuen Technologien gepr¨agt, und im zweiten war das nicht anders. Da die Waffentechnik weit fortgeschritten war und insbesondere Granatenwerfer mit großer Reichweite zur Verf¨ ugung standen, waren die Milit¨ ars an Berechnungen interessiert, die ihnen vorhersagen konnten, wo ihre Granaten nach dem Abschuss nun eigentlich einschlagen w¨ urden. Solche ballistischen Rechnungen waren f¨ ur die mechanischen Rechenmaschinen viel zu kompliziert, und um die n¨otigen Berechnungen durchf¨ uhren zu k¨onnen, machte man sich in Harvard an den Bau einer programmgesteuerten Rechenmaschine namens ASCC, den Automatic Sequence Controlled Computer. Geistiger Vater dieses Rechners

20

1 Grundlagen

war der Mathematiker Howard Aiken. Im Gegensatz zu Zuse verwendete er das Dezimalsystem und benutzte die elektromechanischen Relais, um schnelle Kopplungsmechanismen zwischen den Zahnr¨adern herzustellen, die in seiner Maschine noch eine bedeutende Rolle spielen. Vielleicht war das der Grund f¨ ur die riesigen Ausmaße des ASCC; er bestand aus 750000 Einzelteilen, hatte eine L¨ange von 16 Metern, eine H¨ohe von etwa 2,5 Metern und wog 35 Tonnen - ziemlich schwer, wenn man bedenkt, dass jeder Taschenrechner heute mehr oder weniger dasselbe kann wie der ASCC. F¨ ur eine Multiplikation brauchte der Computer 6 Sekunden, f¨ ur eine Division 10 Sekunden, und obwohl er seit 1944 15 Jahre lang lief, wurde schnell klar, dass diese langen Rechenzeiten das Ende der Relaisrechner bedeuten w¨ urden. Es war einfach noch zu viel Mechanik in diesen Ger¨ aten, man brauchte etwas Besseres, schnellere Kopplungsmechanismen, die wesentlich h¨ohere Rechengeschwindigkeiten erlaubten. Der ASCC hieß u ¨brigens nicht lange ASCC, er wurde dann in MARK I umbenannt und durch die Ger¨ate MARK II bis MARK IV weiter entwickelt.

Relaisrechner Die ersten programmgesteuerten Rechner auf der Basis elektromechanischer Relais waren der ZUSE Z3 von Konrad Zuse (1941) und der ASCC oder auch MARK I von Howard Aiken (1944). W¨ahrend der Z3 auf dem Zweiersystem beruhte, arbeitete der ASCC noch mit Dezimalzahlen. Die Abkehr von der Mechanik ließ dann auch nicht lange auf sich warten. Noch bevor der MARK I ganz fertig gestellt war, kam man auf die Idee, Elektronenr¨ ohren beim Bau von Rechnern einzusetzen, mit denen man den mechanischen Schwerf¨alligkeiten der Relais aus dem Weg gehen konnte. Elektronenr¨ ohren gab es schon lange, sp¨atestens seit dem Beginn des zwanzigsten Jahrhunderts, aber sie litten unter dem Problem ihrer großen Empfindlichkeit und geringen Lebensdauer, weshalb ihr Einsatz in einem Rechner zun¨ achst sehr bedenklich aussah. Auf der anderen Seite ist eine Elektronenr¨ ohre ein vollelektronisches Bauteil, ohne jede Mechanik, und auf genauso etwas hatten die Rechnerkonstrukteure gewartet. Als die R¨ohre dann nach einer etwa f¨ unfzigj¨ ahrigen Entwicklung einen vertretbaren Standard erreicht hatte, sodass man sie als Schaltelement in Computern verwenden konnte, war dann auch kein Halten mehr, die Rechner der so genannten ersten Computergeneration konnten gebaut werden. Schon 1946 wurde der erste vollelektronische Rechner in Amerika in Betrieb genommen, der Electronic Numerical Integrator and Computer, abgek¨ urzt ENIAC, von John Eckert und John Maunchly von der Universit¨ at von Pennsilvanya. Alle Schaltungen im ENIAC, ausgenommen die Ein- und Ausgabe, waren vollelektronisch auf der Basis von R¨ ohren konstruiert; f¨ ur die Zahlendarstellung wurde zwar nach wie vor das Dezimalsystem benutzt, aber trotzdem konnte man sie auf Elektronenr¨ ohren im Rechner abbilden. Die Maschine hatte den Nachteil, dass zur Programmierung keine Lochkarten oder ¨ahnliches verwendet wurden. Wollte man also etwas in

1.2 Wie alles begann

21

der Programmierung ¨ andern, dann blieb dem geplagten Programmierknecht nichts anderes u uhevoll auf einer Schaltta¨brig als das neue Programm m¨ fel mit Leitungen und Steckern zusammenzustecken. Vielleicht f¨ uhrten diese unangenehmen Erfahrungen kurz darauf zur Entwicklung der ersten h¨ oheren Programmiersprachen, denn es war einfach kein guter Zustand, dass der Rechner komplizierte mathematische Probleme in wenigen Sekunden l¨ osen konnte, die Benutzer aber Tage brauchten, um ihn von einem Problem auf das andere umzur¨ usten.

Abb. 1.5. ENIAC (Quelle: http://de.wikipedia.org/wiki/ENIAC)

In dieser komplizierten Maschine versammelten sich 18000 R¨ ohren auf einer Bodenfl¨ache von etwa 150 Quadratmetern; Sie k¨ onnen sich leicht vorstellen, welcher Aufwand getrieben werden musste, um sie bei der immer noch hohen Gefahr des R¨ ohrenausfalls am Laufen zu halten. Tats¨ achlich haben die Ingenieure es geschafft, den w¨ ochentlichen Ausfall bei zwei bis drei R¨ ohren zu stabilisieren und damit die Leistungsf¨ahigkeit zu garantieren. Vergessen Sie nicht: der ENIAC kam nur zwei Jahre nach dem ASCC heraus, aber die Zeit f¨ ur eine Multiplikation war aufgrund der deutlich verbesserten Technologie von sechs Sekunden auf drei Millisekunden gefallen. Was die technische Seite betraf, so beruhte die zweite von Eckert und Maunchly konzipierte Maschine, der EDVAC, wieder auf den Elektronenr¨ ohren, die schon den ENIAC ausgef¨ ullt hatten. Die Programmierung war allerdings deutlich einfacher, denn inzwischen war man auf die Idee gekommen, den Speicherbereich, in dem die Daten abgelegt werden, auch f¨ ur die Ablage der Programminstruktionen zu nutzen und damit die Programmierung eines Computers deutlich flexibler und einfacher zu gestalten. Wesentlich beteiligt an der

22

1 Grundlagen

Entwicklung und Ausarbeitung dieser Idee war der Mathematiker John von Neumann, von dem im n¨ achsten Abschnitt u ¨ber den Rechneraufbau noch die Rede sein wird. Jetzt wurden die Rechnungen auch im Dualsystem durchgef¨ uhrt, und der gesamte Aufbau des Rechners entsprach ziemlich genau der Architektur der Theorie gebliebenen Analytischen Maschine von Babbage.

Die erste Computergeneration Unter der ersten Computergeneration versteht man die auf der Basis von Elektronenr¨ ohren funktionierenden Rechner der sp¨aten vierziger und der f¨ unfziger Jahre des zwanzigsten Jahrhunderts wie beispielsweise der ENIAC und der EDVAC, aber auch der UNIVAC, der auf den Prinzipien des EDVAC beruht. Auf die erste Computergeneration folgte die zweite, aber hier wird es tats¨ achlich Zeit, diesen historischen Abriss zu beenden, denn inzwischen haben wir uns doch schon bedenklich der heutigen Zeit gen¨ ahert. Mit dem Ersetzen der Elektronenr¨ohren durch Transistoren als Bauelemente, was sowohl im Hinblick auf die Zuverl¨assigkeit als auch auf die Rechengeschwindigkeit und ¨ die Ubersichtlichkeit der Ausmaße deutliche Fortschritte brachte, wurden die Computer der zweiten Generation eingef¨ uhrt, und damit begann auch der ¨ Ubergang vom Experimentalcomputer zum Massencomputer. W¨ ahrend alle Rechner, die ich Ihnen vorgestellt habe, Experimentalcomputer waren, Einzelst¨ ucke, die nur einmal auf der Welt vorkamen, ging man bald darauf zur fabrikm¨ aßigen Computerfertigung u ¨ber - eine Folge der neuen Technologie, die in der zweiten Computergeneration zum Einsatz kam. Gab es beispielsweise 1953 in den USA gerade einmal 50 Computer und in der restlichen Welt eher weniger als mehr, so waren 1959 in den USA schon etwa 4000 Rechner im Einsatz und an die 900 in der restlichen Welt. Schon 1970 war die stattliche Zahl von weltweit 100000 Computern reichlich u ¨berschritten, und wie viele Millionen Apparate heute die B¨ uros, Wohnzimmer und Kinderzimmer beherrschen, weiß niemand zu sagen. Beachtlich, wenn man bedenkt, dass Howard Aiken, der Sch¨ opfer des MARK I, noch 1948 meinte, in den USA gebe es einen Bedarf f¨ ur f¨ unf, h¨ ochstens sechs Computer, und mehr w¨ urde kein Mensch brauchen.

Die zweite Computergeneration Unter der zweiten Computergeneration versteht man die auf der Basis von Transistoren funktionierenden Rechner der sp¨ aten f¨ unfziger Jahre und der sechziger Jahre des zwanzigsten Jahrhunderts.

1.2.6 Programmiersprachen Noch ein Wort zu einem anderen Aspekt, der bisher etwas untergegangen ist. Wie Sie sehen konnte, verlief die Entwicklung der Computer von den ersten lochkartengesteuerten Maschinen hin zu besser konzipierten Rechnern wie dem EDVAC, die auf dem Konzept des gespeicherten flexiblen Programms

1.2 Wie alles begann

23

beruhten. Das war ja schon mal ganz gut, aber auf welche Weise sollte man diese Programme denn schreiben? Manche Leute hielten diese Frage f¨ ur falsch gestellt; als man den großen Logiker Kurt G¨ odel fragte, meinte er, Programmiersprachen seien unn¨ otig, Logik gen¨ uge. Vielleicht lag es unter anderem auch an solchen Auffassungen, dass er sp¨ater verr¨ uckt geworden ist. Die Ingenieure und Benutzer der alten Maschinen waren vielleicht willens und in der Lage, die Programme in maschinenlesbarer Form zu formulieren, aber wenn man f¨ ur eine weitere Verbreitung der Computer sorgen wollte, dann musste man M¨oglichkeiten zu einer komfortableren Programmierung bereit stellen. Um beispielsweise den MARK I von Aiken zu programmieren, musste der Benutzer f¨ ur jede Instruktion eine Unmenge von L¨ ochern in einen Papierstreifen stanzen. Das war kein reines Vergn¨ ugen, und es war klar, dass es so nicht bleiben konnte: die Idee der h¨oheren Programmiersprachen war geboren, ohne die man sich die Informatik u ¨berhaupt nicht mehr denken kann. Dieser Idee nachzugehen, war nicht so selbstverst¨ andlich, wie Sie vielleicht glauben. Howard Aiken zum Beispiel, der Sch¨ opfer des MARK I, scheint in der komplizierten Programmierung seiner Maschine u ¨berhaupt kein Problem gesehen zu haben. Einer neuen Mitarbeiterin, im Hauptberuf immerhin eine von Computerproblemen bis dahin unbelastete Mathematikprofessorin in Harvard, zeigte er einmal einen großen Kasten und meinte, das sei eine Rechenmaschine und sie solle doch bitte bis Donnerstag damit die Koeffizienten der Arcustangensreihe ausrechnen. Die arme Frau konnte nichts anderes sagen als Ja, Sir“ und sich verzweifelt fragen, an welchen Verr¨ uckten sie da geraten ” war. Sie hat es u brigens gut u berstanden und es sp¨ a ter noch bis zur Admiralin ¨ ¨ im wissenschaftlichen Dienst der Navy gebracht. Es fing an mit einer gar nicht hohen, sondern sehr maschinennahen Sprache, dem Assembler, bei dem jeder Befehl noch einem Maschinenbefehl oder doch nur einer sehr kleinen Zahl von Maschinenbefehlen entsprach. Damit war zwar das Programmieren etwas menschlicher geworden, weil man nicht mehr direkt mit den Einsen und Nullen hantieren musste, aber jeder, der schon einmal mit Assembler zu tun hatte, weiß, dass diese Art der Programmierung eine ziemliche Plage sein kann. Die erste ernstzunehmende und auch noch erfolgreiche Hochsprache war dann FORTRAN, der Formula Translator, aus dem Jahr 1957. Mit FORTRAN konnte man endlich Formeln ann¨ ahernd so in einen Computer programmieren, wie man sie vom Papier her gew¨ ohnt war, und musste sich nicht mehr mit den maschineninternen Einsen oder Nullen herum¨argern. Außerdem ging diese Bequemlichkeit nicht zu Lasten der Geschwindigkeit: ein FORTRAN-Programm, das vom mitgelieferten Compiler in ein maschinenlesbares Programm u ¨bersetzt worden war, lief genauso schnell wie ein von Hand direkt in die Maschine geschriebenes Programm, bei dem sich der Programmierer noch selbst Gedanken u ¨ber die Verteilung der Nullen und Einsen machen musste. Beide Aspekte zusammen, die Bequemlichkeit und die Geschwindigkeit, waren schon sehr u ¨berzeugende Argumente, aber vielleicht lag das u ¨berzeugendste Argument in dem Umstand, dass FORTRAN nun mal vom Marktf¨ uhrer IBM angeboten wurde. Trotz deutlich

24

1 Grundlagen

neuerer Programmiersprachen wird FORTRAN u ¨brigens auch heute noch in aktualisierten Versionen eingesetzt. W¨ahrend FORTRAN eher f¨ ur den Bedarf des Technikers und des Naturwissenschaftlers gedacht war, musste sich der Betriebswirt oder der Lohnbuchhalter anderweitig nach einer passenden Programmiersprache umsehen, so etwas wie FORTRAN passte einfach nicht zu betriebswirtschaftlichen Problemen. Die kommerziellen Anwender mussten aber nicht lange warten, denn bereits 1959 kam die Common Business Oriented Language, abgek¨ urzt COBOL, heraus und beherrschte f¨ ur lange Zeit den Markt der Programmiersprachen f¨ ur betriebswirtschaftliche Anwendungen. COBOL ist eine sehr geschw¨atzige Programmiersprache, die Erfinder wollten sie so gestalten, dass man den Programmtext schon beim Lesen verstehen konnte, ohne weitere Dokumentation, und legten deshalb Wert darauf, manche Operationen durch Worte anstatt durch algebraische Symbole auszudr¨ ucken. Funktioniert hat das nat¨ urlich nicht, denn wer wird schon so etwas wie MULTIPLY A BY B schreiben, wenn ein schlichtes A * B auch funktioniert? Immerhin hat sich auch COBOL sehr lange gehalten und ist heute noch im Gebrauch, wenn auch nicht mehr so intensiv wie fr¨ uher. Sein fr¨ uher und durchschlagender Erfolg k¨onnte nat¨ urlich auch ein wenig damit zusammenh¨ angen, dass schon 1960 die US-Regierung verlauten ließ, sie w¨ urde keinen Computer mehr kaufen, auf dem COBOL nicht liefe, ein sch¨ ones Beispiel f¨ ur freie Marktwirtschaft. Zum Jahreswechsel 1999/2000 geisterte es sogar noch einmal durch die Zeitungen, als die EDV-Abteilungen auf die Idee kamen, dass die alten seit ¨ Urzeiten laufenden COBOL-Programme beim Ubergang auf das Jahr 2000 zu großen Schwierigkeiten f¨ uhren k¨onnten. F¨ ur kurze Zeit muss in Florida die H¨olle los gewesen sein, zumindest geht das Ger¨ ucht, dass die alten l¨ angst pensionierten COBOL-Programmierer amerikanischer Firmen unter Androhung hoher Honorare von ihren Golfpl¨ atzen und Str¨ anden gescheucht wurden, weil sie die einzigen waren, die noch mit den COBOL-Programmen umgehen und die Gefahrenquellen beseitigen konnten. Passiert ist dann beim Jahrtausendwechsel nur sehr wenig, aber immerhin konnten sich ein paar amerikanische Rentner noch etwas ihren Ruhestand vergolden.

Programmiersprachen Die ersten h¨oheren Programmiersprachen waren der Formula Translator FORTRAN und dieCommon Business Oriented Language COBOL. Pl¨ otzlich sind wir schon im Jahr 2000 angekommen, und das hat nun wirklich mit den Anf¨angen nichts mehr zu tun. Jetzt wird es Zeit, sich etwas genauer anzusehen, was aus den Ideen von Charles Babbage geworden ist, und sich mit dem Aufbau eines heutigen Rechners zu befassen.

1.3 Rechneraufbau

25

¨ Ubungen 1.4. Lochkarten wurden in der fr¨ uhen Zeit der Datenverarbeitung oft zur Speicherung von Daten verwendet. Diskutieren Sie die Vor- und Nachteile von Lochkarten als Datenspeicher. 1.5. Informieren Sie sich u autern ¨ber die Funktionsweise eines Relais und erl¨ Sie sie. Wof¨ ur wurden Relais in den ersten elektromechanischen Rechenmaschinen eingesetzt? Wo liegt ihre wesentliche Schw¨ ache im Vergleich zu den Elektronenr¨ohren oder Transistoren? 1.6. Wie sind die ersten elektronischen Rechner im Hinblick auf ihre Programmierm¨oglichkeiten zu beurteilen? Diskutieren Sie die Auffassung des Logikers Kurt G¨odel, Programmiersprachen seien unn¨ otig, Logik gen¨ uge v¨ ollig.

1.3 Rechneraufbau In der alten Zeit der Datenverarbeitung war jeder Recher anders. Es handelte sich im wesentlichen um Einzelst¨ ucke, und die Konstrukteure suchten u ¨ber einen langen Zeitraum nach einer Architektur, die den Problemen angemessen war und dem Rechner eine hinreichend schnelle und effiziente Arbeitsweise erm¨oglichten. Dass sich so eine standardisierte Architektur nicht von heute auf morgen entwickeln und etablieren kann, d¨ urfte klar sein, aber immmerhin hat sich das Grundprinzip schon in den vierziger Jahren des zwanzigsten Jahrhunderts herauskristallisiert, als der ungarische Mathematiker John von Neumann in einem Artikel das Konzept des gespeicherten Programms erl¨ auterte. Er war keineswegs grenzenlos computergl¨ aubig, was seine Arbeiten zur Rechnerarchitektur wohl noch etwas glaubhafter und u ¨berzeugender machte, als sie es inhaltlich ohnehin schon waren. In seiner amerikanischen Zeit war er Mitglied einer Kommission zur Bewilligung von Geldern f¨ ur die Entwicklung von Computern, und als eine Marinedienststelle einmal einen Computer beantragte mit dem etwas vagen Argument, man wolle bestimmte Probleme damit l¨osen, fragte von Neumann gezielt nach, um welche Probleme es sich denn handle. Der Antragsteller nannte ein mathematisches Problem, woraufhin von Neumann zehn Minuten nachdachte und dann die L¨ osung des Problems an die Tafel schrieb. Da der verdutzte Marineoffizier kein weiteres Problem wusste, mit dem man die Anschaffung des Computers h¨ atte rechtfertigen k¨ onnen, war die Welt vor einem unn¨ otigen Computer bewahrt worden. 1.3.1 Der Aufbau eines Taschenrechners John von Neumann wusste also genau, wof¨ ur man einen Computer braucht und wof¨ ur nicht, und ich werde Ihnen jetzt Schritt f¨ ur Schritt zeigen, wie ein Rechner nach der Architektur von Neumanns aufgebaut ist. Dazu fangen wir

26

1 Grundlagen

klein an und werfen zun¨achst einen Blick auf die grunds¨ atzliche Architektur eines Taschenrechners. Das muss kein großer ernsthaft leistungsf¨ ahiger Taschenrechner sein, f¨ ur den Moment gen¨ ugt mir einer, der zur Addition ganzer Zahlen f¨ahig ist. Was passiert beispielsweise, wenn Sie mit Ihren Taschenrechner die Addition 5 + 2 ausf¨ uhren wollen? Sie werden die Zahl 5 eingeben, dann die +-Taste dr¨ ucken und anschließend die Zahl 2 eingeben. Das ist nicht weiter aufregend. Wie Sie aber schon im letzten Abschnitt gesehen haben, wird ein Rechner nicht mit der dezimalen Zahl 5 oder auch 3 arbeiten; da er nun mal elektronisch ist und auf der Frage Strom oder nicht Strom“ beruht, ” arbeitet er nur mit zwei Zust¨ anden, also mit den Ziffern 0 und 1. Ihr Rechner muss also auf irgendeine Weise die beiden eingegebenen Zahlen in jeweils eine Folge aus Nullen und Einsen umwandeln, anders gesagt: er muss die beiden Dezimalzahlen in die von Leibniz erfundenen Dualzahlen umwandeln. Wie so etwas im Einzelnen geht, werde ich Ihnen im n¨ achsten Abschnitt zeigen. Im Augenblick gen¨ ugt die Erkenntnis, dass dezimale Ziffern in duale Zahlen codiert werden m¨ ussen und dass man das mit Hilfe einer Art von Codetabelle erledigen kann, die die Informationen f¨ ur die Umrechnung bereitstellt. Jetzt habe ich also die bin¨are Darstellung der 5 bestimmt, ich verrate Ihnen schon einmal, dass dabei 0101 herauskommt. Bevor nun das Pluszeichen eingetippt wird, muss diese 0101 gespeichert werden, denn mit ihr will ich nachher noch rechnen. Ihr Rechner braucht daher einen Arbeitsspeicher, der die eingegebenen Zahlen und Symbole aufnehmen kann. In diesem Arbeitsspeicher landet also die Zahl 0101, aber der Speicher ist nur eine Zwischenstation, in ihm wird nichts gerechnet. Daf¨ ur ist das Rechenwerk da, in das die eingegebene Zahl 0101 aus dem Arbeitsspeicher u uhrt wird. Das anschließend ¨berf¨ eingegebene Pluszeichen wird auch erst einmal vom Arbeitsspeicher aufgenommen. Ein Pluszeichen ist aber keine Zahl, hat also im Rechenwerk nichts zu suchen, sondern man k¨ onnte sagen, dass es die Verarbeitung steuert, die mit den beiden Eingabewerten durchgef¨ uhrt werden soll. Deshalb gibt es auch noch ein Steuerwerk, das die n¨ otigen Operationen an den Eingabedaten organisiert, und genau dort wird das Pluszeichen abgelegt. Was dann mit der Zahl 2 passiert, wird keinen u ¨berraschen: sie muss erst ins Dualsystem umgerechnet werden, was zu der dualen Zahl 0010 f¨ uhrt, und u ¨ber den Arbeitsspeicher ins Rechenwerk transportiert werden. Nun haben wir alles an der richtigen Stelle. Im Steuerwerk steht der gew¨ unschte Befehl, im Rechenwerk die Eingabedaten, und daher sorgt der Additionsbefehl daf¨ ur, dass die beiden in bin¨ arer Form vorliegenden Daten addiert werden. Auch u ¨ber die Addition solcher Zahlen werde ich Ihnen im n¨achsten Abschnitt berichten, f¨ ur den Moment werden Sie mir hoffentlich glauben, dass dabei die duale Zahl 0111 herauskommt. Wird nun der Taschenrechner die Ziffernfolge 0111 anzeigen? Das ist eher unwahrscheinlich, Sie wollen ja schließlich eine anst¨ andige Zahl in Ihrem Display sehen. Der Rechner wird also diese bin¨are Ziffernfolge in seinen Arbeitsspeicher laden und sie erneut mit Hilfe einer Codetabelle in die Dezimalzahl 7 umrechnen, die dann zum guten Schluss auf Ihrer Ausgabeeinrichtung, dem Display, erscheint.

1.3 Rechneraufbau

Zentraleinheit

Prozessor

27

Tastatur

Codetabelle

Steuerwerk Arbeitsspeicher Rechenwerk

Codetabelle

Ausgabe

Abb. 1.6. Architektur eines Taschenrechners

In Abbildung 1.6 k¨ onnen Sie den eben beschriebenen Ablauf noch einmal in einer Graphik sehen. Die Zentraleinheit umfasst also sowohl den Prozessor, der sich aus Rechenwerk und Steuerwerk zsammensetzt, als auch den Arbeitsspeicher. Beides ist schon unverzichtbar, wenn Sie so einfache Aufgaben wie die Addition zweier ganzer Zahlen bew¨ altigen wollen. Einerseits ist das keine gute Nachricht, denn ein Taschenrechner ist doch wohl kein allzu kompliziertes Ger¨ at und hat trotzdem schon eine recht aufwendige Struktur. Aber andererseits sieht die Welt doch wieder etwas heller aus, wenn ich Ihnen verrate, dass Sie hier schon wesentliche Teile des Aufbaus eines anst¨andigen programmierbaren Rechners gefunden haben, von dem wir gar nicht mehr so weit entfernt sind. Nat¨ urlich kann man einen Taschenrechner nicht unbegrenzt einsetzen, seine begrenzte Leistungsf¨ ahigkeit l¨ asst seinen Einsatz nur bei recht u onnen ¨bersichtlichen Aufgaben sinnvoll erscheinen. Sie k¨ beispielsweise in seinem Arbeitsspeicher nur wenige Ziffern unterbringen, f¨ ur gr¨oßere Datenmengen ist er schlichtweg zu klein. Die Rechenoperationen wird sich der Rechner kaum merken k¨ onnen; wenn Sie also mehrmals hintereinander das Gleiche tun wollen, wenn auch vielleicht mit anderen Zahlen, dann bleibt Ihnen nichts anderes u ¨brig als mehrmals hintereinander das Gleiche einzutippen. Und falls Sie zuf¨ allig an den falschen Schalter kommen und Ihrem Taschenrechner den Strom abdrehen, ist der gesamte Inhalt des Arbeitsspeichers gel¨oscht, weil er keine Daten dauerhaft speichern kann. Es ist also klar, dass wir f¨ ur gr¨oßere Aufgaben eine Erweiterung der Architektur vornehmen m¨ ussen, aber keine Bange: der Grundstein ist mit dem Aufbau des Taschenrechners bereits gelegt.

28

1 Grundlagen

Architektur eines Taschenrechners Ein Taschenrechner ben¨otigt neben der Eingabevorrichtung Tastatur und der Ausgabevorrichtung Display eine Zentraleinheit, in der die n¨ otigen Verarbeitungen vorgenommen werden. Sie besteht im Wesentlichen aus dem Prozessor und dem Arbeitsspeicher. Der Arbeitsspeicher nimmt die u ¨ber die Tastatur eingegebenen Daten auf, leitet sie an den Prozessor weiter und nimmt wieder eventuelle Ergebnisse auf. Der Prozessor besteht aus dem Steuerwerk, das die Abarbeitung der gew¨ unschten Operationen organisiert, und dem Rechenwerk, in dem die eigentlichen Rechnungen durchgef¨ uhrt werden. 1.3.2 Die Architektur eines von Neumann-Rechners Zun¨ achst einmal ist klar, dass Sie f¨ ur die Bearbeitung komplexerer Aufgaben mehr als nur die eine oder andere Ziffer in Ihrem Arbeitsspeicher abspeichern k¨onnen m¨ ussen; man braucht also eine deutliche Erweiterung des Arbeitsspeichers. Aber warum heißt der Arbeitsspeicher eigentlich Arbeitsspeicher? Nat¨ urlich weil man mit ihm und seinen Inhalten arbeiten will, und das bedeutet, dass er nicht irgendwo extern, beispielsweise in Form einer Festplatte vorliegen kann, sondern weiterhin direkt in der Zentraleinheit des Rechners, denn Ihr Prozessor muss schnell auf die Daten des Arbeitsspeichers zugreifen k¨ onnen. Genau deshalb verschwindet auch sein Inhalt, wenn Sie der Maschine den Saft abdrehen: der Arbeitsspeicher ist auf vollelektronischer Basis organisiert, und wenn da kein Strom mehr fließt, dann hat der Kaiser sein Recht verloren. Genau in dieser Organisationsform liegt allerdings auch sein Vorteil, da auf diese Weise eine schnelle Kommunikation zwischen dem Prozessor, der die Rechenarbeit erledigen soll, und dem Arbeitsspeicher, der die dazu notwendigen Daten bereitzustellen hat, stattfinden kann. Beachten Sie, dass sowohl die n¨otigen Befehle als auch die Daten im gleichen Arbeitsspeicher unterkommen; man hat darauf verzichtet, den Datenspeicher und den Befehlsspeicher grunds¨atzlich voneinander zu trennen, und das ist auch gut so. Schließlich wird von Problem zu Problem das Verh¨ altnis zwischen reinen Daten und Befehlen ein anderes sein, und wenn Sie zwei sauber getrennte Speicherbereiche h¨atten, dann w¨are je nach Art der Aufgabe die Gefahr der Speicherplatzverschwendung recht groß. Da es aber nur einen Arbeitsspeicher gibt, in dem man ganz nach Bedarf mal dieses und mal jenes parkt, kann das Problem der Verschwendung erst gar nicht auftreten. In Ihrem Arbeitsspeicher sollen nun also die Daten abgelegt werden und die Befehle zu ihrer Verarbeitung. Diese Befehle beschr¨ anken sich nicht mehr nur auf simple Rechenoperationen wie Plus oder Minus; Sie haben schon am Beispiel der Programmiersprachen FORTRAN und COBOL gesehen, dass auch kompliziertere algebraische Ausdr¨ ucke m¨oglich sind. Also braucht der Rechner eine erweiterte Tastatur, die flexible Eingaben erm¨ oglicht: die Ihnen allen vertraute Computertastatur, wesentlich gr¨oßer und leistungsf¨ ahiger als die Tastatur eines u ¨blichen Taschenrechners. Und auch die Ausgabe erfordert ein

1.3 Rechneraufbau

29

etwas aussagekr¨aftigeres Ger¨ at als das Display eines Taschenrechners, womit wir beim ebenfalls vertrauten Computerbildschirm angelangt sind. Aber wie sieht es nun im Inneren der Zentraleinheit aus, dem eigentlichen Herzst¨ uck des Computers? Nicht viel anders als in einem Taschenrechner, nur eben gr¨oßer, besser und hoffentlich auch schneller. Auch hier besteht der Prozessor aus einem Steuerwerk, das die organisatorische Arbeit erledigt, und einem Rechenwerk, dem die eigentliche Aufgabe des Rechnens zugeordnet ist - das werden Sie in den n¨ achsten Abschnitten noch genauer sehen. Die Einheit von Steuerwerk und Rechenwerk nennt man auch auf Englisch die Central Processing Unit abgek¨ urzt CPU. Der Arbeitsspeicher enth¨ alt, wie besprochen, Daten und Befehle, also sowohl das Programm, mit dem die Daten zu bearbeiten sind, als auch die Daten, die das Programm zu bearbeiten hat. Hier sehen Sie schon, wie wichtig das Steuerwerk f¨ ur die korrekte Verarbeitung der Daten ist, seine Rolle ist in etwa die eines Organisators oder Projektleiters in einem Betrieb. Es sorgt daf¨ ur, dass die einzelnen Befehle des Programms aus ihren Parkpl¨atzen im Arbeitsspeicher geholt werden, es besorgt dem Rechenwerk die n¨otigen Daten aus dem Arbeitsspeicher, damit das Rechenwerk auch etwas zu rechnen hat, es veranlasst das Rechenwerk, mit den u ¨bermittelten Befehlen die u ¨bermittelten Daten zu bearbeiten, und am Ende schaufelt es die berechneten Ergebnisse wieder in den Arbeitsspeicher, damit der Benutzer seine Freude daran hat. Kurz gesagt: das Rechenwerk erledigt die Rechenaufgaben, das Steuerwerk dagegen die organisatorischen Aufgaben.

Zentraleinheit

Prozessor Steuerwerk Rechenwerk

Externer Speicher

Daten

Eingabegeräte

Arbeitsspeicher Daten Programme

Ausgabegeräte

Programme Abb. 1.7. Aufbau eines von Neumann-Computers

30

1 Grundlagen

Wenn Sie nun einen Blick auf Abbildung 1.7 werfen, dann sehen Sie schon die L¨osung eines Problems, das ich noch gar nicht erw¨ aht hatte. Was macht man denn, wenn der Arbeitsspeicher zu klein ist? So ein Speicher, der auf elektronischen Schaltungen beruht, kann nicht unbegrenzt groß werden, und es kann leicht passieren, dass nicht alle Befehle oder alle Daten, die f¨ ur eine bestimmte Verarbeitung gebraucht werden, in diesen Speicher hineinpassen. Und das ist nicht mal das einzige Problem, ein anderes liegt noch viel n¨ aher: was passiert denn, wenn Sie Ihre Daten brav eingetippt, Ihre Programme fleißig dem Arbeitsspeicher u allt? ¨bermittelt haben und dann der Strom ausf¨ Die Antwort kennen Sie schon, dann ist eben alles verloren, man kann von vorne anfangen und f¨ uhlt sich so wie Sisyphos, der dazu verurteilt war, den immer gleichen Felsbrocken immer wieder auf den immer gleichen H¨ ugel zu rollen und kurz vor dem Ziel an der immer gleichen Stelle festzustellen, dass der elende Stein ihm schon wieder entglitt und nach unten rollte. Nun hatte aber Sisyphos die G¨ otter arg erz¨ urnt und musste seine ewige Strafe in der Unterwelt abb¨ ußen, w¨ ahrend Sie vollst¨ andig am Leben sind und ¨ hoffentlich keinen Arger mit irgendwelchen G¨ottern haben. Deshalb gibt es f¨ ur unsere Probleme auch eine einfache L¨ osung. Die Daten und Programme landen eben nicht gleich im Arbeitsspeicher, sondern man pflegt sie in einem externen Speicher abzulegen, der seine Informationen absolut unelektronisch aufbewahrt und daher nicht vom Stromfluss abh¨ angig ist. Normalerweise beruht so ein externer Speicher auf den u blichen Festplatten, die auf Magneti¨ sierungsbasis arbeiten, aber auch Magnetb¨ander oder aber CDs sind denkbar, die mit Laser arbeiten. In jedem Fall sind Ihre Eingaben dann vor pl¨ otzlichen Stromausf¨allen gesichert, und es muss nur f¨ ur eine Verbindung zwischen dem externen Speicher und dem Arbeitsspeicher gesorgt werden. Da der externe Speicher nicht so fl¨ uchtig ist wie der Arbeitsspeicher, nennt man ihn auch Festspeicher, und da sich umgekehrt der Arbeitsspeicher im Gegensatz zum externen Speicher innerhalb der Zentraleinheit befindet, bezeichnet man ihn auch als den internen Speicher. Um der Wahrheit die Ehre zu geben, ordnen manche Autoren auch den externen Speicher der Zentraleinheit zu tun, womit er genau genommen zum internen Speicher wird, aber das ist im Grunde reine Definitionssache. Der externe Speicher, u ¨blicherweise in Form einer Festplatte, hat also mehrere Funktionen. Erstens sorgt er daf¨ ur, dass eingegebene Daten und Programme mehrfach verwendbar sind, unabh¨ angig von der Stromversorgung des Computers. Zweitens kann man nat¨ urlich auch umgekehrt die Ergebnisse, die der Computer liefert, nicht nur dem unsicherern Arbeitsspeicher u ¨berlassen, sondern sie auch auf dem Festspeicher ablegen, damit die Nachwelt auch noch aten, etwas davon hat; insofern geh¨ort auch die Festplatte zu den Ausgabeger¨ die in Abbildung 1.7 neben dem externen Speicher platziert wurden. Und drittens k¨onnen Sie mit Hilfe des Festspeichers auch das oben angesprochene Problem der zu groß geratenen Programme oder Datens¨ atze l¨ osen. Wenn beispielsweise ein Programm so groß geworden ist, dass es nicht mehr vollst¨ andig in den Arbeitsspeicher, aber immer noch locker auf die Festplatte passt, dann

1.3 Rechneraufbau

31

ist es m¨oglich, das Programm eben nicht ganz, sondern nur h¨ appchenweise zusammen mit den jeweils n¨otigen Daten in den Arbeitsspeicher zu laden, dann vom Prozessor die geladenen Befehle ausf¨ uhren zu lassen und nach ihrer Ausf¨ uhrung einfach die n¨ achste Portion von Befehlen aus dem Festplattenspeicher zu holen. Das klingt recht einfach, ist aber recht kompliziert, denn irgendjemand muss das alles ja steuern. Sie werden kaum den Drang versp¨ uren, Ihre m¨ uhsam geschriebenen Programme auch noch in leicht verdauliche Portionen aufzuteilen, Sie wollen das Programm starten und zusehen, ob es richtig ist. Deshalb wird dieses h¨ appchenweise Einlagern von Programmteilen auch nicht vom Benutzer durchgef¨ uhrt, sondern vom so genannten Betriebssystem, einem speziellen Programm, das die Abl¨ aufe auf dem Rechner organisiert.

Architektur eines von Neumann-Computers Ein von Neumann-Computer ist durch das Konzept des gespeicherten Programms gekennzeichnet. In seinem Arbeitsspeicher werden sowohl reine Datens¨ atze als auch Programmteile gespeichert, auf die seine CPU direkt zugreifen kann, was eine schnelle Abarbeitung der Programme gew¨ ahrleisten soll. Zus¨ atzlich verf¨ ugt er in der Regel u ¨ber externe Speicher, oft in Form einer Festplatte, auf denen gr¨oßere Datenmengen und Programme abgelegt wer¨ den k¨ onnen als im Arbeitsspeicher. Uber das Betriebssystem wird gesteuert, welche Programmteile vom Arbeitsspeicher aufgenommen werden.

1.3.3 Arbeitsspeicher und Festplattenspeicher Nun habe ich die ganze Zeit u ¨ber verschiedene Speicher geredet, und wir sollten wenigstens einen kurzen Blick darauf werfen, wie so ein Speicher aussieht. Der Arbeitsspeicher oder auch Hauptspeicher besteht einfach nur aus einer Folge von vielen gleichgroßen Speicherelementen, die auf der Basis elektronischer Schaltungen organisiert sind. Da es hier um elektrischen Strom geht und man nur unterscheidet, ob Strom fließt oder nicht, gibt es f¨ ur ein Speicherelement genau zwei verschiedene M¨oglichkeiten der Belegung: Strom fließt oder nicht, also 1 oder 0. Ein einzelnes Element, das nur 0 oder 1 sein kann, wird als Bit bezeichnet, was nichts weiter ist als eine Abk¨ urzung f¨ ur binary digit, also bin¨ are Einheit, und darauf anspielt, dass Sie sich bald mit der Arithmetik bin¨ arer Zahlen werden herumschlagen d¨ urfen. Nun kann man aber in einem Bit nicht allzuviele Informationen unterbringen, und deshalb fasst man acht Bits zusammen zu einem Byte. Bei Licht betrachtet sind diese Bytes die Grundeinheiten der Speicherung und nicht die kleineren Bits, aus denen sich die Bytes zusammensetzen. Auf ein einzelnes Bit zuzugreifen ist gar nicht so einfach und außerdem meistens ziemlich sinnlos, da es Ihnen keine nennenswerte Information liefern kann. Die Bytes im Arbeitsspeicher sind dagegen Einheiten, mit denen man etwas anfangen kann; es ist m¨ oglich, ein ganzes Zeichen wie etwa einen Buchstaben in Form eines Bytes abzuspeichern, und vor allem hat jedes Byte seine eigene Adresse im Hauptspeicher. Einfacher

32

1 Grundlagen

kann man es sich gar nicht mehr vorstellen, die erste Adresse ist die 0, und dann geht es ganz schlicht linear weiter, indem man sch¨ on der Reihe nach nummeriert. Sie k¨ onnen es sich vorstellen wie in einem Gef¨ angnis, in dem die Bytes in ordentlich durchnummerierten Zellen gefangen gehalten werden, und vielleicht liegt es an dieser Assoziation, dass man die mit einer Adresse gekennzeichneten Speicherbereiche meistens als Speicherzellen bezeichnet. Da eine Adresse so gut ist wie eine andere, kann man auf jede Speicherzelle so schnell zugreifen wie auf jede andere; man spricht deshalb auch vom wahlfreien Zugriffsspeicher, auf Englisch Random Access Memory, abgek¨ urzt RAM.

Speicherstelle 0 Speicherstelle 1 Speicherstelle 2 Speicherstelle 3 Speicherstelle 4 Speicherstelle 5

Abb. 1.8. Aufbau des Arbeitsspeichers

In Abbildung 1.8 sehen Sie, dass der Arbeitsspeicher ganz einfach als Abfolge von hintereinander liegenden Speicherzellen aufgebaut ist. Die Nummerierung erfolgt nat¨ urlich in Wahrheit mit bin¨ aren Zahlen, aber die lernen Sie ja erst im n¨achsten Abschnitt, also habe ich hier noch mit Dezimalzahlen durchgez¨ahlt. Dass der Arbeitsspeicher etwas mehr als nur f¨ unf Speicherzellen haben wird, d¨ urfte klar sein, und in der Regel wird es bei den heutigen Speichergr¨oßen auch etwas zu schwerf¨ allig, die Bytes einzeln zu z¨ ahlen, sondern man ordnet sie in Tausender- und Millionenpaketen - wenn auch nicht ganz. W¨ahrend ein Kilogramm genau 1000 Gramm entspricht, finden Sie in einem Kilobyte nicht ebenso 1000 Byte, sondern genau 1024. Warum so eine krumme Zahl? Weil sie u ¨berhaupt nicht so krumm ist,wie sie auf den ersten Blick aussieht. Ich habe schon mehrfach erw¨ ahnt, dass in einem modernen Computer alle Rechnungen und eben auch alle Adressierungen auf der Basis der Bin¨arzahlen vorgenommen werden, und die beruhen auf der Basis 2. Und wie man im Dezimalsystem sich immer an den Zehnerpotenzen 1, 10, 100, 1000 usw. orientiert, bilden im Bin¨ arsystem die Zweierpotenzen 1, 2, 4, 8 usw. die achstn¨ otigen Anhaltspunkte. Wegen 1024 = 210 ist aber 1024 die zu 1000 n¨ liegende Zweierpotenz, und daher hat ein Kilobyte keine 1000 Byte, sondern 1024. Genauso ist es bei der n¨ achstgr¨ oßeren Einheit: ein Megabyte, das in Gewicht gerechnet einer Tonne entsprechen w¨ urde, hat nicht eine Million Byte,

1.3 Rechneraufbau

33

sondern 220 = 1048576 Byte. Niemand merkt sich diese genauen Zahlen, f¨ ur ungef¨ahre Rechnungen reicht in aller Regel die Gleichsetzung von einem Kilobyte mit etwa 1000 Byte und einem Megabyte mit etwa einer Million Byte. Ein Megabyte war noch in den achtziger Jahren des zwanzigsten Jahrhunderts gar nicht mal wenig, man hatte Festplattenspeicher von vielleicht 20 Megabyte und fragte sich verzweifelt, wie man die jemals voll bekommen sollte, ein u ¨blicher Arbeitsspeicher fasste gerade 512 Kilobyte. Das hat sich gr¨ undlich ge¨andert und wird sich auch weiter ¨ andern. Vor mir liegt ein aktueller Werbeprospekt eines großen Medienhauses, das mir immer wieder in der Werbung versichert, ich sei doch nicht bl¨od. Der PC, den mir das Prospekt heute, Anfang des Jahres 2005, vorstellt, hat einen Arbeitsspeicher von 1024 Megabyte, und einen Festplattenspeicher, f¨ ur den man schon gar nicht mehr in Megabyte rechnet: die n¨ achsth¨ ohere Einheit ist das Gigabyte, das 1024 Megabyte entspricht, und der aktuelle Prospektrechner hat freundliche 500 Gigabyte auf seiner Festplatte. Verglichen mit meinem ersten PC hat diese Maschine also einen 2048-mal so großen Arbeitsspeicher und sogar einen 51200-mal so großen Festplattenspeicher - keine schlechte Entwicklung f¨ ur ungef¨ahr 15 Jahre.

Arbeitsspeicher Der Arbeitsspeicher oder auch Hauptspeicher besteht aus linear angeordneten Speicherzellen, die auf der Basis elektronischer Schaltungen organisiert sind. Eine Speicherzelle kann ein Byte aufnehmen, wobei ein Byte aus acht Bit besteht und jedes Bit entweder den Wert 0 oder den Wert 1 hat. Jede Speicherzelle im Arbeitsspeicher hat eine Adresse, was dazu f¨ uhrt, dass jede Speicherzelle u ¨ber ihre Adresse gleich schnell angesprochen werden kann. Man spricht deshalb auch vom wahlfreien Zugriffsspeicher, auf Englisch Random Access Memory, abgek¨ urzt RAM. Nur kurz ein paar Worte u ¨ber die Festplatte, den externen Speicher. Er heißt deshalb extern, weil er nicht direkt zur Zentraleinheit geh¨ ort, und er hat den unangenehmen Nachteil, dass man nicht so schnell auf die dort gespeicherten Daten zugreifen kann wie auf die Daten im Arbeisspeicher. Aufgrund seiner v¨ ollig anderen Speicherungstechnik ist die Festplatte aber kein fl¨ uchtiger Speicher, sondern eben ein permanenter, was sie zur dauerhaften Speicherung von Daten unverzichtbar werden l¨asst. Der Trick ist eigentlich ganz einfach und funktioniert ¨ahnlich wie bei einer alten Schallplatte. Auf eine kreisf¨ ormige Scheibe aus stabilen Tr¨agermaterial wird eine magnetisierbare Schicht aufgetragen, die dann in mehrere konzentrische Spuren aufgeteilt wird - mehrere Kreise mit verschiedenen Radien, deren Mittelpunkt mit dem Mittelpunkt der Scheibe u ¨bereinstimmt. Das ist u ¨brigens ein großer Unterschied zu den guten alten Schallplatten, denn da gab es nur eine Spur, die sich spiralf¨ ormig u ¨ber die gesamte Kreisfl¨ache der Platte wand. Deshalb brauchte ja auch ein Plattenspieler nur einen einzigen Tonabnehmer, denn sobald er einmal den Anfang der Spur gefunden hatte, blieb ihm gar nichts anderes u ¨brig, als sich

34

1 Grundlagen

bis zum Ende der Spur durchzuarbeiten, sofern nicht gerade die Hauskatze auf den Plattenspieler springen musste. Das ist bei der Festplatte etwas anders. Da beide Seiten der Platte als Datentr¨ager verwendet werden, braucht man nat¨ urlich auch zwei Schreib/Lesek¨opfe, einen f¨ ur oben, einen f¨ ur unten. Die k¨ onnen aber nicht einfach so wie bei einer Schallplatte vorne anfangen und dann durch die ganze Platte marschieren, das w¨ urde viel zu lang dauern und w¨ urde der Tatsache nicht gerecht, dass wir es mit verschiedenen Spuren zu tun haben. Der Schreib/Lesekopf muss also erst einmal zur richtigen Spur gebracht werden, und dann muss man auf der Spur auch noch die richtige Stelle finden. Damit die Adressierung etwas u ¨bersichtlicher wird, sind die Spuren noch in einzelne Sektoren aufgeteilt, und wenn auf der Festplatte eine bestimmte Datei gesucht wird, dann wird der Schreib-/Lesekopf zur passenden Spur transportiert und dann die Platte so gedreht, dass der Sektor, in dem die gesuchte Datei beginnt, sich unter dem Schreib-/Lesekopf befindet. Sie sehen also: die Festplatte heißt nicht deshalb Festplatte, weil sie fest und unbeweglich ist, das ist sie ganz und gar nicht, denn zur Suche nach dem passenden Sektor muss sie rotieren k¨onnen. Sie wird auch nicht nur zu festlichen Gelegenheiten aus dem Materialschrank gekramt, der einzige Grund f¨ ur ihren Namen ist der Umstand, dass sie ihre Daten fest und stromunabh¨angig speichern kann. Ich w¨ urde mich allerdings nicht der Illusion hingeben, eine Festplatte sei ein absolut sicheres Speichermedium f¨ ur alle Zeiten. Sie ist zwar in aller Regel in einem gut gesch¨ utzten Geh¨ ause untergebracht, weshalb Sch¨ aden wie etwa katzenbedingte Kratzspuren eher unwahrscheinlich sind, aber sie heißt auch nicht ohne Grund Magnetplatte. Ob an einer bestimmten Speicherstelle eine Eins oder eine Null steht, h¨ angt davon ab, ob diese Stelle magnetisiert ist oder nicht, und wenn Sie beispielsweise einen starken Magneten an Ihrem Rechenr vorbei tragen und damit die Magnetisierung der Festplatte a ¨ndern, dann sind Ihre Daten genauso hin¨ uber wie bei einem Arbeitsspeicher ohne Strom.

Abb. 1.9. Festplatte mit Spuren und Sektoreneinteilung

Vermutlich haben Sie schon die Gr¨ unde f¨ ur den leichten Nachteil der Festplatte gegen¨ uber dem Arbeitsspeicher bemerkt. Um Datenelemente aus dem

1.3 Rechneraufbau

35

Arbeitsspeicher zu holen, muss der Computer nicht die geringste Bewegung durchf¨ uhren, alles beruht auf elektrischem Strom. Dagegen kommen bei einer Festplatte in jedem Fall mechanische Elemente zum Tragen, der Schreib/Lesekopf muss bewegt werden und die Platte wird zum Einstellen des passenden Sektors ein wenig rotieren. Solche echten physischen Bewegungen dauern ihre Zeit, aber keine Angst, es h¨ alt sich in Grenzen. Die Zugriffszeit auf eine moderne Festplatte liegt im Bereich weniger Millisekunden, und es ist zu erwarten, dass im Lauf der n¨ achsten Jahre die Zeiten genauso wie u ¨brigens die Preise noch fallen werden.

Festplatten Eine Festplatte ist ein magnetischer Datenspeicher, der in Spuren und Sektoren eingeteilt ist. Um eine gesuchte Datei auf der Festplatte aufzufinden, sind sowohl Bewegungen eines Schreib-/Lesekopfes als auch eine Rotation der Platte selbst n¨otig; auf Grund dieser mechanischen Bewegungen sind die Zugriffszeiten h¨oher als beim vollelektronischen Arbeitsspeicher. Festplatten eignen sich zur permanenten Speicherung großer Datenmengen. Ich will Sie nicht mehr langweilen als unbedingt n¨ otig, und deshalb werde ich jetzt nicht mehr lang und breit u ber Disketten und ihre Laufwerke reden. ¨ Im Prinzip ist eine Diskette nichts anderes als eine abgespeckte Festplatte, die aber, wie Sie wissen, nicht fest im Rechner verankert ist, sondern nur von Fall zu Fall als Speichermedium verwendet wird, vor allem zur Datensicherung und um Daten von einem Rechner zum anderen zu transportieren. Beide Zwecke sind aber nicht mehr so aktuell wie fr¨ uher, was nicht zuletzt auf die geringe Speicherkapazit¨at der u blichen Disketten zur¨ uckzuf¨ uhren ist: mit nur 1, 44 ¨ Megabyte locken Sie heute keinen Hund mehr hinter dem Ofen hervor, diese Kapazit¨ at reicht nicht aus, um mit der heute u oße mitzuhal¨blichen Dateigr¨ ten. F¨ ur so etwas wird heute gerne das optische Speichermedium Compact Disc, abgek¨ urzt CD, oder seit neuester Zeit die DVD ben¨ utzt. Ihre Speicherkapazit¨ at ist ungleich gr¨oßer, und in absehbarer Zeit wird wohl kaum noch jemand die klassischen Disketten verwenden. Lassen Sie mich kurz den Inhalt dieses Abschnitts rekapitulieren: Sie wissen jetzt im Groben, wie ein heutiger von Neumann-Computer aufgebaut ist und haben einen Eindruck von den f¨ ur den Arbeitsspeicher und den Festspeicher verwendeten Methoden. Was ich bisher noch u ¨berhaupt nicht angesehen habe, ist der Teil der Zentraleinheit, in dem die eigentliche Rechenarbeit geschieht, n¨ amlich das Rechenwerk. Das hat seine Gr¨ unde. Die Arbeit im Rechenwerk geschieht auf bin¨arer Basis, es wird nur mit Zahlen zur Basis 2 gerechnet, und das sind nicht gerade die Zahlen, mit denen Sie t¨ aglich zu tun haben. Um zu verstehen, was im Rechenwerk vor sich geht, kann ich Ihnen also die M¨ uhe nicht ersparen, sich im n¨achsten Abschnitt verst¨ arkt mit den Rechenmethoden des Zweiersystems zu besch¨aftigen, vornehm formuliert: mit der bin¨ aren Arithmetik. Auf der Basis dieser Arithmetik kann ich Ihnen dann im f¨ unften Abschnitt zeigen, mit welchen konkreten Schaltungen ein Computer rechnen kann.

36

1 Grundlagen

¨ Ubungen 1.7. Wie Sie gesehen haben, besitzt ein heutiger Computer sowohl einen Arbeitsspeicher als auch einen Festspeicher. (a) Untersuchen Sie, ob man einen modernen Computer auch ohne einen vollelektronischen Arbeitsspeicher konstruieren kann. Gehen Sie dabei auf die grunds¨atzliche M¨oglichkeit und auf das Problem der Arbeitsgeschwindigkeit eines solchen Rechners ein. (b) Gehen Sie den Fragen aus (a) nach unter der Voraussetzung, dass zwar ein vollelektronischer Arbeitsspeicher, daf¨ ur aber keine Festplatte vorhanden ist. 1.8. Diskutieren Sie, warum man in der Zentraleinheit einerseits Speicherzellen zur puren Aufnahme von Daten und andererseits ein Rechenwerk zum Rechnen mit diesen Daten vorsieht. Warum hat man nicht gleich die Speicherzellen des Arbeitsspeichers mit Rechenf¨ ahigkeiten versehen und sich ein separates Rechenwerk gespart? 1.9. Berechnen Sie die Kapazit¨at des Arbeitsspeichers und des Festplattenspeichers meines alten PC. 1.10. Gegeben sei ein Speicher mit einer Kapazit¨ at von vier Megabyte. Exakt um 12 Uhr wird in diesen Speicher ein Zeichen (also ein Byte) eingelesen, um 12.01 Uhr vier weitere Zeichen, um 12.02 Uhr 16 weitere Zeichen, um 12.03 Uhr 64 weitere Zeichen usw. Zu welchem Zeitpunkt kann keine weitere Zeichenreihe eingelesen werden und wieviel Speicherplatz ist dann belegt? 1.11. Eine Festplatte rotiert mit 7200 Umdrehungen pro Minute und hat einen Durchmesser von 3, 5 Zoll, wobei ein Zoll 2, 54 Zentimetern entspricht. Mit welcher Geschwindigkeit, gemessen in Metern pro Sekunde, bewegt sich der a¨ußerste Rand der Platte?

1.4 Bin¨ are Arithmetik ¨ Uber die Aufgabe des Computers, Daten zu verarbeiten, haben wir uns schon mehrfach geeinigt, und Sie haben auch schon etwas dar¨ uber gelernt, wie er das macht. Was soll man aber eigentlich unter Daten verstehen? Da gehen die Meinungen etwas auseinander. Man kann nat¨ urlich sagen, dass jede in irgendeiner Form aufgeschriebene Zeichenfolge schon so etwas wie Daten darstellt, aber das ist wenig befriedigend und erinnert an die Auffassung mancher privaten Fernsehsender, alles was auf dem Bildschirm herumzappelt, sei unterhaltend. Irgendein Sinn sollte mit Daten verbunden sein, sonst w¨ are der Versuch, sie zu verarbeiten, v¨ollig sinnlos und die Datenverarbeitung w¨ are ein Spiel mit inhaltsleeren Zeichenketten.

1.4 Bin¨ are Arithmetik

37

1.4.1 Daten Ich will daher davon ausgehen, dass Daten einen Bezug zu irgendeinem Problem haben, an dessen L¨ osung man interessiert ist. Genau wie es sich sp¨ ater bei den Algorithmen herausstellen wird, sind Daten Hilfsmittel, um ein Problem zu l¨osen, und nicht sinnlos hingekritzelte Zeichenketten.

Daten Unter Daten versteht man Angaben zu Personen, Sachen oder Sachverhalten, die zur L¨ osung eines Problems zweckdienlich sein k¨ onnen. Niemand kann die Zweckdienlichkeit eines bestimmten Datensatzes f¨ ur ein bestimmtes Problem garantieren, aber man sollte mit Daten wenigstens die Hoffnung verbinden, dass ihre Verwendung zur L¨ osung des einen oder anderen Problems sinnvoll sein kann. Je nach Betrachtungsweise k¨onnen Sie nun die Daten auf verschiedene Weise unterteilen. Eine beliebte Einteilung ist die nach dem Grad der Ver¨ anderlichkeit. Daten u ¨ber die Angestellten in einer Firma wie beispielsweise Name, Geburtsdatum oder Kinderzahl werden sich nicht alle f¨ unf Minuten ver¨ andern, sie bleiben u ¨ber lange Zeitr¨aume konstant und werden deshalb Stammdaten genannt. Umgekehrt hat Ihr Mitarbeiter aber auch Daten, die sich sehr wohl jeden Tag ver¨ andern k¨onnen wie zum Beispiel die t¨ agliche Arbeitszeit oder der Stundenlohn bei Akkordentlohnung. Da diese Daten st¨ andig in Bewegung sind, spricht man hier gerne von Bewegungsdaten - der große Jedi-Ritter Meister Yoda w¨ urde sie wahrscheinlich f¨ ur sehr zukunftstr¨ achtig halten, da nach seiner Meinung auch die Zukunft in st¨andiger Bewegung ist. Das ist aber nicht die einzige Einteilungsm¨oglichkeit. Sie k¨ onnen sich auch daf¨ ur entscheiden, die Daten nach ihrer Stellung im Verarbeitungsprozess einzuteilen in Eingabedaten und Ausgabedaten, oder sie nach der Art ihrer Verwendung zu klassifizieren. Eine Personalnummer ist sicher etwas anderes als eine Gehaltsangabe, denn die Personalnummer dient der Sortierung und Identifizierung Ihrer Mitarbeiterdaten, w¨ahrend das Gehalt nichts weiter als eine Mengenangabe ist, genauso wie die schon erw¨ ahnte Kinderzahl oder die Anzahl der geleisteten Arbeitsstunden. Daten, die zur Sortierung und Identifizierung dienen, nennt man Ordnungsdaten, und solche Daten, die schlichte Mengenangaben liefern, heißen aus offensichtlichen Gr¨ unden Mengendaten. Selbstverst¨ andlich sind auch noch andere Verwendungszwecke denkbar. Sie hatten schon im letzten Abschnitt gesehen, dass die Daten irgendwo im Arbeitsspeicher abgelegt werden m¨ ussen, damit sie einer Verarbeitung zug¨ anglich werden, und die dabei verwendeten Speicherzellen haben bestimmte Adressen. Auch das sind Daten, diese Adressen m¨ ussen schließlich verwaltet werden, um an die abgelegten Datens¨atze wieder heranzukommen, und weil hier origen Daten die Abl¨aufe im Rechner gesteuert werden, nennt man die zugeh¨ Steuerdaten. Hinaus will ich aber im Moment auf eine dritte Einteilungsm¨ oglichkeit. Es macht einen Unterschied, ob Sie nur numerische Daten verwenden, mit de-

38

1 Grundlagen

nen Sie anschließend rechnen, oder so genannte alphanumerische, die sich aus Buchstaben und Zahlen zusammensetzen k¨onnen und zum Rechnen offenbar nur bedingt geeignet sind. Je nach der Art der Zeichen kan die anschließende Verarbeitung ausgesprochen unterschiedlich sein. Haben Sie beispielsweise ein Programm zur L¨ osung quadratischer Gleichungen geschrieben, dann wird es irgendwann die Eingabe bestimmter Zahlenwerte erwarten, aus denen es dann die L¨osungen berechnet, und an dieser Stelle d¨ urfen Sie keine Buchstaben und schon gar keine Sonderzeichen eingeben, sonst bricht Ihr Programm zusammen. Die Art der verwendeten Zeichen richtet sich also stark nach der Art der durchzuf¨ uhrenden Verarbeitung.

Einteilungsm¨ oglichkeiten f¨ ur Daten Es gibt verschiedene M¨oglichkeiten, Daten einzuteilen, beispielsweise: • • • •

nach nach nach nach

der der der der

Ver¨ anderlichkeit, Verwendung, Stellung im Verabeitungsprozess, Art der verwendeten Zeichen.

In diesem Abschnitt geht es mir vor allem um die Verarbeitungsmethode Rechnen“. Es ist in den letzten Abschnitten immer wieder deutlich geworden, ” dass s¨ amtliche Verarbeitungen im Rechner auf bin¨ arer Basis vor sich gehen, mit Nullen und Einsen, und das spielt vor allem im Rechenwerk eine große Rolle. Die dort anzutreffenden Schaltungen beruhen auf dem Rechnen mit bin¨ aren Zahlen, weshalb ich Ihnen in diesem Abschnitt zeigen will, wie die bin¨ are Arithmetik funktioniert. 1.4.2 Bin¨ arzahlen Ich habe schon mehrfach die Tatsache angesprochen, dass ein Computer irgendwie mit Einsen und Nullen rechnet. Das klingt f¨ ur normale Menschen zun¨ achst etwas seltsam: wie soll man denn mit Einsen und Nullen rechnen k¨onnen, da kommt man doch nicht weit. Stimmt aber nicht. Auch das vertraute Deziamlsystem hat nur zehn Ziffern, und trotzdem kann man damit ¨ weiter als bis zehn z¨ahlen, warum sollte etwas Ahnliches dann nicht auch mit zwei Ziffern funktionieren? Versetzen Sie sich einmal in die Lage eines Computers. Von außen akzeptiert er selbstverst¨andlich die u ¨blichen dezimalen Zahlen in der u ¨blichen Darstellung, und er gibt auch seiner Ergebnisse wieder in dieser Form aus, aber seine Verarbeitungen macht er v¨ollig anders. Die internen Schaltungen eines Rechners beruhen auf dem Fließen von Strom, und da gibt es nur zwei Grundzust¨ ande: Strom fließt oder er fließt nicht. Beim Aufbau des Zahlensystems habe ich dagegen zehn Grundzust¨ande, n¨ amlich die Ziffern von Null bis Neun, aus denen sich alles andere zusammensetzt. So viele M¨ oglichkeiten

1.4 Bin¨ are Arithmetik

39

besitzt der Computer nicht, er muss seine gesamte Arithmetik, all seine Rechnungen, zusammensetzen aus den erw¨ ahnten zwei Grundzust¨ anden. In Ziffern u ugung, sondern nur ¨bersetzt heißt das: ihm stehen keine neun Ziffern zur Verf¨ zwei, und damit muss er auskommen. Sehen wir uns also an, wie man die nat¨ urlichen Zahlen computerfreundlich darstellen kann. Die Darstellung, nach der ich suche, beruht auf nur zwei Ziffern, und deshalb spricht man oft vom Zweiersystem oder auch Bin¨ arsystem; andere bevorzugen den Namen Dualsystem. Es spielt aber keine Rolle, wie Sie es nennen. Wichtig ist nur dass es darum geht, s¨ amtliche nat¨ urlichen Zahlen mit Hilfe von nur zwei Ziffern darzustellen. Dabei ist es sinnvoll, sich erst einmal zu u ¨berlegen, wie eigentlich das vertraute Dezimalsystem aufgebaut ist. Die Bedeutung der Dezimalzahl 13271 d¨ urfte Ihnen vertraut sein. Offenbar hat die 1 ganz vorne einen v¨ ollig anderen Wert als die 1 ganz hinten, denn sie steht in Wahrheit f¨ ur 10000, w¨ ahrend die hintere 1 einfach nur f¨ ur sich selbst steht. Die Wertigkeit einer Ziffer h¨ angt also davon ab, an welcher Stelle der Zahl sie steht, und je weiter man nach links kommt, desto h¨ oher steigt man in der Zehnerpotenz. Im Falle meiner Beispielzahl gilt: 13271 = 1 · 104 + 3 · 103 + 2 · 102 + 7 · 101 + 1 · 100 . Nach diesem Prinzip kann ich jetzt genau aufschreiben, was man unter einer Dezimalzahl versteht: es ist eine Folge von endlich vielen Ziffern zwischen 0 und 9, und der tats¨achliche Zahlenwert jeder Ziffer h¨ angt davon ab, an welcher Stelle der Zahl die Ziffer steht. Je weiter links, desto h¨ oher ist die Zehnerpotenz, mit der man die Ziffer multiplizieren muss.

Dezimalzahlen Eine (n + 1)-stellige Dezimalzahl ist eine Folge von n + 1 Ziffern an an−1 . . . a1 a0 , die alle zwischen 0 und 9 liegen. Der dezimale Wert dieser Zahl betr¨ agt an · 10n + an−1 · 10n−1 + · · · + a1 · 10 + a0 . Zwei Worte dazu. In dieser Definition steht nur, dass sich eine Dezimalzahl aus den Ziffern 0 bis 9 zusammensetzt, und dass man die wirkliche Bedeutung jeder Ziffer nur sehen kann, wenn man darauf achtet, an welcher Stelle der Zahl sie steht. Nat¨ urlich klingt das verbal etwas gef¨ alliger als formelm¨ aßig, aber in der Informatik muss man nun mal ab und zu mit Formeln umgehen. Vielleicht verwirrt Sie u ¨brigens der Umstand, dass die Zahl (n + 1)-stellig ist. Das liegt einfach nur daran, dass ich bei der Nummer 0 mit dem Z¨ ahlen anfange und bei n wieder aufh¨ore. Im Falle meiner Beispielzahl 13271 habe ich offenbar f¨ unf Stellen, also ist n + 1 = 5 und damit n = 4. Die Ziffern lauten

40

1 Grundlagen

dann a0 = 1, a1 = 7, a2 = 2, a3 = 3 und a4 = 1. Sie sehen, die allgemeine Definition passt genau zum Beispiel. W¨ are ja auch schlimm, wenns anders w¨are. Das ist ja alles schon mal ganz sch¨on, aber die Dezimalzahlen kennen Sie schon ziemlich lange, und eigentlich wollte ich auf die Bin¨ arzahlen hinaus. Sie sind aber genauso aufgebaut wie die Dezimalzahlen, nur eben mit zwei Ziffern anstatt zehn. Eine typische Bin¨ arzahl lautet beispielsweise 101112 , wobei die kleine Zwei bedeutet, dass es sich um eine Zahl zur Basis 2 handelt. W¨ are das eine Dezimalzahl, also eine Zahl zur Basis 10, dann h¨ atte sie die Bedeutung 1 · 104 + 0 · 103 + 1 · 102 + 1 · 101 + 1 · 100 , wie wir es eben besprochen haben. Es ist aber keine Dezimalzahl, sondern soll eine Bin¨ arzahl sein, und bei den Bin¨arzahlen spielt die Zahl 2 die gleiche Rolle wie bei den Dezimalzahlen die 10. Ich muss daher nur die Basiszahl austauschen und aus der 10 eine 2 machen, um die eigentliche Bedeutung der Bin¨ arzahl 101112 vor mir zu sehen. Ihr dezimaler Wert kann nur 1 · 24 + 0 · 23 + 1 · 22 + ·21 + 1 · 20 sein, denn ich musste u ¨berall die Basis 10 gegen die Basis 2 austauschen. Rechnet man das aus, so ergibt sich die Dezimalzahl 23, weshalb also die Bin¨ arzahl 101112 der Dezimalzahl 23 entspricht. Jetzt d¨ urften Sie so weit sein, die Definition der Bin¨ arzahlen zu verstehen. Sie sieht fast genauso aus wie Definition der Dezimalzahlen, nur dass ich auf die richtigen Ziffern achten muss: bisher hatte ich 0 bis 9, jetzt habe ich nur noch 0 und 1. Und auch beim dezimalen Wert wird sich etwas a ¨ndern.

Bin¨ arzahlen Eine (n + 1)-stellige Bin¨ arzahl oder auch Dualzahl ist eine Folge von n + 1 Ziffern an an−1 . . . a1 a0 , die alle entweder gleich 0 oder gleich 1 sind. Der dezimale Wert dieser Zahl betr¨ agt an · 2n + an−1 · 2n−1 + · · · + a1 · 2 + a0 . Werfen Sie einen Blick auf den dezimalen Wert einer solchen Bin¨ arzahl. Bei den Dezimalzahlen musste ich jede Ziffer mit der passenden Zehnerpotenz multiplizieren, um den richtigen Wert zu erhalten, weil ich nun einmal u ugte. Bei meinen Bin¨arzahlen habe ich aber nur noch ¨ber zehn Ziffern verf¨ zwei Ziffern, und deshalb bleibt mir nichts anderes u ¨brig, als zu Zweierpotenzen u ¨berzugehen. Da Sie vermutlich mit dieser seltsamen Art von Zahlen noch selten etwas zu tun hatten, zeige ich Ihnen noch ein paar Beispiele. Um Verwechslungen zu vermeiden, werde ich alle Bin¨ arzahlen mit dem Index 2 versehen, damit klar ist, dass es sich um Zahlen zur Basis zwei handelt. Wie lautet nun der dezimale Wert von 1010102 ? Die Zahl besteht aus sechs Ziffern, daher ist die h¨ochste Potenz 25 . Damit ergibt sich: 1010102 = 1 · 25 + 0 · 24 + 1 · 23 + 0 · 22 + 1 · 21 + 0 · 20 = 32 + 8 + 2 = 42.

1.4 Bin¨ are Arithmetik

41

1010102 ist also die bin¨are Darstellung der vertrauten Zahl 42, ist doch eigentlich ganz einfach. Immerhin musste ich hier noch die Stellen der Bin¨ arzahl z¨ahlen, damit ich wusste, mit welcher Zweierpotenz ich anfangen muss. Etwas einfacher hat man es, wenn man bei der Umrechnung von rechts nach links vorgeht und nicht wie eben von links nach rechts. Sie wissen ja, dass Sie bei 1 anfangen m¨ ussen und sich dann immer entlang der Zweierpotenzen hochhangeln sollen. Das bedeutet zum Beispiel: 110111012 = 1 + 4 + 8 + 16 + 64 + 128 = 221, denn ganz rechts finden Sie eine bin¨ are 1, die einer vertrauten 1 entspricht. Nach einer 0 kommt dann wieder eine 1, also gibt es keine Zweierstelle, sondern erst wieder eine Viererstelle, der sich eine Achterstelle und eine Sechzehnerstelle anschließen. Die Zweiunddreißigerstelle wird dann wieder ausgelassen, und ganz links versammeln sich eine Vierundsechzigerstelle und eine Einhundertachtundzwanzigerstelle. Auf die gleiche Weise k¨ onnen Sie dann zum Beispiel 10001012 = 1 + 4 + 64 = 69 rechnen. Das wird ein recht langer Abschnitt, und Sie sollten unterwegs schon wissen, an welchen Beispielen Sie u ¨ben k¨onnen. Falls Sie schon ein wenig rechnen wollen, w¨are das der richtige Zeitpunkt f¨ ur die Aufgabe 1.12. 1.4.3 Umrechnungsverfahren Sie wissen nun also, was man unter einer Bin¨arzahl versteht und wie man eine Bin¨arzahl in eine Dezimalzahl umrechnet. Daraus entstehen aber sofort zwei Probleme. Der Computer soll schließlich mit allen nat¨ urlichen Zahlen rechnen k¨onnen, und das setzt voraus, dass es auch zu jeder nat¨ urlichen Zahl eine bin¨are Darstellung gibt. Die Frage ist also: kann man jede nat¨ urliche Zahl als Bin¨arzahl darstellen? Und falls man das kann: wie findet man konkret die passende Bin¨arzahl? Es n¨ utzt dem Rechner schließlich gar nichts, wenn er weiß, dass es eine bin¨are Darstellung gibt, aber keine Ahnung hat, wie er sie finden soll. Beide Probleme will ich jetzt auf einmal l¨ osen, indem ich Ihnen ein Umrechnungsverfahren zeige, das Dezimalzahlen in Bin¨ arzahlen umrechnet. Sehen wir uns das zun¨ achst an einem Beispiel an. Ich untersuche die Dezimalzahl m = 17. Bin¨ arzahlen orientieren sich an Zweierpotenzen, also suche ich nach der gr¨oßten Zweierpotenz, die noch in die 17 hineinpasst. Das ist offenbar 16 = 24 . Es bleibt also etwas von m u ¨brig, das die 16 u amlich genau 17−16 = 1. In die 1 passt nat¨ urlich weder die ¨bersteigt, n¨ n¨achste unter 16 liegende Zweierpotenz 8 noch die 4 noch die 2. Die Bin¨ ardarstellung hat daher keine Achter-, Vierer- oder Zweierstelle, sondern nur noch die pure Einserstelle 1. Mit anderen Worten: ich habe herausgefunden, dass 17 = 16 + 1 = 1 · 24 + 0 · 23 + 0 · 22 + 0 · 21 + 1 · 20 gilt, und daraus folgt: 17 = 100012 .

42

1 Grundlagen

So kann man das immer machen. Am Anfang sucht man nach der gr¨ oßten Zweierpotenz 2n , die die gegebene Zahl m nicht u ¨bersteigt. Dann zieht man 2n von m ab und sieht nach, was u ¨brigbleibt. Dabei kann es sein, dass man schon unter die n¨ achste m¨ ogliche Zweierpotenz 2n−1 rutscht, und in diesem Fall geh¨ort an die entsprechende Stelle eine 0, ansonsten eine 1. Anschließend muss ich eine Stelle tiefer gehen und testen, ob im Rest noch Platz ist f¨ ur 2n−2 , und so hangle ich mich immer tiefer, bis ich schließlich bei 20 am Boden angekommen bin. Wichtig dabei ist nur, dass man in jedem Schritt darauf achtet, das bisher Erreichte von m abzuziehen und nachzusehen, ob die n¨ achste Zweierpotenz noch hineinpasst. Dieses Verfahren werde ich jetzt in ein pr¨ aziser beschriebenes Verfahren fassen. Gegeben ist also eine Dezimalzahl m, die ich in ihre Bin¨ ardarstellung oßte Zweierpotenz, die an an−1 ...a1 a0 umrechnen will. Dazu suche ich die gr¨ m nicht u ¨bersteigt, das heißt: 2n ≤ m < 2n+1 . Da dann m < 2n+1 gilt, ist 2n tats¨achlich die gr¨ oßte Zweierpotenz, die in m hineinpasst. Also setze ich an = 1. Anschließend berechne ich m − 2n . Die vorher errechnete Zweierpotenz wird nun also von m abgezogen, damit ich feststellen kann, welche Zweierpotenzen noch in den Rest passen. Falls m − 2n ≥ 2n−1 ist, passt die n¨achstkleinere Zweierpotenz in den Rest. Also gibt es eine 2n−1 erstelle, und ich setze an−1 = 1. Falls dagegen m − 2n < 2n−1 ist, passt die n¨achstkleinere Zweierpotenz eben nicht in den Rest. Also setze ich an−1 = 0, denn es gibt keine besetzte 2n−1 erstelle. So oder so, die ullt, 2n−1 erstelle ist nun entweder mit einer 0 abgetan oder mit einer 1 gef¨ und ich kann mich der n¨ achsten Stelle widmen, die zu 2n−2 geh¨ ort. Zu diesem Zweck berechne ich m − 2n − an−1 · 2n−1 . Von dem alten Rest muss ich also die neue Stelle abziehen, um den neuen Rest zu erhalten. Falls an−1 = 0 war, muss ich hier nat¨ urlich gar nichts tun, und ansonsten muss ich genau die 2n−1 abziehen, die im alten Rest noch enthalten waren, damit ich mich jetzt um die kleineren Zweierpotenzen k¨ ummern kann. Ich gehe dabei nach dem gleichen Prinzip vor wie in Schritt 2. Falls m − 2n − an−1 2n−1 ≥ 2n−2 ist, passt die n¨achstkleinere Zweierpotenz in den Rest. Also gibt es eine 2n−2 erstelle, und ich setze an−2 = 1. Falls dagegen m − 2n − an−1 2n−1 < 2n−2 ist, passt die n¨achstkleinere Zweierpotenz nicht in den Rest. Also setze ich an−2 = 0. ardarWiederholen Sie das Verfahren, bis a0 berechnet ist, dann steht die Bin¨ stellung der Dezimalzahl m vor Ihnen.

Umrechnung einer Dezimalzahl in eine Bin¨ arzahl Das folgende Verfahren rechnet eine Dezimalzahl m in ihre Bin¨ ardarstellung an an−1 ...a1 a0 um. • Man suche die gr¨oßte Zweierpotenz, die m nicht u ¨bersteigt, das heißt: 2n ≤ m < 2n+1 .

1.4 Bin¨ are Arithmetik

43

Man setze an = 1. • Man berechne m − 2n . Falls m − 2n ≥ 2n−1 ist, setze man an−1 = 1. Falls m − 2n < 2n−1 ist, setze man an−1 = 0. • Man berechne m − 2n − an−1 · 2n−1 . Falls m − 2n − an−1 2n−1 ≥ 2n−2 ist, setze man an−2 = 1. Falls m − 2n − an−1 2n−1 < 2n−2 ist, setze man an−2 = 0. • Man wiederhole das Verfahren, bis a0 berechnet ist. Ich sage es noch einmal: dieses Verfahren ist nichts weiter als die pr¨ azisere Beschreibung dessen, was ich vorhin schon gemacht habe. Man sieht einfach nach, ob die n¨ achstkleinere Zweierpotenz noch in das hineinpasst, was von der Zahl noch u ardarstellung an ¨brig geblieben ist. Falls ja, bekommt die Bin¨ der passenden Stelle eine 1, falls nein gibt es eine 0. Ich weiß, auch das ist ungewohnt und neu, und deshalb kann eine weitere Beispielrechnung nicht schaden. Ich will die Dezimalzahl m = 42 in eine Bin¨ arzahl umwandeln. Das hat den Vorteil, dass ich die umgekehrte Richtung vorhin schon beschritten habe und deshalb leicht kontrollieren kann, ob mein Ergebnis stimmt. Ich werde dabei nach den eben beschriebenen Schritten vorgehen. • Die gr¨ oßte Zweierpotenz, die m nicht u ¨bersteigt, ist 25 = 32, also ist n = 5 und a5 = 1. Das bedeutet, dass m eine Zweiunddreißigestelle hat. • Nun muss ich sehen, was noch hineinpasst, wenn ich 32 abgezogen habe. Es gilt: m − 25 = 42 − 32 = 10, und ich muss mich hier nur noch um den Rest 10 k¨ ummern. Die n¨achstkleinere Zweierpotenz lautet 24 = 16, und wegen 10 < 16 existiert keine Sechzehnerstelle, das heißt: a4 = 0. • Der neue Wert a4 · 24 muss jetzt von der 10 abgezogen werden, aber da ich keine Sechzehnerstelle hatte, ist auch nichts zu tun. Folglich ist m − 25 − a4 24 = 10. Es geht jetzt aber um die Zweierpotenz 23 = 8, und die liegt ganz sicher unterhalb von 10, passt also in das hinein, was von der Zahl noch u ¨briggeblieben ist. Daher gibt es eine Achterstelle, und ich muss a3 = 1 setzen. • Weiteres Subtrahieren ist angesagt. Von 10 ziehe ich die Zweierpotenz 23 = 8 ab, und das ergibt: m − 25 − a4 24 − a3 23 = 2. Ich glaube, ich kann ab jetzt auf die exakte Durchf¨ uhrung des Rituals verzichten. Wenn der Rest genau 2 ist, dann gibt es nat¨ urlich keine Viererstelle, sondern nur noch eine Zweierstelle, und auch auf die Einserstelle kann und muss ich verzichten, denn mit der Zweierstelle ist bereits alles erledigt. Das bedeutet: a2 = 0, a1 = 1, a0 = 0.

44

1 Grundlagen

Damit habe ich alle Ziffern der Bin¨ardarstellung gefunden und muss sie nur noch aufschreiben, angefangen mit a5 = 1 an der Spitze. Insgesamt ergibt sich also: 42 = 1010102 , was auch mit meiner alten Rechnung u ¨bereinstimmt. Diese Methode funktioniert immer. Sie werden mir allerdings zustimmen, dass es beispielsweise kein reines Vergn¨ ugen ist, die gr¨ oßte Zweierpotenz zu finden, die 1896268482366748 nicht u ¨bersteigt, und dass u ¨berhaupt das ganze Verfahren doch ein wenig zur Schwerf¨ alligkeit neigt. Und noch ein Grund spricht gegen die Methode. Sie werden sich erinnern, dass Sie auf Ihrer Tastatur Dezimalzahlen eingeben und der Computer dann mit Bin¨ arzahlen rechnet. Er muss also irgendwie die eingegebenen Dezimalzahlen in Bin¨ arzahlen umrechnen, und genau dieses Problem beackere ich ja gerade. Aber kann er das auf die Weise, die ich Ihnen vorgestellt habe? Er kann es nicht. Das beschriebene Umrechnungsverfahren ist voll von dezimalen Rechnungen, andauernd werden irgendwelche Dezimalzahlen von anderen Dezimalzahlen abgezogen, werden wieder andere Dezimalzahlen miteinander verglichen, und das wird Ihr Computer beim besten Willen nicht auf die Reihe bekommen. Um dieses Verfahren durchf¨ uhren zu k¨onnen, m¨ ussten Sie ihm erst einmal alle vorkommenden Zahlen in bin¨ arer Form zur Verf¨ ugung stellen, und das heißt, Sie m¨ ussten die Umrechnung erledigen, bevor Sie die Umrechnung erledigen. Klingt nicht gut und w¨are auch nicht gut; wir m¨ ussen uns nach einem besseren Umrechnungsverfahren umsehen. Ich werde Ihnen jetzt eine deutlich einfachere Umrechnungmethode zeigen. Das Prinzip sehen wir uns erst einmal am Beispiel der Dezimalzahl m = 247 an. Was passiert, wenn ich dieses m durch 10 teile und dabei den Rest aufschreibe? Dann ist 247 : 10 = 24 Rest 7. Das Divisionsergebnis 24 teile ich wieder mit Rest durch 10 und erhalte 24 : 10 = 2 Rest 4. Wenn ich dann zum Schluss auch dieses Divisionsergebnis durch 10 teile, so ergibt sich 2 : 10 = 0 Rest 2. Was f¨allt Ihnen auf, wenn Sie die Divisionsreste von unten nach oben betrachten? Sie ergeben nebeneinander geschrieben wieder genau die Zahl 247. Man erh¨alt also die Dezimalstellen einer Zahl, indem man immer wieder mit Rest durch 10 teilt und anschließend die Reste in umgekehrter Reihenfolge aufschreibt. Und bei der Bin¨ ardarstellung ist das nicht anders, nur dass Sie hier nat¨ urlich nicht durch 10 teilen, sondern durch die Basiszahl der Bin¨ arzahlen, also durch 2. Sch¨on herausgefunden, aber bisher ist das nur eine Vermutung, die auf einem Analogieschluss von Dezimalzahlen auf Bin¨ arzahlen beruht. Sehen wir

1.4 Bin¨ are Arithmetik

45

uns erst einmal an einem Beispiel an, ob das auch funktioniert. Dazu betrachte ich die Dezimalzahl m = 13. Im Folgenden schreibe ich auf, was passiert, wenn man andauernd durch 2 teilt, die Reste aufschreibt und dann das jeweilige Divisionsergebnis wieder durch 2 teilt. Dann gilt: 13 6 3 1

: : : :

2 2 2 2

= = = =

6 Rest 1 3 Rest 0 1 Rest 1 0 Rest 1.

Schreibt man nun die Reste in umgekehrter Reihenfolge als Bin¨ arzahl auf, so ergibt sich 11012 = 8 + 4 + 1 = 13, also ist alles in bester Ordnung. Das gibt Anlass zur Hoffnung, und wie stimmen vermutlich darin u ¨berein, dass diese Methode doch etwas einfacher ist als die erste. Man muss nur oft genug durch zwei teilen und die Divisionsreste richtig herum aufschreiben. Ich formuliere das jetzt wieder als ein allgemeing¨ ultiges Verfahren.

Umrechnung einer Dezimalzahl in eine Bin¨ arzahl Das folgende Verfahren rechnet eine Dezimalzahl m in ihre Bin¨ ardarstellung an an−1 ...a1 a0 um. • Man dividiere m durch 2 und notiere den Rest bei der Division. • Man dividiere das Divisionsergebnis durch 2 und notiere wieder den Rest bei dieser Division. • Man wiederhole den letzten Schritt, bis als Divisionsergebnis der Wert 0 auftritt. • Man schreibe die aufgetretenen Reste in der umgekehrten Reihenfolge ihres Auftretens als Bin¨arzahl. Die ermittelte Bin¨arzahl ist dann die bin¨are Darstellung von m. Vielleicht sollte ich noch ein Wort u ¨ber das Abbruchkriterium verlieren: sobald das Divisionsergebnis eine Null liefert, soll ich aufh¨ oren. Was w¨ urde geschehen, wenn ich diese Vorschrift ignoriere? Im n¨ achsten Schritt w¨ urde ich das alte Divisionsergebnis durch 2 teilen, und da ich vorher bereits eine Null ermittelt habe, w¨are das Ergebnis schlicht 0 Rest 0. Und auch alle weiteren Schritte k¨ onnen mir nur immer mehr Nullen liefern. Die Reste muss ich dann aber in der umgekehrten Reihenfolge ihres Auftretens notieren, und das heißt, dass ich auf diese Weise nur eine Unmenge f¨ uhrender Nullen produziere, von denen es in Politik und Wirtschaft schon genug gibt, und die meine Bin¨ arzahl sicher nicht verbessern. Deshalb kann ich das Verfahren beenden, sobald das Divisionsergebnis eine Null liefert. Nach dem Notieren eines Verfahrens sollte man immer noch ein Beispiel rechnen, damit man sich an das Verfahren gew¨ ohnt. Zum Gl¨ uck ist die Rechenmethode ziemlich u arende Kommen¨bersichtlich, sodass ich hier auf erkl¨ tare verzichten kann. Ich werde jetzt also m = 157 in seine Bin¨ ardarstellung umrechnen. Es gilt:

46

1 Grundlagen

157 78 39 19 9 4 2 1

: 2 = 78 Rest1 : 2 = 39 Rest0 : 2 = 19 Rest1 : 2 = 9 Rest1 : 2 = 4 Rest1 : 2 = 2 Rest0 : 2 = 1 Rest0 : 2 = 0 Rest1.

Folglich ist 157 = 100111012 . Sie sollen hier aber nicht nur h¨ ubsche Rechenmethoden kennenlernen, sondern auch wissen, warum sie funktionieren. Es n¨ utzt Ihnen sp¨ ater schließlich auch nichts, wenn Sie in einer Firma wissen, mit welchem Programm man die Datens¨atze sortiert und welche Arbeitsschritte dabei durchgef¨ uhrt werden m¨ ussen, aber nichts dar¨ uber wissen, nach welchem Prinzip Ihre Programme funktionieren: dann w¨ aren Sie beim ersten Problem verloren, weil Sie nicht w¨ ussten, wo Sie anfangen sollen, nach der Probleml¨ osung zu suchen. Zum Gl¨ uck ist die Begr¨ undung f¨ ur das Divisionsverfahren leicht einzusehen. Aus dem ersten Verfahren wissen wir, dass man m auf jeden Fall als Bin¨arzahl schreiben kann, also als Summe von Zweierpotenzen m = an · 2n + an−1 · 2n−1 + · · · + a1 · 2 + a0 , wobei an an−1 ...a1 a0 die Bin¨ ardarstellung von m ist. Bei der ersten Division durch 2 erh¨alt man also: m : 2 = (an · 2n + an−1 · 2n−1 + · · · + a1 · 2 + a0 ) : 2 = an · 2n−1 + an−1 · 2n−2 + · · · + a1 Rest a0 , denn jeden einzelnen Summanden kann ich auf Grund der vorhandenen Zweierpotenzen durch 2 dividieren, indem ich einfach den Exponenten um 1 heruntersetze - nur der Summand a0 hat keine Zweierpotenz bei sich, bei der man irgendetwas heruntersetzen k¨onnte, und deshalb ist er der Rest bei der Division. Bei der zweiten Division muss ich nun das Divisionsergebnis durch 2 teilen. Das liefert: (an · 2n−1 + an−1 · 2n−2 + · · · + a1 ) : 2 = an · 2n−2 + an−1 · 2n−3 + · · · + a2 Rest a1 mit dem gleichen Argument wie eben. Die erste Division hat mir als Rest a0 geliefert, also die niedrigste Bin¨arstelle von m. Die zweite Division hat dann uhrt, also zur n¨ achsten Bin¨ arstelle von m. Setzt man nun als Rest zu a1 gef¨ das Verfahren fort, so erh¨ alt man nat¨ urlich durch andauernes Dividieren als Reste alle weiteren Bin¨ arstellen, bis man zum Schluss bei an angekommen ist. Daher bekomme ich die gesamte Bin¨ardarstellung von m, indem ich alle

1.4 Bin¨ are Arithmetik

47

aufgetretenen Reste in der umgekehrten Reihenfolge ihres Auftretens von links nach rechts aufschreibe. Ein sch¨ones Verfahren, ausgesprochen einfach durchzuf¨ uhren, so viel ist wahr. Erinnern Sie sich aber an das Problem, das ich oben angesprochen hatte: kann der Computer die dringend notwendige Umrechnung von dezimalen in bin¨are Zahlen auf diese u uhren? Wieder eine der ¨bersichtliche Art durchf¨ Entt¨auschungen, die das Leben gelegentlich bereit h¨ alt; nat¨ urlich kann er es nicht, denn es wird ja eine Dezimalzahl nach der anderen durch 2 geteilt. Da der Rechner nur mit Bin¨arzahlen rechnen kann, m¨ usste er auch hier die Umrechnung schon vorgenommen haben, bevor er sie vornehmen kann. Die bisher besprochenen Umrechnungsverfahren sind sehr praktisch beim Umrechnen von Hand, dem Computer n¨ utzen sie gar nichts. Jetzt k¨onnen Sie die Aufgabe 1.13 l¨ osen. 1.4.4 Addition Der letzte Abschnitt endete etwas trostlos, aber das ist kein Grund aufzugeben. Ein Umrechnungsverfahren, das auch der Computer versteht, wird sich aus den Grundrechenarten ergeben, u ¨ber die ich hier sprechen will. Es reicht n¨amlich nicht, Dezimalzahlen in Bin¨ arzahlen umrechnen zu k¨ onnen, das Rechenwerk des Computers soll schließlich auch in der Lage sein, konkrete Rechnungen durchzuf¨ uhren, sonst haben alle unsere Bem¨ uhungen keinen Sinn. Fangen wir mit der Addition an. Sie ist extrem einfach, wenn es darum geht, nur die bin¨aren Ziffern zu addieren, denn davon gibt es nur zwei. Indem Sie die entsprechenden dezimalen Rechnungen als Vergleich heran ziehen, finden Sie: 02 + 02 = 02 , 02 + 12 = 12 , 12 + 02 = 12 , 12 + 12 = 102 . Das letzte Ergebnis gibt auch schon einen Hinweis auf Additionen von mehrstelligen bin¨aren Zahlen, denn man kann hier eine Analogie zum Addieren asst sich nicht von dezimalen Zahlen feststellen: die Summe von 12 und 12 l¨ mehr mit Hilfe einer einzigen Ziffer ausdr¨ ucken, und daher geht man zu zweistelligen Zahlen u ¨ber, wie man das im dezimalen Bereich zum Beispiel bei ¨ 5 + 5 auch machen w¨ urde. So etwas nennt man einen Ubertrag in die n¨ achste ¨ Stelle, wobei hier der Ubertrag schon bei der Addition von zwei Einsen ent¨ ¨ steht. Uberraschend ist das aber eigentlich nicnt; ein Ubertrag entsteht dann, wenn beim Zusammenz¨ahlen die Basiszahl erreicht ist, und das ist bei bin¨ aren Zahlen nun mal die 2. Sobald ich also die Zahl 2 aufschreiben m¨ usste, bin ich ¨ gezwungen, zu einem Ubertrag Zuflucht zu nehmen, weil in meinem bin¨ aren System die Ziffer 2 nicht existiert. Das klingt alles ganz ¨ ahnlich wie bei den Dezimalzahlen, nur eben mit der Basis 2 statt mit der Basis 10. Deshalb funktioniert das schriftliche Addieren auch ganz genauso wie im Falle von Dezimalzahlen: Man schreibt die bin¨ aren Zahlen untereinander und addiert dann der Reihe nach von rechts nach links,

48

1 Grundlagen

¨ wobei man Ubertr¨ age jeweils in die n¨ achste Stelle mitnimmt. Das Beste wird sein, ich zeige Ihnen das an Beispielen. Zun¨achst m¨ ochte ich die beiden bin¨ aren undig unterZahlen 100012 und 10112 addieren. Dazu schreibe ich sie rechtsb¨ einander und fange von rechts nach links mit dem Addieren an. +

100 01 1 01111

111 00 alt das Sie sehen, was passiert. In der Einserstelle ist 12 + 12 = 102 , also erh¨ ¨ Ergebnis in der Einserstelle eine 0, und den Ubertrag 1 schleppe ich mit in die Zweierstelle. Nun habe ich in der Zweierstelle die Addition 12 + 12 + 02 , ¨ denn den Ubertrag muss man mitaddieren, und das ergibt wieder 102 . Somit schreibe ich in die Zweierstelle des Ergebnisses wieder eine 0 und belaste die ¨ Viererstelle mit einem neuen Ubertrag 1. So langsam bekommen wir Routine. In der Viererstelle ergibt sich die Addition 12 + 02 + 02 , was genau 12 ergibt ¨ und mir f¨ ur die Achterstelle jeden Ubertrag erspart. Sowohl in der Achterals auch in der Sechzehnerstelle ist dann nur eine bin¨ are 12 auf eine bin¨ are 02 zu addieren mit dem jeweils gleichen Ergebnis 12 . Insgesamt komme ich unden sollte ich nachsehen, auf das Ergebnis 111002 , und aus Sicherheitsgr¨ ob das u ¨berhaupt stimmt. Das ist aber kein Problem. Aus 100012 = 17 und 10112 = 11 folgt sofort 100012 + 10112 = 17 + 11 = 28 = 111002 , denn 111002 = 16 + 8 + 4 = 28. Es passt also alles zusammen. ¨ Ein Beispiel k¨onnte reiner Zufall sein, und außerdem kann ein wenig Ubung in einem neuen Verfahren nicht schaden. Ich addiere jetzt also die beiden bin¨ aren Zahlen 1110112 und 111012 . Nach dem eben besprochenen Schema ergibt das die folgende Rechnung. 11 1 0 11 + 1111111011

101 1 0 00 Hier geschieht auch nichts anderes als vorher. Bei der Addition der Einser-, Zweier- und Viererstellen erhalte ich jeweils das Ergebnis 102 , also eine 0 in ¨ der Ergebniszahl und eine 1 im Ubertrag. Bei der Achterstelle m¨ ussen Sie ein urlich wenig aufpassen, denn hier tritt die Addition 12 + 12 + 12 auf, die nat¨ uhrt, also zu einer 1 in der Ergebniszahl und einer zu dem Resultat 112 f¨ ¨ 1 im Ubertrag. Das gleiche passiert in der Sechzehnerstelle, w¨ ahrend dann in der Zweiundreißigerstelle die Addition 12 + 12 Sie zu dem Ergebnis 102 bringt. Noch ein kurzer Blick auf die Kotrollrechnung: es gilt 1110112 = 59 und 111012 = 29, also 1110112 + 111012 = 59 + 29 = 88 = 10110002 , denn 10110002 = 64 + 16 + 8 = 88. Es ist nichts schief gegangen. Und dabei geht auch nie etwas schief, das ist genau die Methode, mit der man bin¨are Zahlen addiert und die auch der Computer bei seinen Rechenoperationen anwendet. Das d¨ urfte schon eine allgemeine Regel wert sein.

1.4 Bin¨ are Arithmetik

49

Addition bin¨ arer Zahlen Man addiert bin¨ are Zahlen, indem man sie untereinander schreibt, von rechts nach links stellenweise addiert, die Einerziffer der Addition in die Ergebnis¨ zahl schreibt und eventuell auftretende Ubertr¨ age in die jeweils n¨ achste Stelle u bernimmt. ¨ K¨ urzer gesagt: man addiert bin¨ar genauso wie dezimal, nur eben zur Basis 2. 1.4.5 Subtraktion und Zweierkomplement ¨ Auch die bin¨ are Subtraktion bietet auf den ersten Blick keine Uberraschungen, allerdings wird es hier zu einem zweiten Blick kommen. Zun¨ achst einmal kann man sich auf den Standpunkt stellen, dass auch hier das von den Dezimalzahlen gewohnte Verfahren analog zur Addition verwendet werden kann, und das funktioniert auch problemlos. Ich zeige Ihnen das an einem kleinen Beispiel, indem ich 110012 − 10102 berechne. Wie u ¨blich schreibt man die beiden Zahlen untereinander und rechnet von rechts nach links. 11 0 01 − 111011 0

1 1 11 Sie m¨ ussen dabei immer nur darauf achten, alles ganz genauso zu machen wie bei dezimalen Zahlen - nur mit dem Unterschied, dass die Basis hier 2 lautet und nicht 10. In der Einserstelle wollen sie von 0 auf 1 z¨ ahlen, was keinerlei Schwierigkeiten macht und zum Resultat 1 f¨ uhrt. In der Zweierstelle sieht es schon schlechter aus, Sie m¨ ussen von 1 auf 0 z¨ ahlen, und das geht nat¨ urlich nicht. Die 0 wird daher als 102 interpretiert, die 1, die Ihnen zu ¨ unten in die Viererstelle geschrieben, dieser 102 oben fehlt, wird als Ubertrag und in der Ergebiszahl haben Sie eine 1, da 12 + 12 = 102 gilt; so h¨ atten sie das bei vergleichbaren Verh¨altnissen im Dezimalsystem auch gemacht. Diese ¨ Situation wiederholt sich auf Grund des Ubertrages in der Viererspalte: das ¨ Z¨ahlen von der 1 auf die 0 liefert Ihnen einen Ubertrag in die Achterstelle und in der Ergebniszahl wieder eine 1. Jetzt sehe ich mir die Achterstelle an, und ¨ da sieht die Sache anders aus. Durch den Ubertrag, den mir die Viererstelle eingebrockt hat, steht jetzt bei der unteren Zahl 12 + 12 = 102 , und von dieser 102 aus muss ich hochz¨ahlen auf 12 . Das geht nur dann, wenn diese ¨ f¨ ur die bin¨are 12 eigentlich eine 112 ist und ich mir wieder einen Ubertrag Sechzehnerstelle aufhalse. Hier habe ich dann nur noch von der 1 auf die 1 zu z¨ahlen, was ich mir genausogut sparen kann. Der u ¨bliche Test sollte auch beim Subtrahieren nicht fehlen. Es gilt 110012 = 25 und 10102 = 10, also urlich gilt 11112 = 8 + 4 + 2 + 1 = 15. 110012 − 10102 = 25 − 10 = 15, und nat¨ Wieder ist alles gut gegangen.

50

1 Grundlagen

Es ist nicht einzusehen, warum ich beim Subtrahieren gutgl¨ aubiger sein soll als ich es beim Addieren war; ein zweites Beispiel sollte ich schon noch anf¨ uhren. Berechnen wir also 1010012 − 111112 . 1 0 1 0 01 − 1 1111111 1

1 0 10 Es gibt nichts Neues unter der Sonne, schon gar nicht beim Subtrahieren. In der Einserstelle pasiert gar nichts, denn das Hochz¨ ahlen von der 1 zur 1 liefert f¨ ur die Ergebniszahl eine 0. Auch die Zweierstelle verh¨ alt sich nicht ungew¨ ohnlich, ich z¨ahle von 1 auf 0 hoch und erhalte um den Preis eines ¨ Ubertrags die 1 f¨ ur die Ergebniszahl. Immerhin etwas neuer ist die Lage bei der ¨ Viererstelle. Wegen des Ubertrags habe ich in der unteren Zahl jetzt 12 + 12 = urlich wieder den 102 , was ich auf eine 0 hochz¨ahlen muss - das liefert nat¨ ¨ Ubertrag 1 f¨ ur die Achterstelle und die 0 f¨ ur die Ergebniszahl. Der Rest der Rechnung besteht nur noch aus Altvertrautem, weshalb ich nicht mehr n¨ aher draus eingehe. Und auch der Test kann mich nicht mehr wirklich aus der Ruhe bringen; es gilt 1010012 = 41 und 111112 = 31, also ist 1010012 − 111112 = 41 − 31 = 10 = 10102 , denn 10102 = 8 + 2 = 10. Warum h¨ atte es auch nicht funktionieren sollen? Schon bei der Addition haben die Bin¨ arzahlen die Analogie zu den Dezimalzahlen gut vertragen, und bei der Subtraktion ist es nicht anders.

Subtraktion bin¨ arer Zahlen Man subtrahiert eine kleinere Bin¨arzahl von einer gr¨ oßeren, indem man die kleinere unter die gr¨oßere schreibt, von rechts nach links stellenweise subtra¨ hiert und eventuell auftretende Ubertr¨ age in die jeweils n¨ achste Stelle u ¨bernimmt. Auch das ist wie bei den Dezimalzahlen und von daher nichts Besonderes. Ich hatte aber schon angek¨ undigt, dass es einen zweiten Blick auf die Subtraktion geben w¨ urde, nachdem der erste Blick sich als etwas u ¨berraschungsarm herausgestellt hat. Diese Art des Subtrahierens hat n¨ amlich zwei Probleme. ¨ Beim manuellen Rechnen neigt man dazu, sich mit den Ubertr¨ agen zu verheddern, die den meisten Leuten erfahrungsgem¨aß bei der Addition leichter fallen als bei der Subtraktion. Das w¨are noch nicht weiter schlimm, denn die ganze bin¨are Rechnerei soll ja zeigen, wie ein Computer intern rechnet, und der wird sich schon nicht verheddern. Stimmt. Aber denken Sie daran, dass das Rechnen im Computer mit Hilfe von elektronischen Schaltelementen durchgef¨ uhrt wird, die man erst einmal bauen muss, und je weniger verschiedene Schaltelemente man sich u ur die Addition braucht man ¨berlegen muss, desto besser. F¨ auf jeden Fall ein Schaltelement, das bleibt nicht aus. Es w¨ are aber g¨ unstig, wenn man auch mit dieser einen Art von Schaltelementen ausk¨ ame und sich nicht noch eine neues f¨ ur die Subtraktion ausdenken m¨ usste. Ich will jetzt also

1.4 Bin¨ are Arithmetik

51

daran gehen, eine Subtraktion durchzuf¨ uhren, ohne wirklich subtrahieren zu m¨ ussen, nur durch Additionen. Am Beispiel einer dezimalen Rechnung zeige ¨ ich Ihnen, wie man Ubertr¨ age vermeiden kann, und im bin¨ aren Fall wird sich sogar der Vorgang des Subtrahierens selbst verfl¨ uchtigen und nur Additionen zur¨ ucklassen. Zun¨ achst einmal das dezimale Beispiel, es geht um 3784 − 1717. Das zugeh¨orige Rechenschema finden Sie hier, anschließend erkl¨ are ich, was beim Rechnen eigentlich passiert ist. −

9999 1717

+

8282 3784

+

12066 1

12067 −10000

2067 ¨ Erinnern Sie sich daran, dass ich ohne Ubertr¨ age abziehen wollte? Nun handelt es sich hier um vierstellige Zahlen, und die einzige vierstellige Dezimalzahl, ¨ von der ich mit Sicherheit jede vierstellige Dezimalzahl ohne jeden Ubertrag subtrahieren kann, ist die 9999. Also rechne ich erst einmal 9999 − 1717. Das ist nat¨ urlich die falsche Subtraktion, also muss ich das Ergebnis irgendwie korrigieren. Addiert man im n¨ achsten Schritt die gew¨ unschten 3784 und anschließend noch eine 1 dazu, so hat man insgesamt die Rechnung 9999 − 1717 + 3784 + 1 = 10000 + 3784 − 1717 durchgef¨ uhrt. Diese Zahl ist also genau um 10000 gr¨oßer als die gesuchte, weshalb ich dann auch am Ende noch diese u ussigen 10000 abziehe und ¨bersch¨ damit das gesuchte Ergebnis 2067 erhalte. Hier kommen zwar zwei Subtraktionen vor, aber jede ist harmlos. Die erste erfolgt ohne den leisesten Hauch ¨ eines Ubertrags, weil von 9999 subtrahiert wird. Und die zweite kann man durch das Streichen der f¨ uhrenden Ziffer 1 erledigen, denn Sie m¨ ussen hier nur die Zahl 10000 von einer f¨ unfstelligen Dezimalzahl abziehen. Auf diese Weise konnte ich subtrahieren, ohne wirklich subtrahieren zu m¨ ussen. Sie sehen, das Prinzip ist ganz einfach: man subtrahiert von der gr¨oßtm¨ogli¨ chen Zahl mit der gleichen Anzahl von Ziffern, um l¨astige Ubertr¨ age zu vermeiden, und gleicht den dabei verursachten Fehler sp¨ater durch Addieren und Streichen einer Eins wieder aus. Wie ich Ihnen gleich zeigen werde, funktioniert das gleiche Prinzip auch bei Bin¨ arzahlen mit dem zus¨atzlichen Vorteil, dass man die erste Subtraktion eigentlich u ¨berhaupt nicht vornehmen

52

1 Grundlagen

muss. Zun¨achst wieder ein Beispiel, das die Methode illustriert; ich berechne 110012 − 10102 . Damit ich keine Problem mit der Anzahl der Stellen bekomunf Stellen. Nun me, schreibe ich die 10102 als 010102 , so haben beide Zahlen f¨ haben Sie gerade gelernt, dass man die abzuziehende Zahl von der gr¨ oßtm¨ oglichen Zahl mit der gleichen Anzahl von Stellen subtrahieren muss. Bei den Dezimalzahlen bestand diese Riesenzahl aus lauter Neunen, weil 9 nun mal die h¨ochste Ziffer im Dezimalsystem ist. Und wie lautet die h¨ ochste Ziffer im Bin¨arsystem? Richtig, das ist die 1, und daher lautet die gr¨ oßtm¨ ogliche f¨ unfstellige Bin¨arzahl schlicht 111112 . Das Rechenschema ist dann das folgende. −

11111 01010

+

10101 11001

+

101110 1

101111 −100000

1111 Was ist hier geschehen? Zuerst habe ich ganz nach Plan 010102 von 111112 ¨ abgezogen, und das ging ganz ohne Ubertrag. Auf dieses Zwischenergebnis habe ich dann die urspr¨ ungliche Zahl 110012 addiert, um mich wieder der eigentlichen Aufgabe zu n¨ ahern. Nat¨ urlich habe ich jetzt 111112 zu viel auf der Rechnung, was ich noch ein wenig verschlimmere, indem ich eine schlichte ¨ uhrt zu einem Uberschuss von 111112 + 12 = 1000002 , 12 dazu addiere. Das f¨ und das ist das Beste, was mir passieren konnte. Sie sehen n¨amlich, dass agt, eine Zahl mit einer 1 vorne und mein bisheriges Ergebnis 1011112 betr¨ f¨ unf weiteren Stellen. Und davon muss ich 1000002 abziehen, ebenfalls eine 1 vorne mit f¨ unf anschließenden Nullen. Nichts k¨onnte also leichter sein, als ur nur die f¨ uhrende 1 diese u ussige 1000002 abzuziehen, ich muss daf¨ ¨bersch¨ aus bei dem bisherigen Ergebnis 1011112 streichen und finde das Endergebnis 11112 So geht das tats¨ achlich immer. Die eigentliche Subtraktion erledigen Sie, indem Sie von der gr¨ oßtm¨ oglichen Zahl mit der passenden Anzahl von Stellen subtrahieren, also von einer hinreichend langen Ansammlung von Einsen, was ¨ immer ohne Ubertrag funktioniert. Dann addieren Sie noch die urspr¨ ungliche gr¨ oßere Zahl, addieren eine weitere Eins und streichen aus dem entstandenen Ergebnis die f¨ uhrende Eins heraus, um schließlich beim Endergebnis zu landen. Sch¨on und gut, aber noch nicht wirklich u ¨berzeugend. Das Ziel war ja, eine Subtraktion hinzubekommen, ohne subtrahieren zu m¨ ussen, nur mit Additionen. Aber auch wenn es vielleicht nicht so aussieht: dieses Ziel ist

1.4 Bin¨ are Arithmetik

53

schon erreicht. Bei der Rechnung 111112 − 010102 kam beispielsweise 101012 heraus, und jetzt vergleichen Sie einmal 01010 mit 10101. F¨ allt Ihnen etwas auf? Nat¨ urlich f¨ allt Ihnen etwas auf, man kommt von der ersten Zahl auf die zweite Zahl, indem man die Nullen durch Einsen und die Einsen durch Nullen ersetzt. Das kann auch gar nicht anders sein, denn an jeder Stelle der Subtraktion subtrahieren Sie von einer bin¨ aren 1, da ich als Ausgangspunkt ahlt habe. Nun ist aber 12 − 02 = 12 , der Subtraktion die Zahl 111112 gew¨ sodass aus einer Eins eine Null wird, und 12 − 12 = 02 , sodass aus einer Null eine Eins wird. Die erste vorkommende Subtraktion ist also nur scheinbar eine echte Subtraktion, in Wahrheit reicht es, wenn Sie Einsen zu Nullen werden lassen und umgekehrt. Diese Operation ist so wichtig, dass sie einen eigenen Namen bekommen hat: das Einserkomplement. Und weil im Verlauf der Rechnung auch noch eine schlichte 12 addiert werden muss, hat man sich auch daf¨ ur einen Namen ausgedacht: die Summe aus Einserkomplement und 12 heißt Zweierkomplement.

Einserkomplement und Zweierkomplement Das Einserkomplement einer Bin¨arzahl erh¨alt man, indem man die Nullen der Zahl durch Einsen und ihre Einsen durch Nullen ersetzt. Ihr Zweierkomplement erh¨ alt man, indem man zu ihrem Einserkomplement noch 12 addiert. Damit haben wir schon alles zusammen, um eine subtraktionsfreie“ Sub” traktion einer kleineren bin¨aren Zahl von einer gr¨ oßeren durchf¨ uhren zu k¨onnen. Im Beispiel haben sie gesehen, wie es geht: man bildet erst das Einserkomplement der abzuziehenden Zahl, addiert dann die urspr¨ ungliche gr¨ oßere Zahl, und addiert auf diese Summe noch 12 . Anders gesagt: auf die urspr¨ unglich gr¨ oßere Zahl addiert man das Zweierkomplement der kleineren. Das ergibt ¨ ein bißchen zu viel, aber das macht gar nichts, denn den Uberschuss kann man beseitigen, indem man die f¨ uhrende Eins streicht.

Subtraktion bin¨ arer Zahlen Sind a und b Bin¨arzahlen und gilt a ≥ b, so berechnet man a − b folgendermaßen. Falls b weniger Stellen hat als a, erg¨anzt man b durch f¨ uhrende Nullen und bildet das Zweierkomplement von b. Dieses Zweierkomplement von b addiert man zu a und streicht von dem Ergebnis die f¨ uhrende Eins. Das Resultat ist a − b. Ist a < b, so berechnet man b − a und nimmt das Ergebnis negativ. Die f¨ uhrenden Nullen stellen nicht nur eine große Versuchung dar, sich zwischendurch kurz u außern, sondern sind ¨ber Politiker und Manager zu ¨ auch f¨ ur die Subtraktion sehr bedeutend. Wenn Sie n¨ amlich vergessen, eine zu kurz geratene Zahl b mit f¨ uhrenden Nullen auf die passende Stellenzahl zu bringen, wird das Ihr ganzes Ergebnis ruinieren. Nehmen sie als Beispiel ullten 12 lautet die Aufgabe 10012 − 12 . Das Einserkomplement der unaufgef¨ nat¨ urlich gerade 02 , und da man darauf noch eine 12 addieren muss, ergibt urde also von mir verlangen, sich das Zweierkomplement 12 . Das Verfahren w¨

54

1 Grundlagen

10012 + 12 = 10102 auszurechnen und davon die f¨ uhrende Eins zu streichen, das ergibt 0102 = 102 . Aber dummerweise ist 10012 − 12 = 10002 = 102 , und dieser Fehler konnte nur auftreten, weil ich die kleinere Zahl 12 nicht durch f¨ uhrende Nullen auf die richtige Form 00012 gebracht hatte. In diesem Fall funktioniert n¨ amlich wieder alles; das Zweierkomplement lautet 11102 + 1 = 11112 , also hat man die Addition 10012 + 11112 = 110002 , und das Streichen der f¨ uhrenden Eins ergibt das korrekte Resultat 10002 . Sie sehen: sobald man eine Grundschaltung f¨ ur die Addition hat, kann man die Subtraktion ebenfalls mit Hilfe dieser Additionsschaltung durchf¨ uhren, damit ist mein Ziel erreicht. Jetzt fehlen nur noch Multiplikation und Division, aber die sind schnell erledigt. ¨ Ubungen gef¨allig? Dann sollten Sie sich an Aufgabe 1.14 machen. 1.4.6 Multiplikation und Division Machen wir es kurz: multiplizieren und dividieren kann man Bin¨ arzahlen genauso wie Dezimalzahlen, nur dass die Ausf¨ uhrung etwas einfacher ist. Um also beispielsweise 1010112 · 1012 auszurechnen, multipliziert man den ersten Faktor mit der f¨ uhrenden Ziffer 1 des zweiten und schreibt das Ergebnis auf. Anschließend multipliziert man den ersten Faktor mit der n¨ achsten Ziffer 0 des zweiten und schreibt das Ergebnis unter das erste Ergebnis, allerdings um eine Stelle nach rechts verschoben. Und so macht man weiter, bis alle Ziffern des zweiten Faktors abgearbeitet sind. Da aber nur Einsen und Nullen vorkommen k¨onnen, sind die einzelnen Multiplikationen ausgesprochen leicht: entweder Sie schreiben den ersten Faktor hin oder eine gleichlange Reihe von Nullen. Dass man am Ende alle Zwischenergebnisse addieren muss, wird Sie kaum u ¨berraschen. Im Rechenschema sieht das dann so aus: 101011· 101

101011 000000 oder etwas k¨ urzer 101011

11010111

101011· 101

1010110 101011

11010111

Die erste Variante entspricht genau dem, was ich oben beschrieben habe, in der zweiten Variante habe ich noch ausgenutzt, dass die Multiplikation mit 0 nicht so sehr viel bringt und es gen¨ ugt, an das vorherige Ergebnis eine 0 zu h¨angen - das kennen Sie noch aus der Schule, und ich muss nicht viele Worte dar¨ uber verlieren. Wichtig ist aber, dass auch die Multiplikation genau genommen nur aus Additionen besteht und man deshalb auch hier wieder auf ¨ Additionsschaltungen zur¨ uckgreifen kann. Ubrigens ist das Multiplizieren mit Zweierpotenzen besonders angenehm: wenn sie mit 102 = 2 multiplizieren, dann m¨ ussen Sie an den anderen Faktor nur eine 0 anh¨angen, beim Multiplizieren mit 1002 = 4 zwei Nullen und so weiter.

1.4 Bin¨ are Arithmetik

55

Multiplikation bin¨ arer Zahlen Die Multiplikation bin¨arer Zahlen erfolgt analog zum bekannten dezimalen Verfahren der schriftlichen Multiplikation. Sie k¨ onnen sich sicher vorstellen, wie es bei der Division aussehen wird: die ganzzahlige bin¨are Division unterscheidet sich nicht wesentlich von der bekannten dezimalen. Wollen Sie also beispielsweise den Dividenden 101012 durch den Divisor 112 teilen, so laufen Sie so lange von links nach rechts durch den Dividenden, bis Ihr Divisor in ihn hineinpasst. Das ist wesentlich einfacher als im dezimalen Fall, denn Sie haben nur zwei Ziffern zur Verf¨ ugung statt deren zehn und deshalb passt der Divisor entweder genau einmal in die betrachtete Zahl oder gar nicht. Sobald man etwas Passendes gefunden hat, schreibt man den Divisor an die richtige Stelle unter den Dividenden, subtrahiert und zieht die n¨achste Stelle herunter. Konkret sieht das dann so aus: 10101:11=111 11

100 11

11 11

0 Also ist 101012 : 112 = 1112 . Allerdings geht nicht jede Division auf, oft genug muss man mit einem Rest rechnen, und das ist genau dann der Fall, wenn keine weiteren Stellen mehr zu finden sind, die man herunterziehen k¨onnte, und die letzte Subtraktion nicht den Wert 0 ergeben hat. Macht aber auch nichts, dann schreibt man eben den verbleibenden Rest auf wie im folgenden Beispiel. 1 0 0 0 1 : 1 0 1 1 = 1 Rest 1 1 0 1011

110 Sie m¨ ussen also nichts weiter k¨onnen als subtrahieren, und da wir ja schon die Subtraktion auf die Addition zur¨ uckgef¨ uhrt hatten, reduziert sich auch das Dividieren zum Schluss auf das schlichte Addieren. Aber nicht ganz. Immerhin sollen Sie ja auch nachsehen, in welchen Teil des Dividenden der Divisor passt, und zu diesem Zweck bleibt Ihnen nichts anderes u ¨brig als zwei bin¨are Zahlen miteinander zu vergleichen. Wie so etwas geht, werden Sie sich in einer ¨ Ubungsaufgabe u ¨berlegen. Auch bei der Division gibt es einen Spezialfall, der sie besonders einfach ussen werden l¨ asst, n¨ amlich die Zweierpotenzen. Teilen Sie durch 102 = 2, so m¨

56

1 Grundlagen

Sie nur den Dividenden seiner letzten Stelle berauben, die dann zum Divisionsrest wird. Beim Teilen durch 1002 streichen streichen Sie entsprechend die letzten beiden Stellen des Dividenden und machen sie zum Rest und so weiter. Daher ist ohne weitere Rechnung beispielsweise 10102 : 102 = 1012 , denn den Rest 0 muss ich nicht aufschreiben, und 1011012 : 1002 = 10112 Rest 012 .

Division bin¨ arer Zahlen Die Division bin¨ arer Zahlen erfolgt analog zum bekannten dezimalen Verfahren der schriftlichen Division. Zum Selbstrechnen stehen die Aufgabe 1.15 und 1.16 zur Verf¨ ugung. 1.4.7 Computerorientierte Umrechnungsverfahren Das Leben k¨ onnte sch¨on und der Abschnitt beendet sein, wenn nicht noch ganz am Anfang ein wichtiger Punkt offen geblieben w¨ are. Sie hatten gesehen, dass die besprochenen Umrechnungsverfahren von Bin¨ arzahlen in Dezimalzahlen und umgekehrt zwar beim Rechnen von Hand funktionieren, den Computer aber vor eine un¨ uberwindbare H¨ urde stellen: sie verwenden dezimale Arithmetik, und der Computer versteht nun mal nichts anderes als die bin¨ are Arithmetik. Irgendwie muss er aber in der Lage sein, die Dezimalzahlen, die ich ihm die Tastatur eingebe, in Bin¨arzahlen umzuwandeln, sonst k¨ onnte er nicht damit rechnen. Ich brauche also ein Verfahren, dass die Umrechnungen ganz ohne dezimale Rechnungen, auf rein bin¨arer Basis vornimmt. Jetzt haben wir aber - im Gegensatz zum Anfang dieses Abschnitts - alles zur Verf¨ ugung, um so ein Verfahren zu entwickeln. Ich gehe zun¨ achst von einer Dezimalzahl aus, die dem Computer beispielsweise u ¨ber die Tastatur eingegeben wird. Diese Dezimalzahl besteht aus einzelnen Ziffern. Da ich irgendwie eine Verbindung zwischen der dezimalen und der bin¨ aren Darstellung finden muss, brauche ich in meinem Rechner eine kleine Tabelle, in der die Bin¨ardarstellungen der dezimalen Ziffern 0 bis 9 sowie der 10 aufgelistet sind. Die Tabelle wird also etwa so aussehen: 0 0000 1 0001 2 0010 3 0011 4 0100 5 0101 6 0110 7 0111 8 1000 9 1001 10 1010

wobei ich mich auf vierstellige Bin¨arzahlen beschr¨anken kann. Diese Tabelle ist also in meinem Rechner gespeichert. Um nun zum Beispiel die dreistellige

1.4 Bin¨ are Arithmetik

57

Dezimalzahl 247 in eine Bin¨ arzahl umzurechnen, fange ich mit der ersten Ziffer an und sehe in der Tabelle nach, wie ihre bin¨ are Darstellung lautet: Die 2 hat die Darstellung 102 . Nun war diese 2 aber gar keine reine 2, denn hinterher kommt noch mindestens eine weitere Stelle, also war die 2 in Wahrheit mindestens eine 20. Das schadet gar nichts, ich kann ja problemlos die bisher erreichte Ziffer 102 mit der Bin¨ arzahl 10102 = 10 multiplizieren und erhalte 101002 . Die n¨achste Ziffer in meiner Dezimalzahl war 4 = 1002 . Die addiere uhrt. ich auf die bisher erreichte Bin¨arzahl, was zu 101002 + 1002 = 110002 f¨ Dezimal betrachtet, habe ich jetzt 20 + 4 = 24 gerechnet. H¨ atte meine Zahl nur zwei Stellen gehabt, dann w¨are ich jetzt fertig. Hat sie aber nicht, eine Stelle ist noch da. Meine 24 war also gar keine simple 24, sondern eine 240, muss also wieder mit der dezimalen 10 multipliziert werden. Bin¨ ar gerechnet ergibt das 110002 · 10102 = 111100002 . Jetzt bin ich immerhin so weit, dass ich die 240 bin¨ar dargestellt und dazu nicht eine einzige dezimale Rechnung vorgenommen habe. Eine Stelle ist aber noch da, n¨ amlich die 7, und da mir die gespeicherte Tabelle sagt, dass 7 = 1112 gilt, muss ich nur noch auf das bisher erreichte Ergebnis die 1112 addieren, um zu dem Endergebnis 247 = 111100002 + 1112 = 111101112 zu kommen. Dieses Verfahren f¨ uhrt immer zum Ziel. Man beginnt bei der f¨ uhrenden Ziffer der Dezimalzahl und wandelt sie mit Hilfe der Tabelle in eine Bin¨ arzahl um. Gibt es noch weitere Stellen, so multipliziert man die Ziffer in ihrer bin¨ aren ohen, und addiert die folForm mit 10102 = 10, um ihre Wertigkeit zu erh¨ gende Ziffer, nat¨ urlich in eine Bin¨ arzahl umgewandelt, dazu. Und so macht man immer weiter, immer wieder mit der bin¨ aren Entsprechung der dezimalen 10 multiplizierend und die noch folgende Ziffer in ihrer bin¨ aren Darstellung addierend, bis alle Ziffern aufgebraucht sind. Sie brauchen daf¨ ur neben der Umrechnungstabelle f¨ ur die bin¨aren Ziffern nur ein Register, also eine kleine Speicherstelle zur Aufnahme der Zwischenergebnisse und des Endergebnisses, und schon kann Ihr Rechner jede dezimale Eingabe ohne den leisesten Hauch einer dezimalen Rechnung in eine Bin¨ arzahl umrechnen.

Computerorientierte Umrechnung einer Dezimalzahl in eine Bin¨ arzahl Das folgende Verfahren rechnet eine Dezimalzahl m auf der Basis der bin¨ aren Arithmetik in ihre Bin¨ardarstellung um. • Man rechne die f¨ uhrende Ziffer von m mit Hilfe einer computerinternen Umrechnungstabelle in eine Bin¨arzahl um und speichere diese Bin¨ arzahl in einem Register R. • Falls keine weitere Ziffer folgt, ist die Umrechnung beendet. Gibt es mindestens eine weitere Ziffer, so berechne man R · 10102 , addiere darauf die aus der Tabelle entnommene Bin¨ardarstellung der folgenden Ziffer und speichere das Ergebnis wieder in R. • Man wiederhole so lange den letzten Schritt, bis alle Ziffern abgearbeitet sind.

58

1 Grundlagen

F¨ ur m = 234 ist im ersten Schritt R = 102 , im zweiten Schritt R = 102 · 10102 + 112 = 101112 , und im letzten Schritt R = 101112 · 10102 + 1002 = ardarstellung von 234 111010102 , womit dann im Register R genau die Bin¨ steht. Wieder etwas geschafft, und noch immer ist das nur die halbe Miete. Um alle Probleme zu l¨osen, muss ich auch noch eine Bin¨ arzahl in eine dezimale Zahl umrechnen k¨onnen, ohne dezimale Arithmetik zu verwenden. Das ist leichter als man denkt und beruht auf dem gleichen Prinzip wie das zweite Umrechnungsverfahren von dezimaler in bin¨ arer Richtung, das ich Ihnen am Anfang des Abschnitts gezeigt habe. Sehen wir uns noch einmal das Beispiel der Dezimalzahl m = 247 an. Was passiert, wenn ich dieses m durch 10 teile und dabei den Rest aufschreibe? Dann ist 247 : 10 = 24 Rest 7. Das Divisionsergebnis 24 teile ich wieder mit Rest durch 10 und erhalte 24 : 10 = 2 Rest 4. Wenn ich dann zum Schluss auch dieses Divisionsergebnis durch 10 teile, so ergibt sich 2 : 10 = 0 Rest 2. Man erh¨alt also die Dezimalstellen einer Zahl, indem man immer wieder mit Rest durch 10 teilt und anschließend die Reste in umgekehrter Reihenfolge aufschreibt. Und niemand kann mich daran hindern, diese Rechnungen, die ich hier gerade dezimal erledigt habe, auf bin¨ arer Basis durchzuf¨ uhren. Falls irgendeine Rechnung den Wert 111101112 ergeben hat, sagt mir das eben besprochene Prinzip, dass ich durch die dezimale 10, also durch die bin¨ are 10102 teilen und den Rest bei dieser Division notieren sollte. Die Division 111101112 : 10102 ergibt nach dem Verfahren, das wir vorhin besprochen haben genau 110002 Rest 1112 . Nun soll ich das gerade erreichte Ergebnis wieder durch 10102 teilen, und das liefert mir 102 Rest 1002 . Und wenn ich zum guten Schluss auch noch dieses Ergebnis der Teilung durch 10102 unterwerfe, bekomme ich nat¨ urlich 02 Rest 102 . Das ist gut, denn ein Blick auf die in umgekehrter Reihenfolge aufgeschriebenen Reste 102 , 1002 und 1112 zeigt, dass es sich genau um die Ziffern 2, 4 und 7 handelt: die Dezimalziffern von 247. Mit meiner kleinen Tabelle und rein bin¨ arer Arithmeitk habe ich also die Dezimaldarstellung der gegebenen Zahl 111101112 gefunden.

Computerorientierte Umrechnung einer Bin¨ arzahl in eine Dezimalzahl Das folgende Verfahren rechnet eine Bin¨arzahl m auf der Basis der bin¨ aren Arithmetik in ihre Dezimaldarstellung um. • Man teile m durch 10102 und notiere den Rest. • Man teile das Divisionsergebnis durch 10102 und notiere den neuen Rest.

1.4 Bin¨ are Arithmetik

59

• Man wiederhole den zweiten Schritt, bis als Divisionsergebnis der Wert 02 auftritt. • Man rechne die Reste mit derUmrechnungstabelle in Dezimalzahlen um und gebe diese Reste im umgekehrter Reihenfolge aus. alt man im ersten Auch hier noch ein kurzes Beispiel. F¨ ur m = 11012 erh¨ Schritt 11012 : 10102 = 12 Rest 112 . Der zweite Schritt liefert dann 12 : 10102 = 02 Rest 12 . Die beiden Reste lauten 12 = 1 und 112 = 3, also handelt es sich um die Dezimalzahl 13. ¨ Ubungsbeispiele finden Sie in Aufgabe 1.17. 1.4.8 Hexadezimalzahlen Noch eine Kleinigkeit, dann haben Sie die Rechnerarithmetik hinter sich. Wie Sie sehen konnten, sind bin¨are Zahlen recht praktisch, da sie nur mit Nullen und Einsen arbeiten, aber aus dem gleichen Grund k¨ onnen sie ziemlich schnell ziemlich lang werden. Denken Sie beispielsweise an den Arbeitsspeicher, u ¨ber den wir im letzten Abschnitt gesprochen hatten. Jede Speicherzelle hat eine eigene Adresse, und jede Adresse ist nat¨ urlich eine Nummer, die bin¨ ar dargestellt werden muss, sonst k¨onnte der Rechner nicht mit ihr umgehen. Nimmt man nur mal ein Kilobyte Arbeitsspeicher an, so laufen die Nummern dezimal gerechnet von 0 bis 1023, denn jedes Byte bekommt eine laufende Nummer. Das sieht noch nicht so schlimm aus, aber bin¨ ar heißt das: von 02 bis 11111111112 , und das ist schon nicht so leicht zu lesen. Nicht vergessen: das war nur ein Kilobyte, ein heutiger Arbeitsspeicher kann schon mal locker onnen Sie ohne zu ein Gigabyte an Arbeitsspeicher vorweisen, also 230 Byte - k¨ z¨ogern mit dreißigstelligen Bin¨arzahlen umgehen? Manchmal besteht außerdem die Notwendigkeit, sich einen Speicherauszug ausdrucken zu lassen, und der Speicher besteht nun mal aus einer Unmenge von Einsen und Nullen, keine sehr augenfreundliche Lekt¨ ure. Um so etwas absolut Unlesbares wenigstens ein wenig lesbarer zu machen, hat man die Hexadezimalzahlen entwickelt, auf deutsch die Sechzehnerzahlen. Die Idee ist einfach genug. Teilt man eine Bin¨ arzahl auf in Viererp¨ ackchen, dann kann jedes Viererp¨ackchen jeden Wert zwischen 00002 und 11112 annehmen, dezimal gerechnet zwischen 0 und 15. Wie Sie unschwer feststellen k¨onnen, sind das f¨ ur jedes Viererp¨ackchen 16 m¨ ogliche Werte, weshalb man ja auch von Hexadezimalzahlen spricht. Im Sinne einer einfachen Darstellung braucht man also f¨ ur jeden Wert zwischen 0 und 15 ein eigenes Zeichen, damit man sie leicht auseinander halten kann. Nun gut, f¨ ur die Ziffern von 0 bis 9 muss ich da nicht lange suchen und nehme selbstvetst¨ andlich die Ziffern von 0 bis 9. Nur die Zahlen von 10 bis 15 sollten noch mit Zeichen versehen werden, und daf¨ ur verwendet man die ersten f¨ unf Buchstaben des Alphabets. Man schreibt also A = 10, B = 11, C = 12, D = 13, E = 14 und F = 15. Damit wird beispielsweise 11111111112 = 11 1111 11112 = 3F F16 : zuerst teilt

60

1 Grundlagen

man die Zahl von rechts nach links in Viererbl¨ ocke auf und dann setzt man diese Viererbl¨ocke in die hexadezimalen Ziffern um. Der vorderste Block hat nur zwei Stellen, aber das schadet gar nichts, denn 112 = 00112 = 3 = 316 . Weiterhin ist 11112 = 15 = F16 , und so entsteht die Hexadezimalzahl 3F F16 . Sie werden zugeben, dass man drei g¨ ultige Stellen leichter lesen kann als acht.

Hexadezimalzahlen und Bin¨ arzahlen Man erh¨ alt den hexadezimalen Wert einer Bin¨arzahl, indem man sie von rechts nach links in Vierergruppen zusammenfasst und die hexadezimalen Werte dieser Vierergruppen aufschreibt. Umgekehrt erh¨ alt man die Bin¨ ardarstellung einer Hexadezimalzahl, indem man die einzelnen hexadezimalen Ziffern in bin¨ are Vierergruppen aufl¨ost. Zwei Beispiele sollen das Ganze noch abrunden. Zun¨ achst bestimme ich die Hexadezimaldarstellung von 110011111012 . Die Vierergruppen von rechts nach links lauten 1101, 0111 und 0110, wobei ich ganz vorne eigentlich nur eine Deiergruppe 110 habe, aber das Hinzuf¨ ugen von f¨ uhrenden Nullen hat noch nie einer Zahl geschadet. Nun gilt aber 11012 = 13 = D16 , 0111 = 7 = 716 und 0110 = 6 = 616 . Daraus folgt, wenn man wieder die richtige Reihenfolge herstellt, 110011111012 = 67D16 . Es ist ziemlich klar, dass die hexadezimale Darstellung etwas u ¨bersichtlicher ist und daher beim Lesen von Speicherausz¨ ugen in aller Regel bevorzugt wird. Nun gehe ich umgekehrt vor und suche die Bin¨ardarstellung der Hexadezimalzahl 4F A16 . Ich muss jede hexadezimale Ziffer in eine bin¨are Vierergruppe aufl¨ osen. Es gilt: A16 = 10 = 10102 , F16 = 15 = 11112 und 416 = 4 = 0100. Damit folgt: 4F A16 = 0100111110102 = 100111110102 . Genug der Arithmetik, im n¨achsten Abschnitt werden Sie sehen, wie man die Rechenoperationen konkret im Computer realisiert. ¨ Ubungen 1.12. Berechnen Sie die dezimalen Werte der folgenden Bin¨ arzahlen. (a) m = 1101100112 (b) m = 111100012 1.13. Berechnen Sie auf zwei verschiedene Arten die Bin¨ ardarstellung von m = 278. 1.14. F¨ uhren Sie die folgenden bin¨aren Rechenvorg¨ ange durch. Subtraktionen sind dabei mit Hilfe des Zweierkomplements anzugehen. (a) 11010102 + 101011102 (b) 10102 + 01101102 (c) 111011012 − 1010012 (d) 11012 − 111012

1.5 Logische Schaltungen und Addierer

61

1.15. F¨ uhren Sie die folgenden bin¨aren Rechenvorg¨ ange durch. (a) 1101102 · 10112 (b) 101001001112 · 1002 (c) 11011011012 : 11012 (d) 10000012 : 1002 1.16. Entwickeln Sie ein Verfahren, das zwei bin¨ are Zahlen der Gr¨ oße nach vergleicht und feststellt, welche die gr¨oßere ist. Beschreiben Sie das Verfahren verbal. 1.17. F¨ uhren Sie eine computerorientierte Umrechnung von einer Dezimalzahl in eine Bin¨arzahl bzw. umgekehrt durch. (a) Dezimalzahl m = 238 (a) Bin¨arzahl m = 1100112 1.18. F¨ uhren Sie eine Umrechnung von einer Bin¨ arzahl in eine Hexadezimalzahl bzw. umgekehrt durch. (a) Bin¨arzahl 1101100112 (b) Hexadezimalzahl ABC16 1.19. Eine Oktalzahl ist eine Folge aus n + 1 Ziffern an an−1 ...a1 a0 , wobei jede der Ziffern zwischen 0 und 7 liegt. Wie kann man auf sehr einfache Weise sie Umrechnung von Bin¨arzahlen in Oktalzahlen oder umgekehrt vornehmen?

1.5 Logische Schaltungen und Addierer Die bin¨are Arithmetik in allen Ehren, doch noch fehlt etwas sehr Wichtiges. Sie wissen jetzt zwar, wie man grunds¨ atzlich so rechnet, als w¨ are man ein Computer, aber wie wird das nun eigentlich auf der Basis von elektrischem Strom im Rechenwerk realisiert? Auch Einsen und Nullen kann kein Computer der Welt direkt verstehen, er versteht nur den Strom, der fließt oder es bleiben l¨asst. Wir m¨ ussen uns also ein paar Gedanken dar¨ uber machen, wie man die Rechenoperationen aus dem letzten Abschnitt konkret mit Hilfe von elektronischen Schaltungen umsetzen kann. Gl¨ ucklicherweise haben Sie bei der bin¨aren Arithmetik gelernt, dass im Grunde jede Rechenoperation auf Additionen zur¨ uck gef¨ uhrt werden kann, und deshalb werde ich mich hier darauf beschr¨anken, eine Art von Addierwerk zu bauen, das in der Lage sein soll, bin¨are Zahlen zu addieren. Wie man dann die anderen Grundrechenarten ¨ aus diesem Addierwerk zusammensetzt, folgt direkt aus den Uberlegungen des letzten Abschnitts. Das wesentliche Hilfsmittel bei der Realisierung der Addition sind die logischen Schaltungen oder auch logischen Gatter. Sie beruhen auf nur drei sehr einfachen logischen Konstruktionen, weshalb man auch drei verschiedene Grundschaltungen unterscheidet. Fangen wir vorsichtig mit der ersten an, schwer ist keine von ihnen.

62

1 Grundlagen

1.5.1 Die UND-Schaltung Aus dem t¨aglichen Sprachgebrauch ist Ihnen die Verwendung des Wortes und“ mehr als vertraut, und genau diesen Sprachgebrauch setzt die so ge” nannte UND-Schaltung oder auch AND-Schaltung um. Denken Sie immer daran, dass es hier um Einsen und Nullen geht, also um fließenden oder nicht fließenden Strom. Hat man nun beispielsweise zwei bin¨ are Inputs, also vielleicht zwei Nullen oder auch eine Null und eine Eins, so m¨ ochte man darauf auf einfache Weise einen bin¨ aren Output machen, und eine M¨ oglichkeit dazu bietet die UND-Schaltung. Sie liefert genau dann eine 1 im Output, wenn alle Inputs auf 1 stehen; hat man auch nur einen auf 0 stehenden Input, so ergibt sich am Ende der UND-Schaltung gnadenlos eine 0. Physikalisch betrachtet, kann sie durch eine Reihenschaltung oder auch Serienschaltung wie in Abbildung 1.10 realisiert werden.

A

B

Abb. 1.10. Reihenschaltung

Sie sehen eine Stromquelle, die durch die beiden senkrechten Linien verschiedener L¨ange symbolisiert wird. Das seltsame runde Ding rechts ist das g¨ angige Symbol f¨ ur einen Stromverbraucher, also eine Gl¨ uhbirne, ein PC oder sonst irgendetwas, das Ihre Stronmrechnung hochtreibt. Offenbar kann nur dann Strom fließen, wenn die beiden Schalter A und B geschlossen sind, ansonsten wird der Stromfluss r¨ ude unterbrochen und der Stromverbraucher wird Ihre Rechnung nicht belasten. Stellt man sich einen Schalter als eine Art Relais vor, dann kann der Schalter geschlossen werden, wenn er duch einen magnetisierten Eisenkern nach unten gezogen wird, und dieser Eisenkern wird durch fließenden Strom magnetisiert - das haben Sie schon bei der Aufgabe 1.5 gesehen. Ein existierender Inputstrom f¨ uhrt also zu einem geschlossenen Schalter, anders gesagt: steht der Input auf 1, so wird der Schalter geschlossen. Steht er dagegen auf 0, wird niemand mehr den Schalter nach unten ziehen, er bleibt offen und der betrachtete Stromkreis kann nicht geschlossen werden. Nat¨ urlich wird so etwas heute nicht mehr u ¨ber elektromechanische Relais geregelt, sondern u ¨ber vollelektronische Schaltelemente, aber darauf kommt es hier gar nicht an. Wichtig ist, dass Sie sehen, wie Input und Output zusammenh¨angen: stehen beide Inputs auf 1, ist der Outputstromkreis geschlossen,

1.5 Logische Schaltungen und Addierer

63

steht also abenfalls auf 1. Steht auch nur einer der Inputs auf 0, ist der Outputstromkreis unterbrochen und steht daher auf 0. Die UND-Schaltung liefert also genau dann eine 1, wenn alle Inputs auf 0 stehen, ansonsten liefert sie eine triste 0. Das Formelsymbol hat man der Aussagenlogik entnommen, f¨ ur zwei Inputs schreibt man A∧B, f¨ ur drei Inputs A, B und C entsprechend A∧B ∧C. Ich will mich hier nicht u ur das UND-Symbol ∧ verbrei¨ber die Rechenregeln f¨ ten, das geh¨ort in Ihre Vorlesungen zur Algebra, Diskreten Mathematik oder auch Digitaltechnik. Wir sehen uns lieber an, wie sich die UND-Schaltung in einer konkreten Tabelle ausdr¨ uckt. Schwer ist das nicht, man muss nur alle m¨ oglichen Input-Kombinationen und ihren jeweiligen Output ordentlich in einer Tabelle zusammenfassen. Bei drei Inputs sieht das dann folgendermaßen aus. A B C A∧B∧C 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1 0 1 1 0 1 1 1 1

Sie sehen auch hier wieder: der Output liefert Ihnen genau dann eine 1, wenn alle Inputs auf 0 standen. Beachten Sie u oglichen Input¨brigens, wie ich die m¨ werte in der Tabelle eingetragen habe. Da es sich um drei Inputs handelt, habe ich es mit dreistelligen bin¨ aren Zahlen zu tun, und deshalb habe ich einfach die dreistelligen bin¨ aren Zahlen in die Zeilen der Tabelle hinein geschrieben. Von 0002 bis 1112 , also von 0 bis 7, gibt es aber genau acht Zahlen, daher hat die Tabelle auch acht Zeilen.

A

&

A

B

B Abb. 1.11. Symbol der UND-Schaltung

Sollten Sie jedesmal, wenn Sie eine UND-Schaltung in ein etwas komplizierteres Schaltsystem einzeichnen m¨ ussen, das komplette Bild aus Abbildung 1.10 abmalen, dann w¨ urden Sie sich mit Recht beschweren. Die Beschwerde kann aber gar nicht erst aufkommen, denn man hat sich ein graphisches Symbol u ¨berlegt, das die Einzeichnung von UND-Schaltungen in Schaltbilder zur ¨ leichten Ubung werden l¨ asst; Sie sehen dieses f¨ ur sich sprechende Symbol in Abbildung 1.11.

64

1 Grundlagen

UND-Schaltung Die UND-Schaltung bestimmt aus mehreren bin¨ aren Inputs einen bin¨ aren Output. Der Output ist genau dann 1, wenn alle Inputs auf 1 stehen; hat auch nur ein Input den Wert 0, so liefert die UND-Schaltung den Output 0. Die UND-Verkn¨ upfung von zwei Inputs A und B wird mit dem Symbol A ∧ B symbolisiert.

1.5.2 Die ODER-Schaltung Das Wort oder“ hat in der deutschen Sprache zwei Bedeutungen, auch wenn ” man sich das nicht immer klar macht. Der Straßenr¨ auber, der Ihnen auf der BEbene der Frankfurter Konstablerwache den freundlichen Satz Geld her oder ” ich schieße“ zuraunt, meint genau genommen entweder-oder“, denn sobald ” Sie Ihre Brieftasche herausr¨ ucken, sollte man zumindest hoffen, dass er Sie ungeschoren l¨ asst. Im Zusammenhang mit der Informatik im Allgemeinen und der Logik sowie den Schaltungen im Besonderen ist etwas anderes gemeint, aber ich bin ja auch kein Straßenr¨auber. Unser Oder ist ein Oder-Auch, ein einschließendes Oder, sodass mit A oder B“ immer gemeint ist: A oder B ” oder beides. Nach dieser Vorrede ist vielleicht schon klar, was man unter einer ODERSchaltung bzw. OR-Schaltung zu verstehen hat. Sie liefert genau dann den Output 1, wenn mindestens einer der beteiligten Inputs den Wert 1 hat; nur wenn alle Inputs auf 0 stehen, wird sie den Output 0 liefern. Physikalisch betrachtet, kann sie durch eine Parallelschaltung wie in Abbildung 1.12 realisiert werden.

A

B

Abb. 1.12. Parallelschaltung

Im Gegensatz zur UND-Schaltung ist die ODER-Schaltung nicht allzu kleinlich. Wenn einer der beiden Schalter offen ist, kann der Strom noch immer durch die andere Leitung fließen, sodass der Stromkreis geschlossen bleibt. Bei mehr als zwei Schaltern ist das nat¨ urlich genauso: sobald auch nur ein Schalter geschlossen ist, also mindestens ein Input den Wert 1 hat, wird die

1.5 Logische Schaltungen und Addierer

65

ODER-Schaltung den Output 1 liefern, und so war es ja auch geplant. Erst bei durchg¨angig ge¨ offneten Schaltern, also bei einer Belegung aller Inputs mit Nullen, erhalten Sie den Output 0. Das Formelsymbol hat man wieder der Aussagenlogik entnommen, f¨ ur zwei Inputs schreibt man A ∨ B, f¨ ur drei Inputs A, B und C entsprechend A ∨ B ∨ C und so weiter. Wie sieht das nun in einer Tabelle aus? Ganz einfach. Nehmen wir wie im Falle der UND-Schaltung wieder drei Inputs A, B und C an, so ist A ∨ B ∨ C genau dann 0, wenn sowohl A = 0 als auch B = 0 als auch C = 0 gilt, in allen anderen F¨ allen kommt 1 heraus. An der Menge der m¨ oglichen Inputkombinationen ¨ andert sich gar nichts, es sind immer noch die gleichen acht M¨oglichkeiten wie bei der Tabelle f¨ ur die UND-Schaltung. Damit ergibt sich die folgende Tabelle. A 0 0 0 0 1 1 1 1

B 0 0 1 1 0 0 1 1

A∨B∨C 0 1 1 1 1 1 1 1

C 0 1 0 1 0 1 0 1

Und auch f¨ ur die ODER-Schaltung hat sich ein eigenes Symbol durchgesetzt, das Sie in Abbildung 1.13 bewundern k¨ onnen.

A 1

A

B

B Abb. 1.13. Symbol der ODER-Schaltung

ODER-Schaltung Die ODER-Schaltung bestimmt aus mehreren bin¨ aren Inputs einen bin¨ aren Output. Der Output ist genau dann 0, wenn alle Inputs auf 0 stehen; hat auch nur ein Input den Wert 1, so liefert die ODER-Schaltung den Output 1. Die ODER-Verkn¨ upfung von zwei Inputs A und B wird mit dem Symbol A ∨ B symbolisiert.

66

1 Grundlagen

1.5.3 Die NICHT-Schaltung Ein logisches Gatter fehlt noch in der Liste, n¨ amlich die Schaltung f¨ ur pubertierende Jugendliche: was auch immer Sie ihnen sagen, sie wollen ohnehin das Gegenteil. F¨ ur solche Zwecke hat man die NICHT-Schaltung oder auch NOTSchaltung erfunden, und sie ist von allen drei logischen Grundschaltungen die ¨ einfachste - aus einer 1 macht sie eine 0, aus einer 0 dagegen eine 1. Ubersichtlicher gehts nicht mehr. Man spricht auch von der Negation eines Inputs und meint damit, dass sich der Wert des Inputs genau in sein Gegenteil verkehrt. F¨ ur diese Negation sind sogar zwei verschiedene Symbole im Gebrauch; ist A ein Input f¨ ur die NICHT-Schaltung, so wird der Output von manchen als A und von anderen als ¬A bezeichnet. Beides ist gebr¨ auchlich, und Sie k¨ onnen sich aussuchen, was Ihnen besser gef¨allt. Die tabellarische Darstellung der Negation ist ausgesprochen u ¨bersichtlich, da ich nur einen Input und einen Output habe. Das f¨ uhrt zu der folgenden Tabelle. A A 0 1 1 0

Es wird niemanden u ur die NICHT-Schaltung ¨berraschen, dass sich auch f¨ ein Symbol durchgesetzt hat, das ihre Darstellung in gr¨oßeren Schaltbildern vereinfacht. In Abbildung 1.14 ist es zu sehen.

1

A

A

Abb. 1.14. Symbol der NICHT-Schaltung

NICHT-Schaltung Die NICHT-Schaltung bestimmt aus einem bin¨aren Input einen bin¨aren Output. Der Output ist genau dann 1, wenn der Input auf 0 steht und genau dann 0, wenn der Input auf 1 steht. Wird ein Input A der NICHT-Schaltung unterworfen, so schreibt man f¨ ur den Output A oder auch ¬A.

So viel zu den Grundschaltungen. Jetzt sehen wir uns an, was man damit anfangen kann. 1.5.4 Der Halbaddierer Es mag ja ganz nett sein, u ugen, aber ¨ber die drei Grundschaltungen zu verf¨ mein eigentliches Ziel war es, eine Schaltung aufzubauen, die in einem Rech-

1.5 Logische Schaltungen und Addierer

67

ner die bin¨are Addition realisiert, denn das ist die Grundoperation f¨ ur die gesamte Arithmetik. Von diesem Ziel bin ich aber nicht mehr sehr weit entfernt. Was passiert denn, wenn ich beispielsweise zwei bin¨ are Ziffern addiere? ¨ Es ergibt sich eine Ergebnisziffer und unter Umst¨ anden ein Ubertrag, den ich beim Addieren der n¨achsten beiden Ziffern ber¨ ucksichtigen muss. Anders gesagt: ich habe zwei Inputs, n¨amlich die beiden zu addierenden Ziffern, und ¨ zwei Outputs, n¨amlich die Ergebnisziffer und den Ubertrag, und das kann man, wenn man Spaß daran hat, in einer Tabelle darstellen, genau wie bei den Tabellen der Grundschaltungen. Das gilt aber nicht nur f¨ ur die Addition zweier Bin¨arziffern. Wann immer Sie ein Zuordnung von einem oder mehreren bin¨aren Inputs zu einem oder mehreren bin¨aren Outputs haben, k¨ onnen Sie nat¨ urlich eine Tabelle aufbauen, in deren linken Teil Sie alle m¨ oglichen Inputkombinationen auflisten und in deren rechten Teil Sie die jeweiligen Outputs f¨ ur die jeweilige Inputkombination aufschreiben. So etwas ist ein Geduldsspiel, weiter nichts. Tabellen n¨ utzen mir aber nichts, ich brauche Schaltungen, die auf meinen drei Grundschaltungen beruhen. Und schon haben wir ein kleines Problem: wenn eine Funktionstabelle aus einem oder mehreren Inputs und einem oder mehreren Outputs gegeben ist, wie kann man dann nur aus den drei Grundschaltungen eine Schaltung aufbauen, die genau diese Funktionstabelle realisiert? Das ist leichter als man vielleicht denkt, ich zeige es Ihnen einmal am Beispiel des ausschließenden ODER. Die ODER-Schaltung, auf die wir uns vorhin geeinigt hatten, war bekanntlich ein einschließendes Oder, ein OderAuch. Denkt man an den Straßenr¨auber in der Konstablerwache, so f¨ allt einem sofort wieder das alte Entweder-Oder ein, das viel strenger ist als unser altes Oder-Auch: entweder A oder B kann nur heissen, dass nicht beide gleichzeitig etwas zum Ergebnis beitragen k¨ onnen, und das bedeutet, das Ergebnis ist dann 1, wenn entweder A auf 1 steht oder B auf 1 steht. Sollten beide gleichzeitig auf 0 oder beide gleichzeitig auf 1 stehen, muss sich der Output 0 ergeben. Man bezeichnet das als ein ausschließendes ODER, abgek¨ urzt als XOR, wobei das X f¨ ur eXclusive“ steht, und verwendet daf¨ ur das Symbol ” A ⊕ B. In einer Tabelle sieht das so aus: A 0 0 1 1

B 0 1 0 1

A⊕B 0 1 1 0

Offenbar liefert A ⊕ B genau dann eine 1 wenn entweder A oder B auf 1 steht. Alles klar, aber ich will doch nur die drei Grundschaltungen verwenden und nicht alle f¨ unf Minuten gezwungen sein, eine weitere Grundschaltung einzuf¨ uhren. Folglich muss ich jetzt zusehen, wie ich diese neue Schaltung aus meinen alten Schaltungen zusammensetze. Sehen wir uns das XOR etwas genauer an und formulieren die Bedingungen um, unter denen es den Output 1 liefert. Es ist doch A ⊕ B = 1, genau dann,

68

1 Grundlagen

wenn A = 0 und B = 1 gilt oder wenn A = 1 und B = 0 gilt. Da aber A = 0 genau dann gilt, wenn man A = 1 hat, bedeutet das: A ⊕ B = 1 gilt genau dann, wenn A = 1 und B = 1 gilt oder wenn A = 1 und B = 1 gilt. Das ist aber praktisch, denn in dieser Beschreibung kommen nur noch die Schl¨ usselworte und“, oder“ und die Negation vor, und das waren genau ” ” meine drei logischen Grundschaltungen. Wenn Sie jetzt n¨amlich die letzte Beschreibung in eine formale Schreibweise u ¨bersetzen, dann stellen Sie fest, dass genau dann A ⊕ B = 1 gilt, wenn A ∧ B = 1 oder B ∧ A = 1 gilt. Und da ich auch ein Symbol f¨ ur das ODER habe, folgt insgesamt:

A ⊕ B = (A ∧ B) ∨ (B ∧ A).

Sobald man das einmal gefunden hat, kann man es auch leicht nachpr¨ ufen, indem man einfach f¨ ur die rechte Seite eine Tabelle aufstellt und die Ergebnisspalte mit der von A ⊕ B vergleicht: nat¨ urlich sind dann beide Spalten identisch. Damit ist das XOR u ¨bersetzt in eine Kombination der drei bekannten ¨ und beliebten Grundschaltungen. Ublicherweise setzt man so etwas um in ein Schaltbild, das den Vorteil einer etwas gr¨oßeren Anschaulichkeit hat. Das Schaltbild f¨ ur das ausschließende ODER sehen Sie in Abbildung 1.15.

A

1 &

B

A

B

1

1 &

Abb. 1.15. Schaltbild der XOR-Schaltung

Die beiden Inputs heißen A und B. Von A aus ger¨at man sowohl in ein NICHT-Gatter als auch - u ¨ber eine Abzweigung, die durch einen dicken schwarzen Punkt markiert wird, - in ein UND-Gatter, und genau das Gleiche passiert mit B. Der Wert A wird dann u ¨ber eine UND-Schaltung mit B uhrt. Analog wird der Wert kombiniert, was zu dem Zwischenergebnis A ∧ B f¨ B u ¨ber die zweite UND-Schaltung kombiniert mit A selbst, und das ergibt nat¨ urlich A ∧ B. Das war es auch schon fast, denn diese beiden Zwischenergebnisse m¨ ussen nur noch in einem letzten ODER-Gatter zusammengefasst werden, um am Ende das Ergebnis A ⊕ B = (A ∧ B) ∨ (B ∧ A) zu erhalten.

1.5 Logische Schaltungen und Addierer

69

Die XOR-Schaltung Die XOR-Schaltung bestimmt aus zwei bin¨aren Inputs einen bin¨ aren Output. Der Output ist genau dann 1, wenn genau ein Input auf 1 steht, wenn also entweder der eine oder andere Input den Wert 1 hat. Haben beide Inputs den gleichen Wert, so liefert die XOR-Schaltung den Output 0. Die XORVerkn¨ upfung von zwei Inputs A und B wird mit dem Symbol A ⊕ B symbolisiert. Sie kann mit Hilfe der drei Grundschaltungen UND, ODER und NICHT erzeugt werden. Nun sind wir so weit, dass wir den ersten Addierer bauen k¨ onnen, den so genannten Halbaddierer. Er leistet nichts weiter als die Addition zweier ¨ Bin¨ arziffern und liefert dabei eine Ergebnisziffer und einen Ubertrag, beides kann 0 oder 1 werden. Da es nun um konkrete Ziffern geht, werde ich die Bezeichnungsweise ¨andern und die beiden Inputziffern mit x und y bezeichnen, ¨ die Ergebnisziffer r nennen und den Ubertrag u. Dann ist also r die Ziffer, die man beim schriftlichen Addieren unter dem Strich finden w¨ urde, und u ist ¨ der Ubertrag, der f¨ ur das weitere Rechnen u ¨ber den Strich geschrieben wird. Das ergibt die folgende Tabelle. x 0 0 1 1

x

y

y 0 1 0 1

r 0 1 1 0

u 0 0 0 1

r

x y

&

u

Abb. 1.16. Halbaddierer

Werfen Sie einen Blick auf die beiden Ergebnisspalten. Kommen sie Ihnen bekannt vor? Ich hoffe doch, denn beide haben wir bereits besprochen. Die Ergebnisziffer r entspricht genau der XOR-Verkn¨ upfung der beiden Inputs x und

70

1 Grundlagen

¨ y, w¨ahrend der Ubertrag noch einfacher gebaut ist, denn er wird genau dann 1, wenn beide Inputs auf 1 stehen und stellt daher nur die UND-Verkn¨ upfung von x und y dar. Also ist schlicht r = x ⊕ y und u = x ∧ y. Um das wieder in ein Schaltbild zu zeichnen, muss ich jetzt nat¨ urlich nicht mehr alle Einzelheiten der XOR-Schaltung aufmalen, die kennen Sie bereits aus Abbildung 1.15. Es gen¨ ugt jetzt, das XOR als eine Schaltung zu betrachten, die man bereits als Bauteil zur Verf¨ ugung hat, und diese Schaltung als eine Einheit in die gesuchte Schaltung des Halbaddieres einzubauen. In Abbildung 1.16 sehen Sie, was dabei herauskommt. Die Symbole der Schaltung habe ich bereits alle erkl¨ art, deshalb werde ich jetzt nicht mehr n¨ aher darauf eingehen. In jedem Fall haben wir hier eine Schaltung zusammengebaut, die auf der Basis von elektrischem Strom mit Hilfe der drei Grundschaltungen zwei bin¨ are Ziffern addiert und eine Ergeb¨ nisziffer sowie einen Ubertrag berechnet. Die Schaltung heißt deshalb Halbaddierer, weil sie nur die H¨ alfte der Arbeit erledigt, die man beim Addieren von Bin¨arzahlen braucht. Wenn Sie n¨amlich, wie im letzten Abschnitt besprochen, zwei bin¨are Zahlen addieren, dann ist es zwar sicher wahr, dass Sie in jeder ¨ Stelle eine Ergebnisziffer und einen Ubertrag bekommen werden, aber Sie m¨ ussen immer damit rechnen, dass aus der vorher betrachteten bin¨ aren Stelle ¨ noch ein Ubertrag da ist, den Sie ber¨ ucksichtigen m¨ ussen. Bei Additionen der Form 11 1 0 11 + 1111111011

101 1 0 00 wird es ziemlich h¨aufig passieren, dass man drei Ziffern addieren muss und nicht nur zwei, und genau das kann der Halbaddierer nicht leisten. Es hilft also nichts, ich werde das Spiel noch etwas weiter treiben und Ihnen zeigen, wie man einen Volladdierer baut, der dieses Problem l¨ ost.

Halbaddierer Der Halbaddierer bestimmt aus zwei bin¨aren Inputs zwei bin¨ are Outputs. Er addiert zwei Bin¨arziffern x und y und liefert dabei eine Ergebnisziffer r und ¨ einen Ubertrag u. Man kann ihn zusammen setzen aus einer XOR-Schaltung und einer UND-Schaltung; es gilt r = x ⊕ y und u = x ∧ y. 1.5.5 Der Volladdierer Wir hatten uns bereits geeinigt: bei der Addition zweier mehrstelliger Bin¨arzahlen wird man nicht nur jeweils zwei bin¨ are Ziffern x und y, sondern ¨ leider auch noch einen alten Ubertrag u1 aus der vorherigen Stelle addieren m¨ ussen. Diese Addition aus drei Inputs liefert dann eine Ergebnisziffer r und ¨ orige einen neuen Ubertrag u2 , genau wie schon beim Halbaddierer. Die zugeh¨

1.5 Logische Schaltungen und Addierer

Tabelle ist schnell aufgeschrieben, da Sie Schlaf beherrschen. x y u1 0 0 0 0 0 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1

71

die bin¨ are Addition mittlerweile im

r u2 0 0 1 0 1 0 0 1 1 0 0 1 0 1 1 1

Jetzt muss ich nur noch genau das Gleiche machen wie vorher bei der XORSchaltung und beim Halbaddierer: ich muss feststellen, wie ich diese Tabelle in eine vern¨ unftige Schaltung umsetzen kann. Das Prinzip ist aber auch nicht anders; solange man sich an die immer gleiche Vorgehensweise h¨ alt, kann nichts schiefgehen. Ich fange mit dem Output r an. Wann steht r auf 1? Offenbar genau dann, wenn genau ein Input auf 1 steht oder wenn alle drei Inputs auf 1 stehen. In der zweiten Zeile ist beispielsweise nur u1 = 1, und das heißt, dass sowohl x = 1 als auch y = 1 gilt. Anders gesagt: genau dann gilt gleichzeitig x = 0 und y = 0 und u1 = 1, wenn x ∧ y ∧ u1 = 1 gilt. In diesem Fall habe ich auf jeden Fall r = 1. Aber nicht nur in diesem Fall, es k¨onnte ja auch sein, dass x = 0, y = 1 und u1 = 0 gilt, wie es der dritten Zeile meiner Tabelle entspricht. Das bedeutet nat¨ urlich x = 1, y = 1 und u1 = 1, also ist dieser Fall genau onnen sich schon denken, wie es dann gegeben, wenn x ∧ y ∧ u1 = 1 ist. Sie k¨ weiter geht. In der f¨ unften Zeile hat r wieder eine 1 aufzuweisen, und weil hier x = 1, y = 0 und u1 = 0 gilt, muss x ∧ y ∧ u1 = 1 sein. Damit habe ich die F¨alle erledigt, in denen genau einer der Inputs auf 1 steht. Es bleibt noch die letzte Zeile der Tabelle, bei der u ¨berhaupt alles zu 1 wird, insbesondere auch x, y und u1 . Diese drei haben aber genau dann gleichzeitig den Wert 1, wenn upfung gemacht. x ∧ y ∧ u1 = 1 ist, denn genauso war die UND-Verkn¨ Gefunden habe ich jetzt, dass r genau dann den Wert 1 liefert, wenn x ∧ y ∧ u1 = 1 oder x ∧ y ∧ u1 = 1 oder x ∧ y ∧ u1 = 1 oder x ∧ y ∧ u1 = 1 gilt. Und weil wir f¨ ur solche Zwecke eine ODER-Verkn¨ upfung haben, heißt das:

r = (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) = (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ).

In der letzten Zeile habe ich dabei nur den ersten Klammerausdruck nach vorne gestellt, damit ich nicht gleich mit so etwas Negativem wie einer Negation anfangen muss. Sch¨on ist das nicht, aber n¨otig, denn jetzt habe ich meine logische Schaltung f¨ ur die Ergebnisziffer schon zusammen. Ich werde sie auch gleich in ein Schaltbild eintragen, aber das liefert nur noch eine graphische Veranschaulichung dieser Formel, die Informationen stecken schon vollst¨ andig in dem

72

1 Grundlagen

Ausdruck, den ich aus der Tabelle geschlossen habe. Noch bin ich allerdings ¨ nicht fertig, der Ubertrag u2 fehlt mir noch, und er verdient einen genaueren Blick. Gehen wir zun¨achst nach dem gleichen Schema vor wie eben. Der Output u2 liefert in der vierten, sechsten, siebten und achten Zeile der Tabelle eine 1, was ich wieder leicht in eine logische Schaltung u ¨bersetzen kann. In der vierten uhrt. Zeile ist x = 0, y = 1 und u1 = 1, was zu dem Ausdruck x ∧ y ∧ u1 f¨ Nach der gleichen Methode entsprechen die restlichen interessanten Zeilen den Ausdr¨ ucken x ∧ y ∧ u1 , x ∧ y ∧ u1 und x ∧ y ∧ u1 . Daraus folgt, dass

u2 = (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 )

gilt. Schon wieder so ein langes Elend. Denken Sie daran: das sollen ja konkrete Schaltungen in einem Computer werden, und je weniger Bauteile Sie ben¨otigen, desto besser f¨ ur die Kostenrechnung und f¨ ur Ihren Arbeitsplatz. ¨ urzer darstellen, mit Den Ubertrag u2 k¨onnen Sie aber tats¨achlich ein wenig k¨ einer deutlich einfacheren Schaltung. Noch einmal ziehe ich die Tabelle zu Rate und sehe mir genauer an, wann u1 = 1 ist. Das gilt n¨amlich genau dann, wenn mindestens zwei der Inputs auf 1 stehen, wenn also x ∧ y = 1 oder ullt x ∧ u1 = 1 oder y ∧ u1 = 1 ist. Sobald eine dieser drei Bedingungen erf¨ ist, k¨onnen Sie sich auf keinen Fall in der ersten, zweiten, dritten oder f¨ unften Zeile der Tabelle befinden, denn dort w¨ urde jede der drei UND-Verkn¨ upfungen den Wert 0 ergeben. In der letzten Zeile sind sogar alle drei Bedingungen auf einmal erf¨ ullt, und in den anderen drei Zeilen jeweils eine, weshalb ich u2 auch durch den wesentlich einfacheren Ausdruck u2 = (x ∧ y) ∨ (x ∧ u1 ) ∨ (y ∧ u1 ) darstellen kann. Sollten Sie daran zweifeln, dann empfehle ich Ihnen, diesen Ausdruck einfach in einer Input-Output-Tabelle auszuwerten und das Ergebnis mit u2 zu vergleichen. Sp¨atestens das wird Sie u ¨berzeugen. ¨ Nun habe ich sowohl f¨ ur die Ergebnisziffer r als auch f¨ ur den Ubertrag u2 eine logische Schaltung gefunden und muss die beiden Schaltungen nur noch in einem Schaltbild darstellen. Das wird nat¨ urlich etwas komplizierter ausfallen als die bisherigen recht u ¨bersichtlichen Schaltbilder, denn die Schaltungen sind nun mal aufwendiger; sie sehen es in Abbildung 1.17 vor sich. Niemand hat behauptet, dass das Schaltbild des Volladdierers den Sch¨onheitspreis des Jahres gewinnt, aber man kann nicht alles haben. Wenn Sie die Verbindungslinien unter Ber¨ ucksichtigung der dick eingezeichneten Kno¨ tenpunkte verfolgen, werden Sie die Ubereinstimmung des Schaltbildes mit meinen m¨ uhsam ermittelten logischen Ausdr¨ ucken feststellen, und nur darauf kommt es an. Im unteren Teil des Bildes werden zum Beispiel jeweils zwei Inputs in einer UND-Schaltung verkn¨ upft, woraufhin die Ergebnisse der UNDVerkn¨ upfungen in einem ODER-Gatter zusammwen gef¨ uhrt werden: das ist ¨ genau die Schaltung, die mir den Ubertrag u2 liefert. Im oberen Teil habe ich zun¨achst alle drei Inputs in ein UND-Gatter gef¨ uhrt und den Ausgang dieses UND-Gatters zum Input eines ODER-Gatters werden lassen: damit wird

1.5 Logische Schaltungen und Addierer

73

x y u1 & 1 1 1 &

1

&

r

& & &

1

u2

&

Abb. 1.17. Volladdierer

x ∧ y ∧ u1 zu einem der vier Inputs des großen ODER-Gatters, und genauso verlangt es der logische Ausdruck f¨ ur r. Danach wird jeder der drei Inputs x, y und u1 durch ein NICHT-Gatter geschickt und so mit den anderen Inputs UND-verkn¨ upft, dass die drei Ausdr¨ ucke x ∧ y ∧ u1 , x ∧ y ∧ u1 und x ∧ y ∧ u1 entstehen, die sich dann zusammen mit dem vorher erzeugten x ∧ y ∧ u1 in einem ODER-Gatter vereinigen, das schließlich die Ergebnisziffer r liefert. So viel Aufwand f¨ ur die schlichte Notwendigkeit, zwei bin¨are Ziffern samt ¨ Ubertrag zu addieren. Damit haben Sie aber auch den Grundbaustein in der Hand; Sie hatten gesehen, dass die gesamte bin¨are Arithmetik sich auf die Addition zur¨ uckf¨ uhren l¨asst, und wie man eine Addition von bin¨aren Ziffern umsetzt, habe ich Ihnen gerade gezeigt.

Volladdierer Der Volladdierer bestimmt aus drei bin¨aren Inputs zwei bin¨are Outputs. Er ¨ addiert zwei Bin¨arziffern x und y zu einem Ubertrag u1 und liefert dabei eine ¨ Ergebnisziffer r und einen Ubertrag u2 . Es gelten die Formeln: r = (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 ) ∨ (x ∧ y ∧ u1 )

und u2 = (x ∧ y) ∨ (x ∧ u1 ) ∨ (y ∧ u1 )

74

1 Grundlagen

Sie sollten doch immerhin einmal sehen, dass man die entwickelten Grundbausteine auch zu etwas Sinnvollem zusammensetzen kann. Wie werden denn jetzt ganze Bin¨arzahlen addiert, wie kommt man von der Beschr¨ ankung auf Ziffern los? Das ist ganz einfach. Nehmen wir an, dass unser Rechner in der Lage sein soll, vierstellige Bin¨ arzahlen zu addieren, so muss ich nur die beiden entwickelten Schaltelemente Halbaddierer“ und Volladdierer“ richtig ” ” zusammenschalten. Zu diesem Zweck bezeichne ich den Halbaddierer mit dem Symbol HA und den Volladdierer mit dem Symbol VA. Addieren will ich die beiden vierstelligen Bin¨ arzahlen x3 x2 x1 x0 und y3 y2 y1 y0 . Dann ergibt sich das Addierwerk aus Abbildung 1.18; die Inputs stehen u ¨ber dem Addierwerk, der Output r4 r3 r2 r1 r0 setzt sich aus den Ergebnissen zusammen, die von den einzelnen Additionsschaltungen geliefert werden.

x 3 y3

x 2 y2

x 1 y1

x 0 y0

VA

VA

VA

HA

u3

r4

r3

u2

r2

u1

r1

r0

Abb. 1.18. Additionswerk

Zuerst werden die beiden Einserstellen x0 und y0 addiert, das sind nur zwei Stellen, also brauche ich dazu nur einen Halbaddierer. Die Ergebnisziffer r0 ¨ wird bei den Ergebnissen notiert, der Ubertrag u1 wird an die n¨ achste Stelle weiter gereicht. F¨ ur die n¨ achste Addition brauche ich dann auch tats¨ achlich einen Volladdierer, denn hier habe ich die beiden Inputziffern x1 , y2 und den ¨ Ubertrag u1 der vorherigen Addition zu addieren. Die Ergebnisziffer r1 , die der ¨ Volladdierer liefert, wird wieder bei den Ergebnissen notiert, der Ubertrag u2 zur n¨achsten ziffernweisen Addition weitergegeben. Und so zieht sich das Spiel durch, ¨ahnlich wie beim Domino, bis das Ende in Gestalt der letzten Stelle ¨ erreicht ist. Hier gibt es nat¨ urlich keinen Ubertrag mehr, denn es steht keine weitere Stelle mehr zur Verf¨ ugung, mit der gerechnet werden k¨ onnte. Ob dann achlich zu den Ergebnissen gerechnet wird, h¨ angt die Ergebnisziffer r4 tats¨

1.5 Logische Schaltungen und Addierer

75

ab von der tats¨achlichen Architektur Ihres Rechenwerks: wenn beispielsweise durchg¨angig nur mit vier bin¨ aren Stellen gerechnet wird, haben Sie im Falle ¨ und r4 geht verloren. Man sollte deshalb r4 = 1 einen so genannten Uberlauf immer eine Stelle mehr zur Verf¨ ugung haben als man zu brauchen glaubt, ¨ denn f¨ ur gew¨ohnlich wird ein Uberlauf im Dunkel der Nacht verschwinden.

Addierwerk Ein Addierwerk setzt sich zusammen aus einem Halbaddierer und mehreren Volladdierern, wobei die Anzahl der Volladdierer von der Anzahl der gew¨ unschten Stellen der verwendeten bin¨aren Zahlen abh¨ angt. Mit ihm ist es m¨ oglich, bin¨ are Zahlen nach den Regeln der bin¨ aren Arithmetik auf der Basis der drei logischen Grundschaltungen zu addieren. Mein Ziel, einen Addierer zu bauen, habe ich jetzt erreicht. Nur eine kleine Anmerkung noch. Sowohl bei der Konstruktion der XOR-Schaltung als auch beim Aufbau des Volladdieres haben Sie gesehen, wie man eine Funktionstabelle, die die Verteilung der Einsen und Nullen bei den Inputs und Outputs beschreibt, in ein Schaltbild umsetzen kann. Dieses Verfahren funktioniert immer, ganz unabh¨angig vom Aufbau der Tabelle, Sie m¨ ussen nur genauso vorgehen, wie wir das bei den besprochenen Schaltungen gemacht haben. Hat aren Output Ihre Tabelle also einige bin¨are Inputs x1 , ..., xn und einen bin¨ r, so sollten Sie im ersten Schritt alle Zeilen der Tabelle heraussuchen, die den Ergebniswert 1 aufzuweisen haben; die anderen interessieren nicht. Dann sehen Sie in den relevanten Zeilen nach, welche Inputwerte vorhanden sind: kann ein Input xi mit einer 1 gl¨anzen, so wird er unbesehen u ¨bernommen, ist er dagegen mit einer 0 geschlagen, wird er der Negation unterworfen und alt anschließend einen logischen aus xi wird xi . Jede relevante Tabellenzeile erh¨ Ausdruck, denn jetzt werden die Inputgr¨oßen durch UND-Verkn¨ upfungen verbunden, wobei Sie nat¨ urlich darauf achten m¨ ussen, ob xi oder xi zu verwenden ist. Und zum guten Schluss werden Sie die logischen Ausdr¨ ucke, die aus den einzelnen Zeilen entstanden sind, durch ODER-Verkn¨ upfungen verbinden. Nehmen wir beispielsweise die Tabelle

x1 0 0 0 0 1 1 1 1

x2 0 0 1 1 0 0 1 1

x3 0 1 0 1 0 1 0 1

r 1 0 0 0 0 1 0 0

mit den drei Inputs x1 , x2 , x3 und dem Output r. Die relevanten Zeilen sind die erste und die sechste, die entsprechenden logischen Ausdr¨ ucke lauten x1 ∧

76

1 Grundlagen

ur den Output r der Ausdruck x2 ∧ x3 bzw. x1 ∧ x2 ∧ x3 . Damit ergibt sich f¨ r = (x1 ∧ x2 ∧ x3 ) ∨ (x1 ∧ x2 ∧ x3 ), und schon ist die Sache erledigt. Sie sehen, dass die resultierenden Ausdr¨ ucke immer auf die gleiche Weise aufgebaut sind: sie bestehen aus Klammerausdr¨ ucken, die durch ODERSchaltungen verkn¨ upft werden, und innerhalb der Klammern gibt es nur Negationen und UND-Verkn¨ upfungen. Einen Ausdruck dieser Art nennt man eine disjunktive Normalform; sie entsteht, wenn Sie sich an den Einsen im Output orientieren. Ich will Ihnen nicht verschweigen, dass man sich auch an den Nullen orientieren kann und dann eine andere Normalform erh¨ alt, die so genannte konjunktive Normalform. Darum k¨ ummern sich aber die B¨ ucher u ur unseren Zu¨ber Algebra, Diskrete Mathematik oder auch Digitaltechnik, f¨ sammenhang soll die disjunktive Normalform gen¨ ugen. Genug geschaltet. Bevor Sie endg¨ ultig abschalten und sich der Sportschau zuwenden, wechsle ich lieber das Thema.

1.5.6 Negative Zahlen Eine Kleinigkeit noch, dann k¨ onnen wir den Bereich der rechnerinternen Abl¨ aufe verlassen. Bisher habe ich mich in allen Verfahren auf das Rechnen mit positiven ganzen Zahlen beschr¨ ankt. Sie wissen aber so gut wie ich, dass sp¨ atestens auf manchen Kontoausz¨ ugen auch negative Zahlen auftauchen, mit denen man irgendwie umgehen muss. Nun weiß ich nicht, wie Sie auf eventuelle negative Betr¨ age auf ihren Ausz¨ ugen reagieren, aber wie ein Computer mit negativen Zahlen umgeht, kann ich Ihnen erkl¨ aren. Sie werden dabei sehen, wie wichtig tats¨ achlich das Zweierkomplement aus dem Abschnitt u ¨ber ¨ bin¨ are Arithmetik ist und wie praktisch es sein kann, Uberl¨ aufe einfach zu ignorieren. In alten Zeiten hat man es sich manchmal einfach gemacht. Jede Zahl ist eine Folge von Bits, und wenn sich alle darauf einigen, das vorderste Bit nicht als Ziffer zu interpretieren, sondern als Vorzeichen, dann ist auch allen gedient. Man hat also beispielsweise bei Zahlen mit acht Stellen das f¨ uhrende Bit als Vorzeichen verstanden, eine 1 als Minus, eine 0 als Plus, und die eigentliche Zahl auf sieben Bits beschr¨ ankt. Die Bitfolge 01010101 ist in dieser Darstellung ahrend die Bitfolge 11010101 der als +10101012 , also als 85 zu verstehen, w¨ oßtm¨ ogliche ganze Zahl aus acht Zahl −10101012 , also −85 entspricht. Die gr¨ Bits ist dann 01111111 = 127, die kleinstm¨ ogliche ist 11111111 = −127, denn die f¨ uhrende 1 beschreibt keine weitere Bin¨ arstelle, sondern das negative Vorzeichen. So hat man das fr¨ uher manchmal gemacht, so macht das heute kein Mensch mehr. Diese Vorgehensweise hat n¨ amlich zwei Nachteile. Erstes kann man hier die schlichte Zahl 0 auf zwei Arten darstellen, als 00000000 und als 10000000, wobei ich der Einfachheit halber wieder von Bitfolgen der L¨ ange acht ausgehe. Was sollte es aber f¨ ur einen Sinn machen, zwei verschiedene Darstellungen f¨ ur so etwas wie eine Null zu haben? Nat¨ urlich ist 0 = −0, was durch die verschiedenen Darstellungsformen zum Ausdruck kommt, aber das ist noch lange kein

1.5 Logische Schaltungen und Addierer

77

Grund, diese simple Tatsache auch noch im Rechenwerk hervorzuheben. Immerhin: damit k¨ onnte man leben, der zweite Grund ist deutlich gewichtiger. Erinnern Sie sich noch an das Zweierkomplement? Es spielte eine bedeutende Rolle bei der bin¨aren Subtraktion, und die ganze bin¨ are Arithmetik habe ich ja nur besprochen, weil der Computer nun mal darauf angewiesen ist. Wenn man also nach einem Verfahren zur Darstellung negativer Zahlen sucht, dann w¨ are es ganz praktisch, diese Darstellung auch ein wenig danach auszurichten, was hinterher mit diesen Zahlen angestellt werden soll. Offenbar haben aber negative Zahlen etwas mit dem Subtrahieren zu tun, also k¨ onnte es nicht schaden, irgendwie das Zweierkomplement ins Spiel zu bringen. Auf genau dieser Idee beruht die heute u ¨bliche rechnerinterne Darstellung negativer Zahlen. Ich zeige Ihnen das Prinzip am Beispiel von Bitfolgen der L¨ange vier, gehe also davon aus, dass ich meine ganzen Zahlen durch jeweils vier Bits darstelle. Genau wie bei der eben besprochenen Methode soll das f¨ uhrende Bit das Vorzeichen beschreiben, die 1 steht f¨ ur Minus, die 0 steht f¨ ur Plus. Die positiven Zahlen fangen also alle mit einer 0 in der vordersten Stelle an, und danach stehen noch drei Stellen zur Verf¨ ugung f¨ ur die eigentliche Zahl. So entspricht zum Beispiel die Bitfolge 0110 der Zahl 3 und 0111 der Zahl 7. Mehr ist im positiven Bereich nicht zu holen, denn die n¨ achste bin¨ are Zahl w¨are 1000, und die hat in der vordersten Stelle leider schon eine 1. So weit war alles wie gehabt. Die negativen Zahlen stelle ich jetzt nicht mehr ¨ durch einfaches Andern des Vorzeichenbits dar, sondern durch das vierstellige Zweierkomplement der entsprechenden positiven Zahl. Falls Sie jetzt etwas verwirrt blicken, kann ich das verstehen, so ein Satz muss an einem Beispiel erkl¨art werden. Darstellen m¨ochte ich die Zahl −6, und daf¨ ur stehen mir vier Bits zur Verf¨ ugung. Die entsprechende positive Zahl lautet 6 mit der vier-Bit-Darstellung 0110. Erinnern Sie sich noch, wie man das Zweierkomplement bestimmt? Zuerst wandelt man alle Einsen in Nullen um und umgekehrt, und dann addiert man noch eine bin¨ are 1 auf das Resultat. Die Umwandlung ergibt die Bitfolge 1001, die Addition einer 1 ergibt das Zweierkomplement 1010. Und schon ist es geschafft, die Bitfolge 1010 ist die rechnerinterne Darstellung der Zahl −6. Nur um den Unterschied einmal klar zu machen: nach der alten Methode h¨atten Sie die Folge 1110 erhalten, es kommt also wirklich etwas anderes heraus. Es kann nicht schaden, noch ein Beispiel zu sehen, diesmal auf der Basis von acht bin¨aren Stellen. Ich m¨ ochte also die Zahl −52 nach dem Zweierkomplementverfahren bin¨ ar darstellen. Zun¨ achst entspricht 52 der Bitfolge 00110100. Das Einserkomplement dieser Bitfolge lautet 11001011, und durch Hinzuaddieren einer bin¨aren 1 finde ich das Zweierkomplement 11001100, womit die Arbeit auch schon getan ist: rechnerintern wird −52 durch die achtstellige Bitfolge 11001100 dargestellt. Aber worin liegt der Vorteil dieses Verfahrens? Sehen wir uns die beiden Nachteile des ersten Verfahrens an und u ufen wir, ob sich etwas gebessert ¨berpr¨ hat! Zun¨achst war da die doppelte Darstellung der 0, einmal als 0 und einmal als −0, und das gibt es jetzt tats¨achlich nicht mehr. Um das einzusehen, gehe

78

1 Grundlagen

ich wieder von einer Darstellung durch vier Bits aus, aber das Argument gilt f¨ ur jede beliebige L¨ ange. Bei vier Stellen entspricht die 0 der Bitfolge 0000, das wird keinen u urlich 1111, ¨berraschen. Das Einserkomplement lautet nat¨ und jetzt muss ich zur Bestimmung des Zweierkomplements noch eine bin¨ are 1 hinzu addieren. Ich bin aber mit 1111 schon am Ende der mit vier Stellen darstellbaren Zahlen angekommen, weiteres Addieren ergibt 10000. Und hier sehen Sie, wie segensreich sich das Wegfallen der u ¨berlaufenden Ziffer auswirken kann, u ¨ber die ich im im Zusammenhang mit dem Addierwerk gesprochen hatte. Da mir nun mal nur vier Ziffern zur Verf¨ ugung stehen, wird die f¨ uhrende 1 nicht mehr ber¨ ucksichtigt; sie kann gar nicht mehr ber¨ ucksichtigt werden, weil jede Zahl einer Bitfolge aus vier Bits entsprechen muss. Also f¨allt sie schlicht und einfach weg, und was u ¨brigbleibt, ist die Bitfolge 0000. Aus 0000 wurde auf diese Weise wieder 0000, zu einer Darstellung auf zwei Arten ist es nicht gekommen. Ein Problem ist erledigt. Die L¨osung des zweiten Problems ergibt sich fast von alleine, wie ich Ihnen wieder an einem Beispiel zeigen werde. Wie schon gewohnt, gehe ich von vierstelligen Bitfolgen aus und m¨ ochte die beiden Zahlen 3 und −2 addieren. Die entsprechenden bin¨ aren Darstellungen lauten 0011, denn 3 ist positiv, und 1110, denn das Einserkomplement von 0010 lautet 1101, und Addieren von 1 liefert 1110. Jetzt habe ich aber die −2 schon als Zweierkomplement zur Verf¨ ugung, und nat¨ urlich ist 3 + (−2) = 3 − 2. Wie subtrahiert man eine Zahl von der anderen? Richtig, indem man auf die andere Zahl das Zweierkomplement der einen Zahl addiert. Genau das kann ich hier tun, ich habe die Bitfolge 0011, die der 3 entspricht, und die Bitfolge 1110, die der −2 in Zweierkomplementform entspricht, und darf ganz einfach die beiden Folgen nach den u ¨blichen Regeln addieren. Das ergibt dann 0011 + 1110 = 10001. Wieder muss ich Ihr Ged¨ achtnis bem¨ uhen: nach der Addition des Zweierkomplements habe ich immer die f¨ uhrende 1 gestrichen, das musste ich von Hand machen. In der Situation des Rechners passiert ¨ das von alleine, denn ich habe hier schon wieder einen Uberlauf um eine Stelle, der nicht ber¨ ucksichtigt werden kann, also ganz einfach verschwindet. Die Addition der Zweierkomplementdarstellung der negativen Zahl auf die positive Zahl f¨ uhrt daher automatisch zum richtigen Ergebnis 0001. Noch ein Beispiel, damit es deutlicher wird. Ich berechne 6 + (−3) und verwende wieder vierstellige Bitfolgen. Die 6 hat dann die Darstellung 0110, die 3 dagegen wird als 0011 geschrieben mit dem Einserkomplement 1100 und dem Zweierkomplement 1101. Addieren ergibt 0110 + 1101 = 10011, aber die vorderste Stelle wird von vornherein ignoriert, also habe ich das Ergebnis 0011, das dem dezimalen Ergebnis 3 entspricht. Indem ich also negative Zahlen durch das Zweierkomplement der entsprechenden positiven Zahl darstelle, passt die rechnerinterne Schreibweise ausgezeichnet zur bin¨aren Arithmetik und man kann problemlos auch mit negativen ussen uns nur noch u Zahlen rechnen. Wir m¨ ¨berlegen, welche Zahlenbereiche mit welcher Stellenzahl abgedeckt werden k¨ onnen. Das ist aber nicht schwer. Sobald mir n bin¨ are Stellen zur Verf¨ ugung stehen, reserviere ich die f¨ uhrende

1.5 Logische Schaltungen und Addierer

79

Stelle f¨ ur das Vorzeichen und behalte n − 1 Stellen u ur die eigentliche ¨brig f¨ Zahl. Die kleinstm¨ ogliche nichtnegative Zahl ist nat¨ urlich die 00...00, die nur aus n Nullen besteht und die u oßtm¨ ogliche lautet ¨bliche 0 darstellt. Die gr¨ 011...11, sie beginnt mit einer 0 f¨ ur das positive Vorzeichen, der n − 1 Einsen nachfolgen. W¨ urde man jetzt noch spaßeshalber eine bin¨ are 1 addieren, dann ¨ erg¨abe das wegen der Ubertr¨ age 100...00, eine f¨ uhrende 1 mit n − 1 Nullen, und das ist nach den Regeln der Bin¨ arzahlen genau 2n−1 . Da dieses 2n−1 aber um 1 gr¨oßer ist als meine gr¨oßtm¨ ogliche positive Zahl, kann ich im positiven Bereich h¨ochstens die Zahl 2n−1 − 1 verarbeiten. Ein klein wenig anders sieht es bei den negativen Zahlen aus. Das Zweierkomplement von 00...001 lautet 11...111, also entspricht −1 der Bitfolge 11...111. Das Zweierkomplement von 00...010 lautet 11...110, also entspricht ¨ −2 der Bitfolge 11...110. Sie sehen, dass ich beim Ubergang von −1 zu −2 in der Bitfolge gerade 1 abziehen musste, und genauso geht das auch weiter: der −3 entspricht die Folge 11...101, der −4 die Folge 11...100 und so weiter. Und wo h¨ort das auf? Das Zweierkomplement der gr¨ oßten darstellbaren positiven Zahl 011...111 ist 100...001, weshalb −(2n−1 − 1) durch die Bitfolge 100...001 dargestellt wird. Aber offenbar habe ich hier noch eine Zahl in Reserve: ich habe angefangen bei 11...111 und habe mich herunter gehangelt bis 10...001 Einen Schritt kann ich jetzt noch machen, und zwar den auf 10...000, eine f¨ uhrende 1 mit n − 1 Nullen. Und da wir, dezimal gerechnet, bei der −1 gestartet waren und hinab gestiegen sind bis zur −(2n−1 − 1), entspricht diese letzte Bitfolge 10...000 der n¨ achstkleineren Dezimalzahl −2n−1 . Man kann daher mit der Zweierkomplementmethode durch n bin¨ are Stellen die negativen aren Stellen Zahlen bis zu −2n−1 darstellen. Insgesamt heißt das: mit n bin¨ kann man die ganzen Zahlen m mit der Eigenschaft −2n−1 ≤ m ≤ 2n−1 − 1 im Rechner abbilden. Haben Sie also zum Beispiel acht g¨ ultige Bin¨ arstellen, so existieren f¨ ur Ihren Computer die ganzen Zahlen zwischen −128 und 127. Geht man u ¨ber zu zwei Bytes oder sechzehn Bits, so stehen schon die Zahlen ugung, also alles zwischen −32768 und zwischen −215 und 215 − 1 zur Verf¨ 32767.

Darstellung ganzer Zahlen mit Vorzeichen Stehen zur rechnerinternen Darstellung ganzer Zahlen n Stellen zur Verf¨ ugung, so stellt man eine positive Zahl dar, indem man an die f¨ uhrende Stelle eine 0 setzt und mit den restlichen n − 1 Stellen die Bin¨ ardarstellung der Zahl realisiert. Eine negative Zahl stellt man durch das Zweierkomplement der entsprechenden positiven Zahl dar. Diese Methode erlaubt die Anwendung der bin¨ aren Arithmetik auch f¨ ur negative Zahlen. Mit n Stellen kann man auf diese Weise die ganzen Zahlen m mit der Eigenschaft −2n−1 ≤ m ≤ 2n−1 − 1 im Rechner abbilden. ¨ Uber das Innenleben des Rechners habe ich jetzt lange genug gesprochen, es wird Zeit, das Grundlagenkapitel zu beenden. Im n¨ achsten Kapitel werde

80

1 Grundlagen

ich Ihnen einiges u ¨ber Algorithmen und ihre Umsetzung in der Programmiersprache C berichten. ¨ Ubungen 1.20. Weisen Sie mit Hilfe von Funktionstabellen die Beziehungen A ∧ B = A ∨ B, A ∨ B = A ∧ B und A = A nach.

1.21. Untersuchen Sie, ob es m¨oglich ist, mit nur zwei logischen Grundschaltungen auszukommen anstatt, wie dargestellt, mit drei Grundschaltungen. 1.22. Stellen Sie f¨ ur r = (x ⊕ y) ⊕ z eine Funktionstabelle auf. Vergleichen Sie die Ergebnisse mit denen der Ergebnisziffer beim Volladdierer. Wie kann man mit Hilfe von (x ⊕ y) ⊕ z das Schaltbild des Volladdierers vereinfachen? 1.23. Setzen Sie die Schaltung aus Abbildung 1.19 in eine Tabelle um.

x

y z

&

1 &

r &

1

Abb. 1.19. Schaltung

1.24. Setzen Sie die folgende Tabelle mit den Inputs x, y und dem Output r in eine logische Schaltung um. x 0 0 1 1

y 0 1 0 1

r 1 0 0 1

Wie h¨ angt diese Schaltung mit der XOR-Schaltung zusammen?

2 Strukturierte Programmierung mit C Leben heißt immer, Entscheidungen treffen“ ” Jean Luc Picard, Captain der Enterprise

Klaus Kinski hat einmal in einem Interview gesagt, Sprache sei das Gef¨ ahrlichste, was es u ¨berhaupt gebe. Obwohl das eine ziemlich gewagte Behauptung ist, die andere Gefahrenquellen wie Lawinen, Waldbr¨ ande oder Finanz¨ amter stark vernachl¨ assigt, ist doch etwas Wahres daran. Egal welche Sprache Sie einsetzen, die Gefahr von Missverst¨andnissen lauert hinter jedem Satz. Sie k¨onnen beispielsweise mit einem aus zwei Worten bestehenden Satz einem Chinesen sagen, dass Sie ihn gerne etwas fragen w¨ urden. Sprechen Sie aber den gleichen Satz aus und betonen nur einen einzigen Vokal ein klein wenig anders, dann haben Sie auf einmal gefragt, ob Sie ihn k¨ ussen d¨ urfen, und je nach Laune und Statur Ihres chinesischen Gegen¨ ubers kann seine Reaktion die Wahrheit von Kinskis Meinung deutlich illustrieren. So etwas passiert aber nicht nur bei ostaiatischen Sprachen, es kommt auch in unseren Breiten vor, sowohl beim Sprechen als auch beim Programmieren. Als ich vor langer Zeit Programmierer bei einer großen Softwarefirma mit drei Buchstaben war, hatte ich auch gelegentlich mit Kunden zu tun, die die von der Firma entwickelte Software einsetzen wollten. Nat¨ urlich kann es gerade bei großen Systemen - vorkommen, dass auch fehlerhafte Programme an die Kunden ausgeliefert werden, und genau das hatte man sich bei diesem Kunden geleistet. War ja auch nicht so schlimm, schließlich gab es einen telefonischen Notdienst, der leider an diesem Tag gerade von mir besetzt war. Ich erkl¨ arte dem Kunden also am Telefon, welche Zeile er in das Programm einf¨ ugen musste, damit es klaglos funktionierte; es war wirklich nur eine Zeile, die einen so genannten else-Zweig regelte. Was immer das auch sein mag, in jedem Fall ist else“ ein englisches Wort mit der deutschen Bedeutung an” ” sonsten“, und der Kunde sollte einfach das, was ich ihm diktierte, w¨ ortlich in sein Programm hineinschreiben. Das hat er auch gemacht. Anschließend versuchte er das Ganze zum Laufen zu bringen und verk¨ undete mir verzweifelt, es gebe eine ganz seltsame Fehlermeldung, das Wort else“ werde nicht ” verstanden. Nun war die Verzweiflung ganz auf meiner Seite, denn wenn eine Programmiersprache das Wort else“ vorsieht, dann sollte dieses Wort, an der ” richtigen Stelle platziert, zu keinem Fehler f¨ uhren. Hatte er es denn wirklich

82

2 Strukturierte Programmierung mit C

genau an die Stelle geschrieben, wo es hingeh¨ orte? Es las mir noch einmal vor, alles in Ordnung. So ging es eine ganze Weile hin und her, ich wusste keinen Rat mehr, bis dem Kunden auf einmal die Erleuchtung kam: Ja, h¨ atte ich ” vielleicht else hinten mit e schreiben m¨ ussen?“ Ein Programmierer. Ein Chefprogrammierer sogar, der nicht wusste, wie man ein unz¨ahlige Male benutztes Wort einer Programmiersprache schreibt, und dem man im konstant freundlichen Ton erkl¨ aren musste, dass es eben nicht els, sondern else heißt. Das war kein reines Vergn¨ ugen, aber vor allem zeigt es, wie wichtig es ist, gerade beim Programmieren sprachlich pr¨ azise zu bleiben. Genau wie in der chinesischen Sprache kann ein falsch - oder vielleicht gar nicht - gesetzter Vokal, ein auf bestimmte Weise falsch geschriebenes Wort zum Zusammenbruch oder auch zu falschen Resultaten f¨ uhren. Damit Ihnen so etwas nicht passiert, werde ich mich in diesem und dem n¨ achsten Kapitel mit zwei Programmiersprachen befassen. Dieses zweite Kapitel ist erst mal der Sprache C gewidmet, aber nicht nur: ich will Ihnen hier einerseits eine Einf¨ uhrung in das Programmieren mit C geben und Sie andererseits ein wenig in das algorithmische Denken einf¨ uhren. Schließlich n¨ utzt es Ihnen rein gar nichts, wenn Sie genau wissen, wie man in C welche Worte schreibt und sie aneinandersetzen darf, Sie aber keine Ahnung haben, mit welchen Methoden man bestimmte Probleme l¨ ost. Das ist wie in jeder anderen Sprache auch; Rechtschreibung ist wichtig, Grammatik auch, aber sie sind nur Mittel zum Zweck, und der Zweck sind die Inhalte, die Sie zum Ausdruck bringen wollen. Deshalb beginne ich dieses Kapitel mit ein paar grunds¨ atzlichen Bemerkungen u ber Algorithmen und werde dann mit Ihnen die ersten Schritte mit der ¨ Sprache C unternehmen. F¨ ur eine Weile werden wir uns mit den u ¨blichen Kontrollstrukturen Sequenz, Auswahl und Wiederholung besch¨ aftigen, woraufhin ich versuchen werde, Ihnen Zeiger, verkettete Listen und Funktionen nahe zu bringen. Auch einige Worte zur Dateiverarbeitung werden nicht fehlen, und unterwegs werden Ihnen als graphische Beschreibungsmittel immer wieder Struktogramme und Programmablaufpl¨ ane begegnen.

2.1 Einfu ¨ hrung Es steht außer Frage, dass die elektronische Datenverarbeitung mit Hilfe so genannter Programme erfolgt, dar¨ uber hatten wir schon gelegentlich gesprochen. Dennoch ist das nur die halbe Wahrheit. Mit Hilfe eines Programms bilden Sie im Computer etwas ab, was Sie oder vielleicht auch Ihre Kollegen sich vorher ausgedacht haben, ein Verfahren, das irgendetwas bewirken soll. Ohne Verfahren kein Programm, denn ohne Verfahren w¨ ussten Sie u ¨berhaupt nicht, was Sie programmieren sollten. Und da Verfahren zu einfach klingt, hat man sich die Bezeichnung Algorithmus ausgedacht.

2.1 Einf¨ uhrung

83

2.1.1 Algorithmen Was ist ein Algorithmus? Die Formulierungen gehen hier auseinander, machmal auch die Meinungen, aber das ist f¨ ur unsere Zwecke nicht weiter schlimm. Wir hatten uns darauf geeinigt, dass ein Algorithmus das Verfahren ist, das dem konkreten Programm zugrunde liegt, und die Programme werden in aller Regel auf einem Computer ausgef¨ uhrt. Also dient der Computer der Ausf¨ uhrung von Algorithmen, wobei das Programm die Rolle des Vermittlers zwischen dem abstrakten Algorithmus und dem konkreten Computer spielt: man kann dem Rechner nicht auf Deutsch erkl¨ aren, was er jetzt zu tun hat, dazu braucht man eine klar definierte und auf die Bed¨ urfnisse der Maschine abgestimmte Sprache. Wenn Sie aber eine Verfahrensanleitung, also den Algorithmus, u ¨bersetzen k¨onnen sollen in die Anweisungen einer Programmiersprache, dann darf dieser Algorithmus sich nicht in den Gefilden philosophischer Großz¨ ugigkeit bewegen, sondern muss klar und exakt sein. Das ist nicht alles. Kein Computer der Welt kann unendlich lange laufen, also darf kein Algorithmus der Welt unendlich lang sein. Und das heißt, dass sowohl die Anzahl der Verfahrensvorschriften als auch die Anzahl der konkret durchzuf¨ uhrenden Einzelschritte nicht unendlich groß werden darf. Sie d¨ urfen also keine Anweisung der Form Solange 1 = 1 ist, gib die Zahl 17 am Bildschirm ” aus“ in Ihren Algorithmus schreiben, denn damit h¨ atten Sie zwar nur eine einzige Verfahrensvorschrift, diese einzige Verfahrensvorschrift m¨ usste jedoch unendlich oft durchgef¨ uhrt werden, und das ist offenbar nicht m¨ oglich. Fast haben wir es schon, nur noch eine Kleinigkeit fehlt. Wie kommt man auf die Idee, sich einen Algorithmus zu u ¨berlegen? Sicher nicht aus heiterem Himmel, weil heute Mittwoch oder gar Donnerstag ist. Ein Algorithmus dient immer der L¨osung eines Problems, einer Aufgabe, sonst w¨ are er sinnlos. Sobald Sie also ein Problem haben und dieses Problem mit Hilfe endlich vieler klarer Verfahrensvorschriften l¨ osen wollen, die sich in endlich vielen Einzelschritten durchf¨ uhren lassen, k¨onnen Sie mit Fug und Recht von einem Algorithmus sprechen.

Algorithmus Ein Algorithmus ist eine endliche Menge von eindeutig festgelegten Verfahrensvorschriften zur L¨osung eines Problems durch eine in der Regel endliche Menge von Einzelschritten. Man kann die Auffassung vertreten, die Menge der Einzelschritte d¨ urfte auch unendlich groß werden; es gibt auch mathematische Verfahren, die eine unendliche Anzahl von Wiederholungen verlangen und u ¨bereinstimmend als Algorithmen bezeichnet werden. Sobald Sie solche Verfahren aber konkret anwenden wollen, werden Sie gezwungen sein, nach einer gewissen Zeit aufzuh¨ oren - m¨ oglichst noch vor dem Ende des Universums, weil sonst Ihre berechneten Ergebnisse zu nichts mehr zu gebrauchen sind und somit auch kein Problem l¨ osen k¨onnen. F¨ ur uns hat ein Algorithmus deshalb in jeder

84

2 Strukturierte Programmierung mit C

Hinsicht endlich zu sein, auch wenn das in der Mathematik manchmal anders aussieht. ¨ Ubrigens klingt das Wort Algorithmus zwar sehr griechisch, kommt aber aus der arabischen Sprache und lehnt sich an den Namen des arabischen Mathematikers und Astronomen Mohamed ibn Musa al-Hwarizmi aus dem neunten Jahrhundert an, der zwar sicher keine Computerprogramme geschrieben, aber immerhin als erster arabischer Mathematiker das Ihnen vertraute Dezimalsystem benutzt und systematisch beschrieben hat. Es handelt sich also um ein reines Kunstwort, und das ist auch in Ordnung, denn schließlich ist ein Algorithmus auch etwas K¨ unstliches. 2.1.2 Programmiersprachen F¨ uhrt man nun einen Algorithmus aus, so spricht man von einem Prozess, und der Ausf¨ uhrende ist der Prozessor. Wer dabei die Rolle des Prozessors spielt, h¨angt ganz vom Einzelfall ab. Soll zum Beispiel meine Tochter zwei nat¨ urliche Zahlen auf dem Papier addieren, dann ist sie der Prozessor. Soll dagegen eine Addition auf dem Computer vorgenommen werden, wird man je nach Betrachtungsweise den Computer oder seine Zentraleinheit oder gar nur das Rechenwerk als den Prozessor bezeichnen - in der Regel nat¨ urlich die schon im letzten Kapitel erw¨ahnte CPU. Beim Ausf¨ uhren eines etwas komplizierteren Algorithmus zum Sortieren einer großen Menge von Datens¨ atzen kann Ihnen kein menschlicher Prozessor mehr die Ausf¨ uhrung des Algorithmus abnehmen, dieser Prozess muss auf einer Maschine laufen. Es d¨ urfte klar sein, dass wir uns im Folgenden nur mit Algorithmen befassen werden, die als Prozessor einen Computer verwenden. Immerhin gibt es ja auch viele Algorithmen, die nur mit Hilfe eines Computers sinnvoll zur Ausf¨ uhrung gebracht werden ¨ k¨onnen. Uber die Gr¨ unde habe ich mich schon am Anfang des ersten Kapitels ausgelassen, als es um die Vorteile der maschinellen Datenverarbeitung ging: der Computer hat eine ausgesprochen hohe Verarbeitungsgeschwindigkeit, er ist in aller Regel wesentlich zuverl¨ assiger als ein menschlicher Prozessor, seine Speicherkapazit¨at ist gerade bei der Verarbeitung von Massendaten eher angemessen als ein menschliches Kurzzeitged¨ achtnis, und selbstverst¨ andlich ist er auch oft genug die kosteng¨ unstigste L¨osung. Nun entsteht aber, wenn man maschinell ausf¨ uhrbare Algorithmen haben m¨ochte, ein kleines Problem. Der Prozessor muss n¨ amlich den Algorithmus in irgendeiner Weise interpretieren k¨ onnen, und dazu geh¨ oren immer zwei Dinge: erstens muss er u ¨berhaupt einmal jeden Schritt des Algorithmus verstehen, und zweitens muss er auch in der Lage sein, jede verlangte Operation auszuf¨ uhren. Wie schon erw¨ ahnt, k¨onnen Sie dem Computer nicht einfach erz¨ahlen, was er machen soll, auch wenn das Captain Jean Luc Picard auf der Br¨ ucke des Raumschiffs Enterprise immer wieder mit gekonnter Eleganz vorf¨ uhrt, Sie m¨ ussen Ihren Algorithmus in eine Form bringen, die der Computer versteht. Und damit sind wir bei der Programmierung und den Programmiersprachen. Der Algorithmus selbst ist zwar v¨ ollig unabh¨ angig von

2.1 Einf¨ uhrung

85

einer verwendeten Programmiersprache, um ihn aber dem Computer verst¨ andlich zu machen, braucht man eine Formulierung in einer Programmiersprache, m¨oglichst auch noch in einer, die der verwendete Computer versteht. Es wird Ihnen also nichts anderes u ¨brig bleiben, als Ihren Algorithmus in einer Programmiersprache als Programm zu formulieren und dann dem Computer zur Kenntnis zu geben. Welche Programiersprache Sie dabei verwenden, h¨ angt von Ihrem Geschmack und von der Sachlage ab. Zun¨ achst einmal unterscheidet man zwischen zwei Klassen von Programmiersprachen: den maschinennahen Programmiersprachen und den h¨ oheren Programmiersprachen. Warum maschinennahe Sprachen so heißen, wie sie heißen, ist leicht zu verstehen. Jede Anweisung einer maschinennahen Sprache kann entweder direkt von der CPU als ein Befehl oder doch wenigstens als eine sehr kleine Folge von Befehlen an die Maschine verstanden werden. Deshalb k¨ onnen die in einer maschinennahen Sprache erlaubten Anweisungen auch nur sehr einfach sein, sonst w¨ are die CPU mit der direkten Umsetzung schlicht u ¨berfordert. Was aber der CPU die Sache leicht macht, erschwert sie dem Programmierer: einen komplizierten Algorithmus mit kleinen und einfachen Anweisungen umzusetzen, f¨ uhrt offenbar zu langen und un¨ ubersichtlichen Programmen. Aus diesem Grund hat man die h¨ oheren Programmiersprachen erfunden, die es dem Programmierer leicht machen und der CPU etwas schwerer. Sie sind in aller Regel an einem sehr einfachen Englisch orientiert, was die Verst¨andlichkeit f¨ ur einen menschlichen Leser deutlich erh¨oht, und vor allem fassen sie in ihren Befehlen etliche kleine maschinennahe Befehle zusammen. Anders gesagt: was Sie in einer maschinennahen Sprache erst m¨ uhsam von Hand zusammenprogrammieren m¨ ussen, wird Ihnen von einer h¨oheren Sprache als fertiges Paket frei Haus geliefert. Nat¨ urlich macht das die Programmierung einfacher und reduziert die Fehleranf¨ alligkeit. Standardbeispiele f¨ ur maschinennahe Sprachen sind die Assembler-Sprachen, und auch an h¨oheren Programmiersprachen herrscht kein Mangel, ich nenne hier nur FORTRAN, COBOL, Basic, Pascal, C und Java, ohne jeden Anspruch auf Vollst¨andigkeit. Zwei davon werde ich hier besprechen, n¨ amlich C und Java.

Programmiersprachen Algorithmen, die auf einem Computer ausgef¨ uhrt werden sollen, m¨ ussen mit Hilfe einer Programmiersprache als Programme geschrieben werden. Man unterscheidet zwischen den maschinennahen Programmiersprachen, bei denen jede Anweisung direkt entweder in einen einzigen maschinenverst¨ andlichen Befehl oder in eine kleine Folge solcher Befehle umgesetzt werden kann, und den h¨ oheren Programmiersprachen, die st¨arker an den Bed¨ urfnissen des Programmierers ausgerichtet sind und meistens auf einem einfachen englischen Vokabular beruhen. Denken Sie immer daran: der Algorithmus, Ihre Verfahrensbeschreibung ist die Grundlage der Verarbeitung, das Programmieren setzt die Existenz eines Algorithmus voraus, der in eine Programmiersprache umgesetzt wer-

86

2 Strukturierte Programmierung mit C

den soll. Wenn Sie also ein Problem mit Hilfe eines Programms l¨ osen wollen, dann ist es immer zu empfehlen, in drei Schritten vorzugehen. Zuerst sollten Sie sich dar¨ uber klar werden, um welches Problem es eigentlich geht. Das klingt wesentlich trivialer als es ist, erinnern Sie sich nur einmal an die Geschichte u ¨ber den unn¨otigen Computer, den John von Neumann verhindert hat. Oft genug wird wild in die Gegend hinein programmiert, ohne dass sich jemand Rechenschaft dar¨ uber ablegt, welche Ziele genau erreicht, welche konkreten Probleme gel¨ ost werden sollen; deshalb ist die Problemdefinition, in der das anzugehende Problem so genau wie m¨ oglich beschrieben wird, von großer Bedeutung. Ist das Problem einmal definiert, so k¨ onnen Sie daran gehen, einen Algorithmus zur L¨osung des Problems zu entwerfen - man spricht dann vom Programmentwurf. Darin wird also noch nicht das konkrete Programm geschrieben, sondern es werden die Verfahrensschritte festgelegt, die anschließend in der Programmierung in eine Programmiersprache umgesetzt werden. Zugegeben: bei kleinen Problemen kann man die ersten beiden Teile dieses Ablaufs, also die Problemdefinition und den Programmentwurf, mehr oder weniger im Kopf erledigen, aber dar¨ uber nachdenken sollte man auf jeden Fall, und ich werde auch bei den kleinen Programmen, die wir uns hier ansehen, immer wieder darauf zur¨ uckkommen. Bei großen Systemen ist allerdings ein strukturiertes Vorgehen unverzichtbar, wenn man nicht ganz schnell in gewaltige Schwierigkeiten kommen will; das Fach Software Engineering ist die Antwort der Informatiker auf das wilde Programmieren der informatischen Fr¨ uhzeit. Eine kleine Einschr¨ ankung muss ich noch machen. Im Prinzip ist tats¨ achlich der Algorithmus unabh¨ angig von der Programmiersprache, in die er anschließend umgesetzt wird, oder er sollte es wenigstens sein. Wie die meisten hohen Prinzipien ist allerdings auch dieses Unabh¨ angigkeitsprinzip nicht vollst¨andig und konsequent durchf¨ uhrbar. Verschiedene Programmiersprachen sind nun mal auf verschiedene Anwendungsgebiete zugeschnitten, und das kann ein Algorithmus nicht einfach ignorieren. Wie ich schon im ersten Kapitel berichtet hatte, ist die Sprache COBOL f¨ ur betriebswirtschaftliche Anwendungen gedacht, w¨ ahrend FORTRAN einen naturwissenschaftlich-technischen Hintergrund hat. Niemand k¨ ame auf die Idee, eineSatellitenflugbahn mit einem COBOL-Programm berechnen zu lassen, und genauso unsinnig w¨ are es, ein System zur Lohnbuchhaltung in FORTRAN programmieren zu wollen. Da Sie aber in der Regel schon beim Programmentwurf, wenn es erst mal um den reinen Algorithmus geht, schon wissen, in welcher Programmiersprache Ihr Algorithmus umgesetzt werden soll, liegt es nahe, auf die Eigenheiten der Programmiersprache R¨ ucksicht zu nehmen. Sie werden beispielsweise im Laufe dieses Kapitels lernen, was ein Zeiger ist und wie man mit so etwas in C umgeht. COBOL dagegen hat keine Zeiger. W¨ urden Sie in einem Algorithmus, der f¨ ur COBOL gedacht ist, irgendwelche Zeiger verwenden? Was immer ein Zeiger auch sein mag, sein Einsatz in einem Algorithmus mit der uhevoll erZielsprache COBOL w¨ are v¨ollig sinnlos, weil Sie dann Ihren m¨

2.1 Einf¨ uhrung

87

stellten Algorithmus u ahlten Programmiersprache ¨berhaupt nicht in der gew¨ formulieren k¨onnten. Welche Methoden und Verfahren Sie also in einem Algorithmus einsetzen, wird davon abh¨angen, welche Methoden und Verfahren von der gew¨ahlten Programmiersprache unterst¨ utzt werden.

Programmerstellung Bei der Erstellung eines Programms zur L¨osung eines Problems sollte man schrittweise vorgehen. Eine m¨ogliche Einteilung sieht drei Schritte vor: • Problemdefinition, • Programmentwurf, • Programmierung. Der im Programmentwurf entwickelte Algorithmus sollte sich an den Gegebenheiten der f¨ ur die Programmierung verwendeten Programmiersprache orientieren, damit er in dieser Sprache realisiert werden kann.

2.1.3 Software Ein Computer ist eine konkrete Maschine, die aus einer Menge verschiedener Einzelteile besteht, aus physisch vorhandenen Komponenten. Die Gesamtheit dieser technischen Ger¨ate und Einrichtungen bezeichnet man gerne als Hardware, ein englisches Wort f¨ ur Eisenwaren. Das kann aber nicht alles sein. Wir hatten uns gerade darauf geeinigt, dass ein Computer neben seiner Funktion als Staubf¨ anger vor allem der Ausf¨ uhrung von Algorithmen dient, indem er als Prozessor f¨ ur Programme zur Verf¨ ugung steht. Ein Algorithmus ist kein physikalischer Gegenstand. Man kann zwar das zugeh¨ orige Programm auf eine Festplatte speichern, aber die zur Speicherung verwendeten Festplattensektoren wird kaum jemand mit dem Programm gleichsetzen wollen. Algorithmen und Programme sind und bleiben etwas Abstraktes, eine geistige Konstruktion, und ganz sicher keine Eisenwaren. Aus diesem Grund hat sich f¨ ur die in einem Computer einsetzbaren Programme die Bezeichnung Software durchgesetzt, um den Gegensatz zur Hardware zu dokumentieren und deutlich zu machen, dass zum Betrieb eines Computers eben etwas mehr n¨ otig ist als die anfassbaren Komponenten. Nun gibt es aber verschiedene Arten von Software, und man neigt dazu, sie in zwei Klassen zu unterscheiden: auf der einen Seite haben Sie die Systemsoftware, auf der anderen die Anwendungssoftware. Ohne ein gewisses Maß an Systemsoftware kann kein Rechner existieren. Irgend jemand muss sich darum k¨ ummern, dass sich Benutzer anmelden k¨onnen und dass Programme, wie Sie es bei der Beschreibung der Computerarchitektur gesehen haben, geladen und ausgef¨ uhrt werden k¨onnen. Man braucht eine Software, die die Verwaltung der Dateien organisiert, die daf¨ ur sorgt, dass die Steuerung der Prozesse reibungslos abl¨ auft und sich mehrere Prozesse nicht gegenseitig st¨ oren - all das erledigt das Betriebssystem, es verwaltet die Ressourcen ihres Rechners und erspart es

88

2 Strukturierte Programmierung mit C

Ihnen, sich selbst direkt mit der Hardware herumschlagen zu m¨ ussen. Ob das nun ein Windows-Dialekt oder Linux oder sonst etwas ist: die grunds¨ atzlichen Aufgaben des Betriebssystems sind immer gleich, und kein Computerbenutzer kann darauf verzichten. Das Betriebssystem ist wohl der wichtigste Teil der Systemsoftware, aber nicht der einzige. Dazu geh¨oren auch noch Dienstprogramme und Werkzeuge, die dem Benutzer und vor allem dem Entwickler von Software das Leben erleichtern sollen. Sie werden gleich C-Programme schreiben m¨ ussen - womit eigentlich? Dazu brauchen Sie irgendeinen Editor, mit dessen Hilfe Sie eine Datei erstellen k¨ onnen, Sie werden auch einen so genannten Compiler ben¨ otigen, der Ihr Programm u ur den Computer verst¨ andliche ¨berhaupt erst in eine f¨ Form bringt, und vielleicht legen Sie auch Wert auf einen Internetbrowser, damit Sie im Internet nach den m¨ oglichen Ursachen f¨ ur Fehler in Ihren Programmen suchen k¨onnen. All das geh¨ort nicht mehr zum Betriebssystem, aber auf jeden Fall zur Systemsoftware, denn Programme dieser Art erlauben es, mit Ihrem System zu arbeiten. Die Systemsoftware ist also die Voraussetzung, ohne die nichts geht. Und was soll mit ihrer Hilfe eigentlich gehen? Nat¨ urlich die Programmierung, auf die will ich ja hier hinaus. Und die Programme, die Sie erstellen, die Programme, die Sie schreiben, damit sie jemand anwendet, fallen unter den Begriff Anwendungssoftware. Ob das nun ein Abrechnungsprogramm ist oder ein Programm zur Berechnung von Wettervorhersagen oder sonst irgendeine Anwendung: es handelt sich in jedem Fall um Anwendungssoftware.

Softwarearten Unter Software versteht man die Menge der Programme, die auf einem Computer eingesetzt werden k¨onnen. Man unterteilt sie in Systemsoftware und Anwendungssoftware, wobei die Systemsoftware noch einmal unterteilt wird in das Betriebssystem sowie systemnahe Dienstprogramme und Werkzeuge. Sie konnten in dem Abschnitt u ¨ber die historische Entwicklung der Computer sehen, dass man Software und nat¨ urlich auch Anwendungssoftware schon seit langer Zeit entwickelt. Was man heute darunter versteht, konnte nat¨ urlich erst mit der Erfindung der Programmiersprachen seinen Anfang nehmen; seit es FORTRAN gibt, haben die Software-Entwickler munter vor sich hin programmiert. Das f¨ uhrte aber zu Problemen, weil jeder seinen eigenen Stil entwickelte und sich unter Software-Entwicklern damals wie heute ein leichter Hang zu einer Nach mir die Sintflut“-Mentalit¨at ausbildete: solange der Ent” wickler sein Programm verstand, war alles in Ordnung, die Nachwelt hat keinen interessiert. Insbesondere wurden mit Begeisterung wilde Spr¨ unge in Programmen ausgef¨ uhrt, die dazu f¨ uhrten, dass ein Programm eben noch einen bestimmten Befehl ausf¨ uhrte und sich dann infolge eines Sprungbefehls pl¨ otzlich an einer v¨ ollig anderen Stelle befand. Es erinnerte, falls die Sprungbereitschaft stark ausgepr¨agt war, streckenweise an manche Gespr¨ achspartner, die einen Satz anfangen, sich mitten im Satz unterbrechen, dann den unter-

2.1 Einf¨ uhrung

89

brechenden Satz wieder durch einen anderen Einschub zerhacken und dieses Spiel so lange betreiben, bis nicht mehr die geringste Hoffnung besteht, zu dem eigentlich angefangenen Satz zur¨ uckzufinden. Sie k¨onnen sich vorstellen, dass Programme dieser Art unter Umst¨ anden leichte Qualit¨atsprobleme aufwiesen, weshalb man sich ab den sp¨ aten sechziger Jahren bem¨ uht hat, zu einem besseren Stil zu finden. Nach einem grundlegenden Artikel des holl¨andischen Informatikers Edsger Dijkstra, in dem er zu recht die Sprunganweisung verdammte, ging man langsam u ¨ber zum Prinzip der strukturierten Programmierung, u ¨ber die ich auch in diesem Kapitel vor allem reden will. Ihr Grundgedanke ist einfach genug: um Himmels Willen niemals einen Sprungbefehl verwenden und alle Programmanweisungen sch¨ on der Reihe nach erledigen. Eine Anweisung in einem Programm kann man nur erreichen, wenn man die vorherige Anweisung erledigt hat, und sobald man mit einer Anweisung fertig ist, geht man zur n¨ achsten u ¨ber, ohne wie ein K¨anguruh durch den Programmtext zu h¨ upfen. Da es aber manchmal - und sogar ziemlich h¨aufig - n¨ otig ist, einem Programm eine gewissen Flexinilit¨ at zu erlauben, wurden in der strukturierten Programmierung bestimmte Kontrollstrukturen eingef¨ uhrt, mit deren Hilfe man die Abarbeitungsreihenfolge der Programmanweisungen beeinflussen kann. Was man darunter im Einzelnen versteht, werden Sie gleich sehen, wenn es um konkrete C-Programme geht; f¨ ur den Moment will ich nur sagen, dass man in der strukturierten Programmierung die Kontrollstrukturen Sequenz, Auswahl und Wiederholung kennt. Sie werden Ihnen ab jetzt immer wieder begegnen.

Strukturierte Programmierung Ein Programm entspricht dann den Regeln der strukturierten Programmierung, wenn es keinerlei Sprunganweisungen enth¨ alt und die Steuerung des Programmablaufs mit Hilfe der Kontrollstrukturen Sequenz, Auswahl und Wiederholung erfolgt. Genug der grauen Theorie. Im n¨achsten Abschnitt sehen wir uns die ersten C-Programme an. ¨ Ubungen 2.1. Untersuchen Sie die Wirkungsweise des folgenden Algorithmus, der mit einer ganzen Zahl a arbeitet. Welchen Wert hat die Zahl a nach der Abarbeitung des Algorithmus? K¨onnte man den Algorithmus auch einfacher formulieren? Falls a > 0 ist dann gehe folgendermaßen vor: Falls a < 0 ist, setze a = 1 ansonsten setze a = 2

90

2 Strukturierte Programmierung mit C

Beachten Sie, dass die einger¨ uckten Zeilen eine Gesamtheit bilden, sodass also im Fall a > 0 beide Anweisungen nach dem Doppelpunkt ausgef¨ uhrt werden m¨ ussen. 2.2. Gegeben seien die folgenden beiden Algorithmen zur Beschreibung des Verhaltens eines Autofahrers an einer Straßenkreuzung. Wie interpretieren Sie die verschiedenen Einr¨ uckungen? In welcher Situation unterscheiden sich Algorithmus 1 und Algorithmus 2? Welcher Algorithmus erscheint Ihnen zweckm¨aßiger? Algorithmus 1: Falls die Ampelanlage funktioniert dann gehe folgendermaßen vor: falls die Ampel rot oder gelb anzeigt dann bleibe stehen ansonsten fahre Algorithmus 2: Falls die Ampelanlage funktioniert dann gehe folgendermaßen vor: falls die Ampel rot oder gelb anzeigt dann bleibe stehen ansonsten fahre 2.3. Das folgende Programm in einer nicht existierenden Programmiersprache enth¨alt mehrere Sprungbefehle, die sich auf die jeweils voran gestellten Zeilennummern beziehen. Stellen Sie fest, zu welchem Problem dieses Programm f¨ uhrt. 01 x=1 02 y=2 03 gehe zu Zeile 07 04 berechne 2x 05 berechne x+y 06 gehe zu Zeile 03 07 gehe zu Zeile 04

2.2 Erste C-Programme Die Programmiersprache C dient der strukturierten Programmierung, nicht mehr und nicht weniger. Als man C entwickelt hat, dachte noch kein Mensch an so etwas wie Objektorientierung, und deshalb ist C von der objektorientierten Programmierung meilenweit entfernt. Das ist kein Nachteil f¨ ur eine Programmiersprache, sofern sie f¨ ur Aufgaben gedacht ist, die man mit strukturierter Programmierung erledigen kann und die nichts weniger brauchen als

2.2 Erste C-Programme

91

irgendwelche Objekte - solche Aufgaben gibt es noch immer massenweise, weshalb auch nicht zu erwarten ist, dass das klassische C bald sein seliges Ende erreicht und unwiderruflich den objektorientierten Sprachen weichen muss. 2.2.1 Die Entwicklung von C Wie es zur Entwicklung der Programmiersprache C kam, ist schnell erz¨ ahlt. Ab 1969 arbeiteten Ken Thomson und Dennis Ritchie in den recht ber¨ uhmten AT&T-Bell-Laboratories ein neues Betriebssystem mit dem Namen UNIX, von dem man auch heute noch hin und wieder etwas h¨ ort. Ein Werkzeug dabei war die bereits vorhandene Programmiersprache BCPL, die Thomson weiter entwickelte und mit dem sprechenden Namen B versah. Dabei handelte es sich um ein reines Werkzeug zur Systemprogrammierung, das - grob gesprochen - nicht zwischen einer ganzen Zahl und einer N¨ ahmaschine unterscheiden konnte; B kannte nichts anderes als so genannte Maschinenworte, die man zur Systemprogrammierung brauchte, und f¨ ur eine Programmiersprache, mit der man vielleicht auch Anwendungssoftware erstellen wollte, war das kein Zustand. Dass B zus¨ atzlich noch sehr große N¨ ahe zu Assembler aufwies und daher nicht u aßig benutzerfreundlich war, machte die Sache nicht besser. ¨berm¨ Thomson machte sich daher an eine Weiterentwicklung von B, die er konsequenterweise C nannte. Mit C war es dann m¨ oglich, sich an die Prinzipien der strukturierten Programmierung zu halten, und dar¨ uber hinaus war C eine typisierte Sprache: es machte jetzt durchaus einen Unterschied, ob man mit ganzen Zahlen oder Kommazahlen hantierte, ob man Buchstaben verarbeiten wollte oder numerische Werte, denn C konnte diese verschiedenen Datentypen deutlich voneinander unterscheiden. Einerseits war und ist C also eine Sprache zur Entwicklung von Anwendungssoftware, andererseits haben Thomson und Ritchie bereits 1973 das Betriebssystem UNIX fast vollst¨ andig mit Hilfe von C entwickelt, woraus man schließen muss, dass C auch zum Schreiben von Systemsoftware mehr als geeignet ist. Inzwischen ist C schon lange eine eigenst¨andige Programmiersprache, keineswegs mehr abh¨ angig von UNIX, sondern auf den verschiedensten Rechnertypen unter den verschiedensten Betriebssystemen einsetzbar, vom schlichten PC bis zum Supercomputer. Sie werden zugeben, dass es nicht schaden kan, sich ein wenig mit einer so weit verbreiteten Sprache auszukennen. Die erste Standardisierung von C fand 1978 statt, als B. Kerninghan und Ritchie ihr Buch The C Programming Language“ heraus brachten, auch heu” te noch ein Standardbuch, mit dem man weltweit C lernt. Einen offiziellen Standard gab es dann zehn Jahre sp¨ater mit dem so genannten ANSI-C des American National Standard Institute, abgek¨ urzt ANSI.

Die Entwicklung von C Die Programmiersprache C wurde in den fr¨ uhen siebziger Jahren des zwanzigsten Jahrhunderts im Zusammenhang mit dem Betriebssystem UNIX ent-

92

2 Strukturierte Programmierung mit C

wickelt. Sie dient einerseits der Anwendungsprogrammierung, wird aber andererseits auch in der Systemprogrammierung, vor allem in Bezug auf UNIX eingesetzt.

2.2.2 Ein erstes Programm Irgendwann muss man ja mal anfangen, sich an die konkreten Programme zu wagen. Ich will dabei nichts u urzen und beginne mit einer Variante des ¨berst¨ Programms, das Sie in so ziemlich jedem C-Buch finden werden. Im Original von Kerninghan und Ritchie gibt es die Worte Hello world“ auf dem Bild” schirm aus; da wir uns hier aber nicht in Amerika befinden, ziehe ich es vor, Sie mit einem schlichten Guten Morgen“ zu begr¨ ußen. Das Programm sieht ” dann folgendermaßen aus. /* gruss.c Guten Morgen */ #include main( ) { printf("Guten Morgen\n"); } Allzu sch¨ on ist das sicher nicht, aber es z¨ahlen ja auch die inneren Werte. Sehen wir uns einmal an, wie dieses Programm aufgebaut ist. Es beginnt mit irgendwelchen Informationen, die zwischen /* und */ eingeschlossen sind. Solche Informationen sind immer unproblematisch, denn an /* und */ k¨ onnen Sie erkennen, dass es sich um Kommentare handelt. In einen Kommentar k¨ onnen Sie schreiben, was Sie wollen, er dient nur der Information des Lesers und wird vom eigentlichen Programm v¨ollig ignoriert. Er kann sich u ¨brigens auch u ¨ber mehrere Zeilen erstrecken, das ist ganz egal, Hauptsache, er wird zwischen /* und */ gesteckt. Diesem Kommentar entnehme ich, dass das Programm gruss.c heißt und irgendetwas mit dem Ausdruck Guten Morgen“ zu tun hat. ” Anschließend sehen Sie eine Leerzeile, woraus Sie schon schließen k¨ onnen, dass man in ein C-Programm jederzeit ohne Folgen Leerzeilen einf¨ ugen darf; die sind dem Programm ¨ahnlich gleichg¨ ultig wie Kommentare. Die nachfolgende Anweisung #include verschiebe ich kurz auf sp¨ ater, lassen Sie mich erst auf main() eingehen. Wenn man es genau nimmt, handelt es sich hier um eine Funktion, aber Funktionen werden wir erst sp¨ ater besprechen, also beschr¨ anke ich mich erst einmal auf den Hinweis, dass mit main() das Hauptprogramm eingeleitet wird. Mein C-Programm beginnt seine Ausf¨ uhrung am Anfang dessen, was ich unter main() abgelegt habe, und deshalb muss in usste das Projedem C-Programm auch so ein main() vorkommen, sonst w¨ gramm nicht, wo es u ¨berhaupt anfangen soll. Versuchen Sie einmal, einfach nur main anstatt main() zu schreiben - beim Schreiben selbst wird das nat¨ urlich noch keine Probleme machen; wenn es aber darum geht, das Programm zum

2.2 Erste C-Programme

93

Laufen zu bringen, werden Sie die Folgen sp¨ uren. Vielleicht kennen Sie das noch aus der Mathematik: main() ist nun mal eine Funktion, und eine Funktion hat immer etwas, was man in sie einsetzen kann, weshalb man ja auch meistens f (x) oder so ¨ ahnlich f¨ ur Funktionen schreibt. Und in C erkennt man eine Funktion eben an der Existenz der beiden Klammern, ganz gleich ob dazwischen etwas steht oder nicht. Jede Funktion muss also als Zusatz die ¨ Klammern ( ) mit sich schleppen, sonst gibt es Arger. Was nun in der main()-Funktion, also im eigentlichen Hauptprogramm, passiert, finden Sie innerhalb der Mengenklammern { und }. Die ge¨ offnete Klammer bezeichnet den Anfang des Anweisungsteils der main()-Funktion, hier stehen s¨amtliche Anweisungen, aus denen die Funktion besteht. Im Falle des riesigen Programms gruss.c habe ich nur eine Anweisung zu bieten, sie heißt printf("Guten Morgen\n") und ist im Vergleich zur dar¨ uber stehenden Zeile etwas nach rechts einger¨ uckt. Das ist Absicht. In der Regel verwendet man eine einfache Einr¨ uckungstechnik, um so etwas wie eine Hierarchie abzubilden: die Anweisung printf("Guten Morgen\n") geh¨ ort zur main( )Funktion, sie ist nichts weiter als ein Teil von main(), weshalb man sie eben ein wenig nach rechts r¨ uckt. Das ist nicht zwingend notwendig, Sie k¨ onnen ¨ auch jede Zeile wieder ganz vorne anfangen lassen, aber es dient der Ubersichtlichkeit und tr¨ agt dazu bei, ein Programm anst¨ andig zu strukturieren schließlich sollen wir hier strukturiert programmieren. Nat¨ urlich hat die Anweisung printf("Guten Morgen\n") auch eine Bedeutung, printf() steht f¨ ur formatiertes Drucken oder formatierte Ausgabe, und das heißt einfach nur, dass alles, was in der Klammer nach printf() steht, nach bestimmten Regeln ausgegeben wird. Beachten Sie u ¨brigens, dass es sich bei printf() um eine vordefinierte Funktion handelt, und Funktionen schreibt man u ¨blicherweise immer mit dem Zusatz () auf, damit der Leser das Kommando an den Klammern sofort als Funktion erkennt. In Fall meines kleinen Programms wird also die Zeichenkette Guten Mor” gen“ ausgegeben und danach in eine neue Zeile gewechselt, denn das so genannte Steuerzeichen \n ist ein Zeilentrenner, der bewirkt, dass das Programm nach der Ausgabe in eine neue Zeile wechselt. Ohne den Zeilentrenner bleiben Sie in der zuerst ausgegebenen Zeile bis ans Ende Ihrer Tage. Man h¨ atte also die gleiche Ausgabe auch mit dem folgenden Programm erreichen k¨ onnen: /* gruss.c Guten Morgen */ #include main( ) { printf("Guten "); printf("Morgen"); printf("\n"); }

94

2 Strukturierte Programmierung mit C

Die ersten beiden Ausgabekommandos bringen hier n¨ amlich alles in eine Zeile, und erst danach wird durch die Verwendung des Zeilentrenners f¨ ur eine neue Zeile gesorgt. Sie k¨ onnen hier auch noch eine weitere wichtige Eigenart von C-Programmen deutlich sehen: eine Anweisung muss immer mit einem Strichpunkt abgeschlossen werden, sonst kommen Sie in Schwierigkeiten. Wenn ich im Rahmen des Textes Anweisungen aufschreibe, dann lasse ich den Strichpunkt immer weg, weil es beim Lesen eines Satzes nur verwirrt, aber im Programm d¨ urfen Sie ihn nicht vergessen. Dass am Ende des Programms, also am Ende der main()-Funktion, dann die schließende Klammer steht, hatte ich schon gesagt. Ein Punkt ist aber noch offen: was soll dieses omin¨ ose #include bedeuten? Ganz einfach. C in seiner Grundform kennt u oglichkeiten zur Ausgabe von ¨berhaupt keine M¨ Daten auf dem Bildschirm oder gar zur Dateneingabe u ¨ber die Tastatur. Die printf()-Anweisung kann man also nicht einfach so verwenden, Sie m¨ ussen vorher daf¨ ur sorgen, dass Ihr C-Programm diese Anweisung auch versteht, und genau das passiert mit #include. Die Datei stdio.h ist eine so genannte Header-Datei, in der bestimmte Ein- und Ausgabefunktionen zu finden sind, die Ihnen ohne diese Datei nicht zur Verf¨ ugung st¨ unden. Durch den Befehl #include ist es angenehmerweise m¨ oglich, alle in der Header-Datei angegebenen Funktionen auch in dem Programm zu benutzen, das den Befehl zum Einbinden der Header-Datei enth¨ alt. Weitere HeaderDateien werden Ihnen im Lauf der Zeit noch begegnen.

main()-Funktion und Ausgabebefehl Jedes C-Programm muss eine main()-Funktion enthalten, wobei darauf zu achten ist, dass sie nicht nur als main, sondern durch Angabe von Klammern als main() definiert wird. Die Ausgabe am Bildschirm wird durch printf() geregelt; der printf()-Befehl ist in der Header-Datei stdio.h zu finden, die durch die Anweisung #include in das aktuelle Programm eingebunden wird. Das erste C-Programm ist nun geschrieben. Und jetzt? Irgendwie m¨ ussen Sie den Computer dazu bringen, etwas damit anzustellen, und das geschieht mit Hilfe spezieller Programme. Das erste der Hilfsprogramme haben Sie wohl bereits benutzt: den Programmtext tippen Sie mit irgendeinem Editor und speichern ihn dann unter einem selbstgew¨ ahlten Namen als Datei ab. Ich hatte mich hier f¨ ur den Namen gruss.c entschieden. Der eigentliche Dateiname gruss ist dabei ziemlich egal, bei der Endung haben Sie allerdings keine Wahlfreiheit: ein C-Programm muss die Endnung .c“ aufweisen, sonst erkennt der Compiler nicht, dass Sie ” ihm ein C-Programm pr¨asentieren. Und damit haben wir schon das zweite unbedingt n¨ otige Hilfsprogramm gefunden: den Compiler. Da Ihre CPU nicht in der Lage ist, den in den Editor geschriebenen Quellcode, also das eigentliche C-Programmm, zu verstehen, muss jemand dieses C-Programm nehmen und in eine maschinenverst¨andliche Sprache u ¨bersetzen. Das kann

2.2 Erste C-Programme

95

nat¨ urlich nur dann gut gehen, wenn Ihr Programm einwandfrei den Regeln der C-Grammatik entsprochen hat, da darf man sich keine Schlampereien erlauben. Ich als menschlicher Leser verstehe wahrscheinlich auch die Aufforderung printd("Guten Morgen\n"), der Compiler als Vertreter ¨ außerster Genauigkeit versteht sie nicht. In einem ersten Schritt muss er also die syntaktische Korrektheit Ihres Programms u ufen, das er in der vorher angeleg¨berpr¨ ten Quelldatei findet, und das heißt, der Compiler geht Ihren Programmtext Zeichen f¨ ur Zeichen, Wort f¨ ur Wort durch und stellt fest, ob Sie sich auch an die Regeln der C-Grammatik gehalten haben. Werden diese Regeln irgendwo durchbrochen, so wird er Ihnen die Regelverst¨ oße in Form von Fehlermeldungen um die Ohren hauen und sich weigern, das vorliegende Programm in lauff¨ahigen Maschinencode zu u ¨bersetzen - wie sollte er auch, da er aufgrund der fehlerhaften Grammatik gar nicht wissen kann, was Sie eigentlich von ihm wollen. In diesem Fall liegt es also an Ihnen, noch einmal das Quellprogramm mit Hilfe des Editors zu bearbeiten und die angezeigten Fehler auszumerzen. Sobald Sie das geschafft haben, wird der n¨achste Compilerlauf anzeigen, dass Ihr Programm fehlerfrei ist und deshalb lauff¨ ahiger Maschinencode erzeugt wurde. Sollten Sie beispielsweise mit einem PC auf Windows-Basis arbeiten, so hat der Compiler eine Datei namens gruss.exe erzeugt, wobei die Endung exe eine Abk¨ urzung f¨ ur executable, also ausf¨ uhrbar darstellt und somit bereits das ausf¨ uhrbare Programm vorliegt. Manchmal ist das Leben aber auch einfacher. Es gibt recht komfortable Entwicklungsumgebungen, die Ihnen die Arbeit des st¨ andigen Wechsels zwischen Editor, Compiler und Ausf¨ uhrung abnehmen, weil alle diese Komponenten in der Entwicklungsumgebung integriert sind. Sie werden dann also beispielsweise im Rahmen einer graphischen Benutzeroberfl¨ ache Ihren Programmtext schreiben, ihn mit einem Mausklick - nat¨ urlich auf der richtigen ¨ Schaltfl¨ache - abspeichern, mit einem weiteren Mausklick die Ubersetzung ¨ durch den Compiler anwerfen und schließlich, sofern die Ubersetzung fehlerfrei funktioniert hat, durch einen letzten Mausklick das Programm laufen lassen und die originelle Begr¨ ußung Guten Morgen“ bewundern. Das kann man ” so machen und es ist bequem, aber man muss es nicht. Genauso gut k¨ onnen Sie das Editieren beispielsweise mit dem DOS-Editor erledigen, den Compiler durch einen Befehl vom DOS-Fenster aus aufrufen und dann vom gleichen Fenster aus durch Eingabe des Befehls gruss daf¨ ur sorgen, dass die ausf¨ uhrbare Datei gruss.exe zur Anwendung kommt und Ihr Programm abl¨ auft. Wie Sie im Einzelnen vorgehen, h¨angt dabei von Ihrem pers¨ onlichen Geschmack und den M¨oglichkeiten Ihres C-Compilers ab. Noch zwei Worte zum Compiler und zum Kompilieren. Bei großen Programmen kann und wird es vorkommen, dass sie aus getrennten Modulen bestehen, aus Teilprogrammen, die auch in getrennten Dateien abgelegt werden, weil zum Beispiel mehrere Entwickler daran arbeiten und man Durcheinander vermeiden will. Die einzelnen Module kann man dann zwar separat u ¨bersetzen in maschinenlesbaren Code, aber damit das gesamte Programm zur Verf¨ ugung steht, m¨ ussen Sie diese Module noch zu einem Ganzen zusam-

96

2 Strukturierte Programmierung mit C

menf¨ ugen. Das geschieht durch den Aufruf eines Linkers, der die nach dem Kompilieren vorliegenden Teile zu einem lauff¨ ahigen Programm zuammenbindet und die Voraussetzungen daf¨ ur schafft, dass alle diese Teile koordiniert zusammenarbeiten. Wie er das im Einzelnen macht, braucht uns hier nicht zu interessieren, so riesig werden unsere Programme nicht werden. Der Linker wird also nach dem Compiler aktiv. Am Anfang des Compilierens dagegen muss der Pr¨ aprozessor seiner Arbeit nachgehen, den ich schon einmal, wenn auch ohne Namensnennung, erw¨ ahnt hatte. Erinnern Sie sich noch an die Header-Dateien? Die Anweisung #include ist eine Anweisung an den Pr¨aprozessor, sich die entsprechende Datei zu besorgen und f¨ ur ¨ die Dauer der Ubersetzung in den Quellcode hinein zu kopieren. Auf diese Weise sorgt der Pr¨aprozessor daf¨ ur, dass alle Informationen aus der angegebenen Header-Datei auch in Ihrem Programm verf¨ ugbar sind und Sie beispielsweise die Ein- und Ausgabefunktionen aus der Datei stdio.h ungest¨ ort einsetzen k¨onnen.

Der Entwicklungsprozess Der Programmtext wird mit Hilfe eines Editors geschrieben und in einer Datei mit der Endung .c“ gespeichert. Anschließend u uft der Compiler die ¨berpr¨ ” syntaktische Korrektheit des Programms und u bersetzt es im Falle einer er¨ ¨ folgreichen Uberpr¨ ufung in maschinenlesbaren Code, der dann zur Ausf¨ uhrung gelangen kann. Zu Beginn des Compilerlaufs werden vom Pr¨ aprozessor die angegebenen Header-Dateien in das Programm eingef¨ ugt. Besteht das Programm aus mehreren separaten Modulen, so m¨ ussen die erzeugten Codeteile mit einem Linker zusammen gebunden werden. 2.2.3 Variablen Das war nun ziemlich viel Erkl¨arung f¨ ur ein Programm, das nicht allzu viel Leistung erbringt; es sollte doch m¨oglich sein, etwas mehr von einem C-Programm zu verlangen als eine schlichte Begr¨ ußung. Ein etwas leistungsf¨ ahigeres Programm sehen wir uns jetzt an. /* rechnen.c -- summe berechnen */ #include main() { int x; int y; int z; x=3; y=4; z=x+y; printf("%d",z); }

2.2 Erste C-Programme

97

Das Programm f¨ angt ¨ ahnlich an wie das vorherige; u ¨ber Kommentare, Header-Dateien und die main()-Funktion brauche ich nicht mehr zu ¨ außern. Innerhalb der main()-Funktion finden sie jetzt aber etwas Neues, und das sind die so genannten Variablen. Es werden hier drei Variablen namens x, y und z angelegt, die alle mit dem Vorsatz int geschlagen sind. Die Bedeutung ist ganz einfach. Ich will hier nichts anderes als eine Addition ganzer Zahlen durchf¨ uhren, also brauche ich Variablen, die ganze Zahlen repr¨ asentieren. Das teile ich dem Compiler mit, indem ich so etwas wie int x schreibe und ihm dadurch klar mache, dass x eine Variable vom Typ Integer sein soll. Zu jeder Variable geh¨ort also ein Datentyp, der angibt, um was f¨ ur eine Art von Variable es sich handeln soll, in diesem Fall um eine Integer-Variable. Sobald der Compiler auf so eine Anweisung st¨ oßt, wird er im Arbeitsspeicher des Rechners ein wenig Speicherplatz reservieren - eben so viel, dass eine ganze Zahl hineinpasst. Auf dieses kleine St¨ uckchen Speicherplatz kann dann mein Programm sorglos zugreifen, wann immer es die Bezeichnung x verwendet. Dass Variablen nichts anderes sind als Anwartschaften auf etwas Speicherplatz, hat Konsequenzen. Sie haben im ersten Kapitel gesehen, dass der Arbeitsspeicher in Speicherzellen aufgeteilt ist, und deshalb wird die Gr¨ oße beispielsweise einer ganzen Zahl abh¨ angen von der Gr¨oße der Speicherzellen, in denen sie abgelegt werden soll. Wie man so etwas ausrechnet, haben Sie im Abschnitt u ¨ber logische Schaltungen und Addierer gesehen. Hat man beispielsweise zwei Byte, also 16 Bits, f¨ ur eine Zahl zur Verf¨ ugung, so kann man ganze Zahlen zwischen −215 = −32768 und 215 − 1 = 32767 in einer Variable vom Typ int ablegen, bei vier Byte, also 32 Bits, liegt der Bereich schon zwischen −231 = −2147483648 und 231 − 1 = 2147483647. Das ist Ihnen nicht neu und soll nur zeigen, dass ich Sie im letzten Kapitel nicht zu meinem pers¨ onlichen Vergn¨ ugen mit Zahlendarstellungen gequ¨ alt habe. Wie groß nun die ganzen Zahlen auf Ihrem eigenen C-Compiler werden k¨onnen, weiß ich nicht, das h¨ angt von Ihrem Compiler, Ihrem Computer und Ihrem Betriebssystem ab. Oft genug hat man eine L¨ ange von vier Byte, und das ist doch immerhin schon etwas. Nat¨ urlich gibt es nicht nur Variablen vom Datentyp int, in sp¨ ateren Beispielen werden Sie noch andere Typen sehen. In meinem kleinen Programm rechnen habe ich drei int-Variablen angegeben, und dieses Angeben nennt man normalerweise Deklarieren. Sie k¨onnen jede Variable einzeln deklarieren, was ich aber nicht empfehle, denn es spart Zeit und Platz, die Deklaration typweise zu erledigen. An Stelle der drei separaten Deklarationen h¨ atte ich n¨amlich auch k¨ urzer int x, y, z; schreiben k¨ onnen: gleicher Effekt, weniger Platz und Schreibarbeit. Nun werden diese Variablen nicht nur deklariert, mit ihnen passiert auch etwas: endlich habe ich einmal ein Programm, das konkret rechnet. Nachdem x der Wert 3 und y der Wert 4 mit den Kommandos x=4 bzw. y=3 zugewiesen wurde, berechnet das Programm die Summe aus x und y und weist sie der Variablen z zu. Das Kommando z=x+y zerf¨ allt also bei seiner Ausf¨ uhrung in zwei Teile: zuerst wird die Summe aus den beiden Variablen x und y be-

98

2 Strukturierte Programmierung mit C

rechnet, und dann wird das Ergebnis der Variablen z zugewiesen. Wie Sie sehen, erfolgt die Zuweisung eines Wertes an eine Variable mit einem schlichten Gleichheitszeichen; nachdem diese Zuweisung erfolgt ist, ist z mit dem Wert 7 belegt. Schon mal nicht schlecht, aber der Benutzer des Programms wird ein gewisses Interesse daran haben, die Ergebnisse seiner Rechnung auch zu sehen, und daf¨ ur gibt es schließlich die printf()-Anweisung. Hier lautet sie printf("%d",z), und das sieht einigermaßen abschreckend aus. Nur Geduld, das ist halb so wild. Wie ich Ihnen erz¨ ahlt habe, st¨ oßt unser printf() eine formatierte Ausgabe an, und irgendwie muss man dem Kommando die n¨ otigen Formatanweisungen zukommen lassen. Ausgeben wird es immer eine Zeichenkette; die Frage ist nur, wie sich diese Zeichenkette zusammensetzt. W¨ ahrend die Zeichenkette selbst durch Anf¨ uhrungszeichen begrenzt wird, finden Sie in ihr die etwas seltsam anmutende Angabe %d - ein so genanntes Formatelement, mit dem man steuert, wie die Ausgabe der Variablen stattfinden soll. Formatelemente fangen immer mit % an und geben an, wo und wie die nachfolgenden Argumente in die Ausgabe einzuf¨ ugen sind. Da ich in die Zeichenkette nur das Formatelement %d geschrieben habe, wird auch nur die anschließend aufgef¨ uhrte ganzzahlige Variable z am Bildschirm ausgegeben, und das passt auch gut zusammen, denn %d benennt immer die Ausgabe eines ganzzahligen Wertes. Wie m¨ usste man formatieren, wenn man zum Beispiel sagen wollte Die Summe lautet 7“? Nichts einfacher als das. Man packt die auszuge” bende Zeichenkette wieder in Anf¨ uhrungszeichen, und an die Stelle, an der die Variable vorkommen m¨ usste, schreibt man das entsprechende Formatelement. Konkret heißt das dann: printf("Die Summe lautet %d",z). Bei der Ausf¨ uhrung dieses Befehls wird an der passenden Stelle die Variable z unter Verwendung des Formatelementes ausgegeben. Das setzt nat¨ urlich voraus, dass Variable und Formatelement zueinander passen: Sie k¨ onnen beispielsweise eine reelle Zahl, bei der Stellen nach dem Komma vorkommen, nicht mit einer Formatelement f¨ ur ganze Zahlen ausgeben. Und wie kann man daf¨ ur sorgen, dass die gesamte Rechenaufgabe samt Ergebnis zur Ausgabe kommt? Auch nicht schwer. Wenn Sie das Kommando printf("Die Summe aus %d und %d lautet %d",x,y,z); in Ihr Programm aufnehmen, wird es Ihnen die Ausgabe Die Summe aus 3 und 4 lautet 7 liefern. F¨ ur jede auszugebende Variable m¨ ussen Sie also ein passendes Formatelement in Ihre Zeichenkette einf¨ ugen. Die Variablen werden dann in der Reihenfolge ihres Auftretens mit den Formatelementen verglichen, und wenn alles zusammen passt, kann problemlos die Ausgabe erfolgen. Die auszugebenden Variablen nennt man auch Argumente.

Variablen und Ausgabe Eine Variable gibt eine Speicherstelle im Arbeitsspeicher an. Jede Variable muss einen Datentyp haben; eine ganzzahlige Variable wird beispielsweise durch die Angabe int vor dem Variablennamen als ganzzahlig gekennzeich-

2.2 Erste C-Programme

99

net. Die Gr¨ oße der entsprechenden Speicherstelle und damit auch die Gr¨ oße der verwendbaren Zahlen h¨angt vom Compiler, vom Computer und vom Betriebssystem ab. Die Ausgabe von Variablen erfolgt mit der printf()-Anweisung. Ausgegeben wird immer eine Zeichenkette, zu jedem auszugebenden Argument muss es in der Zeichenkette ein entsprechendes Formatelement geben.

2.2.4 Eingabe von der Tastatur Zugegeben: die Rechenaufgabe 3 + 4 = 7 h¨atten sie wahrscheinlich auch noch ohne einen C-Compiler l¨osen k¨onnen - vermutlich sogar schneller. Aber was ist mit 3275789 + 7648242 oder noch schlimmeren Zahlen? Zu Fuß macht das keinen rechten Spaß mehr, aber das bisherige Programm rechnen.c ist daf¨ ur nicht geeignet, es sei denn, Sie ¨andern die Belegungen der Variablen x und y im Programmtext und starten anschließend wieder den Compiler. Bei einem so kleinen Programm w¨are das notfalls, wenn einem nichts anderes einf¨ allt, noch vertretbar, bei einem gr¨oßeren k¨ ame kein Mensch auf so eine umst¨ andliche Idee. Man braucht vielmehr eine M¨ oglichkeit, den Wert einer Variablen w¨ ahrend der Laufzeit des Programms zu beeinflussen, und diese M¨oglichkeit liefert der scanf()-Befehl. Sehen Sich sich einmal an, wie das Rechenprogramm unter Einsatz dieser Anweisung aussieht. /* rechnen.c -- summe berechnen */ #include main() { int x, y, z; scanf("&d",&x); scanf("&d",&y); z=x+y; printf("Die Summe aus %d und %d lautet %d",x,y,z); } Mit dem Kommando scanf("%d",&x) machen Sie dem Compiler zweierlei klar. Erstens erkl¨aren Sie ihm, dass jetzt gleich eine Eingabe erfolgen soll, und zwar eine Eingabe einer ganzen Zahl: das kann er schon an der Formatangabe %d erkennen, die Ihnen bereits bei der formatierten Ausgabe begegnet ist. Und zweitens soll diese Eingabe in die Variable x hineingeschrieben werden, was durch die Angabe von &x ausgedr¨ uckt wird. Vielleicht wundern Sie sich ein wenig dar¨ uber, was wohl dieses seltsame & vor dem Variablennamen soll, und da wundern Sie sich nicht alleine. C regelt das ein wenig umst¨ andlich. Sie m¨ ussen dem Compiler n¨amlich direkt mitteilen, an welcher Stelle des Arbeitsspeichers er die Eingabe unterbringen soll, die pure Angabe des Variablennamens alleine reicht nicht aus. Wie Sie sp¨ater noch sehen werden, ist

100

2 Strukturierte Programmierung mit C

das aber keine große Sache, denn die zu einer Variablen x geh¨ orende konkrete Speicherstelle kann man ganz einfach mit &x ansprechen, weshalb dann auch im scanf()-Kommando jede Variable mit dem &-Zeichen versehen sein muss. Lassen Sie jetzt das Programm laufen, so wird es gleich nach dem Start anhalten und eine Eingabe u onnen ¨ber die Tastatur erwarten, die Sie ihm auch g¨ sollten; schenken Sie Ihrem Programm also eine ganze Zahl und schließen Sie diese Eingabe mit der Return-Taste ab. Kaum haben Sie Ihre Arbeit getan, m¨ ussen Sie gleich noch mal an die Arbeit, denn schließlich hat Ihr Programm zwei Eingabekommandos zu bieten. Erst wenn Sie sowohl x als auch y eingegeben haben, kann die Berechnung stattfinden und das Ergebnis ausgegeben werden. Achten Sie dabei unbedingt darauf, dass Sie auch wirklich ganze Zahlen eingeben, also eine Eingabe vornehmen, die zum Kommando passt; andernfalls m¨ ussen sie damit rechnen, dass das Programm dummerweise nicht abbricht, sondern mit v¨ ollig sinnlosen Werten weiter rechnet. Guter Stil ist das aber immer noch nicht. Der Benutzer des Programms wird zweimal mit einem blinkenden Cursor konfrontiert und hat nicht die leiseste Ahnung, was er nun machen soll, weil es ihm keiner erkl¨ art hat. Bevor Sie von einem Anwender Ihres Programms also eine Eingabe verlangen, sollten Sie ihm freundlicherweise mitteilen, was f¨ ur eine Eingabe Ihnen zu Ihrem Gl¨ uck noch fehlt. In unserem Fall k¨ onnte das so aussehen: /* rechnen.c -- summe berechnen */ #include main() { int x, y, z; printf("Geben Sie bitte eine ganze Zahl ein:\n"); scanf("&d",&x); printf("Geben Sie bitte noch eine ganze Zahl ein:\n"); scanf("&d",&y); z=x+y; printf("Die Summe aus %d und %d lautet %d",x,y,z); } Jetzt erst weiß der Anwender, wie er mit dem Programm umgehen soll, und alle sind zufrieden. Alle, bis auf mich, denn noch immer ist die Eingabe etwas umst¨ andlich programmiert. Wenn man schon bei der Ausgabe mehrere Variablen in einem Kommando ausgeben kann, dann sollte et¨ was Ahnliches auch bei der Eingabe m¨ oglich sein. Das ist auch so. Anstatt sich mit einem scanf()-Kommando pro Variable zu plagen, k¨ onnen Sie auch schlicht das eine Kommando scanf("%d %d",&x,&y) verwenden, und schon werden beide Variablen von der Tastatur eingelesen. Nat¨ urlich m¨ ussen Sie in diesem Fall auch Ihren auffordernden Text in so etwas wie printf("Geben Sie bitte zwei ganze Zahlen ein:\n") um¨ andern. Dagegen muss der Benutzer Ihres Programms darauf achten, entweder zwei ganze

2.2 Erste C-Programme

101

Zahlen, getrennt durch ein Leerzeichen, einzugeben und dann die Eingabe mit der Return-Taste abzuschließen, oder aber genau wie vorhin vorzugehen und nach jeder der beiden Zahlen die Return-Taste zu dr¨ ucken. Beides funktioniert und liefert das richtige Ergebnis.

Das scanf()-Kommando Die formatierte Eingabe von der Tastatur erfolgt mit Hilfe des scanf()Kommandos. Es ben¨otigt eine Formatangabe wie z.B. "&d", in der festgelegt wird, welche Art von Variablen jetzt eingelesen werden soll, und eine anschließende Liste von Variablen, wobei vor jeden Variablennamen das &-Zeichen gesetzt werden muss, da auf diese Weise direkt die Arbeitsspeicheradresse der jeweiligen Variablen angesprochen wird. Sollen mehrere Variablen eingelesen werden, so muss die Formatangabe der Anzahl und den Datentypen der Variablen angepasst sein. Das scanf()-Kommando wird uns ab jetzt noch h¨ aufiger begegnen, zum Beispiel gleich im n¨achsten Teil. 2.2.5 Arithmetik Sie k¨ onnen jetzt u ¨ber die Tastatur ein- und auf dem Bildschirm wieder ausgeben, immerhin. Was Sie bisher nur sehr eingeschr¨ ankt k¨ onnen, ist rechnen, aber das wird sich gleich ¨andern. Wie jede Programmiersprache, die sich der strukturierten Programmierung verschrieben hat, verf¨ ugt selbstverst¨ andlich auch C u ¨ber die n¨otigen Grundrechenarten, und man schreibt sie ziemlich genauso auf, wie man sich das vorstellt. Dazu ein kleines Beispielprogramm. /* grundrechenarten.c -- grundrechenarten anwenden */ #include main() { int x, y, a, b, c, d; printf("Geben Sie bitte zwei ganze Zahlen ein:\n"); scanf("&d &d",&x, &y); a=x+y; b=x-y; c=x*y; d=x/y; printf("Die Werte lauten: %d, %d, %d und %d\n",a,b,c,d); } Sie sehen, wie es funktioniert. Plus, Minus, Mal und Durch symbolisiert man durch +, -, * und /, und mehr steckt nicht dahinter. Ein Problem sollte

102

2 Strukturierte Programmierung mit C

man allerdings nicht untersch¨ atzen. Sobald Sie das Programm laufen lassen und die Eingaben x = 5 und y = 9 von sich geben, dann werden Sie zwar die korrekten Werte a = 14, b = −4 und c = 45 erhalten, aber zur allgemeinen Freude wird d = 0 sein. Wie? 5/9 = 0? Ja, manchmal schon, wenn man n¨amlich den Fehler macht, nur mit ganzen Zahlen zu rechnen. Nat¨ urlich ist 5/9 = 0, 555.., aber dieses Ergebnis k¨onnen Sie der ganzzahligen Variablen d auf keinen Fall zumuten, die nimmt nur an, was sie kennt: ganze Zahlen. Und der ganzzahlige Anteil von 0, 555.. ist nun mal schlicht 0, weshalb auf d das Ergebnis 0 stehen wird. Dagegen muss man etwas tun. Damit Sie auch mit reellen Zahlen rechnen, mit Kommazahlen umgehen k¨ onnen, hat man den Datentyp float erfunden. Er sorgt daf¨ ur, dass die Nachkomastellen nicht einfach abgeschnitten, sondern tats¨achlich registriert und auf Wunsch weiter verarbeitet werden. Also sollte es doch gen¨ ugen, jetzt die Variable d als float-Variable zu deklarieren, um alle Probleme mit der Division zu l¨osen. Das Programm w¨ urde dann folgendermaßen aussehen. /* grundrechenarten.c -- grundrechenarten anwenden */ #include main() { int x, y, a, b, c; float d; printf("Geben Sie bitte zwei ganze Zahlen ein:\n"); scanf("&d &d",&x, &y); a=x+y; b=x-y; c=x*y; d=x/y; printf("Die Werte lauten: %d, %d, %d und %f\n",a,b,c,d); } Die Variable d ist aus der Liste der ganzzahligen Variablen verschwunden und taucht als eine float-Variable wieder auf, also als eine schlichte reelle oder rationale Zahl, die auch mit Nachkommastellen geschlagen sein kann. Und da die Formatangabe %d f¨ ur ganze Zahlen reserviert ist, muss sich auch bei der Ausgabe etwas ¨ andern: als letztes Formatelement taucht jetzt %f auf, was offenbar bedeutet, dass hier eine float-Variable ausgegeben werden soll. So weit, so gut. Lassen Sie jetzt das Programm laufen mit den Eingabewerten x = 5 und y = 9, dann erhalten Sie die Ausgabe: Die Werte lauten 14, -4, 45 und 0.000000 Hat sich ja richtig gelohnt. Die Variable d wird zwar tats¨ achlich als Kommazahl ausgegeben, wobei die Beobachtung, dass man genau genommen einen Punkt und kein Komma verwendet, nicht unter den Tisch fallen sollte - aber der Wert selbst hat sich nicht verbessert. Das ist auch kein Wunder. Durch

2.2 Erste C-Programme

103

den Befehl d=x/y; wurde erst einmal der Quotient aus x und y berechnet und dann das Ergebnis der float-Variablen d zugewiesen, aber da beim puren Rechnen immer noch niemand anderes beteiligt war als zwei int-Variablen, wurde auch nur ganzzahlig gerechnet. Und bei der ganzzahligen Division von 5 durch 9 ergibt sich nun mal 0, denn alle Reste werden abgeschnitten. Somit konnte d keinen anderen Wert bekommen als eine nette dezimale 0. Solange also nur ganzzahlige Variablen an einer Rechnung beteiligt sind, wird auch nur ganzzahig gerechnet, unabh¨angig vom Typ der Ergebnisvariablen. Um die Rechnung genauer durchf¨ uhren zu k¨ onnen, muss auch schon in den eigentlichen Operationen, beim tats¨achlichen Rechnen eine float-Zahl beteiligt sein; eine reicht schon, dann wird alles auf float umgerechnet. Im folgenden Programm sehen Sie, wie man das machen kann. /* division.c -- dividieren ganzer zahlen */ #include main() { int x, y; float d; printf("Geben Sie bitte zwei ganze Zahlen ein:\n"); scanf("&d &d",&x, &y); d=(x*1.0)/y; printf("Das Ergebnis lautet: %f\n",d); } Indem Sie x mit der Kommazahl 1.0 multiplizieren, erf¨ ullen Sie die Bedingung, dass eine float-Zahl an der Rechnung beteiligt sein muss, und als Ergebnis werden Sie dann 0.555556 erhalten. Sie k¨ onnen aber auch, wenn Sie die Datentypen nicht durcheinander bringen wollen, zwei weitere float-Variablen a und b einf¨ uhren, die Eingaben f¨ ur x und y in a und b zwischenspeichern und dann mit diesen Variablen weiter rechnen. Nach der Eingabe Ihrer ganzzahligen Variablen m¨ ussten Sie also die Kommandos a=x; b=y vorsehen und dann d=a/b rechnen lassen - auch das funktioniert. Das alles ist nat¨ urlich nur dann n¨otig, wenn Sie als Eingangswerte ganze Zahlen haben: rechnen Sie von Anfang an mit float-Variablen, werden alle Verrenkungen unn¨ otitg, sofern Sie bei der Eingabe auf die richtigen Formate achten. /* divisionreell.c -- dividieren von kommazahlen */ #include main() { float x,y; float d; printf("Geben Sie bitte zwei reelle Zahlen ein:\n");

104

2 Strukturierte Programmierung mit C

scanf("&f &f",&x, &y); d=x/y; printf("Das Ergebnis lautet: %f\n",d); } Sollten Sie u uren, die Nachkomma¨brigens den umgekehrten Wunsch versp¨ stellen einer float-Zahl verschwinden zu lassen, dann ist nichts einfacher als das: weisen Sie den Inhalt der float-Variablen einer int-Variablen zu und die Sache hat sich. F¨ ur eine float-Variable z, die mit 1.234 belegt ist, und eine int-Variable x w¨ urde also die Wertzuweisung x=z dazu f¨ uhren, dass auf x der Wert 1 zu finden ist, da die Nachkommastellen von z abgeschnitten wurden. Noch eine kurze Warnung: vermeiden Sie Divisionen durch null. Die gehen nicht nur in der Mathematik schief, sondern auch beim Programmieren. Der Compiler ist nicht schlau genug, bei den Anweisungen int x,y,z=0; y=x/z; schon w¨ahrend der Syntaxpr¨ ufung zu bemerken, dass Sie hier Unsinn rechnen wollen - wie sollte er auch, die Befehle sind ja korrekt aufgeschrieben. Erst wenn das Programm l¨ auft, wird festgestellt, dass die anbefohlene Rechnung gar nicht durchf¨ uhrbar ist, und das Programm wird mit einer Fehlermeldung abbrechen. Aber noch eine andere Kleinigkeit sollte Ihnen an dieser Programmzeile auffallen: man kann eine Variable schon bei ihrer Deklaration mit Leben f¨ ullen, indem man ihr sofort einen Wert zuweist. So etwas wie int z=0 nennt man Initialisierung.

Grundrechenarten Die vier Grundrechenarten werden mit Hilfe der Operatoren +, -, * und / durchgef¨ uhrt. Die Division ganzer Zahlen erfolgt ganzzahlig, also unter Abschneiden der Nachkommastellen. Um mit Kommazahlen rechnen zu k¨ onnen, verwendet man den Datentyp float. 2.2.6 Datentypen Sie sehen: welchen Datentyp ich verwende, h¨angt stark vom Problem ab, das ich gerade angehe. Im Folgenden habe ich Ihnen einmal die Datentypen aufgelistet, mit denen man es u ¨blicherweise zu tun hat. Zuerst haben wir da den Datentyp int, eine Abk¨ urzung f¨ ur Integer, also f¨ ur ganze Zahlen. Sofern Sie Ihre ganzen Zahlen auf zwei Byte beschr¨ ankt sind, k¨ onnen Sie mit diesem Datentyp die ganzen Zahlen zwischen −32768 und 32767 darstellen, wie ich es Ihnen am Ende des f¨ unften Abschnittes im ersten Kapitel gezeigt habe. Sollten Sie u ugen, l¨ auft der Zahlenbe¨ber vier Byte verf¨ onnen u reich im Datentyp int von −231 bis 231 − 1. Sie k¨ ¨brigens leicht heraus finden, ob Ihre ganzen Zahlen bei 32767 enden: schreiben Sie ein C-Programm, das in eine int-Variable x den Wert 32767 einlieft und dann y = x+1 setzt, wobei nat¨ urlich auch y eine Integer-Variable sein muss. Falls die ganzen Zahlen noch weiter gehen, wird auf y der Wert 32768 stehen, und alles ist gut.

2.2 Erste C-Programme

105

Falls aber nicht, werden Sie feststellen, dass die Variable y mit dem Wert −32768 versehen ist. Warum? Verrate ich nicht, das sollen Sie sich in einer ¨ Ubungsaufgabe selbst u ¨berlegen. In jedem Fall kann man mit int-Variablen die besprochenen Grundrechenarten auf die besprochene Weise durchf¨ uhren, und noch eine mehr: bei der Division haben Sie gesehen, dass zwangsl¨ aufig die Nachkommastellen abgeschnitten wurden. Nun gibt es aber f¨ ur ganze Zahlen die Division mit Rest, zum Beispiel ist 5/3 = 1 Rest 2 und 11/4 = 2 Rest 3. Die Division selbst liefert immer das ganzzahlige Divisionsergebnis. Den Rest erh¨alt man dagegen mit der modulo-Operation %. Hat man also beispielsweise die int-Variablen x=11 und y=4, so ergibt x % y den Wert 3, weil der Rest bei der Division 11/4 eben genau 3 ist. Sollten Sie genau wissen, dass Ihre ganzen Zahlen nicht u aßig lang ¨berm¨ werden, so k¨onnen Sie durch den Einsatz des Datentyps short oder auch short int - das ist genau das Gleiche - ein wenig Speicherplatz sparen. Zahlen vom Typ short haben nur halb so viel Platz zur Verf¨ ugung wie Zahlen vom Typ int; bei meinen zweibytigen ganzen Zahlen heißt das also, dass eine Deklaration wie short x oder short int y zu ganzen Zahlen mit einer L¨ ange von einem Byte, also acht Bits, f¨ uhrt. Wie im ersten Kapitel besprochen, folgt daraus: der Bereich an ganzen Zahlen, den der Datentyp short abdeckt liegt zwischen −27 und 27 − 1, also zwischen −128 und 127 - kein großer Bereich, sondern eher ein kurzer, aber was soll man erwarten, wenn schon die Zahlen selbst so wenig Platz haben. Davon abgesehen, sind die m¨ oglichen Operationen die gleichen wie gerade beim Datentyp int. W¨ahrend short-Zahlen die verk¨ urzte Variante von int-Zahlen darstellen, haben Sie in den long-Zahlen das genaue Gegenteil vor sich: eine long-Zahl oder auch long int-Zahl kann im Arbeitsspeicher doppelt so viel Platz f¨ ur sich beanspruchen wir eine gew¨ ohnliche int-Zahl, in dem von mir angenommenen Fall der zweibytigen int-Zahlen also immerhin vier Byte. Da vier Byte bekanntlich 32 Bits entsprechen, k¨ onnen Sie im Datentyp long die ganzen ur Zahlen von −231 bis 231 − 1 darstellen, nicht mehr und nicht weniger. F¨ die u ugen, denn ¨blichen ganzzahligen Anwendungen sollte das eigentlich gen¨ immerhin sind das die Zahlen zwischen −2 147 483 648 und 2 147 483 647, das ist gar nicht so wenig. Eine mit long x oder auch long int x deklarierte Variable k¨onnen Sie also eine ganze Weile mit großen ganzen Zahlen plagen, ehe etwas Unangenehmes passiert. Erw¨ahnen sollte ich noch kurz die vorzeichenlosen ganzen Zahlen, die sich im Datentyp unsigned int versammeln. Sie sind dann zu empfehlen, wenn Sie garantieren k¨onnen, dass in Ihrem Programm keine negativen Zahlen auftreten werden und Sie gleichzeitig etwas Platz im Arbeitsspeicher einsparen wollen. Da eine Zahl vom Typ unsigned int genauso vorzeichenlos ist wie sie heißt, wird sie grunds¨atzlich als positive Zahl gerechnet, weshalb man f¨ ur sie kein Vorzeichenbit braucht. Das ist praktisch, denn bei einer Breite von zwei Byte, also 16 Bits, stehen in diesem Fall alle 16 Bits zur Speicherung der puren Zahl bereit, und das bedeutet, dass die gr¨oßte vorzeichenlose Zahl, die Sie mit zwei Byte darstellen k¨ onnen, der Bitfolge 11111111 11111111 entspricht, also

106

2 Strukturierte Programmierung mit C

der Zahl 216 − 1 = 65535. Der durch den Datentyp unsigned int abdeckbare Zahlenbereich liegt also zwischen 0 und 65535. Aber Vorsicht: auch hier gibt es die u assigen Bereich verlassen. Was ¨blichen Probleme, sobald Sie den zul¨ passiert mit den folgenden drei Programmzeilen? unsigned int x, y; x=65535; y=x+1; Eingepackt in ein korrektes Hauptprogramm, w¨ urde nach Ablauf dieses Programmst¨ ucks auf der Variablen y der Wert 0 stehen. Den Grund werden Sie ¨ sich im Rahmen einer Ubungsaufgabe selbst u ¨berlegen. Ich weiß: eine Auflistung und Analyse der verschiedenen Datentypen wirkt ¨ahnlich spannend wie die gesammelten Werke des Deutschen Wetterdienstes von 1978. Das t¨auscht aber. W¨ ahrend man mit den alten Wetterberichten nun wirklich nichts mehr anfangen kann, sind die Datentypen nun mal unverzichtbar, um verschiedene Arten von Daten verarbeiten zu k¨ onnen. Etwas Geduld noch, es dauert auch nicht mehr lange. Wie w¨ urden Sie beispielsweise Zeichen verarbeiten ohne einen passenden Datentyp? C stellt daf¨ ur den Datentyp char zur Verf¨ ugung, f¨ ur den man gerade mal ein ganzes Byte, also acht Bits braucht. Das w¨ urde schon die bin¨ are Darstellung von 256 Zeichen erlauben, aber um rechnerintern den Datentyp char von den ganzen Zahlen nicht allzu sehr unterscheiden zu m¨ ussen, hat man sich daf¨ ur entschieden, auch hier ein Vorzeichenbit zu verwenden. Als Zahlen betrachtet, haben die Zeichen daher einen Bereich von −128 bis 127, und nur die Werte von 0 bis 127 werden verwendet zur Darstellung von Zeichen. Damit auch alle Programmierer die gleichen Zeichen mit den gleichen bin¨aren Verschl¨ usselungen belegen, gibt es den so genannten ASCII-Code, eine Abk¨ urzung f¨ ur American Standard Code for Information Interchange, der die Nummern der einzelnen Zeichen festlegt. Die Ziffern 0 bis 9 haben, als Zeichen betrachtet, beispielsweise die Nummern 48 bis 57, die Großbuchstaben liegen zwischen 65 und 90, die Kleinbuchstaben zwischen 97 und 122. Der Witz ist aber der, dass Sie eine char-Variable jederzeit wie eine int-Variable behandeln k¨onnen und umgekehrt. Nehmen wir zum Beispiel das folgende Programm. /* zeichen.c -- Zeichen und Zahlen */ #include main() { char x,y; x=65; y=x+1; printf("\n Der Wert lautet %c %c",x,y); } Die Variablen x und y sind als char-Variablen deklariert, aber trotzdem wird x mit dem Wert 65 belegt. Damit erh¨ alt x das Zeichen mit der Nummer

2.2 Erste C-Programme

107

65, und das ist genau das große A. Wegen y=x+1 wird auf y das Zeichen mit der Nummer 66 stehen: das große B. genauso gut h¨ atte ich auch x=’A’; y=x+1; schreiben k¨onnen, das Wechselspiel zwischen int und char funktioniert in beiden Richtungen. Wenn man aber einer char-Variablen auch wirklich explizit ein Zeichen zuweisen will, dann muss es in Hochkommas eingeschlossen sein, also nicht nur x=A, sondern korrekt x=’A’. Das Formatelement f¨ ur Zeichen lautet u ¨brigens %c, Sie konnten es schon im Beispielprogramm sehen. Verl¨asst man den Bereich der ganzen Zahlen, so kommt man, wie Sie es schon bei der Arithmetik gesehen haben, schnell zu den Kommazahlen. Da aber erstens beim Programmieren kein Komma verwendet wird, sondern ein Punkt, und man zweitens noch ausdr¨ ucken will, dass dieser Dezimalpunkt mal hier, mal da stehen kann und seine Position nicht starr festgelegt ist, spricht man hier von Gleitpunktzahlen. Den zugeh¨origen Datentyp float haben Sie schon in vorherigen Beispielen bewundern k¨ onnen. Will man beispielsweise die Zahl 123, 452867 in einer float-Variablen abspeichern, so wird man die Programmzeilen float x; x=123.452867; verwenden oder das Ganze direkt in dem einen Befehl float x = 123.452867 erledigen, der die Deklaration und die erste Wertbelegung verbindet. Intern sieht die Zahl allerdings anders aus, da wird eine Exponentialschreibweise verwendet. Aus der Potenzrechnug wissen Sie, dass 123, 452867 = 1, 23452867·102 gilt, und dem entspricht auch die Speicherungsmethode f¨ ur float-Zahlen. Sie werden aufgeteilt in eine Mantisse, das ist in meinem Beispiel die Zahl 1.23452867, einen Exponenten, das ist bei uns der Wert 2, und nat¨ urlich wie u blich ein Vorzeichenbit. Dass man auf diese Weise ausgesprochen große Zah¨ len darstellen kann, d¨ urfte klar sein: Sie m¨ ussen ja nur einen etwas gr¨ oßeren Exponenten verwenden wie zum Beispiel 17, und schon haben Sie eine Zahl in der Gr¨oßenordnung von 1017 dargestellt. Standardm¨ aßig haben Sie in der Mantisse eine Genauigkeit von sechs Stellen, w¨ahrend der Zehnerexponent in aller Regel zweistellig bleiben muss. Diese Exponentialdarstellung kann man auch direkt zur Angabe einer Zahl verwenden, beispielsweise durch float x=3.553421e12. Damit wird die Zahl 3, 553421 · 1012 in der float-Variablen x gespeichert. In welcher Weise Sie die Zahlen dann ausgeben, h¨ angt von Ihrem Formatelement ab. Die Anweisung printf("Die Zahl lautet %f",x); wird nat¨ urlich die float-Zahl x in der u ¨blichen Form mit Vor- und Nachkommastellen ausgeben. Anders sieht es aus bei dem Ausgabekommando printf("Die Zahl lautet %e",x); denn das Formatelement %e sorgt f¨ ur die Ausgabe in der Exponentialschreibweise. Auch Gleitpunktzahlen kann man etwas genauer machen. Wollen Sie von sechs auf zw¨olf Stellen gehen, so m¨ ussen sie Variablen vom Typ double einsetzen, sind sogar 24 Stellen erw¨ unscht, ist der Datentyp long double zu

108

2 Strukturierte Programmierung mit C

empfehlen. Ansonsten verhalten sich die Zahlen dieser Typen genauso wie float-Zahlen, weshalb ich mich nicht weiter dar¨ uber auslasse. Noch ein paar Worte zu den Formatelementen f¨ ur die verschiedenen Datentypen, dann gehen wir sofort zu den Kontrollstrukturen u ¨ber. Sie hatten bereits gesehen, dass ganze Zahlen schlicht die Formatangabe %d zu sch¨ atzen wissen, Gleitpunktzahlen je nach Darstellungsart auf %f oder %e warten und Zeichen sich mit %c zufrieden geben. Man kann das aber noch etwas genauer steuern. Wollen Sie beispielsweise sicher sein, dass Ihre ganze Zahl mit mindestens sechs Stellen ausgegeben wird, dann verwenden Sie einfach das Formatelement %6d; das kann hilfreich sein bei der Erstellung von Tabellen mit Hilfe eines C-Programms. Entsprechendes gilt f¨ ur Variablen vom Typ long, die sehnlichst auf das Formatelement %ld warten. Interessant ist noch, wie Sie das Formatelement %f exakter kontrollieren k¨ onnen. Sie k¨ onnen n¨ amlich einerseits festlegen, wie viele Zeichen minedstens insgesamt f¨ ur die Zahl ausgegeben werden sollen: mit %6f w¨ urde eine float-Zahl beispielsweise eine Ausgabebreite von garantiert sechs Stellen aufweisen. Und andererseits k¨ onnen Sie auch die Stellen nach dem Dezimalpunkt angeben, indem Sie zum Beispiel %.2f schreiben und damit klar machen, das zwei Zeichen hinter dem Dezimalpunkt erw¨ unscht sind. Es w¨are bedauerlich, beide M¨ oglichkeiten nicht kombinieren zu k¨onnen, und nat¨ urlich ist auch das m¨ oglich. Mit einer Angabe wie %6.2f weisen Sie an, dass die Ausgabe Ihrer float-Variablen mindestens sechs Stellen breit sein soll, davon zwei Stellen nach dem Dezimalpunkt.

Datentypen Zur Verarbeitung unterschiedlicher Zahlenarten stellt C verschiedene Datentypen bereit. Man unterscheider dabei zwischen Typen zur Verarbeitung von ganzen Zahlen, von Zeichen und von Gleitpunktzahlen, wobei sich verschiedene Datentypen zu den ganzen Zahlen bzw. zu den Gleitpunktzahlen vor allem in der Gr¨ oße des darstellbaren Zahlenbereichs unterscheiden. Wichtige Datentypen sind: int, long, short, unsigned int, char, float, double und long double. Zur Ausgabe von Variablen werden verschiedene Formatelemente eingesetzt. Sie beginnen immer mit dem %-Zeichen und steuern die Ausgabe der Variablen auf dem Bildschirm. ¨ Ubungen 2.4. Schreiben Sie ein C-Programm, das den Benutzer dazu auffordert, drei ganze Zahlen einzugeben, diese drei Zahlen einliest und dann das Volumen des Quaders ausrechnet, dessen Seitenl¨angen diese Zahlen darstellen. Nat¨ urlich soll am Ende ein Antwortsatz ausgegeben werden. ¨ 2.5. Andern Sie das Programm aus Aufgabe 2.4 so um, dass nicht mehr ganzzahlige Eingaben erwartet werden, sondern Gleitpunktzahlen. Testen Sie bei

2.3 Kontrollstrukturen

109

der Ausgabe der Ergebnisse verschiedene M¨ oglichkeiten zur Angabe von Formatelementen wie z.B. %f, %8.3f oder %e. 2.6. Schreiben Sie ein C-Programm, das folgendes leistet. Der Benutzer wird aufgefordert, den Radius eines Kreises einzugeben, nat¨ urlich als Gleitpunktzahl. Daraufhin berechnet das Programm den Umfang und die Fl¨ ache des Kreises und gibt sie aus. Anschließend wird der Benutzer aufgefordert, den Umfang eines weiteren Kreises einzugeben, und das Programm gibt den Radius und die Fl¨ache des neuen Kreises aus. 2.7. Schreiben Sie ein C-Programm, mit dem man das Zusammenspiel der beiden Datentypen int und char testen kann. Der Benutzer soll eine ganze Zahl eingeben k¨onnen und das Programm das Zeichen mit der entsprechenden Nummer ausgeben. 2.8. Gegeben sei ein Rechner, auf dem int-Vaiablen zwei Byte im Arbeitsspeicher belegen. In eine int-Variable x wird der Wert 32767 eingelesen und dann die Anweisung y = x+1 f¨ ur eine weitere int-Variable y durchgef¨ uhrt. Warum hat dann y den Wert −32768? 2.9. Gegeben sei ein Rechner, auf dem int-Vaiablen zwei Byte im Arbeitsspeicher belegen. In eine unsigned int-Variable x wird der Wert 65535 eingelesen und dann die Anweisung y = x+1 f¨ ur eine weitere unsigned int-Variable y durchgef¨ uhrt. Warum hat dann y den Wert 0?

2.3 Kontrollstrukturen Im letzten Abschnitt habe ich Sie sehr intensiv mit Details plagen m¨ ussen. Leider war daa unvermeidbar, denn bevor man an die richtige und ernsthafte Programmierung gehen kann, muss man wissen, mit welchen grunds¨ atzlichen Strukturen man es in der jeweiligen Programmiersprache zu tun hat. Das ist jetzt aber erst mal erledigt; Sie wissen Bescheid u ¨ber Variablen und Arithmetik, u onnen wir loslegen ¨ber Datentypen und Formatelemente, und jetzt k¨ mit den zentralen Grundbausteinen der strukturierten Programmierung: den Kontrollstrukturen. Im Prinzip sind sie immer gleich, ob Sie nun C einsetzen oder Pascal, Java oder FORTRAN, Sie werden an den drei Kontrollstrukturen Sequenz, Auswahl und Wiederholung nie vorbei kommen. Das ist aber auch gut so, denn sie sind sehr m¨ achtige Hilfsmittel der Programmierung, und davon abgesehen sind sie gar nicht schwer zu verstehen. In diesem Abschnitt werden ich Ihnen die drei Kontrollstrukturen vorstellen und Ihnen zeigen, wie man sie einerseits in C umsetzt und andererseits mit den graphischen Beschreibungsmethoden der Struktogramme und der Programmablaufpl¨ ane unabh¨ angig von einer Programmiersprache darstellen kann.

110

2 Strukturierte Programmierung mit C

2.3.1 Sequenz Am einfachsten ist die schlichte Sequenz zu verstehen. Dass jedes Programm aus verschiedenen Anweisungen besteht, haben Sie nat¨ urlich schon l¨ angst mitbekommen, und eine Abfolge aus einzelnen Schritten, aus einzelnen Anweisungen, die sch¨on brav nacheinander auszuf¨ uhren sind, nennt man eine Sequenz. Zu jedem Zeitpunkt der Verarbeitung wird genau ein Schritt ausgef¨ uhrt, wobei jeder Schritt auch genau einmal ausgef¨ uhrt wird, nichts wird ausgelassen, nichts wiederholt. Sie k¨ onnen es sich vorstellen wie das vollst¨ andige Ausl¨ offeln einer Suppe: zu jedem Zeitpunkt wird genau ein L¨ offel gegessen, keiner wird ausgelassen, denn Sie essen Ihre Suppe brav auf, und es wird auch keiner doppelt gegessen, zumindest hoffe ich das. Was die Programmsequenz von der Suppe unterscheidet, ist der Umstand, dass es im Programm eine klar definierte Reihenfolge der Abarbeitung gibt, die schon beim Aufschreiben einer Sequenz deutlich wird; bei der Suppe ist das offenbar anders. Ein kleines Beispiel einer Sequenz gibt das folgende Programm, das eine W¨ahrungsumrechnung vornimmt. Nach dem Kurs des heutigen Tages entspricht ein Euro genau einem US-Dollar und 31 US-Cent. Das Umrechnungsprogramm lautet dann folgendermaen. /* geld.c -- waehrungen umrechnen */ #include main() { float enachd=1.31; float euro, dollar; printf("Geben Sie einen Euro-Betrag ein:\n"); scanf("%f",&euro); dollar = euro * enachd; printf("%6.2f Euro sind %6.2f Dollar\n",euro,dollar); } Das Programm ist wohl ziemlich selbsterkl¨ arend. Der eingegebene EuroBetrag wird mit dem Umrechnungsfaktor multipliziert, und anschließend werden der Euro-Betrag und der Dollar-Betrag mit zwei Stellen nach dem Dezimalpunkt ausgegeben. Eine klassische Sequenz, denn eines wird nach dem anderen gemacht, alles passiert genau einmal, nichts wird ausgelassen, nichts wiederholt. Programmiertechnisch ist das kaum ein weiteres Wort wert. Da aber nicht alle Programmierer die gleiche Programmiersprache verwenden, kann es nicht schaden, so etwas auch anders darstellen zu k¨ onnen, unabh¨ angig von einer konkreten Programmiersprache, und die beiden beliebtesten Hilfsmittel zu diesem Zweck sind Struktogramme und Programmablaufpl¨ ane. Struktogramme wurden 1973 von Nassi und Shneiderman als Methode zur Strukturierung vorgeschlagen, weshalb man sie auch ab und zu als Nassi-

2.3 Kontrollstrukturen

111

Shneiderman-Diagramme bezeichnet, und erfreuen sich mehr als 30 Jahre nach ihrer Geburt heute noch großer Beliebtheit. Jeder Verarbeitungsschritt wird in einem schlichten rechteckigen Kasten dargestellt, mehrere Verarbeitungsschritte zusammen ergeben einen Block. Den grunds¨ atzlichen Aufbau sehen Sie in Abbildung 2.1.

Abb. 2.1. Struktogramm

Hier werden drei Anweisungen in einem Block zusammengefasst, mehr gibt ein so elementares Struktogramm nicht her. Auch mein kleines Programm geld.c sieht als Struktogramm nicht aufregender aus als vorher; in Abbildung 2.2 k¨onnen sie es bewundern

Abb. 2.2. Struktogramm zur W¨ ahrungsumrechnung

Wie Sie sehen, bildet das Struktogramm genau die einzelnen Schritte meines Programms ab, aber man kann es auch umgekehrt sehen: das Struk-

112

2 Strukturierte Programmierung mit C

togramm liefert den Algorithmus f¨ ur mein Programm, sozusagen den Programmentwurf, und als Programmierer habe ich nichts anderes mehr zu tun als diesen Algorithmus in ein korrektes C-Programm zu u ¨bersetzen. Ob man dabei im Struktogramm schon dem Programmierer die Datentypen f¨ ur die einzelnen Variablen vorgibt, ist ein wenig Geschmackssache; ich habe es hier unterlassen. Vergessen Sie nicht: das Struktogramm ist unabh¨ angig von der Programmiersprache oder sollte es wenigstens sein, Datentypen aber k¨ onnen sehr abh¨angig sein von der verwendeten Programmiersprache, also ist es sicher nicht sinnlos, in einem Struktogramm die Datentypen nicht zu konkret werden zu lassen. Mehr Struktogrammtechnik gibt eine schlichte Sequenz nicht her, also k¨ onnen wir uns gleich ansehen, wie das Ganze in Form eines Programmablaufplanes, abgek¨ urzt PAP, aussieht. Im Falle einer Sequenz besteht ein PAP meistens nur aus zwei Arten von Elementen: aus Parallelogrammen, die eine Ein- oder Ausgabe symbolisieren, und aus Rechtecken, mit denen Verarbeitungsschritte gekennzeichnet werden. Na gut, ein Start- und ein Endesymbol sind auch noch oft dabei, aber viele Leute lassen das auch einfach weg. Wie man diese Symbole kombiniert, sehen Sie in Abbildung 2.3

Start geld setze enachd=1.31 Ausgabe: Eingabeaufforderung für euro einlesen euro

dollar = euro * enachd

Ausgabe euro und dollar Ende geld

Abb. 2.3. Programmablaufplan zur W¨ ahrungsumrechnung

2.3 Kontrollstrukturen

113

Es passiert genau das Gleiche wie schon im Struktogramm, nur eben anders dargestellt. Die einzelnen Schritte, sei es nun eine Ein-/Ausgabe oder eine Verarbeitung, werden durch Pfeile miteinander verbunden, damit kein Zweifel u ¨ber die Reihenfolge entstehen kann, und in die Elemente werden die jeweils n¨otigen Anweisungen geschrieben. Mehr steckt nicht dahinter, und viel schwieriger wird es nicht. Bevor ich mit der Auswahl zur n¨ achsten Kontrollstruktur u ¨bergehe, sollte ich noch eines kurz erw¨ ahnen. Den Umrechnungsfaktor enachd hatte ich ganz schlicht als Variable definiert und gleich bei der Deklaration mit einem Wert versehen. Nun sollte aber eigentlich so ein Umrechnungsfaktor, wenn er einmal festgelegt ist, nicht andauernd wieder ge¨ andert werden; auf irgend etwas muss sich der Mensch ja verlassen k¨ onnen, auch wenn der europ¨ aische Stabilit¨atspakt nicht das Papier wert ist, auf dem man ihn gedruckt hat. Um eine nicht mehr allzu variable Variable auch in C darstellen zu k¨ onnen, gibt es das Konzept der Konstanten. Anstatt innerhalb der main()-Funktion eine Variable enachd zu definieren, h¨ atten Sie auch durch eine Pr¨ aprozessoranweisung dem Compiler mitteilen k¨onnen, dass er jetzt unf f¨ ur alle Zeiten unter dem Namen ENACHD die Zahl 1.31 verstehen soll. Das funktioniert ¨ ahnlich wie schon bei der #include-Anweisung mit dem Zeichen #, und das Programm lautet dann wie folgt: /* geld.c -- waehrungen umrechnen */ #include #define ENACHD = 1.31 main() { float euro, dollar; printf("Geben Sie einen Euro-Betrag ein:\n"); scanf("%f",&euro); dollar = euro * ENACHD; printf("%6.2f Euro sind %6.2f Dollar\n",euro,dollar); } Es hat sich eingeb¨ urgert, Konstanten mit Großbuchstaben zu schreiben, das ist aber reine Konvention und vom Compiler nicht vorgeschrieben. Wenn Sie sich aber daf¨ ur entscheiden, eine Konstante mit Großbuchstaben zu bezeichnen, dann m¨ ussen Sie das auch durchhalten und d¨ urfen unterwegs beispielsweise nicht mehr enachd anstatt ENACHD schreiben, denn das w¨ urde Ihnen der Compiler doch sehr u ¨bel nehmen.

Die Sequenz Unter einer Sequenz versteht man eine Abfolge aus einzelnen Schritten, die nacheinander auszuf¨ uhren sind. Zu jedem Zeitpunkt der Verarbeitung wird genau ein Schritt ausgef¨ uhrt, wobei jeder Schritt genau einmal ausgef¨ uhrt wird.

114

2 Strukturierte Programmierung mit C

Eine Sequenz l¨asst sich sowohl mit Hilfe eines Struktogramms als auch eines Programmablaufplanes darstellen; im Struktogramm kann man die Anweisungen einer Sequenz zu einem Block zusammenfassen, im Programmablaufplan wird die Verarbeitungsreihenfolge durch Pfeile zwischen den einzelnen Elementen symbolisiert.

2.3.2 Auswahl Nichts gegen Sequenzen, aber das Leben hat nun mal nicht immer so einen ordentlichen Ablauf, wie das eine schlichte Sequenz gerne h¨ atte. Oft genug muss man Entscheidungen treffen, die dann den gesamten weiteren Lebensweg beeinflussen, und so etwas passiert nat¨ urlich nicht nur bei Captain Picard auf der Enterprise, sondern auch bei Programmen. Was ich Ihnen bisher gezeigt habe, war zu unflexibel, zu starr, da der Ablauf ohne jede Variationsm¨ oglichkeit vorgegeben war. Sie brauchen sich nur einmal mein altes Beispiel der Gehaltsabrechnung und Lohnbuchhaltung ins Ged¨ achtnis zu rufen: je nach Steuerklasse wird die Berechnung des Nettogehalts verschieden ablaufen, also kann man sie nicht mit Hilfe einer starren Sequenz in ein Programm fassen. Dieses Problem l¨ost das Prinzip der Auswahl, die die Ausf¨ uhrung eines Schrittes von einer bestimmten Bedingung abh¨ angig macht. Das kennen Sie aus dem richtigen Leben - wenn Sie beispielsweise f¨ ur die n¨ achste Klausur lernen, dann haben Sie gute Chancen, sie zu bestehen, wenn nicht, dann eben nicht. Es ist Ihre Entscheidung, und a¨hnliche Entscheidungen beeinflussen auch den Ablauf eines Programms. Schon das schlichte Umrechnungsprogramm, das wir vorhin entwickelt haben, liefert hier ein Beispiel. Vielleicht will der Benutzer ja gar nicht mehr umrechnen, weil ihm gerade eingefallen ist, dass er dringen den Rasen m¨ahen muss. F¨ ur solche F¨ alle sollte man ihm eine gewissen Entscheidungsfreiheit lassen, die das folgende Programm garantiert. /* geld.c -- waehrungen umrechnen */ #include #define ENACHD 1.31 main() { float euro, dollar; char antwort; printf("Wollen Sie Euro in Dollar umrechnen?\n"); scanf("%c",&antwort); if (antwort == ’j’) { printf("Geben Sie einen Euro-Betrag ein:\n"); scanf("%f",&euro);

2.3 Kontrollstrukturen

115

dollar = euro * ENACHD; printf("%6.2f Euro sind %6.2f Dollar\n",euro,dollar); } } Der Text erkl¨art sich fast von selbst. Das Programm fragt den Anwender, ob er tats¨achlich Euro in Dollar umrechnen m¨ ochte, und erwartet dann die Eingabe seiner Antwort von der Tastatur. Zu diesem Zweck habe ich eine char-Variable antwort definiert, die mit einem scanf()-Komamndo gef¨ ullt wird; achten Sie dabei vor allem auf das ben¨ otigte Formatelement und die Angabe des &-Zeichens vor dem Variablennamen. Danach beginnt die eigentliche Auswahl. Mit dem Ausdruck if (antwort == ’j’) wird abgefragt, ob der Benutzer das Zeichen j oder irgendetwas anderes eingegeben hat. Nur wenn er sich wirklich mit dem Buchstaben j ge¨ außert hat, wird der nach der Abfrage stehende Block ausgef¨ uhrt. Und so geht das immer. Es wird eine Bedingung abgefragt, und falls diese Bedingung erf¨ ullt ist, werden bestimmte Aktionen durchgef¨ uhrt, falls nicht, dann eben nicht. Die Abfrage der Bedingung erfolgt stets nach dem Schema if (Bedingung). Sie beginnt also immer mit dem Schl¨ usselwort if, an das sich dann, in Klammern gesetzt, die Bedingung anschließt, die Sie u ufen wollen. Vielleicht ist Ihnen aufgefallen, dass ¨berpr¨ bei der Abfrage aus meinem Beispielprogramm eine Seltsamkeit auftaucht: offenbar wird hier gefragt, ob die Variable antwort mit dem Zeichen ’j’ belegt ist, aber die Bedingung lautet eben nicht ganz einfach antwort = ’j’, sondern antwort ==’j’, mit zwei Gleichheitszeichen anstatt einem. Das hat einen einfachen Grund. In C ist das gew¨ ohnliche Gleichheitszeichen mit der Wertzuweisung verbunden, weshalb so etwas wie antwort = ’j’ der Variablen antwort das Zeichen ’j’ zuweisen w¨ urde anstatt danach zu fragen, ob es schon da ist. Da Wertzuweisungen nun einmal etwas v¨ ollig anderes sind als Vergleiche und Abfragen, hat man f¨ ur die Abfrage, ob ein Teil gleich einem anderen ist, das doppelte Gleichheitszeichen erfunden. Und was sollen die Klammern nach der Abfrage? Nichts einfacher als das. Die komplette Auswahl hat in dieser Form den Aufbau if (Bedingung) Aktion; uhrt Falls also die Bedingung erf¨ ullt ist, soll die n¨achstfolgende Aktion ausgef¨ werden, und mehr nicht. Nun will ich aber gleich ein paar Dinge erledigt wissen, falls der Benutzer wirklich umrechnen will, und nicht nur die eine Anweisung, die direkt auf die Abfrage folgt. Damit der Compiler das erkennt, fasse ich die Anweisungen, die ich als Einheit betrachten will, durch den Einsatz der Mengenklammern { und } zu einem Block zusammen, den dann der Compiler tats¨achlich als Einheit erkennt und akzeptiert. Sobald sie also mehrere Anweisungen durch die Mengenklammern als einen Block deklarieren, wird auch der gesamte Block ausgef¨ uhrt, falls die Bedingung erf¨ ullt ist. Ist das erledigt, geht das Programm einfach zur n¨ achsten Anweisung u ¨ber. Ist die Bedingung dagegen nicht erf¨ ullt, so wird das Programm bei der ersten Anweisung nach dem bewussten Block weitergef¨ uhrt. Ein kleines Beispiel soll das verdeutlichen.

116

2 Strukturierte Programmierung mit C

/* beispiel.c

*/

#include main() { int x,y=-1; printf("Bitte eine ganze Zahl x eingeben:\n"); scanf("%d",&x); if (x > 0) y=1; printf("Der Wert fuer y lautet %d",y); } } Die eingelesene int-Variable x wird gefragt, ob sie gr¨ oßer als 0 ist; in diesem Fall soll y auf 1 gesetzt werden. Die anschließende Ausgabe von y findet in jedem Fall statt, egal ob x gr¨ oßer oder kleiner als 0 ist, denn ich habe nach der if-Abfrage nichts eingeklammert, sodass die Ausgabeanweisung nicht mehr zum if-Block geh¨ ort. Nur ihr Ergebnis h¨ angt nat¨ urlich vom x-Wert ab: war x positiv, so wird f¨ ur y der Wert 1 ausgegeben, war x negativ oder 0, so gibt das Programm f¨ ur y den vordefinierten Wert −1 aus. Sie sehen an diesem Beispiel u brigens, dass es nicht nur Abfragen auf Gleichheit gibt, man kann auch ¨ anderes testen. Hier habe ich den >-Vergleich angewendet, dazu stehen auch noch die Vergleichsm¨ oglichkeiten < und != zur Verf¨ ugung, wobei der letzte Vergleich als ungleich“ zu verstehen ist, sowie die Mischformen =. ” Der gesante if-Block l¨ asst sich sehr angenehm durch ein graphisches Element in einem Struktogramm darstellen; Sie sehen seinen Aufbau in Abbildung 2.4

Abb. 2.4. Struktogramm zur Auswahl

Es wird eine Bedingung gestellt und u uft, ob sie g¨ ultig ist. Falls sie ¨berpr¨ erf¨ ullt ist, geht das Struktogramm in den Ja“-Zweig und f¨ uhrt die Anwei”

2.3 Kontrollstrukturen

117

sungen aus, die sich dort versammeln, falls nicht, wird am Ende des AuswahlBlocks weiter gemacht, wobei das seltsame Zeichen ∅ symbolisiert, dass nichts geschehen soll. Mein kleines Beispielprogramm hat dann das Struktogramm aus Abbildung 2.5.

Abb. 2.5. Auswahlblock in einem Struktogramm

Sie k¨onnen mit bloßem Auge sehen, was los ist: nach dem Einlesen von x wird mit der Abfrage nach dem Vorzeichen von x der if-Block angegangen. Sobald er auf die eine oder andere Weise abgearbeitet ist, kann die Verarbeitung einfach bei der n¨ achsten Anweisung nach dem if-Block fortgef¨ uhrt werden. Auch in einem Programmablaufplan kann man eine Auswahl darstellen, das ist nicht schwerer als mit einem Struktogramm. Ich zeige es Ihnen gleich an der Umsetzung meines kleinen Beispielprogramms in Abbildung 2.6. Es ist nat¨ urlich Geschmackssache, aber ich pers¨ onlich finde das Struktogramm sauberer und u ¨bersichtlicher. In einem PAP wird die Abfrage durch eine Raute symbolisiert, und von der Raute aus zweigen die M¨ oglichkeiten ab, die sich am Ende in dem kleinen Kreis wieder treffen: ist die Bedingung erf¨ ullt, geht man in den ja“-Zweig, falls nicht, geht man in den “nein“-Zweig, und am ” Ende wird alles in dem durch einen Kreis dargestellten Sammelpunkt zusammengef¨ uhrt. Durch dieses Hin- und Herzweigen geht ein wenig an Struktur verloren, das strenge Denken in einer ordentlichen Abfolge, auf das man in der strukturierten Programmierung so großen Wert legt, verliert sich ein wenig in den Verzweigungen. Das kann in einem Struktogramm nicht passieren, weil Sie dort stur Block f¨ ur Block vorgehen und Verzweigungen klar einem bestimmten Block zugeordnet sind. Wie dem auch sei, bisher habe ich Ihnen nur die einfachste Form der Auswahl vorgestellt. Im Allgemeinen wird man nicht nur eine Folge von Anweisungen haben, die bei erf¨ ullter Bedingung zu erledigen sind, sondern noch

118

2 Strukturierte Programmierung mit C

Start beispiel

setze y= -1

einlesen x

x>0 ?

nein

ja

y= -1

Ausgabe y Ende beispiel

Abb. 2.6. Auswahl in einem Programmablaufplan

eine andere, die dann zum Einsatz kommt, wenn die Bedingung nicht erf¨ ullt ist. Es k¨onnte ja beispielsweise sein, dass mein Umrechenprogramm entweder von Euro in Dollar oder aber, je nach Benutzerwunsch, von Dollar in Euro umrechnet. Das realisiert das n¨ achste Programm. /* geld.c -- waehrungen umrechnen */ #include #define ENACHD 1.31 main() { float euro, dollar; int antwort; printf("Geben Sie eine 1 ein, wenn Sie Euro in Dollar umrechnen wollen\n"); printf("Geben Sie eine 2 ein, wenn Sie Dollar in Euro umrechnen wollen\n"); scanf("%d",&antwort);

2.3 Kontrollstrukturen

119

if (antwort == 1) { printf("Geben Sie einen Euro-Betrag ein:\n"); scanf("%f",&euro); dollar = euro * ENACHD; printf("%6.2f Euro sind %6.2f Dollar\n",euro,dollar); } else { printf("Geben Sie einen Dollar-Betrag ein:\n"); scanf("%f",&dollar); euro = dollar / ENACHD; printf("%6.2f Dollar sind %6.2f Euro\n",dollar,euro); } printf("Ende der Umrechnung\n"); } Der Benutzer wird gefragt, was er m¨ ochte: Euro in Dollar umrechnen oder Dollar in Euro. Je nach Eingabe wird dann die eine oder die andere Verarbeitung angestoßen, wobei ich mir die Einf¨ uhrung eines weiteren Umrechnungsfaktors erspart habe und beim Umrechnen von Dollar nach Euro einfach durch den bekannten Faktor ENACHD teile. Im Deutschen w¨ urde man das Ganze so formulieren: Falls die Bedingung erf¨ ullt ist, mache dies, ansonsten ” mache das“. Und dieses ansonsten“ heißt sowohl im Englischen als auch in ” der Sprache C else“ - das ber¨ uhmte else, mit dem der Chefprogrammierer ” seine Probleme hatte. Dass man auch diese etwas bessere Auswahl mit einem Struktogramm darstellen kann, wird keinen u ¨berraschen, und dass sie wie in Abbildung 2.7 aussieht, wahrscheinlich auch nicht.

Abb. 2.7. Auswahlblock in einem Struktogramm

Der bisher leere nein“-Zweig wird jetzt mit Anweisungen gef¨ ullt, mehr ” steckt nicht dahinter. Nicht anders sieht es aus bei der Darstellung im Programmablaufplan: bisher lief der nein“-Zweig einfach nur auf den Sammel” punkt zu, jetzt tauchen dort noch Anweisungen auf.

120

2 Strukturierte Programmierung mit C

nein

Anweisungen

Bedingung

ja

Anweisungen

Abb. 2.8. Auswahl in einem Programmablaufplan

Ge¨andert hat sich nicht viel. Wie zuvor wird eine Bedingung abgefragt, und je nach Antwort geht man in den einen oder in den anderen Zweig. Sobald die jeweiligen Anweisungen ausgef¨ uhrt sind, geht es am Sammelpunkt weiter, egal ob die Bedingung erf¨ ullt war oder nicht. Um es noch einmal zu sagen: die Anweisungen m¨ ussen nicht nur in Form eines einzelnen Befehls vorliegen, sondern k¨ onnen nat¨ urlich auch aus einer ganzen Sequenz von Befehlen bestehen. Die Darstellung meines kleinen Euro-Dollar/Dollar-Euro-Programms als ¨ Struktogramm und als PAP werde ich Ihnen als Ubungsaufgabe u ¨berlassen und mich hier lieber noch der einen oder anderen Verbesserung unserer Auswahl-Struktur widmen. Zun¨ achst m¨ ussen wir uns mit einer kleinen Unklarheit befassen, die auf der M¨ oglichkeit der Schachtelung von if-Abfragen beruht. Sie k¨onnen innerhalb eines if-Blocks und nat¨ urlich auch im elseBlock noch weitere if-Abfragen starten, was dann unter Umst¨ anden zu einer Situation wie der folgenden f¨ uhrt. if (x > 0) if (y > 0) z = y; else z = x; Es ist hier klar, dass z=y gesetzt werden soll, sofern y>0 gilt. Aber worauf bezieht sich der else-Zweig: auf das erste if oder auf das zweite? Das macht einen gewaltigen Unterschied, denn im ersten Fall w¨ urde man z=x setzen, wenn nicht x>0 gilt, und im zweiten w¨ are z=x zu setzen, falls erstens x>0 gilt und dar¨ uber hinaus noch die Bedingung y>0 nicht gilt. Die L¨ osung des Problems ist ganz einfach. Der else-Zweig wird, sofern Sie keine Klammern setzen, immer dem letzten if zugeordnet, f¨ ur das noch kein else existiert. In meinem kleinen Beispiel stellt das else also die Alternative zu der Bedingung y > 0 dar und nicht zu x > 0. In einem Struktogramm k¨ onnte dieses Inter-

2.3 Kontrollstrukturen

121

pretationsproblem erst gar nicht auftreten, da die strenge Blockstruktur von vornherein alles klar werden l¨ asst, wie Sie in Abbildung 2.9 sehen.

Abb. 2.9. Geschachtelte Auswahl

Sollten Sie aber das Bed¨ urfnis haben, von dieser festen Regel abzuweichen, dann kann Ihnen leicht geholfen werden: Sie m¨ ussen nur Klammern an die richtige Stelle setzen. Was passiert zum Beispiel bei dem folgenden Programmst¨ uck? if (x > 0) { if (y > 0) z = y; } else z = x; Hier ist die Lage wieder ganz anders. Falls n¨amlich x>0 gilt, wird der eingeklammerte Block ausgef¨ uhrt; der else-Zweig befindet sich außerhalb dieses Blocks und kann daher auf keinen Fall zu dem inneren if geh¨ oren, das in den Block eingeschlossen ist. Daher geh¨ ort dieses else zum ¨ außeren if, und wir haben die Situation aus Abbildung 2.10. Sobald also in einem if-Zweig mehrere Anweisungen vorkommen, sollte man sie unbedingt in Mengenklammern setzen, selbst wenn es nach der oben angesprochenen Regel vielleicht gar nicht n¨ otig w¨ are: es steigert in jedem Fall die Verst¨andlichkeit des Programms. Dass Sie zus¨ atzlich auch noch die Anweisungen, die inhaltlich zusammen geh¨ oren und deshalb innerhalb eines Blocks stehen, auch noch einr¨ ucken sollten, ist aus den bisher behandelten Beispielprogrammen hoffentlich ebenfalls deutlich geworden; dem Compiler ist die Einr¨ uckung zwar v¨ ollig egal, aber auch sie l¨ asst Ihre Programme f¨ ur den menschlichen Leser wesentlich deutlicher werden. Eine Schachtelung von if-Abfragen hat u ¨brigens auch mein Umrechnungsprogramm dringend n¨ otig. Werfen Sie noch einmal einen Blick darauf: wenn

122

2 Strukturierte Programmierung mit C

Abb. 2.10. Geschachtelte Auswahl

der Benutzer eine 1 eingibt, wird von Euro nach Dollar umgerechnet, und wenn er eine 2 eingibt von Dollar nach Euro, so viel ist wahr. Kommt er allerdings auf die Idee, eine 3, 17 oder sonst irgendeinen Bl¨ odsinn in die Tastatur zu hauen, dann wird das Programm ebenfalls von Dollar nach Euro umrechnen, da es die zweite Eingabem¨ oglichkeit u ¨berhaupt nicht abfragt. Wann immer Sie etwas von 1 Verschiedenes eingeben, wird die Umrechnung von Dollar nach Euro vorgenommen, und der Benutzer fragt sich, was er falsch gemacht hat. In diesem Fall war aber nicht der Benutzer schuld - obwohl er das nach Meinung der Programmierer fast immer ist -, sondern eindeutig der Programmierer, der besser das folgende Programmst¨ uck f¨ ur seine Abfragen verwenden sollte. if (antwort == 1) { printf("Geben Sie einen Euro-Betrag ein:\n"); scanf("%f",&euro); dollar = euro * ENACHD; printf("%6.2f Euro sind %6.2f Dollar\n",euro,dollar); } else { if (antwort == 2) { printf("Geben Sie einen Dollar-Betrag ein:\n"); scanf("%f",&dollar); euro = dollar / ENACHD; printf("%6.2f Dollar sind %6.2f Euro\n",dollar,euro); } else printf("Fehlerhafte Eingabe\n"); } Erst jetzt k¨onnen Sie garantieren, dass die Umrechnung genau bei der Ein-

2.3 Kontrollstrukturen

123

gabe der Zahlen 1 oder 2 stattfindet, bei allen anderen Eingaben gibt es eine Fehlermeldung. Bleiben wir noch einen Moment bei der Schachtelung von if-Abfragen. Damit die Sache nicht zu kompliziert wird, hat man sich f¨ ur u ¨bersichtliche F¨alle eine recht praktische Konstruktion ausgedacht, die auf der oben erw¨ ahnten Regel beruht: die Anwendung von else if. Das folgende Programmst¨ uck zeigt Ihnen, was damit gemeint ist. if (Bedingung 1) Anweisungen 1 else if (Bedingung 2) Anweisungen 2 else if (Bedingung 3) Anweisungen 3 else Anweisungen 4 Es erkl¨art sich tats¨achlich fast von selbst. Eine Bedingung nach der anderen wird abgearbeitet und ausgewertet, und sobald eine Bedingung zutrifft, werden die zu dieser Bedingung geh¨orenden Anweisungen ausgef¨ uhrt - die m¨ ussen Sie nat¨ urlich, falls es mehrere sind, wieder durch Mengenklammern zu einem Block zusammenfassen. Ist also beispielsweise Bedingung 2 erf¨ ullt, Bedingung 1 aber nicht, so wird das Programm nicht in den ersten Anweisungsteil gehen, aber sicher in den zweiten und dort die entsprechenden Anweisungen ausf¨ uhren. Was in den nachfolgenden else-Zweigen steht, interessiert dann keinen mehr. Es kann aber vorkommen, dass keine der Bedingungen erf¨ ullt ist, und auch daf¨ ur ist vorgesorgt: Durchlaufen Sie erfolglos alle angegebenen Bedingungen, ohne auch nur ein einziges Mal auf Zustimmung zu stoßen, werden einfach die Anweisungen aus dem letzten else-Teil durchgef¨ uhrt. Eins fehlt uns noch zum Gl¨ uck, dann ist das if erledigt. Bisher konnte ich immer nur eine schlichte Bedingung wie z.B. x>0 abfragen. Das Leben ist aber manchmal komplizierter und verlangt von mir, Bedingungen miteinander zu verkn¨ upfen und abzufragen, ob gleichzeitig x>0 und x0 && x