Sie sind auf Seite 1von 448

Studienheft

GDI01

Einführung in die Informatik


1219K09
Das Studienheft und seine Teile sind urheberrechtlich geschützt.
Jede Nutzung in anderen als den gesetzlich zugelassenen Fällen
ist nicht erlaubt und bedarf der vorherigen schriftlichen
Zustimmung des Rechteinhabers. Dies gilt insbesondere für das
öffentliche Zugänglichmachen via Internet, Vervielfältigungen und
Weitergabe. Zulässig ist das Speichern (und Ausdrucken) des
Studienheftes für persönliche Zwecke.

$
GDI01

Einführung in die Informatik


1219K09

Prof. Dr. Thomas Rießinger


©

Werden Personenbezeichnungen aus Gründen der besseren Lesbarkeit nur in der männlichen oder
weiblichen Form verwendet, so schließt dies das jeweils andere Geschlecht mit ein.
Falls wir in unseren Studienheften auf Seiten im Internet verweisen, haben wir diese nach sorgfältigen
Erwägungen ausgewählt. Auf die zukünftige Gestaltung und den Inhalt der Seiten haben wir jedoch
keinen Einfluss. Wir distanzieren uns daher ausdrücklich von diesen Seiten, soweit darin rechtswid-
rige, insbesondere jugendgefährdende oder verfassungsfeindliche Inhalte zutage treten sollten.
© HfB, 02.12.20, Berk, Eric (904709)

Einführung in die Informatik


GDI01

Inhaltsverzeichnis
1219K09

Vorwort ....................................................................................................................... 1

1 Informatik und Algorithmen ................................................................................... 3


1.1 Informatik und das EVA-Prinzip ............................................................... 4
1.2 Algorithmen und Programmiersprachen .................................................. 9

2 Umsetzung von Algorithmen ................................................................................. 16


2.1 Kontrollstrukturen ...................................................................................... 16
2.2 Objektorientierung ...................................................................................... 24
2.3 Komplexität ................................................................................................. 31

3 Rechner ..................................................................................................................... 36
3.1 Die Analytische Maschine ......................................................................... 36
3.2 Die Turing-Maschine .................................................................................. 38
3.3 Die Von-Neumann-Architektur ................................................................. 42
3.4 Moderne Programmiersprachen und Betriebssysteme ............................ 46

4 Dualzahlen ................................................................................................................ 50
4.1 Zahlensysteme ............................................................................................. 50
4.2 Umrechnung ................................................................................................ 52
4.3 Addition und Subtraktion .......................................................................... 55
4.4 Hexadezimalzahlen und Oktalzahlen ....................................................... 60
4.5 ASCII-Code und Unicode ........................................................................... 63

5 Logische Schaltungen und Addierer .................................................................... 65


5.1 Die logischen Schaltungen ......................................................................... 65
5.2 Halbaddierer und Volladdierer .................................................................. 69

Anhang
A. Lösungen der Übungen im Text ................................................................. 77
B. Literaturverzeichnis .................................................................................... 89
C. Abbildungsverzeichnis ............................................................................... 90
D. Tabellenverzeichnis ..................................................................................... 91
E. Sachwortverzeichnis ................................................................................... 92
F. Einsendeaufgabe Typ A .............................................................................. 95
1219K09

GDI01
© HfB, 02.12.20, Berk, Eric (904709)

GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Vorwort
GDI01Einführung in die Informatik1219K09

Welche Ausbildung auch immer Sie durchlaufen, für welchen Studiengang Sie sich auch
entschieden haben, Sie werden in jedem Fall in mehr oder weniger starkem Ausmaß mit
Computern und daher auch mit Informatik zu tun haben. Nun ist aber die Informatik
ein Fach mit vielen Einzeldisziplinen, und es kann daher nicht schaden, sich erst einmal
einen Überblick darüber zu verschaffen, was man eigentlich unter Informatik versteht
und welche Fragestellungen dabei auftreten können, bevor man sich in die Tiefen der
Einzelheiten begibt.
Diesem Zweck dient genau das Studienheft, das Sie gerade in der Hand halten. Sie wer-
den zunächst das Grundprinzip der Informatik kennenlernen und sich mit dem Algo-
rithmenbegriff beschäftigen. Diese Grundbegriffe werden dann konkretisiert, indem Sie
sich mit zwei grundlegenden Ideen der Programmierung befassen, nämlich der struktu-
rierten und der objektorientierten Programmierung, und sich Gedanken machen über
die Frage, wie komplex Algorithmen werden können. Dann gehen wir zu konkreten
Rechnern über: Sie werden etwas darüber lernen, wie man sich den inneren Aufbau frü-
herer Computer vorgestellt hat und wie sehr diese Vorstellungen noch heute nachwir-
ken.
Da sämtliche Operationen innerhalb eines Computers auf dem dualen Zahlensystem be-
ruhen, bleibt es nicht aus, dass Sie einiges über den Umgang mit Dualzahlen und ihre
Verwendung in einem Rechner lernen, und am Ende werden Sie sehen, wie man diese
abstrakten Kenntnisse in konkrete logische Schaltungen bis hin zum Volladdierer um-
setzt. Kurz gesagt: Wir werden einen Weg gehen vom allgemeinen Begriff der Informa-
tik bis zur technischen Umsetzung innerhalb des Rechners.
Beim Bearbeiten dieses Studienhefts wünschen wir Ihnen viel Spaß und Erfolg!
Ihre Studienleitung

GDI01 1
© HfB, 02.12.20, Berk, Eric (904709)

Vorwort

2 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

1 Informatik und Algorithmen


In diesem Kapitel lernen Sie, was man unter Informatik und unter dem EVA-
Prinzip versteht. Außerdem sehen Sie, was Algorithmen mit Problemen zu tun
haben und warum zur Lösung von Problemen Programmiersprachen benötigt
werden.

Wenn Sie sich mit Informatik beschäftigen wollen, dann sollten Sie eine Vorstellung da-
von haben, was so ein Computer kann und was er nicht kann, und Sie sollten ihn weder
über- noch unterschätzen. Beides habe ich allerdings immer wieder erlebt. Das vielleicht
interessanteste Beispiel hat mir vor vielen Jahren eine Informatikstudentin des ersten Se-
mesters geliefert, die sich mit der Aufgabe plagte, die bekannte und beliebte p,q-Formel
zur Lösung quadratischer Gleichungen in der Programmiersprache Pascal zu program-
mieren. Nun will ich Ihnen nicht gleich mit Formeln auf die Nerven fallen und nur sa-
gen, dass man bei dieser Formel aus den beiden gegebenen Werten p und q die beiden
Lösungen einer quadratischen Gleichung ausrechnen kann. Das zu programmieren hat
die Studentin dann auch versucht; ihr Programm sorgte dafür, 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ösung eingeben und war der Meinung, das Problem wäre jetzt gelöst.
Vielleicht haben Sie schon einmal das Wort Softwarekrise gehört. Damit bezeichnet
man eine Phase etwa in den 1960er Jahren, in der die Informatiker merkten, dass plan-
loses Vor-sich-hin-Programmieren ziemlich schnell ins Chaos führt und man etwas ko-
ordinierter vorgehen sollte. Diese Softwarekrise fand ihre endgültige Lösung in dem
dreizeiligen Programm meiner Studentin, denn wenn man das gewünschte Ergebnis
gleich selbst eingibt, dann kann das Programm selbst nichts mehr falsch machen ... Aber
wieder im Ernst: Indem sie die Möglichkeiten des Computers gleichzeitig unterschätzte
und überschätzte, hatte sie einen doppelten Anfängerfehler gemacht, den ich Ihnen ger-
ne ersparen möchte. Die Überschätzung lag darin, dass ihr nicht klar war, wie genau
man dem Rechner mitteilen muss, was er im Einzelnen zu tun hat – wenn Sie dem Com-
puter kein genaues Rechenverfahren angeben, wird er entweder gar nichts oder nur Un-
sinn ausrechnen. Und weil die Studentin anscheinend ihrem eigenen Optimismus dann
doch nicht mehr so traute, ging sie gleich zu einer folgenschweren Unterschätzung über:
Man kann ja über Computer denken, was man will, aber wenn man ihnen die Ergebnisse
selbst eingeben müsste, könnte man sich auch den Strom sparen, mit dem man sie be-
treibt. Übrigens hatte sie dann auch noch vergessen, die Ergebnisse am Bildschirm aus-
zugeben, aber ich will hier nicht kleinlich werden.
So etwas findet man häufiger, vom Studenten des ersten Semesters bis hin zu Chefpro-
grammierern großer Firmen. Der Grund für solche Fehleinschätzungen liegt wohl oft
darin, dass die Leute nichts über das Innenleben von Computern wissen und keine Vor-
stellung davon haben, wie wichtig und unverzichtbar Algorithmen und Programme für
den erfolgreichen Einsatz von Computern sind. Damit Ihnen nicht das Gleiche passiert,
will ich in diesem Kapitel über Algorithmen reden, über Programme und über Soft-
ware. Zuerst sollten wir uns darüber verständigen, was man eigentlich mit Computern
anstellt und wie man davon ausgehend den Begriff Informatik verstehen kann.

GDI01 3
© HfB, 02.12.20, Berk, Eric (904709)

1 Informatik und Algorithmen

1.1 Informatik und 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ätte. Natürlich müssen Ihre Ge-
haltszahlungen über die Lohnbuchhaltung erledigt werden, und die hat in jedem Unter-
nehmen mehr oder weniger die gleichen Abläufe. Gehen wir einmal davon aus, dass Ihr
Arbeitgeber eher altmodisch orientiert ist und keine Computer in seiner Firma duldet –
das wäre heutzutage etwas ungewöhnlich, aber denkbar. Trotzdem wird man die Daten
Ihrer Arbeitszeiten irgendwie erfassen müssen; das kann zum Beispiel geschehen, indem
man die Stempelkarten der abgelaufenen Woche durcharbeitet, auf denen Sie und Ihre
Kollegen die jeweiligen täglichen Zeiten angegeben haben. Falls es keine Zeiterfassung
dieser Art gibt, wird irgendjemand die Anwesenheitslisten durchgehen müssen, um Ihre
An- und Abwesenheitsdaten festzustellen, denn Sie könnten ja während der Arbeitszeit
auch im Freibad gewesen sein. Damit aus diesen Daten am Ende klingende Münze er-
zeugt werden kann, wird dann der zuständige Sachbearbeiter die Anzahl Ihrer Anwe-
senheitsstunden in seinen Tischrechner eingeben. All diese Aktivitäten 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 Un-
ternehmensleitung immer noch keinen Computer anschaffen wollte. Das muss man
können, nicht jeder kennt sich in den Regeln und Vorschriften der Lohnbuchhaltung aus,
die unter Umständen auch noch durch spezielle Firmenregelungen ergänzt werden und
natürlich auch zu den Vorstellungen des Finanzamtes passen müssen. Der Sachbearbei-
ter, vermutlich ein gelernter Lohnbuchhalter, berechnet also den Bruttolohn aus den ein-
gegebenen Arbeitsstunden, dem Stundenlohn und eventuellen Überstundenzuschlägen
und geht dann zur Nettoberechnung über, um die Abzüge auszurechnen. Davon gibt es,
wie Sie wahrscheinlich schon schmerzlich erfahren haben, eine ganze Menge, und zwar
in Form von Steuern und Sozialabgaben. Der klägliche Rest, der nach dem Abzug der
Abgaben vom Bruttolohn übrig bleibt, ist dann Ihr Nettolohn, der hoffentlich noch aus-
reicht, 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üchern oder ähnlichen Unterlagen
abgelegt hat, wie beispielsweise die Lohnsteuersätze, die Sozialversicherungssätze oder
auch Daten aus der Personalkartei über Ihren Familienstand und die Anzahl Ihrer Kin-
der. Die Aktivitäten unseres Buchhalters bezeichnet man als Verarbeitung.
Sobald nun alles verarbeitet ist, muss man mit den ermittelten Ergebnissen auch noch
etwas anfangen, sonst könnte man sich die ganze Rechnerei sparen. Damit Sie Ihr Geld
bekommen, muss eine Kassenanweisung geschrieben werden, für Ihre Lohnmitteilung
muss ein entsprechender Beleg an den Postausgang gehen, auch das Finanzamt wird et-
was über den Vorgang wissen wollen und die Sozialversicherungskassen sicher auch.
Mit anderen Worten: Die Ergebnisse der Verarbeitung werden auf verschiedenen Medi-
en 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ämlich Eingabe → Verarbeitung → Ausgabe}.

4 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Informatik und Algorithmen 1

Die Situation verändert sich, wenn der Chef sich endlich einen Ruck gibt und den einen
oder anderen Computer einschließlich der nötigen Programme anschafft – vornehm
ausgedrückt spricht man allerdings nicht von Programmen, sondern von Software. Wie
sieht jetzt die Lohnbuchhaltung aus? Die Anwesenheitsdaten der Mitarbeiter werden
jetzt vermutlich maschinell erfasst, zum Beispiel von Eingabestationen am Eingangstor
zum Firmengelände, und automatisch an einen zentralen Computer weitergegeben. Das
kann vollautomatisch geschehen, sofern die Eingabestationen mit dem Zentralrechner
vernetzt sind. 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ägt
– das hängt von der eingesetzten Technik ab. In jedem Fall ist der erste Schritt der Lohn-
buchhaltung die Eingabe der nötigen Daten. Sind die Daten erst einmal im Computer
angekommen, muss er mit ihnen arbeiten, also die nötigen Verarbeitungsschritte vor-
nehmen. Die Regeln und Vorschriften, nach denen der Lohnbuchhalter in der guten al-
ten Zeit gearbeitet hat, stecken jetzt in den Anweisungen des verwendeten Programms,
in der Software, und dieses Programm arbeitet mit zwei verschiedenen Datenarten: ers-
tens mit den Daten, die aus der Zeiterfassung in den Rechner übertragen wurden, und
zweitens mit den zusätzlichen Daten, die auch der Lohnbuchhalter gebraucht hat, näm-
lich mit den Steuersätzen und Ähnlichem mehr. Da ein Computer schwerlich in den
Steuertabellen nachsehen kann, sind diese Daten auf der Festplatte des Rechners abge-
speichert, sodass er jederzeit auf sie zugreifen kann. Während der eigentlichen Berech-
nung, in der wieder Bruttolohn, Abzüge und Nettolohn ausgerechnet werden, kommen
sowohl Ihre Anwesenheitsdaten als auch die für Ihren Fall nötigen auf der Festplatte
vorrätigen Daten in den aktuellen Arbeitsspeicher des Rechners, damit er, wenn er nun
schon mal Rechner heißt, auch alles Gewünschte ausrechnen kann. Die Rolle des Tisch-
rechners, den noch der Lohnbuchhalter verwendet hatte, spielt dabei das Rechenwerk
des Computers.
Sie werden zugeben, dass die Aktivitäten des letzten Absatzes sicher die Bezeichnung
Verarbeitung verdienen. Der nächste Schritt ist wenig überraschend. Die berechneten
Ergebnisse müssen wieder den verschiedenen beteiligten Stellen zur Verfügung gestellt
werden, indem man Ausdrucke macht, Bildschirmausgaben ansieht oder über eine Netz-
werkverbindung direkt die Ergebnisse an einen anderen Rechner weitergibt. Also ergibt
sich als letzter Schritt der computergestützten Datenverarbeitung wieder die Ausgabe.
Sehen Sie den prinzipiellen Unterschied zwischen der manuellen und der maschinellen
oder auch automatisierten Datenverarbeitung, wie ich sie hier beschrieben habe? Nein?
Kein Wunder, es gibt ja auch keinen. Beide funktionieren nach dem gleichen Prinzip:
Zuerst werden Daten eingegeben, dann werden sie nach bestimmten Regeln und Metho-
den verarbeitet, wobei unter Umständen 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üsselwörter
Eingabe, Verarbeitung und Ausgabe in einer eingängigen Abkürzung zusammen. Da
aber in der Informatik alles möglichst auf Englisch formuliert sein muss, gibt es auch
eine englische Variante des EVA-Prinzips. Sie müssen nur bedenken, dass Eingabe Input,
Verarbeitung Process und Ausgabe Output heißt, und schon haben Sie alles zusammen,
um auch die Abkürzung IPO-Prinzip zu verstehen.

GDI01 5
© HfB, 02.12.20, Berk, Eric (904709)

1 Informatik und Algorithmen

Die Datenverarbeitung verläuft 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 IPO-Prinzip spricht.

Kann es Ihnen also egal sein, ob Sie Ihre Datenverarbeitung automatisiert 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ührung recht deutliche Unterschiede.
Was jedem vielleicht zuerst einfällt, ist die unterschiedliche Geschwindigkeit. Das mer-
ken Sie schon, wenn Sie eine einfache Multiplikation schriftlich ausführen oder mithilfe
eines Taschenrechners und dabei die Zeit messen. Noch viel deutlicher wird das natür-
lich, 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ässt sich mit einem Rechner sehr schnell durchführen,
von Hand wäre 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ät erfordern, kann es ein Mensch immer noch oft mit
einem Computer aufnehmen.
Etwas anders sieht es aus bei der Zuverlässigkeit. Computer sind in aller Regel deutlich
zuverlässiger als Menschen, auch wenn sie gelegentlich abstürzen – aber das soll auch
schon bei Menschen vorgekommen sein, vor allem am Wochenende. Sie können den
Computer die immer gleichen Aufgaben so oft durchführen lassen, wie Sie wollen, er
wird keine Ermüdungserscheinungen zeigen, keine Flüchtigkeitsfehler machen oder Sie
durch bewusste Schlamperei ärgern; er macht einfach immer das, was Sie ihm gesagt ha-
ben. Genau darin liegt allerdings auch ein Problem: Wenn Sie der Maschine die falschen
Regeln mitgeteilt, sie also falsch programmiert haben, dann wird sie natürlich auch treu
und zuverlässig die falschen Ergebnisse liefern. Zwar immer die gleichen, aber eben fal-
sche. Man kann dafür aber nicht den Computer verantwortlich machen, der hat wie üb-
lich nur getan, was Sie ihm gesagt haben. Auch das Programm kann nichts dafür: Die
Verantwortung liegt hier eindeutig bei dem Programmierer oder dem Anwender, der ein
falsches Programm geschrieben oder falsche Eingabedaten verwendet hat.
Sie dürfen nicht vergessen, dass es beim Computereinsatz auch um wirtschaftliche Fra-
gen geht, und deshalb besteht ein wesentlicher Vorteil der automatisierten Datenverar-
beitung in den geringeren Kosten, die sie verursachen. Die einmalige Anschaffung von
Rechnern, verbunden mit der nötigen Software, verursacht natürlich zunächst einmal
Kosten, aber wenn sie erst mal da sind, dann sparen sie auch einiges ein. Die Maschine
ist auf mittlere Sicht billiger als der menschliche Lohnbuchhalter, denn sie bezieht kein
Gehalt und keine Lohnnebenkosten, ganz zu schweigen davon, dass man für 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ätze verloren gehen
– nicht immer kann man alles eindeutig bewerten.

6 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Informatik und Algorithmen 1

Wesentliche Vorteile der automatisierten Datenverarbeitung im Vergleich zur manu-


ellen Datenverarbeitung sind
• höhere Geschwindigkeit,
• höhere Zuverlässigkeit,
• geringere Kosten.

Sie sehen also, es ist sinnvoll, bei Aufgaben der Datenverarbeitung auf einen Computer
zurückzugreifen, und daher sollten wir einen Blick auf die Aufgabenfelder werfen, für
die so ein Rechner eingesetzt werden kann.
Wenn man ihn schon als einen Rechner bezeichnet, dann sollte zu seinen grundlegenden
Aufgaben sicher das Rechnen gehören, genauer gesagt die Ausführung von Berechnun-
gen. Das können Berechnungen verschiedenster Art sein, beispielsweise die Berechnung
des Nettolohns aus dem Bruttolohn und den Randbedingungen, über die ich vorhin ge-
sprochen habe. Solche Berechnungen könnte auch jeder Lohnbuchhalter durchführen;
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üssen. Anders sieht das aus bei bestimmten techni-
schen oder physikalischen Problemen, bei denen keine Massendaten verarbeitet werden,
sondern ausgesprochen komplizierte Rechenverfahren abgearbeitet werden müssen, für
die ein noch so begabter Mensch mehr Zeit bräuchte, als ein Leben hergibt. Diese Ver-
fahren lassen sich in einem Programm beschreiben, und der Computer kann mit etwas
Glück aufgrund seiner hohen Geschwindigkeit die nötigen Berechnungen vornehmen.
Rechnen ist nicht alles im Leben. Denken Sie wieder einmal an die maschinell durchge-
führte Lohnbuchhaltung, die am Ende, nachdem alles gerechnet ist, irgendwelche Er-
gebnisse liefert. Nun kann es ja sein, dass die Steuerprüfung Ihrer Firma auf den Zahn
fühlen und Ihre Abrechnungen überprüfen will, und zu diesem Zweck müssen die Er-
gebnisse 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 ma-
schinell vornimmt, also mit Computerunterstützung. Auch hier gibt es verschiedene
Möglichkeiten. Es kann sich um langfristig anzulegende Daten handeln, die wird man
auf einem bestimmten Speichermedium ablegen, von dem man hofft, dass man es not-
falls wiederfindet. Dazu gehören zum Beispiel Reproduktionen von alten Manuskripten
oder Bildern, die man der Nachwelt erhalten will. Wenn Sie aber von Frankfurt nach Te-
neriffa fliegen wollen und eine Flugbuchung vorgenommen haben, dann können Sie mit
einem gewissen Recht erwarten, dass die Angestellten beim Einchecken nicht erst das
richtige Speichermedium suchen, solche Daten müssen natürlich sofort verfügbar sein.
Man verwendet also Computer auch, um Daten abzuspeichern, und zwar entweder
langfristig oder direkt verfügbar.
Vielleicht haben Sie schon darauf gewartet, dass ich endlich über das Internet rede, und
natürlich haben Sie recht: Wenn es um die Grundaufgaben eines Computers geht, muss
ich es schon mal erwähnen. Es gibt ja nicht nur einen Computer auf der Welt, und wenn
man die Leistungen und Informationen dieser Computer miteinander verbinden will,
dann spricht man von Computernetzwerken. Über das Internet hat man heute einen
Riesenverbund von Rechnern zur Verfügung, auf die eine Riesenmenge von Benutzern
zugreifen kann, und das regelt sich nicht von allein. Um diese Kommunikation zwi-

GDI01 7
© HfB, 02.12.20, Berk, Eric (904709)

1 Informatik und Algorithmen

schen den Computern zu steuern, brauchen Sie selbstverständlich wieder Computer, de-
ren Aufgabe es ist, die Übermittlung der gewünschten Informationen von Ort zu Ort zu
steuern.
Und damit habe ich schon das nächste Schlüsselwort gefunden: Steuerung und, was fast
immer dazugehört, Kontrolle. Nicht nur das Internet und die allgemeine Datenkommu-
nikation müssen gesteuert werden, meine Waschmaschine und mein Auto auch. Auch
das übernehmen oft genug Computer, ob Navigationssysteme oder Waschmaschinen-
steuerung, ob Autopiloten im Flugzeug oder Steuerungsgeräte für Produktionsroboter,
sie alle haben die Aufgabe, Geräte zu steuern und zu kontrollieren.
Jetzt sind wir so weit, eine Definition des Begriffs Computer geben zu können, den ich
schon so oft benutzt habe. Mein etwas angejahrtes 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ünn. Ich verwende deshalb die eben ermittelten Grundfunktionen und
definiere einen Computer durch die Aufgaben, für die er da ist.

Ein Computer ist ein Gerät, das Daten speichert und verarbeitet, das auf dieser Basis
Berechnungen durchführt, andere Geräte steuern kann und das mit anderen Geräten
und mit Menschen in Verbindung treten kann.

Es wird nichts darüber gesagt, dass es sich unbedingt um ein elektronisches Gerät han-
deln 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 hyd-
raulischen Computern selten etwas hört. Wenn ich also von einem Computer rede, dann
meine ich natürlich in aller Regel ein elektronisches Gerät, das der obigen Definition ge-
nügt.
Sie sollten dabei aber nicht aus dem Auge verlieren, dass Computer nur ein Mittel zum
Zweck sind, und der Zweck ist der im EVA-Prinzip beschriebene: die Eingabe, Verarbei-
tung und Ausgabe von Daten. Da das Ganze mithilfe des elektronischen Gerätes Com-
puter stattfinden soll, spricht man auch, wie Sie natürlich wissen, gerne von elektroni-
scher Datenverarbeitung, abgekürzt EDV. Und was ist jetzt Informatik? Auch da gehen
die Definitionen etwas auseinander, zumal in der englischen Sprache das Wort Informa-
tik 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äbe es ohne Computer so
etwas wie Informatik vermutlich ü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 ist die Wissenschaft, die sich mit den theoretischen Grundlagen, den Mit-
teln und Methoden sowie mit der Anwendung der elektronischen Datenverarbeitung
beschäftigt, das heißt mit der Informationsverarbeitung unter Einsatz von Compu-
tern.

8 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Informatik und Algorithmen 1

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 automatisierte
Informationsverarbeitung betreibt. Zu diesem Zweck werde ich im nächsten Abschnitt
ein wenig über Algorithmen reden.

Übung 1.1:
Analysieren Sie im Hinblick auf das EVA-Prinzip die Tätigkeiten, die ein Mensch
bzw. ein Computer durchführen muss, um zwei Gleichungen mit zwei Unbekannten
zu lösen, also zum Beispiel

ax  by  e
cx  dy  f

mit den Unbekannten x und y. Geben Sie dabei genau an, welche Eingaben nötig
sind, welche Verarbeitungsschritte durchgeführt werden und wie die Ausgabe ge-
staltet werden sollte.

Übung 1.2:
Analysieren Sie die möglichen Nachteile, die die automatisierte Datenverarbeitung
im Vergleich zur manuellen Datenverarbeitung hat.

Übung 1.3:
Ein Handlungsreisender startet in Frankfurt eine Rundreise, die ihn durch 19 weitere
Städte und zum Schluss wieder zurück nach Frankfurt führt. In jeder Stadt will er
genau einmal Station machen (außer natürlich in Frankfurt, wo er sowohl startet als
auch ankommt).
a) Zeigen Sie, dass es genau
19 ⋅ 18 ⋅ 17 ⋯ 3 ⋅ 2 ⋅ 1
verschiedene Reiserouten gibt. Man nennt diese Zahl 19!  Fakultät von 19.
b) Der Reisende hat einen Computer zur Verfügung, der pro Sekunde 10 Milliarden
verschiedene Routen durchchecken kann. Wie lange braucht er, um alle mögli-
chen Reiserouten durchzugehen und so die kürzeste Route herauszufinden?

1.2 Algorithmen und Programmiersprachen


Es steht außer Frage, dass die elektronische Datenverarbeitung mithilfe sogenannter
Programme erfolgt. Dennoch ist das nur die halbe Wahrheit. Mithilfe 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üssten Sie überhaupt nicht, was Sie programmieren
sollten. Und da Verfahren zu einfach klingt, hat man sich die Bezeichnung Algorithmus
ausgedacht.
Was ist nun ein Algorithmus? Die Formulierungen gehen hier auseinander, manchmal
auch die Meinungen, aber das ist für unsere Zwecke nicht weiter schlimm. Wir hatten
uns darauf geeinigt, dass ein Algorithmus das Verfahren ist, das dem konkreten Pro-
gramm zugrunde liegt, und die Programme werden in aller Regel auf einem Computer

GDI01 9
© HfB, 02.12.20, Berk, Eric (904709)

1 Informatik und Algorithmen

ausgeführt. Also dient der Computer der Ausführung von Algorithmen, wobei das Pro-
gramm die Rolle des Vermittlers zwischen dem abstrakten Algorithmus und dem kon-
kreten Computer spielt: Man kann dem Rechner nicht auf Deutsch erklären, was er jetzt
zu tun hat, dazu braucht man eine klar definierte und auf die Bedürfnisse der Maschine
abgestimmte Sprache. Wenn Sie aber eine Verfahrensanleitung, also den Algorithmus,
übersetzen können sollen in die Anweisungen einer Programmiersprache, dann darf
dieser Algorithmus sich nicht in den Gefilden philosophischer Großzügigkeit bewegen,
sondern muss klar und exakt sein. Das ist nicht alles. Kein Computer der Welt kann un-
endlich 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 kon-
kret durchzuführenden Einzelschritte nicht unendlich groß werden darf. Sie dürfen also
keine Anweisung der Form „Solange 1  1 ist, gib die Zahl 17 am Bildschirm aus“ in Ih-
ren Algorithmus schreiben, denn damit hätten Sie zwar nur eine einzige Verfahrensvor-
schrift, diese einzige Verfahrensvorschrift müsste jedoch unendlich oft durchgeführt
werden, und das ist offenbar nicht möglich.
Fast haben wir es schon, nur noch eine Kleinigkeit fehlt. Wie kommt man auf die Idee,
sich einen Algorithmus zu überlegen? Sicher nicht aus heiterem Himmel, weil heute
Mittwoch oder gar Donnerstag ist. Ein Algorithmus dient immer der Lösung eines Pro-
blems, einer Aufgabe, sonst wäre er sinnlos. Sobald Sie also ein Problem haben und die-
ses Problem mithilfe endlich vieler klarer Verfahrensvorschriften lösen wollen, die sich
in endlich vielen Einzelschritten durchführen lassen, können Sie mit Fug und Recht von
einem Algorithmus sprechen.

Ein Algorithmus ist eine endliche Menge von eindeutig festgelegten Verfahrensvor-
schriften zur Lösung eines Problems durch eine in der Regel endliche Menge von
Einzelschritten.

Man kann die Auffassung vertreten, die Menge der Einzelschritte dürfte auch unendlich
groß werden; es gibt mathematische Verfahren, die eine unendliche Anzahl von Wieder-
holungen verlangen und übereinstimmend als Algorithmen bezeichnet werden. Sobald
Sie solche Verfahren aber konkret anwenden wollen, werden Sie gezwungen sein, nach
einer gewissen Zeit aufzuhören – möglichst noch vor dem Ende des Universums, weil
sonst Ihre berechneten Ergebnisse zu nichts mehr zu gebrauchen sind und somit auch
kein Problem lösen können. Für uns hat ein Algorithmus deshalb in jeder Hinsicht end-
lich zu sein, auch wenn das in der Mathematik manchmal anders aussieht.
Übrigens klingt das Wort Algorithmus zwar eher griechisch oder vielleicht auch latei-
nisch, kommt aber aus der arabischen Sprache und lehnt sich an den Namen des arabi-
schen Mathematikers und Astronomen Mohamed ibn Musa al-Hwarizmi aus dem 9.
Jahrhundert an, der zwar sicher keine Computerprogramme geschrieben, aber immer-
hin 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ünstliches.
Führt man nun einen Algorithmus aus, so spricht man von einem Prozess, und der Aus-
führende ist der Prozessor. Wer dabei die Rolle des Prozessors spielt, hängt ganz vom
Einzelfall ab. Wollen Sie zum Beispiel zwei natürliche Zahlen auf dem Papier addieren,
dann sind Sie der Prozessor. Soll dagegen eine Addition auf dem Computer vorgenom-
men werden, wird man den Computer oder einen bestimmten Teil des Computers, über

10 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Informatik und Algorithmen 1

den wir später noch sprechen werden, als Prozessor bezeichnen. Beim Ausführen eines
etwas komplizierteren Algorithmus zum Sortieren einer großen Menge von Datensätzen
kann Ihnen kein menschlicher Prozessor mehr die Ausführung des Algorithmus abneh-
men, dieser Prozess muss auf einer Maschine laufen. Es dürfte klar sein, dass wir uns im
Folgenden nur mit Algorithmen befassen werden, die als Prozessor einen Computer ver-
wenden.
Nun entsteht aber, wenn man maschinell ausführbare Algorithmen haben möchte, ein
kleines Problem. Der Prozessor muss nämlich den Algorithmus in irgendeiner Weise in-
terpretieren können, und dazu gehören immer zwei Dinge: Erstens muss er überhaupt
einmal jeden Schritt des Algorithmus verstehen, und zweitens muss er auch in der Lage
sein, jede verlangte Operation auszuführen. Wie schon erwähnt, können Sie dem Com-
puter nicht einfach erzählen, was er machen soll, Sie müssen 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öllig unabhängig von
einer verwendeten Programmiersprache, um ihn aber dem Computer verständlich zu
machen, braucht man eine Formulierung in einer Programmiersprache, möglichst auch
noch in einer, die der verwendete Computer versteht. Es wird Ihnen also nichts anderes
übrig bleiben, als Ihren Algorithmus in einer Programmiersprache als Programm zu for-
mulieren und dann dem Computer zur Kenntnis zu geben. Welche Programmiersprache
Sie dabei verwenden, hängt von Ihrem Geschmack und von der Sachlage ab.

Algorithmen, die auf einem Computer ausgeführt werden sollen, müssen mithilfe ei-
ner Programmiersprache als Programme geschrieben werden.

Denken Sie immer daran: Der Algorithmus, Ihre Verfahrensbeschreibung, ist die Grund-
lage der Verarbeitung, das Programmieren setzt die Existenz eines Algorithmus voraus,
der in eine Programmiersprache umgesetzt werden soll. Wenn Sie also ein Problem mit-
hilfe eines Programms lösen wollen, dann ist es immer zu empfehlen, in drei Schritten
vorzugehen. Zuerst sollten Sie sich darüber klar werden, um welches Problem es eigent-
lich geht. Das klingt wesentlich trivialer, als es ist, denn oft genug wird wild in die Ge-
gend hinein programmiert, ohne dass sich jemand Rechenschaft darüber ablegt, welche
Ziele genau erreicht, welche konkreten Probleme gelöst werden sollen; deshalb ist die
Problemdefinition, in der das anzugehende Problem so genau wie möglich beschrieben
wird, von großer Bedeutung. Ist das Problem einmal definiert, so können Sie daran ge-
hen, einen Algorithmus zur Lösung 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 Program-
mierung 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 erledi-
gen, aber darüber nachdenken sollte man auf jeden Fall. Bei großen Systemen ist aller-
dings ein strukturiertes Vorgehen unverzichtbar, wenn man nicht ganz schnell in gewal-
tige Schwierigkeiten kommen will; das Fach Software Engineering ist die Antwort der
Informatiker auf das wilde Programmieren der informatischen Frühzeit.
Eine kleine Einschränkung muss ich noch machen. Im Prinzip ist tatsächlich der Algo-
rithmus unabhängig von der Programmiersprache, in die er anschließend umgesetzt
wird, oder er sollte es wenigstens sein. Wie die meisten hohen Prinzipien ist allerdings

GDI01 11
© HfB, 02.12.20, Berk, Eric (904709)

1 Informatik und Algorithmen

auch dieses Unabhängigkeitsprinzip nicht vollständig und konsequent durchführbar.


Verschiedene Programmiersprachen sind nun mal auf verschiedene Anwendungsgebie-
te zugeschnitten, und das kann ein Algorithmus nicht einfach ignorieren. So hat man
zum Bespiel vor langer Zeit die Programmiersprache COBOL für betriebswirtschaftliche
Anwendungen gedacht, während FORTRAN einen naturwissenschaftlich-technischen
Hintergrund hatte. Niemand wäre jemals auf die Idee gekommen, eine Satellitenflug-
bahn mit einem COBOL-Programm berechnen zu lassen, und genauso unsinnig wäre es,
ein System zur Lohnbuchhaltung in FORTRAN programmieren zu wollen. Da Sie aber
in der Regel beim Programmentwurf, wenn es um den reinen Algorithmus geht, schon
wissen, in welcher Programmiersprache Ihr Algorithmus umgesetzt werden soll, liegt es
nahe, auf die Eigenheiten der Programmiersprache Rücksicht zu nehmen. Welche Me-
thoden und Verfahren Sie also in einem Algorithmus einsetzen, wird davon abhängen,
welche Methoden und Verfahren von der gewählten Programmiersprache unterstützt
werden.

Bei der Erstellung eines Programms zur Lösung eines Problems sollte man schritt-
weise vorgehen. Eine mögliche Einteilung sieht drei Schritte vor:
• Problemdefinition
• Programmentwurf
• Programmierung
Der im Programmentwurf entwickelte Algorithmus sollte sich an den Gegebenhei-
ten der für die Programmierung verwendeten Programmiersprache orientieren, da-
mit er in dieser Sprache realisiert werden kann.

Ein Computer ist eine konkrete Maschine, die aus einer Menge verschiedener Einzelteile
besteht, aus physisch vorhandenen Komponenten. Die Gesamtheit dieser technischen
Geräte und Einrichtungen bezeichnet man gerne als Hardware, ein englisches Wort für
Eisenwaren. Das kann aber nicht alles sein. Wir hatten uns gerade darauf geeinigt, dass
ein Computer neben seiner Funktion als Staubfänger vor allem der Ausführung von Al-
gorithmen dient, indem er als Prozessor für Programme zur Verfügung steht. Ein Algo-
rithmus ist kein physikalischer Gegenstand. Man kann zwar das zugehörige Programm
auf eine Festplatte speichern, aber die zur Speicherung verwendeten Festplattensektoren
wird kaum jemand mit dem Programm gleichsetzen wollen. Algorithmen und Program-
me sind und bleiben etwas Abstraktes, eine geistige Konstruktion, und ganz sicher keine
Eisenwaren. Aus diesem Grund hat sich für die in einem Computer einsetzbaren Pro-
gramme die Bezeichnung Software durchgesetzt, um den Gegensatz zur Hardware zu
dokumentieren und deutlich zu machen, dass zum Betrieb eines Computers eben etwas
mehr nötig ist als die anfassbaren Komponenten.
Nun gibt es aber verschiedene Arten von Software, und man neigt dazu, sie in zwei Klas-
sen zu unterscheiden: Auf der einen Seite haben Sie die Systemsoftware, auf der ande-
ren die Anwendungssoftware. Ohne ein gewisses Maß an Systemsoftware kann kein
Rechner existieren. Irgendjemand muss sich darum kümmern, dass sich Benutzer an-
melden und dass Programme geladen und ausgeführt werden können. Man braucht eine
Software, die die Verwaltung der Dateien organisiert, die dafür sorgt, dass die Steuerung
der Prozesse reibungslos abläuft und sich mehrere Prozesse nicht gegenseitig stören – all
das erledigt das Betriebssystem, es verwaltet die Ressourcen Ihres Rechners und erspart

12 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Informatik und Algorithmen 1

es Ihnen, sich selbst direkt mit der Hardware herumschlagen zu müssen. Ob das nun ein
Windows-Dialekt oder Linux oder sonst etwas ist: Die grundsätzlichen Aufgaben des
Betriebssystems sind immer gleich, und kein Computerbenutzer kann darauf verzichten.
Zur Systemsoftware zählt man neben dem Betriebssystem auch noch eine Reihe anderer
sogenannter Dienstprogramme und Werkzeuge, auf die ich jetzt nicht näher eingehen
werde. Wichtig ist: Die Systemsoftware ist die Voraussetzung, ohne die nichts geht. Und
was soll mit ihrer Hilfe eigentlich gehen? Natürlich 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 Wettervor-
hersagen oder sonst irgendeine Anwendung: Es handelt sich in jedem Fall um Anwen-
dungssoftware.

Unter Software versteht man die Menge der Programme, die auf einem Computer
eingesetzt werden können. Man unterteilt sie in Systemsoftware und Anwendungs-
software, wobei die Systemsoftware noch einmal unterteilt wird in das Betriebssys-
tem sowie systemnahe Dienstprogramme und Werkzeuge.

Seit es Programmiersprachen gibt, mit deren Hilfe man Software verschiedenster Art
entwickeln kann, neigten die Entwickler zumindest in der Anfangszeit dazu, munter vor
sich hin zu programmieren. Das führte aber zu Problemen, weil jeder seinen eigenen Stil
entwickelte und sich unter Software-Entwicklern ein leichter Hang zu einer „Nach mir
die Sintflut“ -Mentalität ausbildete: Solange der Entwickler sein Programm verstand,
war alles in Ordnung, die Nachwelt hat keinen interessiert. Insbesondere wurden mit
Begeisterung wilde Sprünge in Programmen durchgeführt, die zur Folge hatten, dass ein
Programm eben noch einen bestimmten Befehl ausführte und sich dann infolge eines
Sprungbefehls plötzlich an einer völlig anderen Stelle befand. Es erinnerte, falls die
Sprungbereitschaft stark ausgeprägt war, streckenweise an manche Gesprächspartner,
die einen Satz anfangen, sich mitten im Satz unterbrechen, dann den unterbrechenden
Satz wieder durch einen anderen Einschub zerhacken und dieses Spiel so lange betrei-
ben, bis nicht mehr die geringste Hoffnung besteht, zu dem eigentlich angefangenen
Satz zurückzufinden.
Sie können sich vorstellen, dass Programme dieser Art unter Umständen leichte Quali-
tätsprobleme aufwiesen, weshalb man sich ab den späten 1960er-Jahren bemüht hat, zu
einem besseren Stil zu finden. Nach einem grundlegenden Artikel des holländischen In-
formatikers Edsger Dijkstra, in dem er zu Recht die Sprunganweisung verdammte, ging
man langsam über zum Prinzip der strukturierten Programmierung. Ihr Grundgedan-
ke ist einfach genug: Niemals einen Sprungbefehl verwenden und alle Programmanwei-
sungen schön der Reihe nach erledigen. Eine Anweisung in einem Programm kann man
nur erreichen, wenn man die vorherige Anweisung erledigt hat, und sobald man mit ei-
ner Anweisung fertig ist, geht man zur nächsten über, ohne wie ein Känguru durch den
Programmtext zu hüpfen. Da es aber manchmal – und sogar ziemlich häufig – nötig ist,
einem Programm eine gewisse Flexibilität zu erlauben, wurden in der strukturierten
Programmierung bestimmte Kontrollstrukturen eingeführt, mit deren Hilfe man die
Abarbeitungsreihenfolge der Programmanweisungen beeinflussen kann. Es handelt sich
dabei um die berühmten Kontrollstrukturen Sequenz, Auswahl und Wiederholung,
die ich im nächsten Kapitel besprechen werde.

GDI01 13
© HfB, 02.12.20, Berk, Eric (904709)

1 Informatik und Algorithmen

Ein Programm entspricht dann den Regeln der strukturierten Programmierung,


wenn es keinerlei Sprunganweisungen enthält und die Steuerung des
Pro­gramm­ablaufs mithilfe der Kontrollstrukturen Sequenz, Auswahl und Wieder-
holung erfolgt.

Übung 1.4:
Untersuchen Sie die Wirkungsweise des folgenden Algorithmus, der mit einer gan-
zen Zahl a arbeitet. Welchen Wert hat die Zahl a nach der Abarbeitung des Algo-
rithmus? Könnte 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}
Beachten Sie, dass die eingerückten Zeilen eine Gesamtheit bilden, sodass also im
Fall a0 beide Anweisungen nach dem Doppelpunkt ausgeführt werden müssen.

Übung 1.5:
Gegeben seien die folgenden beiden Algorithmen zur Beschreibung des Verhaltens
eines Autofahrers an einer Straßenkreuzung. Wie interpretieren Sie die verschiede-
nen Einrückungen? In welcher Situation unterscheiden sich Algorithmus 1 und Al-
gorithmus 2? Welcher Algorithmus erscheint Ihnen zweckmäß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

14 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Informatik und Algorithmen 1

Übung 1.6:
Das folgende Programm in einer nicht existierenden Programmiersprache enthält
mehrere Sprungbefehle, die sich auf die jeweils vorangestellten Zeilennummern be-
ziehen. Stellen Sie fest, zu welchem Problem dieses Programm führt.
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

GDI01 15
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen


Sie lernen in diesem Kapitel die Prinzipien der strukturierten Programmierung
und der objektorientierten Programmierung kennen. Zudem sehen Sie, wie man
die Komplexität von Algorithmen beurteilt.

Im ersten Kapitel haben Sie gesehen, dass man zur Lösung bestimmter Probleme Algo-
rithmen entwickelt und zur Umsetzung dieser Algorithmen so etwas wie Programmier-
sprachen braucht, weil der Computer sonst nicht weiß, was er mit den Algorithmen an-
fangen soll. Aber nach welchen Prinzipien soll man bei der Entwicklung eines
Algorithmus vorgehen? Gibt es verschiedene Arten der Problemlösung? Und wenn man
verschiedene Lösungen des gleichen Problems hat: Wie kann man entscheiden, welche
Lösung die bessere ist? Solchen Fragen werde ich in diesem Kapitel nachgehen.

2.1 Kontrollstrukturen
Irgendwann müssen wir auch etwas konkreter werden, und dieser Zeitpunkt ist jetzt ge-
kommen. Ich werde Sie hier noch nicht mit syntaktischen Details bestimmter Program-
miersprachen behelligen – zumindest nicht allzu sehr, weil das die Aufgabe späterer Stu-
dienhefte sein wird. Was ich Ihnen hier zeigen will, das sind die bereits angekündigten
Kontrollstrukturen, ihre grafische Darstellung mithilfe von Struktogrammen und we-
nigstens ansatzweise ihre Umsetzung in der Programmiersprache C.
Am einfachsten ist die schlichte Sequenz zu verstehen. Dass jedes Programm aus ver-
schiedenen Anweisungen besteht, haben Sie natürlich schon längst mitbekommen, und
eine Abfolge aus einzelnen Schritten, aus einzelnen Anweisungen, die schön brav nach-
einander auszuführen sind, nennt man eine Sequenz. Zu jedem Zeitpunkt der Verarbei-
tung wird genau ein Schritt ausgeführt, wobei jeder Schritt auch genau einmal ausge-
führt wird, nichts wird ausgelassen, nichts wiederholt. Sie können es sich vorstellen wie
das vollständige Auslöffeln einer Suppe: Zu jedem Zeitpunkt wird genau ein Löffel ge-
gessen, 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 Reihen-
folge der Abarbeitung gibt, die schon beim Aufschreiben einer Sequenz deutlich wird;
bei der Suppe ist das offenbar anders.
Wie so eine Sequenz konkret aussieht, kann man sich am Beispiel der Währungsumrech-
nung vorstellen. Nach dem Kurs des heutigen Tages (wir befinden uns im Herbst 2015)
entspricht ein Euro einem US-Dollar und 7,4 US-Cent. Schon in einer rein verbalen Be-
schreibung ist der Umrechnungsalgorithmus wohl ziemlich selbsterklärend. Der ge-
wünschte Euro-Betrag wird mit dem Umrechnungsfaktor 1,074 multipliziert und an-
schließ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. Nun
könnte man auch ohne größere Probleme ein Programm in der Programmiersprache C
verfassen, das genau diesen schlichten Algorithmus abbildet, und das werde ich in Kürze
auch tun. Da aber nicht alle Programmierer die gleiche Programmiersprache verwen-
den, kann es nicht schaden, so etwas auch anders darstellen zu können, unabhängig von
einer konkreten Programmiersprache, und ein beliebtes Hilfsmittel zu diesem Zweck
sind Struktogramme.

16 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

Struktogramme wurden 1973 von Nassi und Shneiderman als Methode zur Strukturie-
rung vorgeschlagen, weshalb man sie auch ab und zu als Nassi-Shneider­man-Dia-
gramme bezeichnet, und erfreuen sich auch heute noch großer Beliebtheit. Jeder Verar-
beitungsschritt wird in einem schlichten rechteckigen Kasten dargestellt, mehrere
Verarbeitungsschritte zusammen ergeben einen Block. Den grundsätzlichen Aufbau se-
hen Sie in Abb. 2.1.

Sequenz
Anweisung 1

Anweisung 2

Anweisung 3

Abb. 2.1: Struktogramm einer Sequenz

Hier werden drei Anweisungen in einem Block zusammengefasst, mehr gibt ein derart
elementares Struktogramm nicht her. Auch mein kleiner Umrechnungsalgorithmus
sieht als Struktogramm nicht aufregender aus als vorher; in Abb. 2.2 können Sie es be-
wundern. Wie Sie sehen, bildet das Struktogramm genau die einzelnen Schritte meines
verbal beschriebenen Algorithmus ab, nur noch etwas genauer, da hier schon Namen für
die beteiligten Variablen vergeben werden: eine Variable enachd für den Umrech-
nungsfaktor, eine Variable euro für den betrachteten Euro-Betrag und natürlich auch
eine Variable dollar für den berechneten Dollar-Betrag. Der Benutzer soll also aufge-
fordert werden, einen Euro-Betrag einzugeben, danach wird umgerechnet und zum
Schluss sollen sowohl der Euro- als auch der berechnete Dollar-Betrag ausgegeben wer-
den. Das ist kein Hexenwerk.
Nun ist aber das Struktogramm unabhängig von der verwendeten Programmiersprache
oder sollte es jedenfalls weitgehend sein. Anders sieht es aus, wenn ich das bereits er-
stellte Struktogramm als Vorlage für ein Programm – beispielsweise in der Program-
miersprache C – verwende, denn sobald ich eine konkrete Programmiersprache einsetze,
muss ich mich auf die Gepflogenheiten dieser Sprache einlassen. Sie werden im Verlauf
der weiteren Hefte noch ausreichend Gelegenheit haben, sich mit den Details von C zu
befassen, weshalb ich Ihnen hier zwar das eine oder andere C-Programm als Realisie-
rung eines Struktogramms vorstellen, aber nicht auf alle syntaktischen Einzelheiten ein-
gehen werde.

Umrechnung
setze enachd = 1,074

Ausgabe Eingabeaufforderung für euro

einlesen euro

dollar = euro * enachd

Ausgabe euro und dollar

Abb. 2.2: Struktogramm zur Währungsumrechnung

GDI01 17
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Das eigentliche Programm will ich Ihnen jetzt nicht länger vorenthalten.
/* geld.c –- waehrungen umrechnen */

#include stdio.h

main()
{
float enachd1.074;
float euro, dollar;
printf("Geben Sie einen Euro-Betrag ein:\n);
scanf("%f“ ,&euro);
dollar  euro * enachd;
printf("%6.2 f. Euro sind %6.2 f. Dollarn“ , euro,dollar;
}

Falls Sie so etwas noch nie gesehen haben, wirkt es vielleicht ein wenig seltsam, aber in
Wahrheit ist es nicht weiter dramatisch. Die erste Zeile verrät Ihnen, dass das Programm
den Namen geld.c trägt und der Umrechnung von Währungen dient. Dass diese Zeile
in die Zeichen /* … */ eingeschlossen ist, bedeutet, dass es sich nur um einen Kom-
mentar handelt, der zur Information des Lesers, Benutzers oder Programmierers dient,
für den Ablauf des Programms aber nicht die geringste Bedeutung hat. Danach sehen
Sie den Befehl #include stdio.h, der schlimmer wirkt, als er ist. C in seiner
Grundform kennt überhaupt keine Möglichkeiten zur Ausgabe von Daten auf dem Bild-
schirm oder gar zur Dateneingabe über die Tastatur. Um also überhaupt zu ermöglichen,
dass der Euro-Betrag eingegeben und der Dollar-Betrag ausgegeben wird, müssen Sie
vorher dafür sorgen, dass Ihr C-Programm die entsprechenden Anweisungen auch ver-
steht, und genau das passiert mit #includestdio.h. Die Datei stdio.h ist eine
sogenannte Header-Datei, in der bestimmte Ein- und Ausgabefunktionen zu finden sind,
die Ihnen ohne diese Datei nicht zur Verfügung stünden. Durch den Befehl
#includestdio.h ist es angenehmerweise möglich, alle in der Header-Datei ange-
gebenen Funktionen auch in dem Programm zu benutzen, das den Befehl zum Einbinden
der Header-Datei enthält.
Danach geht es eher undramatisch weiter. Mit main() wird angezeigt, dass nun end-
lich das eigentliche Programm beginnt, das sogenannte Hauptprogramm, in dem die
wirkliche Arbeit erledigt wird. Und die läuft wie folgt ab: Erst wird eine Variable
enachd angelegt und mit dem Wert 1,074 versehen. Dann werden die beiden Variablen
euro und dollar zur Verfügung gestellt. Anschließend ergeht eine Aufforderung an
den Benutzer des Programms, den Euro-Betrag einzugeben, woraufhin ebendieser Be-
trag über die Tastatur eingegeben werden kann. Ohne auf Einzelheiten der Befehle ein-
zugehen, will ich hier nur sagen, dass das printf()-Kommando der Ausgabe am Bild-
schirm dient, während Sie mithilfe des scanf()-Kommandos eine Eingabe über die
Tastatur tätigen können. Ist das erst einmal erledigt, wird natürlich umgerechnet, und
die Werte für Euro und Dollar werden am Bildschirm angezeigt.
Viele Worte für ein so kleines Programm, doch wer hier zum ersten Mal einem C-Pro-
gramm begegnet, braucht vielleicht etwas Zuspruch. Wie schon gesagt: Das eigentliche
Programmieren mit C werden Sie zu einem späteren Zeitpunkt erlernen; hier will ich Ih-
nen nur einen Eindruck vermitteln.

18 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

Auswahl
Bedingung
wahr falsch

Anweisungen Anweisungen

Abb. 2.3: Struktogramm zur Auswahl

Nichts gegen Sequenzen, aber das Leben hat nun mal nicht immer so einen ordentlichen
Ablauf, wie das eine schlichte Sequenz gerne hätte. Was ich Ihnen bisher gezeigt habe,
war zu unflexibel, zu starr, da der Ablauf ohne jede Variationsmöglichkeit vorgegeben
war. Sie brauchen sich nur einmal mein altes Beispiel der Gehaltsabrechnung und Lohn-
buchhaltung ins Gedächtnis zu rufen: Je nach Steuerklasse wird die Berechnung des
Nettogehalts verschieden ablaufen, also kann man sie nicht mithilfe einer starren Se-
quenz in ein Programm fassen. Dieses Problem löst das Prinzip der Auswahl, die die
Ausführung eines Schrittes von einer bestimmten Bedingung abhängig macht. Das ken-
nen Sie aus dem richtigen Leben – wenn Sie beispielsweise für die nächste Klausur ler-
nen, dann haben Sie gute Chancen, sie zu bestehen, wenn nicht, dann eben nicht. Es ist
Ihre Entscheidung, und ähnliche Entscheidungen beeinflussen auch den Ablauf eines
Programms. Wie so etwas grundsätzlich in einem Struktogramm aussieht, zeigt Ihnen
Abb. 2.3.
Es wird eine Bedingung gestellt und überprüft, ob sie gültig ist. Falls sie erfüllt ist, geht
das Struktogramm in den „wahr“-Zweig und führt die Anweisungen aus, die sich dort
versammeln, falls nicht, begibt es sich in den „falsch“-Zweig und tut das, was man dort
von ihm verlangt. Nur einer der beiden Zweige wird ausgeführt, ja nach dem Status der
Bedingung.
Das klingt nun ein wenig abstrakt, und deshalb zeige ich Ihnen ein konkreteres Beispiel:
einen sehr schlichten Algorithmus zur Steuerberechnung. Ausgehend vom Bruttoein-
kommen, das natürlich erst einmal eingegeben werden muss, gibt es zwei Fälle. Falls das
Bruttoeinkommen unter 100000 Euro liegt, soll der Steuersatz 30 % betragen, ansonsten
35 %. Das ist sicher kein Abbild der steuerrechtlichen Wirklichkeit, aber für unsere Zwe-
cke momentan ausreichend. Nach der Berechnung der Steuer soll dann das resultierende
Nettoeinkommen ausgegeben werden. Wie so etwas im Struktogramm aussieht, zeigt
Ihnen Abb. 2.4.

Nettogehalt
einlesen brutto

setze grenze = 100000,00

brutto < grenze ?


wahr falsch

steuer = 0,3 * brutto steuer = 0,35 * brutto


  


Ausgabe brutto, netto

Abb. 2.4: Struktogramm zur Auswahl

GDI01 19
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Sie werden vermutlich zugeben, dass dieses Struktogramm selbsterklärend ist, und ge-
nau das macht den Reiz der Struktogramm-Technik aus: Man kann sie leicht lesen und
leicht verstehen, sobald man die elementaren grafisch-syntaktischen Möglichkeiten ver-
standen hat. Anmerken sollte ich vielleicht noch, dass offenbar nach einer beendeten
Auswahl alles wieder gut ist und die nachfolgenden Anweisungen in jedem Fall durch-
geführt werden, egal in welchem Zweig der Auswahl man sich vorher befunden hat.
Und auch den Begriff der Anweisung sollten Sie nicht allzu eng sehen, denn es kann
durchaus passieren, dass Sie nur dann etwas erledigt wissen wollen, wenn die gegebene
Bedingung wahr ist. In diesem Fall lassen Sie einfach den Anweisungsteil des „falsch“-
Zweigs leer.
Sie sollten aber auch sehen, wie sich eine Auswahl in einem C-Programm niederschlägt.
/* netto.c – nettogehalt ausrechnen */

#include stdio.h

main()
{
float brutto, netto, grenze;
grenze  100000,00;
printf("Geben Sie den Bruttobetrag ein\n");
scanf("%f“ .&brutto
if (brutto  grenze)
{
steuer  0,3 * brutto;
}
else
{
steuer  0,35 * brutto;
}
printf("Ihr Bruttogehalt lautet %8.2f“,brutto
printf("Ihr Nettogehalt lautet %8.2f“,netto
}

Über die seltsame Form der Ausgabeanweisung brauchen Sie sich nicht zu grämen, das
werden Sie später noch genauer lernen; das ominöse „%8.2f“ ist nur eine Formatanwei-
sung, die angibt, in welchem Format die jeweilige Zahl ausgegeben werden soll, nämlich
mit acht Stellen, davon zwei nach dem Dezimalpunkt. Wichtiger ist hier die Formulie-
rung der Auswahl: Die Bedingung schreibt man in Klammern nach einem if, danach
schreibt man die Sequenz der gewünschten Anweisungen in ein Paar von Mengenklam-
mern, und alle Anweisungen, die bei nicht vorliegender Bedingung durchgeführt wer-
den sollen, finden Sie – wieder von Mengenklammern umschlossen – im sogenannten
else-Zweig. Vergessen Sie dabei nicht, dass die Sequenz innerhalb des if-oder des
else-Zweiges wiederum alles Mögliche enthalten kann, also zum Beispiel noch eine
Abfrage.

20 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

Eine Auswahl zwischen mehreren Möglichkeiten kann man mithilfe der if-Anwei-
sung realisieren. Sie hat die grundsätzliche Form
if (Bedingung)
Anweisungen 1
else
Anweisungen 2

Mehrere if-Abfragen können ineinander geschachtelt werden. Entsprechende Re-


geln gelten für die Darstellung in einem Struktogramm.

Ich will gar nicht erst so tun, als hätte ich Ihnen schon alles über die Kontrollstruktur
„Auswahl“ berichtet, aber das würde jetzt auch zu weit führen. Hier will ich Ihnen nur
einen Eindruck von den Prinzipien der strukturierten Programmierung vermitteln, und
deshalb gehen wir jetzt zur nächsten Kontrollstruktur über: der Wiederholung.
Die letzte der drei klassischen Kontrollstrukturen finden Sie jeden Tag auch ohne Com-
puter im täglichen Leben. Manche Dinge wiederholen sich eben immer und immer wie-
der, man steht morgens auf, geht ins Bad, frühstückt oder auch nicht und verlässt die
Wohnung. Diesen Ablauf müssen Sie offenbar nicht jeden Tag neu lernen, mit der Zeit
funktioniert das alles ohne weiteres Nachdenken automatisch, und auf dem gleichen
Prinzip der Wiederholung oder auch Iteration beruht auch die Kontrollstruktur, über
die ich jetzt reden will. Sie tritt in drei verschiedenen Varianten auf, die eigentlich alle
das Gleiche machen, weshalb ich Ihnen auch nur eine der drei Versionen vorstellen wer-
de: die Wiederholung als nicht abweisende Schleife.
Der Name hat einen Grund. Es geht hier um eine Reihe von Anweisungen, die vermut-
lich nicht nur einmal, sondern mehrfach hintereinander ausgeführt werden sollen. Na-
türlich will man sie nur einmal aufschreiben und dann in Rahmen eines Struktogramms
oder eines C-Programms deutlich machen, dass diese Sequenz von Anweisungen wie-
derholt durchgeführt werden soll. So etwas nennt man eine Schleife. Wollen Sie nun si-
cherstellen, dass die Sequenz in jedem Fall mindestens einmal durchgeführt wird, so
spricht man von einer nicht abweisenden Schleife. Wie kann man das realisieren? Ganz
einfach: mithilfe einer Bedingung. Die Sequenz soll nach dem ersten Durchlauf nur noch
dann wiederholt werden, wenn eine bestimmte Bedingung, die sogenannte Durchfüh-
rungsbedingung, erfüllt ist; ist das nicht der Fall, so hat sie ihre Daseinsberechtigung
verloren und Sie können zu anderen Dingen übergehen. Die prinzipielle Darstellung ei-
ner nicht abweisenden Schleife in einem Struktogramm sehen Sie in Abb. 2.5.

do-Schleife
Anweisung 1

Anweisung 2

Anweisung 3

Durchführungsbedingung

Abb. 2.5: Struktogramm zur nicht abweisenden Schleife

GDI01 21
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Der sogenannte Schleifenrumpf besteht hier aus drei Anweisungen, die auf jeden Fall
einmal durchgeführt werden und deren weitere Durchführung dann davon abhängt, ob
die am Ende geprüfte Durchführungsbedingung erfüllt ist. Falls ja, fängt alles von vorn
an, falls nein, ist die Schleife beendet und es geht nach dem Schleifenblock weiter. Man
kann an dem Struktogramm übrigens deutlich sehen, warum man diese Schleifenart
auch als fußgesteuerte Schleife bezeichnet, denn die alles entscheidende Durchfüh-
rungsbedingung steht nun mal unten, am Fuß der Schleife.
Auch hier will ich es nicht bei der abstrakten Beschreibung belassen, sondern Sie bitten,
sich einmal das Struktogramm in Abb. 2.6 anzusehen. Es ist nicht schwer zu verstehen,
was hier geschieht. Zunächst wird der Benutzer aufgefordert, eine natürliche Zahl ein-
zugeben. Nun bieten aber die meisten gebräuchlichen Programmiersprachen gar keine
Möglichkeit, sich auf natürliche Zahlen einzuschränken, sondern verfügen nur über so
etwas wie ganze Zahlen oder auch reelle Zahlen mit Nachkommastellen. Sie müssen
also damit rechnen, dass die Variable n, die im nächsten Schritt eingegeben wird, eine
ganze Zahl ist, also auch negativ sein kann. Deswegen finden Sie als nächsten Schritt
eine Auswahl: Ist die Zahl positiv oder nicht? Falls nein, ist die Sache an der Unfähigkeit
des Benutzers gescheitert, eine negative Zahl von einer positiven zu unterscheiden, und
er bekommt eine entsprechende Meldung. Falls aber doch, soll das Programm die Sum-
me der natürlichen Zahlen von 1 bis n berechnen – und das funktioniert mithilfe einer
nicht abweisenden Schleife.
Vor dem Start der Schleife wird einer Variablen namens summe der Wert 0 zugewiesen
und einer anderen Variable namens i der Wert 1. Dieses i dient als Zählvariable. Im
ersten Schleifendurchlauf wird zunächst die Anweisung summe  summe  i durch-
geführt. Das ist nicht mathematisch zu verstehen, sondern eine schlichte Wertzuwei-
sung: Der Variablen summe wird die Summe aus dem zugewiesen, was derzeit auf
summe und auf i steht. Damit beiden Variablen bisher noch nichts passiert war, hat
summe vor dieser Wertzuweisung den Inhalt 0 und i den Inhalt 1. Also wird summe
die Summe 01 zugewiesen, weshalb nach der ersten Wertzuweisung auf summe der
Wert 1 steht. In der nächsten Anweisung soll dann der Variablen i der um 1 erhöhte
bisherige Wert von i zugewiesen werden. Die mathematisch verstandene Gleichung
i  i  1 wäre natürlich übelster Blödsinn: Eine Zahl kann nicht die gleiche bleiben,
wenn man sie um 1 erhöht. Das ist aber auch mit der Anweisung ii1 nicht gemeint,
hier soll nur der Variablen i der um 1 erhöhte bisherige Wert der Variablen i zuge-
wiesen werden. Mit anderen Worten: Durch diese Anweisung wird \verb!i! einfach nur
um 1 erhöht und hat also nach dem ersten Schleifendurchlauf den Wert 2.
Nehmen wir jetzt einmal an, ich hätte am Anfang für n den Wert 3 eingegeben. Dann
wird offenbar die Bedingung in locker erfüllt, und das Programm geht in den nächs-
ten Schleifendurchlauf. Nun steht aber auf summe der Wert 1 und auf i der Wert 2,
also wird die erste Wertzuweisung im Schleifenrumpf dazu führen, dass auf summe jetzt
der Wert 1  2  3 steht. Daraufhin wird wieder i um 1 erhöht, erhält also den Wert 3
und kann noch immer den Vergleichstest mit der Variablen n bestehen, denn auch die
steht auf 3. Somit kommt es zu einem letzten Durchlauf meiner Schleife, noch einmal
werden die Anweisungen ihres Schleifenrumpfes durchlaufen. Was dabei passiert, ist
wohl klar: Der aktuelle Wert von i wird auf den aktuellen Wert von summe addiert,
das ergibt 3  3  6, und anschließend wird i traditionsgemäß um 1 erhöht. Damit ver-
lässt i allerdings den grünen Bereich der Schleifenbedingung, denn kaum einer wird
behaupten wollen, dass 4  3 gilt. Die do-Schleife ist also beendet und die Summe ist
berechnet.

22 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

Summe
Ausgabe: Bitte eine natürliche Zahl eingeben

einlesen n

n>0?
wahr falsch

summe = 0

i=1
Ausgabe: Hier liegt
summe = summe + i
keine natürliche Zahl vor
i = i+1

solange i <= n

Abb. 2.6: Struktogramm zur Summenbildung

Ich will es mit der C-Programmierung nicht übertreiben und mich hier auf die Bespre-
chung der Struktogramme beschränken. Nur eines noch: Warum stand in dem allgemei-
nen Struktogramm aus Abb. 2.5 der Begriff do-Schleife? Das liegt daran, dass in einem
C-Programm jede nicht abweisende Schleife mithilfe des do-Kommandos realisiert
wird. Im nachfolgenden Merksatz sage ich dazu noch eine Kleinigkeit.

Die do Schleife, auch nicht abweisende Schleife oder fußgesteuerte Schleife ge-
nannt, erlaubt es, einen Anweisungsblock wiederholt durchführen zu lassen, sofern
bestimmte Durchführungsbedingungen erfüllt sind. In C lautet das Schema der do
Schleife:
do
{
Anweisungen
}
while(Bedingungen);

Die Anweisungen im sogenannten Schleifenrumpf werden in jedem Fall mindestens


einmal ausgeführt. Entsprechende Regeln gelten für die Darstellung in einem Struk-
togramm.

Ich verschweige Ihnen nicht, dass es mit der abweisenden Schleife und der Zählschleife
noch zwei weitere Schleifenarten gibt, aber um einen Eindruck vom Prinzip der Schleife
zu bekommen, dürfte die nicht abweisende Schleife völlig ausreichen. Tatsächlich ist da-
mit die Einführung in die Prinzipien der strukturierten Programmierung auch schon be-
endet, und nach der einen oder anderen Übung kommen wir zu etwas anderem: der Ob-
jektorientierung.

Übung 2.1:
Formulieren Sie einen Algorithmus in Form eines Struktogramms, der je nach Aus-
wahl des Benutzers einen Eurobetrag in einen Dollarbetrag oder einen Dollarbetrag
in einen Eurobetrag umrechnet.

GDI01 23
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Übung 2.2:
Schreiben Sie ein Struktogramm, das den folgenden Algorithmus abbildet.
Es ist nach einem stark vereinfachten Verfahren die Steuer auf das monatliche Ein-
kommen auszurechnen. Das Einkommen ist über die Tastatur einzugeben. Die ersten
600 Euro sind steuerfrei. Die nächsten 600 Euro werden mit 30 % besteuert und der
Rest mit 40 %. Beträgt allerdings das gesamte monatliche Einkommen mehr als
15000 Euro, so sind statt 40 % 50 % zu veranschlagen.

Übung 2.3:
Schreiben Sie ein Struktogramm, das den folgenden Algorithmus abbildet.
In einem Labor sind Messreihen zu verarbeiten. Aus Erfahrung weiß man, dass keine
Werte anfallen, die über 1000 bzw. unter –1000 liegen und dass es mindestens einen
Messwert gibt. Dem Benutzer liegt nun eine Liste von Messwerten vor, die er in den
Computer eingeben soll, wobei die Eingabe beendet ist, sobald ein Wert über 1000
oder unter –1000 eingegeben wird. Es ist dabei zu beachten, dass dieser letzte Wert
nicht mehr zur eigentlichen Messreihe gehört, sondern nur dem Abbruch der Einga-
be dient, und dass der erste Wert der Messreihe in jedem Fall ein zulässiger Wert ist.
Das Programm soll dann folgende Größen berechnen und ausgeben:
• die Summe aller positiven Werte und den Durchschnitt dieser Werte
• die Summe aller negativen Werte und den Durchschnitt dieser Werte
• die Summe aller Werte und den Durchschnitt aller Werte

Übung 2.4:
Eine lineare Gleichung hat die Form ax  b  0 mit den bekannten Koeffizienten a
b
und b und der Unbekannten x. Die Lösung der Gleichung lautet x   , wobei man
a
auf die Sonderfälle, die entstehen können, wenn der eine oder andere Koeffizient den
Wert 0 annimmt, achten muss. Schreiben Sie einen Algorithmus in Form eines
Struktogramms, mit dessen Hilfe man eine beliebige Anzahl von linearen Gleichun-
gen lösen kann. Vor jeder neuen Gleichung soll der Benutzer gefragt werden, ob er
eine weitere Gleichung lösen will. Bei einer positiven Antwort wird eine Gleichung
eingegeben und gelöst, bei einer negativen wird das Programm beendet.

2.2 Objektorientierung
Man kann mit einem gewissen Recht sagen, dass ohne die Prinzipien der strukturierten
Programmierung, ohne die gerade besprochenen Kontrollstrukturen die Welt der Infor-
matik deutlich ärmer wäre. Trotzdem ist strukturierte Programmierung nicht alles, die
Welt hat sich auch nach der Erfindung der Struktogramme weitergedreht und neue Ide-
en sind aufgekommen. Eine davon, die heute eine große Rolle spielt, ist die Idee der Ob-
jektorientierung. Worum geht es dabei? Während wir uns bisher auf das sogenannte
prozedurale Programmieren konzentriert haben, bei dem die Aktivität, der Ablauf, die
Prozedur im Mittelpunkt des Interesses steht, kommt jetzt noch etwas hinzu. Jede Akti-
vität, jeder Vorgang hat einen direkten Bezug zu einem Objekt, das nun in den Fokus
rückt und meine Aufmerksamkeit beansprucht. Selbstverständlich müssen die Aktivitä-

24 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

ten selbst noch immer programmiert werden, und das geschieht noch immer nach den
Methoden der strukturierten Programmierung. Aber alles, was geschieht, wird bei der
objektorientierten Programmierung einen Bezug zu einem Objekt haben, wird seine
Eigenschaften ändern oder schlicht dafür sorgen, dass mit dem Objekt etwas passiert.
Was soll das heißen? Denken Sie beispielsweise an ein Auto und an die Aktivität des
Fahrens. Konzentriert man sich vor allem auf die Aktivität, so kann man darüber spre-
chen, dass Autos fahren oder Züge oder auch Heißluftballons – es geht vor allem um die
Aktivität, genau wie es bei der strukturierten Programmierung vor allem darum ging,
bestimmte Aktivitäten durch ein Struktogramm oder ein C-Programm abzubilden. Nun
ist aber der Fahrvorgang bei einem Auto ganz anders als bei einem Heißluftballon, und
diesen Unterschieden kann man Rechnung tragen, indem man die Aufmerksamkeit dem
Auto zuwendet und nur noch vom Fahren eines Autos spricht. Jede Aktivität wird – um
wieder allgemeiner zu sprechen – bei der objektorientierten Betrachtung einem be-
stimmten Objekt zugeordnet und kann nicht mehr freischwebend mal für diesen, mal
für jenen Träger verwendet werden.
Das bedeutet selbstverständlich, dass man dem zu schreibenden Programm genau sagen
muss, welche Objekte zulässig sind und welche Aktivitäten diese Objekte durchführen
dürfen, denn Aktivitäten ohne Objekte sind nicht mehr erlaubt. Und da man zum Bei-
spiel nicht jedem Auto einzeln sagen will, dass es fahren darf, wird man die Gesamtheit
aller Autos zur Menge aller Autos zusammenfassen, wobei jedes Mitglied dieser Menge
die Eigenschaft hat, fahren zu dürfen. Man spricht im Rahmen der objektorientierten
Programmierung allerdings nicht von Mengen, sondern von Klassen. Alles spielt sich
innerhalb von Klassen ab, und kein Datenelement, keine Aktivität kann sich diesem
Prinzip entziehen. Eine Klasse ist im Grunde genommen nichts weiter als eine abstrakte
Beschreibung eines Objekts mit seinen Daten und seinen möglichen Aktivitäten, und
das heißt, in einer Klassendeklaration wird in aller Regel beschrieben, wie die zukünfti-
gen Objekte aussehen sollen , die ich verarbeiten will, und was man mit ihnen anstellen
kann. Dass man Eigenschaften von Elementen zu einer Einheit zusammenfasst, ist
nichts Neues, das macht man auch im Alltag immer wieder. Aber dass man auch noch
die möglichen Aktionen gleich mit definiert, ist ein völlig neues Konzept mit weitrei-
chenden Konsequenzen, und genau das ist ein wesentlicher Punkt der Objektorientie-
rung. In einer einzigen Struktur, eben der Klasse, werden sowohl die Daten als auch die
Operationen vereinigt, die man auf diese Daten anwenden kann – ein Konzept, das man
oft als Kapselung bezeichnet. Dadurch wird sichergestellt, dass nur die Operationen, die
in einer Klasse definiert wurden, die Daten eines Objekts dieser Klasse ändern können,
sodass also der Zugriff auf Objekte einer Klasse ausschließlich mithilfe der vordefinier-
ten Aktionen, der sogenannten Methoden dieser Klasse, erfolgen soll.
Mir ist schon klar, wie abstrakt sich das anhört, aber gerade im Zusammenhang mit ob-
jektorientiertem Design muss man sich an ein wenig Abstraktion gewöhnen. Wie schon
bei der strukturierten Programmierung will ich Ihnen auch hier nur ein paar Grundzüge
zeigen, und natürlich werden wir nicht bei der abstrakten Beschreibung der Prinzipien
stehen bleiben. Da es sich bei der objektorientierten Programmierung um eine Program-
miermethode handelt, gibt es auch Programmiersprachen, die objektorientiert arbeiten,
und eine der bekanntesten dieser Sprachen ist Java. Die wenigen Programmierbeispiele,
die ich Ihnen im Folgenden zeige, sind in Java geschrieben.

GDI01 25
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Kehren wir also zurück zur Klasse der Autos, die ich aus naheliegenden Gründen mit
dem Namen Auto bezeichne. Ein Auto ist ohne Frage ein Objekt, und dieses Objekt hat
bestimmte Eigenschaften, die man aufschreiben kann. Es hat eine Marke, eine Farbe und
ein Gewicht, wie jeder andere Körper. Und dazu weist es noch eine bestimmte Höchst-
geschwindigkeit auf, die von Auto zu Auto verschieden ist. Jedes Auto hat diese Eigen-
schaften, also kann ich eine Klasse Auto bilden, die mir genau diesen Sachverhalt in
eine Java-Klasse übersetzt. Die Klasse lautet dann:
class Auto
{
String marke;
String farbe;
int gewicht;
int geschwindigkeit;
}

Der Name der Klasse fängt mit einem Großbuchstaben an, und das ist kein Zufall: Klas-
sennamen sollten Sie immer mit einem Großbuchstaben beginnen lassen, das ist zwar
nicht zwingend, aber eine weitverbreitete Konvention. Die Mitglieder dieser Klasse sind
dann die schon lange angekündigten Objekte. Objekte der gleichen Art, die man durch
die gleichen Eigenschaften beschreiben kann, werden eben in einer Klasse zusammen-
gefasst; die Klasse ist sozusagen der große Topf, in dem alle Objekte der gleichen Art
schwimmen. Man nennt deshalb die Objekte auch Instanzen der Klasse.
Die Eigenschaften der Objekte der Klasse Auto habe ich schon aufgelistet: Ein Auto wird
beschrieben durch Marke, Farbe, Gewicht und Geschwindigkeit. Im Programmtext habe
ich das ausgedrückt durch vier sogenannte Membervariablen oder auch Attribute, de-
ren einzige Rolle es ist, die nötigen Eigenschaften der Objekte aufzunehmen. Ich musste
daher die vier Attribute marke vom Typ String, farbe vom Typ String,
gewicht vom Typ int und schließlich geschwindigkeit vom Typ int anlegen:
Ohne Variablen keine Eigenschaften, wo sollte ich die Eigenschaften sonst hinschreiben?
Vermutlich wird es Sie nicht sehr wundern, dass eine Variable vom Typ int der Auf-
nahme einer ganzen Zahl, also einer Integer-Zahl dient, während eine Variable vom Typ
String Zeichenketten, also eben Strings, aufnehmen kann.

Nun habe ich also die Eigenschaften der Klasse Auto festgelegt, nach denen sich in Zu-
kunft jedes Auto richten muss. Die Klasse ist aber nur ein Sammelbehälter, eine Art Ga-
rage, in die jedes Auto hineinpasst; durch das Anlegen der Klasse wird noch kein einzi-
ges Objekt kreiert. Nur weil Sie eine Garage kaufen, wird sich noch kein einziges Auto
in die Garage verirren, es sei denn, Sie kaufen eines und fahren es hinein. Bei Objekten
ist das nicht anders, man muss sie erst anlegen, bevor es sie gibt. Ein Objekt der Klasse
Auto können Sie nun in der Programmiersprache Java durch

Auto meinAuto;

deklarieren, aber damit existiert es genau genommen immer noch nicht, sondern Sie tei-
len nur mit, dass meinAuto ein Objekt der Klasse Auto werden soll. Ist das Objekt
meinAuto auf diese Weise erst einmal deklariert, also angekündigt, genügt das Kom-
mando
meinAuto  new Auto();

26 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

um das gewünschte Objekt tatsächlich mit dem benötigten Speicherplatz zu versehen


und damit das eigentliche Anlegen zu erledigen. Der erste Teil ist also die Deklaration,
in der ein Objekt angekündigt wird, der zweite dagegen die echte Definition, in der das
Objekt konkret erzeugt wird. Sie können aber auch alles in einem Schritt erledigen, in-
dem Sie gleich
Auto meinAuto  new Auto();

schreiben und auf diese Weise Deklaration und Definition auf einmal hinter sich brin-
gen. Dass der Objektname hier mit einem kleinen Buchstaben beginnt, ist übrigens kein
Zufall: Das sollten Sie immer so machen, auch wenn es sich nur um eine Konvention
handelt.
Das macht einen etwas umständlichen Eindruck, aber man kann sich daran gewöhnen.
Wichtig ist, dass Sie erst eine Klasse definieren müssen, und dann durch Deklaration
und Definition einzelne Objekte dieser Klasse anlegen können.
Nehmen wir also an, Sie haben erstens die Klasse Auto deklariert und zweitens das
konkrete Autoobjekt meinAuto angelegt. Dann ist das bisher nichts weiter als ein Auto
ohne Eigenschaften, und wir sollten ihm schnellstens ein paar Eigenschaften verleihen.
Das geht ganz einfach mit der sogenannten Punktnotation. Das Autoobjekt heißt
meinAuto das erste Attribut heißt marke. Wenn es sich nun um ein Auto der Marke
„Opel“ handeln soll, dann können Sie dem Objekt diese Eigenschaft mit dem Kommando
meinAuto.marke"Opel";

zuweisen. Natürlich funktioniert das mit den anderen Attributen nicht anders, sodass
Sie durch die Angaben
meinAuto.marke"Opel";
meinAuto.farbe"blau";
meinAuto.gewicht1300;
meinAuto.geschwindigkeit180;

einen blauen Opel definiert haben, der 1300 Kilogramm wiegt und mit maximal 180 Ki-
lometern pro Stunde auf der Autobahn fährt.
Schön und gut, aber was nützen mir diese Angaben, wenn ich sie zwar in die Klasse hi-
neinbringe, aber nie mehr wieder heraus? Das kann nicht passieren, auch davor bewahrt
mich die Punktnotation. Will ich beispielsweise die Farbe meines Autos meinAuto wis-
sen, brauche ich nur das Java-Kommando
System.out.println(meinAuto.farbe);

abzusetzen, und schon wird der entsprechende String farbe von meinAuto am Bild-
schirm ausgegeben. Das eher unschöne System.out.println entspricht dabei dem
printf, das Sie schon bei unseren C-Programmen gesehen haben.

GDI01 27
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Eine Klasse wird in der Programmiersprache Java deklariert durch die Angabe eines
Klassennamens und von Membervariablen oder auch Attributen in der Form
class Klassenname
{
Attribut 1;
Attribut 2;
.......
}

Dabei sollte der Klassenname mit einem Großbuchstaben anfangen, was aber nicht
zwingend ist.
Ein Objekt der Klasse wird deklariert durch
Klassenname objektname;

und vollständig definiert, also mit Speicherplatz versehen, durch


objektname  new Klassenname();

Beide Arbeitsgänge lassen sich in dem einen Kommando


Klassenname objektname  new Klassenname();

vereinigen. Man bezeichnet ein Objekt einer Klasse auch als eine Instanz der Klasse.
Auf die Attribute eines angelegten Objektes kann man mit der Punktnotation
objektname.Attribut zugreifen.

Das war noch nicht alles, was ich Ihnen über Klassen und Objekte zu sagen habe. Um
weiterzukommen, muss ich aber erst einmal einen neuen Begriff einführen: die Metho-
de. Methoden dienen dazu, die Aktivitäten zu beschreiben, die mit meinen Objekten
möglich sind, denn bisher habe ich ja nur Daten beschrieben und nicht gesagt, was man
damit machen kann. Was kann ich zum Beispiel mit meinen Autoobjekten anstellen? Es
handelt sich nicht um die echten Autos, sondern nur um eine Autoverwaltung im Com-
puter, das dürfen Sie nicht vergessen. Trotzdem wäre es sinnvoll, zu jedem Auto unbü-
rokratisch eine Beschreibung ausgeben oder seine Farbe ändern zu können. Kein Prob-
lem, wird sofort erledigt, indem ich zwei Methoden definiere, die mir diese Arbeit
abnehmen. Damit Sie sehen, wie eine Methode in eine Klasse integriert wird, zeige ich
Ihnen hier die komplette um die beiden Methoden erweiterte Klasse.
class Auto
{
String marke;
String farbe;
int gewicht;
int geschwindigkeit;

String beschreiben()
{
return "Der Auto ist ein "  marke  ", seine Farbe ist "
 farbe  ", sein Gewicht betraegt "  gewicht
 " Kilogramm, und seine Geschwindigkeit betraegt "
 geschwindigkeit  ".";

28 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

}
}

Eigentlich passiert auch hier nicht viel Neues. Methoden sind Funktionen, die man auf
ein Objekt anwendet. Die Methode beschreiben() verlangt keinen Parameter, kei-
nen Input, da sie sich nur auf das aktuelle Auto und sonst auf gar nichts bezieht; also ist
ihre Parameterliste leer. Dagegen soll sie eine das Auto beschreibende Zeichenkette aus-
geben, braucht also den Rückgabetyp String. Dieses String bedeutet daher nur, dass
die Methode beschreiben() eine Zeichenkette liefern soll. Im sogenannten Metho-
denrumpf wird dann angegeben, was die Methode zu tun hat: Sie soll eine Beschreibung
des Autos liefern, um das es gerade geht, indem sie seine Eigenschaften in einem Satz
zusammenfasst und an die aufrufende Stelle zurückgibt. Beachten Sie dabei, dass ich
hier nicht die Punktnotation wie zum Beispiel meinAuto.marke verwendet, sondern
einfach nur marke geschrieben habe. Das muss auch so sein, denn ich habe ja noch gar
kein Objekt angelegt. Zurzeit beschreibe ich noch die Klasse mit ihren Membervariablen
und ihren Methoden, Objekte der Klasse existieren noch nicht. So etwas wie return
marke bedeutet also nur: Wenn für irgendein Objekt später mal diese Methode aufge-
rufen werden sollte, dann gib bitte die Marke des Autos zurück, um das es dann gehen
wird.
Was kann man denn nun anfangen mit einer solchen Klasse? Eine Klasse der vorgestell-
ten Art bildet nur einen Rahmen, ein Muster, nach dem sich alle Objekte zu richten ha-
ben, aber sie ist kein Programm, das man ausführen kann. In der Klasse Auto lege ich
die Struktur meiner Autodaten – das sind die Attribute – und die mit den Autodaten
möglichen Operationen – das sind die Methoden – ab. Ich führe aber noch keine kon-
krete Verarbeitung durch, ich sage nur, wie Verarbeitungen auszusehen haben, wenn sie
irgendwann einmal durchgeführt werden.
Nach diesem Prinzip läuft es eigentlich immer. Sie definieren innerhalb einer grundle-
genden Klasse, wie Ihre Datenstrukturen aussehen und welche Operationen damit er-
laubt sein sollen. Wer auch immer dann mit Ihrer Autoklasse und ihren Objekten han-
tieren will, muss sich nach dem richten, was Sie vorher in der Klasse Auto festgelegt
haben. Jede weitere Verarbeitung von Autoobjekten muss mit den Methoden auskom-
men, die in der Autoklasse zur Verfügung gestellt werden. Das ist einerseits eine Ein-
schränkung, weil man sich an dem vorhandenen Methodenbestand orientieren muss.
Andererseits ist es aber auch ein ungeheurer Vorteil, denn wenn diese Methoden anstän-
dig dokumentiert sind, muss man sich für weitere Entwicklungen nicht mehr darum
kümmern, wie sie programmiert sind: Der Entwickler muss dann nur noch wissen, wel-
che Parameter sie brauchen und was sie liefern; ihr Innenleben interessiert nicht mehr.
Gerade das macht die objektorientierte Programmierung für größere Projekte so inter-
essant. Ein Entwickler programmiert eine bestimmte Klasse, legt die Datenstrukturen
und Methoden fest und dokumentiert Input- und Outputverhalten der Methoden. Jeder
andere Entwickler kann dann auf diese Klasse, ihre Objekte und ihre Methoden zurück-
greifen, ohne sich Gedanken darüber machen zu müssen, wie die Methoden nun im Ein-
zelnen programmiert sind. Um also beispielsweise die Eigenschaften eines Autos auszu-
geben, muss das Hauptprogramm auf die oben definierte Methode beschreiben()
zurückgreifen, die genau diese Eigenschaften liefert. Er darf streng genommen die
Punktnotation für die Attribute im Rahmen eines Hauptprogramms gar nicht anwen-
den, weil er damit das Prinzip, sich nur über die vordefinierten Methoden den Objekten
zu nähern, außer Kraft setzen würde. Und weil in genau diesem Prinzip die Hauptidee
der Kapselung besteht, sollte man sich tunlichst auch daran halten.

GDI01 29
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Eine Methode ist eine Funktion, die immer für ein bestimmtes Objekt ausgeführt
wird. Sie wird innerhalb der Klassendeklaration festgelegt und kann dann mithilfe
der Punktnotation für jedes Objekt dieser Klasse ausgeführt werden.
Innerhalb einer Klasse legt man also die Datenstruktur und die erlaubten Methoden
fest. Von anderen Klassen aus kann dann mithilfe der Methoden mit den konkreten
Objekten der ursprünglichen Klasse gearbeitet werden.

Wenn Sie also ein konkretes Objekt namens meinAuto angelegt haben, dann können
Sie in der Programmiersprache Java die Beschreibung dieses Autos durch das Komman-
do
System.out.println(meinAuto.beschreiben())

am Bildschirm ausgeben lassen – und auf keine andere Weise.


Nur eine Bemerkung noch, dann können wir den Bereich der objektorientierten Pro-
grammierung wieder verlassen. Die eine Methode, die ich Ihnen hier gezeigt habe, war
natürlich ausgesprochen einfach. Das muss nicht immer so sein. Sie müssen sich nur
vorstellen, dass Ihre Objekte größere Zahlenfelder sind, sodass in jedem Objekt eine Rei-
he von Zahlen abgespeichert werden. In so einem Fall kann man sich auch wesentlich
komplexere Methoden vorstellen: Man könnte zum Beispiel in einer Methode die Be-
rechnung der Summe aller im Objekt versammelten Zahlen programmieren oder auch
eine Sortierung der Größe nach. Und selbstverständlich kommt dann beim Entwurf sol-
cher Methoden wieder all das zum Tragen, was ich Ihnen im letzten Abschnitt über
strukturierte Programmierung berichtet habe. Die objektorientierte Programmierung
kann somit die strukturierte Programmierung keineswegs ersetzen, sondern sie verwen-
det sie, um die eigenen Methoden entwerfen zu können.
Natürlich habe ich Ihnen hier nur die Ansätze der Objektorientierung vorstellen können.
Auch das ist ein weites Feld, über das ganze Bücher geschrieben werden. Aber die
Grundidee der Klassen, Objekte und Methoden sollte Ihnen klar geworden sein.

Übung 2.5:
Schreiben Sie eine Java-Klasse Linear, mit der linerare Gleichungen der Form
ax  b  0 und ihre Lösungen modelliert werden sollen. Die Klasse soll zwei Attri-
bute a und b für die beiden Koeffizienten der Gleichung enthalten; der in der Spra-
che C als float bezeichnete Datentyp heißt hier double. Weiterhin soll es eine
Methode loesen()geben, mit der die Lösung einer Gleichung berechnet wird. Sie
können sich dabei auf den Fall beschränken, dass a und b von null verschieden sind.

Übung 2.6:
Entwerfen Sie eine Klasse Rechteck, die zur Modellierung beliebiger Rechtecke
dient. Welche Attribute muss diese Klasse haben? Welche Methoden brauchen Sie,
wenn Fläche und Umfang des jeweiligen Rechtecks berechenbar sein sollen? Wie
kann man dann von einer anderen Klasse aus diese beiden Größen berechnen? Mit
Ausnahme der letzten Frage kann die Aufgabe verbal beantwortet werden.

30 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

Übung 2.7:
Erweitern Sie die Klasse Linear aus Übung 2.5 um eine Methode
kommentieren(), die eine Zeichenkette erzeugt. Es soll sich dabei um einen Kom-
mentar handeln, der die Fälle a  0 und b  0 bzw. a  0 und b  0 beschreibt. Die
nötige Auswahl hat in Java die gleiche Struktur wie in der Programmiersprache C,
die Abfrage, ob a  0 gilt, lautet if (a  0).

2.3 Komplexität
Bisher habe ich mich vor allem mit der Frage beschäftigt, wie man ein gegebenes Prob-
lem mithilfe eines passenden Algorithmus lösen kann. Das ist aber nicht alles. Es kann
nämlich vorkommen, dass es verschiedene Algorithmen zur Lösung des gleichen Prob-
lems gibt, so wie es beispielsweise mehrere Möglichkeiten gibt, mit dem Auto von Ihrem
derzeitigen Standort an einen anderen Ort Ihrer Wahl zu gelangen. Bei einer Autofahrt
wird man den Weg wählen, der nach irgendeinem Kriterium der günstigste ist: den kür-
zesten Weg, den bequemsten oder auch den mit dem geringsten zu erwartenden Sprit-
verbrauch. Bei Algorithmen ist es ähnlich, denn Sie werden natürlich unter verschiede-
nen zur Verfügung stehenden Algorithmen den mit dem geringsten
Ressourcenverbrauch einsetzen.
Aber was bedeutet das? Üblicherweise unterscheidet man zwischen zwei Arten von Res-
sourcen: der verbrauchten Zeit und dem benötigten Speicherplatz. Entsprechend werden
auch zwei Arten von Komplexität unterschieden. Untersucht man, wie viel Zeit ein Al-
gorithmus-Durchlauf verbraucht, so spricht man von Zeitkomplexität; will man hinge-
gen wissen, wie viel Speicherplatz bei einem Durchlauf des Algorithmus verbraucht
wird, so redet man über die Speicherkomplexität. Natürlich hängt beides von der Men-
ge bzw. der Größe der verarbeiteten Daten ab: Wenn Sie nur eine Gehaltsabrechnung
vornehmen, dann geht das schneller als bei 10000 Abrechnungen, und wenn Sie die
Quadratwurzel aus 16 berechnen wollen, so muss man davon ausgehen, dass das weni-
ger Zeit verbraucht als die Bestimmung der Wurzel aus 1522756. Es wird also einen Pa-
rameter n geben, der die jeweilige Komplexität beeinflusst. Am Beispiel der Zeitkomple-
xität werde ich Ihnen das jetzt einmal zeigen.

GDI01 31
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

Primzahl
Ausgabe: Eingabeaufforderung für
eine natürliche Zahl n>2

einlesen n

setze i=2

prüfe: ist n durch i teilbar?

setze i = i+1

solange i < n und


keine Teilbarkeit vorliegt

Lag Teilbarkeit
vor?
wahr falsch

Ausgabe: Ausgabe:
n ist keine Primzahl n ist Primzahl

Abb. 2.7: Struktogramm zum Primzahltest

Bei der Verschlüsselung von Daten ist man immer wieder auf Primzahlen angewiesen,
also auf von 1 verschiedene natürliche Zahlen, die nur durch 1 und durch sich selbst teil-
bar sind. Es gibt leider keine Formel, mit der man Primzahlen ausrechnen könnte, und
deshalb haben Sie, sofern Sie testen wollen, ob eine gegebene Zahl n eine Primzahl ist,
keine Wahl: Sie müssen durch alle Zahlen teilen, die als Teiler überhaupt infrage kom-
men, also durch alle natürlichen Zahlen größer als 1 und kleiner als n. Falls keine dieser
Divisionen aufgeht, lag eine Primzahl vor; falls Sie dagegen einmal erfolgreich dividiert
haben, ist Ihr n nur eine ganz gewöhnliche Zahl und keine Primzahl. Wie man diese ver-
bale Beschreibung in ein Struktogramm umsetzt, sehen Sie in Abb. 2.7.
Was genau passiert hier? Sie geben eine Zahl n  2 ein und testen mithilfe einer Schleife,
ob dieses n durch irgendetwas teilbar ist. Zu diesem Zweck beginnen Sie mit dem po-
tenziellen Faktor i  2 und erhöhen, solange keine Teilbarkeit vorliegt, dieses i so lange
um 1, bis entweder die Division von n durch i aufgeht oder aber i nicht mehr kleiner als
n ist und somit den Wert n erreicht hat. Die nachfolgende Ausgabe gibt dann je nach
erzieltem Ergebnis bekannt, ob es sich bei n um eine Primzahl handelt oder nicht. Die
Frage ist nun: Wie viele Schritte braucht dieser Algorithmus im schlimmsten Fall? Of-
fenbar tritt der schlimmste Fall genau dann ein, wenn die Schleife nicht deshalb vorzei-
tig abgebrochen werden kann, weil eben Teilbarkeit vorlag, also wenn n eine Primzahl
ist. Da ich aber im Vorhinein nicht wissen kann, ob oder wann eine der Testdivisionen
aufgehen wird – sonst bräuchte ich den Algorithmus gar nicht – muss ich davon ausge-
hen, dass die Schleife vollständig durchlaufen wird, und das heißt, dass ich alle Zahlen
zwischen i  2 und i  n – 1 prüfe. Das ergibt n – 2 Durchläufe. Vor der Schleife habe
ich noch eine Eingabeaufforderung und eine Eingabe, nach der Schleife finden Sie eine
Auswahl, in der eine Ausgabe vorgenommen wird. Das sind also noch einmal 4 Opera-
tionen, sodass ich insgesamt auf n  2 Aktionen komme. Innerhalb jedes Schleifen-
durchlaufs muss ich allerdings testen, ob n durch i teilbar ist, und dann i um 1 erhöhen,
was mindestens zwei Operationen darstellt. Der Einfachheit halber gehe ich hier davon
aus, dass der Test auf Teilbarkeit sich wirklich in einer einzigen Operation durchführen
lässt, die nicht länger dauert als alle anderen Operationen wie Eingabe oder Hochsetzen
einer Variablen. Das bedeutet, dass ich bei meinen Schleifendurchläufen 2 · (n – 2)  2n
– 4 Operationen durchführen muss und somit der Testalgorithmus mit insgesamt 2n –
4  4  2n Operationen zu Buche schlägt.

32 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

Primzahl
Ausgabe: Eingabeaufforderung für
eine natürliche Zahl n>2

einlesen n

setze i=2

prüfe: ist n durch i teilbar?

setze i = i+1

solange i*i <= n und


keine Teilbarkeit vorliegt

Lag Teilbarkeit
vor?
wahr falsch

Ausgabe: Ausgabe:
n ist keine Primzahl n ist Primzahl

Abb. 2.8: Struktogramm zum verbesserten Primzahltest

Sie sehen hier deutlich die Abhängigkeit der Anzahl der nötigen Operationen von der
Eingabegröße n, denn je größer die Zahl n ist, desto mehr Operationen wird der Algo-
rithmus benötigen und desto mehr Zeit wird er brauchen. Geht man davon aus, dass alle
Operationen wie Eingabe, Ausgabe, Addition oder auch Teilbarkeitsprüfung im Wesent-
lichen den gleichen Zeitbedarf haben, so kann man folgern, dass der Algorithmus zum
Primzahltest 2n Zeiteinheiten verbrauchen wird. Man macht es sich aber üblicherweise
noch einfacher und sagt schlicht, der Algorithmus habe eine Zeitkomplexität der Grö-
ßenordnung n, was man mit der Abkürzung O(n) beschreibt. Wenn Sie also einem Al-
gorithmus mit der Zeitkomplexität O(n) begegnen, dann ist damit nur gemeint, dass er
– abhängig vom Parameter n – etwa c · n Zeiteinheiten für sich beanspruchen kann, wo-
bei c eine feste Zahl ist, die nicht von n abhängt. Es ist also ganz egal, ob die konkrete
Zahl der Operationen bei 2n, bei 3n  4 oder gar bei 7n – 6 liegt, man hat es in jedem
Fall mit der Zeitkomplexität O(n) zu tun. Konstante Summanden bei der Anzahl der
Operationen wie in 3n  4 oder in 7n – 6 vernachlässigt man gerne, weil sie für große
Werte von n ohnehin keine Rolle mehr spielen: Ob Sie nun 300004 oder 300000 Opera-
tionen durchzuführen haben, kann Ihnen ziemlich egal sein.

Ist T(n) die von n abhängige Laufzeit eines Algorithmus und gibt es eine Zahl c  0,
sodass für alle n die Beziehung T (n )  c  n gilt, so schreibt man kurz T(n)  O(n) und
sagt, die Zeitkomplexität ist von der Größenordnung O(n).
Das Symbol O(n) wird als ein Landau-Symbol bezeichnet.

Selbstverständlich gibt es auch noch andere Größenordnungen wie z. B. O(n2) oder


O(n3), je nach Problemlage. Es kann aber auch sein, dass man die Komplexität O(n) noch
etwas verbessern kann, wie Sie anhand des Struktogramms aus Abb. 2.8 feststellen kön-
nen. Dem ersten Algorithmus habe ich nämlich zu viel Arbeit zugemutet. Nehmen Sie
als Beispiel die Primzahl n  29. Wenn Sie die potenziellen Teiler 2,3,4 und 5 getestet ha-
ben, können Sie schon aufhören, Sie müssen das Spiel gar nicht bis hin zu 28 fortsetzen.
Warum? Die Quadratwurzel aus 29 liegt zwischen 5 und 6. Sollte sich nun ein Faktor
größer als 5 finden, durch den man 29 ohne Rest teilen kann, dann muss das Divisions-

GDI01 33
© HfB, 02.12.20, Berk, Eric (904709)

2 Umsetzung von Algorithmen

ergebnis auf jeden Fall unter 5 liegen, denn wenn Sie durch eine Zahl größer als 29
teilen, dann kann nur ein Ergebnis unter 29 herauskommen, sonst würde das Produkt
der beiden Faktoren über 29 liegen. Der Faktor unterhalb von 6 müsste in diesem Fall
dem Algorithmus also schon lange aufgefallen sein, und da er das nicht ist, hat es ihn
nie gegeben. Es ist daher völlig ausreichend, die Schleife nur laufen zu lassen, solange
i  n ist oder – anders gesagt – solange i 2  n gilt. Genau diese Durchlaufbedingung
macht den einzigen Unterschied zwischen dem ersten und dem zweiten Algorithmus
aus, wie Sie an den Struktogrammen unschwer ablesen können. Ich muss deshalb die
Schleife deutlich seltener laufen lassen, nur noch bis maximal n und nicht mehr bis
n, und das kann bei großen Werten von n schon einen Unterschied ausmachen. Natür-
lich wird man nun sagen, der verbesserte Algorithmus hat eine Zeitkomplexität von
O ( n ) und ist somit dem ursprünglichen Algorithmus vorzuziehen, weil er schneller
läuft.
Wie Sie gesehen haben, kann es also sehr verschiedene Komplexitäten geben, die man
dann mit der O-Notation ausdrückt. Ob O(n), O ( n ) , ob O (n 2 ) , O (n 3 ) oder gar
O (log2 n ) , sie alle beschreiben, wie viel Zeit beim Durchlauf eines Algorithmus ver-
braucht wird, also seine Zeitkomplexität. Sobald man also weiß, dass die Zeitkomplexi-
tät T(n) eines bestimmten Algorithmus kleiner oder gleich c  g (n ) ist, wobei c eine feste
Zahl und g(n) eine Funktion wie g (n )  n oder g (n )  n 2 darstellt, kann man stets die
Aussage T(n)  O(g(n)) treffen.

Sind f(n) und g(n) auf den natürlichen Zahlen definierte Funktionen mit positiven
Werten, so gilt f(n)  O(g(n)), falls es eine konstante Zahl c gibt, sodass
f (n )  c  g (n ) für alle hinreichend großen natürlichen Zahlen n gilt.

Bedenken Sie also: Auch für T(n)  3n2  16 ist T(n)  O(n2), weil ab n  4 in jedem Fall
3n 2  16  4n 2 gilt.

Übung 2.8:
Gegeben sei der Algorithmus, der in dem Struktogramm aus Abb. 2.6 beschrieben
wird.
a) Bestimmen Sie die größtmögliche Anzahl der Operationen, die der Algorithmus
ausführen muss.
b) Welche Zeitkomplexität T(n) weist der Algorithmus in Abhängigkeit von der
Eingabe n auf, wenn man davon ausgeht, dass jede Operation gleich lang dauert?
Beschreiben Sie die Zeitkomplexität mit einem Landau-Symbol.

34 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Umsetzung von Algorithmen 2

Übung 2.9:
Es seien A und B zwei Algorithmen, die das gleiche Problem lösen. Für Eingaben mit
der Eingabegröße n benötigt Algorithmus A 500n2 – 16 Operationen, während Al-
1 11
gorithmus B n 3  n  7 Operationen braucht. Jede Operation dauert gleich lang.
2 2
a) Beschreiben Sie die Zeitkomplexität von A und von B mit einem Landau-Sym-
bol.
b) Welchen Algorithmus sollte man wählen, um das gegebene Problem bei einer
Eingabegröße von n  256 zu lösen?
c) Welchen Algorithmus sollte man wählen, wenn das Problem immer eine Einga-
begröße von mindestens n  1024 aufweist?

GDI01 35
© HfB, 02.12.20, Berk, Eric (904709)

3 Rechner
Hier lernen Sie einige Vorläufer der heutigen Computer kennen, nämlich die
Analytische Maschine, die Turing-Maschine und die Von-Neumann-Architektur,
und Sie erfahren etwas über heutige Programmiersprachen und Betriebssysteme.

Bisher habe ich Ihnen gezeigt, worin die grundlegenden Probleme der Informatik beste-
hen, und Sie haben einen ersten Einblick in die Welt der Algorithmen gewonnen. Wie
Sie feststellen konnten, denkt man sich Algorithmen vor allem deshalb aus, weil man
Probleme mit ihnen lösen möchte, und dieser schlichte Umstand hat eine ebenso schlich-
te Folgerung: Irgendwo und irgendwie muss man diese Algorithmen auch durchführen
können, ohne alles selbst von Hand machen zu müssen, und zu diesem Zweck braucht
man einen Computer, einen Rechner. Rechenmaschinen gab es schon sehr früh, sofern
man den antiken Abakus dazurechnen will. Raffinierter waren dann die mechanischen
Rechenmaschinen aus dem 17. Jahrhundert, die beispielsweise Wilhelm Schickard, Blai-
se Pascal und Gottfried Wilhelm Leibniz gebaut haben, wobei Leibniz übrigens in die-
sem Zusammenhang das System der Dualzahlen zur Basis 2 erfand, das Sie im vierten
Kapitel noch beschäftigen wird.
Aber all diese Geräte waren eben nur Rechenmaschinen, die dem Benutzer zwar die
Durchführung der Grundrechenarten abnehmen konnten, mehr aber auch nicht. Wie
kam man denn eigentlich zum Prinzip der programmierbaren Maschine, die Algorith-
men ausführen kann? Ich will Sie hier nicht mit der gesamten Geschichte der Informatik
behelligen; das ist ein weites und interessantes Feld, aber man muss es mit der Historie
auch nicht zu weit treiben. Vorstellen werde ich Ihnen aber drei wichtige Konzepte, ei-
nes aus dem 19. und zwei aus dem 20. Jahrhundert, auf denen auch heute noch die Ar-
chitektur moderner Computer beruht: die Analytische Maschine von Charles Babbage,
die Turing-Maschine von Alan Turing und die Von-Neumann-Architektur. Die beiden
Maschinen wurden aus verschiedenen Gründen nie gebaut, aber ihre Konzepte sind
noch immer wichtig und einflussreich und haben beispielsweise die Von-Neumann-Ar-
chitektur beeinflusst, auf der auch die heutigen Computer basieren.

3.1 Die Analytische Maschine


Einerseits war der englische Mathematiker Charles Babbage 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 neh-
men – Verhältnisse, die einen deutschen Professor von heute mit leisem Neid erfüllen.
Andererseits war er recht erfolglos, weil er das große Projekt seines Lebens, die Konst-
ruktion einer programmgesteuerten Rechenmaschine, also eines Universalrechners zur
Lösung beliebiger algorithmischer Probleme, nie verwirklichen konnte, obwohl er drei-
ßig Jahre lang 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 aus dem 17. Jahrhundert waren reine Rechenmaschinen; Babbage wollte
mehr. Er wollte eine Analytische Maschine bauen, die von Fall zu Fall programmiert
werden konnte, um an flexiblen Eingabedaten ebenso flexible Berechnungen vorneh-
men zu können. Falls Sie das an die Funktion eines modernen Computers erinnert, ha-
ben Sie völlig recht, Babbage hatte nichts anderes vor, als einen richtigen Computer auf
rein mechanischer Basis zu konstruieren.

36 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Rechner 3

Erstaunlicherweise entsprach nicht nur sein Ziel einem modernen Computerbegriff,


sondern er dachte sich auch ein Konstruktionsprinzip aus, das stark heutigen Konstruk-
tionen ähnelt. Er hatte ein vollautomatisches Rechenwerk für die vier Grundrechenarten
vorgesehen, das er mithilfe von Zahnrädern realisieren wollte, allerdings auf dezimaler
Basis und nicht zur Basis 2. Die zu verarbeitenden Zahlen, die auszugebenden Zahlen
und die Zwischenergebnisse sollten in einem separaten Zahlenspeicher gehalten wer-
den, in den immerhin 1000 Zahlen zu je 50 Stellen passten. Es gab ein auf Lochkarten
basierendes Eingabegerät und ein Ausgabegerät, 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 Gegen-
satz zu den alten Rechenmaschinen sollte die Analytische Maschine also nicht nur die
Funktion eines verbesserten Abakus übernehmen, sondern Babbage wollte echte flexible
Programme zur Verarbeitung seiner Eingabedaten schreiben können, bis hin zu Ver-
zweigungen in den Programmen, die je nach Eingabedaten verschiedene Verarbeitungs-
möglichkeiten eröffneten. Dabei war vorgesehen, die Programmierung auf Lochkarten
festzuhalten, also auf kleinen Pappkarten, in die Löcher eingestanzt werden, wobei ver-
schiedene Lochkombinationen verschiedene Informationen enthalten.
Die Idee war revolutionär und hat in den heutigen Computern überlebt. Leider war die
Durchführung mit den damaligen technischen Mitteln unmöglich, viele Jahre nach Bab-
bage hat man aber anhand seiner Pläne mit moderneren technischen Mitteln seine Ana-
lytische Maschine nachgebaut und dabei festgestellt, dass seine Konstruktion in jeder
Hinsicht funktionsfähig gewesen wäre; Babbage hatte seine guten Ideen nur ein wenig
zu früh gehabt. Für die Idee einer Allzweckmaschine, bei der die Grundrechenarten fest
in der Mechanik der Zahnräder installiert waren und die eigentlichen Instruktionen, die
Programmanweisungen, über Lochkarten der Maschine vermittelt wurden und dann
durch ein kompliziertes Hebelsystem in mechanische Vorgänge umgesetzt werden
konnten – für 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 er-
setzen konnte, rückten solche Ideen in die Nähe des Möglichen.

Die Analytische Maschine von Charles Babbage war, obwohl sie aufgrund mechani-
scher Probleme nie gebaut wurde, ein Vorläufer der modernen programmgesteuerten
Computer. Ihr Konstruktionsplan sah neben einer Ein- und einer Ausgabevorrich-
tung ein Rechenwerk, einen Speicher und ein Steuerwerk vor, das die Operationen
des Programms koordinieren sollte.

Übung 3.1:
Lochkarten wurden in der frühen Zeit der Datenverarbeitung oft zur Speicherung
von Daten verwendet. Diskutieren Sie die Vor- und Nachteile von Lochkarten als
Datenspeicher.

Übung 3.2:
Diskutieren Sie die Auffassung des Logikers Kurt Gödel, Programmiersprachen sei-
en unnötig, Logik genüge völlig.

GDI01 37
© HfB, 02.12.20, Berk, Eric (904709)

3 Rechner

3.2 Die Turing-Maschine


Babbages Analytische Maschine wurde nie gebaut, weil die Technik seiner Zeit noch
nicht so weit war, seine Ideen zu realisieren. Auch eine Turing-Maschine hat bis heute
noch niemand gebaut, aber der Grund ist ein ganz anderer: Sie ist eine rein theoretische
Konstruktion, die man grundsätzlich nicht bauen kann und die vor allem dazu dient,
Grundprinzipien der Berechenbarkeit zu verdeutlichen und zu untersuchen. Eine Tu-
ring-Maschine ist ein theoretisches Modell, mit dessen Hilfe man die Arbeitsweise eines
Computers auf mathematische Weise gut analysieren kann, insbesondere die Frage, was
eigentlich mithilfe von Algorithmen berechnet werden kann und was nicht.
Alan Turing war einer der wenigen Mathematiker, die es zu cineastischen Ehren ge-
bracht haben, denn in dem Film „Enigma“ ging es darum, wie Turing während des Zwei-
ten Weltkrieges die deutsche Dechiffriermaschine Enigma knackte, woraufhin den Bri-
ten die Entzifferung der mit der Enigma verschlüsselten deutschen Funksprüche
möglich war. Genützt hat ihm das in seinem späteren Leben wenig, denn sein Ende war
tragisch: Mit gerade einmal 42 Jahren beging er Selbstmord, letztlich deshalb, weil man
ihn staatlicherseits wegen seiner Homosexualität verfolgte, die damals – in den 1950er-
Jahren – noch als strafbar galt.
Seine theoretische Maschine hat ihn überlebt. Man kann sie sehr abstrakt so definieren,
dass man hinterher genauso schlau ist wie vorher, aber das muss nicht sein. Im Folgen-
den will ich Ihnen an einem Beispiel die Funktionsweise einer Turing-Maschine zeigen.
Die Bestandteile einer Turing-Maschine sind denkbar einfach und überschaubar. Zu-
nächst besitzt sie ein Band mit beliebig vielen Speicherstellen, was schon ein Grund da-
für ist, dass man sie nur in der Theorie betrachten kann, denn beliebig viele Speicher-
stellen kann kein realer Rechner aufweisen. Damit man mit diesen Speicherstellen etwas
anfangen kann, hat die Maschine einen Lese- und Schreibkopf, der jeweils eine Speicher-
stelle lesen und etwas auf eine Speicherstelle schreiben oder von ihr löschen oder auch
einfach alles beim Alten lassen kann. Zusätzlich kann er um eine Stelle nach rechts oder
nach links rücken oder es bleiben lassen. Was er schreiben kann, ist nicht beliebig, es gibt
ein Alphabet aus endlich vielen Zeichen, und nichts anderes darf auf dem Band landen
als Zeichen dieses Alphabets, zu denen immer das Leerzeichen „Blank“ gehören muss.
Und woher weiß er, was er schreiben soll? Das hängt vom jeweiligen Programm der Tu-
ring-Maschine ab, das man mithilfe bestimmter Programmanweisungen und vor allem
mithilfe von sogenannten Zuständen der Turing-Maschine formuliert. Mehr hat sie
nicht. Und mehr braucht sie auch nicht, denn alles, was man überhaupt berechnen kann,
kann auch mithilfe einer Turing-Maschine berechnet werden, und weil sie einen eher
schlichten Aufbau hat, eignet sie sich eben ausgezeichnet zur mathematischen Analyse
von Berechenbarkeitsproblemen.

38 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Rechner 3

..... 1 1 1 .....

..... a 1 1 .....

..... a 1 1 b .....

Abb. 3.1: Band der Turing-Maschine

Das alles klingt noch sehr abstrakt, und deshalb werde ich diese allgemeinen Bemerkun-
gen jetzt konkretisieren. Ich will eine Turing-Maschine bauen, die in der Lage ist, eine
beliebige, aber endliche Zahl von Einsen, die nebeneinander auf dem Band stehen, zu
verdoppeln. Hat man es also anfangs mit drei Einsen zu tun, sollen es am Ende sechs
sein, waren es anfangs nur zwei, dann sind es zum Schluss natürlich vier und so weiter.
Ich gehe ab jetzt davon aus, dass es sich zu Beginn um drei Einsen handelt und das Band
somit wie im ersten Teil von Abb. 3.1 aussieht. Ich werde nun das Turing-Programm
aufschreiben, das dieses Problem löst. Beim ersten Lesen werden Sie nicht viel verste-
hen, aber selbstverständlich werde ich das gesamte Programm anschließend erklären.
Mein Alphabet ist nicht groß; es besteht nur aus den vier Zeichen 1, a, b und □, wobei
□ für das Leerzeichen Blank steht. Und das Programm ist das folgende.

z 0 ,1  z1 , a , R
z1 ,1  z1 ,1, R
z1 , b  z1 , b , R
z1 , z2 , b , L
z2 , b  z2 , b , L
z2 ,1  z2 ,1, L
z2 , a  z 0 ,1, R
z 0 , b  z 0 ,1, R
z 0 , z E ,, H

Auf den ersten Blick eher unübersichtlich, aber ich werde das alles jetzt mit Ihnen
durchgehen. Ich gehe davon aus, dass der Lese- und Schreibkopf direkt über der ersten
1 steht; seine Position wird in Abb. 3.1 jeweils durch einen Pfeil angezeigt. z0 ist einer
der Zustände der Maschine, der als Anfangszustand betrachtet wird und somit sofort
zum Tragen kommt: Wenn die Maschine im Zustand z0 ist und dabei eine 1 liest – und
das ist sicher am Anfang der Fall –, dann soll sie in einen anderen Zustand z1 übergehen,
auf die aktuelle Stelle ein a schreiben und dann um eine Stelle nach rechts rücken, was
durch das Symbol R zum Ausdruck gebracht wird. Das ist der Inhalt der ersten Pro-
grammzeile. Dabei ist z1 nur ein Symbol für einen weiteren Zustand, den ich benutzen
werde, um die Verarbeitung weiter beschreiben zu können. In jedem Fall steht der Kopf
jetzt über der zweiten 1, und das Band sieht aus wie im zweiten Teil von Abb. 3.1. Dass

GDI01 39
© HfB, 02.12.20, Berk, Eric (904709)

3 Rechner

die erste 1 nun durch ein a ersetzt wurde, dient nur der Markierung: Diese 1 wurde jetzt
als solche erkannt und soll nach den vorhandenen Einsen an die Kette angehängt wer-
den.
Die Maschine befindet sich nun im Zustand z1 und steht über dem zweiten Zeichen, also
einer 1, und das heißt, dass nun die zweite Programmzeile zieht. Folglich bleibt es beim
Zustand z1 und bei der dort stehenden 1, und der Kopf bewegt sich wieder um eine Stelle
nach rechts, wo er wieder eine 1 vorfindet. Daher zieht noch einmal die zweite Pro-
grammzeile, es bleibt bei der 1 und wir wandern nach rechts – bedenken Sie dabei, dass
immer nur die Programmzeile ausgewählt wird, die zu der aktuellen Kombination aus
Zustand und gelesenem Zeichen passt; die Reihenfolge der Programmzeilen spielt dage-
gen keine Rolle. Aber jetzt hat sich etwas geändert, denn der Kopf steht nun über dem
Leerzeichen □, weshalb die vierte Programmzeile ihr Existenzrecht beweist. Die Maschi-
ne geht daher in den Zustand z2 über, schreibt an die bisherige Leerstelle ein b und wan-
dert um eine Stelle nach links. Dieses b wird später zu einer 1 werden, im Moment muss
ich aber noch ein anderes Zeichen einsetzen, damit ich die neuen Einsen von den alten
unterscheiden kann.
Nun haben Sie die Situation aus dem dritten Teil von Abb. 3.1, die Turing-Maschine be-
findet sich im Zustand z2 und der Kopf steht über der dritten ursprünglichen 1. Somit
zieht hier die sechste Programmzeile, die dafür sorgt, dass Zustand und Zelleninhalt er-
halten bleiben und der Kopf wieder um einen Schritt nach links rückt. Das ändert nicht
viel, denn jetzt steht der Kopf wieder über einer 1 und muss deshalb infolge der sechsten
Programmzeile einen weiteren Schritt nach links rücken. Das a, das er dort vorfindet,
sorgt in Verbindung mit dem Zustand z2 für den Einsatz der siebten Programmzeile, die
dafür sorgt, dass die Maschine in den Zustand z0 übergeht, eine 1 dort hinschreibt, wo
eben noch ein a war, und schließlich um eine Position nach rechts rückt. Das Resultat
sehen Sie im ersten Teil von Abb. 3.2.

..... 1 1 1 b .....

..... 1 1 1 b b b .....

..... 1 1 1 1 1 1 .....

Abb. 3.2: Band der Turing-Maschine

Sehen Sie, was inzwischen geschehen ist? Die erste 1 hatte ich in ein a umgeschrieben,
um zu markieren, dass sie am Ende der Einserkette angehängt werden soll. Das ist nun
erledigt, weshalb ich eben dieses a wieder zu einer 1 umwandeln kann. Und damit fängt
alles wieder von vorn an, denn der Lese- und Schreibkopf steht wieder über einer 1, die
ans Ende kopiert werden soll, und der aktuelle Zustand ist wie schon am Anfang z0. Die
aktuelle 1 wird also in ein a umgesetzt und der Kopf wandert nach rechts. Der einzige
Unterschied zu vorhin besteht darin, dass er unterwegs auf eine mit b belegte Speicher-
zelle trifft und damit auch die dritte Zeile des Programms zu ihrem Recht kommt, denn

40 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Rechner 3

nun wird dieses b an seiner Stelle gelassen, während der Kopf um eine Stelle nach rechts
rückt. Und sobald im nächsten Schritt der Kopf auf eine Leerstelle trifft, während der
Zustand immer noch auf z1 steht, beginnt der Weg zurück, wobei der Zustand auf z2 ge-
setzt wird. Nach einer Weile trifft der Kopf dann auf das vorher eingesetzte a, verwan-
delt es zurück in eine 1, und der Zustand wird wieder auf z0 gesetzt. Das kennen wir
schon, denn jetzt geht es weiter wie gehabt, nämlich mit der ersten Programmzeile.
Nachdem nun also dreimal hintereinander so ziemlich das Gleiche geschehen ist, ergibt
sich die Situation aus dem zweiten Teil von Abb. 3.2. Aber jetzt passiert etwas Neues.
Die Maschine ist wegen der siebten Programmzeile in den Zustand z0 übergegangen und
hat das dritte a durch eine 1 ersetzt. Nun trifft der Kopf allerdings auf ein b, was endlich
die achte Programmzeile wirksam werden lässt – und das gleich mehrfach. Alle drei b-
Zeichen werden nun unter Beibehaltung des Zustandes durch Einsen ersetzt, bis schließ-
lich nach dem letzten Rücken nach rechts die allerletzte Programmzeile aktiv wird: Die
Maschine geht in den Endzustand zE über, lässt das Leerzeichen rechts von der letzten
1, wie es ist, und macht nichts mehr, was durch das Symbol H für „Halten“ ausgedrückt
wird. Und damit haben wir die endgültige Situation aus dem dritten Teil von Abb. 3.2
erreicht. Sie können leicht nachprüfen, dass diese Turing-Maschine nicht nur für drei ge-
gebene Einsen funktioniert, sondern auch für jede beliebige Anzahl, sogar wenn gar kei-
ne 1 auf dem Band steht. In einer der Übungen haben Sie dazu Gelegenheit.
Ohne Frage ist das alles sehr ungewohnt, aber es ist ein einfaches Modell eines Compu-
ters, das erstens ausgesprochen viele Möglichkeiten in sich trägt und zweitens wegen der
geringen Zahl seiner Komponenten gut analysiert werden kann. Noch einmal: Mit Ma-
schinen dieser Art kann man alles berechnen, was überhaupt berechenbar ist. Natürlich
handelt es sich bei der hier beschriebenen Turing-Maschine um ein sehr spezielles Kon-
strukt, bei der das Programm fest in die Maschine eingebaut ist. Es ist aber auch möglich,
eine sogenannte universelle Turing-Maschine zu konstruieren, mit deren Hilfe man jede
beliebige spezielle Turing-Maschine programmieren kann. Und somit ist die Turing-Ma-
schine das einfachste existierende Modell eines allgemein einsetzbaren Computers.

Eine Turing-Maschine ist ein theoretisches Modell für einen programmierbaren


Computer. Sie besteht aus einem beliebig langen Band, das beliebig viele Speicher-
zellen enthält, einem Lese- und Schreibkopf, der Zeichen vom Band lesen und auf
das Band schreiben kann und in der Lage ist, sich um jeweils eine Stelle nach rechts
oder links zu bewegen, und einem vorgegebenen endlichen Alphabet. Die Turing-
Maschine kann endlich viele verschiedene Zustände annehmen, die den Verlauf von
Turing-Programmen steuern.
Mithilfe einer Turing-Maschine lässt sich jede Art von Berechnung programmieren.

Falls Sie einmal genauer sehen möchten, wie man sich an den Aufbau eines solchen Tu-
ring-Programms herantastet mit genau dem Ergebnis, das ich Ihnen hier gezeigt habe,
empfehle ich das unter Spannagel (2013) im Literaturverzeichnis angegebene Video.

Übung 3.3:
Weisen Sie nach, dass die oben beschriebene Turing-Maschine auch dann ihren
Zweck erfüllt, wenn auf dem Band zu Anfang keine 1 oder nur eine 1 steht.

GDI01 41
© HfB, 02.12.20, Berk, Eric (904709)

3 Rechner

Übung 3.4:
Konstruieren Sie eine Turing-Maschine, die die Addition 3  4  7 in der folgenden
Form durchführt: Auf dem Band der Maschine ist die Zeichenkette „1111111“ ge-
geben, als Resultat soll auf dem Band die Zeichenkette „1111111“ stehen.

3.3 Die Von-Neumann-Architektur


In der alten Zeit der Datenverarbeitung war jeder konkrete Rechner anders. Es handelte
sich im Wesentlichen um Einzelstücke, und die Konstrukteure suchten über einen lan-
gen Zeitraum nach einer Architektur, die den Problemen angemessen war und dem
Rechner eine hinreichend schnelle und effiziente Arbeitsweise ermöglichte. Dass sich so
eine standardisierte Architektur nicht von heute auf morgen entwickeln und etablieren
kann, dürfte klar sein, aber immerhin hat sich das Grundprinzip schon in den 1940er
Jahren herauskristallisiert, als der ungarische Mathematiker John von Neumann in ei-
nem Artikel das Konzept des gespeicherten Programms erläuterte. Er war keineswegs
grenzenlos computergläubig, was seine Arbeiten zur Rechnerarchitektur wohl noch et-
was glaubhafter und überzeugender machte, als sie es inhaltlich ohnehin schon waren.
In seiner amerikanischen Zeit war er Mitglied einer Kommission zur Bewilligung von
Geldern für die Entwicklung von Computern, und als eine Marinedienststelle einmal ei-
nen Computer beantragte mit dem etwas vagen Argument, man wolle bestimmte Prob-
leme damit lösen, fragte von Neumann gezielt nach, um welche Probleme es sich denn
handle. Der Antragsteller nannte ein mathematisches Problem, woraufhin von Neu-
mann zehn Minuten nachdachte und dann die Lösung des Problems an die Tafel schrieb.
Da der verdutzte Marineoffizier kein weiteres Problem wusste, mit dem man die An-
schaffung des Computers hätte rechtfertigen können, war die Welt vor einem unnötigen
Computer bewahrt worden.
John von Neumann wusste also recht gut, wofür man einen Computer braucht und wo-
für nicht, und ich werde Ihnen jetzt zeigen, wie ein Rechner nach der Architektur von
Neumanns aufgebaut ist – eine Architektur, nach der sich auch heute noch die meisten
Computer richten. Ohne Frage wird ein Rechner einen Arbeitsspeicher benötigen, um
die Daten, die bearbeitet werden sollen, aufzunehmen, denn das Ganze soll ja schnell
vonstattengehen, und daher müssen die Daten auch schnell zugänglich sein. Für die Be-
arbeitung komplexerer Aufgaben wird man mehr als nur die eine oder andere Ziffer im
Arbeitsspeicher abspeichern können müssen; man braucht also einen Arbeitsspeicher ei-
ner gewissen Größe, in den auch genug hineinpasst. Aber warum genau heißt der Ar-
beitsspeicher eigentlich Arbeitsspeicher? Natürlich weil man mit ihm und seinen Inhal-
ten arbeiten will, und das bedeutet, dass er nicht irgendwo extern, beispielsweise in
Form einer Festplatte, vorliegen kann, sondern direkt innerhalb des Rechners, denn Ihr
Prozessor, auf den ich gleich noch zu sprechen komme, muss schnell auf die Daten des
Arbeitsspeichers zugreifen können. 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 verlo-
ren. Genau in dieser Organisationsform liegt allerdings auch sein Vorteil, da auf diese
Weise eine schnelle Kommunikation zwischen dem Prozessor, der die Rechenarbeit er-
ledigen soll, und dem Arbeitsspeicher, der die dazu notwendigen Daten bereitzustellen
hat, stattfinden kann. Beachten Sie, dass sowohl die nötigen Befehle als auch die Daten
im gleichen Arbeitsspeicher unterkommen; man hat darauf verzichtet, den Datenspei-
cher und den Befehlsspeicher grundsätzlich voneinander zu trennen, und das ist auch

42 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Rechner 3

gut so. Schließlich wird von Problem zu Problem das Verhältnis zwischen reinen Daten
und Befehlen ein anderes sein, und wenn Sie zwei sauber getrennte Speicherbereiche
hätten, dann wäre 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 auftre-
ten. Erwähnen sollte ich noch, dass man auch den Begriff des Speicherwerks zur Be-
zeichnung des Arbeitsspeichers verwendet.
Offenbar reicht es nicht, nur Daten und Befehle zu ihrer Verarbeitung bereitzustellen,
irgendwo muss auch die eigentliche Verarbeitung stattfinden, und der passende Ort da-
für ist der Prozessor, den man auch gern als Central Processing Unit, abgekürzt CPU,
bezeichnet. Er besteht aus einem Steuerwerk, das die organisatorische Arbeit erledigt,
und einem Rechenwerk, dem die eigentliche Aufgabe des Rechnens zugeordnet ist.
Auch für diese beiden Teile des Computerinnenlebens gibt es weitverbreitete englische
Bezeichnungen, nämlich Arithmetical Logical Unit, abgekürzt ALU, für das Rechen-
werk und Control Unit für das Steuerwerk. Tatsächlich ist die englische Bezeichnung
für das Rechenwerk präziser als die deutsche, denn im Rechenwerk wird zwar gerech-
net, aber erstens kann man dort auch logische Operationen ausführen und zweitens be-
ruht auch jeder Rechenvorgang in einem Computer auf logischen Schaltungen, die Sie
im fünften Kapitel kennenlernen werden. Beachten Sie übrigens, dass die im Arbeits-
speicher abgelegten Befehle ganz ordentlich Schritt für Schritt, also sequenziell, durch-
geführt werden und schon deshalb ein Rechner auf Basis der Von-Neumann-Architektur
als eine Art Realisierung einer Turing-Maschine betrachtet werden kann, bei der man
auch immer nur einen Schritt nach dem anderen machen darf.
Sie sollten aber nicht vergessen, wie wichtig auch das Steuerwerk für die korrekte Ver-
arbeitung der Daten ist, seine Rolle ist in etwa die eines Organisators oder Projektleiters
in einem Betrieb. Es sorgt dafür, dass die einzelnen Befehle des Programms aus ihren
Parkplätzen im Arbeitsspeicher geholt werden, es besorgt dem Rechenwerk die nötigen
Daten aus dem Arbeitsspeicher, damit das Rechenwerk auch etwas zu rechnen hat, es
veranlasst das Rechenwerk, mit den übermittelten Befehlen die übermittelten Daten zu
bearbeiten, und am Ende schaufelt es die berechneten Ergebnisse wieder in den Arbeits-
speicher, damit der Benutzer seine Freude daran hat. Kurz gesagt: Das Rechenwerk er-
ledigt die Rechenaufgaben, das Steuerwerk dagegen die organisatorischen Aufgaben.
Und wie kommt der Arbeitsspeicher an seine Daten? Das funktioniert mithilfe des Ein-
gabe-/Ausgabewerks, das die Ein- und Ausgabe von Daten vom und zum Anwender –
beispielsweise über Tastatur und Bildschirm – oder von und zu anderen Systemen über
bestimmte Schnittstellen steuert. Damit sind dann auch schon fast alle Komponenten
der Von-Neumann-Architektur beschrieben mit einer Ausnahme: Die eingegebenen Da-
ten müssen irgendwie ihren Weg zur CPU finden, also zum Rechen- und zum Steuer-
werk, und was berechnet wurde, sollte vielleicht auch wieder für die Ausgabe zur Ver-
fügung stehen, sonst hätte man sich das ganze Rechnen sparen können. Für diesen Hin-
und Hertransport der Daten und natürlich auch der Befehle steht das Bussystem zur Ver-
fügung, das nichts anderes zu tun hat, als Daten verschiedenster Art von hier nach da
zu transportieren.

GDI01 43
© HfB, 02.12.20, Berk, Eric (904709)

3 Rechner

Eingabegeräte

Prozessor
Arbeitsspeicher
Steuerwerk
Daten

Rechenwerk Programme

Bussystem

Externer Daten Ausgabegeräte


Speicher
Programme

Abb. 3.3: Architektur nach John von Neumann

Die klassische Architektur eines Computers nach John von Neumann haben wir damit
bereits besprochen, Sie können ihre grafische Darstellung in Abb. 3.3 sehen. Es kann
aber nicht schaden, einen Schritt weiter zu gehen und sich das eine oder andere damit
verbundene Problem anzusehen. Was macht man denn beispielsweise, wenn der Ar-
beitsspeicher 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ür eine bestimmte Verarbeitung gebraucht werden, in diesen Spei-
cher hineinpassen. Und das ist nicht mal das einzige Problem, ein anderes liegt noch viel
näher: Was passiert, wenn Sie Ihre Daten brav eingetippt, Ihre Programme fleißig dem
Arbeitsspeicher übermittelt haben und dann der Strom ausfällt? Das ist einfach zu be-
antworten, denn dann ist eben alles verloren, man kann von vorn anfangen und fühlt
sich so wie Sisyphos, der dazu verurteilt war, den immer gleichen Felsbrocken immer
wieder auf den immer gleichen Hügel zu rollen und kurz vor dem Ziel an der immer glei-
chen Stelle festzustellen, dass der elende Stein ihm schon wieder entglitt und nach unten
rollte.
Dieses Problem lässt sich aber lösen. Die Daten und Programme landen eben nicht
gleich im Arbeitsspeicher, sondern man pflegt sie in einem externen Speicher abzule-
gen, der nicht vom Stromfluss abhängig ist. Oft beruht ein externer Speicher auf den üb-
lichen Festplatten, die auf Magnetisierungsbasis arbeiten, aber auch CDs oder USB-
Sticks sind denkbar, die auf anderen Prinzipien basieren. In jedem Fall sind Ihre Einga-
ben dann vor plötzlichen Stromausfällen gesichert, und es muss nur für eine Verbindung
zwischen dem externen Speicher und dem Arbeitsspeicher gesorgt werden. Da der ex-
terne Speicher nicht so flüchtig ist wie der Arbeitsspeicher, nennt man ihn auch Fest-
speicher, und umgekehrt bezeichnet man den Arbeitsspeicher auch als den internen
Speicher.
Der externe Speicher hat also mehrere Funktionen. Erstens sorgt er dafür, dass eingege-
bene Daten und Programme mehrfach verwendbar sind, unabhängig von der Stromver-
sorgung des Computers. Zweitens kann man natürlich auch umgekehrt die Ergebnisse,
die der Computer liefert, nicht nur dem unsicheren Arbeitsspeicher überlassen, sondern
sie auch auf dem Festspeicher ablegen, damit die Nachwelt auch noch etwas davon hat;
insofern gehört auch die Festplatte zu den Ausgabegeräten. Und drittens können Sie mit-
hilfe des Festspeichers auch das oben angesprochene Problem der zu groß geratenen Pro-
gramme oder Datensätze lösen. Wenn beispielsweise ein Programm so groß geworden
ist, dass es nicht mehr vollständig in den Arbeitsspeicher, aber immer noch locker auf
die Festplatte passt, dann ist es möglich, das Programm eben nicht ganz, sondern nur

44 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Rechner 3

häppchenweise zusammen mit den jeweils nötigen Daten in den Arbeitsspeicher zu la-
den, dann vom Prozessor die geladenen Befehle ausführen zu lassen und nach ihrer Aus-
führung einfach die nächste 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üren, Ihre mühsam geschriebenen Program-
me auch noch in leicht verdauliche Portionen aufzuteilen, Sie wollen das Programm
starten und zusehen, ob es richtig ist. Deshalb wird dieses häppchenweise Einlagern von
Programmteilen auch nicht vom Benutzer durchgeführt, sondern vom bereits erwähn-
ten Betriebssystem, einem speziellen Programm, das die Abläufe auf dem Rechner or-
ganisiert.

Die Von-Neumann-Architektur ist durch das Konzept des gespeicherten Programms


gekennzeichnet. Dabei werden im Arbeitsspeicher sowohl reine Datensätze als auch
Programmteile gespeichert, auf die die CPU direkt zugreifen kann, was eine schnelle
Abarbeitung der Programme gewährleisten soll. Zusätzlich verfügt ein Computer
auf Basis dieser Architektur in der Regel über externe Speicher, oft in Form einer
Festplatte, auf denen größere Datenmengen und Programme abgelegt werden kön-
nen als im Arbeitsspeicher. Über das Betriebssystem wird gesteuert, welche Pro-
grammteile vom Arbeitsspeicher aufgenommen werden.

Unabhängig von der Von-Neumann-Architektur sollten wir aber noch einen Blick auf
den Arbeitsspeicher oder auch Hauptspeicher werfen. Er besteht einfach nur aus einer
Folge von vielen gleich großen Speicherelementen, die auf der Basis elektronischer
Schaltungen organisiert sind. Da es hier um elektrischen Strom geht und man nur un-
terscheidet, ob Strom fließt oder nicht, gibt es für ein Speicherelement genau zwei ver-
schiedene Möglichkeiten der Belegung: Strom fließt oder nicht, also 1 oder 0. Ein ein-
zelnes Element, das nur 0 oder 1 sein kann, wird als Bit bezeichnet, was nichts weiter
ist als eine Abkürzung für binary digit, also binäre Einheit, und darauf anspielt, dass Sie
sich bald mit der Arithmetik binärer oder auch dualer Zahlen befassen werden. Nun
kann man aber in einem Bit nicht allzu viele Informationen unterbringen, und deshalb
fasst man acht Bits zusammen zu einem Byte. Bei Licht betrachtet sind diese Bytes die
Grund­einheiten 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ßer-
dem meistens ziemlich sinnlos, da es Ihnen keine nennenswerte Information liefern
kann. Die Bytes im Arbeitsspeicher sind dagegen Einheiten, mit denen man etwas an-
fangen kann; es ist möglich, ein ganzes Zeichen wie etwa einen Buchstaben in Form ei-
nes Bytes abzuspeichern, und vor allem hat jedes Byte seine eigene Adresse im Haupt-
speicher. Einfacher 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ön der Reihe nach num-
meriert. Sie können es sich vorstellen wie in einem Gefängnis, in dem die Bytes in or-
dentlich 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, abge-
kürzt RAM.

GDI01 45
© HfB, 02.12.20, Berk, Eric (904709)

3 Rechner

Übung 3.5:
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 vollelekt-
ronischen Arbeitsspeicher konstruieren kann. Gehen Sie dabei auf die grundsätz-
liche Möglichkeit und auf das Problem der Arbeitsgeschwindigkeit eines solchen
Rechners ein.
b) Gehen Sie den Fragen aus a) nach unter der Voraussetzung, dass zwar ein voll-
elektronischer Arbeitsspeicher, dafür aber keine Festplatte vorhanden ist.

Übung 3.6:
Diskutieren Sie, warum man in der Zentraleinheit einerseits Speicherzellen zur pu-
ren Aufnahme von Daten und andererseits ein Rechenwerk zum Rechnen mit diesen
Daten vorsieht. Warum hat man nicht gleich die Speicherzellen des Arbeitsspeichers
mit Rechenfähigkeiten versehen und sich ein separates Rechenwerk gespart?

Übung 3.7:
Gegeben sei ein Speicher mit einer Kapazität von einem Gigabyte, also von 230 Byte.
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 ein-
gelesen werden und wie viel Speicherplatz ist dann belegt?

3.4 Moderne Programmiersprachen und Betriebssysteme


Die Zeiten von Babbage, Turing und Neumann sind lange vorbei, aber ihre Ideen leben
noch heute in genau dem Computer, der vermutlich auf Ihrem Schreibtisch steht. Des-
halb werde ich in diesem Abschnitt das eine oder andere aktuelle Betriebssystem vor-
stellen und, da Betriebssysteme auch nur Mittel zum Zweck sind, auch ein paar Worte
über derzeitige Programmiersprachen sagen. Ich hatte Ihnen schon berichtet, was der
Zweck eines Betriebssystems ist: Man braucht eine Software, die die Verwaltung der Da-
teien organisiert, die dafür sorgt, dass die Steuerung der Prozesse reibungslos abläuft
und sich mehrere Prozesse nicht gegenseitig stören – all das erledigt das Betriebssystem,
es verwaltet die Ressourcen Ihres Rechners und erspart es Ihnen, sich selbst direkt mit
der Hardware herumschlagen zu müssen.
Bei einem Rechner, auf den mehrere Benutzer gleichzeitig zugreifen können, muss das
Betriebssystem dafür sorgen, dass alle parallel laufenden Prozesse die nötigen Ressour-
cen bekommen, damit alle Anforderungen erfüllt werden. Aber auch die Fehlerbehand-
lung gehört zu seinen Aufgaben, denn manche Fehler können automatisch behoben wer-
den, ohne dass der Benutzer etwas davon merken muss. Und dass sich ein
Betriebssystem auch noch um die Sicherheit eines Rechners kümmern sollte, muss man
im Zeitalter der Hacker wohl kaum betonen.
Abhängig von ihrem Einsatzzweck gibt es unterschiedliche Arten von Betriebssyste-
men. Um nur einige Beispiele zu nennen: Betriebssysteme für PCs zählen zu den einfa-
cheren, da sie in der Regel Einbenutzersysteme sind, obwohl sie unter Umständen meh-

46 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Rechner 3

rere Prozesse verwalten können. Großrechnerbetriebssysteme sind da schon deutlich


komplexer, ebenso wie Realzeitbetriebssysteme, die immer in Echtzeit reagieren müs-
sen. Verteilte Betriebssysteme kontrollieren dagegen mehrere Rechner gleichzeitig und
stellen sie nach außen wie einen einzigen Rechner dar.
Bleiben wir bei den PCs, die Sie alle kennen. Das wohl verbreitetste Betriebssystem für
PCs ist Windows von der Firma Microsoft in seinen vielfältigen Ausprägungen, aber
auch Linux, eine Unix-Variante, hat keine kleine Fangemeinde. Windows gibt es schon
seit 1983, und seither hat Microsoft etliche Versionen von Windows herausgebracht. Ur-
sprünglich war es nur die grafische Benutzeroberfläche zum Betriebssystem MS-DOS,
aber seit Windows 95 hat es sich zu einem eigenständigen Multitasking-Betriebssystem
entwickelt, also zu einem System, bei dem mehrere Arbeiten zeitgleich ausgeführt wer-
den können – zumindest sieht es für den Anwender so aus, denn echte Parallelverarbei-
tung findet hier nicht statt. Auch heute noch gibt es die grafische Oberfläche, die in
mehreren Fensterdarstellungen erfolgt, und die Bedienung mit der Maus ist keine kom-
plizierte Angelegenheit. Die zurzeit (2018) aktuelle Fassung heißt Windows 10 und ist
zum Betrieb nicht nur auf PCs, sondern auch noch auf anderen Geräten wie beispiels-
weise Tablets geeignet. Im Gegensatz zur meist einfachen Bedienung ist der innere Auf-
bau von Windows recht komplex und bleibt dem Benutzer weitgehend verborgen. Vor-
gesehen ist es im Prinzip als Ein-Benutzer-System, wobei man davon ausgeht, dass der
Benutzer nicht viel von Computern versteht und vom System geführt werden muss.
Unix, das in den 1970er-Jahren von den Bell Laboratories entwickelt wurde, ist ein Sys-
tem ganz anderer Art und ist inzwischen für einen weiten Bereich von Rechnern vom
PC bis hin zum Großrechner verfügbar. Unix war von Anfang an als Mehrbenutzersys-
tem angelegt und ist ebenfalls multitaskingfähig. Lange Zeit galt es als eher benutzerun-
freundlich, weil es im Gegensatz zu Windows keine einfache grafische Oberfläche an-
bot, aber fensterorientierte grafische Oberflächen stehen schon lange auch für Unix zur
Verfügung. Ein sehr bekanntes Unix-Derivat ist das PC-Betriebssystem Linux, ein li-
zenzfreies Betriebssystem, dessen Architektur interessierten Programmierern offenliegt,
die sich an seiner weiteren Entwicklung beteiligen können. Im Gegensatz zu Windows
ist der Aufbau von Linux eher einfach und nachvollziehbar, und man geht davon aus,
dass der Benutzer weiß, was er tut und womit er es zu tun hat. In seinen Ursprüngen
handelt es sich um ein System von Softwareentwicklern für Softwareentwickler, und
diese Philosophie des mündigen Benutzers hat man weitgehend beibehalten.
Sowohl Linux als auch Windows werden auf PCs eingesetzt. Inzwischen gibt es aber
auch verschiedene mobile Geräte wie Tablets und Smartphones, und auch die brauchen
Betriebssysteme. Am weitesten verbreitet ist das Betriebssystem Android, das für genau
diese mobilen Geräte von einer Google-Tochterfirma entwickelt wurde und 2008 in ei-
ner ersten Version auf den Markt kam. Im Gegensatz zu seinem Konkurrenten iOS, dem
Betriebssystem für iPhone und iPad von Apple, ist Android auf Geräten verschiedenster
Hersteller erhältlich. Es handelt sich um ein recht offenes System, bei dem man Anwen-
dungssoftware verschiedenster Art installieren kann.
Das System iOS von Apple kommt auf den verschiedenen Apple-Geräten wie zum Bei-
spiel dem iPhone und dem iPad zum Einsatz. Es wurde 2007 zusammen mit dem ersten
iPhone auf den Markt gebracht. Im Gegensatz zu Android, das von Geräten verschie-
denster Hersteller verwendet werden kann, darf iOS nur auf Apple-Geräten eingesetzt

GDI01 47
© HfB, 02.12.20, Berk, Eric (904709)

3 Rechner

werden. Und auch bei den Anwendungen, den sogenannten Apps, geht man restriktiv
vor: Es sind nur die offiziellen Apps von Apple zugelassen, die man auch nur über Apple
selbst beziehen kann.
Auch Microsoft hat etwas zum Markt der mobilen Geräte beigetragen. Das mobile Be-
triebssystem Windows Phone lehnt sich stark an das übliche Windows an, was man
auch an den Nummerierungen sieht, denn Windows Phone 8.1 ist ist die mobile Fassung
von Windows 8, und die neuere Fassung Windows 10 Mobile bezieht sich natürlich auf
die aktuelle Windows-Fassung Windows 10. Die mobilen Windows-Systeme lassen sich
zwar recht einfach an die persönlichen Wünsche des Benutzers anpassen, aber dennoch
sind sie ähnlich geschlossen wie iOS und hängen stark von Microsoft-eigenen Apps ab.
Nun sind aber Betriebssysteme nicht alles, man sollte auch Anwendungen programmie-
ren können, und wie Sie wissen, braucht man dazu Programmiersprachen. Ein Klassi-
ker ist die Programmiersprache C, die in den 1970er Jahren an den Bell Labs entwickelt
wurde. Es ist die klassische Sprache der strukturierten Programmierung, mit der man all
das umsetzen kann, was ich Ihnen über strukturierte Programmierung berichtet habe,
und noch viel mehr. C erlaubt die Programmierung nahe an der Hardware, und es ist
möglich, direkte Zugriffe auf den Speicher mithilfe sogenannter Zeiger durchzuführen.
Genau wegen dieser Nähe zur durchführenden Hardware, also wegen der Maschinen-
nähe, ist C eine ausgesprochen effiziente Sprache und kann auch in der Systemprogram-
mierung, also der Programmierung von Betriebssystemen, eingesetzt werden.
Beim puren C ist man aber nicht stehen geblieben. Schon in den 1980er-Jahren wurde
die Erweiterung C entwickelt, ausgesprochen C plus plus, die vor allem der objekto-
rientierten Programmierung dient. Es handelt sich tatsächlich um eine Erweiterung von
C, und das heißt, was C kann, das kann auch C++, weshalb Sie auch mit C++ problemlos
die reine strukturierte Programmierung betreiben können. Aber nicht nur. Wichtig ist,
dass C++ eben auch objektorientiert ist und Sie deshalb die Verfahrensweisen der objek-
torientierten Programmierung wie Klassenbildung und Kapselung, die ich Ihnen im
zweiten Kapitel gezeigt habe, anwenden können, und natürlich auch alle anderen objek-
torientierten Konstruktionen wie z. B. Vererbung oder Polymorphie, deren Erklärung
den Rahmen dieses Heftes sprengen würde. Auch C++ ist sowohl in der System- als auch
in der Anwendungsprogrammierung einsetzbar.
Angelehnt an C ist die Programmiersprache C\#, ausgesprochen C sharp, eine Mi-
crosoft-Entwicklung, die syntaktische Ähnlichkeiten mit C und der Programmier-
sprache Java aufweist und zu den streng objektorientierten Sprachen zählt. Sie ist zwar
prinzipiell auf verschiedenen Umgebungen einsetzbar, wird aber häufig im Rahmen ei-
ner speziellen Microsoft-Umgebung namens .NET verwendet.
Und damit sind wir schon bei der Programmiersprache Java, einer Entwicklung aus den
1990er-Jahren, die man nicht mit der gleichnamigen indonesischen Insel verwechseln
sollte. Java ist eine vollständig objektorientierte Sprache, wenn man davon absieht, dass
einfache Datentypen wie ganze Zahlen nicht automatisch als Objekte verarbeitet wer-
den. Ansonsten erfolgen der Zugriff auf und die Verarbeitung von Daten grundsätzlich
mithilfe von Attributen und Methoden, wie ich es Ihnen schon im zweiten Kapitel ge-
zeigt habe, und auch die bereits bei C erwähnten weitergehenden objektorientierten
Verfahrensweisen sind vorhanden. Wichtig ist bei Java noch die sogenannte Plattfor-
munabhängigkeit. Damit ist gemeint, dass sich Java-Programme mithilfe einer speziel-
len Technologie namens virtuelle Maschine ohne zusätzliche Anpassungen auf allen
Systemen ausführen lassen, für die eine solche virtuelle Maschine zur Verfügung steht.

48 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Rechner 3

Das hat Vorteile, aber auch einen Nachteil, denn gerade wegen der Plattformunabhän-
gigkeit kann Java kaum für Aufgaben der Systemprogrammierung eingesetzt werden,
weshalb ihr Schwerpunkt in der Anwendungsprogrammierung liegt.
Damit ist mein kurzer Überblick über ein paar Betriebssysteme und Programmierspra-
chen beendet. Vergessen Sie nicht: Das war nur eine kleine Auswahl aus einer großen
Menge. Sie können sich ja einmal den Spaß machen, im Internet einen Blick auf Listen
von Betriebssystemen und Programmiersprachen zu werfen; dann werden Sie merken,
was ich mit einer großen Menge meine. Man muss nicht alle diese Sprachen und Syste-
me kennen, und schon gar nicht muss man sie alle beherrschen. Wichtig ist, dass man
grundsätzlich weiß, worum es bei solchen Dingen geht; die jeweiligen Details werden
Sie dann lernen, wenn Sie mit einem Betriebssystem oder einer Programmiersprache
konkret arbeiten und Probleme lösen sollen.
Da es hier nur um einen enzyklopädischen Überblick ging, verzichte ich auf abfragende
Übungen und gehe zum nächsten Kapitel über, in dem Sie lernen werden, wie die duale
Arithmetik funktioniert.

GDI01 49
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen
Sie lernen hier, dass die Darstellung von Daten im Rechner auf dem dualen Zah-
lensystem beruht. Dabei sehen Sie, wie man mit dualen Zahlen rechnet, wie man
sie in hexadezimaler und oktaler Form schreiben kann und wie die Datendarstel-
lung mithilfe des ASCII-Codes funktioniert.

Über die Aufgabe des Computers, Daten zu verarbeiten, haben wir uns schon mehrfach
geeinigt, und Sie haben auch schon etwas darüber gelernt, wie er das macht. Was soll
man aber eigentlich unter Daten verstehen? Da gehen die Meinungen etwas auseinan-
der. Man kann natürlich sagen, dass jede in irgendeiner Form aufgeschriebene Zeichen-
folge schon so etwas wie Daten darstellt, aber das ist wenig befriedigend und erinnert
an die Auffassung mancher Fernsehsender, alles, was auf dem Bildschirm herumzappelt,
sei unterhaltend. Irgendein Sinn sollte mit Daten verbunden sein, sonst wäre der Ver-
such, sie zu verarbeiten, völlig sinnlos und die Datenverarbeitung wäre ein Spiel mit in-
haltsleeren Zeichenketten. Ich will daher davon ausgehen, dass Daten einen Bezug zu
irgendeinem Problem haben, an dessen Lösung man interessiert ist – sie sind ein Hilfs-
mittel, um ein Problem zu lösen, und nicht sinnlos hingekritzelte Zeichenketten.

Unter Daten versteht man Angaben zu Personen, Sachen oder Sachverhalten, die zur
Lösung eines Problems zweckdienlich sein können.

Niemand kann die Zweckdienlichkeit eines bestimmten Datensatzes für ein bestimmtes
Problem garantieren, aber man sollte mit Daten wenigstens die Hoffnung verbinden,
dass ihre Verwendung zur Lösung des einen oder anderen Problems sinnvoll sein kann.
Und natürlich müssen diese Daten dem Computer in einer Form zur Verfügung gestellt
werden, die er versteht, mit der er etwas anfangen kann. Da gibt es aber nur eine Mög-
lichkeit. Ein Computer ist ein elektronisches Instrument, er basiert darauf, dass Strom
fließt oder auch nicht, und dazwischen gibt es gar nichts. Er muss also mit zwei Grund-
zuständen zurande kommen: Strom fließt oder Strom fließt nicht. Auch seine Rechen-
operationen haben keine andere Wahl, als sich auf diese beiden Grundzustände einzu-
lassen, und das bedeutet, dass wir eine sehr spezielle Art von Zahlen brauchen – Zahlen,
die nur zwei Grundzustände kennen, die man üblicherweise mit 0 und 1 bezeichnet. Das
sind die sogenannten Dualzahlen oder auch Binärzahlen.

4.1 Zahlensysteme
Man muss sich mit der Tatsache abfinden, dass ein Computer irgendwie mit Einsen und
Nullen rechnet. Das klingt für normale Menschen zunächst etwas seltsam: Wie soll man
denn mit Einsen und Nullen rechnen können, da kommt man doch nicht weit. Stimmt
aber nicht. Auch das vertraute Dezimalsystem hat nur zehn Ziffern, und trotzdem kann
man damit weiter als bis zehn zählen, warum sollte etwas Ähnliches dann nicht auch
mit zwei Ziffern funktionieren?
Versetzen Sie sich einmal in die Lage eines Computers. Von außen akzeptiert er selbst-
verständlich die üblichen dezimalen Zahlen in der üblichen Darstellung, und er gibt
auch seine Ergebnisse wieder in dieser Form aus, aber seine Verarbeitungen macht er
völlig anders. Die internen Schaltungen eines Rechners beruhen auf dem Fließen von
Strom, und da gibt es nur zwei Grundzustände: Strom fließt oder er fließt nicht. Beim

50 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Dualzahlen 4

Aufbau des Zahlensystems habe ich dagegen zehn Grundzustände, nämlich die Ziffern
von null bis neun, aus denen sich alles andere zusammensetzt. So viele Möglichkeiten
besitzt der Computer nicht, er muss seine gesamte Arithmetik, all seine Rechnungen, zu-
sammensetzen aus den erwähnten zwei Grundzuständen. In Ziffern übersetzt heißt das:
Ihm stehen keine zehn Ziffern zur Verfügung, sondern nur zwei, und damit muss er aus-
kommen.
Sehen wir uns also an, wie man die natürlichen Zahlen computerfreundlich darstellen
kann. Die Darstellung, nach der ich suche, beruht auf nur zwei Ziffern, und deshalb
spricht man oft vom Zweiersystem oder auch Dualsystem. Es spielt aber keine Rolle,
wie Sie es nennen. Wichtig ist nur, dass es darum geht, sämtliche natürlichen Zahlen
mithilfe von nur zwei Ziffern darzustellen. Dabei ist es sinnvoll, sich erst einmal zu
überlegen, wie eigentlich das vertraute Dezimalsystem aufgebaut ist.
Die Bedeutung der Dezimalzahl 13271 dürfte Ihnen vertraut sein. Offenbar hat die 1
ganz vorne einen völlig anderen Wert als die 1 ganz hinten, denn sie steht in Wahrheit
für 10000, während die hintere 1 einfach nur für sich selbst steht. Die Wertigkeit einer
Ziffer hängt also davon ab, an welcher Stelle der Zahl sie steht, und je weiter man nach
links kommt, desto höher 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 Dezimal-
zahl versteht: Es ist eine Folge von endlich vielen Ziffern zwischen 0 und 9, und der tat-
sächliche Zahlenwert jeder Ziffer hängt davon ab, an welcher Stelle der Zahl die Ziffer
steht. Je weiter links, desto höher ist die Zehnerpotenz, mit der man die Ziffer multipli-
zieren muss.

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ägt
an ⋅ 10n  an-1 ⋅ 10n-1 ⋯  a1 ⋅ 10  a0.

Dualzahlen sind genauso aufgebaut wie die Dezimalzahlen, nur eben mit zwei Ziffern
anstatt zehn. Eine typische Dualzahl lautet beispielsweise 101112, wobei die kleine Zwei
bedeutet, dass es sich um eine Zahl zur Basis 2 handelt, und genau deshalb muss ich die
Basiszahl 2 verwenden, um die eigentliche Bedeutung der Dualzahl 101112 vor mir zu
sehen. Ihr dezimaler Wert kann nur 1 · 24  0 · 23  1 · 22  1 · 21  1 · 20 sein, denn
überall dort, wo ich bei einer Dezimalzahl die Basis 10 eingesetzt hätte, gehe ich hier zur
Basis 2 über. Rechnet man das aus, so ergibt sich die Dezimalzahl 23, weshalb also die
Dualzahl 101112 der Dezimalzahl 23 entspricht.
Jetzt dürften Sie so weit sein, die Definition der Dualzahlen zu verstehen. Sie sieht fast
genauso aus wie die 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 ändern.

GDI01 51
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen

Eine (n  1)-stellige Dualzahl oder auch Binärzahl 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ägt
an ⋅ 2n  an-1 ⋅ 2n-1 ⋯  a1 ⋅ 2  a0.

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 Dualzahlen mit dem Index 2 versehen, damit klar ist, dass es sich um Zahlen zur Ba-
sis 2 handelt.
Wie lautet nun der dezimale Wert von 1010102 ? Die Zahl besteht aus sechs Ziffern, da-
her ist die höchste Potenz 25. Damit ergibt sich:
10101021 ⋅ 25  0 ⋅ 24  1 ⋅ 23  0 ⋅ 22  1 ⋅ 21  0 ⋅ 20  32 8 2 42.
1010102 ist also die duale Darstellung der vertrauten Zahl 42. Allerdings musste ich hier
noch die Stellen der Dualzahl zählen, 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. Das bedeutet zum Beispiel:
110111012  1  4  8  16  64  128  221,
denn ganz rechts finden Sie eine duale 1, die einer vertrauten 1 entspricht. Nach einer 0
kommt dann wieder eine 1, also gibt es keine Zweierstelle, sondern erst wieder eine Vie-
rerstelle, der sich eine Achterstelle und eine Sechzehnerstelle anschließen. Die Zweiund-
dreißigerstelle wird dann wieder ausgelassen, und ganz links versammeln sich eine Vier-
undsechzigerstelle und eine Einhundertachtundzwanzigerstelle. Auf die gleiche Weise
können Sie dann zum Beispiel 100001012  1  4  64  69 rechnen.

Übung 4.1:
Berechnen Sie die dezimalen Werte der folgenden Dualzahlen.
a) m  1101100112
b) m  111100012

Übung 4.2:
Entwickeln Sie ein Verfahren, das zwei duale Zahlen der Größe nach vergleicht und
feststellt, welche die größere ist. Beschreiben Sie das Verfahren verbal.

4.2 Umrechnung
Im letzten Abschnitt haben Sie gesehen, wie man den dezimalen Wert einer gegebenen
Dualzahl berechnen kann, nämlich einfach durch Ausnutzung der Definition. Wie ist es
aber umgekehrt? Wie lautet also beispielsweise die duale Darstellung der Dezimalzahl
13? Das ist nicht so schwer, denn eine Dualzahl besteht aus einer Summe von Zweier-
potenzen, und Sie können die 13 schreiben als 13  8  4  1  23  22  20, was der
Dualzahl 11012 entspricht. Man muss also nur überprüfen, welche Zweierpotenzen man

52 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Dualzahlen 4

addieren muss, um die gewünschte Dezimalzahl zu erhalten, und schon kann man die
entsprechende Dualzahl ablesen. Am besten fängt man bei der größten passenden Zwei-
erpotenz an und arbeitet sich dann langsam nach unten vor.
Das ist aber manchmal leichter gesagt als getan. Sie werden mir vermutlich zustimmen,
dass es beispielsweise kein reines Vergnügen ist, die größte Zweierpotenz zu finden, die
1896268482366748 nicht übersteigt, und auch die Suche nach den nächstkleineren
Zweierpotenzen könnte hier problematisch werden. Ich werde Ihnen deshalb jetzt eine
deutlich einfachere Umrechnungsmethode 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ällt Ihnen auf, wenn Sie die Divisionsreste von unten nach oben betrachten? Sie
ergeben nebeneinander geschrieben wieder genau die Zahl 247. Man erhält also die De-
zimalstellen einer Zahl, indem man immer wieder mit Rest durch 10 teilt und anschlie-
ßend die Reste in umgekehrter Reihenfolge aufschreibt. Und bei der Dualdarstellung ist
das nicht anders, nur dass Sie hier natürlich nicht durch 10 teilen, sondern durch die Ba-
siszahl der Dualzahlen, also durch 2, die hier die Rolle der 10 übernimmt.
Sehen wir uns erst einmal an einem Beispiel an, ob das auch funktioniert. Dazu betrach-
te ich wieder die Dezimalzahl m  13. Im Folgenden schreibe ich auf, was passiert, wenn
man andauernd durch 2 teilt, die Reste aufschreibt und dann das jeweilige Divisionser-
gebnis wieder durch 2 teilt. Dann gilt:
13 : 2  6 Rest 1
6 : 2  3 Rest 0
3 : 2  1 Rest 1
1 : 2  0 Rest 1
Schreibt man nun die Reste in umgekehrter Reihenfolge als Dualzahl auf, so ergibt sich
11012  8  4  1  13, also ist alles in Ordnung.
Man muss also nur oft genug durch 2 teilen und die Divisionsreste richtig herum auf-
schreiben. Ich formuliere das jetzt wieder als ein allgemeingültiges Verfahren.

GDI01 53
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen

Das folgende Verfahren rechnet eine Dezimalzahl m in ihre Dualdarstellung 


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 die-
ser Division.
• Man wiederhole den letzten Schritt, bis als Divisionsergebnis der Wert 0 auftritt.
• Man schreibe die aufgetretenen Reste in der umgekehrten Reihenfolge ihres Auf-
tretens als Dualzahl.
Die ermittelte Dualzahl ist dann die duale Darstellung von m.

Vielleicht sollte ich noch ein Wort über das Abbruchkriterium verlieren: Sobald das Di-
visionsergebnis eine Null liefert, soll ich aufhören. Was würde geschehen, wenn ich die-
se Vorschrift ignoriere? Im nächsten Schritt würde ich das alte Divisionsergebnis durch
2 teilen, und da ich vorher bereits eine Null ermittelt habe, wäre das Ergebnis schlicht 0
Rest 0. Und auch alle weiteren Schritte können 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ührender Nullen produziere, von
denen es in Politik und Wirtschaft schon genug gibt und die meine Dualzahl 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öhnt. Zum Glück ist die Rechenmethode ziemlich über-
sichtlich, sodass ich hier auf erklärende Kommentare verzichten kann. Ich werde jetzt
also m  157 in seine duale Darstellung umrechnen. Es gilt:

157 : 2  78 Rest 1
78 : 2  39 Rest 0
39 : 2  19 Rest 1
19 : 2  9 Rest 1
9 : 2  4 Rest 1
4 : 2  2 Rest 0
2 : 2  1 Rest 0
1 : 2  0 Rest 1
Folglich ist 157  100111012.
So viel für den Moment zu Umrechnungen. Etwas später, wenn ich Ihnen etwas über He-
xadezimalzahlen und Oktalzahlen berichte, werden wir uns noch einmal mit dem Um-
rechnen zwischen Zahlensystemen befassen.

Übung 4.3:
Berechnen Sie die Dualdarstellung von m  278 und von n  131.

54 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Dualzahlen 4

4.3 Addition und Subtraktion


Es reicht natürlich nicht aus, Dezimalzahlen in Dualzahlen umrechnen zu können, das
Rechenwerk des Computers soll schließlich auch in der Lage sein, konkrete Rechnungen
durchzuführen, sonst haben alle unsere Bemühungen keinen Sinn.
Fangen wir mit der Addition an. Sie ist extrem einfach, wenn es darum geht, nur die du-
alen Ziffern zu addieren, denn davon gibt es nur zwei. Indem Sie die entsprechenden de-
zimalen Rechnungen als Vergleich heranziehen, 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 du-
alen Zahlen, denn man kann hier eine Analogie zum Addieren von dezimalen Zahlen
feststellen: Die Summe von 12 und 12 lässt sich nicht mehr mithilfe einer einzigen Ziffer
ausdrücken, und daher geht man zu zweistelligen Zahlen über, wie man das im dezima-
len Bereich zum Beispiel bei 5  5 auch machen würde. So etwas nennt man einen Über-
trag in die nächste Stelle, wobei hier der Übertrag schon bei der Addition von zwei Ein-
sen entsteht. Überraschend ist das aber eigentlich nicht; ein Übertrag entsteht dann,
wenn beim Zusammenzählen die Basiszahl erreicht ist, und das ist bei dualen Zahlen
nun mal die 2. Sobald ich also die Zahl 2 aufschreiben will, bin ich gezwungen, zu einem
Übertrag Zuflucht zu nehmen, weil in meinem dualen System die Ziffer 2 nicht existiert.
Das klingt alles ganz ähnlich 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 dualen Zahlen untereinander und addiert
dann der Reihe nach von rechts nach links, wobei man Überträge jeweils in die nächste
Stelle mitnimmt. Das Beste wird sein, ich zeige Ihnen das an Beispielen. Zunächst möch-
te ich die beiden dualen Zahlen 100012 und 10112 addieren. Dazu schreibe ich sie rechts-
bündig untereinander und fange von rechts nach links mit dem Addieren an.

1 0 0 0 1
1 01 11 1
 1 1 1 0 0

Sie sehen, was passiert. In der Einserstelle ist 12  12  102, also erhält das Ergebnis in
der Einserstelle eine 0, und den Übertrag 1 schleppe ich mit in die Zweierstelle. Nun
habe ich in der Zweierstelle die Addition 12  12  02, denn den Übertrag muss man
mitaddieren, und das ergibt wieder 102. Somit schreibe ich in die Zweierstelle des Er-
gebnisses wieder eine 0 und belaste die Viererstelle mit einem neuen Übertrag 1. So
langsam bekommen wir Routine. In der Viererstelle ergibt sich die Addition 12  02  02,
was genau 12 ergibt und mir für die Achterstelle jeden Übertrag erspart. Sowohl in der
Achter- als auch in der Sechzehnerstelle ist dann nur eine duale 12 auf eine duale 02 zu
addieren mit dem jeweils gleichen Ergebnis 12. Insgesamt komme ich auf das Ergebnis
111002, und aus Sicherheitsgründen sollte ich nachsehen, ob das ü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önnte reiner Zufall sein, und außerdem kann ein wenig Übung in einem
neuen Verfahren nicht schaden. Ich addiere jetzt also die beiden dualen Zahlen 1110112
und 111012. Nach dem eben besprochenen Schema ergibt das die folgende Rechnung.

GDI01 55
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen

1 1 1 0 1 1
 1 11 11 11 01 1
1 0 1 1 0 0 0

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 Übertrag. Bei der Achterstelle müssen Sie ein wenig aufpassen, denn hier tritt
die Addition 12  12  12 auf, die natürlich zu dem Resultat 112 führt, also zu einer 1 in
der Ergebniszahl und einer 1 im Übertrag. Das Gleiche passiert in der Sechzehnerstelle,
während dann in der Zweiunddreißigerstelle die Addition 12  12 Sie zu dem Ergebnis
102 bringt. Noch ein kurzer Blick auf die Kontrollrechnung: es gilt 1110112  59 und
111012  29, also 1110112  111012  59  29  88  10110002, denn 10110002  64 
16  8  88. Es ist nichts schiefgegangen.
Und dabei geht auch nie etwas schief, das ist genau die Methode, mit der man duale Zah-
len addiert und die auch der Computer bei seinen Rechenoperationen anwendet. Das
dürfte schon eine allgemeine Regel wert sein.

Man addiert Dualzahlen, indem man sie untereinanderschreibt, von rechts nach
links stellenweise addiert, die Einerziffer der Addition in die Ergebniszahl schreibt
und eventuell auftretende Überträge in die jeweils nächste Stelle übernimmt.

Kürzer gesagt: Man addiert dual genauso wie dezimal, nur eben zur Basis 2.
Auch die duale Subtraktion bietet auf den ersten Blick keine Überraschungen, allerdings
wird es hier zu einem zweiten Blick kommen. Zunächst einmal kann man sich auf den
Standpunkt stellen, dass auch hier das von den Dezimalzahlen gewohnte Verfahren ana-
log zur Addition verwendet werden kann, und das funktioniert auch problemlos. Ich zei-
ge Ihnen das an einem kleinen Beispiel, indem ich 110012  10102 berechne. Wie üblich
schreibt man die beiden Zahlen untereinander und rechnet von rechts nach links.

1 1 0 0 1
 1 11 01 1 0
1 1 1 1

Sie müssen dabei immer nur darauf achten, alles ganz genauso zu machen wie bei dezi-
malen Zahlen – nur mit dem Unterschied, dass die Basis hier 2 lautet und nicht 10. In
der Einserstelle wollen Sie von 0 auf 1 zählen, was keinerlei Schwierigkeiten macht und
zum Resultat 1 führt. In der Zweierstelle sieht es schon schlechter aus, Sie müssen von
1 auf 0 zählen, und das geht natürlich nicht. Die 0 wird daher als 102 interpretiert, die
1, die Ihnen zu dieser 102 oben fehlt, wird als Übertrag unten in die Viererstelle geschrie-
ben, und in der Ergebniszahl haben Sie eine 1, da 12  12  102 gilt; so hätten sie das bei
vergleichbaren Verhältnissen im Dezimalsystem auch gemacht. Diese Situation wieder-
holt sich aufgrund des Übertrages in der Viererspalte: Das Zählen von der 1 auf die 0
liefert Ihnen einen Übertrag 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 Über-
trag, den mir die Viererstelle eingebrockt hat, steht jetzt bei der unteren Zahl 12  12 
102, und von dieser 102 aus muss ich hochzählen auf 12. Das geht nur dann, wenn diese
duale 12 eigentlich eine 112 ist und ich mir wieder einen Übertrag für die Sechzehner-

56 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Dualzahlen 4

stelle aufhalse. Hier habe ich dann nur noch von der 1 auf die 1 zu zählen, was ich mir
genauso gut sparen kann. Der übliche Test sollte auch beim Subtrahieren nicht fehlen.
Es gilt 110012  25 und 10102  10, also 110012 – 10102  25 – 10  15, und natürlich
gilt 11112  8  4  2  1  15. Wieder ist alles gut gegangen.
Es ist nicht einzusehen, warum ich beim Subtrahieren gutgläubiger sein soll, als ich es
beim Addieren war; ein zweites Beispiel sollte ich schon noch anführen. Berechnen wir
also 1010012 – 111112.

1 0 1 0 0 1
 1 11 11 11 1 1
1 0 1 0

In der Einserstelle passiert gar nichts, denn das Hochzählen von der 1 zur 1 liefert für
die Ergebniszahl eine 0. Auch die Zweierstelle verhält sich nicht ungewöhnlich, ich zäh-
le von 1 auf 0 hoch und erhalte um den Preis eines Übertrags die 1 für die Ergebniszahl.
Immerhin etwas neuer ist die Lage bei der Viererstelle. Wegen des Übertrags habe ich in
der unteren Zahl jetzt 12  12  102, was ich auf eine 0 hochzählen muss – das liefert
natürlich wieder den Übertrag 1 für die Achterstelle und die 0 für die Ergebniszahl. Der
Rest der Rechnung besteht nur noch aus Altvertrautem, weshalb ich nicht mehr näher
darauf 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ätte es auch nicht funktionieren sollen? Schon bei der Addition haben die Du-
alzahlen die Analogie zu den Dezimalzahlen gut vertragen, und bei der Subtraktion ist
es nicht anders.

Man subtrahiert eine kleinere Dualzahl von einer größeren, indem man die kleinere
unter die größere schreibt, von rechts nach links stellenweise subtrahiert und even-
tuell auftretende Überträge in die jeweils nächste Stelle übernimmt.

Auch das ist wie bei den Dezimalzahlen und von daher nichts Besonderes. Ich hatte aber
schon angekündigt, dass es einen zweiten Blick auf die Subtraktion geben würde, nach-
dem der erste Blick sich als etwas überraschungsarm herausgestellt hat. Diese Art des
Subtrahierens hat nämlich zwei Probleme. Beim manuellen Rechnen neigt man dazu,
sich mit den Überträgen zu verheddern, die den meisten Leuten erfahrungsgemäß bei
der Addition leichter fallen als bei der Subtraktion. Das wäre noch nicht weiter schlimm,
denn die ganze duale 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 mithilfe von elektronischen Schaltelementen durchgeführt wird, die man erst
einmal bauen muss, und je weniger verschiedene Schaltelemente man sich überlegen
muss, desto besser. Für die Addition braucht man auf jeden Fall ein Schaltelement, das
bleibt nicht aus. Es wäre aber günstig, wenn man auch mit diesem einen Schaltelement
auskäme und sich nicht noch ein neues für die Subtraktion ausdenken müsste. Ich will
jetzt also daran gehen, eine Subtraktion durchzuführen, ohne wirklich subtrahieren zu
müssen, nur durch Additionen. Am Beispiel einer dezimalen Rechnung zeige ich Ihnen,
wie man die unangenehmen Subtraktionsüberträge vermeiden kann, und im dualen Fall
wird sich sogar der Vorgang des Subtrahierens selbst verflüchtigen und nur Additionen

GDI01 57
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen

zurücklassen. Zunächst einmal das dezimale Beispiel, es geht um 3784 – 1717. Das zu-
gehörige Rechenschema finden Sie hier, anschließend erkläre ich, was beim Rechnen ei-
gentlich passiert ist.

9 9 9
9
 1 7 1
7
8 2 8
2
 3 7 8
4
1 2 0 6
6
 1
1 2 0 6 7
 1 0 0 0 0
2 0 6 7

Erinnern Sie sich daran, dass ich ohne Überträge abziehen wollte? Nun handelt es sich
hier um vierstellige Zahlen, und die einzige vierstellige Dezimalzahl, von der ich mit Si-
cherheit jede vierstellige Dezimalzahl ohne jeden Übertrag subtrahieren kann, ist die
9999. Also rechne ich erst einmal 9999 – 1717. Das ist natürlich die falsche Subtraktion,
also muss ich das Ergebnis irgendwie korrigieren. Addiert man im nächsten Schritt die
gewünschten 3784 und anschließend noch eine 1 dazu, so hat man insgesamt die Rech-
nung
9999 – 1717 3784 1 100003784 – 1717
durchgeführt. Diese Zahl ist also genau um 10000 größer als die gesuchte, weshalb ich
dann auch am Ende noch diese überschüssigen 10000 abziehe und damit das gesuchte
Ergebnis 2067 erhalte. Hier kommen zwar zwei Subtraktionen vor, aber jede ist harmlos.
Die erste erfolgt ohne den leisesten Hauch eines Übertrags, weil von 9999 subtrahiert
wird. Und die zweite kann man durch das Streichen der führenden Ziffer 1 erledigen,
denn Sie müssen hier nur die Zahl 10000 von einer fünfstelligen Dezimalzahl abziehen.
Auf diese Weise konnte ich subtrahieren, ohne wirklich subtrahieren zu müssen.
Sie sehen, das Prinzip ist ganz einfach: Man subtrahiert von der größtmöglichen Zahl
mit der gleichen Anzahl von Ziffern, um lästige Überträge zu vermeiden, und gleicht
den dabei verursachten Fehler später durch Addieren und Streichen einer Eins wieder
aus. Wie ich Ihnen gleich zeigen werde, funktioniert das gleiche Prinzip auch bei Dual-
zahlen mit dem zusätzlichen Vorteil, dass man die erste Subtraktion eigentlich über-
haupt nicht vornehmen muss. Zunächst wieder ein Beispiel, das die Methode illustriert;
ich berechne 110012 – 10102. Damit ich keine Problem mit der Anzahl der Stellen be-
komme, schreibe ich die 10102 als 010102, so haben beide Zahlen fünf Stellen. Nun ha-
ben Sie gerade gelernt, dass man die abzuziehende Zahl von der größtmöglichen Zahl
mit der gleichen Anzahl von Stellen subtrahieren muss. Bei den Dezimalzahlen bestand
diese Riesenzahl aus lauter Neunen, weil 9 nun mal die höchste Ziffer im Dezimalsystem
ist. Und wie lautet die höchste Ziffer im Dualsystem? Das ist die 1, und daher lautet die
größtmögliche fünfstellige Dualzahl schlicht 111112. Das Rechenschema ist dann das
folgende.

58 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Dualzahlen 4

1 1 1 1 1
 0 1 0 0 1
1 0 1 1 0
 1 1 0 1 0
1 0 1 1 0 1
 1
1 0 1 1 1 1
 1 0 0 0 0 0
1 1 1 1

Was ist hier geschehen? Zuerst habe ich ganz nach Plan 010102 von 111112 abgezogen,
und das ging ganz ohne Übertrag. Auf dieses Zwischenergebnis habe ich dann die ur-
sprüngliche Zahl 110012 addiert, um mich wieder der eigentlichen Aufgabe zu nähern.
Natürlich habe ich jetzt 111112 zu viel auf der Rechnung, was ich noch ein wenig ver-
schlimmere, indem ich eine schlichte 12 dazuaddiere. Das führt zu einem Überschuss
von 111112  12  1000002, und das ist das Beste, was mir passieren konnte. Sie sehen
nämlich, dass mein bisheriges Ergebnis 1011112 beträgt, eine Zahl mit einer 1 vorne und
fünf weiteren Stellen. Und davon muss ich 1000002 abziehen, ebenfalls eine 1 vorne mit
fünf anschließenden Nullen. Nichts könnte also leichter sein, als diese überschüssige
1000002 abzuziehen, ich muss dafür nur die führende 1 aus dem bisherigen Ergebnis
1011112 streichen und finde das Endergebnis 11112.
So geht das tatsächlich immer. Die eigentliche Subtraktion erledigen Sie, indem Sie von
der größtmöglichen Zahl mit der passenden Anzahl von Stellen subtrahieren, also von
einer hinreichend langen Ansammlung von Einsen, was immer ohne Übertrag funktio-
niert. Dann addieren Sie noch die ursprüngliche größere Zahl, addieren eine weitere
Eins und streichen aus dem entstandenen Ergebnis die führende Eins heraus, um
schließlich beim Endergebnis zu landen. Schön und gut, aber noch nicht wirklich über-
zeugend. Das Ziel war ja, eine Subtraktion hinzubekommen, ohne subtrahieren zu müs-
sen, nur mit Additionen. Aber auch wenn es vielleicht nicht so aussieht: Dieses Ziel ist
schon erreicht. Bei der Rechnung 111112 – 010102 kam beispielsweise 101012 heraus,
und jetzt vergleichen Sie einmal 01010 mit 10101. Natürlich fällt Ihnen hier auf, dass
man von der ersten Zahl auf die zweite Zahl kommt, indem man die Nullen durch Ein-
sen und die Einsen durch Nullen ersetzt. Das kann auch gar nicht anders sein, denn an
jeder Stelle der Subtraktion subtrahieren Sie von einer dualen 1, da ich als Ausgangs-
punkt der Subtraktion die Zahl 111112 gewählt habe. Nun ist aber 12 – 02  12, 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 Wahr-
heit 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ür einen Namen ausgedacht: Die Summe aus Einserkomplement und
12 heißt Zweierkomplement.

Das Einserkomplement einer Dualzahl erhält man, indem man die Nullen der Zahl
durch Einsen und ihre Einsen durch Nullen ersetzt. Ihr Zweierkomplement erhält
man, indem man zu ihrem Einserkomplement noch 12 addiert.

GDI01 59
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen

Damit haben wir schon alles zusammen, um eine „subtraktionsfreie“ Subtraktion einer
kleineren dualen Zahl von einer größeren durchführen zu können. Im Beispiel haben Sie
gesehen, wie es geht: Man bildet erst das Einserkomplement der abzuziehenden Zahl,
addiert dann die ursprüngliche größere Zahl und addiert auf diese Summe noch 12. An-
ders gesagt: Auf die ursprünglich größere Zahl addiert man das Zweierkomplement der
kleineren. Das ergibt ein bisschen zu viel, aber das macht gar nichts, denn den Über-
schuss kann man beseitigen, indem man die führende Eins streicht.

Sind a und b Dualzahlen und gilt a ≥ b, so berechnet man a – b folgendermaßen.


Falls b weniger Stellen hat als a, ergänzt man b durch führende Nullen und bildet das
Zweierkomplement von b. Dieses Zweierkomplement von b addiert man zu a und
streicht von dem Ergebnis die führende Eins. Das Resultat ist a – b. Ist a  b, so be-
rechnet man b – a und nimmt das Ergebnis negativ.

Die führenden Nullen sind dabei für die Subtraktion sehr bedeutend. Wenn Sie nämlich
vergessen, eine zu kurz geratene Zahl b mit führenden Nullen auf die passende Stellen-
zahl zu bringen, wird das Ihr ganzes Ergebnis ruinieren. Nehmen Sie als Beispiel die
Aufgabe 10012 – 12. Das Einserkomplement der unaufgefüllten 12 lautet natürlich gera-
de 02, und da man darauf noch eine 12 addieren muss, ergibt sich das Zweierkomple-
ment 12. Das Verfahren würde also von mir verlangen, 10012  12  10102 auszurechnen
und davon die führende 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ührende Nullen auf die richtige Form 00012 gebracht hatte. In die-
sem Fall funktioniert nämlich wieder alles; das Zweierkomplement lautet 11102  1 
11112, also hat man die Addition 10012  11112  110002, und das Streichen der führen-
den Eins ergibt das korrekte Resultat 10002.
Sie sehen: Sobald man eine Grundschaltung für die Addition hat, kann man die Subtrak-
tion ebenfalls mithilfe dieser Additionsschaltung durchführen. Damit ist mein Ziel er-
reicht.

Übung 4.4:
Führen Sie die folgenden dualen Rechenvorgänge durch. Subtraktionen sind dabei
mithilfe des Zweierkomplements anzugehen.
a) 11010102  101011102
b) 10102  01101102
c) 111011012 – 1010012
d) 11012 – 111012

4.4 Hexadezimalzahlen und Oktalzahlen


Noch eine Kleinigkeit, dann haben Sie die Rechnerarithmetik hinter sich. Wie Sie sehen
konnten, sind duale Zahlen recht praktisch, da sie nur mit Nullen und Einsen arbeiten,
aber aus dem gleichen Grund können sie ziemlich schnell ziemlich lang werden. Denken
Sie beispielsweise an den Arbeitsspeicher, den auch Ihr Computer besitzt und in dem
sich die aktuell verwendeten Daten befinden. In Krisensituationen kann die Notwendig-

60 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Dualzahlen 4

keit auftreten, sich einen Speicherauszug ausdrucken zu lassen, und der Speicher besteht
nun mal aus einer Unmenge von Einsen und Nullen, keine sehr augenfreundliche Lek-
türe. 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 Dualzahl auf in Viererpäckchen, dann kann
jedes Viererpäckchen jeden Wert zwischen 00002 und 11112 annehmen, dezimal gerech-
net zwischen 0 und 15. Wie Sie unschwer feststellen können, sind das für jedes Vierer-
päckchen 16 mögliche Werte, weshalb man ja auch von Hexadezimalzahlen spricht. Im
Sinne einer einfachen Darstellung braucht man also für jeden Wert zwischen 0 und 15
ein eigenes Zeichen, damit man sie leicht auseinanderhalten kann. Für die Ziffern von 0
bis 9 muss ich da nicht lange suchen und nehme selbstverständlich die Ziffern von 0 bis
9. Nur die Zahlen von 10 bis 15 sollten noch mit Zeichen versehen werden, und dafür
verwendet man die ersten fünf Buchstaben des Alphabets. Man schreibt also A  10,
B  11, C  12, D  13, E  14 und F  15. Damit wird beispielsweise 11111111112 
11111111112  3FF16: Zuerst teilt man die Zahl von rechts nach links in Viererblöcke auf
und dann setzt man diese Viererblöcke in die hexadezimalen Ziffern um. Der vorderste
Block hat nur zwei Stellen, aber das schadet gar nichts, denn 112  00112  3  316. Wei-
terhin ist 11112  15  F16, und so entsteht die Hexadezimalzahl 3FF16. Sie werden zuge-
ben, dass man drei gültige Stellen leichter lesen kann als zehn.

Man erhält den hexadezimalen Wert einer Dualzahl, indem man sie von rechts nach
links in Vierergruppen zusammenfasst und die hexadezimalen Werte dieser Vierer-
gruppen aufschreibt. Umgekehrt erhält man die duale Darstellung einer Hexadezi-
malzahl, indem man die einzelnen hexadezimalen Ziffern in duale Vierergruppen
auflöst.

Zwei Beispiele sollen das Ganze noch abrunden. Zunächst bestimme ich die Hexadezi-
maldarstellung von 110011111012. Die Vierergruppen von rechts nach links lauten
1101, 0111 und 0110, wobei ich ganz vorn eigentlich nur eine Dreiergruppe 110 habe,
aber das Hinzufügen von führenden 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 he-
xadezimale Darstellung etwas übersichtlicher ist und daher beim Lesen von Speicher-
auszügen in aller Regel bevorzugt wird. Nun gehe ich umgekehrt vor und suche die Du-
aldarstellung der Hexadezimalzahl 4FA16. Ich muss jede hexadezimale Ziffer in eine
duale Vierergruppe auflösen.
Es gilt: A16  10  10102, F16  15  11112 und 416  4  0100. Damit folgt: 4FA16 
0100111110102  100111110102
Sie sollten aber nicht glauben, dass Hexadezimalzahlen nur als eine abkürzende Schreib-
weise zu verstehen sind; selbstverständlich handelt es sich auch um schlichte Zahlen ge-
nau wie die Dezimalzahlen oder die Dualzahlen – nur eben zur Basis 16. Deshalb ist bei-
spielsweise
67D16  6 · 162  7 · 161  13 · 160  1661,
denn das hexadezimale D steht für die dezimale Zahl 13, und ansonsten müssen Sie die
16 als Basis einsetzen, die man entsprechend potenzieren muss. Noch ein letztes Beispiel,
dann haben wir das auch schon erledigt.

GDI01 61
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen

Es gilt:
4FA16  4 · 162  15 · 161  10 · 160  1274,
da das hexadezimale F für die Dezimalzahl 15 steht und Sie das hexadezimale A als eine
dezimale 10 ansprechen dürfen.
Nicht ganz so wichtig wie die Hexadezimalzahlen sind die Oktalzahlen, die diesen Na-
men durchaus zu Recht tragen, denn es handelt sich nur um Zahlen zur Basis 8 – die
vierte und letzte Basis für ein Zahlensystem, mit der ich Sie behellige, nach den Basen
10, 2 und 16. Für sie muss man keine neuen Ziffernsymbole erfinden, da man mit den
bekannten Ziffern 0 bis 7 problemlos auskommt. Da das Prinzip nicht anders ist als bei
den Hexadezimalzahlen, darf ich mich hier etwas kürzer fassen.
Da es sich bei den Oktalzahlen um Zahlen zur Basis 8 handelt, können Sie leicht 
vom oktalen in das dezimale System umrechnen. So ist zum Beispiel
4238  4 · 82  2 · 81  3 · 80  275 und 6728  6 · 82  7 · 81  2 · 80  442.
Das ist nichts wirklich Neues. Und auch die Umsetzung in Dualzahlen ist nicht schwer:
Die benötigten Ziffern von 0 bis 7 entsprechen genau den dreistelligen Dualzahlen von
0002 bis 1112, und das bedeutet, dass man zur Umrechnung einer Oktalzahl in das duale
System eben mit Dreierpäckchen arbeiten muss, genau wie bei den Hexadezimalzahlen
Viererpäckchen zum Einsatz kamen.

Man erhält den oktalen Wert einer Dualzahl, indem man sie von rechts nach links in
Dreiergruppen zusammenfasst und die oktalen Werte dieser Dreiergruppen auf-
schreibt. Umgekehrt erhält man die duale Darstellung einer Oktalzahl, indem man
die einzelnen oktalen Ziffern in duale Dreiergruppen auflöst.

Auch das sollte man an einem Beispiel sehen. Ich greife dafür auf die Dualzahl
110011111012 zurück, die ich bereits in die Hexadezimaldarstellung umgewandelt hatte.
Was damals die Vierergruppen geleistet haben, erledigen jetzt die Dreiergruppen, wie-
der in der Reihenfolge von rechts nach links aufgelistet. Sie lauten 1012, 1112, 0012 und
0112, wobei ich die letzte Gruppe gewinnen konnte, indem ich den verbliebenen vorde-
ren zwei Stellen eine 0 vorgesetzt habe. Umgerechnet in oktale Schreibweise ergibt das
die Ziffern 5, 7, 1 und 3, also insgesamt die Oktalzahl 31758. So einfach ist das. Und um-
gekehrt ist es auch nicht schlimmer, wie Sie am Beispiel der Oktalzahl 5728 sehen kön-
nen, denn es gilt:
58  1012, 78  1112 und 28  0102,
also erhalten Sie: 5728  10111110102.
Falls Sie der Auffassung sind, dass noch eine Kleinigkeit fehlt, dann haben Sie völlig
recht. Ich habe nämlich noch kein Wort darüber verloren, wie man eine gegebene Dezi-
malzahl in eine Hexadezimal- oder eine Oktalzahl umrechnet. Aber bei Licht betrachtet
können Sie das schon, denn erstens sind Sie in der Lage, Dezimalzahlen in Dualzahlen
umzurechnen, und zweitens haben Sie gerade gelernt, wie man aus einer Dualzahl die
entsprechende Hexadezimal- oder Oktalzahl herstellt. Natürlich ist das ein zweistufiger

62 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Dualzahlen 4

Prozess, den man vielleicht gerne in einem Schritt zusammenfassen würde. Wie ein Ver-
fahren zur direkten Umrechnung aussieht, werden Sie sich in Übung 4.7 selbst überle-
gen.

Übung 4.5:
Führen Sie eine Umrechnung von einer Dualzahl in eine Hexadezimalzahl bzw. um-
gekehrt durch.
a) Dualzahl 1101100112
b) Hexadezimalzahl ABC16

Übung 4.6:
Führen Sie eine Umrechnung von einer Dualzahl in eine Oktalzahl bzw. umgekehrt
durch.
a) Dualzahl 1101100112
b) Oktalzahl 45678

Übung 4.7:
Entwickeln Sie in Analogie zum Verfahren zur Umrechnung von Dezimalzahlen in
Dualzahlen Methoden zur Umrechnung von Dezimalzahlen in Hexadezimal- bzw.
Oktalzahlen. Testen Sie die Methode für Hexadezimalzahlen an der Dezimalzahl
2367.

4.5 ASCII-Code und Unicode


Nur noch ein kurzer Abschnitt, dann ist wieder ein Kapitel geschafft. Vermutlich ist Ih-
nen aufgefallen, dass ich bisher die Dualzahlen nur benutzt habe, um Zahlen darzustel-
len. Was ist denn nun mit irgendwelchen Zeichen wie zum Beispiel Buchstaben, Satz-
zeichen oder gar Steuerzeichen? Auch die müssen einem Computer mitgeteilt und von
ihm verstanden werden können, und das ist – wie in jeder anderen Kommunikationssi-
tuation auch – nur dann möglich, wenn man sich auf einen gemeinsamen Standard ei-
nigt. Man braucht eine gemeinsame Sprache, damit auch jeder das gleiche Zeichen auf
die gleiche Weise darstellt, und eine solche Übereinkunft nennt man in diesem Falle ei-
nen Code. Zwei dieser Codes sind sehr weit verbreitet und ausgesprochen bekannt: der
ASCII-Code und der Unicode.
ASCII steht für American Standard Code for Information Interchange. Er wurde 1963
vom American National Standards Institute (ANSI) festgelegt und sieht in der ursprüng-
lichen Version siebenstellige Dualzahlen zur Codierung vor. Man spricht hier allerdings
üblicherweise nicht von Dualzahlen, sondern schlicht von sieben Bits. Da es sich aber
trotzdem um Dualdarstellungen handelt und jede der sieben Stellen die Werte 0 und 1
annehmen kann, ist es in der ursprünglichen Form des ASCII-Codes also möglich, 
27  128 Zeichen darzustellen; man muss sich nur darauf einigen, welche duale Darstel-
lung für welches Zeichen stehen soll. Da man aber meistens mit ganzen Bytes, also acht-
stelligen Dualzahlen, arbeiten möchte, ist es heute üblich, acht Bits zu verwenden an-
statt nur sieben, was die Anzahl der darstellbaren Zeichen auf 256 verdoppelt. Diese
zusätzliche Stelle kann man nutzen, um sprachspezifischen Besonderheiten gerecht zu

GDI01 63
© HfB, 02.12.20, Berk, Eric (904709)

4 Dualzahlen

werden, weil zum Beispiel der ursprüngliche ASCII-Code wegen seiner amerikanischen
Wurzeln keine deutschen Umlaute kennt. Man war dabei aber klug genug, die sprach-
spezifischen Erweiterungen so zu regeln, dass sie mit dem ursprünglichen ASCII-Code
weitgehend kompatibel sind, sodass alle im ASCII-Code definierten Zeichen auch in den
verschiedenen Erweiterungen durch die gleichen Bitmuster codiert werden.
Es wäre nicht sehr sinnvoll, wenn Sie den gesamten ASCII-Code auswendig lernen
müssten. Ich gebe Ihnen hier nur das eine oder andere Beispiel. So steht die achtstellige
Dualzahl 000011012 für das Sonderzeichen „cr“, was nichts anderes als „carriage return“
bedeutet und somit die Entertaste repräsentiert. Natürlich kann man sie auch hexadezi-
mal schreiben und erhält dann die Darstellung 0D16 oder dezimal den Wert 13. Die üb-
lichen Großbuchstaben A bis Z entsprechen den fortlaufend hochgezählten Hexadezi-
malwerten 4116 bis 5A16, während die kleinen Buchstaben a bis z durch 6116 bis 7A16
dargestellt werden. Um nur noch zwei Beispiele zu nennen: Das Ausrufungszeichen
wird durch 2116 codiert, das Fragezeichen durch 3F16. Wie Sie schon hieran sehen kön-
nen, ist die Verwendung von Hexadezimalzahlen eine hilfreiche Sache, wenn es um die
übersichtliche Schreibweise von ASCII-Codierungen geht.
Noch hilfreicher werden hexadezimale Darstellungen beim Einsatz des Uni­codes. Be-
rücksichtigt man nämlich, dass es neben den im ASCII-Code abgelegten Zeichen auch
noch ganz andere Zeichen gibt, die sich vollkommen von denen aus unserem Standard-
alphabet unterscheiden, wie zum Beispiel das arabische Alphabet oder gar die mathe-
matischen Sonderzeichen, so kann man leicht einsehen, dass die 256 Zeichen des ASCII-
Codes nicht ausreichen. Deshalb ist man im Rahmen des Unicodes übereingekommen,
mehr als 8 Bit zur Codierung zu verwenden, sich also nicht auf achtstellige Dualzahlen
zu beschränken. Üblicherweise verwendet man dann mehrere Bytes. Mit zwei Bytes, die
sechzehnstelligen Dualzahlen bzw. vierstelligen Hexadezimalzahlen entsprechen, lassen
sich 216  65536, mit vier Bytes sogar 4294967296 verschiedene Zeichen codieren, was
vermutlich für eine Weile reichen dürfte. Es führt aber nicht weiter, sich auf Einzelheiten
des Unicodes einzulassen, denn im Grunde hat sich nichts geändert: Es handelt sich nur
um eine Konvention zur dualen oder auch hexadezimalen Darstellung von Zeichen im
Rechner.

Übung 4.8:
a) Stellen Sie die Zeichenkette ?Informatik! im ASCII-Code in hexadezimaler
Schreibweise dar.
b) Welches Wort wird durch den auf zweistelligen Hexadezimalzahlen beruhenden
ASCII-Code 436F64696572756E67 dargestellt?

64 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

5 Logische Schaltungen und Addierer


In diesem Kapitel lernen Sie, wie man die Arithmetik des vorhergehenden Kapi-
tels konkret in logische Schaltungen umsetzt. Insbesondere lernen Sie drei logi-
sche Grundschaltungen kennen und sehen, wie aus diesen Grundschaltungen ein
Additionswerk aufgebaut werden kann.

Sie wissen jetzt zwar, wie man grundsätzlich so rechnet, als wäre 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ässt. Wir müssen uns also ein paar
Gedanken darüber machen, wie man die Rechenoperationen aus dem letzten Abschnitt
konkret mithilfe von elektronischen Schaltungen umsetzen kann. Glücklicherweise ha-
ben Sie bei der binären Arithmetik gelernt, dass Subtraktionen auf Additionen zurück-
zuführen sind, und man kann sich auch schnell überlegen, dass das Multiplizieren und
Dividieren mithilfe von Additionen und Subtraktionen durchgeführt werden können.
Deshalb werde ich mich hier darauf beschränken, eine Art von Addierwerk zu bauen,
das in der Lage sein soll, duale Zahlen zu addieren.
Das wesentliche Hilfsmittel bei der Realisierung der Addition sind die logischen Schal-
tungenoder 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.

5.1 Die logischen Schaltungen


Aus dem täglichen Sprachgebrauch ist Ihnen die Verwendung des Wortes „und“ mehr
als vertraut, und genau diesen Sprachgebrauch setzt die sogenannte 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 beispiels-
weise zwei duale Inputs, also vielleicht zwei Nullen oder auch eine Null und eine Eins,
so möchte man daraus auf einfache Weise einen dualen Output machen, und eine Mög-
lichkeit 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 realisiert werden, aber auf die tech-
nische Realisierung kommt es hier gar nicht an. Wichtig ist vor allem, dass Sie sehen,
wie Input und Output zusammenhängen: Stehen beide Inputs auf 1, so ergibt sich auch
der Output 1; steht auch nur einer der Inputs auf 0, so wird sich der Output 0 ergeben.
Die UND-Schaltung liefert also genau dann eine 1, wenn alle Inputs auf 1 stehen, an-
sonsten liefert sie eine triste 0.

A
& A∧B
B

Abb. 5.1: Symbol der UND-Schaltung

GDI01 65
© HfB, 02.12.20, Berk, Eric (904709)

5 Logische Schaltungen und Addierer

Das Formelsymbol hat man der Aussagenlogik entnommen, für zwei Inputs schreibt
man A ∧ B, für drei Inputs A,B und C entsprechend A ∧ B ∧ C. Ich will mich hier nicht
über die Rechenregeln für das UND-Symbol ∧ verbreiten, das gehört in die Algebra, Dis-
krete Mathematik oder auch Digitaltechnik. Wir sehen uns lieber an, wie sich die UND-
Schaltung in einer konkreten Tabelle ausdrückt. Schwer ist das nicht, man muss nur alle
möglichen Input-Kombinationen und ihren jeweiligen Output ordentlich in einer Tabel-
le zusammenfassen. Bei drei Inputs sieht das dann wie in Tab. 5.1 aus.

Tab. 5.1: Tabelle der UND-Schaltung

A B C A∧B∧C
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 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 übrigens, wie ich die möglichen Inputwerte in der Tabelle
eingetragen habe. Da es sich um drei Inputs handelt, habe ich es mit dreistelligen dualen
Zahlen zu tun, und deshalb habe ich einfach die dreistelligen dualen Zahlen in die Zeilen
der Tabelle hineingeschrieben. Von 0002 bis 1112, also von 0 bis 7, gibt es aber genau
acht Zahlen, daher hat die Tabelle auch acht Zeilen.
Zur Verdeutlichung hat man sich ein grafisches Symbol überlegt, das die Einzeichnung
von UND-Schaltungen in Schaltbilder zur leichten Übung werden lässt; Sie sehen dieses
für sich sprechende Symbol in Abb. 5.1.

Die UND-Schaltung bestimmt aus mehreren dualen Inputs einen dualen 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üpfung von zwei
Inputs A und B wird mit dem Symbol A ∧ B symbolisiert.

Gehen wir einen Schritt weiter und sehen uns die nächste logische Schaltung an. Das
Wort „oder“ hat in der deutschen Sprache zwei Bedeutungen, auch wenn man sich das
nicht immer klarmacht. Der nette Straßenräuber, der Ihnen auf der B-Ebene der Frank-
furter Konstablerwache den freundlichen Satz „Geld her oder ich schieße“ zuraunt,
meint genau genommen „entweder – oder“, denn sobald Sie Ihre Brieftasche herausrü-
cken, sollte man zumindest hoffen, dass er Sie ungeschoren lässt. Im Zusammenhang
mit der Informatik im Allgemeinen und der Logik sowie den Schaltungen im Besonde-
ren ist etwas anderes gemeint, unser Oder ist ein Oder-auch, ein einschließendes Oder,
sodass mit „A oder B“ immer gemeint ist: A oder B oder beides.

66 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Logische Schaltungen und Addierer 5

Nach dieser Vorrede ist vielleicht schon klar, was man unter einer ODER-Schaltung
bzw. OR-Schaltung zu verstehen hat. Sie liefert genau dann den Output 1, wenn min-
destens 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 Parallelschal-
tung realisiert werden. Wie schon bei der UND-Schaltung interessiert mich hier aber die
logische, nicht die physikalische Seite dieser Schaltung. Ihr Formelsymbol hat man wie-
der der Aussagenlogik entnommen, für zwei Inputs schreibt man A ∨ B, für 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ällen kommt 1 heraus.
An der Menge der möglichen Inputkombinationen ändert sich gar nichts, es sind immer
noch die gleichen acht Möglichkeiten wie bei der Tabelle für die UND-Schaltung. Damit
ergibt sich Tab. 5.2.

Tab. 5.2: Tabelle der ODER-Schaltung

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

Und auch für die ODER-Schaltung hat sich ein eigenes Symbol durchgesetzt, das Sie in
Abb. 5.2 bewundern können.

A
 A∨B
B

Abb. 5.2: Symbol der ODER-Schaltung

Die ODER-Schaltung bestimmt aus mehreren dualen Inputs einen dualen 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üpfung
von zwei Inputs A und B wird mit dem Symbol A ∨ B symbolisiert.

Eine logische Schaltung fehlt noch in der Liste der grundlegenden Schaltungen, nämlich
die NICHT-Schaltung oder auch NOT-Schaltung, und sie ist von allen drei logischen
Grundschaltungen die einfachste – aus einer 1 macht sie eine 0, aus einer 0 dagegen eine

GDI01 67
© HfB, 02.12.20, Berk, Eric (904709)

5 Logische Schaltungen und Addierer

1. Man spricht daher auch von der Negation eines Inputs und meint damit, dass sich der
Wert des Inputs genau in sein Gegenteil verkehrt. Für diese Negation sind sogar zwei
verschiedene Symbole im Gebrauch; ist A ein Input für die NICHT-Schaltung, so wird
der Output von manchen als A und von anderen als ¬A bezeichnet. Beides ist gebräuch-
lich, und Sie können sich aussuchen, was Ihnen besser gefällt.
Die tabellarische Darstellung der Negation ist ausgesprochen übersichtlich, da ich nur
einen Input und einen Output habe. Das führt zu Tab. 5.3.

Tab. 5.3: Tabelle der NICHT-Schaltung

A A
0 1
1 0

Es wird niemanden überraschen, dass sich auch für die NICHT-Schaltung ein Symbol
durchgesetzt hat, das ihre Darstellung in größeren Schaltbildern vereinfacht. In Abb. 5.3
ist es zu sehen.

A 1

Abb. 5.3: Symbol der NICHT-Schaltung

Die NICHT-Schaltung bestimmt aus einem dualen Input einen dualen 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ür den Output A oder auch ¬A.

Übung 5.1:

Weisen Sie mithilfe von Tabellen die Beziehungen A  B  A  B , A  B  A  B


und A  A nach.

Übung 5.2:
Untersuchen Sie, ob es möglich ist, mit nur zwei logischen Grundschaltungen aus-
zukommen anstatt, wie dargestellt, mit drei Grundschaltungen.

So viel zu den Grundschaltungen. Jetzt sehen wir uns an, was man damit anfangen kann.

68 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Logische Schaltungen und Addierer 5

5.2 Halbaddierer und Volladdierer


Mein Ziel ist es, eine Schaltung aufzubauen, die in einem Rechner die duale Addition
realisiert, denn das ist die Grundoperation für die gesamte Arithmetik. Von diesem Ziel
bin ich aber nicht mehr sehr weit entfernt. Was passiert denn, wenn ich beispielsweise
zwei duale Ziffern addiere? Es ergibt sich eine Ergebnisziffer und unter Umständen ein
Übertrag, den ich beim Addieren der nächsten beiden Ziffern berücksichtigen muss. An-
ders gesagt: Ich habe zwei Inputs, nämlich die beiden zu addierenden Ziffern, und zwei
Outputs, nämlich die Ergebnisziffer und den Übertrag, und das kann man in einer Ta-
belle darstellen, genau wie bei den Tabellen der Grundschaltungen. Das gilt aber nicht
nur für die Addition zweier Ziffern. Wann immer Sie eine Zuordnung von einem oder
mehreren dualen Inputs zu einem oder mehreren dualen Outputs haben, können Sie na-
türlich eine Tabelle aufbauen, in deren linkem Teil Sie alle möglichen Inputkombinatio-
nen auflisten und in deren rechtem Teil Sie die jeweiligen Outputs für die jeweilige In-
putkombination aufschreiben. So etwas ist ein Geduldsspiel, weiter nichts. Tabellen
nützen mir aber nichts, ich brauche Schaltungen, die auf meinen drei Grundschaltungen
beruhen. Und schon haben wir ein kleines Problem: Wenn so 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 aus-
schließenden ODER. Die ODER-Schaltung, auf die wir uns vorhin geeinigt hatten, war
ein einschließendes Oder, ein Oder-auch. Es gibt aber auch noch das Entweder-oder, das
viel strenger ist als unser altes Oder-auch: Entweder A oder B kann nur heißen, dass
nicht beide gleichzeitig etwas zum Ergebnis beitragen können, und das bedeutet, das Er-
gebnis ist dann 1, wenn entweder A auf 1 steht oder B auf 1 steht. Sollten beide gleich-
zeitig auf 0 oder beide gleichzeitig auf 1 stehen, muss sich der Output 0 ergeben. Man
bezeichnet das als ein ausschließendes ODER, abgekürzt als XOR, wobei das X für „eX-
clusive“ steht, und verwendet dafür das Symbol A  B . Die tabellarische Darstellung
zeigt Tab. 5.4.

Tab. 5.4: Tabelle der XOR-Schaltung

A B AB
0 0 0
0 1 1
1 0 1
1 1 0

Offenbar liefert A  B genau dann eine 1, wenn entweder A oder B auf 1 steht. Nun
will ich aber nur die drei Grundschaltungen verwenden und nicht alle fünf Minuten ge-
zwungen sein, eine weitere Grundschaltung einzuführen. Folglich muss ich jetzt zuse-
hen, 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, 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

GDI01 69
© HfB, 02.12.20, Berk, Eric (904709)

5 Logische Schaltungen und Addierer

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üsselwörter „und“, „oder“ und die Negation vor, und das waren genau mei-
ne drei logischen Grundschaltungen. Wenn Sie jetzt nämlich die letzte Beschreibung in
eine formale Schreibweise ü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ür das ODER ha-
be, folgt insgesamt:

A  B  ( A  B )  ( A  B ).

Sobald man das einmal gefunden hat, kann man es auch leicht nachprüfen, indem man
einfach für die rechte Seite eine Tabelle aufstellt und die Ergebnisspalte mit der von
A  B vergleicht. Natürlich sind dann beide Spalten identisch.
Damit ist das XOR übersetzt in eine Kombination der drei bekannten und beliebten
Grundschaltungen. Üblicherweise setzt man so etwas um in ein Schaltbild, das den Vor-
teil einer etwas größeren Anschaulichkeit hat. Das Schaltbild für das ausschließende
ODER sehen Sie in Abb. 5.4.

A 1
&
B
⊕ 


1
&

Abb. 5.4: Schaltbild der XOR-Schaltung

Die beiden Inputs heißen A und B. Von A aus gerät man sowohl in ein NICHT-Gatter
als auch – ü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
über eine UND-Schaltung mit B kombiniert, was zu dem Zwischenergebnis A  B
führt. Analog wird der Wert B über die zweite UND-Schaltung kombiniert mit A selbst,
und das ergibt natürlich A  B . Das war es auch schon fast, denn diese beiden Zwi-
schenergebnisse müssen nur noch in einem letzten ODER-Gatter zusammengefasst wer-
den, um am Ende das Ergebnis A  B  ( A  B )  ( B  A ) zu erhalten.

Die XOR-Schaltung bestimmt aus zwei dualen Inputs einen dualen Output. Der Out-
put 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 XOR-Verknüpfung von zwei Inputs A und B
wird mit dem Symbol A  B symbolisiert. Sie kann mithilfe der drei Grundschal-
tungen UND, ODER und NICHT erzeugt werden.

Und weil das XOR so eine schöne Schaltung ist, hat es auch ein eigenes Symbol, das Sie
sich in Abb. 5.5 ansehen können.

70 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Logische Schaltungen und Addierer 5

A
=1 ⊕ B
B

Abb. 5.5: Symbol der XOR-Schaltung

Nun bin ich so weit, dass ich den ersten Addierer bauen kann, den sogenannten Halb-
addierer. Er leistet nichts weiter als die Addition zweier Dualziffern und liefert dabei
eine Ergebnisziffer und einen Übertrag, beides kann 0 oder 1 werden. Da es nun um kon-
krete Ziffern geht, werde ich die Bezeichnungsweise ändern und die beiden Inputziffern
mit x und y bezeichnen, die Ergebnisziffer r nennen und den Übertrag u. Dann ist also
r die Ziffer, die man beim schriftlichen Addieren unter dem Strich finden würde, und u
ist der Übertrag, der für das weitere Rechnen über den Strich geschrieben wird. Das er-
gibt dann Tab. 5.5.

Tab. 5.5: Tabelle des Halbaddierers

x y r u
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1

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üpfung der beiden Inputs x und y, während der Übertrag noch ein-
facher gebaut ist, denn er wird genau dann 1, wenn beide Inputs auf 1 stehen, und stellt
daher nur die UND-Verknüpfung 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ürlich nicht
mehr alle Einzelheiten der XOR-Schaltung aufmalen, die kennen Sie bereits aus
Abb. 5.4. Es genügt jetzt, das XOR als eine Schaltung zu betrachten, die man bereits als
Bauteil zur Verfügung hat, und diese Schaltung als eine Einheit in die gesuchte Schal-
tung des Halbaddierers einzubauen. In Abb. 5.6 sehen Sie, was dabei herauskommt.

x r
=1
y

u
&

Abb. 5.6: Halbaddierer

Ich habe damit eine Schaltung zusammengebaut, die auf der Basis von elektrischem
Strom mithilfe der drei Grundschaltungen zwei Ziffern addiert und eine Ergebnisziffer
sowie einen Übertrag berechnet. Die Schaltung heißt deshalb Halbaddierer, weil sie nur

GDI01 71
© HfB, 02.12.20, Berk, Eric (904709)

5 Logische Schaltungen und Addierer

die Hälfte der Arbeit erledigt, die man beim Addieren von Dualzahlen braucht. Wenn
Sie nämlich zwei duale Zahlen addieren, dann ist es zwar sicher wahr, dass Sie in jeder
Stelle eine Ergebnisziffer und einen Übertrag bekommen werden, aber Sie müssen im-
mer damit rechnen, dass aus der vorher betrachteten Stelle noch ein Übertrag da ist, den
Sie berücksichtigen müssen. Bei Additionen der Form

1 1 1 0 1 1
 1 11 11 11 01 1
1 0 1 1 0 0 0

wird es ziemlich häufig 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 weitertreiben und Ihnen zeigen, wie man einen Volladdierer baut,
der dieses Problem löst.

Der Halbaddierer bestimmt aus zwei dualen Inputs zwei duale Outputs. Er addiert
zwei Ziffern x und y und liefert dabei eine Ergebnisziffer r und einen Übertrag u.
Man kann ihn zusammensetzen aus einer XOR-Schaltung und einer UND-Schal-
tung; es gilt r  x  y und u  x ∧ y.

Bei der Addition zweier mehrstelliger Dualzahlen wird man nicht nur jeweils zwei Zif-
fern x und y, sondern leider auch noch einen alten Übertrag u1 aus der vorherigen Stelle
addieren müssen. Diese Addition aus drei Inputs liefert dann eine Ergebnisziffer r und
einen neuen Übertrag u2, genau wie schon beim Halbaddierer. Die zugehörige Tab. 5.6
ist schnell aufgeschrieben.

Tab. 5.6: Tabelle des Volladdierers

x y u1 r u2

0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1

Jetzt muss ich nur noch feststellen, wie ich diese Tabelle in eine vernünftige Schaltung
umsetzen kann. 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

72 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Logische Schaltungen und Addierer 5

x  y  u1  1 gilt. In diesem Fall habe ich auf jeden Fall r  1. Aber nicht nur in diesem
Fall, es könnte ja auch sein, dass x  0,y  1 und u1  0 gilt, wie es der dritten Zeile mei-
ner Tabelle entspricht. Das bedeutet natürlich x  1, y  1 und u1  1 , also ist dieser Fall
genau dann gegeben, wenn x  y  u1  1 ist. Sie können sich schon denken, wie es wei-
ter geht. In der fünften 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älle erledigt, in denen ge-
nau einer der Inputs auf 1 steht. Es bleibt noch die letzte Zeile der Tabelle, bei der über-
haupt alles zu 1 wird, insbesondere auch x, y und u1. Diese drei haben aber genau dann
gleichzeitig den Wert 1, wenn x  y  u1  1 ist, denn genauso war die UND-Verknüp-
fung gemacht.

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ür solche
Zwecke eine ODER-Verknüpfung 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 zweiten Zeile habe ich dabei nur den ersten Klammerausdruck nach vorn gestellt,
damit ich nicht gleich mit so etwas Negativem wie einer Negation anfangen muss. Jetzt
habe ich meine logische Schaltung für die Ergebnisziffer schon zusammen. Ich werde sie
auch gleich in ein Schaltbild eintragen, aber das liefert nur noch eine grafische Veran-
schaulichung dieser Formel, die Informationen stecken schon vollständig in dem Aus-
druck, den ich aus der Tabelle geschlossen habe. Noch bin ich allerdings nicht fertig, der
Übertrag u2 fehlt mir noch, und er verdient einen genaueren Blick.
Gehen wir zunächst 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 übersetzen kann. In der vierten Zeile ist x  0,y  1 und
u1  1, was zu dem Ausdruck x  y  u1 führt. Nach der gleichen Methode entsprechen
die restlichen interessanten Zeilen den Ausdrücken 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.
Denken Sie daran: Das sollen konkrete Schaltungen in einem Computer werden, und je
weniger Bauteile Sie benötigen, desto besser für die Kostenrechnung. Den Übertrag u2
können Sie aber tatsächlich ein wenig kürzer darstellen, mit einer deutlich einfacheren
Schaltung. Noch einmal ziehe ich die Tabelle zurate und sehe mir genauer an, wann
u2  1 ist. Das gilt nämlich genau dann, wenn mindestens zwei der Inputs auf 1 stehen,
wenn also x ∧ y  1 oder x ∧ u1  1 oder y ∧ u1  1 ist. Sobald eine dieser drei Bedingun-
gen erfüllt ist, können Sie sich auf keinen Fall in der ersten, zweiten, dritten oder fünften
Zeile der Tabelle befinden, denn dort würde jede der drei UND-Verknüpfungen den

GDI01 73
© HfB, 02.12.20, Berk, Eric (904709)

5 Logische Schaltungen und Addierer

Wert 0 ergeben. In der letzten Zeile sind sogar alle drei Bedingungen auf einmal erfüllt,
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 verglei-
chen. Spätestens das wird Sie überzeugen.
Nun habe ich sowohl für die Ergebnisziffer r als auch für den Übertrag u2 eine logische
Schaltung gefunden und muss die beiden Schaltungen nur noch in einem Schaltbild dar-
stellen. Das wird natürlich etwas komplizierter ausfallen als die bisherigen recht über-
sichtlichen Schaltbilder, denn die Schaltungen sind nun mal aufwendiger; sie sehen es in
Abb. 5.7 vor sich.


1

&

1
1
1

&
&  r

&

&
& 
2

&

Abb. 5.7: Volladdierer

Wenn Sie die Verbindungslinien unter Berücksichtigung der dick eingezeichneten Kno-
tenpunkte verfolgen, werden Sie die Übereinstimmung des Schaltbildes mit meinen
mühsam ermittelten logischen Ausdrücken feststellen, und nur darauf kommt es an. Im
unteren Teil des Bildes werden zum Beispiel jeweils zwei Inputs in einer UND-Schaltung
verknüpft, woraufhin die Ergebnisse der UND-Verknüpfungen in einem ODER-Gatter
zusammengeführt werden: Das ist genau die Schaltung, die mir den Übertrag u2 liefert.
Im oberen Teil habe ich zunächst alle drei Inputs in ein UND-Gatter geführt und den
Ausgang dieses UND-Gatters zum Input eines ODER-Gatters werden lassen: Damit
wird x ∧ y ∧ u1 zu einem der vier Inputs des großen ODER-Gatters, und genauso verlangt
es der logische Ausdruck für r. Danach wird jeder der drei Inputs x, y und u1 durch ein
NICHT-Gatter geschickt und so mit den anderen Inputs UND-verknüpft, dass die drei
Ausdrücke 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.
Damit haben Sie nun den Grundbaustein in der Hand. Sie hatten gesehen, dass die ge-
samte duale Arithmetik sich auf die Addition zurückführen lässt, und wie man eine Ad-
dition von dualen Ziffern umsetzt, habe ich Ihnen gerade gezeigt.

74 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Logische Schaltungen und Addierer 5

Der Volladdierer bestimmt aus drei dualen Inputs zwei duale Outputs. Er addiert
zwei Ziffern x und y zu einem Übertrag u1 und liefert dabei eine Ergebnisziffer r und
einen Übertrag 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 ).

Aber wie werden denn jetzt ganze Dualzahlen addiert, wie wird man die Beschränkung
auf Ziffern los? Das ist ganz einfach. Nehmen wir an, dass unser Rechner in der Lage
sein soll, vierstellige Zahlen zu addieren, so muss ich nur die beiden entwickelten Schal-
telemente „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 Dualzahlen x3x2x1x0 und
y3y2y1y0. Dann ergibt sich das Additionswerk aus Abb. 5.8; die Inputs stehen über dem
Additionswerk, der Output r4r3r2r1r0 setzt sich aus den Ergebnissen zusammen, die von
den einzelnen Additionsschaltungen geliefert werden.

x3 y3 x2 y2 x1 y1 x0 y0

VA VA VA HA

u3 u2 u1

r4 r3 r2 r1 r0

Abb. 5.8: 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 Übertrag u1 wird an die nächste Stelle weitergereicht. Für die nächste Addi-
tion brauche ich dann auch tatsächlich einen Voll­addierer, denn hier habe ich die bei-
den Inputziffern x1, y1 und den Übertrag u1 der vorherigen Addition zu addieren. Die
Ergebnisziffer r1, die der Volladdierer liefert, wird wieder bei den Ergebnissen notiert,
der Übertrag u2 zur nächsten ziffernweisen Addition weitergegeben. Und so zieht sich
das Spiel durch, bis das Ende in Gestalt der letzten Stelle erreicht ist. Hier gibt es natür-
lich keinen Übertrag mehr, denn es steht keine weitere Stelle mehr zur Verfügung, mit
der gerechnet werden könnte. Ob dann die Ergebnisziffer r4 wirklich zu den Ergebnissen
gerechnet wird, hängt ab von der tatsächlichen Architektur Ihres Rechenwerks: Wenn
beispielsweise durchgängig nur mit vier Stellen gerechnet wird, haben Sie im Falle r4 
1 einen sogenannten Überlauf und r4 geht verloren. Man sollte deshalb immer eine Stel-
le mehr zur Verfügung haben, als man zu brauchen glaubt, denn für gewöhnlich wird
ein Überlauf im Dunkel der Nacht verschwinden.

GDI01 75
© HfB, 02.12.20, Berk, Eric (904709)

5 Logische Schaltungen und Addierer

Ein Additionswerk oder auch Addierwerk setzt sich zusammen aus einem Halbad-
dierer und mehreren Volladdierern, wobei die Anzahl der Volladdierer von der An-
zahl der gewünschten Stellen der verwendeten Dualzahlen abhängt. Mit ihm ist es
möglich, Dualzahlen nach den Regeln der dualen Arithmetik auf der Basis der drei
logischen Grundschaltungen zu addieren.



&

&

r
&
1

1

1

Abb. 5.9: Schaltung

Übung 5.3:

Stellen Sie für r  (x  y )  z eine Funktionstabelle auf. Vergleichen Sie die Ergeb-
nisse mit denen der Ergebnisziffer beim Volladdierer. Wie kann man mithilfe von
(x  y )  z das Schaltbild des Volladdierers vereinfachen?

Übung 5.4:
Setzen Sie die Schaltung aus Abb. 5.9 in eine Tabelle um.

Übung 5.5:
Setzen Sie Tab. 5.7 mit den Inputs x,y und dem Output r in eine logische Schaltung
um. Wie hängt diese Schaltung mit der XOR-Schaltung zusammen?

Tab. 5.7: Funktionstabelle

x y r
0 0 1
0 1 0
1 0 0
1 1 1

Genug geschaltet, und damit Sie auch wirklich ein mal abschalten können, darf ich Ih-
nen mitteilen, dass Sie nun diese Einführung in die Informatik hinter sich gebracht ha-
ben.

76 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

A. Lösungen der Übungen im Text


1.1 Diese Aufgabe setzt ein wenig Mathematik aus der Mittelstufe voraus.
• Eingabe:
• Bekanntgeben, dass es sich um ein lineares Gleichungssystem handelt, zum
Beispiel durch Eingabe des passenden Programmnamens.
• Zahl der Gleichungen eingeben (2).
• Zahl der Unbekannten eingeben (2).
• Koeffizienten der linken Seite a,b,c,d in der richtigen Reihenfolge einge-
ben.
• Koeffizienten der rechten Seite e,f in der richtigen Reihenfolge eingeben.
• Verarbeitung:
• Berechnen der „Determinante“ ad – bc.
• Falls ad – bc  0, gibt es keine eindeutige Lösung.
ed  bf af  ec
• Falls ad  bc  0 , gilt x  und y  .
ad  bc ad  bc
• Ausgabe:
• Falls es eine eindeutige Lösung gibt, Ausgeben von x und y in der richti-
gen Reihenfolge.
• Falls es keine eindeutige Lösung gibt, Ausgeben einer entsprechenden
Meldung.
1.2 Mögliche Nachteile sind:
• Hohe Anschaffungskosten und hohe laufende Kosten durch häufigen Wech-
sel der Technologie.
• Schwer zu entdeckende Fehler durch kleine Programmierfehler.
• Blindes Vertrauen der Benutzer auf eventuell fehlerhafte Entscheidungen des
Computers.
• Fehlende Kreativität beim maschinellen Lösen von Problemen.
• Abschreckung eventueller Kunden durch maschinelle Abfertigung.
• Verlust von Arbeitsplätzen.
1.3 a) Der Reisende hat für seine erste Stadt nach Frankfurt genau 19 Möglichkei-
ten zur Auswahl, denn so viele Städte stehen auf seiner Liste. Sobald er sich
auf irgendeine erste Stadt festgelegt hat, bleiben ihm für die zweite Stadt nur
noch 18 Möglichkeiten, denn in der ersten ist er schon gewesen und jede
Stadt soll nur einmal angefahren werden. Für die ersten beiden Städte gibt es
daher genau 19 · 18 Kombinationsmöglichkeiten. Sobald die ersten beiden
Städte festliegen, gibt es für die Auswahl der dritten Stadt nur noch 17 Mög-
lichkeiten, da zwei Städte schon angefahren wurden und jede der ursprüng-
lich 19 Städte nur einmal besucht werden soll. Für die ersten drei Städte gibt
es daher genau 19 · 18 · 17 Kombinationsmöglichkeiten. Auf diese Weise re-

GDI01 77
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Übungen im Text

duziert sich die Anzahl der noch zur Auswahl stehenden Städte von Station
zu Station um 1, sodass jeweils eine um 1 geringere Zahl zum bereits vorhan-
denen Produkt dazu multipliziert werden muss. Insgesamt hat man daher
19 · 18… 2 · 1  19! verschiedene mögliche Reiserouten.
b) Der gegebene Rechner kann 1010 Routen pro Sekunde testen. Um festzustel-
len, wie lange er für die Ermittlung der optimalen Route braucht, muss man
einen einigermaßen genauen Wert für 19! ausrechnen; es gilt 19! ≈ 1,216451
· 1017. Diese Anzahl der möglichen Reiserouten muss man durch die Anzahl
der überprüfbaren Routen pro Sekunde teilen. Das ergibt
1,216451  1017
10
 1,216451  107 . Da ein Jahr 31536000  3,1536 · 107 Sekun-
10
1,216451  107
den hat, braucht der gegebene Computer  0,3857 Jahre, also
3,1536  107
etwa 141 Tage.
1.4 Sofern a0 ist, wird a auf den Wert 2 gesetzt, ansonsten behält a seinen Wert.
Die Abfrage, ob a0 gilt, ist völlig sinnlos, da an dieser Stelle schon a0 fest-
gestellt wurde. Man kann also diese Abfrage weglassen.
1.5 Betrachtet man nur den reinen Text, so sind beide Algorithmen identisch. Der
einzige Unterschied liegt in der Einrückung, und genau dadurch wird der we-
sentliche inhaltliche Unterschied zum Ausdruck gebracht. In Algorithmus 1 wird
nur beschrieben, was zu tun ist, wenn die Ampelanlage funktioniert: Falls dann
die Ampel rot oder gelb ist, soll der Fahrer stehen bleiben, ansonsten fahren. Das
Wort sonst“ in diesem Algorithmus bezieht sich also, wie man nur an der Einrü-
ckung sehen kann, auf den „wenn“-Fall, dass die Ampel rot oder gelb ist, be-
schreibt also die Situation, dass eine funktionierende Ampel grün ist. Algorith-
mus 2 arbeitet ganz anders. Zunächst beschreibt auch er die Handlungsfolge im
Falle einer funktionierenden Ampel: Falls die Ampel rot oder gelb ist, soll der
Fahrer stehen bleiben. Weiter verrät dieser Algorithmus nichts über das Verhal-
ten bei einer funktionierenden Ampel, denn der sonst“-Zweig in der letzten Zeile
bezieht sich aufgrund der Einrückung auf die Situation, dass die Ampelanlage
nicht funktioniert, stellt also den sonst“-Zweig zu der übergeordneten Bedin-
gung dar, dass die Ampelanlage funktioniert. Algorithmus 2 weist also den Fah-
rer an, bei einer nicht funktionierenden Ampelanlage einfach loszufahren. Im
Falle einer funktionierenden Anlage erhält der Fahrer zwar bei roter oder gelber
Ampel klare Anweisungen, weiß aber nicht, was er bei einer grünen Ampel zu
tun hat. Dagegen hat Algorithmus 1 das Verhalten bei einer funktionierenden
Anlage vollständig beschrieben, den Fahrer aber mit der Situation einer nicht
funktionierenden Anlage allein gelassen. Die beiden Algorithmen unterscheiden
sich also dann, wenn die Ampelanlage funktioniert und die Ampel grün ist (Al-
gorithmus 1 empfiehlt dann fahren, Algorithmus 2 schweigt sich aus) und wenn
die Ampelanlage nicht funktioniert (Algorithmus 1 sagt gar nichts, Algorithmus
2 empfiehlt fahren). Obwohl also beide Algorithmen keineswegs vollständig
sind, ist aus Sicherheitsgründen eher Algorithmus 1 zu empfehlen.
1.6 Sobald das Programm von Zeile 03 zu Zeile 07 gesprungen ist, ergibt sich immer
wieder der folgende Ablauf: 07 → 04 → 05 → 06 → 03 → 07 → 04 → …. Das
Programm befindet sich daher in einer Endlosschleife.

78 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Übungen im Text A

2.1 Das Struktogramm findet sich in Abb. A.1.

Umrechnen
enachd = 1,074
Ausgabe:
Euro nach Dollar (1)?
Dollar nach Euro (2)?
Einlesen antwort
antwort = 1?
wahr falsch
Ausgabe: antwort = 2?
euro eingeben wahr falsch
euro eingeben Ausgabe:
dollar eingeben
dollar = euro * enachd
dollar eingeben Ausgabe
Fehlermeldung
dollar ausgeben euro = dollar / enachd
euro ausgeben

Abb. A.1: Struktogramm zu Übung 2.1

2.2 Das Struktogramm findet sich in Abb. A.2.

Steuer
einkommen eingeben
einkommen <= 600 ?
wahr falsch
einkommen <= 1200 ?
wahr falsch
e = einkommen 1200
Ausgabe: steuer = einkommen > 15000 ?
keine Steuern 0,3 * (einkommen 600) wahr falsch
steuer = steuer =
180 + 0,5*e 180 + 0,4*e
steuer ausgeben

Abb. A.2: Struktogramm zu Übung 2.2

2.3 Das Struktogramm findet sich in Abb. A.3

GDI01 79
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Übungen im Text

Messung
summeplus=0,summeminus=0,summe=0

anzahlplus=0,anzahlminus=0,anzahl=0

eingeben x

summe = summe + x

anzahl = anzahl +1

x>0?
wahr falsch

summeplus = 
summeplus + x wahr falsch

anzahlplus = summeminus = summeminus + x


keine Aktion
anzahlplus + 1 anzahlminus = anzahlminus + 1

eingeben x

 

anzahlplus > 0 ?
wahr falsch
Ausgabe:
Ausgabe: keine positiven Werte
summeplus, summeplus/anzahlplus

anzahlminus > 0 ?
wahr falsch
Ausgabe:
Ausgabe: keine negativen Werte
summeminus, summeminus/anzahlminus

anzahl > 0 ?
wahr falsch
Ausgabe: Ausgabe: keine Werte
summe, summe/anzahl

Abb. A.3: Struktogramm zu Übung 2.3

2.4 Das Struktogramm findet sich in Abb. A.4

Lineare Gleichungen
Ausgabe: Lösen linearer Gleichungen
eingeben von a und b

a=0?
wahr falsch
b=0?  
wahr falsch
Ausgabe: Ausgabe: Ausgabe: x
allgemeingültig unlösbar
Ausgabe: weitere Gleichung?
eingeben antwort
solange antwort = 'j' oder antwort = 'J'

Abb. A.4: Struktogramm zu Übung 2.4

80 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Übungen im Text A

2.5 Die Klasse lässt sich folgendermaßen programmieren.


class Linear
{
double a;
double b;

double loesen()
{
return -b/a;
}
}

2.6 Die Klasse braucht mindestens die beiden Attribute breite und hoehe, die
den Typ double haben sollten. Zur Berechnung der Fläche braucht man eine
Methode flaeche, die den Wert breite * hoehe zurückgibt; zur Berech-
nung des Umfangs braucht man eine Methode umfang, die den Wert
2*(breite  hoehe) zurückgibt. Hat man dann ein konkretes Objekt
meinrechteck der Klasse Rechteck gegeben, so kann man die entsprechen-
den Berechnungen mithilfe von meinrechteck.umfang() und
meinrechteck.flaeche() vornehmen.

2.7 Die zusätzliche Methode lautet:


String kommentieren()
{
if (a  0)
{
if (b  0)
return "Die Gleichung ist allgemeingueltig";
else
return "Die Gleichung ist unloesbar";
}
else
return "Alles klar";
}

2.8 a) Der Algorithmus startet mit einer Ausgabe, einer Eingabe und einer Abfrage.
Für n0 erfolgen zwei Wertzuweisungen und n Schleifendurchläufe, wobei
in jedem Durchlauf zwei Additionen und eine Abfrage stattfinden. Insge-
samt sind das 3  1  1  3n  3n  5 Operationen.
b) Nach Teil a) ist T(n)  O(n).
2.9 a) Da man sich beim Einsatz der Landausymbole auf die höchste vorkommende
Potenz beschränken kann, gilt TA(n)  O(n2) und TB(n)  O(n3).
b) Für n  256 braucht Algorithmus A genau 32767984 Operationen, Algorith-
mus B dagegen 8390023 Operationen, wie man durch Einsetzen in die For-
meln feststellt. Daher ist für n  256 Algorithmus B vorzuziehen.

GDI01 81
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Übungen im Text

c) Es gilt:

1 3 11 1 11
n  n  7  (500n 2  16)  n 3  500n 2  n  23
2 2 2 2
1
  (n 3  1000n 2  11n  46).
2
Für n ≥ 1024  1000 ist aber n3 – 1000n2  n2 · (n – 1000) 0 und natürlich
1 11
auch 11n  46  0. Daher ist in diesem Fall n 3  n  7  500n 2  16 , wes-
2 2
halb Algorithmus A vorzuziehen ist.
3.1 • Vorteile:
– niedrige Kosten
– Haltbarkeit
– auch ohne Maschine lesbar
– leicht zu beschreiben
• Nachteile:
– Dateneingabe zeitraubend
– Dateneingabe fehleranfällig
– platzraubende Lagerung
– niedrige Einlesegeschwindigkeit
3.2 Gödels Aussage ist im Prinzip richtig, da jedes Computerprogramm nichts ande-
res als Logik braucht. Schließlich baut es auf den beiden Zuständen „kein Strom
fließt“ und „Strom fließt“ auf, die man durch die beiden Zustände 0 und 1 be-
schreiben kann, was den beiden Wahrheitswerten „falsch“ und „wahr“ ent-
spricht. In der Praxis ist Gödels Auffassung aber unbrauchbar, denn ein direktes
Programmieren eines Computers nur auf der Basis von Nullen und Einsen ist ex-
trem aufwendig und fehleranfällig.
3.3 Bei einem Band ohne Einsen kommt zunächst die Programmzeile
z 0 , z E ,, H zum Einsatz. Es wird zu Beginn ein Leerzeichen gelesen, da-
nach geht die Maschine in den Endzustand über, schreibt das Leerzeichen wieder
auf das Band und hält an. Wegen 2 · 0  0 wurde die Anzahl der Einsen verdop-
pelt. Bei nur einer 1 startet die Maschine mit der ersten Programmzeile, geht in
den Zustand z1 über, schreibt ein a anstelle der 1 und rückt nach rechts. Da sie
dort auf eine Leerstelle trifft, zieht nun die dritte Programmzeile, nach der die
Maschine in den Zustand z2 übergeht, die Leerstelle durch ein b ersetzt und nach
links rückt. Dort agiert die siebte Programmzeile, ersetzt das a durch eine 1, geht
in den Zustand z0 über und rückt wieder nach rechts, wo die Turing-Maschine
wegen der achten Programmzeile auch das b durch eine 1 ersetzt, im Zustand z0
verharrt und nach rechts rückt. Dort trifft sie im Zustand z0 auf eine Leerstelle
und wird daher im letzten Schritt in den Endzustand übergehen und anhalten.

82 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Übungen im Text A

3.4 Das Turing-Programm lautet:

z 0 ,1  z 0 ,1, R
z 0 ,   z 0 ,1, R
z 0 ,  z1 ,, L
z1 ,1  z E ,, H

3.5 a) Ein Computer wäre auch ohne einen ausgeprägten Arbeitsspeicher denkbar,
wenn man dafür sorgt, dass zumindest das jeweils zur Verarbeitung benötig-
te Element in der Zentraleinheit gespeichert werden kann. Liegt also eine
kleine Zahl hinreichend großer Speicherstellen in der Zentraleinheit vor, so
kann ein Computer im Prinzip auch ohne Arbeitsspeicher zum Einsatz kom-
men. Sinnvoll wäre das allerdings nicht, denn es führt dazu, dass andauernd
Schreib- und Lesevorgänge auf die Festplatte und von der Festplatte durch-
geführt werden müssen, was die Verarbeitungsgeschwindigkeit stark redu-
zieren würde.
b) Ersetzt man die Festplatte durch eine andere Form von Festspeicher, so ist
auch ein Computer ohne Platte denkbar. Nimmt man ihm aber jede Form des
Festspeichers, so sind die Schwierigkeiten kaum noch zu beheben. Zwar
wäre es möglich, Daten und Programme über Lochkarten ein- und sogar
wieder auszugeben, aber von einem in irgendeiner Form modernen Compu-
ter wäre man mit dieser Konstruktion weit entfernt.
Insgesamt ergibt sich, dass ein moderner Computer sowohl über einen Fest-
speicher als auch über einen Hauptspeicher verfügen sollte.
3.6 Der Arbeitsspeicher dient nur der Aufnahme von Daten und Programmstücken,
ein Rechenwerk ist wegen seiner weitergehenden Aufgaben deutlich komplizier-
ter. Schon um die Konstruktion nicht zu kompliziert werden zu lassen, ist eine
Aufteilung daher sinnvoll. Außerdem wird stets nur mit kleinen Datenmengen
auf einmal gerechnet, sodass es völlig unnötig wäre, den gesamten Arbeitsspei-
cher mit den Fähigkeiten des Rechenwerks zu versehen. Das Argument, man
könnte doch im Gegenzug den Arbeitsspeicher klein halten, ist nicht schlüssig,
wie schon in der vorherigen Aufgabe diskutiert wurde.
3.7 Der Speicher enthält 1 Gigabyte, also 230 Byte. Um 12:01 Uhr sind 1  4 Byte be-
setzt, um 12:02 Uhr 1  4  16  1  4  42 Byte und so weiter. Also sind nach
n Minuten

4n 1  1 22n 2  1
1  4  4 2    4n  
3 3
Byte im Speicher besetzt, wobei die Summenformel der geometrischen Summe
entspricht. Gesucht ist also das größte n mit der Eigenschaft:

22n 2  1
 230 beziehungsweise 22n 2  3  230  1.
3

GDI01 83
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Übungen im Text

Für n  14 ist die Ungleichung offenbar erfüllt, wie man durch Einsetzen sofort
feststellt. Für n  15 steht auf der linken Seite 232  4  230  3  230  1 , weshalb
n  14 die gesuchte Zahl ist.

230  1
Um 12:14 Uhr muss der Prozess also gestoppt werden, und es sind Byte
3
im
4.1 a) 1101100112  1  2  16  32  128  256  435.
b) 111100012  1  16  32  64  128  241.

4.2 Gegeben seien zwei Dualzahlen m und n, die ohne führende Nullen notiert wer-
den. Hat m mehr Ziffern als n, so ist m  n. Hat n mehr Ziffern als m, so ist nm.
Ist die Anzahl der Ziffern von m und n gleich, so vergleiche man, beginnend mit
den führenden Ziffern, die Ziffern von m und n, die an jeweils der gleichen Po-
sition stehen. Sind sie jeweils gleich, so gilt m  n. Ansonsten gibt es eine Posi-
tion, bei der zum ersten Mal ein Unterschied auftritt. Hat an dieser Position m
die Ziffer 1 und n die Ziffer 0, so ist mn. Hat dagegen an dieser Position n die
Ziffer 1 und m die Ziffer 0, so ist n  m.
4.3 Es gilt:

278 : 2  139 Rest 0


139 : 2  69 Rest 1
69 : 2  34 Rest 1
34 : 2  17 Rest 0
17 : 2  8 Rest 1
8 : 2  4 Rest 0
4 : 2  2 Rest 0
2 : 2  1 Rest 0
1 : 2  0 Rest 1

Also ist 278  1000101102. Auch n  131 kann man auf diese Weise behandeln.
Man sieht aber, dass 131  128  2  1  27  21  20 gilt, und daher ist
131  100000112.
4.4 a) Die Addition ergibt 11010102  101011102  1000110002.
b) Die Addition ergibt 10102  01101102  10000002.
c) Gesucht ist 111011012 – 1010012. Das Zweierkomplement der Zahl
001010012 ist 110101102  12  110101112. Die Addition ergibt 111011012
 110101112  1110001002
Streichen der führenden 1 ergibt dann 111011012 – 1010012  110001002.

84 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Übungen im Text A

d) Gesucht ist 11012 – 111012, also –(111012 – 11012. Das Zweierkomplement


von 011012 ist 100102  12  100112.
Die Addition ergibt: 111012  100112  1100002.
Streichen der führenden 1 ergibt dann 111012 – 11012  100002, 
also 11012 – 111012  100002.
4.5 a) Aufteilung in Viererblöcke:
1101100112  0001101100112.
Wegen 00012  116, 10112  B16 und
00112  316 folgt
1101100112  1B316.
b) Gegeben ist ABC16. Aus A16  10102, B16  10112 und C16  11002 folgt ABC16
 1010101111002.
4.6 a) Aufteilung in Dreierblöcke:
1101100112  1101100112.
Wegen 1102  68 und 0112  38
folgt 1101100112  6638.
b) Wegen 48  1002, 58  1012, 68  1102 und 78  1112 folgt
45678  1001011101112.
4.7 Das folgende Verfahren rechnet eine Dezimalzahl m in ihre Hexadezimaldarstel-
lung anan–1…a1a0 um.
• Man dividiere m durch 16 und notiere den Rest bei der Division.
• Man dividiere das Divisionsergebnis durch 16 und notiere wieder den Rest
bei dieser Division.
• Man wiederhole den letzten Schritt, bis als Divisionsergebnis der Wert 0 auf-
tritt.
• Man schreibe die aufgetretenen Reste in der umgekehrten Reihenfolge ihres
Auftretens als Hexadezimalzahl.
Ersetzt man die Basis 16 durch die Basis 8, so hat man das Verfahren zur Um-
rechnung in Oktalzahlen.
Es gilt:

278 : 2  139 Rest 0


139 : 2  69 Rest 1
69 : 2  34 Rest 1

2367 : 16  147 Rest 15


147 : 16  9Rest 3
9 : 16  0Rest 9.

Wegen 15  F16 ergibt sich 2367  93F16.

GDI01 85
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Übungen im Text

4.8 a) Die beiden Zeichen „?“ und „!“ sind im Text mit der Codierung 3F bzw. 21
angegeben. Da die Buchstaben mit fortlaufender Nummerierung codiert
werden und die Codierungen der Buchstaben „A“ und „a“ mit 41 bzw. 61 an-
gegeben sind, ergibt sich insgesamt die hexadezimale Darstellung
3F496E666F726D6174696B21.
b) Hier ist das Wort „Codierung“ dargestellt.
5.1 Die nötigen Werte finden Sie in Tab. A.1.

Tab. A.1: Tabelle zu Übung 5.1

A B AB AB AB AB AB AB A A


0 0 0 1 1 0 1 1 1 0
0 1 1 0 0 0 1 1 1 0
1 0 1 0 0 0 1 1 0 1
1 1 1 0 0 1 0 0 0 1

5.2 Die drei logischen Grundschaltungen lauten UND, ODER und NICHT. Nach
Übung 5.1 ist aber beispielsweise A  B  A  B und damit A  B  A  B .
Man kann also die UND-Schaltung aus der ODER-Schaltung und der NICHT-
Schaltung kombinieren, sodass nur zwei Schaltungen nötig sind. Entsprechend
folgt aus A  B  A  B , dass man die ODER-Schaltung aus der UND-Schaltung
und der NICHT-Schaltung kombinieren kann.
5.3 Die nötigen Werte finden Sie in Tab. A.2.
Der Vergleich mit der Ergebnisziffer r des Volladdierers ergibt, dass
r  (x  y )  u1 gilt.

Setzt man also die XOR-Schaltung voraus, so kann man die Ergebnisziffer im
Volladdierer als Kombination zweier XOR-Schaltungen realisieren. Das verein-
fachte Schaltbild sehen Sie in Abb. A.5.


1

=1

=1 r

&
& 
2
&

Abb. A.5: Volladdierer

86 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Übungen im Text A

Tab. A.2: Tabelle zu Übung 5.3

x y z x y (x  y )  z
0 0 0 0 0
0 0 1 0 1
0 1 0 1 1
0 1 1 1 0
1 0 0 1 1
1 0 1 1 0
1 1 0 0 0
1 1 1 0 1

5.4 Die logische Formel zu der Schaltung lautet

((x  y  z )  (x  y  z ))  (x  y ).

Zur Abkürzung setze ich r1  x  y  z , r2  x  y  z , r3  r1  r2 , r4  x  y


und r  r3  r4 . Das ergibt Tab. A.3.

Tab. A.3: Tabelle zu Übung 5.4

x y z r1 r2 r3 r4 r

0 0 0 0 0 0 1 0
0 0 1 0 0 0 1 0
0 1 0 0 0 0 1 0
0 1 1 0 0 0 1 0
1 0 0 0 0 0 1 0
1 0 1 0 0 0 1 0
1 1 0 0 1 1 0 0
1 1 1 1 0 1 0 0

Die Ergebnisspalte besteht nur aus Nullen. Tatsächlich sieht man an der Tabelle,
dass r3  x  y gilt, und es gilt x  y  x  y . Somit ist r4 die Negation von r3,
weshalb r  r3  r4 immer den Wert 0 haben muss.

GDI01 87
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Übungen im Text

5.5 Die logische Formel für die Tabelle lautet:

r  (x  y )  (x  y ).

Sie wird durch das Schaltbild aus Abb. A.6 dargestellt.

Offenbar ist r  x  y .

x 1
&
y 1



&

Abb. A.6: Schaltbild

88 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

B. Literaturverzeichnis
Ernst, H.; Schmidt, J.; Beneken, G.: (2015). Grundkurs Informatik. 
5. Aufl., Heidelberg: Springer Vieweg.
Gumm, H. P.; Sommer, M.: (2013). Einführung in die Informatik. 
10. Aufl., Berlin: Oldenbourg Wissenschaftsverlag.
Herold, H.; Lurz, B. (2012). Grundlagen der Informatik. 
2. Aufl., Pearson Studium.
Rechenberg, P. (2000). Was ist Informatik? 
3. Aufl., München: Hanser Fachbuch.
Spannagel, Ch (2013). Turingmaschinen.
http://wikis.zum.de/zum/PH_Heidelberg/Bausteine/Turingmaschinen.

GDI01 89
© HfB, 02.12.20, Berk, Eric (904709)

C. Abbildungsverzeichnis
GDI01

Abb. 2.1 Struktogramm einer Sequenz ..................................................................... 17


Abb. 2.2 Struktogramm zur Währungsumrechnung ............................................... 17
Abb. 2.3 Struktogramm zur Auswahl ....................................................................... 19
Abb. 2.4 Struktogramm zur Auswahl ....................................................................... 19
Abb. 2.5 Struktogramm zur nicht abweisenden Schleife ........................................ 21
Abb. 2.6 Struktogramm zur Summenbildung .......................................................... 23
Abb. 2.7 Struktogramm zum Primzahltest ............................................................... 32
Abb. 2.8 Struktogramm zum verbesserten Primzahltest ......................................... 33
Abb. 3.1 Band der Turing-Maschine ......................................................................... 39
Abb. 3.2 Band der Turing-Maschine ......................................................................... 40
Abb. 3.3 Architektur nach John von Neumann ....................................................... 44
Abb. 5.1 Symbol der UND-Schaltung ....................................................................... 65
Abb. 5.2 Symbol der ODER-Schaltung ..................................................................... 67
Abb. 5.3 Symbol der NICHT-Schaltung ................................................................... 68
Abb. 5.4 Schaltbild der XOR-Schaltung .................................................................... 70
Abb. 5.5 Symbol der XOR-Schaltung ........................................................................ 71
Abb. 5.6 Halbaddierer ................................................................................................. 71
Abb. 5.7 Volladdierer .................................................................................................. 74
Abb. 5.8 Additionswerk .............................................................................................. 75
Abb. 5.9 Schaltung ...................................................................................................... 76
Abb. A.1 Struktogramm zu Übung 2.1 ...................................................................... 79
Abb. A.2 Struktogramm zu Übung 2.2 ...................................................................... 79
Abb. A.3 Struktogramm zu Übung 2.3 ...................................................................... 80
Abb. A.4 Struktogramm zu Übung 2.4 ...................................................................... 80
Abb. A.5 Volladdierer .................................................................................................. 86
Abb. A.6 Schaltbild ...................................................................................................... 88
Abb. F.1 ....................................................................................................................... 95

90 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

D. Tabellenverzeichnis
GDI01

Tab. 5.1 Tabelle der UND-Schaltung ......................................................................... 66


Tab. 5.2 Tabelle der ODER-Schaltung ....................................................................... 67
Tab. 5.3 Tabelle der NICHT-Schaltung ..................................................................... 68
Tab. 5.4 Tabelle der XOR-Schaltung .......................................................................... 69
Tab. 5.5 Tabelle des Halbaddierers ............................................................................ 71
Tab. 5.6 Tabelle des Volladdierers .............................................................................. 72
Tab. 5.7 Funktionstabelle ............................................................................................ 76
Tab. A.1 Tabelle zu Übung 5.1 .................................................................................... 86
Tab. A.2 Tabelle zu Übung 5.3 .................................................................................... 87
Tab. A.3 Tabelle zu Übung 5.4 .................................................................................... 87
Tab. F.1 ........................................................................................................................ 96

GDI01 91
© HfB, 02.12.20, Berk, Eric (904709)

E. Sachwortverzeichnis
GDI01

A D
Abfolge, sequenzielle ....................... 43 Daten ............................................... 50
Additionswerk ................................. 75 Datenverarbeitung ............................. 6
Algorithmus ................................ 3, 10 Definition ........................................ 27
ALU ................................................ 43 Deklaration ...................................... 27
Analytische Maschine ...................... 36 do-Schleife ....................................... 23
Android ........................................... 47 Dualsystem ...................................... 51
Anwendungssoftware ...................... 13 Dualzahl .............................. 50, 51, 52
App ................................................. 48 Addition ...................................... 55
Arbeitsspeicher .......................... 42, 45 Subtraktion ................................. 56
Arithmetical Logical Unit ................. 43 umrechnen .................................. 52
ASCII-Code ..................................... 63
Attribut ........................................... 26 E
Ausgabe ............................................. 4 EDV ................................................... 8
ausschlie{ss ...................................... 69 Eingabe .............................................. 4
Auswahl .................................... 14, 19 Einserkomplement ........................... 59
EVA-Prinzip ....................................... 5
B
Babbage, Charles ............................. 36 F
Betriebssystem ...................... 13, 45, 46 Festplatte ......................................... 44
binary digit ...................................... 45 Festspeicher ..................................... 44
Binärzahl ................................... 50, 52 Funktionstabelle ............................... 69
Bit ............................................. 45, 63
Block ............................................... 17 G
Byte ........................................... 45, 63 Gatter, logische ................................ 65

C H
C ..................................................... 16 Halbaddierer .................................... 71
C++ ................................................. 48 Hardware ......................................... 12
Central Processing Unit .................... 43 Hauptprogramm .............................. 18
Code ................................................ 63 Hauptspeicher .................................. 45
Computer .......................................... 8 Hexadezimalzahl .............................. 60
Aufgabenbereiche ......................... 7
Computernetzwerk ............................ 7 I
Control Unit .................................... 43 Informatik .......................................... 8
CPU ................................................ 43 Instanz ............................................. 26
iOS .................................................. 47
IPO-Prinzip ........................................ 5
Iteration ........................................... 21

92 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

Sachwortverzeichnis E

J R
Java ............................................ 25, 48 RAM ............................................... 45
Random Access Memory .................. 45
K Rechenmaschine
Kapselung ................................... 25, 29 programmgesteuerte ................... 36
Klasse .............................................. 25 Rechenwerk ..................................... 43
Kommunikation ................................. 7 Reihenschaltung .............................. 65
Komplexität ..................................... 31
Kontrolle ........................................... 8 S
Kontrollstruktur .......................... 14, 16 Schaltung ........................................ 65
AND-Schaltung .......................... 65
L logische ....................................... 65
Landau-Symbol ............................... 33 NICHT-Schaltung ....................... 67
NOT-Schaltung ........................... 67
M ODER-Schaltung ......................... 67
Membervariable .............................. 26 OR-Schaltung .............................. 67
Methode ..................................... 25, 28 UND-Schaltung .......................... 65
Schleife
N fußgesteuerte .............................. 22
Nassi ............................................... 17 nicht abweisende ........................ 21
Nassi-Shneiderman-Diagramm ........ 17 Schleifenrumpf ................................ 22
Sequenz ..................................... 14, 16
O Serienschaltung ............................... 65
Objekt ............................................. 25 Shneiderman ................................... 17
Objektorientierung .......................... 24 Software .................................. 3, 5, 12
ODER-Schaltung ............................. 67 Software Engineering ....................... 12
Oktalzahlen ..................................... 62 Softwarekrise ..................................... 3
OR-Schaltung .................................. 67 Speicher
Arbeitsspeicher ........................... 44
P externer ...................................... 44
Parallelschaltung ............................. 67 Festspeicher ................................ 44
Primzahlen ...................................... 32 Hauptspeicher ............................. 45
Problemdefinition ............................ 11 interner ....................................... 44
Programm ..................................... 3, 9 Speicherkomplexität ......................... 31
Programmentwurf ........................... 11 Speicherwerk ................................... 43
Programmiersprache ............. 10, 11, 48 Speicherzelle .................................... 45
Programmierung .............................. 11 Steuerung .......................................... 8
objektorientierte .......................... 25 Steuerwerk ...................................... 43
Prozess ............................................ 11 Struktogramm ........................... 16, 17
Prozessor .................................... 11, 43 Strukturierte Programmierung ......... 13
Systemsoftware ............................... 13

GDI01 93
© HfB, 02.12.20, Berk, Eric (904709)

E Sachwortverzeichnis

T
Turing, Alan .................................... 38
Turing-Maschine .............................. 38

U
Überlauf .......................................... 75
Umrechnung dezimal in dual ........... 53
UND-Schaltung ............................... 65
Unicode ..................................... 63, 64

V
Verarbeitung ...................................... 4
virtuelle Maschine ........................... 48
Volladdierer ..................................... 72
Von Neumann, John ......................... 42
Von Neumann-Architektur ......... 42, 45

W
Wiederholung ............................ 14, 21
Windows ......................................... 47
Windows 10 Mobile ......................... 48
Windows Phone ............................... 48

X
XOR ................................................ 69

Z
Zeitkomplexität ............................... 31
Zustand einer Turing-Maschine ....... 38
Zweierkomplement .......................... 59
Zweiersystem .................................. 51

94 GDI01
© HfB, 02.12.20, Berk, Eric (904709)

F. Einsendeaufgabe Typ A
Einführung in die Informatik Einsendeaufgabencode:
GDI01-XX2-K09

Name: Vorname: Tutor/-in:

Postleitzahl und Ort: Straße: Datum:

Matrikel-Nr.: Studiengangs-Nr.: Note:

Heftkürzel: Druck-Code: Unterschrift:


GDI01 1219K09
Bitte reichen Sie Ihre Lösungen über Online-Campus ein. Falls Sie uns diese per Post senden
wollen, dann fügen Sie bitte die Aufgabenstellung und den Einsendeaufgabencode hinzu.

1. Schreiben Sie einen Algorithmus in Form eines Struktogramms, der eine beliebige
Anzahl quadratischer Gleichungen der Art x2  px  q  0 löst, wobei mindestens
eine Gleichung gelöst werden soll. Der Benutzer soll bei jeder Gleichung die Koeffi-
zienten p und q eingeben, woraufhin der Algorithmus die jeweilige Anzahl der Lö-
sungen sowie die Lösungen selbst bestimmt und ausgibt. Dabei sind die verschiede-
p p2
nen Fälle zu beachten, die bei Anwendung der Lösungsformel x 1,2    q
2 4
auftreten können. Zur Bezeichnung der Quadratwurzel a aus einer Zahl kann
sqrt(a) verwendet werden. Nach jeder gelösten Gleichung soll der Benutzer gefragt
werden, ob er eine weitere Gleichung lösen lassen möchte. Je nach Antwort geht der
Algorithmus dann zur nächsten Gleichung über oder wird beendet.
6 Pkt.
2. a) Welches Problem löst der Algorithmus „Suche“? Erläutern Sie das Verfahren, mit
dem er dieses Problem löst.

Abb. F.1:

GDI01 95
© HfB, 02.12.20, Berk, Eric (904709)

F Einsendeaufgabe Typ A

b) Bestimmen Sie die Komplexität des Algorithmus und beschreiben Sie sie mithilfe
eines Landau-Symbols.
6 Pkt.
3. Konstruieren Sie eine Turing-Maschine, die eine auf dem Band gespeicherte Dual-
zahl in ihr Zweierkomplement verwandelt. Gehen Sie dabei davon aus, dass der Le-
se- und Schreibkopf zu Beginn auf der vordersten, also der am weitesten links ste-
henden Stelle der Dualzahl steht.
6 Pkt.
4. Führen Sie die folgenden Operationen durch:
a) Berechnen Sie 110110112  101010012.
b) Berechnen Sie 110101102 – 11001112 mithilfe des Zweierkomplements.
c) Rechnen Sie die Dezimalzahl 1756 in eine Hexadezimalzahl um.
6 Pkt.
5. Setzen Sie die folgende Tabelle mit den drei Inputs x,y,z und dem Output r in ein
Schaltbild um.

Tab. F.1:

x y z r
0 0 0 1
0 0 1 0
0 1 0 1
0 1 1 1
1 0 0 1
1 0 1 1
1 1 0 1
1 1 1 0

6 Pkt.

96 GDI01
Studienheft

INM11

Grundlagen der Algorithmierung und der


Programmierung Teil 1
0419K03
Das Studienheft und seine Teile sind urheberrechtlich geschützt.
Jede Nutzung in anderen als den gesetzlich zugelassenen Fällen
ist nicht erlaubt und bedarf der vorherigen schriftlichen
Zustimmung des Rechteinhabers. Dies gilt insbesondere für das
öffentliche Zugänglichmachen via Internet, Vervielfältigungen und
Weitergabe. Zulässig ist das Speichern (und Ausdrucken) des
Studienheftes für persönliche Zwecke.

$
INM11

Grundlagen der Algorithmierung


und der Programmierung Teil 1
0419K03

Dr. Inge Adamski


Dr. Bernd Hetze
©

Werden Personenbezeichnungen aus Gründen der besseren Lesbarkeit nur in der männlichen oder
weiblichen Form verwendet, so schließt dies das jeweils andere Geschlecht mit ein.
Falls wir in unseren Studienheften auf Seiten im Internet verweisen, haben wir diese nach sorgfältigen
Erwägungen ausgewählt. Auf die zukünftige Gestaltung und den Inhalt der Seiten haben wir jedoch
keinen Einfluss. Wir distanzieren uns daher ausdrücklich von diesen Seiten, soweit darin rechtswid-
rige, insbesondere jugendgefährdende oder verfassungsfeindliche Inhalte zutage treten sollten.
© HfB, 02.12.20, Berk, Eric (904709)

Grundlagen der Algorithmierung und der Programmierung


INM11

Teil 1

Inhaltsverzeichnis
0419K03

Vorwort ....................................................................................................................... 1

1 Einführung ................................................................................................................ 3

2 Algorithmen .............................................................................................................. 11
2.1 Beispiele ....................................................................................................... 11
2.2 Begriff Algorithmus .................................................................................... 16
Zusammenfassung .................................................................................................... 18
Aufgaben zur Selbstüberprüfung ............................................................................ 19

3 Daten, Datentypen, Datenstrukturen .................................................................... 20


3.1 Definition Daten, Datentyp ....................................................................... 20
3.2 Einfache Datentypen .................................................................................. 22
3.2.1 Ausgewählte ordinale Datentypen ............................................................ 23
3.2.1.1 Datentyp int ................................................................................................. 23
3.2.1.2 Datentyp char .............................................................................................. 24
3.2.2 Nicht-ordinaler Datentyp float .................................................................. 24
3.3 Statische Datenstrukturen .......................................................................... 26
3.3.1 Datenstruktur Feld ...................................................................................... 26
3.3.2 Datenstruktur Verbund – Datensatz ......................................................... 27
3.4 Datentyp Zeiger/Pointer ............................................................................. 29
Zusammenfassung .................................................................................................... 29
Aufgaben zur Selbstüberprüfung ............................................................................ 30

4 Darstellung von Algorithmen ................................................................................. 32


4.1 Struktogramm .............................................................................................. 32
4.2 Grundstrukturen in Struktogrammen ....................................................... 33
4.2.1 Elementare Aktionen .................................................................................. 33
4.2.2 Folge (Sequenz) ........................................................................................... 35
4.2.3 Auswahl (Selektion) .................................................................................... 36
4.2.4 Wiederholung (Schleife, Zyklus, Iteration) .............................................. 38
4.2.4.1 Anfangsgeprüfte Schleife ........................................................................... 38
4.2.4.2 Endgeprüfte Schleife ................................................................................... 39
4.2.4.3 Zählschleife .................................................................................................. 40
4.2.5 Modul ........................................................................................................... 42
Zusammenfassung .................................................................................................... 45
0419K03

Aufgaben zur Selbstüberprüfung ............................................................................ 45

INM11
© HfB, 02.12.20, Berk, Eric (904709)

Inhaltsverzeichnis

5 Ausgewählte Beispiele prozeduraler Algorithmen ............................................. 47


Zusammenfassung ..................................................................................................... 54
Aufgabe zur Selbstüberprüfung ............................................................................... 54

6 Programmiersprachen, künstliche Sprachen ...................................................... 55


6.1 Höhere Programmiersprachen ................................................................... 55
6.2 Beschreibung formalisierter Programmiersprachen ................................ 56
6.2.1 Alphabet und Syntaxregeln ........................................................................ 57
6.2.2 Syntaxbeschreibung mit Syntaxdiagrammen ........................................... 58
6.3 Vom Quelltext zum ausführbaren Programm ........................................... 65
Zusammenfassung ..................................................................................................... 68
Aufgaben zur Selbstüberprüfung ............................................................................. 68

7 Programmiersprache C ........................................................................................... 70
7.1 Aufbau eines C-Programms ....................................................................... 70
7.2 Syntaxregeln von C ..................................................................................... 73
7.2.1 Grundsymbole ............................................................................................. 73
7.2.2 Syntaxdiagramme zu C ............................................................................... 76
7.2.3 Kontextsensitive Nebenbedingungen ........................................................ 81
7.3 Modulkonzept von C – Funktionen in C ................................................... 82
7.3.1 Definition von Funktionen ......................................................................... 83
7.3.2 Aufruf von Funktionen ............................................................................... 86
Zusammenfassung ..................................................................................................... 91
Aufgaben zur Selbstüberprüfung ............................................................................. 91

Anhang
A. Lösungen der Aufgaben zur Selbstüberprüfung ....................................... 93
B. Literaturverzeichnis ..................................................................................... 103
C. Grundstrukturen von Algorithmen ........................................................... 104
D. Syntaxdiagramme der Programmiersprache C ......................................... 106
E. Abbildungsverzeichnis ................................................................................ 112
F. Tabellenverzeichnis ..................................................................................... 114
G. Sachwortverzeichnis .................................................................................... 115
H. Einsendeaufgabe Typ A .............................................................................. 119

INM11
© HfB, 02.12.20, Berk, Eric (904709)

Vorwort
INM11Grundlagen der
Algorithmierung und der Programmierung Teil 10419K03

Dieses Studienheft vermittelt Ihnen ausgewählte Grundlagen der Programmentwick-


lung.
Algorithmen zur Lösung von mathematischen Problemen werden beschrieben, Daten-
typen und Datenstrukturen werden vorgestellt. Darauf aufbauend werden die Grund-
strukturen von Algorithmen nach Nassi-Shneiderman mit vielen Beispielen erarbeitet
und weitere Standardalgorithmen der Informatik an Beispielen erläutert. Schrittweise
werden Sie an das Verstehen und Lesen von Struktogrammen, der grafischen Darstel-
lung von Algorithmen, herangeführt.
Nachdem Programmiersprachen als künstliche Sprachen charakterisiert wurden, wer-
den die Syntaxdiagramme als Beschreibungsmittel der Syntax von Programmierspra-
chen eingeführt. Der Weg vom Quelltext eines Programms in einer Programmiersprache
zum ausführbaren Programm wird kurz dargestellt. Die Schritte, in denen ein Programm
entsteht, werden ausführlich beschrieben.
Als Abschluss erfolgt eine Einführung in die Programmiersprache C. An Beispielen zei-
gen wir Ihnen die Umsetzung einfacher prozeduraler Algorithmen in C-Programme mit-
hilfe von Syntaxdiagrammen für die einzelnen Anweisungen.
Das Modulkonzept, insbesondere die Umsetzung von Modulen, die eine Berechnung re-
alisieren, also ein Ergebnis (ein Datenobjekt, Output) liefern, werden wir ausführlich
darstellen.
Für die ersten Programmierübungen empfehlen wir Ihnen die Softwareentwicklungs-
umgebung ‚Dev-C++‘. Im Internet ist diese als Freeware zu finden. Zusätzlich steht im
StudyOnline eine Anleitung zur Installation und Nutzung dieser Software zur Verfü-
gung. Durch viele Beispiele und Übungen werden Sie bei Ihren ersten Schritten in der
Programmierung unterstützt.
Wir wünschen Ihnen viel Erfolg und Freude beim Selbststudium. Gern stehen wir Ihnen
beratend zur Seite.
Ihre Studienleitung

INM11 1
© HfB, 02.12.20, Berk, Eric (904709)

Vorwort

2 INM11
© HfB, 02.12.20, Berk, Eric (904709)

1 Einführung
Das Bestreben der Menschen, sich körperliche Arbeit durch den Einsatz von Werkzeu-
gen und Maschinen zu erleichtern bzw. einzelne Arbeitsschritte durch Automaten, in ei-
gener Regie, scheinbar selbstständig ausführen zu lassen, hat in der Entwicklungsge-
schichte der Menschen immer eine wichtige Rolle gespielt. Für wiederkehrende/
routinemäßige geistige Tätigkeiten schuf sich der Mensch Hilfsmittel. Es waren anfangs
Rechenbretter, dann mechanische Rechenmaschinen und letztlich elektronische, digitale
Rechentechnik für schnellere und fehlerfreie Berechnungen auf fast allen Gebieten. Die
technische Entwicklung in der Mitte des 20. Jahrhunderts beschleunigte diese Entwick-
lung. Aus dem Rechner wurde der Computer, der als Personal Computer unmittelbar am
Arbeitsplatz Einzug gehalten hat. Die digitale Informationsverarbeitung entstand und
ist heute fester Bestandteil gesellschaftlicher Prozesse. In Ihrem Arbeitsumfeld finden
Sie sicher viele Beispiele für die computergestützte Informationsverarbeitung.
Informationsverarbeitung durch elektronische Systeme lässt sich ganz allgemein durch
drei grundlegende Teilsysteme und den Informationsfluss zwischen ihnen darstellen.

Eingabe Verarbeitung Ausgabe

Abb. 1.1: E-V-A-Prinzip der rechnergestützten Informationsverarbeitung

Eingabe:

Informationen, die zur Verarbeitung benötigt werden, Instruktionen für das verarbeiten-
de System und die zu verarbeitenden Informationen werden durch das Teilsystem „Ein-
gabe“ bereitgestellt.
Es ist die Schnittstelle zwischen Mensch und technischem System. Technische Varianten
sind Maus, Scanner, Tastatur, Touchscreen, externe Speicher (CD, DVD, USB-Stick usw.)
sowie Datenfernübertragungseinheiten.

Verarbeitung:

Das Teilsystem „Verarbeitung“ realisiert den programmgesteuerten Ablauf der Informa-


tionsverarbeitung unter Nutzung von Speichern, Verarbeitungseinheiten und Steuerein-
heiten. Teilsysteme sind Rechenwerk, Steuerwerk, Arbeitsspeicher, Programmspeicher
und die Ein- und Ausgabesteuerung.

Ausgabe:

Die Ergebnisse der Informationsverarbeitung, neue Informationen, werden durch das


Teilsystem „Ausgabe“ aufbereitet. Es ist eine Schnittstelle zwischen technischem System
und Mensch. Technische Varianten sind Bildschirm, Drucker, Lautsprecher, externe
Speicher sowie Datenfernübertragungseinheiten.
In diesem Zusammenhang versteht man den Computer als ein programmgesteuertes
elektronisches System zur digitalen Verarbeitung von Daten. Informationen, die zur pro-
grammgesteuerten Verarbeitung mittels Computer benötigt werden, bezeichnet man als
Programmdaten, kurz: Das ist das Programm. Andererseits werden jene Informationen,
die vom Computer verarbeitet werden, Verarbeitungsdaten genannt, kurz: Das sind die

INM11 3
© HfB, 02.12.20, Berk, Eric (904709)

1 Einführung

Daten. Die Gemeinsamkeiten der rechnerinternen Darstellung von Programm- und Ver-
arbeitungsdaten, es sind codierte Informationen, sowie die Unterschiede in der Nutzung
sind dabei aber stets zu bedenken.
Der Computer benötigt ein Programm und Daten als Eingabe und liefert nach der Ver-
arbeitung Daten als Ausgabe.
Programme sind abstrakt gesehen nach dem EVA-Prinzip aufgebaut.
Die Nutzer moderner Software auf Computern mit modernen Betriebssystemen werden
heute durch benutzerfreundliche Bedienoberflächen in der Nutzung unterstützt. Die
„Fenster“ verfügen über Menüleisten, Werkzeugleisten, Bildlaufleisten, Arbeitsflächen,
Eingabefelder usw. Einem Benutzer dieser Software wird das Grundprinzip der Daten-
verarbeitung dadurch nicht direkt offensichtlich.
Übernehmen Sie jetzt unter unserer Anleitung die Rolle des Programmierers. An einem
kleinen Beispiel soll deutlich werden, wie das EVA-Prinzip zum Leitfaden beim Erstellen
einer Software, eines C-Programms, wird. Andererseits wollen wir Ihnen Mut machen
und zeigen, dass Sie nach dem Studium der Studienhefte INM11 und INM12 die Grund-
lagen des Programmierens beherrschen und kleine Programme selbst erstellen können.

Übung 1.1:
Das Programm „Sparjahre“ dient uns als Beispiel. Dieses Programm soll folgende
Aufgabe korrekt lösen.
„Ein Sparer möchte ein bestimmtes Startkapital nach einer Anzahl von Jahren durch
die Zinsen, die pro Jahr gezahlt werden, verdoppelt haben.“

Das Programm „Sparjahre“ benötigt folgende Eingaben: das Startkapital und den aktu-
ellen Zinssatz.
Die Ausgabe, das Ergebnis des Programms, ist eine Zahl. Sie entspricht der Anzahl der
Sparjahre, die vergehen, bis das Guthaben das Zweifache des Anfangskapitals, das End-
kapital, beträgt.
Zur Verarbeitung:
Sicher fällt Ihnen sofort ein, dass es in der Mathematik dazu eine Berechnungsvorschrift,
die Zinseszinsformel, gibt
n
 p 
K n  K 0 1   ,
 100 
mit Kn … Endkapital, K0 … Startkapital, p/100 ( z. B. 0,05  5 %) Zinssatz und n… An-
zahl der Jahre. Diese Formel hilft uns, die Berechnung zu verstehen, aber sie ist als algo-
rithmische Lösung der Aufgabe zunächst nicht verwendbar. Der gesuchte Algorithmus,
der die Grundlage des späteren Entwurfs von Software ist, soll nur Grundoperationen
als Aktionen enthalten, das Potenzieren bzw. das Wurzelziehen gehören nicht dazu.
Noch einmal zur Zinseszinsrechnung: Das Guthaben erhöht sich pro Jahr um die Zinsen
auf das aktuelle Guthaben. Im darauffolgenden Jahr wird das um die Zinsen erhöhte
Guthaben verzinst usw. Das geschieht jedes Jahr, und nach einer bestimmten Anzahl

4 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Einführung 1

von Jahren beträgt der Wert des Guthabens das Doppelte des Startkapitals, d. h. mindes-
tens 2 · Startkapital. Diese Rechnung ist eine geeignete Grundlage des Algorithmus, der
im C-Programm umgesetzt wird.
Nun wird in vier typischen Schritten die Entstehung des Programms ausführlich darge-
stellt.

Beschreibung der schrittweisen Lösung der Aufgabe


Wir betrachten zwei Zahlenwerte, die jeweils durch eine Eingabe bereitgestellt werden
sollen, das Startkapital und den Zinssatz (z. B. 0,02) als gegeben. Daraus kann das ange-
strebte Endkapital (2 · Startkapital) berechnet werden.
Die Formeln für die Zinsberechnung pro Jahr lauten:
NeuesGuthaben  Guthaben + Guthaben · Zinssatz
Guthaben  NeuesGuthaben
Im folgenden Jahr wird das um die Zinsen erhöhte Guthaben wiederum verzinst usw. Je
niedriger die Zinsen, umso länger dauert es, bis das Sparziel erreicht wird, bzw. umge-
kehrt, je höher die Zinsen, desto kürzer ist die Laufzeit des Sparens. In der folgenden Ta-
belle sind die Werte (Daten), die zur Lösung der Aufgabe benötigt bzw. durch sie berech-
net werden, zusammengestellt. Für jedes Datenobjekt, jeweils eine Variable, werden
eine Name und sein Wertebereich festgelegt:

Tab. 1.1: Daten für die Berechnung

Daten Variablenname Wertebereich


Startkapital anf gebrochene Zahl
(evtl. Zahl mit Nachkommastellen)
Guthaben guthaben gebrochene Zahl
Zinssatz zins gebrochene Zahl
Endkapital end gebrochene Zahl
Sparjahre jahre positive ganze Zahl

Entwurf der Problemlösung


Welche Schritte sind in welcher Reihenfolge auszuführen?
Der Algorithmus, die Schritte der Problemlösung, werden in einem Struktogramm dar-
gestellt. Das Struktogramm ist ein grafisches Hilfsmittel. Es besteht aus Strukturblöcken.
Die geometrische Figur Rechteck ist das Sinnbild eines Blocks, der einer Aktion in der
Problemlösung entspricht. Ein Struktogramm wird von oben nach unten gelesen.

INM11 5
© HfB, 02.12.20, Berk, Eric (904709)

1 Einführung

Das Struktogramm in Abb. 1.2 zeigt die


Eingabe: anf, zins
Schritte der Lösung der Aufgabe „Spar-
jahre“. Die sechs Strukturblöcke entspre- end := 2*anf
chen sechs Aktionen, die sequenziell, d. h. jahre := 0
nacheinander, ausgeführt werden: zwei
Eingaben, drei Zuweisungen zur Initiali- guthaben := anf
sierung der Variablen, die wiederholt aus- Wiederhole solange guthaben < end
zuführende Rechnung und die Ausgabe.
jahre := jahre + 1
Ist diese Folge der Aktionen korrekt,
guthaben := guthaben * (1 + zins)
liefert sie das gewünschte Ergebnis?
Ausgabe: jahre
Wir werden diese Aktionen zur Kontrolle
mit Beispielwerten ausführen und die Abb. 1.2: Struktogramm „Sparjahre“
Werteänderung der Variablen protokollie-
ren.
Dieses Vorgehen nennt man Trockentest, Tab. 1.2 zeigt das Ergebnis.

Tab. 1.2: Trockentest zum Algorithmus „Sparjahre“

anf zins end jahre guthaben


Eingabe 200,00 0,05
Zuweisung 400,00 0 200,00
1. Rechnung 1 200 · (1  0,05)  
200 + 200 · 0,05  210,00
2. Rechnung 2 210 · (1  0,05)  220,50
3. Rechnung 3 220,50  220,50 · 0,05  231,53
4. Rechnung 4 …  243,10
5. Rechnung 5 …  255,26
… …
15. Rechnung 15 …  415,79

Die Rechnung, die hier mehrmals wiederholt wird, entspricht der Formel:
guthaben : guthaben  guthaben · zins
Im Unterschied zur Mathematik wird die Rechnung im Struktogramm nicht durch eine
Gleichung, sondern durch eine Zuweisung beschrieben. Dabei wird das sogenannte Er-
gibtzeichen „ : “ benutzt. Auf der rechten Seite der Zuweisung erfolgt eine Berechnung.
Der alte Wert der Variablen guthaben wird zur Berechnung der Zinsen verwendet. Der
durch die Zinsen erhöhte Wert, Berechnung durch die Addition, wird der auf der linken
Seite stehenden Variablen guthaben zugewiesen. Im nächsten Berechnungsschritt wird
mit diesem neuen Wert gerechnet. So wird die Zinseszinsrechnung korrekt umgesetzt.

6 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Einführung 1

Programmierung
Die Korrektheit des Algorithmus „Sparjahre“ wurde durch den Trockentest überprüft.
Das Protokoll der Veränderung der Daten entspricht im Programm der Veränderung des
Inhalts der Speicherplätze, der jeweiligen Variablen. Nun kann dieser Algorithmus mit-
hilfe der Programmiersprache C in eine Folge von Anweisungen umgesetzt werden.

(1) // Programm Sparjahre.c


(2) #include<stdio.h>
(3) int main() {
(4) float zins, anf, end, guthaben;
(5) int jahre;
(6) printf("\n");
(7) printf("\n Berechnung der Sparjahre \n");
(8) printf(" Geben Sie das Anfangskapital ein: \t");
(9) scanf ("%f",&anf);
(10) printf(" Geben Sie den aktuellen Zinssatz ein: \t");
(11) scanf ("%f",&zins);
(12) end = 2 * anf;
(13) printf("\n");
(14) guthaben = anf;
(15) jahre = 0;
(16) while(guthaben < end){
(17) jahre = jahre + 1;
(18) guthaben = guthaben + guthaben * zins;
(19) }
(20) printf("Nach %d Jahren ist das Sparziel erreicht.", jahre);
(21) printf("\n");
(22) return 0;
(23) }

Code 1.1: Quelltext Sparjahre.c

Moderne Programmierumgebungen, z. B. DEV-C++, stellen Werkzeuge bereit, die effek-


tiv das Erstellen eines Programms unterstützen. Der Quelltext wird im Editor geschrie-
ben. In diesem Beispiel wird die typische C-Schreibweise bereits benutzt.
Betrachten wir den Quelltext zeilenweise:
In Zeile (1) steht der Dateiname, unter dem der Quelltext gespeichert ist.
Die Programmiersprache C besitzt einen kleinen, kompakt gestalteten Sprachkern. Mit
Kompilerdirektiven, diese beginnen mit einem ,#‘ am Zeilenanfang, wird es möglich,
nicht im Sprachkern vorhandene, aber benötigte und bereits vordefinierte Programmbe-
standteile zusätzlich zur Verfügung zu stellen. In Zeile (2) werden die vordefinierten
Funktionen zur Ein- und Ausgabe bereitgestellt.
In den Zeilen (3) bis (23) steht die main-Funktion. Die main-Funktion ist die erste Funk-
tion, die bei Programmstart ausgeführt wird. Sie muss genau einmal im Quelltext vor-
handen sein.
Der Block der Funktion wird durch eine öffnende (in Zeile (3) {) und eine schließende
Blockklammer (in Zeile (23) }) begrenzt. Durch entsprechendes Einrücken der Zeilen
wird die Struktur des Quelltextes deutlich.

INM11 7
© HfB, 02.12.20, Berk, Eric (904709)

1 Einführung

In den Zeilen (4) und (5) werden die Variablen deklariert, Datentyp und Name werden
festgelegt, man spricht von Variablendeklaration. Während die Sprache C++ weitere
Möglichkeiten für eine Variablendeklaration vorsieht, hat diese in C vollständig vor der
ersten ausführbaren Anweisung zu erfolgen.
Mit scanf(…) und printf(…) stehen die am häufigsten genutzten Ein- und Ausgabe-
Funktionen zur Verfügung.
In Zeile (7) wird mit der Anweisung printf(…) der Text ‚Berechnung …‘ ausgegeben.
In den Zeilen (8) und (9) wird über den Text eine Aufforderung auf dem Bildschirm aus-
gegeben und der Cursor mit Abstand zum Text (Tabulator "\t") auf dem Bildschirm po-
sitioniert.
Die Ausgabe von Leerzeilen, in den Zeilen (6), (13) und (21), erfolgt durch die Anwei-
sung printf("\n") (die Zeichenfolge "\n" entspricht new line, neue Zeile). Die Leer-
zeilen sind nur zur Gestaltung des Textes eingefügt worden.
Die Anweisung scanf(…) realisiert die Eingabe einer Zahl.
Der Nutzer des Programms muss die Eingabe mit der Taste (Enter) abschließen.
Im Ablauf folgt die Anweisung Zeile (10), die Ausgabe mit der Aufforderung zur Ein-
gabe der nächsten Zahl und mit Zeile (11) wird diese Eingabe entgegengenommen.
In Zeile (12) erfolgt eine Berechnung und das Ergebnis wird der Variablen end zuge-
wiesen.
In den Zeilen (14) und (15) erhalten die Variablen durch Zuweisung den bereits festge-
legten Startwert.
Mit der Anweisung while(…) (wiederhole solange eine Bedingung , <bed>, gilt), Zeile
(16) bis Zeile (19), wird die wiederholte Ausführung der zwei Anweisungen, Zeilen (17)
und (18), die zu einem Block durch {}-Klammern zusammengefasst sind, gesteuert. Die-
se Anweisungen berechnen die Variablen jahre und guthaben.
Sie werden immer wieder ausgeführt, nämlich solange die Bedingung guthaben  end
gilt. Wenn das Guthaben wertmäßig größer oder gleich dem gewünschten Endkapital
ist, wird die Wiederholung der Berechnung beendet.
Zeile (20) realisiert die Ausgabe des Ergebnisses, der aktuelle Wert der Variablen jahre.
In Zeile (22) ist das Ende der Funktion main() erreicht, mit dem Rückgabewert 0 wird
die fehlerfreie Ausführung angezeigt.
Bevor dieser Quelltext Code 1.1: „Sparjahre.c“ – vom Computer in einem bestimmten
Betriebssystem ausgeführt werden kann, muss er in einen maschinenlesbaren, ausführ-
baren Code übersetzt werden. Das dazu notwendige Übersetzerprogramm ist in eine
Entwicklungsumgebung integriert oder es liegt als Programm separat vor.
In der empfohlenen Programmierumgebung DEV-C++ erfolgt das Übersetzen, das Kom-
pilieren, durch einen Klick auf den Menüpunkt Kompilieren im Menü „Ausführen“
(Abb. 1.3) oder mit Taste (F9). Der Kompiler ist Bestandteil der Programmierumge-
bung.

8 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Einführung 1

Aus dem gemeinsam erarbeiteten fehlerfreien Programmcode wird durch Kompilieren


das Programm „Sparjahre.exe“ (Abb. 1.4 unteres Fenster) erzeugt.

Abb. 1.3: Start des Übersetzens, Kompilieren

Abb. 1.4: Ergebnis der Übersetzung „Sparjahre.exe„

INM11 9
© HfB, 02.12.20, Berk, Eric (904709)

1 Einführung

Test des Programms


Mit dem Klick auf „Ausführen“ oder dem Betätigen der Funktionstaste (F10) startet un-
ser aus der Sicht der Programmiersprache C fehlerfreies Programm. Das „Fenster“ der
Konsolenanwendung wird geöffnet. Die Eingaben werden von der Tastatur „dem Pro-
gramm mitgeteilt“, die Ausgabe zeigt im Fenster das Ergebnis (Abb. 1.5).

Abb. 1.5: Screenshot, Fenster mit Eingaben und Ausgabe zu „Sparjahre.exe“

Das vorliegende Programm entspricht der Aufgabenstellung. Für die korrekte Ausfüh-
rung ist jedoch die korrekte Eingabe von zwei reellen Zahlen Voraussetzung. Bei der
Eingabe der gebrochenen Zahl muss der Dezimalpunkt verwendet werden. Kommt zu-
fällig ein anderes Zeichen in der Eingabe vor, so „fährt sich das Programm fest“, es re-
agiert nicht mehr. Das Fenster muss geschlossen und damit das Programm durch das Be-
triebssystem beendet werden. Unser Programm ist also nicht robust gegen falsche
Eingaben. Andererseits ist es auch nicht benutzerfreundlich. Nur eine Rechnung ist
möglich, das Programm bietet keine Wiederholung der Berechnung an. Trotzdem wird
hier die Arbeit an diesem Beispiel beendet. Es gibt zahlreiche Varianten zur Verbesse-
rung dieses Programms, später kommen wir darauf zurück.

10 INM11
© HfB, 02.12.20, Berk, Eric (904709)

2 Algorithmen
Das Wort Algorithmus ist abgeleitet vom Namen des arabischen Gelehrten Al
Chwarizmi, der um 820 lebte. Es wurde ihm zu Ehren ins Lateinische für Rechen-
verfahren übernommen. Die Rechenverfahren, Algorithmen der Mathematik,
waren und sind eine wichtige Grundlage der Entwicklung moderner leistungsfä-
higer Software. In diesem Kapitel wird ein Begriff Algorithmus erarbeitet, der als
Grundlage für die Programmierung, das Entwerfen und Schreiben von Program-
men, gut geeignet ist.
Niklas Wirth, ein berühmter Schweizer Informatiker, hat in einem grundlegen-
den Fachbuch (1975) folgende Formel aufgestellt:
Programm = Algorithmus + Daten
D. h., ohne Algorithmus kann man kein Programm schreiben. Andererseits ist der
enge Zusammenhang von Algorithmus und Daten immer zu beachten, wenn ein
schnell und korrekt laufendes Programm entstehen soll. Die Begriffe Algorithmus
und Daten sind deshalb von zentraler Bedeutung in der Informatik. Sie werden
in den Kapiteln 1 und 2 dieses Studienhefts erst getrennt betrachtet und danach
in ihrem Zusammenhang erläutert.

Das E-V-A-Prinzip wurde in der Einleitung dieses Studienmaterials genutzt, um rech-


nergestützte Informationsverarbeitung anschaulich zu beschreiben. Darauf aufbauend
wird eine informelle Beschreibung des Algorithmus formuliert:

Ein Algorithmus ist eine Vorschrift zur Lösung einer Menge von gleichartigen Pro-
blemen, d. h. eine Vorschrift zur Lösung einer bestimmten Art von Aufgaben.
Er besteht aus einer endlichen Folge von Schritten, mit der aus bekannten Eingangs-
daten neue Ausgangsdaten eindeutig berechnet werden.

2.1 Beispiele
Der Zusammenhang zur Mathematik ist offensichtlich, deshalb folgen zwei Beispiele
aus diesem Bereich.

Beispiel 2.1: Addition von zwei Brüchen


Allgemein in der Schreibweise der Mathematik lautet die Aufgabe:
X1 X 2
Berechne  
Y1 Y 2
Die Zähler (X1, X2) und Nenner (Y1, Y2) der beiden Brüche sind beliebige natürliche
Zahlen, X1, X2, Y1, Y2  ℕ.
Eine konkrete Aufgabe aus der Vielzahl gleichartiger Aufgaben sei:
4 2
Berechne  
6 9
Wie ist eine solche Aufgabe zu lösen?

INM11 11
© HfB, 02.12.20, Berk, Eric (904709)

2 Algorithmen

Die Mathematik beschreibt das Rechenverfahren, den Algorithmus, kurz durch fol-
gende Formel:

X 1 X 2 X 1 Y 2  X 2 Y 1
 
Y1 Y 2 Y 1 Y 2
Mit Worten werden die Schritte der Berechnung als Schrittfolge beschrieben:

Schrittfolge zu Beispiel 2.1


Addition von zwei Brüchen – ein neuer Bruch wird berechnet:
1. Der Zähler des neuen Bruchs ist eine Summe.
Der erste Summand ergibt sich aus der Multiplikation des Zählers des ersten
Bruchs X1 mit dem Nenner des zweiten Bruchs Y2, X1 · Y2.
Der zweite Summand ergibt sich aus der Multiplikation des Zählers des zweiten
Bruchs X2 mit dem Nenner des ersten Bruchs Y1, X2 · Y1
2. Der Nenner ist ein Produkt. Multipliziere den Nenner des ersten Bruchs Y1 mit
dem Nenner des zweiten Bruchs Y2, Y1 · Y2.

Die Lösung dieser Aufgabe „Addition von zwei Brüchen“, wobei Zähler und Nenner na-
türliche Zahlen sind, kann durch eine mathematische Formel oder mit Worten unter
Verwendung von Variablen und Operationen als Schrittfolge beschrieben werden. Be-
achten Sie: Die Reihenfolge der Berechnungsschritte ist hier nicht fest vorgeschrieben,
sie ist nicht zwingend.
Folgende Aktionen sind Bestandteil der Schrittfolge: Die Zuweisung von Werten an
Variable, die zweistelligen Operationen Addition und Multiplikation.
X
Beispiel 2.2: Kürzen eines Bruchs
Y
Der Zähler X und der Nenner Y sind natürliche Zahlen, X, Y  ℕ.
Der folgende Bruch, das Ergebnis der konkreten Aufgabe aus Beispiel 2.1, soll ge-
kürzt werden.
4 2 36  12 48
  
6 9 54 54
Aus der Mathematik ist die folgende Vorschrift bekannt.
1. Ermittle den größten gemeinsamen Teiler von Zähler und Nenner.
2. Der Zähler des gekürzten Bruchs wird berechnet, indem der Zähler des zu kür-
zenden Bruchs durch den größten gemeinsamen Teiler dividiert wird
3. Der Nenner des gekürzten Bruchs wird berechnet, indem der Nenner des zu kür-
zenden Bruchs durch den größten gemeinsamen Teiler dividiert wird.
• Zu 1.: Wie man leicht sieht, sind die Zahlen 2, 3 und 6 Teiler von 48 und 54. Der
größte gemeinsame Teiler von 48 und 54 ist somit 6.
48
• Zu 2.: Rechne und das Ergebnis ist 8.
6
54
• Zu 3.: Rechne und das Ergebnis ist 9.
6

12 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Algorithmen 2
8
• Das Ergebnis lautet .
9
Nun die Verallgemeinerung:

Schrittfolge zu Beispiel 2.2


X
Der Bruch mit X, Y  ℕ und X, Y  0. wird gekürzt
Y
1. Ermittle den größten gemeinsamen Teiler von X und Y den ggT(X, Y ).
X
X ggT( X ,Y )
2. Berechne 
Y Y
ggT( X ,Y )

Die Schreibweise der Mathematik ist hier hilfreich. Die Funktion ggT(X, Y ) erfordert
zwei Eingaben, X und Y, sie liefert als Ergebnis (als Ausgabe oder als Rückgabewert der
Funktion) den Wert des größten gemeinsamen Teilers von X und Y.
Welche mathematischen Vorüberlegungen sind die Grundlage der folgenden Schrittfol-
ge zur Berechnung von ggT(X, Y )?
• Es gilt X, Y  ℕ wobei X, Y  0.
• Wenn X  Y, dann ist der ggT(X, Y )  X  Y, denn eine Zahl ist größter gemeinsa-
mer Teiler von sich selbst.
• Wenn X  Y und T  0 irgendein gemeinsamer Teiler von X und Y ist, dann gilt auch:
X  a · T und Y  b · T mit a  0 und b  0.
Daraus lässt sich die folgende Gleichung durch Einsetzen aufstellen:
X – Y  a · T – b · T  (a – b) · T
Das bedeutet, der Teiler T  0 ist auch Teiler der Differenz X – Y mit X, Y  ℕ.
• Wenn X  Y und T  0 irgendein gemeinsamer Teiler von X und Y ist, dann gilt auch:
X  a · T und Y  b · T mit a  0 und b  0
Daraus lässt sich folgende Gleichung durch Einsetzen aufstellen:
Y – X  b · T – a · T  (b – a) · T.
Das bedeutet, der Teiler T  0 ist auch Teiler der Differenz Y – X mit X, Y  ℕ.

Beispiel 2.3:
Der Algorithmus für die Berechnung des ggT(X, Y ) kann nun folgendermaßen be-
schrieben werden:

Schrittfolge zu ggT(X, Y)
1. Den Variablen X und Y werden Werte zugewiesen.
2. Wiederhole so lange X  Y ist, die folgende Aktion:
2.1. Wenn X  Y,
2.2. dann erhält X den Wert von X – Y, X : X – Y,

INM11 13
© HfB, 02.12.20, Berk, Eric (904709)

2 Algorithmen

2.3. andernfalls erhält Y den Wert von Y – X, Y : Y – X.


3. Der letzte Wert von X ist das Ergebnis, er entspricht dem ggT(X, Y ).

Tab. 2.1 zeigt die schrittweise Ausführung des Algorithmus für die zwei Eingaben
X  48 und Y  54. Die Ausführung der einzelnen Schritte und die Veränderung der
Werte der Variablen X und Y ist in den aufeinanderfolgenden Zeilen protokolliert.

Tab. 2.1: Schrittweise Algorithmus-Ausführung

Schritt Aktion X Y
1. Zuweisungen 48 54
2. Test: Ist 48  54? Ja
2.1 Ist 48  54? Nein
2.3 48 54 – 48  6
2. Ist 48  6? Ja
2.1 Ist 48  6? Ja
2.2 48 – 6  42 6
2. Ist 42  6? Ja
2.1 Ist 42  6? Ja
2.2 42 – 6  36 6
2. Ist 36  6? Ja
2.1 Ist 46  6? Ja
2.2 36 – 6  30 6
Mehrmals 2., 2.1 und 2.2
2. Ist 12  6? Ja 12 6
2.1 Ist 12  6? Ja
2.2 12 – 6  6 6
2. Ist 6  6? Nein 6 6
3. Der ggT(48, 54) (siehe letzter Wert von X ).

Mit dem Wert ggT(X, Y ) wird nun in der Schrittfolge weiter gerechnet.

X
X : , der neue Zähler.
ggT( X ,Y )
Y
Y : , der neue Nenner.
ggT( X ,Y )

14 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Algorithmen 2

X
Fazit: Das Kürzen eines Bruches erfordert als ersten Schritt die Ausführung eines
Y
Teilalgorithmus, der größte gemeinsame Teiler zweier natürlicher Zahlen muss ermittelt
werden. Danach erst kann die Berechnung, wie vorgegeben, fortgesetzt werden. Diese
Schrittfolge ist zwingend.
Folgende Aktionen sind Bestandteil der Schrittfolge: Die Zuweisung von Werten an
Variable, die zweistelligen Operationen Subtraktion und Division und Tests, Größenver-
gleiche von Zahlenwerten. Diese Tests können zu alternativen Schrittfolgen führen oder
zur Wiederholung einer Schrittfolge.
Die Entwicklung der elektronischen Rechentechnik führte zu vielen neuen Anwendun-
gen in verschiedenen Bereichen des gesellschaftlichen Lebens. Es entstanden daraus
vielfältige neue Aufgabenstellungen, und es wurden und werden neue Algorithmen be-
nötigt. Insbesondere der Bedarf an Software zur Verarbeitung von massenhaft anfallen-
den Daten entwickelte sich rasant. Das Speichern, das Verwalten und das Auswerten
von Daten sind heute wichtige Aufgabenstellungen. Der Fundus der mathematischen
Algorithmen ist auch für diese Aufgaben eine wichtige Grundlage.

Beispiel 2.4:
Das Alter des jüngsten Mitglieds des Kegelvereins „Alle Neune“ soll ermittelt wer-
den. Beachten Sie folgende Vereinfachung: Möglicherweise sind mehrere Personen
im gleichen Jahr geboren, ihr Alter ist gleich. Das Ergebnis ist dann trotzdem nur
eine Altersangabe.
Für die Lösung der Aufgabe benötigt man einen Datenbestand. Das könnte eine Liste
in Tabellenform oder ein Stapel Karteikarten mit allen zur Vereinsverwaltung benö-
tigten Daten, Informationen zu allen Mitgliedern des Vereins, sein.
Die Daten zu den Mitgliedern liegen in unserem Beispiel als Liste in Tabellenform
vor. Eine Zeile der Tabelle enthält alle Angaben zu je einem Mitglied, dabei ist hier
nur das Geburtsjahr von Interesse.
Zur Beschreibung des Algorithmus werden die zwei Variablen jahr und junior benö-
tigt.

Schrittfolge zu Beispiel 2.4


1. Suchen Sie in der ersten Zeile nach dem Geburtsjahr, weisen Sie diesen Zahlen-
wert der Variablen junior zu.
2. Nun
a) Schauen Sie in die nächste Zeile, suchen Sie den Zahlenwert Geburtsjahr,
weisen Sie ihn der Variablen jahr zu.
b) Prüfen Sie ob jahr >junior, wenn ja (dieses Mitglied ist jünger), weisen Sie
junior den Wert von jahr zu, wenn nein verändert sich junior nicht.
Wiederholen Sie a) und b) bis die letzte Zeile angeschaut wurde.
3. Der aktuell in junior stehende Wert ist das Geburtsjahr des jüngsten bzw. der
jüngsten Vereinsmitglieder.

INM11 15
© HfB, 02.12.20, Berk, Eric (904709)

2 Algorithmen

4. Berechnen Sie das Alter des jüngsten Mitglieds, nutzen Sie junior (Geburtsjahr)
und die aktuelle Jahreszahl (alter : aktJahr – junior).

Folgende Aktionen sind Bestandteil der Schrittfolge: Die Zuweisung von Werten an
Variable, die zweistellige Operation Subtraktion, ein Test (ein Größenvergleich) der zu
alternativen Aktionen führt, und ein Test, der zur Wiederholung einer Folge von Aktio-
nen führt oder zum Beenden.
Es gibt verschiedene Lösungswege und damit auch verschiedene Algorithmen zur Lö-
sung dieser Aufgabe. Im Kern ist sie eine konkrete Anwendung einer Standardaufgabe
der Informationsverarbeitung/Datenverarbeitung, der Suche nach dem Maximum, der
größten Zahl, einer endlichen Menge von Zahlen. In verschiedenen Anwendungsgebie-
ten der Wissenschaft, der Wirtschaft oder der Verwaltung gibt es Aufgabenstellungen,
die sich formal gleichen. Solche immer wieder zu lösenden Aufgabenstellungen sind z. B.
das Suchen bestimmter Angaben in einem Datenbestand oder das Sortieren der Daten
eines Datenbestands. Algorithmen für formal gleiche Aufgabenstellungen sind Stan-
dardalgorithmen. Für das Programmieren ist es notwendig, diese zu kennen und beur-
teilen zu können.

2.2 Begriff Algorithmus


In den drei Beispielen wurden vor allem die Rechenoperationen, die Zuweisung, die Ver-
gleiche, Tests usw. herausgearbeitet. Das ist eine mehr operationale Sicht auf den Algo-
rithmus, sie führt zu folgender Aussage.

Satz 2.1:
Ein Algorithmus ist eine Vorschrift, die angibt, wie durch die Ausführung bestimm-
ter elementarer Operationen aus Eingabedaten Ausgabedaten ermittelt werden.

Die Ausführbarkeit des Algorithmus durch eine Maschine, durch den Rechner bzw. das
elektronische System, steht bei dieser Sicht im Vordergrund.
Die folgende Definition bezieht sich auf wesentliche Eigenschaften einer Vorschrift,
durch die sie zum Algorithmus wird.

Definition 2.1:
Ein Algorithmus ist eine in Beschreibung und Ausführung allgemeine, eindeutige,
endliche, determinierte und effektive Vorschrift zur effizienten Lösung eines Prob-
lems.

Merkmale des Begriffs Algorithmus

Allgemeinheit

Ein Algorithmus darf nicht nur die Lösung einer speziellen Aufgabe sein, sondern er
muss die Lösung einer Klasse von Problemen beschreiben.

16 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Algorithmen 2

Endlichkeit

Ein Algorithmus muss aus einer endlichen Anzahl von Schritten bestehen. Durch die
Abarbeitung dieser endlich vielen Schritte muss er nach endlich langer Zeit die Abar-
beitung beenden.

Eindeutigkeit

Die einzelnen Schritte eines Algorithmus und ihre Aufeinanderfolge müssen eindeutig
beschrieben sein.

Determiniertheit

Die mehrmalige Anwendung eines Algorithmus mit denselben Eingangsdaten muss


stets dieselben Ausgangsdaten liefern.

Effektivität

Ein Algorithmus muss real von einer Maschine durchführbar sein.

Effizienz

Ein Algorithmus muss möglichst wenig Speicher und eine möglichst geringe Rechenzeit
erfordern.
In der Informatik werden Algorithmen mit diesen Merkmalen prozedurale Algorith-
men genannt. Die Ausführung dieser Algorithmen beruht auf einer Folge von elemen-
taren Aktionen, die Zustandsänderungen im technischen System (z. B. Strom fließt/
fließt nicht, Ventil offen/Ventil zu) bewirken. Die Zustände sind dabei durch die Werte
von Variablen beschreibbar. Die elementaren Aktionen bewirken die Änderung dieser
Werte.
Mit dem E-V-A-Prinzip beschreibt man die Ausführung:
• Die Eingabe bewirkt die Übernahme von Daten in eine Variable.
• Die Verarbeitung bewirkt eine Wertzuweisung, die Änderung des Wertes einer Va-
riablen aufgrund einer Rechnung bzw. Zwischenrechnung.
• Die Ausgabe bewirkt die Übergabe von Daten, des Wertes von Variablen, an ein Aus-
gabegerät.
Prozedurale Algorithmen sind eine gute Grundlage für das Schreiben von Programmen/
Software.
Welche Arten von Algorithmen kann man noch betrachten?
Wird die Endlichkeit als Merkmal für Algorithmen aufgegeben, dann erhält man nicht-
terminierende, nicht-endende, Algorithmen. Beispielsweise Steuerungsalgorithmen,
die die Grundlage von Steuerungsprozessen sind, sind nicht-endende Prozesse.
Wird das Merkmal der Determiniertheit aufgegeben, dann erhält man nicht-determi-
nierte Algorithmen. Das bedeutet, dass in der Vorschrift zur Lösung einer Aufgabe auf
mindestens eine Aktion zwei verschiedene Aktionen folgen können. Soll z. B. eine Ver-

INM11 17
© HfB, 02.12.20, Berk, Eric (904709)

2 Algorithmen

bindung in einem Straßennetz von Punkt A nach Punkt B ermittelt werden, sind unter-
schiedliche Lösungen möglich. Erfolgt die Auswahl alternativer Aktionen in einem Al-
gorithmus durch Zufall, so erhält man stochastische Algorithmen.
Es können noch viele weiterführende Fragen zu Algorithmen Gegenstand des Interesses
sein, z. B.:
Kann jedes Problem durch einen Algorithmus beschrieben werden? Diese Frage bezieht
sich auf die Frage der Berechenbarkeit von Problemen.
Kann zu jedem Algorithmus ein Programm geschrieben werden? Diese Frage führt zur
Betrachtung von Programmiersprachen und den Anforderungen an sie.
Ist ein Computer grundsätzlich in der Lage, einen Algorithmus, der als Programm vor-
liegt, abzuarbeiten? Die Komplexität von Algorithmen bzw. Programmen muss dazu be-
trachtet werden. Diese und weitere Fragen sind Gegenstand der Theoretischen Informa-
tik.

Zusammenfassung

In diesem Kapitel wurde der Begriff Algorithmus über seine Merkmale definiert und be-
schrieben. Jedes Computerprogramm ist als konkrete Umsetzung eines formal und
sprachunabhängig definierten Algorithmus aufzufassen. Das weitere Vorgehen wird
durch die Betrachtung prozeduraler Algorithmen bestimmt. Diese sind dadurch gekenn-
zeichnet, dass die Problemlösung auf eine Folge elementarer Aktionen, die durch Zu-
standsänderungen charakterisiert sind, zurückgeführt wird.
Das E-V-A-Prinzip ist Grundlage der Informationsverarbeitung mit einem technischen
System, dem Computer. Die Eingabe von Werten über die Tastatur (E) sowie die Ausga-
be von Werten bzw. von Text in einem Anwendungsfenster (A) sind die beiden Dialog-
möglichkeiten mit dem Benutzer des Programms.
Die Verarbeitung von Daten (V) erfolgt durch nur zwei grundlegenden Aktionen:
die Wertzuweisung und die Wertänderung durch Berechnungen. Die Berechnungen
werden mit den mathematischen Grundoperationen Addition, Subtraktion, Multiplika-
tion und Division umgesetzt. Durch Größenvergleiche (zweistellige Operationen) wird
eine Verzweigung, eine alternative Abfolge von Aktionen oder eine wiederholte Abfolge
einer festgelegten Folge von Aktionen möglich.
Bevor Daten und ihr Zusammenhang mit den Algorithmen im nächsten Kapitel näher
betrachtet werden, sollten Sie anhand der folgenden Aufgaben prüfen, ob Sie mit eige-
nen Worten Schrittfolgen zur Lösung einer Aufgabe, also Algorithmen, analog den dar-
gestellten Beispielen beschreiben können.

18 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Algorithmen 2

Aufgaben zur Selbstüberprüfung

2.1 Suchen Sie in der Fachliteratur oder im Internet nach einem effizienteren als den
im Beispiel 2.2 beschriebenen Algorithmus für die Berechnung des größten ge-
meinsamen Teilers. Beschreiben Sie ihn als Schrittfolge in eigenen Worten.
Hinweis: Unter „Euklidischer Algorithmus“ wird man fündig.
2.2 Formulieren Sie in eigenen Worten einen Algorithmus zur Ermittlung des Alters
des ältesten Mitglieds des „Kunst- und Kulturvereins R. Schumann“.
Gehen Sie analog dem Vorgehen in Beispiel 2.4 in Abschnitt 2.1 vor. Betrachten
Sie auch nur das Geburtsjahr.
2.3 Formulieren Sie einen Algorithmus, mit dessen Hilfe von einer Jahreszahl A ent-
schieden werden kann, ob es sich bei dem Jahr A um ein Schaltjahr handelt oder
nicht.
Sie können davon ausgehen, dass 1581  A  2100 gilt, der gregorianische Kalen-
der gilt seit dem Jahr 1582.

INM11 19
© HfB, 02.12.20, Berk, Eric (904709)

3 Daten, Datentypen, Datenstrukturen


Der Zusammenhang von Algorithmus und Daten kann erst aufgezeigt werden,
wenn die Begriffe Daten und Datentyp definiert bzw. erläutert wurden. Diese De-
finitionen werden in diesem Kapitel erarbeitet. Eine Übersicht zu den in den Pro-
grammiersprachen vordefinierten Datentypen und Datenstrukturen wird ange-
geben. Ausgewählte Datentypen und Datenstrukturen werden kurz vorgestellt
und an mindestens einem Beispiel näher erläutert.

3.1 Definition Daten, Datentyp


Informationen über reale Dinge, Vorgänge und Phänomene werden auf unterschiedli-
chen Wegen und mit unterschiedlichen Methoden gewonnen. Um diese nutzen, verbrei-
ten und/oder verarbeiten zu können, bedarf es einer zweckmäßigen (maschinenlesba-
ren, verarbeitbaren) abstrakten Repräsentation als Daten.

Anmerkung:
Die Information über die Körpergröße eines Menschen ist eine Zahlenangabe, ein
Vielfaches einer bestimmten Maßeinheit, z. B. 176 cm. Die Angabe 176 cm könnte
aber auch die Information über die Breite eines Möbelstückes sein.
Informationen zur Temperatur in einem Raum über eine längere Zeit werden durch
eine Folge von Messwerten, eine Messreihe aufgezeichnet. Auch hier werden Zah-
lenwerte einer Maßeinheit zur Darstellung verwendet.
Informationen über die Mitglieder eines Vereins werden durch Zeichenfolgen,
Name, Vorname, Adresse, Geburtsdatum usw., in elektronischer Form aufbereitet
und gespeichert.

Zieht man den Duden zurate, dann sind Daten bzw. Datenobjekte
• durch Beobachtung, Messung oder statistische Erhebung u. a. gewonnene Zahlen-
werte, Angaben oder Befunde;
• zur Lösung einer Aufgabe vorgegebene Werte oder Größen;
• elektronisch gespeicherte Zeichenfolgen.

Definition 3.1:
Daten sind jene Informationen bzw. deren abstrakte Repräsentationen, die von pro-
grammgesteuerten elektronischen digitalen Verarbeitungssystemen zur Verarbei-
tung benötigt bzw. von diesen verarbeitet werden.

Man bezeichnet Daten, die zur programmgesteuerten Verarbeitung mittels Computer


benötigt werden, als Programmdaten und jene, die vom Computer verarbeitet werden,
als Verarbeitungsdaten.
Da nur die Verarbeitungsdaten im Folgenden betrachtet werden, sollen vereinfachend
die Bezeichnungen Daten bzw. Datenobjekten, (Plural) und Datenobjekt (Singular)
verwendet werden.

20 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Daten, Datentypen, Datenstrukturen 3

Daten können von elektronischen digitalen Systemen nur dann gespeichert und verar-
beitet werden, wenn sie entsprechend digitalisiert wurden. Da auf die rechnerinterne
Darstellung von Daten in diesem Studienmaterial nicht eingegangen wird, sei auf die
Darstellung als Folgen von Bits, die Dualziffern 0 und 1, sowie die entsprechende Co-
dierung nur verwiesen.
Ein Datenobjekt hat einen bestimmten Typ, es repräsentiert Information durch einen
konstanten oder variablen Wert aus einem Wertebereich. Innerhalb dieses Wertebe-
reichs sind bestimmte Operationen definiert, d. h., nur diese Operationen sind auf das
Datenobjekt anwendbar.

Definition 3.2:
Ein Datentyp beschreibt eine Menge von zulässigen Werten und eine Menge von
Operationen.

Die folgende Klassifikation der Datentypen zeigt die Vielfalt der in der Informations-
verarbeitung vorkommenden Datentypen:

Datentypen (DT)

einfache DT strukturierte DT/Datenstrukturen

Gleitkomma-
ordinale DT statische DT dynamische DT
zahlen

Feld Verbund Menge Liste Baum Graph

ganze Wahrheits-
Zeichen Aufzählung Zeiger
Zahlen werte

Abb. 3.1: Klassifikation der Datentypen

Die erste Ebene dieser Klassifikation betrachtet die Datentypen unter dem Aspekt der
Struktur, man unterscheidet Daten einfacher Struktur und komplexer Struktur:
Einfache Datentypen sind Einzeldaten, ein Datenobjekt von einem bestimmten Typ
mit elementarer Struktur. Diese einfachen Datentypen sind in vielen Programmierspra-
chen vordefiniert und werden häufig Standarddatentypen genannt.
Strukturierte Datentypen sind jeweils mehrere Datenobjekte, die in bestimmter Wei-
se ‚angeordnet‘ sind, sie bilden eine Struktur. In Algorithmen kann diese Struktur
zweckmäßig genutzt werden oder auch hinderlich sein.

INM11 21
© HfB, 02.12.20, Berk, Eric (904709)

3 Daten, Datentypen, Datenstrukturen

Die nächste Ebene der Klassifikation der strukturierten Datentypen betrachtet den
Aspekt der Veränderbarkeit der Struktur des Datentyps. Wenn die Struktur der Daten-
typen durch Algorithmen geändert werden kann, spricht man von strukturierten dyna-
mischen Datentypen. Ist die Struktur der Datentypen festgelegt, dann werden sie
strukturierte statische Datentypen genannt.

Anmerkung:
Die Begriffe Datentyp und Datenstruktur werden in der Fachliteratur nicht ein-
heitlich gebraucht.
In diesem Studienheft soll vom Datentyp eines Datenobjektes gesprochen werden,
wenn dessen Wertebereich und die möglichen Operationen von Interesse sind. Sind
nicht die einzelnen Datenobjekte und deren Datentyp, sondern ihre Anordnung, die
Struktur, von Interesse, dann soll das Wort Datenstruktur benutzt werden.

3.2 Einfache Datentypen


Die einfachen Datentypen werden zur Umsetzung von Algorithmen in Programme sehr
häufig benötigt, deshalb sind sie in vielen Programmiersprachen bereits vordefiniert.
Zu den einfachen Datentypen gehören die ordinalen, geordneten und abzählbaren, Da-
tentypen und der nicht ordinale, nicht geordnete und nicht abzählbare, Datentyp, die
Gleitkommazahlen. Letzterer Datentyp wird oft Bruchzahl oder Fließkommazahl ge-
nannt und in den verschiedenen Programmiersprachen mit real, float oder double be-
zeichnet.
Die einfachen Datentypen ganze Zahlen, Zeichen und Gleitkommazahlen werden in
diesem Abschnitt durch den Wertebereich und die Operationen beschrieben sowie min-
destens durch ein Beispiel anschaulich.
Für jeden der ausgewählten einfachen Datentypen wird eine kurze Bezeichnung festge-
legt, die in den Studienheften INM11 und INM12 durchgängig verwendet wird.
Der einfache Datentyp Zeiger/Pointer ist in der Struktur elementar, also ein Datenob-
jekt. Die Werte, die Datenobjekte von diesem Datentyp annehmen, sind Adressen. Der
Datentyp Zeiger nimmt eine Sonderrolle ein. Er wird in diesem Abschnitt, weil er ein
wichtiger Datentyp der Programmiersprache C ist, mit vorgestellt und im Studienheft
INM12 ausführlich erläutert.
Ein Datenobjekt vom einfachen Datentyp erhält beim Algorithmieren und Programmie-
ren einen Namen, es ist ein Bezeichner für eine Variable oder einen konstanten Wert.
Andererseits wird im Speicher des Rechners unter einer bestimmten Adresse auf einem
dem Datentyp entsprechend großen reservierten Speicherplatz der aktuelle Wert abge-
legt. Über den Namen im Programm – zur Adresse im Speicher – zum Wert – erfolgt der
Zugriff auf eine Variable des einfachen Datentyps.

22 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Daten, Datentypen, Datenstrukturen 3

Es ist deshalb hilfreich, sich den einfachen Daten-


<name>
typ, wie in Abb. 3.2 gezeigt, mit <name> als Be-
zeichner, mit <adr> als Adresse und <wert> als ak- <adr> <wert>
tueller Wert, vorzustellen.
Abb. 3.2: Bildliche Vorstellung
eines Datenobjekts vom
einfachen Datentyp

3.2.1 Ausgewählte ordinale Datentypen


Die ausgewählten ordinalen Datentypen, die in diesem Abschnitt charakterisiert wer-
den, sind:
• Die ganzen Zahlen, der Datentyp wird mit int bezeichnet, und
• die Zeichen (Buchstaben, Sonderzeichen und Ziffern), der Datentyp wird mit char
bezeichnet.

3.2.1.1 Datentyp int


• Der Wertebereich des Datentyps int ist ein Teilbereich der ganzen Zahlen.
Sei die Variable x vom Typ int, dann ist die Zuweisung x : 45 möglich, aber eben
nicht die Zuweisung x : 4.5 (eine Gleitkommazahl bzw. eine rationale Zahl).
Der Variablen vom Datentyp int kann nur ein Element aus dem Bereich der ganzen
Zahlen zugewiesen werden. Programmiersprachen reagieren hier jedoch unter-
schiedlich. Während durch die Anweisung int x : 4.5; in C für die Variable x der
Wert vier bereitgestellt wird, ruft die gleiche Anweisung in Java einen Fehler hervor.
Erst wenn bekannt ist, mit wie viel Bits ein Datenobjekt vom Datentyp int rechner-
intern dargestellt ist, kann man den Wertebereich exakt angeben.
Werden z. B. zur Darstellung eines Datenobjekts vom Typ int 16 Bit verwendet,
dann sind maximal 216  28 · 28  256 · 256  65536 verschiedene Werte darstellbar.
Diese können dem Zahlenbereich –32767 bis 32768 einschließlich der Null zugeord-
net werden. Das bedeutet, ein Datenobjekt vom Typ int (16 Bit) kann nur einen Wert
aus diesem konkreten Teilbereich der ganzen Zahlen annehmen.
• In der Programmiersprache C wird der Datentyp int rechnerabhängig dargestellt.
• Für den Datentyp int sind u. a. folgende Operationen definiert:
– die zweistelligen Operationen
Addition (+), Subtraktion (–),
Multiplikation (∗), die ganzzahlige Division (/) (Division ohne Rest) und die
Operation Modulo (%) (Rest der ganzzahligen Division)
– die zweistelligen Relationen
 (größer),  (kleiner),  (gleich) und weitere
– die einstelligen Operationen/Funktionen
Dekrementieren (Vorgänger von x), Inkrementieren (Nachfolger von x).
Damit wird deutlich, dass der Wertebereich des Datentyps geordnet ist.

INM11 23
© HfB, 02.12.20, Berk, Eric (904709)

3 Daten, Datentypen, Datenstrukturen

Beispiel 3.1:
int x, y  16, z; Die Variablen x, y, z sind vom Datentyp int.
x  23; Diese Werte werden den int-Variablen zugewiesen.
y  45;
x  ––y; (Wertverringerung) x erhält den Wert 44, den Wert des Vorgängers
von y.
z  y; (Werterhöhung) z erhält den Wert 46, den Wert des Nachfolgers
von y

Beachten Sie bitte: In den Beispielen verwenden wir bereits die Schreibweise von
Deklarationen und Zuweisungen der Programmiersprache C.

3.2.1.2 Datentyp char


• Der Datentyp Zeichen char hat als Wertebereich eine Menge von Zeichen, Zeichen
eines Alphabets, Sonderzeichen und Ziffern.
• Die Zuweisung x : 'A' ist möglich. Das Zeichen 'A' gehört zum Alphabet unseres
Sprachbereichs, es ist in der ASCII-Zeichentabelle mit seiner Codierung, der
rechnerinternen Repräsentation, definiert.
• Siehe http://www.ascii-tabelle.com: Das Zeichen 'A' wird durch ein Bitmuster, das
der Dezimalzahl 65 entspricht, codiert.
• Das Zeichen 'G' wird durch ein Bitmuster, das der Dezimalzahl 71 entspricht, co-
diert.
• Es ist offensichtlich, dass keine arithmetischen Operationen im Wertebereich von
char definiert sind.
• Aber die zweistelligen Relationen, , ,  und  sind definiert.

Beispiel 3.2:
char x, y; → x und y sind Variablen vom Datentyp char, dann sind folgende Zuwei-
sungen möglich:
x  'A';
x  'G';
Der Test x  y? ist ebenso möglich. Der Wertevergleich 65 < 71 liefert als Ergebnis
den logischen Wert ,ja‘.

3.2.2 Nicht-ordinaler Datentyp float


• Der Wertebereich des Datentyps float enthält Dezimalbrüche als Näherungen für
reelle Zahlen. Rechnerintern werden sie durch Gleitkommazahlen (floating point)
dargestellt, und die Gleitkommaarithmetik ist die Grundlage für die Operationen.
Deshalb ist die Bezeichnung float treffend.

24 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Daten, Datentypen, Datenstrukturen 3

Beispiel 3.3:
float x; → Sei x vom Typ float, dann ist die Zuweisung
x  –4.25;
aber auch diese Zuweisung ist möglich: x  15;
Die ganzen Zahlen sind Dezimalbrüche mit dem Nenner 1 (15/1) – Datentyp
float.

• Für den Datentyp float sind u.a. folgende Operationen und Funktionen definiert:
Die zweistelligen Operationen:
– Addition(+), Subtraktion(–),
– Multiplikation(∗), die Division (/)
Die rechnerinterne Darstellung bestimmt den Wertebereich und die Rechengenauigkeit,
deshalb muss der Programmierer Eingaben, Zwischenergebnisse und Endergebnis durch
Überschlag prüfen. Zahlreiche Funktionen sind für diesen Datentyp vordefiniert.

Beispiel 3.4:
float x, y; → x und y sind vom Datentyp float, dann sind z. B.
y  sin(x) die Berechnung des Sinus definiert,
x  fabs(y) die Berechnung des absoluten Betrags.
In den verschiedenen Programmiersprachen gibt es jeweils Zusammenstellungen
der verfügbaren Funktionen.

Abschließend zu den einfachen Datentypen sind in Tab. 3.1 die in der Programmierspra-
che C vordefinierten sogenannten Standarddatentypen zusammengestellt. Die Anga-
ben zu Wertebereich und Genauigkeit sind für einen Programmierer wichtige Informa-
tionen.

Tab. 3.1: Datentypen in C

Wortsymbol für Bytes Wertebereich Genauigkeit


Datentyp
char 1 –128 … 127
unsigned char 1 0 … 255
unsigned short int 2 0 … 65535
signed short int 2 –32767 … 32768
signed long int 4 –2147483648 … 2147483647
unsigned long int 4 0 … 4294967295
float 4 1,17–38 … 3,438 6 Stellen
double 8 2,2–308 … 1,8308 19 Stellen

INM11 25
© HfB, 02.12.20, Berk, Eric (904709)

3 Daten, Datentypen, Datenstrukturen

3.3 Statische Datenstrukturen


Diese Klassifikation ist ein Ausschnitt der
statische Datentypen/Datenstrukturen
Abb. 3.1. Die aufgeführten Datenstrukturen
Feld und Verbund werden in Algorithmen
und Programmen sehr häufig zur Bereit-
stellung und Speicherung von komplexeren Feld Verbund Menge
Daten und Datenbeständen benötigt. Sie
Abb. 3.3: Auszug aus Abb. 3.1
werden deshalb in diesem Abschnitt be-
schrieben.

3.3.1 Datenstruktur Feld


Die Datenstruktur Feld ist eine Aneinan-
e1 e2 … en
derreihung von Daten gleichen Datentyps.
Eine Vorstellung von dieser Struktur ist Abb. 3.4: Darstellung der Datenstruktur
durch Abb. 3.4 gegeben: Feld (Feld e mit n Elementen)

• Die Elemente des Feldes e, e1, e2, …, en sind im Speicher linear angeordnet. Die An-
zahl der Elemente wird in der Deklaration einer Variablen oder einer Konstanten
dieses Datentyps festgelegt und ist im Gültigkeitsbereich der Deklaration fest (sta-
tisch) nicht veränderbar.

Beispiel 3.5:
float messung[233];
Mit dieser Deklaration wird der Speicherplatz für 233 Gleitkommazahlen unter
dem Variablennamen messung reserviert.

Die ‚Ähnlichkeit‘ zwischen der Datenstruktur Feld und dem eindimensionalen Vek-
tor in der Mathematik ist offensichtlich. In dieser Datenstruktur kann eine Menge
von beliebig vielen Zahlenwerten, z. B. 233, gespeichert und für die Verarbeitung be-
reitgestellt werden. Die Berechnung der Summe der Zahlenwerte, die Berechnung
eines Durchschnittswertes u. a. Berechnungen sind möglich.
• Der wahlfreie Zugriff auf die Elemente der Datenstruktur ist die grundlegende
Operation dieser Datenstruktur. Durch den Index, der die Position des Elements im
Feld angibt, kann auf ein bestimmtes Feldelement ‚zugegriffen‘ werden. Somit sind
Aktionen wie die Zuweisung oder die Ausgabe auf einzelne Elemente anwendbar.

Beispiel 3.6:
float messung[233];
messung[45]  24.89; Zuweisung an das Element mit dem Index 45.
printf("Der Wert ist %f Grad Celsius.", messung[15]); Ausgabe
des Wertes des Elementes mit Index 15.

26 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Daten, Datentypen, Datenstrukturen 3

Beispiel 3.7:
char Vorname[15];
Mit dieser Deklaration wird der Speicherplatz für 15 Zeichen unter dem Variab-
lennamen Vorname reserviert. Eine Folge von Zeichen ist ein Feld.

Beachten Sie bitte: In den Beispielen verwenden wir bereits die Schreibweise von
Deklarationen und Zuweisungen der Programmiersprache C. In C beginnt der Index
bei 0 und endet bei 15 deklarierten Elementen mit dem Index 14.
• Operationen wie das Anfügen von weiteren Elementen, das Löschen von bestimm-
ten Elementen des Feldes und das Sortieren der Elemente des Feldes, die Ermittlung
des Maximums usw. sind nicht vordefiniert.
Die zur Lösung dieser Aufgaben notwendigen Algorithmen gehören zu den Stan-
dardalgorithmen, die auf die konkrete Anwendung angepasst werden müssen.

3.3.2 Datenstruktur Verbund – Datensatz


Die Datenstruktur Verbund wird in der Fachliteratur mit record oder structure bezeich-
net. Die Bezeichnung Datensatz ist unabhängig von einer Programmiersprache und für
die Verwendung in Algorithmen treffender, deshalb wird diese im Folgenden verwen-
det.
• Ein Datensatz besteht aus mehreren Komponenten, die gleichen oder unter-
schiedlichen Datentyps sind. In der Deklaration wird dem Datensatz ein Name, ein
Bezeichner, zugeordnet und die Komponenten durch die Angabe eines Namens und
den entsprechenden Datentyp festgelegt.
In den folgenden Beispielen wird die Schreibweise der Programmiersprache C bereits
benutzt. Das Wort struct ist das Schlüsselwort zur Deklaration von Datensätzen in C.

Beispiel 3.8:
1. struct datum { int tag, monat, jahr; }; Der Datensatz datum hat
drei Komponenten vom Datentyp int.
2. struct datum heute; ist die Deklaration einer Variablen vom Datensatztyp
datum.

Beispiel 3.9:
struct person {
char Name[35], Vorname[15];
int Nummer;
struct datum geboren;
};

Der Name dieser Datenstruktur ist person. Der Datensatz hat vier Komponenten:
• Zwei Komponenten sind jeweils eine Datenstruktur, Feld, Name und Vorname, mit
Elementen vom Datentyp char, wobei die Anzahl der Feldelemente in der Deklara-
tion festgelegt ist.
• Eine Komponente, die Nummer, ist vom Datentyp int.
• Eine Komponente ist ein Datensatz, Datentyp datum.

INM11 27
© HfB, 02.12.20, Berk, Eric (904709)

3 Daten, Datentypen, Datenstrukturen

Durch diese Deklaration kann in einem Datenobjekt, einer Variablen vom Datensatztyp
person, die Information über eine reale Person, deren Familienname, Vorname, eine
Nummer sowie das Geburtsdatum codiert, gespeichert und zur Verarbeitung bereitge-
stellt werden.
Hinweis:
Ein Datenbestand, der von einer festen Anzahl von Personen genau diese Daten bereit-
stellt und speichert, könnte durch die Verwendung einer solchen Datenstruktur, ein Feld
mit Elementen des Datentyps person, also Datensätzen, aufgebaut werden, z. B.
person KegelVerein[54];

Durch diese Deklaration wird Speicherplatz für die Angaben (s. o.) zu 54 Personen im
Speicher reserviert.
• Für die Datenstruktur Datensatz ist der Zugriff auf die Komponenten durch den
Punkt-Operator (.) definiert. Der Name des Datensatzes und der Name der Kompo-
nente des Datensatzes werden als Angaben benötigt.
• <name_Datensatz>.<name_Komponente>.<…>

Beispiel 3.10:
struct datum { int tag, monat, jahr; };
struct datum gebDatum;

Die Variable gebDatum vom Datensatztyp datum erhält, wie auch andere Datenobjekte,
durch Zuweisung einen Wert, z. B. das Datum 12.04.1998.
Die Werte von Tag, Monat und Jahr werden durch den komponentenweisen Zugriff mit-
hilfe des Punkt-Operators zugewiesen:
gebDatum.tag = 12;
gebDatum.monat = 4;
gebDatum.jahr = 1998;

Beispiel 3.11:
struct person {
char Name[35], Vorname[15];
int Nummer;
struct datum geboren;
};
struct person KegelVerein[54];

Die Variable KegelVerein ist eine Datenstruktur Feld, sie hat 54 Elemente vom Daten-
satztyp person.
Der wahlfreie Zugriff auf ein Element des Feldes mithilfe des Index und die Anwendung
des Punkt-Operators ermöglichen den Zugriff auf ein bestimmtes Datenobjekt.
Das Geburtsdatum des 24. Mitglieds (Index 23), der 21.03.1978 wird durch die folgen-
den Zuweisungen in den Datenbestand übernommen:
KegelVerein[23].geboren.tag = 21;
KegelVerein[23].geboren.monat = 3;
KegelVerein[23].geboren.jahr = 1978;

28 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Daten, Datentypen, Datenstrukturen 3

3.4 Datentyp Zeiger/Pointer


Ein Datenobjekt vom Datentyp Zeiger hat als Wert eine Adresse, die auf ein Datenob-
jekt eines bestimmten Typs oder auf eine Datenstruktur verweist bzw. zeigt.
Ein Zeiger ist deshalb immer an einen Datentyp/eine Datenstruktur gebunden.
In der Programmiersprache C werden Deklarationen mit dem Datentyp Zeiger/Pointer
mit dem Indirektionsoperator ∗ vorgenommen.

Beispiel 3.12:
int *zi; Die Variable zi ist ein Zeiger, der auf ein Datenobjekt vom Datentyp int
zeigt
struct person *zperson; Die Variable zperson ist ein Zeiger, der auf einen Da-
tensatz person, der vorher definiert wurde, zeigt.

Im Wertebereich des Datentyps Zeiger sind folgende Operationen definiert:


• die Zuweisung,
• die zweistelligen Relationen  (größer),  (kleiner),  (gleich),
• die zweistellige Operation Subtraktion,
• der Referenz-Operator &, um eine Zeigervariable mit einem Adresswert zu initi-
alisieren,
• der Dereferenz-Operator * (die Umkehroperation zu &), um den Wert zu ermitteln,
der unter der Adresse steht, auf die die Zeigervariable zeigt.

Beispiel 3.13:
int *z, zahl, h; Deklaration einer Zeigervariablen und von zwei Variablen
vom int Typ
zahl = 233; Zuweisung eines Wertes an die Variable zahl
z = &zahl; Der Zeigervariablen wird die Adresse der Variablen zugewie-
sen, z zeigt auf Speicherplatz von zahl.
h = *z; Der Variablen h wird der Wert, der unter der Adresse (Wert
von z) steht, zugewiesen.

Der Datentyp Zeiger bietet in der Programmiersprache C eine wichtige Grundlage zur
Umsetzung des Modulkonzepts. Andererseits werden Zeiger zur Erzeugung von dyna-
mischen Datenstrukturen benötigt.

Zusammenfassung

Der Algorithmus, die Vorschrift zur Ausführung von elementaren Operationen, dient
der Verarbeitung von Eingangsdaten zu Ausgangsdaten. Die Begriffe Datentyp und Da-
tenstruktur unterstützen eine nähere Betrachtung der Verarbeitungsdaten. Deren Viel-
falt wird durch die vorliegende Klassifikation der Datentypen bzw. Datenstrukturen
deutlich.

INM11 29
© HfB, 02.12.20, Berk, Eric (904709)

3 Daten, Datentypen, Datenstrukturen

Datentypen werden durch den Wertebereich und die in diesem Bereich definierten Ope-
rationen charakterisiert. Die einfachen Datentypen – int, char, float - und der Datentyp
Zeiger gehören zu den Standarddatentypen von Programmiersprachen.
Die ausgewählten statischen Datenstrukturen – Feld und Verbund – können zwar in ih-
rer Grundstruktur beschrieben werden. Jedoch die Operationen mit diesen Datenstruk-
turen bzw. mit deren Elementen (Standarddatentypen) sind nicht vordefiniert. Für die
konkrete Datenstruktur bzw. die jeweiligen Elemente müssen diese durch spezielle Al-
gorithmen/Operationsfolgen definiert werden. Sogenannte Standardalgorithmen ent-
standen in der Entwicklung der Datenverarbeitung.
Zu Beginn von Kapitel 2 wurde die Frage nach dem Zusammenhang von Algorithmus
und Daten gestellt. Die Antwort ergibt sich nun zusammenfassend aus den Kapiteln 2
und 3.
Zur Lösung von Problemen durch Algorithmen werden konstante und/oder variable
Werte als Daten benötigt. Die gewählten Datentypen und/oder Datenstrukturen ermög-
lichen oder verhindern Operationen, die Ausführung von Aktionen, die der Algorithmus
vorgibt.
Wenn in einem Algorithmus bestimmte Operationen notwendig sind, muss mit einem
bestimmten Datentyp sowie einer bestimmten Datenstruktur gearbeitet werden. So
werden die Operationen, die zur Lösung der Aufgabe nötig sind, möglich. Die optimale
Abstimmung zwischen Daten/Datenstruktur und Algorithmus ist eine der wichtigsten
Voraussetzungen für ein effizientes Programm.

Aufgaben zur Selbstüberprüfung

3.1 Für die rechnerinterne Darstellung eines Datentyps short int werden
2 Byte  2 · 8 Bit verwendet (siehe Tab. 3.1).
Berechnen Sie, wie viel verschiedene Bitfolgen und damit verschiedene Werte dar-
gestellt werden können?
Ein Beispiel einer Bitfolge: 0000 1010 1000 1101
3.2 Der Datentyp float wurde als nicht ordinaler Datentyp beschrieben, sein Werte-
bereich ist eine Teilmenge der Menge der reellen Zahlen.
In diesem Datentyp sind die Operationen Dekrementieren (Nachfolger von x ge-
sucht) bzw. Inkrementieren (Vorgänger von x gesucht) nicht definiert.
Begründen Sie, warum diese Operationen für diesen Datentyp keinen Sinn erge-
ben.
3.3 Für den Datentyp int ist die Potenzfunktion nicht definiert. Beschreiben Sie einen
Algorithmus für die Berechnung von erg  b pot, wobei b  0 und pot  0 vom Da-
tentyp int sind.
3.4 Nutzen Sie die ASCII- Zeichentabelle (http://www.ascii-tabelle.com) und ermit-
teln Sie das Ergebnis des Vergleichs „Der“  „Zu“.
Erläutern Sie, wie Sie das Ergebnis ermittelt haben.

30 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Daten, Datentypen, Datenstrukturen 3

3.5 Ein Datensatz Bankkonto soll folgende Komponenten aufweisen:


• Informationen zum Kontoinhaber, den Namen, den Vornamen, das Geburtsda-
tum und die Adresse mit PLZ, Straße und Wohnort,
• Information zum Kontostand,
• Information zum Typ des Kontos, z. B. ‚Giro‘ oder ‚Fest‘ o. ä. Abkürzungen, die
aus vier Buchstaben bestehen.
Schreiben Sie eine Deklaration analog den Beispielen zum Datentyp Datensatz
auf.

INM11 31
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen


Die Beschreibung einer Schrittfolge zur Lösung einer Aufgabe mit Worten, als
Text in der Umgangssprache, ist keine geeignete Grundlage für das Erstellen von
Software. Eine präzise beschriebene Folge von Aktionen ist erforderlich. Algorith-
men werden deshalb mithilfe von Texten in einer Kunstsprache beschrieben oder
grafisch, unter Verwendung von genormten Sinnbildern, dargestellt. Die grafi-
sche Darstellung eines Algorithmus durch ein Struktogramm wird hier aus zwei
Gründen bevorzugt: Einerseits ist das Struktogramm ein zweckmäßiges Hilfsmit-
tel beim Entwurf von Algorithmen und andererseits lassen sich die so dargestell-
ten prozeduralen Algorithmen relativ einfach in C-Programme umsetzen. Die
Grundstrukturen von Struktogrammen nach Nassi-Shneiderman werden in die-
sem Kapitel erläutert und jede durch mindestens ein Beispiel veranschaulicht.

4.1 Struktogramm
In der Fachliteratur findet man unterschiedliche Arten der Beschreibung und Darstel-
lung von Algorithmen. Neben der informellen sprachlichen Beschreibung durch eine
Kunstsprache, den sogenannten Pseudocode, sind es grafische Darstellungen in Form
von Programmablaufplänen (PAP) bzw. Flussdiagramme und Struktogramme. Die Pro-
grammablaufpläne gelten in der Softwareentwicklung zwar als veraltet, werden aber in
der Dokumentation zur Darstellung eines bestimmten Ablaufverhaltens von Algorith-
men noch häufig verwendet.
Struktogramme stellen Algorithmen im Vergleich zu Programmablaufplänen übersicht-
licher dar. Diese klar strukturierte Abfolge von Aktionen ist gut lesbar und lässt sich re-
lativ leicht in ein Programm umsetzen, zudem ist diese Umsetzung teilweise z. B. durch
das CASE-Tool sogar automatisierbar.
Nassi und Shneiderman haben diese Darstellung 1973 entwickelt, um durch klare Re-
gelungen die Kommunikation zwischen Entwicklern von Software zu erleichtern. Sie
schufen gleichzeitig ein Werkzeug für die Darstellung lesbarer, übersichtlicher und er-
weiterbarer Algorithmen.
Die Sinnbilder für Struktogramme nach Nassi-Shneiderman sind im Blatt 66 261 der
DIN seit 1985 festgelegt.
„Die äußere Form eines Sinnbildes ist immer ein Rechteck; die Unterteilung er-
folgt nur durch gerade Linien.
Die obere Linie eines jeden Sinnbildes bedeutet den Beginn der Verarbeitung,
die untere Linie das Ende der Verarbeitung.
Jedes Sinnbild kann als erste Innenbeschriftung einen Bezeichner tragen. Die
Größe der Gesamtdarstellung und die Unterteilung der Sinnbilder darf dem je-
weiligen Anwendungsfall entsprechend gewählt werden.“
Das elementare Sinnbild ist der Block, ein Rechteck.
Aktion

32 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Darstellung von Algorithmen 4

Ein Block hat folgende Eigenschaften:


• Er beinhaltet eine Aktion, eine elementare Aktion oder eine logische Struktur oder
einen (Unter)Block, einen Teilalgorithmus.
• Er hat nur einen Eingang (obere Kante) und nur einen Ausgang (untere Kante).
• Es ist möglich, durch Aneinanderfügen und/oder Ineinander-Schachtelung neue
Blöcke zu schaffen.

4.2 Grundstrukturen in Struktogrammen


Es ist erwiesen, dass jeder Algorithmus mit den Grundstrukturen
elementare Aktion, Folge, Alternative und Wiederholung
realisierbar und damit darstellbar ist. Diese Grundstrukturen eines Struktogramms wer-
den im folgenden Abschnitt erläutert und durch Beispiele veranschaulicht. Das Modul
als Grundstruktur ermöglicht die Modularisierung von Algorithmen und damit auch die
Wiederverwendbarkeit entwickelter Teilalgorithmen in verschiedenen Aufgabenstel-
lungen.

4.2.1 Elementare Aktionen


Die elementaren Aktionen eines Algorithmus, die Zuweisung, die Eingabe und die
Ausgabe und die leere Aktion werden in Struktogrammen als Block, als eine Aktion,
mit der jeweiligen Beschriftung dargestellt.
Die Zuweisung ist eine Aktion, die den Wert der links stehenden Variablen verändert.
Rechnerintern bedeutet das, dass der Inhalt des Speicherplatzes, der für diese Variable
reserviert ist, mit dem Ergebnis der Auswertung des rechts stehenden Ausdrucks „über-
schrieben“ wird, der alte Inhalt geht verloren.
<variable> ist ein Platzhalter für einen konkreten
<variable> := <ausdruck>
Variablennamen,
: ist das Ergibtzeichen und
<ausdruck> steht für einen konkreten mathemati-
schen Ausdruck.

Beispiel 4.1:
(1) a : 5
(2) a : a · 15
(3) c : a/b
(4) x : 4 + a/7
(5) x : (9 + x)/77

INM11 33
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen

Anmerkungen zu den Beispielen:


• Links und rechts vom Ergibtzeichen kann ein und dieselbe Variable stehen. Vgl. in
(2) und (5), der Wert von a bzw. x wird verändert.
• Ausdrücke werden in linearer Form notiert. Vgl. in (3) die Schreibweise des Bruches.
• Durch runde Klammern sind Änderungen in der Berechnungsreihenfolge möglich.
Vgl. (5) gegenüber (4).
• Die Variable der linken Seite und der Ausdruck müssen typverträglich sein, d. h., der
berechnete Wert des Ausdrucks muss dem Datentyp der Variablen entsprechen. Vgl.
in (3):
– Sind die Variablen a, b und c vom Datentyp int, dann kommt die ganzzahlige
Division zum Einsatz, die Typverträglichkeit ist gegeben.
– Sobald aber eine der Variablen der rechten Seite vom Datentyp float ist, dann
muss c (linke Seite) auch vom Datentyp float sein, dann erst ist die Zuweisung
möglich.
Die Eingabe ist eine elementare Aktion, sie
Eingabe: <variable>, <variable>, …
ermöglicht die Übernahme von über die
Tastatur eingegebenen Werten.

Beispiel 4.2:
(1) Eingabe: x, y, z
(2) Eingabe: n, wn mit i  1(1)n
(3) Eingabe: m, n, matmn mit i  1(1)m und k  1(1)n

Anmerkungen zu den Beispielen:


• Es können endlich viele Variablen in der Liste stehen, sie werden durch Komma ge-
trennt aufgeschrieben. Jede dieser Variablen erhält einen Wert durch die Eingabe zu-
gewiesen. In (1) sind es drei Variablen.
• Soll die Eingabe von einer endlichen Anzahl von Werten in eine Variable vom Da-
tentyp Feld erfolgen, dann wird diese Schreibweise verwendet, siehe (2) und (3).
Zu (2): Die Anzahl n und die einzelnen Elemente des Feldes wn werden in einer be-
stimmten Reihenfolge eingegeben.
Die Schreibweise i  1(1)n bedeutet, dass der Index i die Werte 1 bis n in Einerschrit-
ten (i : i + 1) durchläuft.
Zu (3): Die Werte für m und n werden eingegeben. Mit matmn wird deutlich, dass es
sich um eine Variable vom Datentyp Feld mit zwei Dimensionen (Zeile und Spalte),
also eine Matrix, handelt. Durch die Zähler i  1(1)m und k  1(1)n erfolgt die Zu-
ordnung der Eingabe zu einem Matrixelement.
Die Aktion Ausgabe steht für die Übergabe
Ausgabe: <text> ,<variable>, …
von Text und/oder aktuellen Werten an ein
Ausgabemedium.

34 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Darstellung von Algorithmen 4

Beispiel 4.3:
(1) Ausgabe: : x, y, z
(2) Ausgabe: ,Es gibt keine Loesung fuer diese Gleichung‘
(3) Ausgabe: ,Der groesste gemeinsame Teiler betraegt‘ x

Anmerkungen zu den Beispielen:


• Zu (1): es können endlich viele Variablen in der Liste stehen, sie werden durch Kom-
ma getrennt aufgeschrieben.
• Zu (2): der Text zwischen , ‘ erscheint in der Ausgabe wie angegeben.
• Zu (3): der Text und der Wert der Variablen werden ausgegeben.
Die leere Aktion (nichts tun) hat als elementare Aktion
keine Wirkung auf Werte von Variablen oder auf die Folge
der Aktionen, sie steht einfach dafür, dass in dem Block
‚nichts zu tun ist‘.
In den nachfolgenden Beispielen werden auch leere Aktionen vorkommen.

4.2.2 Folge (Sequenz)


Die Aktion Folge besteht aus einer Aneinanderreihung
Aktion 1
von n Aktionen, die in der Richtung des Datenaustauschs,
in der Reihenfolge 1 bis n genau einmal ausgeführt wer- Aktion 2
den. …

Aktion n

Beispiel 4.4:
Die Werte der Variablen a und b sollen vertauscht werden:
1. Aktion: a wird in h zwischengespeichert.
2. Aktion: a wird der Wert von b zugewiesen.
3. Aktion: b wird der Wert, der in h zwischengespeichert wurde, zugewiesen.

Diese Folge von Zuweisungen muss genau so aus-


h := a
geführt werden.
a := b
Die Folge als Struktur eines Algorithmus ist sehr
starr. Sie kommt als alleinige Struktur nur bei b := h
einfachen Algorithmen vor. Als Grundstruktur
Abb. 4.1: Folge von Zuweisungen,
unterstützt sie jedoch die Zerlegung umfangrei-
zu Beispiel 4.4
cher Aufgabe in elementare Aktionen.

INM11 35
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen

4.2.3 Auswahl (Selektion)


Die Aktion Auswahl wird zur Steuerung der Ausführung von Aktionen in Abhängigkeit
von einer Bedingung verwendet. Es gibt zwei verschiedene Möglichkeiten der Auswahl
1 aus 2 (Alternative) oder die Auswahl 1 aus n (Fallunterscheidung).

Die einfache Auswahl ermöglicht die Wahl


<bedingung>
einer Aktion aus zwei Aktionen.
Ja Nein
Ist die Bedingung <bedingung> erfüllt (Ja), Aktion 1 Aktion 2
wird die Aktion 1 ausgeführt, andernfalls
(Nein) die Aktion 2.
Hinweis zur praktischen Nutzung: Die Aktion 2 kann auch die leere Aktion sein.

Beispiel 4.5:
Von einer Zahl (zahl ) soll ihr Absolutbetrag (Betrag) ermittelt werden. Der Algo-
rithmus beginnt mit der Eingabe der Zahl; nur wenn die eingegebene Zahl eine ne-
gative ganze Zahl ist, ist eine Aktion nötig, z. B. zahl  –7  |–7|  7.

Eingabe: zahl

zahl < 0
Ja Nein
zahl := zahl · –1

Ausgabe: 'Betrag' zahl

Abb. 4.2: Struktogramm zu Beispiel 4.5

Dieses Struktogramm (Abb. 4.2) ist eine Folge von drei Aktionen:
1. Aktion: Eingabe der Zahl durch den Nutzer, die Variable zahl bekommt einen Wert
zugewiesen.
2. Aktion: Alternative: Die Bedingung „Ist der Wert der Variable zahl kleiner als Null?“
wird geprüft. Abhängig vom Ergebnis wird entweder die Aktion im Ja-Zweig
(hier die Multiplikation mit -1) ausgeführt oder die leere Aktion im Nein-
Zweig (Nichts getan!).
3. Aktion: Ausgabe des angegebenen Textes „Betrag“ und des Wertes der Variablen zahl
erfolgt.
• Die mehrfache Auswahl ermöglicht die Wahl einer Aktion aus mehreren Aktionen,
1 aus n (Fallunterscheidung). Die Übereinstimmung des Fallausdrucks mit einer
der Fallkonstanten ist die Grundlage der Entscheidung für eine Aktion i, mit
i  1 … n.

36 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Darstellung von Algorithmen 4

Variante 1:

<fallausdruck>

<fall 1> <fall 2> … <fall n>


Aktion 1 Aktion 2 … Aktion n

Hinweis:
Wenn der Fallausdruck einen Wert hat, der in der Menge der verschiedenen Fallkon-
stanten {<fall1>, <fall 2>, …, <fall n>} nicht vorkommt, dann wird keine Aktion aus-
geführt. Um diesen Fall zu berücksichtigen, sollte die Fallunterscheidung einen
sonst-Zweig enthalten.

Variante 2:

<fallausdruck>

<fall 1> <fall 2> … <fall n> <sonst>


Aktion 1 Aktion 2 … Aktion n Aktion

Beispiel 4.6:
Innerhalb eines Algorithmus soll durch den Nutzer durch die Eingabe einer positi-
ven ganzen Zahl (wahl ) der weitere Fortgang des Algorithmus bestimmt werden.
Zur Auswahl stehen drei Rechenoperationen (ROp 1 bis ROp 3).
Durch Eingabe eines Wertes für wahl  {1, 2, 3}, wird alternativ die jeweilige Re-
chenoperation ausgeführt. Wenn wahl  {1, 2, 3} ist, dann wird keine der angebote-
nen Rechenoperationen ausgeführt. Ein Text wird in diesem Fall ausgegeben.

Eingabe: wahl

wahl

1 2 3 sonst
Ausgabe:
ROp 1 ROp 2 ROp 3
'keine Op'

Abb. 4.3: Struktogramm zu Beispiel 4.6

INM11 37
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen

4.2.4 Wiederholung (Schleife, Zyklus, Iteration)


Die Aktion Wiederholung dient der Umsetzung der mehrfachen Ausführung einer Ak-
tion in einem Algorithmus. Die Worte Schleife, Zyklus oder Iteration werden in der
Fachliteratur als Synonyme zur Bezeichnung der Grundstruktur Wiederholung verwen-
det.
Die wiederholt auszuführende Aktion, der „Kern“ einer Schleife, wird Schleifenkörper
genannt. Die Bezeichnungen Schleife und Schleifenkörper sind besonders anschaulich,
sie werden deshalb im folgenden Text verwendet.
Zwei grundlegende Arten von Schleifen können in einem Struktogramm vorkommen:
• die durch eine Bedingung gesteuerten Schleifen, wobei vor der Ausführung des
Schleifenkörpers – anfangsgeprüfte Schleife oder nach der Ausführung des Schlei-
fenkörpers – endgeprüfte Schleife geprüft werden kann
• und die durch einen Laufbereich gesteuerte Schleife, die Zählschleife.
Wir betrachten zuerst die bedingten Schleifen einzeln, die anfangs- und die endgeprüfte
Schleife, und anschließend die Zählschleife.

4.2.4.1 Anfangsgeprüfte Schleife


Die anfangsgeprüfte Schleife wird in der Fachliteratur häufig kopfgesteuerte oder ab-
weisende Schleife genannt. Vor jedem Schleifendurchlauf, der Ausführung des Schlei-
fenkörpers, wird eine Bedingung ausgewertet.
Ist die Bedingung erfüllt, so wird der
Wiederhole so lange <bed>
Schleifenkörper ausgeführt und anschlie-
ßend ein erneuter Schleifendurchlauf ver-
sucht. <schleifenkoerper>

Ist die Bedingung nicht erfüllt, so wird


die Schleife insgesamt beendet.
Der Name abweisende Schleife ist treffend. Wenn die Bedingung schon vor dem ersten
Schleifendurchlauf nicht erfüllt ist, dann wird sie nicht durchlaufen.

Beispiel 4.7: Zinseszinsrechnung

Ein Kunde schließt bei einer Bank


Eingabe: AKap, EKap, ZSatz (in %)
einen Sparvertrag mit folgenden
Konditionen ab: Er nimmt eine Ein- guthaben := AKap
zahlung von AKap (Startkapital)
Lz := 0
vor. Die Bank garantiert einen fes-
ten Zinssatz ZSatz (Zinsen pro Wiederhole so lange (guthaben < EKap)
Jahr). Da der Kunde ein festes Spar- guthaben := guthaben * (1 + ZSatz/100)
ziel (EKap) hat, möchte er wissen,
nach wie viel Jahren er das Sparziel Lz := Lz + 1
erreicht hat. Die Laufzeit (Lz) des Ausgabe: guthaben, Lz
Sparvertrags wird ermittelt.
Abb. 4.4: Struktogramm zu Beispiel 4.7

38 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Darstellung von Algorithmen 4

Der Algorithmus zur Zinseszinsberechnung besteht aus fünf Aktionen:


1. Aktion: Eingabe des Startkapitals, des Endkapitals und des Zinssatzes
2. Aktion: Zuweisung von AKap an die Variable guthaben für die Berechnung
3. Aktion: Zuweisung des Startwertes an die Variable Lz, die Laufzeit.
4. Aktion: Schleife
Vor Eintritt in die Schleife oder der wiederholten Ausführung erfolgt die Prü-
fung der Bedingung:
Ist das Guthaben kleiner als das Endkapital?
Wenn ja, dann wird der Schleifenkörper, die zwei Aktionen, ausgeführt.
1. Die Berechnung des Guthabens zum Ende eines Sparjahres
2. Die Erhöhung der Laufzeit um ein Jahr.
Wiederholung?
Wenn nein, dann wird die Schleife beendet und die 5. Aktion ausgeführt.

5. Aktion: Ausgabe des Wertes der Variablen guthaben und Lz.

4.2.4.2 Endgeprüfte Schleife


Die endgeprüfte Schleife wird in der Fachliteratur häufig fußgesteuerte oder nicht ab-
weisende Schleife genannt. Nach jedem Schleifendurchlauf, der Ausführung des Schlei-
fenkörpers, wird die Bedingung ausgewertet.

Ist die Bedingung nicht erfüllt, so wird


der Schleifenkörper wiederholt ausgeführt <schleifenkoerper>
und anschließend die Bedingung wieder
geprüft.
Wiederhole bis <bed>
Ist die Bedingung erfüllt, so wird die
Schleife insgesamt beendet.
Der Name nicht abweisende Schleife ist treffend. Die Schleife wird mindestens einmal
durchlaufen, der Schleifenkörper ausgeführt und anschließend die Bedingung ausge-
wertet.

Beispiel 4.8: Eingabeabsicherung


Der Nutzer eines Programms kann sinnvolle, aber auch falsche, nicht passende Ein-
gaben machen. Es ist deshalb notwendig, die Eingabe des Nutzers (Eingabedaten)
vor der eigentlichen Verarbeitung zu prüfen. Diese Aufgabe nennt man kurz Einga-
beabsicherung. Tritt ein Fehler auf, muss die Eingabe wiederholt werden.
Angenommen der Algorithmus benö-
Ausgabe: 'Geben Sie 1 oder 2 ein!'
tigt eine Eingabe wahl  {1 oder 2};
erst wenn die Zahl 1 oder 2 eingege- Eingabe: wahl
ben wurde, wird der Algorithmus wei-
Wiederhole bis (wahl = 1) ODER (wahl = 2)
ter ausgeführt.
Abb. 4.5: Struktogramm zu Beispiel 4.8

Die nicht abweisende/endgeprüfte Schleife ist dafür gut geeignet.

INM11 39
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen

Gehen Sie gedanklich zwei mögliche Fälle durch:


(1) Der Nutzer gibt die Zahl 5 ein, wahl wird der Wert 5 zugewiesen.
Die Bedingung (wahl  1) ODER (wahl  2) ist damit nicht erfüllt, der Schleifen-
körper wird erneut ausgeführt.
(2) Der Nutzer gibt die Zahl 2 ein, wahl wird der Wert 2 zugewiesen.
Die Bedingung (wahl  1) ODER (wahl  2) ist damit erfüllt, die Schleife wird ver-
lassen, die Ausführung der Schleife beendet.

4.2.4.3 Zählschleife
Eine durch einen Laufbereich gesteuerte Schleife wird im Folgenden Zählschleife ge-
nannt, sie ähnelt in Sinnbild und Ausführung der anfangsgeprüften Schleife.
Der Schleifenkörper der Zählschleife wird für jeden Wert eines gegebenen Laufbereichs
je einmal ausgeführt. Der Laufbereich wird mit einer Zählvariablen, die sich von einem
Startwert hin zu einem Endwert mit einer bestimmten Schrittweite ändert, durchlau-
fen.
Mit Eintritt in die Schleife ist die Anzahl
Fuer <zaehlvar> := <anfw> (Sw) <endw>
der Wiederholungen bekannt, diese soll-
te auch nicht manipuliert werden.
<schleifenkoerper>

Die Zählschleife wird eingesetzt, wenn ein vorher definierter Wertebereich lückenlos,
d. h. mit der angegebenen Schrittweite (Sw), vom Anfangswert (anfw) bis zum Endwert
(endw) durchlaufen werden soll.

Beispiel 4.9:

Der Wert von n! (z. B. 4!) soll be-


Eingabe: n (a)
rechnet werden. Nicht nur das End-
ergebnis der Rechnung soll ausgege- fakul := 1 (b)
ben werden, sondern auch die
Fuer i := 1(1) n (c)
Zwischenergebnisse.
fakul := fakul * i

Ausgabe: fakul

Abb. 4.6: Struktogramm zu Beispiel 4.9

Dieses Struktogramm des Algorithmus zur Berechnung von n! besteht aus einer Folge
von drei Aktionen:
• einer Eingabe (a),
• einer Zuweisung (b) und
• einer Zählschleife (c).

40 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Darstellung von Algorithmen 4

Durch einen Trockentest und das Erstellen eines Werteverlaufsprotokolls lässt sich
die schrittweise Ausführung eines durch ein Struktogramm dargestellten Algorithmus
gut verfolgen. Als Hilfsmittel wird eine Tabelle genutzt, die Variablen und deren Verän-
derung werden in Spalten und Zeilen nacheinander in einzelnen Schritten als Simulati-
on der Ausführung des Algorithmus protokolliert.
Werteverlauf in Tabellenform zum Beispiel 4.9 – Berechnung von 4!:

Variablen/Spalten

fakul i n i <= n Folge der Schritte/Zeilen


Startwert: (a) 4
(b) 1 4
(c) 1 4 ja
(c) 1·1 4
(c) Ausgabe 1
(c) 2 4 ja
(c) 1·2 4
(c) Ausgabe 2
(c) 3 4 ja
(c) 2·3 4
(c) Ausgabe 6
(c) 4 4 ja
(c) 6·4 4
(c) Ausgabe 24
(c) 5 4 nein 5 < 4, Ende der Berechnung 4!

Innerhalb einer Zählschleife, also in ihrem Schleifenkörper, kann wiederum eine Zähl-
schleife als Aktion auftreten, man nennt diese Aktion dann geschachtelte Schleife.
Ein typisches Beispiel für die Nutzung von zwei ineinander geschachtelten Zählschlei-
fen zeigt das folgende Struktogramm.
Der dargestellte Algorithmus realisiert
Eingabe: m (Zeilen), n (Spalten)
die Eingabe der Elemente einer Matrix
(mat) mit m Zeilen und n Spalten. Auf Fuer zeile := 1(1) m
ein Element der Matrix wird mit zwei
Fuer spalte := 1(1) n
Indizes, zeile und spalte, zugegriffen,
deshalb mat[zeile,spalte]. Da alle Ele- Eingabe: mat[zeile,spalte]
mente einen Wert erhalten sollen, kom-
men zwei geschachtelte Zählschleifen Abb. 4.7: Struktogramm: Eingabe einer 
zum Einsatz. m, n-Matrix

1. Aktion: Die konstanten Werte m und n werden eingegeben.


2. Aktion: m mal n Elemente hat die Matrix mat, diese werden elementweise ein-
gegeben.

INM11 41
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen

Sei m  2 und n  3, dann werden durch die Eingabe in folgender Reihenfolge den Ele-
menten der Matrix Werte zugewiesen:

Tab. 4.1: Protokoll zu Struktogramm Abb. 4.7

Äußere Zählschleife Innere Zählschleife Eingabe


zeile  1 spalte  1 Eingabe mat[1, 1]
zeile  1 spalte  2 Eingabe mat[1, 2]
zeile  1 spalte  3 Eingabe mat[1, 3]
zeile  2 spalte  1 Eingabe mat[2, 1]
zeile  2 spalte  2 Eingabe mat[2, 2]
zeile  2 spalte  3 Eingabe mat[2, 3]

Folgender Grundsatz der Abarbeitung von geschachtelten Zählschleifen gilt: 


Die innere Schleife wird in jedem Schleifendurchlauf der äußeren Schleife komplett
durchlaufen.

4.2.5 Modul
Um komplexe Probleme oder Aufgabenstellungen effektiv zu lösen, hat sich die Modu-
larisierung eines Algorithmus praktisch bewährt. Eine Aufgabenstellung wird nicht als
Ganzes gelöst, sondern in kleine überschaubare, z. T. bereits gelöste Teilprobleme zer-
legt. Die Lösungen der jeweiligen Teilaufgaben ergeben dann zusammenfassend die Ge-
samtlösung.
Beim Entwurf von Algorithmen wird deshalb dem sogenannten Top-down-Prinzip,
auch als Vorgehen durch schrittweise Verfeinerung bezeichnet, gefolgt.
Die Grundstruktur Modul unterstützt dieses Vorgehen. Der modulare Aufbau eines
Algorithmus ist die Grundlage klar strukturierter und damit gut lesbarer Programme.

Satz 4.1:
Ein Teilalgorithmus, ein Modul, ist eine in sich geschlossene Einheit. Es handelt sich
um eine Zusammenfassung einer Folge von Aktionen, die ein Teilproblem löst.

Das Modul erhält einen Namen und eine definierte Schnittstelle, die sogenannte Para-
meterliste. Informationen gelangen nur über diese Schnittstelle in das Modul hinein (in-
put) bzw. aus dem Modul heraus (output).

42 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Darstellung von Algorithmen 4

Das Modul kann unabhängig von ande- <modul_name> (<formale parameter>)


ren Teilen eines Gesamtalgorithmus
{<kommentar>, <hilfsvariable>, … }
entwickelt werden, es ist austauschbar
und änderbar. Aktion (Aktionsfolge)

Das Modul, mit seinem Namen, seinen


Parametern und seiner inneren Struk- Abb. 4.8: Moduldefinition
tur, wird durch die Moduldefinition
festgelegt.
Die in den runden Klammern anzugebenden formalen Parameter sind Platzhalter für
die aktuellen Parameter, die Schnittstelle des Moduls. Das Hinzufügen von Kommenta-
ren, Hinweisen und Hilfsvariablen kann im praktischen modularen Entwurf hilfreich
sein.
Der Aufruf des Moduls erfolgt durch
den Modulaufruf. Das Modul wird <modul_name> (<aktuelle parameterliste>)
mit seinem Namen und der Angabe
der aktuellen Parameter aufgerufen.
Abb. 4.9: Aufruf eines Moduls

Vorteile des Modulkonzepts:


• Das Modulkonzept bietet die Möglichkeit des modularen Aufbaus von Algorithmen.
Die Zerlegung von Algorithmen in mehrere, leichter lösbare Teilalgorithmen und
deren Zusammenfügen zu einem Gesamtalgorithmus wird unterstützt.
• Die klare Strukturierung führt zur Erhöhung der Übersichtlichkeit von Algorithmen.
• Die arbeitsteilige Entwurfsarbeit wird möglich, so kann die Entwicklungszeit für
Algorithmen und Programme verkürzt werden.
• Das Prinzip der Wiederverwendbarkeit durch das Bereitstellen von Modulen für
Standardprobleme wird unterstützt. Bibliotheken von Standardmodulen stehen zur
Verfügung.
• Um das Modul zu verwenden, muss man seine innere Struktur nicht kennen, das
Prinzip der Kapselung von Daten (engl.: information hiding) wird unterstützt.

Beispiel 4.10:
Algorithmus zur Berechnung der Zahl π durch die Leibniz-Reihe.

1 1 1 1 1  ( 1)i
Die Leibniz-Reihe hat die Form: π  4          4  
1 3 5 7 9  i 0 2i  1

Die Zahl π wird als Produkt von zwei Faktoren berechnet. Der erste Faktor ist die Kon-
stante 4, der zweite Faktor ist ein durch eine Reihe entwickelter numerischer Wert, eine
Summe mit unendlich vielen Summanden. Praktisch kann man die Berechnung nur mit
endlich vielen Summanden durchführen. Folgender Sachverhalt ist offensichtlich, je hö-
her die Anzahl der Summanden dieser Summe, desto genauer ist der berechnete Wert π.

INM11 43
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen

Beispielrechnungen:
π  3,131593, bei 101 Summanden, i  0 bis100
π  3,140593, bei 1001 Summanden, i  0 bis 1000
π  3,141559, bei 30001 Summanden, i  0 bis 30000.
Das wechselnde Vorzeichen der Summanden wird durch den Zähler des Bruchs (–1i ) in
der Summenformel beschrieben. Da Aktionen in Struktogrammen nur elementare Ope-
rationen enthalten dürfen, wird zur Berechnung des Zählers ein Modul zur Berechnung
von (–1)i mit i  ℕ benötigt.
Es ist zweckmäßig, ein universelles und wieder verwendbares Modul zu definieren, ein
Modul zur Berechnung von ax mit a  ℝ, x  ℕ, hoch(a, x, erg).

hoch(a, x, erg)
Input: a … Basis,
x … Exponent
Output: erg … Ergebnis der Berechnung ax

0 1 sonst
erg := 1

erg := 1 erg := a Für i := 1(1) x

erg := erg * a

Abb. 4.10: Moduldefinition zur Berechnung von erg  ax

 ( 1)i 
Die einzelnen Summanden   der Reihe werden jeweils in zwei Aktionen
 2i  1 
berechnet:
1. Die Berechnung des Zählers (z) durch den Aufruf des Moduls hoch(–1, i, z)
z
2. Die Berechnung des Summanden mit
2i  1
Für die Berechnung von π durch die Leibniz-Reihe ergibt sich nun als Folge von Aktio-
nen: Die Eingabe der Anzahl der Summanden, die Zuweisung des Startwertes 4.0 für die
Berechnung von π (Variable pi ), die Initialisierung der Summe (sum) durch die Zuwei-
sung (sum : 0 ) und die Berechnung der Summe (sum) mit anz Summanden sowie ab-
schließend die Multiplikation mit der Konstante 4.

44 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Darstellung von Algorithmen 4

Eingabe: anz

pi: = 4.0

sum := 0.0

Für i := 0(1)anz

hoch(–1, i, z)

sum := sum + z / (2 * i + 1)

pi:= pi * sum

Ausgabe: pi

Abb. 4.11: Struktogramm zur Berechnung von π durch die Leibniz-Reihe (Beispiel 4.10)

Zusammenfassung

In der Fachliteratur werden verschiedene Varianten der textlichen oder grafischen Dar-
stellung von Algorithmen genutzt. Das im Studienheft genutzte Struktogramm nach
Nassi-Shneiderman ist eine klar strukturierte und leicht lesbare grafische Darstellung ei-
nes Algorithmus bzw. eines Moduls. Die Grundstrukturen – elementare Aktion, Folge
(Sequenz), Auswahl (Selektion) und Wiederholung (Schleife) – wurden ausführlich dar-
gestellt.
Alle prozedurale Algorithmen können mithilfe dieser Grundstrukturen umgesetzt wer-
den.
Die Behauptung, dass der Programmierer aus einem Struktogramm relativ leicht ein 
C-Programm erstellen kann, werden Sie am Ende des Studienhefts in den Programmier-
übungen bestätigen können.
In den folgenden Aufgaben zur Selbstüberprüfung werden Sie sich mit Struktogrammen
von Algorithmen, die Lösungen von bekannten mathematischen Aufgaben sind, ausei-
nandersetzen.

Aufgaben zur Selbstüberprüfung

4.1 Zeichnen Sie das Struktogramm zu einem Algorithmus, der von einer beliebigen
durch Eingabe bereitgestellten natürlichen Zahl feststellt, ob es sich um eine gera-
de oder ungerade Zahl handelt. Eine entsprechende Ausgabe soll das Ergebnis des
Tests liefern.
4.2 Zeichnen Sie das Struktogramm zu einem Algorithmus, der von einer beliebigen
durch Eingabe bereitgestellten natürlichen Zahl die Quersumme berechnet.
4.3 Zeichnen Sie das Struktogramm zu einem Algorithmus, der von einer beliebigen
durch Eingabe bereitgestellten natürlichen Zahl alle von 1 verschiedenen Teiler
ermittelt. Wird ein Teiler gefunden, dann erfolgt eine Ausgabe.

INM11 45
© HfB, 02.12.20, Berk, Eric (904709)

4 Darstellung von Algorithmen

4.4 Geben Sie eine Moduldefinition dual_dezi(n, dual, dezi) an.


Das Modul berechnet zu einer Dualzahl die entsprechende Dezimalzahl. Es erhält
als Input n und eine Dualzahl mit n Dualziffern (dual ). Die Dualzahl ist in der
Datenstruktur Feld mit n Elementen gespeichert. Output ist eine positive ganze
Dezimalzahl (dezi ).
4.5 Betrachten Sie noch einmal das Beispiel 4.9. Geben Sie eine Moduldefinition
fakultaet_n(n, erg) für die Berechnung von n! an. Der Parameter n ist der Input
und der Parameter erg ist der Output, Zwischenergebnisse sollen nicht auf dem
Bildschirm ausgegeben werden.
4.6 Geben Sie eine Moduldefinition für ggT(x, y, teiler) an. Das Modul ermittelt den
größten gemeinsamen Teiler der Zahlen x und y (Input), der Wert der Variablen
teiler ist der Output.

46 INM11
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Beispiele prozeduraler Algorithmen


In diesem Kapitel stehen das Kennenlernen von weiteren Standardalgorithmen
und Übungen zur Umsetzung von mathematischen Algorithmen in Struktogram-
me im Vordergrund.
An ausgewählten Beispielen wird der Weg von der Lösungsidee bis zur Darstel-
lung des Algorithmus als Struktogramm anschaulich. Das Lesen und Nachvoll-
ziehen von Abläufen wird geübt.

Beispiel 5.1:
Lage eines Punktes P(x ; y) im kartesischen Koordinatensystem
Von einem Punkt P(x ; y) mit x und y  ℝ soll die Lage im kartesischen Koordinaten-
system bestimmt werden. Einer Variablen Lage soll entsprechend der Lage des Punk-
tes im Koordinatensystem ein Zahlenwert zugeordnet werden.
Sei P(–3,4; 7,8) dann liegt der Punkt P im 2. Quadranten.

Der Wert der Variablen Lage  {1, 2, 3, 4, 0, –1, –2} hat folgende Bedeutung:
Lage  1, der Punkt liegt im 1. Quadranten.
Lage  2, der Punkt liegt im 2. Quadranten.
Lage  3, der Punkt liegt im 3. Quadranten.
Lage  4, der Punkt liegt im 4. Quadranten.
Lage  0, der Punkt hat die Koordinaten (0, 0).
Lage  –1, der Punkt liegt auf der y-Achse.
Lage  –2, der Punkt liegt auf der x-Achse.
Eingabe: Die zu einem Punkt gehörenden Werte, die x- und die y-Koordinate, werden
eingegeben. Diese Werte können Kommazahlen sein, die Daten sind vom Datentyp
float.
Verarbeitung: Die sieben verschiedenen Möglichkeiten der Lage des Punktes sind:
Lage  1  1. Quadrant, x  0 und y  0
Lage  2  2. Quadrant, x  0 und y  0
Lage  3  3. Quadrant, x  0 und y  0
Lage  4  4. Quadrant, x  0 und y  0
Lage  0  Nullpunkt, x  0 und y  0
Lage  –1  auf y-Achse, y  0 und x  0
Lage  –2  auf x-Achse, x  0 und y  0

INM11 47
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Beispiele prozeduraler Algorithmen

Die Mehrfachauswahl kann nicht als Grundstruktur verwendet werden. Der Fallaus-
druck und die Fallkonstanten sind in diesem Beispiel keine ganzzahligen Werte. Die
Grundstruktur für die Tests kann deshalb nur die Alternative sein. Da aber zur endgül-
tigen Entscheidung über die Lage des Punktes maximal 4 Entscheidungen notwendig
sind, sind Schachtelungen notwendig.
Beispiel: Gegeben sei der Punkt P(–3,4; 7,8).
Dann ist die Testreihenfolge:
1. x  0?
2. y  0?, also kein Nullpunkt und nicht auf einer Achse
3. y  0?, also Lage 2 oder Lage 3
4. x  0?, also Lage  2

Eingabe: x , y

x = 0?
Ja Nein

y = 0? y = 0?
Ja Nein Ja Nein

y > 0?
Ja Nein

x > 0? x < 0?
Ja Nein Ja Nein

Lage := 0 Lage := –1 Lage := –2 Lage := 1 Lage := 2 Lage := 3 Lage := 4

Ausgabe: Lage

Abb. 5.1: Struktogramm „Lage des Punktes P(x; y) im Koordinatensystem“, Beispiel 5.1

Ausgabe: Die ermittelte Variable Lage ist vom Datentyp int, ein ganzzahliger Wert. Die
algorithmische Grundstruktur Mehrfachauswahl kann nun verwendet werden, um eine
konkrete Aussage zur Lage des Punktes zu liefern.

Lage

0 –1 –2 1 2 3 4
Ausgabe Ausgabe Ausgabe Ausgabe Ausgabe Ausgabe Ausgabe
„Null-Punkt“ „auf x-Achse“ „auf y-Achse“ „im „im „im „im
1. Quadranten“ 2. Quadranten“ 3. Quadranten“ 4. Quadranten“

Abb. 5.2: Struktogramm zur Spezifizierung der Lage von P(x; y), Beispiel 5.1

48 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Beispiele prozeduraler Algorithmen 5

Beispiel 5.2: Algorithmus zur Berechnung der Summe beliebig vieler reeller Zahlen
Eine beliebige Anzahl von reellen Zahlen {z1, z2, … zn  ℝ, zi Ɑ 0} wird nacheinan-
der eingegeben. Die Zahlen werden nicht gespeichert.
Da die Anzahl der Zahlen beim Start der Eingabe nicht bekannt ist, wird durch die
Eingabe einer negativen Zahl (wert  0) die wiederholte Eingabe von Zahlen been-
det.
Die berechnete Summe (sum) der Zahlen und die ermittelte (gezählte) Anzahl
(zaehl) der eingegebenen Zahlen werden als Ergebnisse ausgegeben.

Eingabe: Über die Tastatur erfolgt die Eingabe der Zahlen, der Summanden. Dabei wird
zur Vereinfachung angenommen, dass der Nutzer korrekte Eingaben tätigt, d. h. als
Zeichen kommen in der Eingabe nur Dezimalziffern und der Dezimalpunkt vor. Diese
Zeichen werden auch in der richtigen Reihenfolge eingegeben.

Verarbeitung: Die Variablen sum und


sum := 0.0
zaehl werden mit dem Wert 0.0 bzw. 0
initialisiert. zaehl := 0

Solange die eingegebene Zahl Eingabe: wert


(wert)  0.0 ist, ist sie ein weiterer Wiederhole so lange wert > 0
Summand. Die Berechnung der neuen
Summe erfolgt und der Zähler wird sum := sum + wert
erhöht. Die nächste Eingabe wird ge- zaehl := zaehl + 1
prüft, entweder der Schleifenkörper
Eingabe: wert
wird erneut durchlaufen oder die Be-
rechnung ist beendet. Ausgabe: sum, zaehl

Ausgabe: Die Werte von sum und Abb. 5.3: Struktogramm zum Algorithmus
zaehl werden ausgegeben. „Summe beliebig vieler reeller
Zahlen“, Beispiel 5.2

Beispiel 5.3: Suche nach dem Minimum von n ganzen Zahlen


In einer Datenstruktur Feld sind n ganze Zahlen gespeichert, feldi  Z mit i : 1(1)n.
Das Minimum (min) dieser Zahlen ist zu ermitteln.

Eingabe: Keine Eingabe, der Wert für n, die Anzahl der Elemente und die einzelnen
Zahlen sind bereits im Speicher, Variable feld, sie sind bereits vorhanden (feldi mit
i  {1 … n}).
Verarbeitung: Eine Variable min wird mit dem Speicherinhalt von Element feld[1] ini-
tialisiert. Nacheinander werden dann alle anderen, von 2 bis n, Feldelemente mit dem
aktuellen Minimum verglichen. Sollte ein Element des Feldes kleiner sein, dann wird es
das neue Minimum.
Ausgabe: Der Wert der Variablen min, also das Minimum, wird ausgegeben.

INM11 49
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Beispiele prozeduraler Algorithmen

min := feld[1]

fuer i := 2(1)n

feld[i] < min?


Ja Nein

min := feld[i]

Ausgabe: min

Abb. 5.4: Struktogramm zum Algorithmus „Suche nach dem Minimum von n-Zahlen“.
Beispiel 5.3

Der Algorithmus „Suche nach dem Minimum von n-Zahlen“ ist ein Standardalgorith-
mus, deshalb ist es sinnvoll, ihn als Modul (Mini) zu definieren.

Mini (anz, f, erg)


Input: anz … Anzahl der Elemente des Feldes f
f … Feld der ganzen Zahlen
Output: erg … Minimum

mini := f[1]

fuer i := 2(1) anz

f[i] < mini?


Ja Nein

mini := f[i]

erg := mini

Abb. 5.5: Moduldefinition Mini(anz, f, erg) – „Suche nach dem Minimum von anz Zahlen“

Dieses Modul kann man nun zur Lösung der


Aufgabe aus Beispiel 5.3 benutzen, das Mini (n, feld, min)
Struktogramm in Abb. 5.6 zeigt den Aufruf
des Moduls: Ausgabe: min

Abb. 5.6: Algorithmus „Suche nach dem


Minimum von n Zahlen“ mit
Modul Mini(…)

Beispiel 5.4: Sortieren von n ganzen Zahlen


Ein Feld von ganzen Zahlen mit n Elementen (feldi mit i  1 … n) befindet sich im
Speicher. Ein Modul Sort, das die Elemente des Feldes in eine bestimmte numerische
Ordnung bringt, ist gesucht. Die Ordnung der Elemente kann absteigend sein, d. h.,
das größte Element ist das erste, oder aufsteigend, d. h., das kleinste Element ist das
erste.

Das folgende Struktogramm ist ein Grobentwurf eines Algorithmus, mit dem das Sor-
tieren eines gegebenen Feldes erfolgt. Durch die Anzeige des Feldes vor und nach dem
Sortiervorgang kann das Ergebnis des Sortierens geprüft werden.

50 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Beispiele prozeduraler Algorithmen 5

Das Feld ist gegeben


Anzeige des unsortierten Feldes
Sortieren des Feldes
Anzeige des sortierten Feldes

Zwei Teilalgorithmen werden benötigt:


1. die Anzeige der Elemente eines Feldes,
2. das Sortieren der Elemente eines Feldes.
Zu 1.: Die Anzeige der Elemente eines Feldes von Zahlen erfolgt lückenlos. Die Anzahl
der Feldelemente ist bekannt, deshalb wird die Zählschleife (i  1 bis n) als Grundstruk-
tur des Algorithmus innerhalb des Moduls gewählt.

Anzeige( anz, feld)


Input: anz … Anzahl der Elemente
feld … Feld der Zahlen

fuer i: = 1(1) anz

Ausgabe: feld[i]

Abb. 5.7: Moduldefinition Anzeige(anz, feld)

Das Modul ist allgemein und wiederverwendbar. Der Datentyp der Elemente und die
numerische Ordnung sind für den Algorithmus nicht von Bedeutung.
Zu 2.: Idee des Sortierens durch „Nachbarschaftsvergleich“: Es werden jeweils zwei be-
nachbarte Feldelemente mit der Ordnungsrelation verglichen. Stehen sie nicht in der ge-
wünschten Ordnung, dann erfolgt der Tausch der beiden Elemente. Nach dem ersten
Durchlauf durch das gesamte Feld ist das gewünschte Element an der letzten Position.
Damit kann das Feld um ein Element verkürzt werden, und der Vergleich wird wie ge-
habt durchgeführt, so lange, bis das zu sortierende Feld nur aus einem Element besteht.
An einem Beispielfeld wird schrittweise der Sortiervorgang dargestellt. Dieses Feld
(feld ) mit sechs Elementen, ganzen Zahlen, wird absteigend sortiert. Im Ergebnis des
Sortierens wird die numerische Ordnung feldi  feldi + 1 mit i  {1 … n – 1} zwischen
beliebigen Feldelementen gelten.

INM11 51
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Beispiele prozeduraler Algorithmen

Tab. 5.1: Sortiervorgang – Protokoll

feld: 12 34 4 17 33 9
Durch wird die notwendige Tauschaktion zwischen zwei benachbarten
Elementen angezeigt.
Start: 12 34 4 17 33 9
1. Durchlauf 34 12 4 17 33 9
17 4 33 9
33 4 9
9 4
Ergebnis 1. Durchlauf 34 12 17 33 9 4
2. Durchlauf verkürztes Feld 34 12 17 33 9 4
17 12 33 9
33 12 9
Ergebnis 2. Durchlauf 34 17 33 12 9 4
3. Durchlauf verkürztes Feld 34 17 33 12 9 4
Ergebnis 3. Durchlauf 34 33 17 12 9 4
4. Durchlauf verkürztes Feld 34 33 17 12 9 4
Ergebnis 4. Durchlauf 34 33 17 12 9 4
5. Durchlauf verkürztes Feld 34 33 17 12 9 4
Ergebnis 5. Durchlauf 34 33 17 12 9 4
Ende, Feld nur ein Element 34 33 17 12 9 4
Ende: 34 33 17 12 9 4

Pro Durchlauf wird ein Element an die richtige Position nach rechts verschoben. Damit
ist nach maximal n – 1 Durchläufen das Feld in der gewünschten Ordnung.
Nach jedem Durchlauf wird die Anzahl der zu vergleichenden Elemente um ein Element
verringert, d. h., beim Durchlauf i  {1 … n – 1} werden maximal n – i Vergleiche aus-
geführt. Zwei geschachtelte Zählschleifen sind die Grundstruktur zur Umsetzung des
Sortierens durch „Nachbarschaftsvergleich“. Durch die Ordnungsrelation  sind im Er-
gebnis die Elemente in der absteigenden (fallenden) Ordnung.

52 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Beispiele prozeduraler Algorithmen 5

Sort (anz, f)
Input: anz … Anzahl der Elemente
f … Feld
Output: f … sortiertes Feld

fuer i: = anz(–1) 2

fuer k := 1(1) i – 1

f[k] < f[k+1]?


Ja Nein

h := f[k]

f[k] := f[k+1]

f[k+1] := h

Abb. 5.8: Moduldefinition Sort(anz, f) – fallendes Sortieren von anz Feldelementen

Im Ja-Zweig der Alternative (Abb. 5.8) realisiert eine Folge aus drei Zuweisungen das
Vertauschen von zwei Elementen des Feldes von ganzen Zahlen. Es ist sicher zweckmä-
ßig, dafür ein Modul zu definieren.

Diese Folge von drei Aktionen Das Modul swap (vertauschen) ist mit den for-
realisiert das Vertauschen von f [k] malen Parametern x und y definiert.
und f [k + 1]
swap (x, y)
h := f[k] input/output x, y

f[k] := f[k + 1] {vertauscht x und y, Hilfsvariable hilf}

f[k + 1] := h hilf := x

x := y

y := hilf

Die Aktion des Aufrufs des Moduls swap(…) veranlasst die Ausführung des Moduls mit
den aktuellen Parametern.
Nun zurück zum Sortieren: Im dargestellten Ablauf des Sortiervorgangs ist dem kriti-
schen Betrachter bereits ein Nachteil des Algorithmus aufgefallen. Bereits im 3. Durch-
lauf ist kein Tausch von Elementen erfolgt. Das heißt, dass sich die Folge bereits in der
gewünschten Ordnung befindet. Der Algorithmus „bemerkt“ dies nicht und läuft stur
weiter. Daraus ergibt sich der Ansatz zur Verbesserung des Algorithmus.
Innerhalb der inneren Schleife wird nach der Aktion swap(…) ein Merker (die Variable
getauscht) auf den Wert 1 gesetzt. Die äußere Schleife, die Zählschleife, wird durch eine
endgeprüfte Schleife ersetzt. Die Verkürzung des Feldes wird mit dem Index i realisiert.
Abhängig vom Wert der Variablen getauscht wird der Sortiervorgang wiederholt oder
beendet.

INM11 53
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Beispiele prozeduraler Algorithmen

Sort_verbessert (anz, f)
Input: anz … Anzahl der Elemente
f … Feld
Output: f … sortiertes Feld

i := anz

getauscht := 0

für k := 1(1) i – 1

f[k] < f[k+1]?


Ja Nein

swap (f[k], f[k+1])

getauscht := 1

i := i – 1

Wiederhole bis getauscht = 0

Abb. 5.9: Moduldefinition Sort_verbessert(anz, f) mit Modul swap(…)

Zusammenfassung

An Beispielen wurde jeweils ausgehend von einer Problemstellung der Weg vom Lö-
sungsansatz bis zur Darstellung des Algorithmus in einem Struktogramm gezeigt. Bei-
spielhaft wurde die Schrittfolge der Verarbeitung von Daten anhand der Veränderung
der Werte der Variablen protokolliert. Das Nachvollziehen von Programmabläufen ist
ein wichtiges Werkzeug zur Prüfung eines Programms.

Aufgabe zur Selbstüberprüfung

5.1 Nutzen Sie das gleiche Feld von ganzen Zahlen aus Beispiel 5.4 und protokollieren
Sie den Sortiervorgang wie er mit dem Algorithmus des Moduls Sort_verbessert()
abläuft.

54 INM11
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen


Mit den folgenden zwei Kapiteln werden ausgewählte Grundlagen für das Erstel-
len von Programmen in der Programmiersprache C bereitgestellt.
In diesem Kapitel wird kurz die Entwicklung der Programmierung von maschi-
nennaher Programmierung zu problemorientierter Programmierung an der Ent-
wicklung der Programmiersprachen nachvollzogen. Die Programmiersprache C
wird in diese Entwicklung eingeordnet und C wird durch Merkmale charakteri-
siert. Die zur Beschreibung der Syntax erforderlichen Begriffe, Alphabet und
Syntaxregeln werden formal beschrieben. Die formale Beschreibung der Syntax
einer Programmiersprache mit Syntaxdiagrammen wird ausführlich behandelt.
Die Synthese und die Analyse von Zeichenfolgen entsprechend einer Syntax wer-
den an Beispielen dargestellt. Damit sind die Grundlagen für die Beschreibung
der Syntax von C bereitgestellt. Abschließend werden die Phasen der Entwick-
lung eines Programms näher erläutert.

6.1 Höhere Programmiersprachen


Die Entwicklung der Programmiersprachen stellt sich heute als ein Weg von maschinen-
nahen, prozessorspezifischen und nicht portablen Programmiersprachen zu höheren,
universellen, prozessorunabhängigen und portablen Programmiersprachen dar.
Anfangs wurden Programme geschrieben, die spezielle Aufgaben auf bestimmten Rech-
nern lösten. Es entwickelten sich deshalb Programmiersprachen, die man mit dem At-
tribut maschinennah charakterisiert. Maschinennahe Programmierung, z. B. mit einer
Assemblersprache, ist prozessorspezifisch. Sie wird heute nur noch für die Programmie-
rung zeitkritischer Prozesse in Echtzeitsystemen oder für das Programmieren von spezi-
ellen Geräten genutzt.
Um den Programmierer sowie die Nutzer von rechnerspezifischen Details zu entlasten,
entstanden höhere problemorientierte Programmiersprachen. Diese formalisierten
Sprachen sind mehr auf zu lösende Problemstellungen bezogen und andererseits der
menschlichen Denk- und Arbeitsweise angepasst. So wird deutlich, dass die Schreibwei-
se von Anweisungen in höheren Programmiersprachen der aus der Mathematik bekann-
ten Schreibweise von Ausdrücken sehr ähnlich ist.
Durch zahlreiche und vielfältige Problemstellungen sowie unterschiedliche Anwen-
dungsgebiete entwickelte sich eine Vielzahl verschiedener höherer Programmierspra-
chen. In Abb. 6.1 werden einige für die Entwicklung wichtige Programmiersprachen ge-
nannt. Die Einteilung in prozedurale und deklarative Programmiersprachen
kennzeichnet zwei grundlegende Sprachtypen.
Der prozedurale Sprachtyp umfasst alle Programmiersprachen, die im engen Sinne ein
Programm als Folge (Sequenz) von einzelnen ausführbaren Befehlen auffassen. Ein-
gaben und Ausgaben erfolgen durch die Variablen (vgl. E-V-A-Prinzip). Durch einen
speziellen Befehl, den Aufruf, können Prozeduren (auch als Module, Routinen oder
Funktionen bezeichnet) in die Sequenz der Befehle eingeordnet werden. Die Prozeduren
erhalten Namen und typisierte Parameterlisten, es sind Unterprogramme eines Haupt-
programms.

INM11 55
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen

Die Programmiersprachen BASIC, PASCAL und C sind Beispiele prozeduraler Program-


miersprachen.
Die Programmiersprachen des deklarativen Sprachtyp sind durch die Idee der künstli-
chen Intelligenz geprägt. Ein zu lösendes Problem wird durch das Deklarieren von Fak-
ten und Regeln beschrieben, die Lösung erfolgt mithilfe von logischen Schlussfolgerun-
gen. Als Beispiel dafür sei die Programmiersprache PROLOG (PROgramming in LOGic)
genannt.

prozedurale deklarative

imperative objektorientierte funktionale/logische

FORTRAN LISP

BASIC COBOL

ALGOL PL1

PASCAL PROLOG
ADA SIMULA

C
PEARL SMALTALK
C++

JAVA PYTHON

Abb. 6.1: Verwandtschaft einiger wichtiger Programmiersprachen

Die Pfeile in Abb. 6.1 zeigen, welche Programmiersprache zum Ausgangspunkt für die
Entwicklung einer neuen Programmiersprache wurde. Es entstanden dadurch Ver-
wandtschaftsbeziehungen zwischen den Programmiersprachen. Die Weiterentwicklung
von C zu C++, der objektorientierten Programmiersprache, wird auch Gegenstand des
Studienhefts INM12 sein.
Die Programmiersprache C, die Sprache, in der Sie programmieren werden, ist eine Pro-
grammiersprache vom prozeduralen Sprachtyp. Sie gehört zu den imperativen (befehls-
orientierten) Sprachen. Insbesondere zu PASCAL besteht eine Verwandtschaft, z. B. das
Zeigerkonzept, der Datentyp Pointer, ist dafür ein Beleg.
C ist eine Allzwecksprache, sie ist geeignet für die Erstellung größerer Programmsyste-
me auf unterschiedlichen Rechnern und Rechnersystemen. Sie verfügt über eine hohe
Anzahl an vordefinierten, standardisierten Unterprogrammen.

6.2 Beschreibung formalisierter Programmiersprachen


Programmiersprachen sind künstliche Sprachen. Künstliche Sprachen sind Zeichensys-
teme, die nur die Verständigung innerhalb eines begrenzten Fachgebiets regulieren, sie
haben sich nicht natürlich entwickelt.

56 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprachen, künstliche Sprachen 6

Programmiersprachen wurden entwickelt, um Zeichenfolgen zu erstellen, die letztlich


bewirken, dass ein Computer bestimmte Anweisungen ausführen kann. Das heißt, im
Gegensatz zu natürlichen Sprachen, bei denen ein Wort mehrere Bedeutungen besitzen
kann, ist in einer Programmiersprache eindeutig festgelegt, welche Zeichenfolgen als
Programme zugelassen sind (Syntax) und was diese Zeichenfolgen bewirken (Semantik).
Die Syntax einer formalisierten Programmiersprache wird durch ein Alphabet, eine
Menge von Zeichen, und durch eine Menge von Regeln, die Syntaxregeln, beschrieben.

6.2.1 Alphabet und Syntaxregeln

Definition 6.1:
Eine formale Sprache L über einem Alphabet A ist eine Teilmenge der Menge der
Zeichenfolgen über A, L(A)  A*. Die Zeichenfolge l  L(A) ist ein Satz der forma-
len Sprache.

Was ist A*? Die Stern-Menge (A*) zu A wird induktiv definiert:


• Die leere Zeichenfolge ε ist Element der Menge A*.
• Wenn x  A und w  A*, dann wx  A* (ein Zeichen wurde angefügt).

Beispiel 6.1:
A1  {a, b, c, 0, 1, 2, 4, 5, 6, 7, 8, 9} ist ein spezielles Alphabet, eine Menge von
Grundsymbolen.
A1*  {aaa , bb , 12, 45, b 23, } ist die Stern-Menge zu A1.
Zeichenfolgen, die mit Zeichen des Alphabets durch fortlaufendes Anfügen gebildet
wurden, sind Elemente von A*.

In dieser Menge A1* ist z. B. die Zeichenfolge ba234 enthalten, also gilt ba234  A1*.
In dieser Menge ist aber nicht die Zeichenfolge Z4u5 enthalten, weil ‚Z‘ und
‚u‘  A1.

Die Stern-Menge A* ist die Menge aller aus den Zeichen des Alphabets A bildbaren Zei-
chenfolgen.
Durch Regeln wird eine Untermenge von Zeichenfolgen, die aus Zeichen aus dem Al-
phabet A bestehen, festgelegt. Die Menge der regelgerechten Zeichenfolgen ist dann eine
Teilmenge von A*, die Sprache L(A)  A*.

Beispiel 6.2:
Alphabet A1  {a, b, c, 0, 1, 2, 4, 5, 6, 7, 8, 9} und zwei Sprachregeln zur Sprache
L(A1).
Regel 1:
Die Zeichenfolgen beginnen mit einem Buchstaben.
und

INM11 57
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen

Regel 2:
Die Zeichenfolgen haben nur maximal fünf Zeichen.

Es gilt:
a67  L(A1) (Regel 1 und 2 korrekt angewandt),
78c  L(A1) (Regel 1 verletzt), aabcbcc  L(A1) (Regel 2 verletzt),
x6789  A1* und damit  L(A1) (das Zeichen x  A1*).

Definition 6.2:
Die Sprachregeln, mit deren Hilfe sich korrekte Sätze aufbauen und analysieren las-
sen, fasst man unter der Bezeichnung Syntax zusammen. Es sind Regeln der Recht-
schreibung, zum Setzen von Satzzeichen und zur Grammatik.

Betrachten wir gleich konkret die Programmiersprache C, hier speziell die Frage: 
Was ist ein C-Programm? Die Beschreibung der Syntax dieser Programmiersprache er-
folgt durch eine Menge von Grundsymbolen (Alphabet der Sprache) und durch die Re-
geln für die Bildung eines Satzes (C-Programm) unter Verwendung dieser Grundsymbo-
le. Die Menge der zulässigen, syntaktisch korrekten C-Programme ist so definiert.

Definition 6.3:
Die Semantik eines Satzes in einer Sprache ist sein Gehalt, seine Bedeutung.

Wenden wir uns auch hier gleich konkret der Programmiersprache C zu:
Die Semantik eines C-Programms ergibt sich aus der Betrachtung des Zusammenhangs
von Eingabe und Ausgabe: Was hat das Programm bewirkt?

Beachten Sie:
Ein C-Programm kann syntaktisch korrekt sein, aber es liefert dennoch ein fehler-
haftes, unsinniges oder nicht gewünschtes Ergebnis. Der Fehler kann bereits im vor-
gedachten Algorithmus oder in der Umsetzung in eine Folge von Anweisungen in
der gewählten Programmiersprache liegen.

6.2.2 Syntaxbeschreibung mit Syntaxdiagrammen


Die Definition der Syntax von Programmiersprachen erfolgt in der Fachliteratur mit
verschiedenen gleichwertigen formalen Mitteln:
• durch eine Grammatik oder
• eine Metasprache, z. B. die erweiterte Backus-Naur-Form, oder
• durch eine grafische Darstellung mit Syntax-Graphen, genannt Syntaxdiagramme.
In diesem Studienheft werden zur Beschreibung der Regeln der Syntax von Program-
miersprachen Syntaxdiagramme verwendet.

58 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprachen, künstliche Sprachen 6

Definition 6.4:
Die Syntax einer formalen Sprache L(G) wird mit einer endlichen Menge von Syn-
taxdiagrammen definiert.
Eines der Syntaxdiagramme ist ausgezeichnet, es ist das Startdiagramm und mit dem
Startsymbol beschriftet.

Abb. 6.2 zeigt das Syntaxdiagramm Program. Es ist das Startdiagramm der Definition
der Syntax von C. Der Name Program ist das Startsymbol. Dieses Syntaxdiagramm de-
finiert den Aufbau eines Satzes eines C-Programms.
Betrachten Sie parallel zu den folgenden Erläuterungen über Syntaxdiagramme stets die
Abb. 6.2 als Beispiel.

Program
(7)
GlobalDeclaration (8)

Import (43)

int main ( ) Block (15)

FormalParameter (30)

FunctionImplementation (28)

Abb. 6.2: Syntaxdiagramm Program (7)

In Syntaxdiagrammen kommen zwei grafische Grundelemente vor: das Rechteck und


das Oval. Ein Oval kann ein Kreis oder ein Rechteck mit abgerundeten Ecken sein.
Folgende Merkmale der Darstellung sind festgelegt:
• Jedes Syntaxdiagramm hat einen eindeutigen Namen. Es ist eine syntaktische Vari-
able, die Hilfssymbol genannt wird. Beispiel: Program
• Jedes Rechteck ist mit einem Namen eines Syntaxdiagramms beschriftet. 
Beispiel: Block (15), es wird auf ein Syntaxdiagramm mit dem Namen Block verwie-
sen. (Die in der Klammer stehende Nummer ist nur ein Hilfsmittel; unter dieser
Nummer findet man das entsprechende Syntaxdiagramm in der Menge aller Syntax-
diagramme.)
• Jedes Oval ist mit einem Element aus dem Alphabet bzw. aus der Menge der Grund-
symbole beschriftet. Es soll deshalb Grundsymbol genannt werden. 
Beispiel: main – diese Zeichenfolge ist ein Grundsymbol von C.
• An jedem Rechteck und an jedem Oval gibt es genau zwei Linien; eine Linie führt
zum Symbol hin und die andere führt vom Symbol weg.

INM11 59
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen

• Linien dürfen sich nicht kreuzen.


• Linien können nur auf legalem Weg durchlaufen werden.
Die Linien „führen“ beim Lesen des Syntaxdiagramms, es wird lesend „durchlaufen“.
Ein legaler Weg durch ein Syntaxdiagramm beginnt am Eingang (mit der Linie, die hin-
führt, dort steht der Name des Diagramms), und er folgt den Linien, wobei die Rechtecke
und Ovale durchquert werden, und er endet am Ausgang (mit der Linie, die wegführt).
An Verzweigungen darf ein beliebiger Weg gewählt werden.
Das Lesen bzw. Verstehen sowie die Verwendung von Syntaxdiagrammen sollen an zwei
Beispielen deutlich werden.
Beispiel 6.3 zeigt, wie man, die Syntaxdiagramme nutzend, einen syntaktisch korrekten
Satz bilden kann (Synthese eines Satzes).
Beispiel 6.4 zeigt, wie man einen Satz auf syntaktische Korrektheit mithilfe der Syntax-
diagramme prüfen kann (Analyse eines Satzes).

Beispiel 6.3:
Die in Abb. 6.3 dargestellten vier Syntaxdiagramme definieren eine spezielle Spra-
che, die Sprache der Bezeichner (Namen für Variablen oder Konstanten).

Bezeichner
Bu Zi
Bu a 1

Bu … 2

Zi0 Z 3

A 4
Zi0
0 … 5

Zi Z 6

Abb. 6.3: Syntaxdiagramme zur Sprache der Bezeichner

Die syntaktischen Variablen, Hilfssymbole, sind Bezeichner, Bu, Zi, Zi0; zu jeder gibt
es ein Syntaxdiagramm. Das Startdiagramm ist Bezeichner.
Die Grundsymbole, die syntaktischen Konstanten, sind die Elemente des Alphabets
der Menge K  {1, 2, …, 9, 0, a …, z, A, …, Z }. Es ist die Menge, in der alle Ziffern des
Dezimalsystems und die Zeichen a … z (Kleinbuchstaben des Alphabets) und die Zei-
chen A … Z (Großbuchstaben des Alphabets) enthalten sind.

60 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprachen, künstliche Sprachen 6

Ein Satz Z der Sprache Bezeichner kann nur aus Zeichen aus der Menge K, der Menge
der Grundsymbole, gebildet werden. Durch das Anfügen von einzelnen Zeichen an die
anfangs leere Ausgangszeichenfolge Z entsprechend den Regeln entsteht ein syntaktisch
korrekter Satz.
Das Bilden, die Synthese, eines Satzes Z eines Bezeichners, könnte wie folgt ablaufen:
• Start beim Startdiagramm Bezeichner. Am Eingang ist Z  ε, die leere Zeichenfolge.
• Man begibt sich auf den Weg durch das Syntaxdiagramm, die Linie führt zum Ein-
gang des Hilfssymbols Bu.
• Das Syntaxdiagramm Bezeichner wird verlassen, und das Syntaxdiagramm Bu wird
jetzt durchlaufen. Der Linie vom Eingang folgend sind alternative legale Wege durch
das Syntaxdiagramm möglich. Durchquert man das Oval mit der Beschriftung ‚A‘
wird die aktuelle Zeichenfolge Z verändert. Das Zeichen, mit dem das Oval beschrif-
tet ist, wird an Z angefügt, Z  εA  A.
Weiter auf dem Weg, der Linie folgend, erreicht man den Ausgang des Syntaxdia-
gramms Bu. (Das Hilfssymbol Bu wurde durch ein Element aus der Menge
K  {a, …, z, A, …, Z } ersetzt.).

• Danach kehrt man zum Ausgang des Hilfs- Bezeichner


symbols Bu im Syntaxdiagramm Bezeichner 2 1
zurück. Bu

An Punkt 2 (siehe Abb. 6.4) ist der Weg nach Bu


unten nicht legal, deshalb folgt man weiter
der Linie und gelangt zu Punkt 1. Hier sind Zi0
zwei Wege alternativ möglich:
Abb. 6.4: Syntaxdiagramm
Bezeichner

1. Geradeaus, zum Ausgang des Syntaxdiagramms, die Bildung/Synthese des Bezeich-


ners ist mit Z  A beendet. Dieser Satz, der nur aus dem Zeichen A besteht, ist sicher
ein syntaktisch korrekter Bezeichner.
2. Oder es wird an Punkt 1 dem Weg nach unten weiter gefolgt, die Synthese geht
weiter.
• Unter Nutzung des Syntaxdiagramms Bu kann ein weiterer Buchstabe oder alterna-
tiv kann unter Nutzung des Syntaxdiagramms Zi0 die Ziffer ‚0‘ oder weiter unter
Nutzung des Syntaxdiagramms Zi eine Ziffer aus der Menge {1, …, 9} angefügt wer-
den. Dann erreicht man wieder Punkt 2 und danach Punkt 1. Der Weg nach unten
in den zwei Varianten kann beliebig oft durchlaufen werden, der Zeichenfolge wird
dann jeweils ein Grundsymbol angefügt.
• Erreicht man den Ausgang des Syntaxdiagramms Bezeichner, des Startsymbols,
dann ist die Synthese des Satzes Z beendet.
Es ist offensichtlich, dass die folgenden Bezeichner Z1, …, Z6 syntaktisch korrekt sind:
Z1  zahl; Z2  wert 2; Z3  ABC ; Z4  i; Z5  Zaehler; Z6  Zaehler; d. h.: Z1, …, Z6 sind
Sätze der Sprache Bezeichner.

INM11 61
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen

Die beiden folgenden Zeichenfolgen Z7  5a, Z8  77z dagegen sind syntaktisch nicht
korrekt. Es sind keine Bezeichner; die Zeichenfolge beginnt in beiden Fällen mit einer
Ziffer.
Auch die folgende Zeichenfolge Z9  Zähler ist syntaktisch nicht korrekt. Sie enthält
den Buchstaben „ä“, er ist nicht Element des Alphabets K (ä  K), deshalb ist Z9 kein gül-
tiger Bezeichner.
Mit dem Beispiel 6.4 wird die Analyse eines Satzes einer Sprache, das Prüfen auf syn-
taktische Korrektheit einer Zeichenfolge, gezeigt.

Beispiel 6.4:
Eine Sprache Ausdruckssprache wird definiert durch:
• die syntaktischen Konstanten, die Menge der Grundsymbole 
G  {, , ∗, DIV, MOD, (,) 0, …, 9, A, …, Z, a, …, z }
• die Hilfssymbole, die syntaktischen Variablen:
EinfAusd, Term, Faktor, Bezeichner und Zahl (siehe Abb. 6.5)
• Das Hilfssymbol EinfAusd ist das Startsymbol, bzw. das Startdiagramm ist Einf-
Ausd.

EinfAusd Term
Term Faktor

+ + *

- - DIV

MOD
Faktor
Bezeichner

Zahl

( EinfAusd )

Abb. 6.5: Syntaxdiagramme zur Ausdruckssprache, Beispiel 6.4

Die Zeichenfolge W  12  (72 DIV 3) – a ist auf syntaktische Korrektheit zu prüfen.


Eine Antwort auf folgende Frage ist gesucht:
Ist die Zeichenfolge W ein Satz der Ausdruckssprache?
In einem ersten Schritt wird geprüft, ob alle Zeichen der Zeichenfolge Grundsymbole
sind, d. h. Elemente der Menge G.
Die Antwort ist ja, alle Zeichen der Zeichenfolge W sind  G. Da wir bereits die Syntax
von Bezeichner kennen und intuitiv auch 12, 72 und 3 jeweils als syntaktisch korrekte
Zahl erkennen, wird in einem zweiten Schritt der Ausdruck W vorverarbeitet. Jeder
syntaktisch korrekte Bezeichner in W wird durch das Symbol „B“ und jede syntaktisch
korrekte Zahl in W wird durch das Symbol „Z“ ersetzt

62 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprachen, künstliche Sprachen 6

Der Ausdruck Wⴕ  Z + (Z DIV Z) – B entspricht W. Wenn man zeigt, dass


W ⴕsyntaktisch korrekt ist, dann ist auch die syntaktische Korrektheit für W gezeigt.
Der Ausdruck W ⴕ besteht aus 9 Grundsymbolen (‚B‘ und ‚Z’ betrachten wir als Grund-
symbole).

Was ist die Idee der folgenden Analyse?


Für alle Zeichen der Zeichenfolge W ⴕ wird ein legaler lückenloser Weg durch die
Menge der Syntaxdiagramme gesucht.
Zwei Hilfsmittel werden benötigt:
Über einen Index i, i  {1, …, n}, wird der Zugriff auf die einzelnen Zeichen von W ⴕ
realisiert (n ist die Anzahl der Zeichen der Zeichenfolge).
Mit einer Menge von Marken M  {M1, M2, …, M99} wird der Weg markiert.
Die Zeichen der Zeichenfolge W ⴕ werden nacheinander betrachtet. Erreicht man auf
dem Weg durch die Syntaxdiagramme ein Oval, dessen Beschriftung mit dem aktuell
zu prüfenden Zeichen übereinstimmt, dann ist das Zeichen ‚akzeptiert‘, d. h., es kann
an dieser Stelle stehen.
Das Erreichen eines Rechtecks lenkt den Weg stets zu jenem Syntaxdiagramm, dessen
Name der Beschriftung des Rechtecks entspricht. Das Setzen der Marke am Ausgang
des Rechtecks dient als Markierung des Rückkehrpunktes. Beachten Sie, dass aus der
Menge der Marken stets die Marke mit dem kleinsten Index entnommen wird. Kehrt
man im weiteren Verlauf zu diesem Punkt zurück, wird die Marke entfernt und in die
Menge M zurückgelegt.
Durch die Vorverarbeitung werden die Zeichen ‚Z‘ und ‚B‘ beim Durchqueren des
Rechtecks mit der Beschriftung „Zahl“ bzw. „Bezeichner“ auch als akzeptiert betrach-
tet.
Die komplette Zeichenfolge ist akzeptiert, wenn der Ausgang des Startdiagramms er-
reicht wird und gleichzeitig alle Zeichen akzeptiert sowie alle Marken M aus den Syn-
taxdiagrammen entfernt sind.

• Start: Am Eingang des Syntaxdiagramms EinfAusd, dem Startdiagramm, hat der In-
dex den aktuellen Wert 1, i  1. Das erste Zeichen von W ⴕ wird geprüft, W1ⴕ  Z .
• Vom Eingang des Syntaxdiagramms der Linie folgend erreicht man eine Verzwei-
gung. Da W1ⴕ  Z , kein Grundsymbol ist, ergibt sich keine Übereinstimmung mit 
oder – (Vorzeichen). Es wird ein anderer Weg gewählt. Geradeaus gelangt man zum
Rechteck mit der Beschriftung Term, an dessen Ausgang die Marke M1 angebracht
wird. Das Syntaxdiagramm Term wird nun betrachtet. Auf legalem Weg erreicht
man das Rechteck Faktor, an dessen Ausgang wird die Marke M2 angebracht.
• Nun wird das Syntaxdiagramm Faktor betrachtet. Auf legalem Weg erreicht man
eine Verzweigung. Da W1ⴕ  Z , durchquert man das Rechteck mit der Beschriftung
Zahl; i wird erhöht, i  2. Das erste Zeichen ist akzeptiert.

INM11 63
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen

• Vom Ausgang des Syntaxdiagramms Faktor kehrt man zu M2 zurück. Die Marke M2
wird entfernt, und an der nachfolgenden Verzweigung entscheidet man über den
weiteren Weg. Da W2ⴕ  Z , ist der Weg nach unten keine Option, also kehrt man zu
M1 zurück, entfernt die Marke M1 und entscheidet an der nachfolgenden Verzwei-
gung über den weiteren Weg. Da W2ⴕ   , durchquert man das Oval mit der Be-
schriftung , erhöht i, i  3. Das zweite Zeichen ist akzeptiert.
• Auf legalem Weg gelangt man zum Rechteck mit der Beschriftung Term, an dessen
Ausgang wird die Marke M1 angebracht. Das Syntaxdiagramm Term wird nun be-
trachtet. Auf legalem Weg erreicht man das Rechteck Faktor, an dessen Ausgang
wird die Marke M2 angebracht. Das Syntaxdiagramm Faktor wird nun betrachtet.
Auf legalem Weg erreicht man eine Verzweigung. Da W 3ⴕ  ( , durchquert man das
Oval mit der Beschriftung (; i wird erhöht, i  4. Das dritte Zeichen ist akzeptiert.
• Auf legalem Weg gelangt man zum Rechteck mit der Beschriftung EinfAusd, an des-
sen Ausgang wird die Marke M3 angebracht. Vom Eingang des Syntaxdiagramms
EinfAusd der Linie folgend erreicht man eine Verzweigung. Der Vergleich von
W 4ⴕ  Z mit den Grundsymbolen + und – (Vorzeichen) zeigt, dass der Weg nach un-
ten keine Option ist. Geradeaus gelangt man zum Rechteck mit der Beschriftung
Term, an dessen Ausgang wird die Marke M4 angebracht. Das Syntaxdiagramm
Term wird nun betrachtet. Auf legalem Weg erreicht man das Rechteck Faktor, an
dessen Ausgang wird die Marke M5 angebracht. Das Syntaxdiagramm Faktor wird
nun betrachtet. Auf legalem Weg erreicht man eine Verzweigung. Da W 4ⴕ  Z ,
durchquert man das Rechteck Zahl; i wird erhöht, i  5. Das vierte Zeichen ist ak-
zeptiert.
• Vom Ausgang des Syntaxdiagramms Faktor kehrt man zu M5 zurück, die Marke M5
wird entfernt, und man entscheidet an der nachfolgenden Verzweigung über den
weiteren Weg. Da W5ⴕ  DIV , durchquert man das Oval mit der Beschriftung DIV;
i wird erhöht, i  6. Das fünfte Zeichen ist akzeptiert.
• Auf legalem Weg erreicht man das Rechteck Faktor, an dessen Ausgang wird die
Marke M5 angebracht. Das Syntaxdiagramm Faktor wird nun betrachtet. Auf lega-
lem Weg erreicht man eine Verzweigung. Da W6ⴕ  Z , durchquert man das Rechteck
mit der Beschriftung Zahl; i wird erhöht, i  7. Das sechste Zeichen ist akzeptiert.
• Vom Ausgang des Syntaxdiagramms Faktor kehrt man zu M5 zurück, die Marke M5
wird entfernt und an der nachfolgenden Verzweigung über den weiteren Weg ent-
schieden. Da W7ⴕ   , ist der Weg nach unten keine Option, also kehrt man zu M4
zurück, entfernt die Marke M4 und entscheidet an der nachfolgenden Verzweigung
über den weiteren Weg. Da W7ⴕ   , wird das Oval mit der Beschriftung – durch-
quert; i wird erhöht, i  8. Das siebte Zeichen ist akzeptiert.
• Auf legalem Weg gelangt man zum Rechteck mit der Beschriftung Term, an dessen
Ausgang wird die Marke M4 angebracht. Das Syntaxdiagramm Term wird nun be-
trachtet. Auf legalem Weg erreicht man das Rechteck mit der Beschriftung Faktor,
an dessen Ausgang wird die Marke M5 angebracht‘. Das Syntaxdiagramm Faktor
wird nun betrachtet. Auf legalem Weg erreicht man eine Verzweigung. Da W 8ⴕ  B ,
durchquert man das Rechteck mit der Beschriftung Name; i wird erhöht, i  9. Das
achte Zeichen ist akzeptiert.
• Vom Ausgang des Syntaxdiagramms Faktor kehrt man zu M5 zurück, die Marke M5
wird entfernt, und man entscheidet an der nachfolgenden Verzweigung über den
weiteren Weg. DaW9'  ), ist der Weg nach unten keine Option. Vom Ausgang des

64 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprachen, künstliche Sprachen 6

Syntaxdiagramms Term kehrt man zu M4 zurück, die Marke M4 wird entfernt, und
man entscheidet an der nachfolgenden Verzweigung über den weiteren Weg. Da
W9ⴕ  ), ist der Weg nach unten keine Option. Vom Ausgang des Syntaxdiagramms
EinfAusd kehrt man zu M3 zurück, die Marke M3 wird entfernt. Da W9ⴕ  ), durch-
quert man das Oval mit der Beschriftung ); i wird erhöht, i  10. Das neunte Zeichen
ist akzeptiert.
• Vom Ausgang des Syntaxdiagramms Faktor kehrt man zu M2 zurück, die Marke M2
wird entfernt. Da kein weiteres Zeichen mehr zu prüfen ist, wird das Syntaxdia-
gramm Term verlassen. Vom Ausgang des Syntaxdiagramms Term kehrt man zu M1
zurück, die Marke M1 wird entfernt. Da kein weiteres Zeichen mehr zu prüfen ist,
wird das Syntaxdiagramm EinfAusd verlassen.
• Das Ende der Analyse ist erreicht, alle Zeichen wurden akzeptiert, auf legalem Weg
konnte der Durchlauf erfolgen. Alle Marken sind entfernt.
Dieses Beispiel illustriert den Ablauf der Syntaxkontrolle von Programmen, einer Zei-
chenfolge mit einer Vielzahl von Zeichen. Es wurde sicher auch deutlich, dass diese Auf-
gabe durch Algorithmen beschrieben werden kann, sie ist programmierbar.

6.3 Vom Quelltext zum ausführbaren Programm


In einer höheren Programmiersprache werden Programme in abstrakter und für den
Menschen leicht verständlicher Ausdrucksweise als Text geschrieben. Die Quelltexte,
die in einer formalen Sprache notierten Zeichenfolgen, werden jeweils als Textdatei ge-
speichert. Diese Texte können automatisiert in eine Maschinensprache übersetzt wer-
den. Das Ergebnis der Übersetzung ist eine Datei, eine von einem Prozessor ausführbare
Befehlsfolge, ein lauffähiges Programm.
Die einzelnen Phasen der Entstehung eines solchen Programms aus dem Quelltext wer-
den im Folgenden etwas näher betrachtet. Der Programmierer nutzt heute eine Soft-
wareentwicklungsumgebung, ein komplexes Softwaresystem, zum Erstellen eines Pro-
gramms.

Eine integrierte Softwareentwicklungsumgebung (Abkürzung IDE, englisch inte-


grated development environment) ist eine Sammlung von Programmen, mit denen
möglichst alle Teilaufgaben der Softwareentwicklung bearbeitet werden können.

Integrierte Softwareentwicklungsumgebungen verfügen in der Regel über folgende


Komponenten:
• einen Texteditor,
• ein Übersetzerprogramm, Kompiler (engl. compiler) oder Interpreter,
• den Linker und
• den Debugger.

INM11 65
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen

In folgenden Schritten, unter Nutzung der Komponenten der IDE, entsteht ein Pro-
gramm:

1. Phase – Editieren:

Das Editieren, das Erstellen und wenn notwendig das Korrigieren des Quelltextes, er-
folgt mit dem syntaxbasierten Editor. Syntaxbasierte Editoren sind Texteditoren, die den
Programmierer beim regelgerechten Aufschreiben von Anweisungsfolgen in einer Pro-
grammiersprache unterstützen.
Durch die farbliche Gestaltung werden Grundsymbole der Programmiersprache hervor-
gehoben, das Einfügen von Klammern, entsprechend den Syntaxregeln, erfolgt automa-
tisch; durch das Einrücken von Texten werden strukturelle Aspekte unterstützt.

2. Phase – Übersetzen:

Da nur syntaktisch korrekte Quelltexte in ein Programm in einer Maschinensprache


(Maschinencode) übersetzt werden können, erfolgen in dieser Phase sowohl die Syntax-
kontrolle als auch das Übersetzen des Quelltextes. Obwohl das Programm nur Überset-
zer genannt wird, erfüllt es beide Aufgaben.
Man unterscheidet, abhängig von der Übersetzungsstrategie, zwei Arten von Überset-
zerprogrammen: die Interpreter und die Kompiler.
Ein Interpreter prüft und übersetzt die einzelnen Anweisungen eines Quellprogramms
nacheinander und führt sie unmittelbar danach aus. Programmübersetzung und Pro-
grammausführung sind eng verbunden, sie sind nicht getrennt.
Enthält ein Quelltext Syntaxfehler, so ist dieser sofort lokalisiert. Er kann korrigiert und
das Übersetzen und Abarbeiten kann fortgesetzt werden.
Ein Kompiler übersetzt das komplette Quellprogramm in einen Zwischencode, den Ob-
jektcode. Es entsteht eine Objektdatei. Enthält ein Quelltext Syntaxfehler, so kann die
Übersetzung nicht abgeschlossen werden. Die Fehler werden protokolliert und entspre-
chende Hinweise dem Programmierer zur Verfügung gestellt. Der Programmierer edi-
tiert den Quelltext und startet den Übersetzungsvorgang erneut.
Da C-Programme oft Bibliotheksroutinen (include-Dateien) nutzen und aus mehreren
Modulen, die auch in getrennt kompilierten Dateien vorliegen können, bestehen, wer-
den verschiedene Objektmodule mit der vorliegende Objektdatei durch den Linker zu ei-
nem Maschinencode zusammengebunden (engl.: to link, d. h. verketten, verbinden). Das
lauffähige Programm (Maschinencode) ist das Ergebnis. Programmübersetzung und
Programmausführung sind getrennt.
Abb. 6.6 illustriert die Entstehung eines C-Programms:

66 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprachen, künstliche Sprachen 6

Editieren Kompilieren Laden, Linken Abarbeiten

Editieren, Übersetzen sowie Laden und Linken und Abarbeiten des


Programms (Laufzeit)

Kompilieren Laden
Kompiler Lader, Linker

Objekt-
datei

Fehlermeldungen Maschinencode

Quelltext
Abarbeiten
Editieren

Eingabedaten Ausgabedaten

(Fehlermeldung)
Editor

Laufzeitsystem

Abb. 6.6: Aufgaben der Komponenten der IDE beim Erstellen eines C-Programms

Für das Erstellen von C-Programmen wird Ihnen die IDE DEV-C++ empfohlen. Eine
Anleitung zur Installation auf Ihrem PC sowie zu deren Nutzung finden Sie in Study-
Online.

Übung 6.1:
Schauen Sie sich jetzt noch einmal die Bedienoberfläche der IDE DEV C++ an.
Der Quelltext „Sparjahre.c“ (Beispiel in der Einleitung) wird in den Texteditor gela-
den, gegebenenfalls editiert und anschließend übersetzt. Die Abb. 1.3 und 1.4 in Ka-
pitel 1 zeigen entsprechende Bilder. Das Ausführen des Programms wird durch den
Menüpunkt „Ausführen“ im Menü „Ausführen“ oder die Taste (F10) ausgelöst.

INM11 67
© HfB, 02.12.20, Berk, Eric (904709)

6 Programmiersprachen, künstliche Sprachen

Zusammenfassung

Die Entwicklung der Programmiersprachen von maschinenorientierten hin zu proble-


mangemessenen Programmiersprachen wurde an einer Zeittafel anschaulich.
Die Programmiersprache C ist in die Gruppe der imperativen Programmiersprachen ein-
zuordnen. Auf Basis dieser Sprache haben Sie Syntaxdiagramme als Mittel der Beschrei-
bung der Syntax von Programmiersprachen kennengelernt. Um dieses Hilfsmittel des
Programmierers aktiv nutzen zu können, finden Sie im Anhang D eine Zusammenstel-
lung von Syntaxdiagrammen zur Programmiersprache C.

Aufgaben zur Selbstüberprüfung

6.1 Gegeben ist die Menge der Grundsymbole A  {0, 1, 2, …, 9} und die drei Syntax-
diagramme Zahl, Zi0 und Zi. Zahl ist das Startdiagramm.
Die Sprache vorzeichenlose ganze Zahl ist damit definiert.
a) Finden Sie drei verschiedene Beispiele für syntaktisch korrekte vorzeichenlose
Zahlen.
b) Prüfen Sie, ob die Ziffernfolge 01234 ein Satz dieser Sprache ist. Begründen Sie
Ihre Entscheidung.

Zahl Zi
0 1

Zi 2

Zi0 3

4
Zi0
0
5

Zi
6

Abb. 6.7: Zu Aufgabe 6.1: Syntaxdiagramme zur Sprache vorzeichenlose ganze Zahl

68 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprachen, künstliche Sprachen 6

6.2 Sprache „ROM-Zahlen“


Die Menge der Grundsymbole R ist gegeben: R  {C, D, I, L, M, V, X}. Die Menge
der Syntaxdiagramme ist durch die folgenden zwei Abbildungen angegeben. Das
Startdiagramm der Sprache „ROM_Zahlen“ ist ROM.
a) Geben Sie drei verschiedene Sätze von „ROM_Zahlen“ an.
b) Gehört der Satz MXXIIV zur Sprache? Begründen Sie Ihre Entscheidung.

ROM

Fm Fc Fx Fi

FH FZ FE

Fm Fc Fx Fi
M C X I

MM CC XX II

MMM CCC XXX III

6.8 a: Teil 1

FH FZ
C D X L

M C

D C

Fc Fx

FE
I V

Fi

6.8 b: Teil 2

Abb. 6.8: Zu Aufgabe 6.2: Syntaxdiagramme

6.3 Semantik der Sätze von „ROM-Zahlen“


Informieren Sie sich unter http://www.roemische-zahlen.net/

INM11 69
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C
In diesem Kapitel werden der grundlegende Aufbau eines C-Programms, das Al-
phabet der Sprache, die Menge der Grundsymbole sowie die Syntaxbeschreibung
von C durch die Menge der Syntaxdiagramme dargestellt. Kleine Beispielpro-
gramme entstehen durch das Umsetzen vorliegender Struktogramme in C-Pro-
gramme.
Der Aufruf und die Definition von Funktionen als wiederverwendbare Module
werden erläutert und mit Beispielen belegt.

Die Programmiersprache C ist eine Allzwecksprache. Sie ist nicht auf die vorrangige
Nutzung in einem bestimmten Anwendungsgebiet zugeschnitten. Der Sprachkern von
C umfasst relativ wenige Sprachelemente. Die zahlreichen vorgefertigten Bibliotheken
von Modulen erleichtern das Programmieren und damit ihre Verwendung für die Lö-
sung von Aufgabenstellungen mit Programmen in vielen Bereichen. C ist keine spezielle
Lehrsprache, und die vielfältigen Möglichkeiten der Umsetzung ein und derselben Auf-
gabe erschwert z. T. auch das Lehren dieser Sprache. Es kann deshalb in diesem Kapitel
nicht der komplette Sprachumfang beschrieben werden.

7.1 Aufbau eines C-Programms


Die Programmiersprache C ist eine prozedurale und imperative Programmiersprache. In
der Programmiersprache C werden Module „Funktionen“ genannt.

Ein C-Programm besteht aus mindestens einer Funktion, die den Namen ‚main‘
(Hauptprogramm) tragen muss.

// Program Einf.c
//Program (7)
//Import (43)
#include <stdio.h>
//Anfang Block (15)
int main(){
//Declaration (9), VariableDeclaration (12)
int i;
//StatementSequence(16), Statement(17)
//Assignment(18), Assignment1(19)
i = 15;
//FunctionCall (33)
printf("Die Zahl ist %d", i);
//ReturnStatement (32)
return 0;
//Ende Block (15)
}

Code 7.1: Einf. c

70 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

Abb. 7.1: C-Programm Einf.c in IDE DEV-C++

Das C-Programm Einf.c (Code 7.1) ist ein einfacher syntaktisch korrekter Quelltext.
Nutzen Sie parallel zu den folgenden Erläuterungen die „Syntaxdiagramme der Pro-
grammiersprache C“ (siehe Anhang D) zur Überprüfung.
Beginnend mit dem Syntaxdiagramm (7), dem Startdiagramm, wird die Syntax dieses
Quelltextes geprüft.
Ein Import, Syntaxdiagramm (43), ist notwendig. Die Bibliothek stdio.h wird für die
Routine zur Ausgabe, printf(…) (siehe Anweisungsfolge von main), benötigt.
Da keine globale Deklaration enthalten ist, folgt auf legalem Weg im Quelltext die Folge
der Grundsymbole int main (). Die Funktion main ist parameterlos.
In Syntaxdiagramm (15) wird der Aufbau eines Blocks definiert.
Zwischen den Grundsymbolen { … } (Block) steht mindestens eine Deklaration, Syntax-
diagramm (9). Mit VariableDeclaration, Syntaxdiagramm (12), ergibt sich der Datentyp
der Variablen als int (TypeIdent), der Bezeichner (Ident) i wird gewählt, damit ergibt
sich die Deklaration int i;. Danach kann in der Folge von Anweisungen (Statement-
Sequence), Syntaxdiagramm (16), mit dieser Variablen gearbeitet werden, z. B. i  15 ist
eine Zuweisung (Assignment), siehe Syntaxdiagramme (18) und (19). Die Zahl 15 ist ein
Ausdruck (Expression). Die Anweisung wird abgeschlossen durch ein Semikolon. Die
nächste Anweisung ist eine Ausgabe auf dem Bildschirm, die mit der Funktion
printf(…) realisiert wird. Dazu benutzt man die Syntax für einen Funktionsaufruf
(FunctionCall) nach Syntaxdiagramm (33). Der Name der Funktion wird notiert, und in
dessen Parameterliste stehen die aktuellen Parameter. Der erste aktuelle Parameter ist
ein Text, in dem ein Platzhalter (%d) für eine Variable vom Typ integer enthalten ist, der
zweite aktuelle Parameter, durch Komma vom ersten getrennt, ist der Variablenname i
(diese Variable wird ausgegeben). Da noch eine Anweisung folgt, schließt das Semikolon
die Anweisung ab.
Am Ende des Blocks der Funktion main wird nach erfolgreichem Ablauf der Folge der
Anweisungen der Wert 0 als Ergebnis geliefert, eine Return-Anweisung (ReturnState-
ment) wird eingefügt. Das Grundsymbol } schließt den Anweisungsblock ab.

INM11 71
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Das C-Programm Einf.c besteht nur aus der Funktion main(). Es hat folgenden grund-
legenden Aufbau:

Import-Anweisungen

Globale Deklaration

int main ( ) {
}

Abb. 7.2: Hauptteile eines C-Programms

Dieser Aufbau entspricht dem Syntaxdiagramm (7) „Program“, dem Startdiagramm der
Menge der Syntaxdiagramme zu C (Anhang D), Abb. 7.3.

Program
(7)
GlobalDeclaration (8)

Import (43)

int main ( ) Block (15)

FormalParameter (30)

FunctionImplementation (28)

Abb. 7.3: Syntaxdiagramm Program (7)

Weitere Funktionsdefinitionen können im Quelltext eines C-Programms im Bereich


der globalen Deklarationen als komplette Funktionsimplementation aufgeführt werden
– siehe Syntaxdiagramme (7), (8), (27) und (28) –
oder
die Funktionen werden jeweils durch ihren Prototypen durch Bereitstellen des Funkti-
onskopfes im Bereich der globalen Deklarationen deklariert – siehe Syntaxdiagramme
(7), (8), (27) –
und
erst nach der Funktion main() wird die Implementation der Funktion in den Quelltext
aufgenommen – siehe Syntaxdiagramme (7), (27), (28).
Dieser zweite Weg ist der zweckmäßigere. Die C-Programme mit zahlreichen Funktio-
nen sind so lesbarer.

72 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

7.2 Syntaxregeln von C


Die Syntax der Programmiersprache C wird beschrieben durch das Alphabet, die Men-
ge der Grundsymbole und die Menge der Syntaxregeln.
Im Folgenden sind wichtige Aussagen für die Programmierung in C zusammengestellt.
An Beispielen werden diese Beschreibungen anschaulich und für den Programmierer
praktikabel.

7.2.1 Grundsymbole
Die Grundsymbole der Programmiersprache C werden in folgende Gruppen eingeteilt:
• Schlüsselwörter
• Bezeichner
• Zahlen (Konstanten)
• Ausdrücke
• Kommentare
• Trennzeichen
• Operatoren
Betrachten wir diese Gruppen genauer:

Schlüsselwörter:

Schlüsselwörter sind reservierte Bezeichner mit vordefinierter Bedeutung. In Tab. 7.1


sind Schlüsselwörter der Programmiersprache C aufgeführt.

Tab. 7.1: Schlüsselwörter von C

Schlüsselwörter zur Bezeichnung Schlüsselwörter in Anweisungen


von Datentypen
char break
const case
double continue
enum default
float do
int else
long for
short goto
signed if
struct return
typedef switch
union while
unsigned
void

INM11 73
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Bezeichner, Zahlen (Konstanten) und Ausdrücke:

Die Syntax von Zahlen, Bezeichnern und Ausdrücken wurde in der Aufgabe zur Selbst-
überprüfung 6.1 und in Abschnitt 6.2.2 in den Beispielen 6.3 und 6.4 erarbeitet. Die dort
erarbeiteten Regeln entsprechen denen der Programmiersprache C.
Elemente aus der folgenden Menge (Z ) von Zeichen können zur Bildung von Bezeich-
nern verwendet werden: Z  {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y,
z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, _, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9}

Zur Beachtung:
In C ist die Groß- und die Kleinschreibung in einer Zeichenfolge, in Schlüssel-
wörtern und Bezeichnern zu beachten:

Beispiel: Else  else, der Bezeichner test ist ungleich dem Bezeichner teSt.

Kommentare:

Kommentare sind für die Anweisungsfolge des C-Programms nicht von Bedeutung, sie
dienen dem besseren Verständnis, der Dokumentation des in ein C-Programm umge-
setzten Algorithmus.
Das Zeichen // am Beginn einer Zeile macht diese zu einer Zeile, in der Kommentare ste-
hen. Das Ende der Zeile ist auch das Ende des Kommentars.
Soll der Kommentar beliebig viele Zeilen umfassen, so wird er mit /* */ eingeschlos-
sen.

Trennzeichen:

Das Leerzeichen, das Zeilenendezeichen, die Tabulatoren, Seitenvorschub und Kom-


mentare sind Trennzeichen. Sie trennen die Grundelemente der Sprache.
Mehrere dieser Trennzeichen hintereinander werden als ein Trennzeichen angesehen.
Viel benutzte Trennzeichen sind z. B.: \t ... – ein Tabulator, \n ... – ein Zeilenvorschub.

Operatoren:

In Tab. 7.2 sind die Operatoren der Programmiersprache C zusammengestellt. Das Ope-
rationszeichen, der Zweck, der Rang und die Assoziativität des Operators sind jeweils
angegeben.

Tab. 7.2: Tabelle der Operatoren

Operator Zweck Rang Assoziativität


. , –, [], () einstellige Operatoren 15 links  rechts
, ––, !, ~, –,, ∗, &, sizeof einstellige Operatoren 14 rechts  links
∗, /(DIV), % (MOD) Punktrechnung 13 links  rechts
, – Strichrechnung 12 links  rechts
 (links)  (rechts) Bit schieben 11 links  rechts

74 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

Operator Zweck Rang Assoziativität


, , ,  Vergleiche 10 links  rechts
, ! Vergleiche 9 links  rechts
& bitweise 8 links  rechts
^ bitweise 7 links  rechts
| bitweise 6 links  rechts
&& logisch 5 links  rechts
|| logisch 4 links  rechts
?: Entscheidung 3 rechts  links
, , –, ∗, /, %, , Zuweisung 2 rechts  links
, &=, |, ^
, Sequenz 1 links  rechts

Anmerkung zur Tabelle:


Je höher der Rang, desto stärker ist die Bindung! In der Menge der Operatoren gibt
es keine Totalordnung, sondern eine Halbordnung, die Rangfolge zwischen allen
Operatoren ist nicht strikt. Mehrere Operatoren sind gleichrangig.
Zum Beispiel ist der Rang von Multiplikation und Division gleich, aber höher als der
Rang von Addition und Subtraktion („Punktrechnung vor Strichrechnung“).
Man kann durch die Rangfolge auf eine explizite Klammerung verzichten. So ist
a  b ∗ c gleichbedeutend mit a  (b ∗ c), da der Multiplikationsoperator einen höhe-
ren Rang hat. Eine Klammerung bietet in der Regel die Möglichkeit, die vorgegebene
Rangfolge punktuell außer Kraft zu setzen. Wenn man (a  b) ∗ c schreibt, wird die
Summe berechnet. Sie ist ein Faktor der Multiplikation.
Eine Reihenfolge ist damit noch nicht gegeben, weil der Ausdruck sowohl von links
nach rechts als auch von rechts nach links (oder beliebig) ausgewertet werden kann.
Inkrement- und Dekrementoperatoren können in 
Postfix-Schreibweise (Operator nachgestellt: x++, bzw. x--) oder 
Präfix-Schreibweise (Operator vorangestellt: ++x, --x) genutzt werden.
Die Zuweisung var=pos++; erfolgt in der Reihenfolge var=pos; (der alte Wert pos
wird an var weitergegeben) und danach wird pos=pos+1; ausgeführt. 
Die Zuweisung var=++pre; wird zuerst pre=pre+1; ausgeführt und danach wird
var=pre; zugewiesen (der geänderte Wert wird weitergegeben).

Beispiel: Mit a=1 und b=2 hat Zuweisung c=++a + b++; also dadurch a=2, b=3,
c=4.

INM11 75
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

7.2.2 Syntaxdiagramme zu C
Die Syntaxregeln der Programmiersprache C sind durch die Menge der Syntaxdiagram-
me (1) bis (42) im Anhang D zu diesem Studienheft zusammengestellt (in StudyOnline
finden Sie diesen Anhang als separate Datei). Es ergibt wenig Sinn, sich diese Syntax-
diagramme nur anzuschauen. Nutzen Sie diese als Nachschlagwerk, als Hilfsmittel beim
Programmieren. Mit den folgenden Beispielen und Erläuterungen wird gezeigt, wie der
Programmierer dieses Hilfsmittel beim Erstellen eines C-Programms nutzt.

Beispiel 7.1:
Ein C-Programm zum Beispiel 5.1, „Lage eines Punktes P(x, y) im kartesischen Ko-
ordinatensystem“ ist zu entwickeln.
Das folgende Struktogramm, das sich aus den Abbildungen 5.1 und 5.2 ergibt, ist die
Grundlage des gesuchten C-Programms.

Eingabe: x , y

x = 0?
Ja Nein

y = 0? y = 0?
Ja Nein Ja Nein

y > 0?
Ja Nein

x > 0? x < 0?
Ja Nein Ja Nein

Lage := 0 Lage := –1 Lage := –2 Lage := 1 Lage := 2 Lage := 3 Lage := 4

Lage

0 –1 –2 1 2 3 4
Ausgabe Ausgabe Ausgabe Ausgabe Ausgabe Ausgabe Ausgabe
„Nullpunkt“ „auf x-Achse“ „auf y-Achse“ „im „im „im „im
1. Quadranten“ 2. Quadranten“ 3. Quadranten“ 4. Quadranten“

Abb. 7.4: Struktogramm „Lage eines Punktes P(x, y) im …“, aus Abb. 5.1 und 5.2
zusammengesetzt

Überlegungen zum Erstellen des Quelltextes aus einem Struktogramm:


1. Welche Variablen müssen in welchem Datentyp deklariert werden?

x und y float x; Die Variablen werden eingegeben, es sind Werte aus


float y; dem Wertebereich der gebrochenen Zahlen (float).
Lage int Lage; Es wird ein Wert aus {–1, –2, 0, 1 … 4} also eine ganze
Zahl (int) zugeordnet.

76 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

2. Welche algorithmischen Grundstrukturen sind zu nutzen?


Der Algorithmus ist eine Folge von drei Aktionen: zwei Eingaben, eine mehrfach ge-
schachtelte Alternative und eine Mehrfachauswahl. Die Mehrfachauswahl soll für
jeden Fall eine Ausgabe vorsehen, d. h., es gibt keinen „sonst“ (default)-Fall.
3. Welche Include-Dateien müssen eingebunden werden?
Mindestens stdio.h für die Eingabe- und Ausgabefunktionen sind vorzusehen.
4. Welche Syntaxdiagramme für Alternative (IfStatement ) und Mehrfachauswahl
(SwitchStatement ) stehen zur Verfügung?
(Nutzen Sie die Tabelle der Grundstrukturen von Algorithmen, Anhang C)

IfStatement
(20)
( BoolExpression (6) ) Statement (17) else Statement (17)

SwitchStatement
(21)
switch ( Expression (6) ) {

case CaseLabel (3) : StatementSequenz (16) BreakStatement (25)

default : StatementSequenz (16) }

Abb. 7.5: Syntaxdiagramme zur Alternative und Mehrfachauswahl

Nun wird programmiert! Die Funktion main() wird implementiert, siehe Syntaxdia-
gramm Program (7). Die Vorüberlegungen 1 bis 3 werden umgesetzt. So entsteht der fol-
gende Code Lage.c.

// Program Lage.c
# include <stdio.h>
int main(){
//Deklaration
float x,y;
int Lage;
//Eingabe von x(%f..float)
scanf("%f",&x);
//Eingabe von y(%f..float)
scanf("%f",&y);

//IfStatement und SwitchStatement noch ergänzen


return 0;
}

Code 7.2: Funktion main() von Lage.c

INM11 77
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Zur Orientierung des Benutzers des Programms werden Textausgaben, Aufforderungen


zur Eingabe, eingefügt. Die geschachtelte if-Anweisung und die switch-Anweisung wer-
den entsprechend der in Abb. 7.4 vorgegebenen Struktur umgesetzt und in der durch die
Syntaxdiagramme (Abb. 7.5) vorgegebenen Syntax aufgeschrieben. Prüfen Sie!

Abb. 7.6: Quelltext Lage.c

Im syntaxbasierten Editor ist der Quelltext Lage.c (Abb. 7.6) durch die farbliche Hervor-
hebung in seiner Struktur und der Anweisungsfolge gut nachvollziehbar.

Beispiel 7.2: Berechnung der Quersumme


Für die Berechnung der Quersumme einer ganzen Zahl liegt in Abb. 7.7. das Struk-
togramm vor. Ein entsprechendes C-Programm ist zu entwickeln.

78 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

Eingabe: zahl

zahl_h := zahl

summe := 0

Wiederhole so lange zahl_h > 0

summe := summe + (zahl_h MOD 10)

zahl_h := zahl_h DIV 10

Ausgabe: ‚Die Quersumme betraegt‘ summe

Abb. 7.7: Struktogramm „Berechnung der Quersumme einer Zahl“

Überlegungen:
1. Die Variablen zahl, zahl_h und summe müssen deklariert werden, dafür ist Datentyp
int vorzusehen.
2. Die Folge der Anweisungen enthält fünf Aktionen, eine Eingabe, zwei Zuweisun-
gen, eine Schleife und eine Ausgabe.
3. Die Schleife ist eine anfangsgeprüfte Schleife.
WhileStatement
(22)
while ( BoolExpression (6) ) Statement (17)

4. Der Operator MOD, ganzzahlige Operation zur Berechnung des Restes der Division
(siehe Tab. 7.2
Operatoren Punktrechnung) hat das Operatorzeichen %, der Operator DIV (ganzzah-
lige Division) wird mit dem Zeichen / umgesetzt.
5. Implementieren von main() zu Quersumme.c:

// Program Quersumme.c
# include <stdio.h>
int main(){
int zahl, zahl_h, summe;
// die Variablen
printf(" Quersumme einer Zahl \n\n");
// die Überschrift
printf(" Geben Sie eine ganze Zahl ein \t");
scanf("%d", &zahl);
//Eingabe von zahl (d auch int)
// die Zuweisungen und die Schleife ergänzen
return 0;
}

Code 7.3: Quersumme.c – Funktion main()

Anschließend werden die zwei Zuweisungen (Assignment(18)) und das WhileState-


ment(22) dem Struktogramm entsprechend in den Quelltext eingefügt.
Prüfen Sie.

INM11 79
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Abb. 7.8: Quelltext Quersumme.c

Beispiel 7.3: Prinzip der Wiederholung eines Programms ohne Neustart


Die bisher betrachteten Beispielprogramme sind insofern nicht benutzerfreundlich,
weil eine Wiederholung des Programms mit anderen Eingaben nur durch einen Neu-
start des Programms möglich ist. Das Beispiel aus der Einleitung zu diesem Studien-
heft soll nun verbessert werden.
Der Benutzer des Programms Sparjahre.c soll am Ende der erstmaligen Ausführung
entscheiden können, ob er eine wiederholte Ausführung des Programms wünscht
oder nicht.

Umsetzung als Struktogramm:

Das Programm soll mindestens einmal


Programm (z. B. Sparjahre.c)
ausgeführt werden, danach soll die Ent-
scheidung getroffen werden, ob wieder- Eingabe: Soll wiederholt werden?
holt wird oder nicht. (wdh = 'j' oder 'n')

Wiederhole bis wdh = 'n'


Die passende algorithmische Grund-
struktur ist die endgeprüfte Schleife. Abb. 7.9: Struktogramm „Prinzip der
Wiederholung eines Programms“

Zur endgeprüften Schleife nach Nassi-Shneiderman (Abb. 7.9) gibt es in der Program-
miersprache C keine analoge Umsetzung! (siehe Anhang B).
Die endgeprüfte Schleife wird mit dem DoWhileStatement realisiert.
Diese Anweisung wird so gelesen: „tue … so lange <bed> erfüllt“.
Also muss die Bedingung im C-Programm angepasst werden!
„tue … so lange wdh=‘j‘ ”

DoWhileStatement
(23)
do Statement (17) while ( BoolExpression (6) ) ;

Abb. 7.10: Syntaxdiagramm DoWhileStatement(23)

80 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

// Programm Sparjahre_nochmal.c
#include<stdio.h>
#include<conio.h>
int main(){
float zins,anf,end,guthaben;
int jahre;
char wdh ='n';
do {
system("cls");
printf("\n");
printf("\n Berechnung der Sparjahre \n");
printf(" Geben Sie das Anfangskapital ein: \t");
scanf("%f", &anf);
printf(" Geben Sie den aktuellen Zinssatz ein: \t");
scanf("%f", &zins);
end=2*anf;
printf("\n");
guthaben=anf;
jahre=0;
while(guthaben<end){
guthaben=guthaben+guthaben*zins;
jahre=jahre+1;
}
printf("Nach %d Jahren ist das Sparziel erreicht.\n", jahre);
printf("\n");
printf(" Moechten Sie die Rechnung wiederholen?(j)a/(n)ein");
wdh = getch();
} while (wdh == 'j');
return 0;
}

Code 7.4: Sparjahre_nochmal.c, erweitertes Programm

Im Code 7.4 sind die zusätzliche Include-Datei, die zusätzlich deklarierte Variable und
die zusätzlichen Anweisungen hervorgehoben (fett gedruckt).
Die Funktion getch() aus der Bibliothek conio.h nimmt von der Tastatur einen Buch-
staben ohne Bildschirmecho (keine Ausgabe) entgegen. Beachten Sie, nur das Zeichen ,j‘
führt zur Wiederholung der Anweisungsfolge. Alle anderen Buchstaben führen zum Be-
enden des Programms.

7.2.3 Kontextsensitive Nebenbedingungen


Neben der Syntax gelten noch weitere einschränkende Bedingungen, Regeln für die Bil-
dung korrekter Sätze in der Programmiersprache C. Man nennt diese kontextsensitive
Nebenbedingungen:
• Alle Bezeichner, die in einem Block auftreten, müssen in einer Deklaration deklariert
sein. Es sind die Gültigkeitsregeln der Bezeichner zu beachten.
• Jeder Bezeichner darf nur einmal deklariert werden.
• Die Groß- und Kleinschreibung von Bezeichnern und Schlüsselwörtern muss beach-
tet werden.
• Einer Variablen von einem bestimmten Datentyp kann nur ein Wert oder ein Aus-
druck von einem zu diesem kompatiblen Datentyp zugewiesen werden.

INM11 81
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

7.3 Modulkonzept von C – Funktionen in C


Die Programmiersprache C ist eine prozedurale Programmiersprache. Das Modulkon-
zept von C ist die Grundlage für die Entwicklung gut strukturierter modularer Program-
me. In der Programmiersprache C sind Module Funktionen.
Module/Funktionen sind Lösungen für Teilaufgaben. Module können bereits vordefi-
niert sein, sie stehen in Bibliotheken zur Verwendung bereit (Importe), oder sie werden
als Teile des Quellprogramms, als (Unter-)Funktionen deklariert, definiert und genutzt.
Grundlegend enthält ein C-Programm mindestens eine Funktion. Die Funktion main ist
das Hauptprogramm, es ist der Startpunkt der Programmausführung.
In der Folge von Anweisungen im Block des Hauptprogramms können dann spezielle
Anweisungen, Funktionsaufrufe (FunctionCalls), stehen.
Komplexere Aufgabenstellungen erfordern Programme, die aus einer Reihe von Modu-
len bestehen.
Das folgende Beispiel, ein Programm, das die Wertetabelle der mathematischen Funkti-
on f (x, k, l ) erzeugt, macht die Vorteile der Verwendung von Modulen anschaulich.

Beispiel 7.4: Ein Programm dient zur Berechnung von

k   k  k  1
y  f (x , k ,l )    * sin(x )    * cos(x )   l  * 3.5
 
l  l  1   
für x  0.4 bis x  1.5 mit einer Schrittweite von Δx  0.1 mit k  5 und l  3 .
Die berechneten Werte sind in einer Tabelle mit zwei Spalten auszugeben: x | y.

m  m!
Beachten Sie: Der Binomialkoeffizient wird berechnet durch   
  n !* (m  n )!
n

Betrachtet man die zur Lösung der Aufgabe notwendigen Rechenschritte und damit den
zu realisierenden Ablauf eines Programms zu Beispiel 7.4, so stellt man fest:
• In der Funktion main wird y  f (x, k, l ) für x  0.4 bis x  1.5 wiederholt (12-mal)
berechnet. Die algorithmische Grundstruktur ist eine Schleife.
• Pro Berechnung von y wird dreimal der Binomialkoeffizient berechnet.
• Zur Berechnung eines Binomialkoeffizienten wird dreimal die Funktion zur Berech-
nung von n! aufgerufen.
Zur Berechnung von y werden somit folgende Funktionen benötigt:
• die vordefinierten Funktionen sin(x) und cos(x) aus der Bibliothek (math.h),
• die Ausgabefunktion printf() aus stdio.h,
• eine Funktion zur Berechnung des Binomialkoeffizienten, binom(m,n) und
• eine Funktion fakul(n) zur Berechnung von n!.
Die Funktionen zur Berechnung des Binomialkoeffizienten und der Fakultät sind nicht
in einer der Standardbibliotheken von C definiert. Sie müssen vom Programmierer erst
als C-Funktionen programmiert werden.

82 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

Anmerkung:
Beim Erstellen eines C-Programms für eine bestimmte Aufgabenstellung steht der
Programmierer oft vor der Fragestellung, ob es bereits für eine bestimmte Teilaufga-
be ein Modul, eine vordefinierte Funktion, gibt. Ist es notwendig und sinnvoll, eine
spezielle Funktion zusätzlich zu programmieren?

Im folgenden Abschnitt werden das Programmieren einer Funktion und die Syntax der
Definition von Funktionen erläutert.
Vorab ist es an dieser Stelle zweckmäßig, den Gebrauch der folgenden drei Begriffe
Funktionsdefinition, Funktionsdeklaration und Funktionsimplementation festzule-
gen.
• Eine Funktion, muss in einem C-Programm definiert werden. Die Definition einer
Funktion umfasst die Deklaration der Funktion und deren Implementation – Func-
tionSpecification(27).
• Die Deklaration einer Funktion erfolgt durch den Funktionskopf Function-
Heading(29). Der Typ der Funktion (der Typ des möglichen Funktionswertes oder
Rückgabewertes) sowie der Name der Funktion und die Parameterliste werden be-
kanntgegeben.
• Das Implementieren einer Funktion – FunctionImplementation(28) – ist das eigent-
liche Programmieren. Der zur Funktionsdeklaration passende Block, mit den lokalen
Deklarationen und den entsprechenden Anweisungen, wird in der Syntax von C no-
tiert.

7.3.1 Definition von Funktionen


Die Syntaxdiagramme FunctionImplementation(28), FunctionHeading(29), FormalPara-
meters(30) und ParamSection(31) beschreiben formal eine Funktionsdefinition.

FunctionImplementation
(28)
FunctionHeading (29) Block (15)

FunctionHeading
(29)
TypeIdent (2) Ident (2) ( FormalParameters (30) )

FormalParameters
(30)
ParamSection (31)

void

ParamSection
(31)
TypeIdent (2) Ident (2)

Abb. 7.11: Syntaxdiagramme zur Definition einer Funktion

INM11 83
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Der Quelltext zur Definition einer Funktion besteht


grundlegend aus zwei Teilen, dem Funktionskopf Funktionskopf (FunctionHeading)
und dem Block der Funktion.
{

Block der Funktion (Block)


}

Beispiel 7.5:
Eine Funktion zur Berechnung von n! ist als Funktion in C zu programmieren. Fol-
gende Schritte sind dazu nötig:
1. Ermitteln der Berechnungsvorschrift zu n!, siehe mathematische Grundlagen
2. Umsetzung des Algorithmus als Moduldefinition.
3. Codierung als C-Quelltext unter Beachtung der Syntax

Zu 1. von Beispiel 7.5:


In der Mathematik wird die Berechnung von n! rekursiv definiert.
0!  1
1!  1
n!  n · (n – 1) für n  2
Das Ergebnis von 5! wird durch eine rekursive Berechnung ermittelt.
Rechenschritte:

5! 5 · 4! 5 · 24 120

4! 4 · 3! 4·6 24

3! 3 · 2! 3·2 6

2! 2 · 1! 2·1 2

Also 5!  120.
Oder man erkennt daraus die iterative Berechnung n!  1 · 2 · 3 … · n.

Zu 2. von Beispiel 7.5: fakul(n, h)


Input: n … ganze Zahl
Das Modul erhält den Namen fakul. Der Output: h … n! (n Fakultät)
Input ist eine Variable n.
h := 1
Das Ergebnis der Rechnung ist h, der Out- fuer i: = 1(1) n
put.
h := h * i
Die Berechnung erfolgt iterativ, die Grund-
struktur Zählschleife wird verwendet. Abb. 7.12: Struktogramm zum Modul
fakul(…)

84 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

Zu 3. von Beispiel 7.5:


Der Input n (ganze positive Zahl) ist der formale Parameter der Funktion, der Output h
ist der Rückgabewert, das Ergebnis der Rechnung. Der Rückgabewert ist vom Typ ganze
positive Zahl, er ist vorzeichenlos und hat einen ganzzahligen Wertebereich. Informie-
ren Sie sich in Tab. 3.1 zum Wertebereich unsigned long int.
Die Zählschleife wird durch das ForStatement(24) implementiert.

ForStatement
(24)
for ( Assignment1 (19) ; BoolExpression (19) ; Assignment1 (19) )

Statement (17)

Abb. 7.13: Syntaxdiagramm ForStatement (24)

// FunctionHeading (29)
// Input n, formaler Parameter
unsigned long fakul(int n){
// Block (15)
// VariableDeclaration (12)
int i;
unsigned long h=1;
// ForStatement (24)
for(i=1;i<n+1;i=i+1)
h=h*i;
// return Ergebnis - output h
return h;
}

Code 7.5: Code der Funktion fakul(…)

Diese Funktionsdefinition könnte in einen Quelltext, in ein C-Programm, bei Bedarf auf-
genommen werden.
Ein Programmierer entwickelt mit zunehmender Programmiererfahrung eigene Vorlie-
ben in der Gestaltung syntaktisch korrekter C-Programme. Eine Empfehlung für die Ge-
staltung eines gut lesbaren Quelltextes sei deshalb an dieser Stelle gestattet.

Hinweis:
Die Deklaration einer Funktion erfolgt im Bereich der globalen Deklarationen
durch die Angabe des Funktionskopfes. Damit ist die Funktion dem Compiler „be-
kannt“.
Die Definition der Funktion wird in den Quelltext nach der Funktion main einge-
fügt.

INM11 85
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Die Möglichkeit, dass eine Funktion innerhalb einer anderen Funktion deklariert wer-
den kann, wird in diesem Studienheft bewusst nicht genutzt. Alle Funktionen sind glo-
bal deklariert. Die Funktion main (...) wird als Hauptprogramm verstanden und alle wei-
teren (Unter-)Funktionen befinden sich auf einer Hierarchieebene. Eine weitere
Hierarchieebene innerhalb der (Unter-)Funktionen gibt es nicht.
Der syntaxbasierte Editor der IDE DEV C++ unterstützt das Implementieren von C-
Quelltexten durch die farbliche Gestaltung des Textes und die Möglichkeit des Verän-
derns der Sichtbarkeit von Modulen (vgl. Abb. 7.14).

Abb. 7.14: Die Funktion main() und die (Unter-)Funktionen zu Beispiel 7.4

7.3.2 Aufruf von Funktionen


Der Aufruf einer Funktion erfolgt durch Nennung des Funktionsnamens. An den Funk-
tionsnamen schließt sich immer das Klammerpaar an, das gegebenenfalls mehrere aktu-
elle Parameter (in C oft Argumente genannt) enthalten kann. Dieses Klammerpaar ist
zwingend erforderlich, auch dann, wenn keine aktuellen Parameter erforderlich sind.

FunctionCall
(33)
FunctionIdent (2) ( ActualParameter (2) ) ;

Abb. 7.15: Syntaxdiagramm FunctionCall (33) – Funktionsaufruf

Die aktuellen Parameter in der Aufrufanweisung einer Funktion müssen zu den forma-
len Parametern der Funktionsdefinition passen. Für den Aufruf einer Funktion benötigt
man keine Kenntnisse über die konkrete Implementation der Funktion.

86 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

Besitzt die Funktion einen Rückgabewert, kann der Funktionsaufruf in einem Ausdruck
verwendet werden. Er kann beispielsweise auf der rechten Seite einer Zuweisung stehen
oder in einer Ausgabeanweisung.

Beispiel 7.6:
Funktionsaufrufe in einem Ausdruck/einer Zuweisung (Berechnung von y in
Beispiel 7.4):
y  binom(k, l ) · sin(x) + binom(k, l – 1) · cos(x) + binom(k – 1, l ) · 3.5;
Ein Funktionsaufruf in einer Ausgabeanweisung:
printf(" 5! hat den Wert %d ,\n", fakul(5) );

Es wurde schon mehrfach darauf hingewiesen, dass der Sprachkern der Programmier-
sprache C im Vergleich zu anderen Programmiersprachen sehr klein ist. Es steht jedoch
eine Vielzahl von vordefinierten Funktionen in sogenannten Bibliotheken zur Verfü-
gung. Im Beispiel 7.4 werden u. a. die Standardfunktionen sin(x) und cos(x) aus der Bi-
bliothek der mathematischen Funktionen zur Berechnung des Wertes von y genutzt.
Die Direktive #include fügt andere Dateien (include-Dateien) in das Quellprogramm
ein. Dabei handelt es sich um Header-Dateien, die die Prototypvereinbarungen der Stan-
dardfunktionen, welche die C-Library bereitstellt, enthalten. Bereits bei der Installation
einer Softwareentwicklungsumgebung für die Programmierung mit C werden bestimm-
te include-Dateien in ein Unterverzeichnis kopiert.
In Tab. 7.3 sind einige wichtige include-Dateien genannt. Die jeweils durch sie bereitge-
stellten Funktionen werden kurz durch das zugeordnete Aufgabenspektrum charakteri-
siert.

Tab. 7.3: Auswahl von include-Dateien

Dateiname Aufgabenspektrum
assert.h Überprüfung von Bedingungen
ctype.h Typkonvertierungen und Typtests
float.h Fließkomma-Bibliothek
limits.h Ermittlung der Grenzen für Datentypen
math.h mathematische Funktionen, sqrt, sin usw.
stddef.h Standardkonstanten
stdio.h Standardeingabe und -ausgabe
stdlib.h Standardbibliotheksfunktionen zu Zufallszahlen und Arithmetik
string.h Funktionen für Daten des Datentyps string
time.h Funktionen und Konstanten zur Nutzung der Echtzeituhr

INM11 87
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Eingabe-/Ausgabe-Funktionen
In der Datei stdio.h, der Bibliothek zur Realisierung von Standardein- und -ausgabe, sind
die Funktionen zur formatierten Eingabe über die Tastatur und die Ausgabe auf dem
Bildschirm enthalten.
Die zur Eingabefunktion zugehörige Funktionsdeklaration lautet:
int scanf(const char *format [,parameter]…);

Mit der Funktion scanf() können Werte unterschiedlicher Datentypen formatiert ein-
gelesen werden. Eingelesen wird dabei von der Standardeingabe (stdin), das ist norma-
lerweise die Tastatur.
Der erste Parameter, der Formatstring *format, ist eine Folge von Zeichen, eine soge-
nannte Umwandlungsangabe. Eine Umwandlungsangabe beginnt mit % und endet mit
dem Umwandlungszeichen.
Beispiele: %d, %f usw.
So werden mit den Formatangaben die Datentypen der aktuellen Parameter, der Varia-
blen, für die Werte eingegeben werden können, festgelegt. Im Tastaturpuffer werden
eingegebene Zeichen zwischengespeichert.
Für den zweiten Parameter und die weiteren gilt folgende Schreibweise:
& <Bezeichner_aktueller_Parameter>
Da durch die Eingabe der Variablen ein neuer Wert zugewiesen wird, muss über die Ad-
resse (Adressoperator) der Zugriff auf den Speicherplatz der Variablen möglich sein.
(Später, wenn der Datentyp Zeiger/Pointer ausführlicher erläutert und für spezielle Auf-
gaben eingesetzt wird, werden Sie diesen Hinweis besser verstehen.).

Hinweis:
Ein typischer Anfängerfehler: scanf("%d", i); // falsch
Der Adressoperator wurde vergessen, das ist kein Syntaxfehler. Erst zur Laufzeit des
Programms entsteht ein Fehler, das Programm reagiert nicht mehr. Das ist fatal, das
Programm muss durch das Betriebssystem beendet werden.

Einige Formatangaben sind in Tab. 7.4 zusammengestellt:

Tab. 7.4: Formatbuchstaben zu scanf()

Umwandlungsangabe Umwandlung
%d oder %i vorzeichenbehafteter Integer als Dezimalwert
%e, %f, %g Fließkommazahl
%c Zeichen
%o Integer als Oktalzahl einlesen
%s Zeichenkette einlesen
%x Hexadezimalwert

88 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

// Programm scanf.c
#include <stdio.h>
int main ( ) {
int i;
// Datentyp int, ganze Zahl
printf("Bitte geben Sie eine Zahl ein : ");
scanf("%d",&i);
// Wartet auf die Eingabe,
//die mit <enter> abgeschlossen wird.
printf("Die Variable hat den Wert %d\n",i);
//Ausgabe der ganzen Zahl
return 0;
}

Code 7.6: Quelltext mit Eingabe und Ausgabe einer ganzen Zahl

Die zur Ausgabefunktion zugehörige Funktionsdeklaration lautet:


int printf( const char *format, pa1, pa2, …).

Die Bibliotheksfunktion printf() dient dazu, eine Zeichenkette (engl.: string) auf der
Standardausgabe auszugeben. In der Regel ist die Standardausgabe der Bildschirm. Der
erste formale Parameter, der Formatstring *format ist eine Folge von Zeichen, die als
konstante Zeichenfolge (zwischen " ") direkt im Programmcode angegeben wird.
In dieser Zeichenfolge können zwei Arten von Objekten vorkommen:
• druckbare Zeichen des ASCII-Codes, sie werden direkt in die Ausgabe kopiert, und
• Umwandlungsangaben.
Eine Umwandlungsangabe beginnt mit % und endet mit dem Umwandlungszeichen.
Es veranlasst die Umwandlung des zugeordneten Parameters in eine formatierte
Ausgabe. In Tab. 7.5, Tabelle der Formatbuchstaben für printf(), sind die mögli-
chen Formate aufgeführt:

Tab. 7.5: Formatbuchstaben für printf()

Formatbuchstaben Beschreibung
d, i Integer-Zahl
u vorzeichenlose Integer-Zahl
c Zeichen, auch Leerzeichen
s Zeichenfolge ohne Leerzeichen
f reelle Zahlen mit Nachkommastellen (double)
e, E reelle Zahlen mit Exponentenschreibweise
p Zeigerwert

INM11 89
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

Hinweis:
Eine kontextsensitive Nebenbedingung ist beim Aufruf der Eingabe- und der Ausga-
befunktion zu beachten:
Die Anzahl der Umwandlungszeichen und der nach dem ersten folgenden weiteren
Parameter muss gleich sein.

Beispiel 7.7:
printf("Das %d. Feldelement hat den Wert\t %d\n", i, f[i]);

In der Zeichenfolge (Beispiel 7.7) sind zwischen „…“, in der Folge von Zeichen, zwei Um-
wandlungsangaben enthalten, je eine Integer-Zahl wird bei der Ausgabe auf dem Bild-
schirm in den Text eingefügt.
Es gilt die Zuordnung:
1. aktueller Parameter zum ersten Umwandlungszeichen und 2. aktueller Parameter
zum zweiten Umwandlungszeichen.
Zwischen dem Zeichen % und dem Umwandlungszeichen können noch zusätzliche op-
tionale Angaben gemacht werden, wie z. B. die Anzahl der dargestellten Dezimalstellen
vor und nach dem Dezimalpunkt einer Fließkommazahl.
Im Beispiel 7.7 werden mit %2d zur Ausgabe der Integer-Zahl zwei Stellen (Spalten auf
dem Raster des Bildschirms) genutzt.

Beispiel 7.8:
printf("Das %2d.Feldelement hat den Wert\t %d\n",i, f[i]);

In den Beispielen 7.7 und 7.8 kommen noch weitere Zeichen vor, es sind nicht-druckbare
Zeichen, sogenannte Backslash-Sequenzen.
Die Zeichenfolge "\t" fügt einen Tabulatorabstand in die Zeichenfolge ein.
Die Zeichenfolge "\n" fügt in die Zeichenfolge ein „new line“ (einen Sprung zum An-
fang der nächsten Zeile) ein.

Übung 7.1:
Testen Sie mit einem einfachen Programm die angegebenen Formatierungsmöglich-
keiten und schauen Sie sich die Effekte der Gestaltung von Ausgaben auf dem Bild-
schirm an.

90 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Programmiersprache C 7

Zusammenfassung

Die Syntax der Programmiersprache C ist durch die Menge der Grundsymbole (das Al-
phabet), die Syntaxregeln (Menge der Syntaxdiagramme) und die kontextsensitiven Ne-
benbedingungen vollständig beschrieben.
Der Aufbau eines einfachen C-Programms und die Erweiterung eines C-Programms
durch Module wurden erläutert. Die Modularisierung von Problemstellungen, das Zer-
legen von Aufgaben in Teilaufgaben, wird durch das vorgestellte Modulkonzept der Pro-
grammiersprache C zweckmäßig unterstützt.
Da der Datentyp Zeiger erst im Studienheft INM12 ausführlich behandelt wird, werden
nur Funktionen mit Wertparametern (engl.: call by value) implementiert.
Nach der Bearbeitung der Aufgaben zur Selbstüberprüfung sollten Sie in der Lage sein,
kleinere Problemstellungen durch das Bereitstellen und die Nutzung eigener Soft-
waremodule zu lösen.

Aufgaben zur Selbstüberprüfung

7.1 Schreiben Sie ein C-Programm, das die Quadratzahlen zu den Zahlen 11 bis 25 be-
rechnet und auf dem Bildschirm anzeigt.
7.2 Schreiben Sie ein C-Programm, das x  a für einen über die Tastatur eingegebe-
nen reellen Wert a mit der Genauigkeit eps  0.0005 berechnet.
Nutzen Sie für die Berechnung das folgende iterative Verfahren.
(a  1)
a) Das Iterationsverfahren beginnt mit dem Startwert x  .
2
 a
b) In jedem weiteren Iterationsschritt wird x  0.5   x   gerechnet.
 x
Das Verfahren wird abgebrochen, wenn eine ausreichende Genauigkeit erzielt ist,
d. h. wenn |(x · x) – a|  eps ist.
Für die Berechnung des Betrags einer reellen Zahl w steht durch die include-Datei
math.h die Standardfunktion fabs(w) zur Verfügung.
7.3 Die in der Mathematik bekannte eulersche Zahl e  2,718 281 828 459 045 ... kann
als Grenzwert der folgenden unendlichen Reihe berechnet werden.

1 1 1 1 1
e        mit k  ℕ0 und 0!  1 ist definiert.
0! 1! 2! 3! k 0 k !

In einem C-Programm soll die eulersche Zahl mit einer vom Nutzer über die Tas-
tatur einzugebenden Anzahl (n, n  ) von Reihengliedern berechnet werden. Tes-
ten Sie das Ergebnis der Rechnung für n  5, n  10, n  15 und n  20.
Beachten Sie die Ausgabe; man kann die Anzahl der Stellen nach dem Komma in
der Anweisung printf(...) anpassen.
Die Funktion fakul(…) muss auch modifiziert werden, der Typ des Ergebnisses
sollte double sein.

INM11 91
© HfB, 02.12.20, Berk, Eric (904709)

7 Programmiersprache C

xn
7.4 Schreiben Sie ein C-Programm, das den Ausdruck d  für x  5.0 bis x  6.0
n!
mit der Schrittweite sw  0.1 tabelliert.
Die ganze Zahl n ist beliebig und wird über die Tastatur eingegeben. Nutzen Sie
die bereits erarbeiteten Funktionen.

92 INM11
© HfB, 02.12.20, Berk, Eric (904709)

A. Lösungen der Aufgaben zur Selbstüberprüfung


2.1 Euklidischer Algorithmus ggT(a, b):
1. Eingabe von zwei Zahlen a und b,
2. Tue
2.1: Ermittle mit r : (a MOD b) den Rest der ganzzahligen Division.
2.2: Weise zu a : b
2.3: Weise zu b : r
Wenn b ≠ 0 wiederhole 2.1, 2.2, 2.3,
andernfalls weiter mit 3.
3. Der größte gemeinsame Teiler ist dann genau ggT(a, b) : a.
Beispiel:
a : 44, b : 66
2.1: r : 44 MOD 66 = 44
2.2: a  66
2.3: b  44, wiederhole
2.1: r  66 MOD 44  22
2.2: a  44
2.3: b  22, wiederhole
2.1: r  44 MOD 22  0
2.2: a  22
2.3: b  0, zu 3.
Ende: Der ggT(44, 66)  22.
2.2 Die Daten zu den Mitgliedern des „Kunst-und Kulturvereins R. Schumann“ ste-
hen in einer Tabelle. Je eine Zeile enthält Name, Vorname, Adresse und Geburts-
datum. Schrittfolge:
1. Suche in der ersten Zeile nach dem Geburtsjahr, weise diesen Zahlenwert
der Variablen old zu.
2. Nun
2.1: Schau in die nächste Zeile, suche den Zahlenwert Geburtsjahr, weise
ihn der Variablen jahr zu.
2.2: Prüfe ob jahr  old, wenn ja (das bedeutet, dieses Mitglied ist älter),
weise old den Wert von jahr zu
Wiederhole (2.1 und 2.2) bis die letzte Zeile angeschaut wurde.
3. Der aktuell in old stehende Wert ist das Geburtsjahr des Ältesten bzw. der
ältesten Vereinsmitglieder.
4. Berechne das Alter des ältesten Mitglieds, nutze old (Geburtsjahr) und die
aktuelle Jahreszahl (ajahr), alter  ajahr – old.

INM11 93
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

2.3 Ein Jahr (A) ist kein Schaltjahr, wenn die Jahreszahl A nicht durch 4 teilbar ist.
Die durch 4 teilbaren Jahreszahlen, die keine Jahrhunderte sind (nicht teilbar
durch 100), sind Schaltjahre.
Jahrhunderte (teilbar durch 100) sind Schaltjahre, wenn sie auch durch 400 teil-
bar sind, andernfalls sind sie keine Schaltjahre.
Also 1972 ist ein Schaltjahr – teilbar durch 4.
1975 ist kein Schaltjahr – nicht durch 4 teilbar.
2000 ist ein Schaltjahr – ein Jahrhundert (teilbar durch 100) ist auch durch 400
teilbar.
1900 ist kein Schaltjahr – ein Jahrhundert (teilbar durch 100) ist nicht durch 400
teilbar.
Schrittfolge:
1. Eingabe A
2. Ist die Zahl A nicht durch 4 teilbar, dann ist es kein Schaltjahr,
andernfalls
Ist die Zahl nicht durch 100 teilbar, dann ist es ein Schaltjahr,
andernfalls
Ist die Zahl durch 400 teilbar, dann ist es ein Schaltjahr,
andernfalls ist es kein Schaltjahr.
3.1 Die beiden Datentypen short int werden mit 2 · 8 Bit intern dargestellt. Da jedes
Bit nur 0 oder 1 sein kann, sind 28 · 28  256 · 256  65536 verschiedene Bitfol-
gen möglich.
Das Dualsystem ist die Grundlage der Verschlüsselung der Zuordnung von Bit-
folge und Information. Vereinfachen wir uns die Betrachtung und prüfen nur
Bitfolgen/Dualzahlen mit 8 Bit.
Die Basis des Dualsystems ist 2, und die Stellenwerte sind Potenzen zur Basis 2,
2i mit i  {0 … 7}.

Dualzahl 0 1 1 0 1 0 1 1 entspricht 107dez


Stellenwert 27 26 25 24 23 22 21 20
berechneter
128 64 32 16 8 4 2 1
Stellenwert

Beispiele:
Das Bitmuster mit 8 Bit : 0000 0110  1 · 22  1 · 21  4  2  6dez
Das Bitmuster 0011 1010  1 · 25  1 · 24  1 · 23  1 · 21 
 32  16  8  2  58dez

94 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

Mit einem Byte (8 Bit) sind 256 verschiedene Bitfolgen möglich.


Die Codierung, die Festlegung der Bedeutung, für Werte von 0 bis 255 oder für
Werte von –128 … 0 … 127 ist möglich.

Codierung:
Dezimalzahl Bitmuster mit 8 Bit
0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 1
2 0 0 0 0 0 0 1 0
3 0 0 0 0 0 0 1 1
4 0 0 0 0 0 1 0 0
… …
127 0 1 1 1 1 1 1 1
–128 1 0 0 0 0 0 0 0
–6 1 1 1 1 1 0 1 0
… …
–1 1 1 1 1 1 1 1 1

Analog für 16 Bit: 0 … 65535 oder –32767 … 0 … 32768 (siehe Tab. 3.1).
Die Beispiel-Bitfolge 0000 1010 1000 1101dual entspricht 2701dez.
3.2 In der Mathematik wurde bewiesen, dass zwischen zwei reellen Zahlen immer
noch eine weitere reelle Zahl liegt (am Beispiel 2 ). Der Wertebereich ist deshalb
nicht ordinal, in diesem Zahlenbereich ergeben diese Operationen keinen Sinn.
Der Datentyp float lässt nur Werte aus einem diskontinuierlichen Teilbereich der
reellen Zahlen zu. Der C-Standard z. B. trifft keine Festlegung, welcher Teilbe-
reich der reellen Zahlen durch den Datentyp float überdeckt wird. 
Es muss damit gerechnet werden, dass verschiedene C-Systeme den Datentyp
float auch unterschiedlich realisieren. Häufig existiert der Datentyp darüber hi-
naus in verschiedenen Versionen.
3.3 Überlegungen:
Sei b  5 und pot  3, dann erg  53  5 · 5 · 5, dreimal die Basis b mit sich selbst
multiplizieren.
Sollte pot  0 sein, ist erg  1, also besser erg  1 · 5 · 5 · 5.
1. Aktion: Eingabe b und pot
2. Aktion: erg : 1, die Variable erg wird initialisiert.
3. Aktion: zaehl : 0

INM11 95
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

4. Aktion: Wiederhole solange zaehl  pot die Aktionen 4.1 und 4.2:
4.1: Aktion: erg  erg · b
4.2: Aktion: zaehl  zaehl  1
5. Aktion: Ausgabe erg
Kontrolle am Beispiel durch Trockentest mit Werteverlaufsprotokoll:

Aktion Variable b Variable pot Variable erg Variable zaehl


1. bis 3. 5 3 1 0
4.1 und 4.2 5 1
4.1 und 4.2 25 2
4.1 und 4.2 125 3
5. Ausgabe 125

3.4 Die beiden Zeichenfolgen „Der“ und „Zu“ sind zeichenweise rechnerintern mit
einem Byte (8 Bit) im ASCII-Code verschlüsselt:
Die Bitfolge 0100 0100 (dual)  44 (hexadezimal)  68 (dezimal)  ,D‘
Die Bitfolge 0101 1010 (dual)  5A (hexadezimal)  90 (dezimal)  ,Z‘
Der Test „Der“  „Zu“ liefert deshalb bereits beim Vergleich der ersten Buchsta-
ben das Ergebnis Ja, weil 68  90.
3.5 Angenommen die Angaben, die eine Bank von einem Kontoinhaber benötigt,
sind: Familienname (Name), Vorname und Geburtsdatum.
Sie vergibt eine Kontonummer, der Kontostand und eine Angabe zum Typ des
Kontos wird gespeichert, z. B. ‚Fest‘ oder ‚Giro‘.
Vom Datensatztyp datum ist die Komponente geboren, und die Komponente
wohnt ist ein Datensatztyp adresse. Beide müssen deklariert werden.
Der Datensatz Bankkonto enthält weiter folgende Angaben als Komponenten:
Name und Vorname sowie typ sind Felder, deren Elemente Zeichen sind.
Die Kontonummer ist eine vorzeichenlose ganze Zahl.
Der Kontostand ist eine gebrochene Zahl.
struct datum { int tag, monat, jahr; };
struct adresse { unsigned int PLZ; char strasse[15]; char
ort[20]; };
struct Bankkonto {
char Name[35], Vorname[15];
struct datum geboren;
struct adresse wohnt;
unsigned int Kontonummer;
float kontostand;
char typ[4];
};

96 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

4.1 In der Mathematik findet man die Aussage: Eine Zahl ist gerade, wenn sie ohne
Rest durch 2 teilbar ist. Die Teilbarkeit durch 2 wird mit der Operation MOD re-
alisiert, der Rest der Division wird ermittelt. Ist der Rest  0, dann handelt es sich
um eine gerade Zahl. Mit der Grundstruktur Alternative, 1 aus 2, werden die
zwei möglichen Ausgaben umgesetzt.

Eingabe: Zahl

Zahl MOD 2 = 0?
Ja Nein
Ausgabe: „Die Zahl ist gerade.“ Ausgabe: „Die Zahl nicht ist gerade.“

4.2 Die Quersumme einer positiven ganzen Zahl (zahl ) wird berechnet, indem die
Summe der einzelnen Ziffern ermittelt wird.
Wenn man eine ganze Dezimalzahl durch 10 dividiert, erhält man als Rest den
Wert der Ziffer mit dem kleinsten Stellenwert, und andererseits wird die Zahl
durch die Division durch 10 um eine Stelle verkürzt. Diese Operationen werden
wiederholt verwendet.
Beispiel: Dreistellige Zahl 129, die Berechnung erfolgt in folgenden Schritten:

zahl : 129 summe : 0


zahl_h : 129 – Hilfsvariable
zahl_h MOD 10  9 summe : summe  9  9
zahl_h DIV 10  12
zahl_h : 12
zahl_h MOD 10  2 summe : summe  2  9  2  11
zahl_h DIV 10  1
zahl_h : 1
zahl_h MOD 10  1 summe : summe  1  11  1  12
zahl_h DIV 10  0
zahl_h : 0

Eingabe: zahl

zahl_h := zahl

summe := 0

Wiederhole solange zahl_h > 0

summe := summe + (zahl_h MOD 10)

zahl_h := zahl_h DIV 10

Ausgabe: „Die Quersumme betraegt“ summe

INM11 97
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

4.3 Die Teiler (teiler ) einer ganzen Zahl (zahl ) ermittelt man, indem man nachein-
ander die Teilbarkeit durch die Zahlen 2 bis zahl lückenlos prüft. Die Operation
MOD wird verwendet. Ist der Rest der Division durch teiler  0, dann ist zahl
durch teiler teilbar.

Eingabe: zahl

Fuer teiler := 2 (1) zahl

zahl MOD teiler = 0?


Ja Nein
Ausgabe: teiler „ist Teiler von“ zahl

4.4 Beispiel: Eine Dualzahl mit 8 Dualziffern sei gegeben: 0100 1101.
Die Dualzahl liegt als Feld mit n (hier 8) Elementen vom Datentyp int vor. Das
Element mit dem höchsten Index, also n (hier 8) hat den Stellenwert 20  1. Das
Element mit dem Index 1 hat den Stellenwert 2n – 1 (27  128).

Feldindex 1 2 3 4 5 6 7 8
dual[8]
0 1 0 0 1 1 0 1
(Dualzahl mit 8 Stellen)
Stellenwerte 27 26 25 24 23 22 21 20

Dual_dezi (n, dual, dezi)


Input: n … Anzahl der Dualziffern
dual … Feld der Dualziffern
Output: dezi … Dezimalzahl

dezi := 0

stellenwert := 1

Fuer i := n (–1) 1

dezi := dezi + duali * stellenwert

stellenwert := stellenwert * 2

Berechnung: dezi  0  1  0  4  8  0  0  64  0  77
4.5 fakultaet_n(n, erg)
Input: n … Zahl
Output: erg … Ergebnis der Rechnung n!
Hilfsvariable i als Zähler

erg := 1

Fuer i := 1(1) n

erg := erg * i

98 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

Überlegen Sie auch folgende Fälle des Modulaufrufs:


fakultaet(1, erg)  erg  1, weil die Zählschleife von 1 bis 1 „läuft“ wird eine
Rechnung ausgeführt, nämlich erg  1 · 1.
fakultaet(0, erg)  erg  1, weil die Zählschleife nicht durchlaufen wird (von 1
bis 0) ist erg  1.
Zur Veranschaulichung des Ablaufs schauen Sie sich noch einmal das Wertever-
laufsprotokoll zu Beispiel 4.9 an.
4.6 Im Kapitel 2 wurde in Beispiel 2.2 ein Algorithmus zur Ermittlung von ggT(x, y)
dargestellt. In der Aufgabe zur Selbstüberprüfung 2.1 haben Sie einen effizienten
Algorithmus zu diesem ggT(x, y) gesucht und mit Worten beschrieben. Daraus
entsteht die Moduldefinition ggT(x, y, teiler).

ggT(x, y, teiler)
Input: zwei Zahlen x und y
{ Hilfsvariable r … Rest }
Output: teiler … größter gem. Teiler

r := x MOD y

x := y

y := r

Wiederhole bis r = 0

teiler := x

INM11 99
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

5.1 feld: 12 34 4 17 33 9
Durch wird die notwendige Tauschaktion zwischen zwei benachbarten
Elementen angezeigt.
Start: 12 34 4 17 33 9
1. Durchlauf 34 12 4 17 33 9
17 4 33 9
33 4 9
9 4
Ergebnis 1. Durchlauf 34 12 17 33 9 4
2. Durchlauf verkürztes Feld 34 12 17 33 9 4
17 12 33 9
33 12 9
Ergebnis 2. Durchlauf 34 17 33 12 9 4
3. Durchlauf verkürztes Feld 34 17 33 12 9 4
Ergebnis 3. Durchlauf 34 33 17 12 9 4
4. Durchlauf verkürztes Feld 34 33 17 12 9 4
Ergebnis 4. Durchlauf 34 33 17 12 9 4
Ende, kein Tausch 34 33 17 12 9 4

6.1 a) z. B. 234, 789, 45099


b) Eine syntaktisch korrekte vorzeichenlose ganze Zahl beginnt nicht mit der
Ziffer ‚0‘.
6.2 a) beliebig
b) ‚MMXXIIV‘ ist kein Satz der Sprache „ROM_Zahlen“. 
MMXXIV oder MMXXII sind korrekte Sätze.
6.3 keine Lösung
7.1 // Programm Quadratzahlen.c
int main(){
int zahl, i;
printf("\n");
printf("\n Berechnung der Quadratzahlen \n");
for(i = 11; i < 26; i++) {
zahl=i*i;
printf("\n");
printf("\t %d Quadrat ist %d ",i,zahl);
}
printf("\n");
return 0;
}

100 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

7.2 // Programm Wurzel.c


#include<stdio.h>
#include <math.h>
int main(){
float a, x, eps;
char c = 'N';
eps = 0.0005;
do {
system("cls");
printf("\n");
printf(" Geben Sie den Wert a ein \t");
scanf("%f", &a);
x = (a + 1)/2;
while (fabs(x * x - a) > eps) {
x = 0.5 * (x + a/x);
}
printf("\n");
printf("Das Ergebnis ist %.5 f. \n", x);
printf("\n");
printf("Rechnung wiederholen? ('j', 'n')");
c=_getch();
} while (c == 'j') ;
return 0;
}

7.3 // Programm Euler.c


// e = 2,718281828459045...
#include <stdio.h>
double fakul(int n);
int main() {
double sum,s;
int i;
unsigned int n;
printf("\n");
printf("\n");
printf("\t Berechnung der eulerschen Zahl durch eine
Reihe");
printf("\n");
printf("\n");
printf("Geben Sie die Anzahl der Reihenglieder ein!\t");
scanf("%d",&n);
sum = 2;
i = 2;
while(i <= n){
sum = sum + 1/fakul(i);
printf("\nDer Wert von e ist %.12 f. \t(%d.)\n",sum,i);
i++;
}
return 0;

INM11 101
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

}
double fakul(int n){
int i;
double h = 1;
for(i = 1; i < n+1; i++)
h = h * i;
return h;
}

7.4 // Programm Ausdruck.c


#include<stdio.h>
unsigned long fakul(int n);
float hoch(float b, int pot);
int main(){
int n;
float d,x,sw;
printf("\n");
printf("\n Berechnung des Ausdrucks d = x^n/n! fuer");
printf("\n x = 5.0 bis x = 6.0 mit einer Schrittweite von
0.1\n");
printf("\n");
printf(" Geben Sie den Wert n ein \t");
scanf("%d", &n);
printf("\n \t x\t\t d fuer n=%d",n);
printf("\n ________________________________");
x = 5.0;
sw = 0.1;
while (x <= 6.0) {
d = hoch(x,n)/fakul(n); // Datentyp float
printf("\n \t %0.2f\t\t %0.4f",x,d);
x = x + sw;
}
printf("\n");
return 0;
}

unsigned long fakul(int n){


int i;
long int h = 1;
for(i = 1; i < n+1;i ++)
h = h * i;
return h;
}

float hoch(float b,int pot){


int i,erg = 1;
if(pot == 0) return 1;
if(pot == 1) return b;
for(i = 1; i <= pot; i++)
erg = erg * b;
return erg;
}

102 INM11
© HfB, 02.12.20, Berk, Eric (904709)

B. Literaturverzeichnis
Ernst, H. et al. (2015). Grundkurs Informatik. Grundlagen und Konzepte für die erfolg-
reiche IT-Praxis. Eine umfassende, praxisorientierte Einführung. 
5. Aufl., ISBN 978-3-8348-0362-7, Heidelberg: Springer Vieweg.
Herold, H.; Lurz, B.; Wohlrab, J. (2012). Grundlagen der Informatik. 
2. aktual. Aufl., ISBN 978-3-8632-6526-7, Pearson.
Louis, D. (2012). C++: Programmieren mit einfachen Beispielen. 
1. Aufl., ISBN 978-3-8272-4770-4, Markt+Technik.
Nassi, I.; Shneiderman, B. (1973). Flowchart techniques for structured programming. 
ACM SIGPLAN Notices.
Schneider, U. (Hrsg.) (2012). Taschenbuch Informatik. 
ISBN 978-3-446-40754-1, 7. neu bearb. Aufl., Leipzig, München: Hanser.

Internetlinks
http://www.c-programmieren.com/C-Lernen.html#Einleitung
http://www.scoberlin.de/content/media/http/informatik/sthiemert/gesamt.pdf
https://de.wikipedia.org/wiki/C_(Programmiersprache)

Anmerkung:
Anregungen zur Gestaltung des Kapitels 6 ergaben sich aus der Lehrtätigkeit an der
TU Dresden, Fakultät Informatik, in der Professur „Grundlagen der Programmie-
rung“, Prof. Dr.-Ing. habil. Heiko Vogler.
Die Syntaxdiagramme des Lehrmaterials zu den Vorlesungen „Algorithmen und Da-
tenstrukturen“ und „Programmierung“ (Ausgabe 2012) wurden von uns übernom-
men, überarbeitet und für dieses Studienmaterial angepasst.

INM11 103
C. Grundstrukturen von Algorithmen C
Nassi-Shneiderman – Struktogramm Pseudocode Syntaxdiagramm von C

<bedingung>

Wenn Bedingung erfüllt 


Ja Nein
dann Aktion 1
Aktion 1 / IfStatement

Alternative
(20)

104
( BoolExpression (6) ) Statement (17) else Statement (17)

<bedingung> Wenn Bedingung erfüllt 


Ja Nein dann Aktion 1
Aktion 1 Aktion 2
sonst Aktion 2

Falls <fallausdruck>
gleich <fall 1>, dann Aktion 1 SwitchStatement
<fallausdruck>
gleich <fall 2>, dann Aktion 2 (21)
switch ( Expression (6) ) {

sonst
<fall 1> <fall 2> … <fall n> gleich <fall n>, dann Aktion n
Aktion 1 Aktion 2 Aktion n Aktion ...
case CaseLabel (3) : StatementSequenz (16) BreakStatement (25)
sonst Aktion

Falls <fallausdruck>
<fallausdruck>
gleich <fall 1>, dann Aktion 1

Mehrfachauswahl
gleich <fall 2>, dann Aktion 2 default : StatementSequenz (16) }

<fall 1> <fall 2> … <fall n>


Aktion 1 Aktion 2 … … Aktion n
...
gleich <fall n>, dann Aktion n

INM11
Grundstrukturen von Algorithmen
© HfB, 02.12.20, Berk, Eric (904709)
Nassi-Shneiderman – Struktogramm Pseudocode Syntaxdiagramm von C

INM11
anfangsgeprüfte
So lange <bed> erfüllt,
tue WhileStatement
Wiederhole so lange <bed> Aktion / Aktionen. (22)
while ( BoolExpression (6) ) Statement (17)

<schleifenkoerper>
Der Schleifenkörper kann eine
Folge von Aktionen enthalten. In C hat die endgeprüfte Schleife folgenden Pseudocode:
tue
endgeprüfte Aktion
Tue solange <bed> erfüllt.
Aktion / Aktionen
Grundstrukturen von Algorithmen

bis <bed> erfüllt. DoWhileStatement


<schleifenkoerper>
(23)
do Statement (17) while ( BoolExpression (6) ) ;
Der Schleifenkörper kann eine
© HfB, 02.12.20, Berk, Eric (904709)

Wiederhole bis <bed> Folge von Aktionen enthalten.

Schleifenarten
Zählschleife Für den Zähler mit Anfangs-
wert (anfw) bis Endwert
Fuer <zaehlvar> := <anfw> (Sw) <endw> (endw) in der Schrittweise
(Sw) ForStatement
(24)
tue for ( Assignment1 (19) ; BoolExpression (6) ; Assignment1 (19) )
<schleifenkoerper>
Aktion / Aktionen

Bei Eintritt in die Schleife ist Statement (17)


die Anzahl der Wiederholun-
gen (endw – anfw bei Sw=1)
bekannt.

105
C
© HfB, 02.12.20, Berk, Eric (904709)

D. Syntaxdiagramme der Programmiersprache C


Jedes Syntaxdiagramm hat einen Namen, das Hilfssymbol. Die in ( ) zu einem Hilfssym-
bol angegebene Zahl verweist auf das ihm entsprechende Sytaxdiagramm oder eine Er-
läuterung.
Erläuterungen:
(1) Das Hilfssymbol „Buchstabe“ steht für einen beliebigen Grossbuchstaben oder
Kleinbuchstaben oder für das Unterstreichungszeich en.
(2) Die Syntax eines Bezeichners ist vordefiniert. Ein Hilfssymbol in dem das Wort
„Ident“ bzw. „Name“ vorkommt ist ein Bezeichner.
„TypeIdent“ ist ein Typbezeichner,
„FunctionIdent“ ist ein Funktionsname,
„TagName“ ist ein Bezeichner für eine Komponente einer Struktur, eines Datensat-
zes,
„ActualParameter“ ist der Bezeichner eines Funktionsarguments,
„ElementType“ ist ein Typbezeichner.
(3) „CaseLabel“ ist eine Fallmarke, eine ganze Zahl.
(4) Die Syntax einer Zahl ist vordefiniert. „Number“ ist eine ganze Zahl.
(5) In der Deklaration eines Aufzählungstyps werden die Elemente aufgezählt, deshalb
das Hilfssymbol „element“.
(6) Die Syntax eines Ausdrucks ist vordefiniert. Ein Hilfssymbol „Expression“ steht für
einen Ausdruck beliebigen elementaren Datentyps. Das Hilfssymbol „BoolExpressi-
on“ bezieht sich das Ergebnis eines Test. Das Ergebnis ist ein logischer Wert, ja oder
nein bzw. ein numerischer Wert. Ist dieser Wert gleich null so entspricht er dem lo-
gischen nein, alle anderen Werte entsprechen ja.

Program
(7)
GlobalDeclaration (8)

Import (42)

int main ( ) Block (15)

FormalParameter (30)

FunctionImplementation (28)

106 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Syntaxdiagramme der Programmiersprache C D

GlobalDeclaration
(8)
Declaration (9)

Functionspecificatation (27)

Declaration
(9)
ConstantDeclaration (10)

VariableDeclaration (12)

TypeDeclaration (13)

ConstantDeclaration
(10)
const TypeIdent (2) ConstDeclaration (11) ;

ConstantDeclaration
(11)
Ident (2) = ConstExpression (6)

VariableDeclaration
(12)
TypeIdent (2) Ident (2) ;

= ConstantExpression (7)

TypeDeclaration

(13)
EnumTypeDeclaration (35)

ArrayTypeDeclaration (14)

StructureTypeDeclaration (39)

ArrayTypeDeclaration

(14)
typedef TypeIdent (2) Ident (2) [ Number (4) ] ;

Block
(15)
{ Declaration (9) StatementSequence (16) ReturnStatement (32) }

INM11 107
© HfB, 02.12.20, Berk, Eric (904709)

D Syntaxdiagramme der Programmiersprache C

StatementSequence

(16)
Statement (17)

Statement
(17)
Assignment (19)

IfStatement (20)

SwitchStatement (21)

WhileStatement (22)

DoWhileStatement (23)

ForStatement (24)

BreakStatement (25)

CompoundStatement (26)

FunctionCall (33)

ReturnStatement (32)
Assigment

(18)
Assignment1 (19) ;
Assigment1

(19)
Iden t (2) = Expression (6)

( TypeIdent (2) )
IfStatement

(20)
if ( BoolExpression (6) ) Statement (17) else Statement (17)

SwitchStatement
(21)
switch ( Expression (6) ) {

case CaseLabel (3) : StatementSequenz (16) BreakStatement (25)

default : StatementSequenz (16) }

108 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Syntaxdiagramme der Programmiersprache C D

WhileStatement
(22)
while ( BoolExpression (6) ) Statement (17)

DoWhileStatement
(23)
do Statement (17) while ( BoolExpression (6) ) ;

ForStatement
(24)
for ( Assigment1 (19) ; BoolExpression (6) ; Assignment1 (19) )

Statement (17)

BreakStatement
(25)
break ;

CompoundStatement

(26)
{ StatementSequence (16) }

Declaration (9)


  

(27)
FunctionHeading (29) ;

FunctionImplementation (28)

FunctionImplementation

(28)
FunctionHeading (29) Block (15)

FunctionHeading

(29)
TypeIdent (2) Ident (2) ( FormalParameters (30) )

FormalParameters
(30)
ParamSection (31)

void

ParamSection
(31)
TypeIdent (2) Ident (2)

INM11 109
© HfB, 02.12.20, Berk, Eric (904709)

D Syntaxdiagramme der Programmiersprache C

ReturnStatement
(32)
return Expression (6) ;

FunctionCall
(33)
FunctionIdent (2) ( ActualParameter (2) ) ;

EnumType

(34)
enum { element (5) } VarIdent (2) ;

TagName (2) , ,

EnumTypeDeclaration

(35)
typedef enum { element (5) } VarIdent (2) ;

TagName (2) ;
ArrayType

(36)
ElementType (2) Ident (2) [ Number (4) ] ;

RecordType

(37)
StructureType (38)

UnionType (41)
StructureType

(38)
struct { FieldList (39) ; } Ident (2) ;

TagName (5) ,
FieldList
(39)
TypeIdent (2) Ident (2)

,
StructureTypeDeclaration

(40)
typedef struct { FieldList (39) ; } Ident (2) ;

TagName (2)

110 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Syntaxdiagramme der Programmiersprache C D

UnionType

(41)
union { FieldList (39) ; } Ident (2) ;

TagName (2) ,
Import

(42)
#include < Ident (2) >

" Ident (2) "

INM11 111
© HfB, 02.12.20, Berk, Eric (904709)

E. Abbildungsverzeichnis
INM11

Abb. 1.1 E-V-A-Prinzip der rechnergestützten Informationsverarbeitung ............ 3


Abb. 1.2 Struktogramm „Sparjahre“ .......................................................................... 6
Abb. 1.3 Start des Übersetzens, Kompilieren ........................................................... 9
Abb. 1.4 Ergebnis der Übersetzung „Sparjahre.exe„ ................................................ 9
Abb. 1.5 Screenshot, Fenster mit Eingaben und Ausgabe zu „Sparjahre.exe“ ...... 10
Abb. 3.1 Klassifikation der Datentypen .................................................................... 21
Abb. 3.2 Bildliche Vorstellung eines Datenobjekts vom einfachen Datentyp ...... 23
Abb. 3.3 Auszug aus Abb. 3.1 .................................................................................... 26
Abb. 3.4 Darstellung der Datenstruktur Feld (Feld e mit n Elementen) ................ 26
Abb. 4.1 Folge von Zuweisungen, zu Beispiel 4.4 ................................................... 35
Abb. 4.2 Struktogramm zu Beispiel 4.5 .................................................................... 36
Abb. 4.3 Struktogramm zu Beispiel 4.6 .................................................................... 37
Abb. 4.4 Struktogramm zu Beispiel 4.7 .................................................................... 38
Abb. 4.5 Struktogramm zu Beispiel 4.8 .................................................................... 39
Abb. 4.6 Struktogramm zu Beispiel 4.9 .................................................................... 40
Abb. 4.7 Struktogramm: Eingabe einer m, n-Matrix ............................................... 41
Abb. 4.8 Moduldefinition ........................................................................................... 43
Abb. 4.9 Aufruf eines Moduls .................................................................................... 43
Abb. 4.10 Moduldefinition zur Berechnung von erg = ax ......................................... 44
Abb. 4.11 Struktogramm zur Berechnung von π durch die Leibniz-Reihe
(Beispiel 4.10) ............................................................................................... 45
Abb. 5.1 Struktogramm „Lage des Punktes P(x; y) im Koordinatensystem“,
Beispiel 5.1 ................................................................................................... 48
Abb. 5.2 Struktogramm zur Spezifizierung der Lage von P(x; y), Beispiel 5.1 ..... 48
Abb. 5.3 Struktogramm zum Algorithmus „Summe beliebig vieler reeller Zahlen“,
Beispiel 5.2 ................................................................................................... 49
Abb. 5.4 Struktogramm zum Algorithmus „Suche nach dem Minimum von 
n-Zahlen“. Beispiel 5.3 ................................................................................ 50
Abb. 5.5 Moduldefinition Mini(anz, f, erg) – „Suche nach dem Minimum von 
anz Zahlen“ .................................................................................................. 50
Abb. 5.6 Algorithmus „Suche nach dem Minimum von n Zahlen“ mit 
Modul Mini(…) ............................................................................................. 50
Abb. 5.7 Moduldefinition Anzeige(anz, feld) ........................................................... 51
Abb. 5.8 Moduldefinition Sort(anz, f ) – fallendes Sortieren von 
anz Feldelementen ....................................................................................... 53
Abb. 5.9 Moduldefinition Sort_verbessert(anz, f ) mit Modul swap(…) ................. 54

112 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Abbildungsverzeichnis E

Abb. 6.1 Verwandtschaft einiger wichtiger Programmiersprachen ...................... 56


Abb. 6.2 Syntaxdiagramm Program (7) .................................................................... 59
Abb. 6.3 Syntaxdiagramme zur Sprache der Bezeichner ....................................... 60
Abb. 6.4 Syntaxdiagramm Bezeichner ..................................................................... 61
Abb. 6.5 Syntaxdiagramme zur Ausdruckssprache, Beispiel 6.4 ........................... 62
Abb. 6.6 Aufgaben der Komponenten der IDE beim Erstellen eines 
C-Programms .............................................................................................. 67
Abb. 6.7 Zu Aufgabe 6.1: Syntaxdiagramme zur Sprache 
vorzeichenlose ganze Zahl .......................................................................... 68
Abb. 6.8 Zu Aufgabe 6.2: Syntaxdiagramme ........................................................... 69
Abb. 7.1 C-Programm Einf.c in IDE DEV-C++ ....................................................... 71
Abb. 7.2 Hauptteile eines C-Programms .................................................................. 72
Abb. 7.3 Syntaxdiagramm Program (7) .................................................................... 72
Abb. 7.4 Struktogramm „Lage eines Punktes P(x, y) im …“, aus Abb. 5.1 und 5.2
zusammengesetzt ........................................................................................ 76
Abb. 7.5 Syntaxdiagramme zur Alternative und Mehrfachauswahl ..................... 77
Abb. 7.6 Quelltext Lage.c ........................................................................................... 78
Abb. 7.7 Struktogramm „Berechnung der Quersumme einer Zahl“ ..................... 79
Abb. 7.8 Quelltext Quersumme.c .............................................................................. 80
Abb. 7.9 Struktogramm „Prinzip der Wiederholung eines Programms“ .............. 80
Abb. 7.10 Syntaxdiagramm DoWhileStatement(23) ................................................. 80
Abb. 7.11 Syntaxdiagramme zur Definition einer Funktion .................................... 83
Abb. 7.12 Struktogramm zum Modul fakul(…) ......................................................... 84
Abb. 7.13 Syntaxdiagramm ForStatement (24) .......................................................... 85
Abb. 7.14 Die Funktion main() und die (Unter-)Funktionen zu Beispiel 7.4 ......... 86
Abb. 7.15 Syntaxdiagramm FunctionCall (33) – Funktionsaufruf .......................... 86

INM11 113
© HfB, 02.12.20, Berk, Eric (904709)

F. Tabellenverzeichnis
INM11

Tab. 1.1 Daten für die Berechnung ............................................................................. 5


Tab. 1.2 Trockentest zum Algorithmus „Sparjahre“ ................................................. 6
Tab. 2.1 Schrittweise Algorithmus-Ausführung ....................................................... 14
Tab. 3.1 Datentypen in C ............................................................................................. 25
Tab. 4.1 Protokoll zu Struktogramm Abb. 4.7 ........................................................... 42
Tab. 5.1 Sortiervorgang – Protokoll ........................................................................... 52
Tab. 7.1 Schlüsselwörter von C ................................................................................... 73
Tab. 7.2 Tabelle der Operatoren .................................................................................. 74
Tab. 7.3 Auswahl von include-Dateien ...................................................................... 87
Tab. 7.4 Formatbuchstaben zu scanf() ................................................................. 88
Tab. 7.5 Formatbuchstaben für printf() ............................................................. 89

114 INM11
© HfB, 02.12.20, Berk, Eric (904709)

G. Sachwortverzeichnis
INM11

A Datentyp ................................... 21, 22


Adresse ............................................ 29 char ............................................ 24
Aktion, elementare .......................... 33 einfacher ..................................... 21
Algorithmen float ............................................ 24
nicht-terminierende .................... 17 int ............................................... 23
stochastische ............................... 18 strukturierter .............................. 21
Algorithmus ............................... 11, 16 Datentypen
prozeduraler ............................... 17 dynamische ................................. 22
Allgemeinheit .................................. 16 nicht ordinale .............................. 22
Alphabet, Grundsymbole ................. 73 ordinale ...................................... 22
Alternative ................................. 33, 77 statische ...................................... 22
ASCII-Codes .................................... 89 Debugger ......................................... 65
ASCII-Zeichentabelle ....................... 24 Deklaration ........................... 28, 83, 85
Ausgabe ...................................... 3, 34 Dereferenz-Operator ........................ 29
Auswahl .......................................... 36 Determiniertheit .............................. 17
Auswahl, einfache ........................... 36
Auswahl, mehrfache ........................ 36 E
Automat ............................................ 3 Effektivität ....................................... 17
Effizienz .......................................... 17
B Eindeutigkeit ................................... 17
Berechnung Eingabe ....................................... 3, 34
iterative ...................................... 84 Eingabe-/Ausgabe-Funktion ............. 88
rekursive ..................................... 84 Endlichkeit ...................................... 17
Bezeichner ............................ 22, 61, 74 E-V-A ................................................ 3
Block .......................................... 32, 33 E-V-A-Prinzip .................................. 17

C F
Codierung ....................................... 24 Fallausdruck .................................... 36
Computer .......................................... 3 Fallunterscheidung .......................... 36
C-Programm .................................... 70 Feld ................................................. 26
Folge ......................................... 33, 35
D Formatangabe .................................. 88
Daten .............................................. 20 Formatbuchstabe ............................. 89
Programmdaten, Funktion .......................................... 70
Verarbeitungsdaten ....................... 3 Funktion, Aufruf .............................. 86
Datenobjekte ................................... 20 Funktionsdefinition .................... 72, 83
Datensatz ........................................ 27 Funktionsdeklaration ....................... 83
Datenstruktur ............................. 22, 26 Funktionsimplementation ................ 83
dynamische ................................. 29 Funktionskopf .................................. 84
statische ...................................... 26

INM11 115
© HfB, 02.12.20, Berk, Eric (904709)

G Sachwortverzeichnis

G N
Gleitkommaarithmetik ..................... 24 Nassi und Shneiderman .................... 32
Gleitkommazahlen ........................... 24 Nebenbedingung, kontextsensitiver .. 81
Grundsymbol ................................... 73
Grundsymbol von C ......................... 59 O
Operatoren ...................................... 74
H Ordnungsrelation ............................. 52
Hauptprogramm .............................. 70
P
I Parameter
Index ............................................... 26 aktueller ...................................... 43
Indirektionsoperator ........................ 29 formaler ...................................... 43
Information ....................................... 3 Pointer ............................................. 29
Informationsverarbeitung ................... 3 Programm .................................... 4, 11
Informationsverarbeitung, ausführbares ............................... 65
computergestützte ......................... 3 Programmiersprache ........................ 55
Interpreter ................................. 65, 66 deklarative .................................. 55
Iteration ........................................... 38 höhere problemorientierte ........... 55
imperative ................................... 56
K künstliche Sprache ...................... 55
Klassifikation der Datentypen .......... 21 maschinennah ............................. 55
Kommentar ...................................... 74 objektorientierte .......................... 56
Kompiler .................................... 65, 66 prozedurale ........................... 55, 56
Komponenten .................................. 27 Programmierumgebung ...................... 7
Konstante DEV-C++ ...................................... 8
syntaktische ................................ 60 Prototypen ....................................... 72
Korrektheit Prozedur .......................................... 55
syntaktische ................................ 62 Punkt-Operator ................................ 28
Künstliche Sprache .......................... 56
Q
L Quelltext .......................................... 65
Laufbereich ...................................... 38
Leibniz-Reihe ............................. 43, 44 R
Linker ........................................ 65, 66 Referenz-Operator ............................ 29

M S
Mehrfachauswahl ............................ 77 Schleife ............................................ 38
Modul .............................................. 42 anfangsgeprüfte ........................... 38
Modularisierung .............................. 42 endgeprüfte ................................. 39
Modulaufruf .................................... 43 geschachtelte ............................... 41
Moduldefinition ............................... 43 Schlüsselwort ................................... 73
Modulkonzept .................................. 29 Schnittstelle ..................................... 42
Selektion .......................................... 36
Semantik .......................................... 57
Sequenz ........................................... 35

116 INM11
© HfB, 02.12.20, Berk, Eric (904709)

Sachwortverzeichnis G

Software ............................................ 4 Z
Softwareentwicklungsumgebung, Zählschleife .......................... 38, 40, 85
integrierte ................................... 65 Zeichen, druckbares ......................... 89
Sortiervorgang ................................. 51 Zeiger .............................................. 22
Sprache, formale .............................. 57 Zeigervariable .................................. 29
Standardalgorithmen ....................... 16 Zugriff ............................................. 28
Standarddatentypen .................... 21, 25 Zugriff, wahlfrei .............................. 26
Startdiagramm ................................. 59 Zuweisung ....................................... 33
Startsymbol ..................................... 59 Zyklus ............................................. 38
Stern-Menge .................................... 57
Struktogramm ................................. 32
Grundstruktur, Aktion ................ 33
Syntax ........................................ 57, 58
syntaxbasierten Editor ..................... 78
Syntaxdiagramme ............................ 58
Syntaxdiagramme zu C .................... 76
Syntaxregeln .................................... 57

T
Top-down-Prinzip ............................ 42
Trennzeichen ................................... 74
Trockentest, Werteverlaufsprotokoll . 41

U
Umwandlungsangaben .................... 89

V
Variable ........................................... 22
syntaktische ................................ 60
Variable, syntaktische
Hilfssymbol ................................ 59
Verarbeitung ...................................... 3
Verbund ........................................... 27

W
Weg, legaler ..................................... 60
Wertebereich ........................ 22, 23, 24
Wiederholung ............................. 33, 38

INM11 117
© HfB, 02.12.20, Berk, Eric (904709)

118 INM11
© HfB, 02.12.20, Berk, Eric (904709)

H. Einsendeaufgabe Typ A
Grundlagen der Algorithmierung und der Programmierung Teil 1 Einsendeaufgabencode:
INM11-XX1-K03

Name: Vorname: Tutor/-in:

Postleitzahl und Ort: Straße: Datum:

Matrikel-Nr.: Studiengangs-Nr.: Note:

Heftkürzel: Druck-Code: Unterschrift:


INM11 0419K03
Bitte reichen Sie Ihre Lösungen über StudyOnline ein. Falls Sie uns diese per Post senden
wollen, dann fügen Sie bitte die Aufgabenstellung und den Einsendeaufgabencode hinzu.

1. Entwerfen Sie einen Algorithmus „Sparvertrag“ und stellen Sie diesen als Strukto-
gramm dar.
Der Wert des Endkapitals E aus einem Sparvertrag mit folgenden Konditionen soll
ermittelt und ausgegeben werden:
• Ein Anfangskapital A wird eingezahlt,
• die Bank gewährt einen konstanten Zinssatz zins,
• das Kapital wird am Ende eines jeden Jahres durch eine konstante Ratenzahlung
Rate erhöht,
• die Laufzeit des Sparvertrags beträgt n Jahre.
30 Pkt.
2. Entwerfen Sie eine Moduldefinition für das Modul „TagNr“.
Aus einem an das Modul übergebenen Datum (tag, monat, jahr) soll ermittelt wer-
den, der wievielte Tag des betreffenden Jahres dieser Tag ist. Berücksichtigen Sie da-
bei, dass das betreffende Jahr ein Schaltjahr sein kann.
30 Pkt.
3. Schreiben Sie ein C-Programm. In diesem Programm sollen folgende drei Aufgaben
in der angegebenen Reihenfolge gelöst werden:
• Ein Feld A mit n (z. B. n  12) ganzen Zahlen wird über die Tastatur eingegeben.
• Das komplette Feld A wird zur Kontrolle auf dem Bildschirm ausgegeben.
• Das Maximum des Feldes A (max) und seine Position (pos) im Feld werden er-
mittelt und auf dem Bildschirm ausgegeben.
40 Pkt.

INM11 119
© HfB, 02.12.20, Berk, Eric (904709)

$
Studienheft

INM12

Grundlagen der Algorithmierung


und Programmierung Teil 2
0619K03
Das Studienheft und seine Teile sind urheberrechtlich geschützt.
Jede Nutzung in anderen als den gesetzlich zugelassenen Fällen
ist nicht erlaubt und bedarf der vorherigen schriftlichen
Zustimmung des Rechteinhabers. Dies gilt insbesondere für das
öffentliche Zugänglichmachen via Internet, Vervielfältigungen und
Weitergabe. Zulässig ist das Speichern (und Ausdrucken) des
Studienheftes für persönliche Zwecke.

$
INM12

Grundlagen der Algorithmierung 


und Programmierung Teil 2
0619K03

Dr. Inge Adamski


Dr. Bernd Hetze
©

Werden Personenbezeichnungen aus Gründen der besseren Lesbarkeit nur in der männlichen oder
weiblichen Form verwendet, so schließt dies das jeweils andere Geschlecht mit ein.
Falls wir in unseren Studienheften auf Seiten im Internet verweisen, haben wir diese nach sorgfältigen
Erwägungen ausgewählt. Auf die zukünftige Gestaltung und den Inhalt der Seiten haben wir jedoch
keinen Einfluss. Wir distanzieren uns daher ausdrücklich von diesen Seiten, soweit darin rechtswid-
rige, insbesondere jugendgefährdende oder verfassungsfeindliche Inhalte zutage treten sollten.
© HfB, 02.12.20, Berk, Eric (904709)

Grundlagen der Algorithmierung 


INM12

und Programmierung Teil 2

Inhaltsverzeichnis
0619K03

Vorwort ....................................................................................................................... 1

1 Datentyp Zeiger ........................................................................................................ 3


1.1 Zur Motivation ............................................................................................ 3
1.2 Grundlagen zur Nutzung des Datentyps Zeiger ...................................... 5
1.3 Null-Zeiger, Operationen mit Zeigern, Zeigerarithmetik ....................... 10
1.3.1 Null-Zeiger .................................................................................................. 10
1.3.2 Operationen mit Zeigern ............................................................................ 10
1.3.3 Adressrechnungen ...................................................................................... 11
Zusammenfassung .................................................................................................... 12
Aufgaben zur Selbstüberprüfung ............................................................................ 13

2 Zeiger zur Umsetzung des Modulkonzepts ......................................................... 14


2.1 Parameterübergabe an Funktionen über Zeiger ...................................... 14
2.2 Rekursive Funktionsaufrufe ....................................................................... 15
2.3 Nutzung modulglobaler Variabler ............................................................. 18
2.4 Verwendung von Kommandozeilenargumenten ..................................... 19
2.5 Zeiger auf Funktionen ................................................................................ 21
Zusammenfassung .................................................................................................... 23
Aufgaben zur Selbstüberprüfung ............................................................................ 24

3 Zeiger, Felder und Zeichenketten .......................................................................... 25


3.1 Felder ............................................................................................................ 25
3.2 Zeichenketten .............................................................................................. 28
3.3 Felder und Zeiger ........................................................................................ 29
3.4 Zeichenketten und Zeiger .......................................................................... 31
3.5 Felder als Funktionsparameter ................................................................... 33
3.6 Datenstruktur Datensatz ............................................................................ 36
Zusammenfassung .................................................................................................... 38
Aufgaben zur Selbstüberprüfung ............................................................................ 39
0619K03

INM12
© HfB, 02.12.20, Berk, Eric (904709)

Inhaltsverzeichnis

4 Dynamische Speicherverwaltung .......................................................................... 40


4.1 Speicherkonzept ........................................................................................... 40
4.2 Bereitstellung von dynamischem Speicher ............................................... 41
4.2.1 Speicher mit malloc() reservieren und mit free() freigeben .................... 41
4.2.2 Die Funktionen calloc() und realloc() ........................................................ 43
4.2.3 Arbeiten mit dynamischen Datentypen .................................................... 47
4.2.4 Lineare Listen ............................................................................................... 47
4.2.5 Einfache Listenoperationen ........................................................................ 49
Zusammenfassung ..................................................................................................... 56
Aufgaben zur Selbstüberprüfung ............................................................................. 57

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ ......... 58


5.1 Spracherweiterungen .................................................................................. 58
5.1.1 Ein- und Ausgabe ........................................................................................ 58
5.1.2 Schlüsselwort „const“ .................................................................................. 59
5.1.3 Standardargumente ..................................................................................... 60
5.1.4 Überladen von Funktionen ......................................................................... 61
5.1.5 Referenzen .................................................................................................... 62
5.2 Objektorientierung als Programmieransatz .............................................. 63
5.3 Klassendefinition, Konstruktoren, Destruktor .......................................... 64
5.3.1 Standardkonstruktor .................................................................................... 65
5.3.2 Explizite Konstruktordefinition .................................................................. 67
5.3.3 Initialisierungslisten .................................................................................... 69
5.3.4 Verwendung von Konstruktoren mit Standardparametern ..................... 70
5.3.5 Arbeit mit mehreren Dateien ..................................................................... 72
5.4 Methoden ...................................................................................................... 74
5.5 Überladen von Operatoren ......................................................................... 76
5.6 Vererbung ..................................................................................................... 78
5.7 Klassenhierarchien ...................................................................................... 86
Zusammenfassung ..................................................................................................... 91
Aufgaben zur Selbstüberprüfung ............................................................................. 91

Anhang
A. Lösungen der Aufgaben zur Selbstüberprüfung ....................................... 93
B. Literaturverzeichnis ..................................................................................... 110
C. Abbildungsverzeichnis ................................................................................ 111
D. Quellcodeverzeichnis ................................................................................... 112
E. Einsendeaufgabe Typ A .............................................................................. 115

INM12
© HfB, 02.12.20, Berk, Eric (904709)

Vorwort
INM12Grundlagen der Algorithmierung und Programmierung Teil 20619K03

Die Verknüpfung von Ingenieurwissenschaften und Informatik prägt den Problem-


lösungsprozess in vielen Bereichen.
Nachdem im Studienheft INM11 grundlegend die Entwicklung und Darstellung von Al-
gorithmen und Datenstrukturen sowie deren Umsetzung in der Programmiersprache C
betrachtet wurden, liegt der Schwerpunkt des vorliegenden Heftes in der Nutzung aus-
gewählter Datentypen auf Basis der Programmiersprache C und stellt die praktische
Programmierung in den Fokus. Zentrales Element ist dabei der Datentyp Zeiger und
dessen Nutzung. Mit Zeigern wird es möglich, sehr universelle dynamische Datentypen
zu benutzen. Das prinzipielle Vorgehen wird am Beispiel der linearen Liste gezeigt.
Die Programmiersprache C wurde in den 1970er-Jahren von K. Thompson und
D. M. Ritchie für die Entwicklung von Betriebssystemen konzipiert. Bei der späteren
Entwicklung weiterer Sprachen, wie z. B. C++, C#, Java, Python, wurde bewusst an Syn-
tax und Eigenschaften von C angeknüpft.
Die Auseinandersetzung mit C liefert daher sehr gute Voraussetzungen, sich später
anderen Programmiersprachen zuzuwenden.
Wohl am engsten mit C verbunden ist die Programmiersprache C++, die durch das Hin-
zufügen von Elementen der objektorientierten Programmierung entstand. Ein Entwick-
lungsziel bestand darin, C++ weitgehend kompatibel mit C zu entwickeln. C++ erweitert
das imperative, strukturierte und prozedurale Programmierkonzept von C zusätzlich um
die Konzepte der objektorientierten und generischen Programmierung.
Um die Prinzipien der objektorientierten Programmierung zu vermitteln, benutzen wir
die Sprache C++. Im letzten Teil des Heftes erfolgt ein kurzer Abriss zur Objekttechno-
logie. Damit werden die Voraussetzungen für ein tieferes Einarbeiten in das objekt-
orientierte Programmieren im Selbststudium bereitgestellt.
In der Regel lassen sich C-Programme auch unter C++ ohne Probleme bearbeiten. Sie
können daher auf die gleiche Entwicklungsumgebung zurückgreifen. Der Compiler bzw.
die Entwicklungsumgebung ist ein Werkzeug und als solches austauschbar. Auch für die
Bearbeitung der Quelltexte dieses Heftes empfehlen wir die Softwareentwicklungs-
umgebung Orwell Dev-C++.
Nach dem Studium dieser Materialien und dem Bearbeiten der Übungsprogramme wer-
den Sie in der Lage sein, eigene Problemlösungsideen und Algorithmen in anspruchs-
volle C/C++-Programme umzusetzen, diese vom Compiler übersetzen zu lassen und am
Rechner auszutesten.
Wir empfehlen Ihnen, alle Programmierbeispiele am Rechner zu bearbeiten. Beginnen
Sie zuerst mit der Bearbeitung der Beispielquelltexte und wenden Sie sich dann den
Aufgaben zur Selbstüberprüfung zu. Programmierpraxis erwirbt man nur durch selbst-
ständiges Problemlösen am Computer.
Auch beim Studium des Studienhefts wünschen wir Ihnen viel Spaß und Erfolg.
Ihre Studienleitung

INM12 1
© HfB, 02.12.20, Berk, Eric (904709)

2 INM12
© HfB, 02.12.20, Berk, Eric (904709)

1 Datentyp Zeiger
Die hervorgehobene Stellung der Programmiersprache C (bzw. C++) bei Anwen-
dern beruht unter anderem auf der Existenz von Sprachmitteln zur systemnahen
Programmierung. In C wird sehr effektiv und nutzerfreundlich der Zugriff auf
Bits und Bytes oder Adressen des Hauptspeichers unterstützt. Es ist möglich,
nicht nur mit den Daten selbst, sondern auch mit Verweisen auf diese zu arbeiten.
Die Grundlagen für ein solches Arbeiten werden durch den Datentyp Zeiger
bereitgestellt.
Mit diesem Ansatz besteht die Möglichkeit, auf zwei Wegen auf eine Variable
zuzugreifen, zum einen „klassisch“ über die Angabe des Variablennamens (Be-
zeichner) und zum anderen über die Adresse dieser Variablen, d. h. über die
Angabe der Abspeicherungsposition im Speicher.
Viele Algorithmen lassen sich unter Nutzung des Zeigerkonzeptes laufzeiteffi-
zienter implementieren. Mithilfe von Zeigern können komplizierte Datenstruktu-
ren einfach aufgebaut werden. Strukturen, die sich dynamisch ändern, lassen
sich ausschließlich mit dieser Technik realisieren.
Das kommende Kapitel greift die im Studienheft INM11 vermittelten Sprach-
grundlagen auf und behandelt die Grundprinzipien zur Arbeit mit Zeigern in
verschiedenen typischen Anwendungsfällen.
Nach dem Durcharbeiten dieses Kapitels werden Sie in der Lage sein, diese
Kenntnisse im Rahmen kleiner Projekte umzusetzen.

1.1 Zur Motivation


Das Vertauschen der Werte zweier Variablen ist ein Teilalgorithmus, der oft in der prak-
tischen Programmierung verwendet werden muss. Im Heft INM11 wurde deshalb in
Kapitel 4 der Modul swap zum Vertauschen zweier ganzer Zahlen konzipiert, dessen
Moduldefinition hier nochmals angegeben wird.

Beispiel 1.1:

swap (x,y)
input/output x,y
{vertauscht x und y mithilfe von hilf}

hilf:=x

x:= y

y:= hilf

Abb. 1.1: Struktogramm zum Modul swap(…)

INM12 3
© HfB, 02.12.20, Berk, Eric (904709)

1 Datentyp Zeiger

Basierend auf den Kenntnissen des Abschnitts „Das Modulkonzept von C – Funk-
tionen in C“ aus INM11 soll diese Funktion swap implementiert und in einer main-
Funktion genutzt werden. Dafür sind die Schritte Deklaration, Definition und Auf-
ruf der Funktion umzusetzen. Daraus ergibt sich der folgende Quelltext:

1 // Programm Tauschen.c
2 #include<stdio.h>
3 void swap (int x, int y);
4
5 int main(){
6 int zahl1=10,zahl2=20;
7 printf("Werte vor Aufruf von swap:\n");
8 printf("zahl1: %d, zahl2: %d\n",zahl1,zahl2);
9 swap(zahl1,zahl2);
10 printf("Werte nach Aufruf von swap:\n");
11 printf("zahl1: %d, zahl2: %d\n",zahl1,zahl2);
12
13 return 0;
14 }
15
16 void swap (int x, int y){
17 int hilf;
18 hilf=x;
19 x=y;
20 y=hilf;
21 }

Code 1.1: Programm Tauschen.c

Erläuterung:
Zeile 3: Vor Nutzung der Funktion wird diese durch die Deklarationsan-
weisung dem Compiler bekannt gemacht. Neben dem kompletten
Funktionskopf ist es auch möglich, nur die entsprechenden Typen
anzugeben, da der Compiler keine Bezeichner überprüft.
Zeilen 5–14: Die Verwendung der Funktion erfolgt in der main-Routine. In
Zeile 6 werden die Ganzzahlvariablen zahl1 und zahl2 ver-
einbart und gleichzeitig initialisiert. Vor und nach dem Aufruf der
Funktion swap werden jeweils die Variablenwerte protokolliert.
Zeilen 16–21: Hier erfolgt die Definition der Funktion swap. Als Eingabegrößen
finden die beiden formalen Parameter x und y vom Typ int
Verwendung. In Zeile 18 wird der lokale Bezeichner hilf bereit-
gestellt. Mit diesen drei Variablen erfolgt das Tauschen der Variab-
lenwerte.
Betrachtet man die Ausgabe des Programms, erkennt man jedoch, dass keine
Werteveränderung erfolgt.
Ausgabe:
Werte vor Aufruf von swap:
zahl1: 10, zahl2: 20
Werte nach Aufruf von swap:
zahl1: 10, zahl2: 20

4 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Datentyp Zeiger 1

Die Erklärung ergibt sich aus der in C festgelegten Art der Parameterübergabe.
Variable vom einfachen Datentyp werden als Parameter mit dem Prinzip „call by value“
übergeben (es wird mit einer Kopie der Variablen gearbeitet). Die Funktion kann nur le-
send, aber nicht schreibend auf solche Parameter zugreifen und nur ein Ergebnis über
den Funktionsnamen mithilfe der return-Anweisung zurückgeben. Diese Rückgabe-
funktionalität fehlt im dargestellten Beispiel, da sie hier nicht geeignet ist. Der Rückga-
betyp einer solchen Funktion, die, wie in unserem Beispiel keinen Wert zurückgibt, ist
daher mit void (leer) anzugeben.

Zusammenfassend kann festgestellt werden:

Die Werte der aktuellen Parameter zahl1 und zahl2 werden bei der Übergabe an
die Funktion swap in die formalen Parameter x und y kopiert und nur innerhalb
von swap vertauscht. Es erfolgt keine Rückgabe von Ergebnissen an die aufrufende
Programmeinheit main.
Um dieses Problem zu lösen, muss die Parameterübergabe an die Funktion nach dem
auch in C bereitstehenden Prinzip „call by reference“ erfolgen. Man arbeitet hier mit
Adressen und Zeigern.
Neben der Übergabe von Adressen von Datenobjekten an Funktionen werden Zeiger in
C zur Übergabe von Funktionen als Argument für andere Funktionen, zur Arbeit mit
Feldern, zur dynamischen Verwaltung von Speicherbereichen sowie zum Aufbau kom-
plexer Datenstrukturen genutzt.
Viele Programme in C und Klassen in C++ sind ohne Zeiger nicht umsetzbar. Allerdings
ist fehlerhafte Zeigerprogrammierung auch ein häufiger Grund für Programmierfehler.
Stellen wir daher die exakte Zeigernutzung in den Mittelpunkt der folgenden Betrach-
tungen.

1.2 Grundlagen zur Nutzung des Datentyps Zeiger


Eine in der Programmiersprache C verwendete Variable wird im Speicher als zusam-
menhängende Folge von einem oder mehreren Bytes abgespeichert. Sie wird immer über
die Adresse des ersten Bytes adressiert. Somit ist es möglich, über eine (Anfangs-)Adres-
se im Speicher diese Variable eindeutig zu identifizieren.
Diese Variable besitzt somit einen Wert, der auf dem angegebenen Speicherbereich
codiert ist. Diese Codierung hängt vom Typ des Wertes ab, wobei neben einfachen
Datentypen auch zusammengesetzte und strukturierte Datentypen möglich sind.

Adresse der Wert der


Variablen v Variablen v

Abb. 1.2: Zuordnung Adresse und Wert einer Variablen

INM12 5
© HfB, 02.12.20, Berk, Eric (904709)

1 Datentyp Zeiger

Sie erinnern sich: Um Zeiger zu benutzen, werden folgende drei Schritte notwendig:

1. Eine Variable vom Datentyp Zeiger/Pointer zur Aufnahme der Speicheradres-


se ist bereitzustellen. Bei der Deklaration dieser Zeigervariablen mithilfe des
Indirektionsoperators ist der Basisdatentyp, für den der Zeiger benutzt werden
soll, anzugeben. Wird ein Zeiger für eine int-Variable vereinbart, ergibt sich
der Datentyp int* (Zeiger auf int oder kurz int-Zeiger). Entsprechende
Datentypen sind float*, double*, char* usw.
2. Diese Zeigervariable ist zu initialisieren, d. h., die zu verwendende Adresse, auf
die der Zeiger gerichtet wird, ist zuzuweisen. Dies geschieht in der Regel über
den Adressoperator &. Er liefert einen Zeiger/Pointer auf die entsprechende
Variable, die selbst aber auch wieder ein Zeiger sein kann. Eine weitere Möglich-
keit der Initialisierung besteht in der Zuweisung des Wertes eines bereits gültig
definierten Zeigers.
3. Der Speicherzugriff auf die somit beschriebene Variable wird zusätzlich zum
Zugriff über den Variablennamen, jetzt auch über den initialisierten Zeiger und
damit verbunden, durch den direkten Zugriff auf eine Adresse möglich. Dazu
benutzt man den Dereferenzoperator *. Dieser liefert den Wert aus dem
Speicherbereich zurück, auf den der Zeiger verweist.

Die folgenden Quelltexte fassen diese Prinzipien nochmals zusammen. Die notwendigen
Programmierschritte sind jeweils als Kommentar angefügt:

Beispiel 1.2:

1 //Programm Zeiger.c
2 #include<stdio.h>
3
4 int main(){
5 int var;
6 int *ptr;
7 var=121;
8 ptr=&var;
9 printf("%i\n",*ptr);
10 return 0;
11 }

Code 1.2: Programm Zeiger.c

Erläuterung:
Zeile 5: Definition der Variablen var vom Typ int.
Zeile 6: Definition des Zeiger ptr vom Typ int*. Als Werte können Adressen
von int-Variablen zugewiesen werden.
Zeile 8: Der Zeiger ptr referenziert die Variable var. Er erhält als Wert die
Adresse der Variablen var zugewiesen. Dazu wird der Adressoperator (&)
genutzt.

6 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Datentyp Zeiger 1

Zeile 9: Der Wert der Variablen, die im Adressbereich steht, auf die der Zeiger ptr
zeigt, wird ausgegeben. Dazu ist der Dereferenz-Operator (*) anzuwenden.

Die Programmiersprache C bietet ein sehr flexibles Zeigerkonzept. Unter anderem steht
auch die Konstruktion Zeiger auf Zeiger zur Verfügung. Abb. 1.3 illustriert das
Beispiel Zeiger.c in grafischer Form. Eine solche Konstruktion wird u. a. benutzt, um
Parameter an die main-Funktion zu übergeben.

Beispiel 1.3:

pptr Zeiger auf Zeiger

ptr Zeiger

var Variable

121 Wert

Abb. 1.3: Zeiger auf Zeiger

1 //Programm Zeiger_auf_Zeiger.c
2 #include<stdio.h>
3
4 int main(){
5 int var;
6 int *ptr;
7 int **pptr;
8 var=121;
9 ptr=&var;
10 pptr=&ptr;
11 printf("%i\n", **pptr);
12 return 0;
13 }

Code 1.3: Programm Zeiger_auf_Zeiger.c

Erläuterung:
Zeile 5: Definition der Variablen var vom Typ int.
Zeile 6: Definition des Zeiger ptr vom Typ int*. Als Werte können Adressen
von int-Variablen zugewiesen werden.
Zeile 7: Definition des Zeiger pptr vom Typ int**. Als Werte können Adressen
vom Typ int* zugewiesen werden.

INM12 7
© HfB, 02.12.20, Berk, Eric (904709)

1 Datentyp Zeiger

Zeile 9: Der Zeiger ptr referenziert die Variable var. Er erhält als Wert die
Adresse der Variablen var zugewiesen.
Zeile 10: Der Zeiger pptr referenziert die Zeiger-Variable ptr. Er erhält als Wert
die Adresse der Variablen ptr zugewiesen.
Zeile 11: Der Wert der Variablen, die im Adressbereich steht, auf die der
Zeiger pptr zeigt, wird ausgegeben. Dazu ist der Dereferenz-Operator (*)
zweifach anzuwenden.
Achtung:
Wird im Programm ein Zeiger verwendet, der zuvor nicht initialisiert wurde, kann
dies zu schwerwiegenden Fehlern führen. Deshalb muss ein Zeiger immer erst posi-
tioniert werden (im Beispiel: ptr=&var;), bevor auf die Speicheradresse zugegrif-
fen werden kann (z. B. mit *ptr), um den damit adressierten Wert zu benutzen.

Beispiel 1.4:
Das folgende Beispiel soll dazu dienen, den beschriebenen Formalismus der Zeiger-
nutzung weiter zu festigen.
Zwei int-Variable, zwei char-Variable und jeweils pro Datentyp eine Zeigervari-
able werden betrachtet.
Zum besseren Verständnis des Algorithmus erfolgt eine Werteprotokollierung in
einer Durchlauftabelle. Zusätzlich steht das Ausgabebild bereit.

// Programm Zeigernutzung.c
#include <stdio.h>
int main(){
int int_z1, int_z2, *zeig_int;
char bu_1, bu_2, *zeig_bu;
int_z1=7;
int_z2=-2;
bu_1='c';

zeig_int=&int_z2;
int_z2+=*zeig_int+1;
printf("%i\n",*zeig_int);
zeig_bu=&bu_1;
bu_2=*zeig_bu;
printf("%c\n",bu_2);

*zeig_int=*zeig_int+int_z1;
printf("%i\n",*zeig_int);
int_z2=int_z1* *zeig_int;
printf("%i\n",int_z2);
(*zeig_int)++;
printf("%i\n",*zeig_int);

return 0;
}

Code 1.4: Programm Zeigernutzung.c

8 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Datentyp Zeiger 1

Mit int_z1=7; int_z2=-2; bu_1='c'; erhalten diese drei Variablen ihre Anfangs-
wertbelegung.
Mit zeig_int=&int_z2; wird mit dem Zeiger zeig_int die Variable int_z2
referenziert.
Durch int_z2+=*zeig_int+1 wird ein neuer Variablenwert berechnet. Die zusam-
mengesetzte Zuweisung lässt sich in int_z2=int_z2+(*zeig_int+1) auflösen. Da-
raus ergibt sich der Wert der rechten Seite der Zuweisung aus der Summe aus dem Wert
der Variablen, auf die zeig_int verweist (*zeig_int=-2), addiert um 1, und dem
alten Wert der Variablen int_z2 selbst. Das Ergebnis ist – 3 (int_z2=-2+(-2+1))
und wird in einer neuen Zeile ausgegeben.
In den nächsten Anweisungen werden die Zeichen verarbeitet.
Mit zeig_bu=&bu_1; wird der Zeiger zeig_bu initialisiert. Der Wert der dazu-
gehörigen Variablen (bu_1='c') wird der Variablen bu_2 zugewiesen.
(bu_2=*zeig_bu;). Zur Ausgabe kommt auch hier nach Zeilenschaltung, aus der
vorangegangenen Ausgabeanweisung, das Zeichen 'c'.
Im letzten Teil werden wieder Ganzzahlvariable bearbeitet. Der Inhalt der Speicherzelle,
auf die der Zeiger zeig_int verweist (Inhalt: *zeig_int), wird aus dessen altem
Wert, addiert mit dem Variablenwert int_z1, neu berechnet. Im Ergebnis entsteht 4.
Es erfolgt die Ausgabe dieses Wertes.
Weiter wird nun der Wert der Variablen int_z2 neu belegt. Dieser entsteht aus dem
Produkt des Wertes von int_z1 und dem Wert, der sich aus dem Dereferenzieren des
Zeigers zeig_int ergibt (Dereferenzieren: *zeig_int). Als Ergebnis erhält
man int_z2=7*4=28. Dieser Wert wird wiederum ausgegeben.
Die letzte Ausgabe wird durch die postfix verwendete Addition auf den Inhalt der von
zeig_int adressierten Speicherzelle (Inhalt: *zeig_int) bestimmt. Dieser Wert er-
gibt sich zu 29.
Um diesen für den C-Neuling relativ komplizierten Sachverhalt effektiv darzustellen,
wählen wir das Mittel der Durchlauftabelle. Beim Erstellen einer Durchlauftabelle wird
das Programm entsprechend den Wertebelegungen „Anweisung für Anweisung abgear-
beitet“. Dabei erfolgt bei einer Veränderung der Werte der Variablen ein Protokollein-
trag.

Werteverhalten:

int_z1 int_z2 bu_1 bu_2 zeig_int1 zeig_int2

7 -2 c c int_z2 bu_1
-3
4
28
29

INM12 9
© HfB, 02.12.20, Berk, Eric (904709)

1 Datentyp Zeiger

Ausgabe:

-3
c
4
28
29

Zusammenfassung:

Ist ptr ein Zeiger und var eine Variable und zeigt ptr auf var und ptr reprä-
sentiert eine gültige Adresse, so ist *ptr wie var selbst zu verwenden (*ptr ist die
Variable, die ptr referenziert).

1.3 Null-Zeiger, Operationen mit Zeigern, Zeigerarithmetik


1.3.1 Null-Zeiger
Ein besonderer Zeiger mit dem Wert 0 wird Null-Zeiger genannt. Dieser Zeiger zeigt
auf „nichts“. Da in C alle Objekte eine von null verschiedene Adresse besitzen, repräsen-
tiert der Null-Zeiger eine ungültige Adresse. Dieser Zeiger wird somit als „unini-
tialisiert“ markiert. Er wird benutzt, um den erfolglosen Abschluss von Zeigeraktionen
anzuzeigen. Mit der dafür benutzten Konstanten NULL kann ein Vergleich von Adres-
sen durchgeführt werden.
Der Null-Zeiger ist ebenso unverzichtbar, wenn mithilfe von Zeigern Datenelemente
untereinander verkettet werden. Algorithmen, die diese Ketten durchlaufen, können nur
am Null-Zeiger das Ende einer Kette erkennen.

1.3.2 Operationen mit Zeigern


Auch für den Datentyp Zeiger sind Operationen definiert. Zeiger können einander zu-
gewiesen werden und lassen sich logisch vergleichen.

Beispiel 1.5:
Im Beispiel werden zwei Zeiger initialisiert. zeig_index wird initialisiert mit der
Adresse von index. zeig_wert erhält den gerade besprochenen NULL-Zeiger zu-
gewiesen.
Sind beide Zeigerwerte nicht gleich, was im vorliegenden Fall gilt, wird dem
Zeiger zeig_wert der Zeiger zeig_index zugewiesen. Das bedeutet, beide Zei-
ger enthalten die gleiche Adresse, hier die der Variablen index, und repräsentieren
den gleichen Inhalt.
Bei der Zuweisung von Zeigern und deren Vergleich sollten sich diese auf Objekte
des gleichen Datentyps beziehen, da sonst Adressfehler zu befürchten sind.

// Programm Zeigervergleich.c
#include <stdio.h>
int main(){
int index;
int *zeig_index=&index;
int *zeig_wert=NULL;

10 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Datentyp Zeiger 1

if (zeig_index != zeig_wert) zeig_wert=zeig_index;


*zeig_index=15;
printf("\nWerte:\t\t%i\t\t\t%i\n",*zeig_index,*zeig_wert);
printf("Adressen:\t%p\t%p\n",zeig_index,zeig_wert);
return 0;
}

Code 1.5: Programm Zeigervergleich.c

Ausgabe:
Werte: 15 15
Adressen: 0028FEA4 0028FEA4

Die hier ausgegebenen Adresswerte sind beispielhaft aufzufassen!

1.3.3 Adressrechnungen
Zeiger werden systemintern wie unsigned-Variablen dargestellt und besitzen entspre-
chend ihrem referenzierten Datentyp ein Längenattribut.
Die Adressarithmetik gestattet:
• Addition ganzer Zahlen zum Zeigerwert:
Der Zeiger wird um eine ganze Zahl von Bytes, entsprechend dem Datentyp, vor-
wärtsgesetzt.
• Subtraktion ganzer Zahlen vom Zeigerwert:
Der Zeiger wird um eine ganze Zahl von Bytes, entsprechend dem Datentyp, zu-
rückgesetzt.
• Subtraktion von zwei Zeigern:
Die Differenz zweier Zeiger entspricht der Anzahl von Bytes, bezogen auf den
Datentyp, die sich zwischen beiden Zeigern befinden.
• Vergleich von zwei Zeigern:
Es kann festgestellt werden, ob zwei Zeiger identisch sind (gleiche Größe = gleicher
Datentyp) oder ob ein Zeiger größer oder kleiner ist als der andere.
• Explizite Zeigerumwandlungen:
Zur Absicherung einer korrekten Adressrechnung sollte die explizite Zeigerum-
wandlung wie in der folgenden Art benutzt werden: 
p_char = (char *)p_int;
Obwohl es in C möglich ist, Größen vom Typ char über den Platzhalter %c als
Zeichen und über den Platzhalter %i als Zahlenwert entsprechend ihrer Stellung in
der ASCII-Tabelle auszugeben, führt eine Zuweisung der Speicherplatzadresse an
eine Zeigervariable des anderen Types (p_char=p_int) in der Regel zu einem
Fehler:
error C2440: '=': 'int *' kann nicht in 'char *' konvertiert
werden

INM12 11
© HfB, 02.12.20, Berk, Eric (904709)

1 Datentyp Zeiger

Beispiel 1.6: Beispiele zur Zeigerarithmetik


int *pi;
double *pd;
char *pc;
...
pi=pi+5;
pd+=3;
pc=pc-4;
pi++;
pc++;
pd--;
...

Erklärung zu den Beispielen:

Zuerst werden die drei Zeiger pi, pd und pc deklariert. Bei einer typischen Speicher-
breite (s. INM01, Kapitel 2) von 4 Byte für int und 8 Byte bei double bedeutet dies:
Mit pi+5 wird um 5 · 4 Byte, bei pd 3 · 8 Byte im Speicher vorwärtsgegangen. Für pc
erfolgt ein Rückwärtsgehen des Zeigers um 4 Byte.
pi++ bedeutet ein Vorwärtsgehen um 4 Byte zur nächsten int-Adresse (Inkrementie-
ren), pc++ das Vorwärtsgehen um 1 Byte zur nächsten char-Adresse und pd-- ein
Rückwärtsgehen (Dekrementieren) um 8 Byte zur voranliegenden double-Adresse.
Die Adressarithmetik gestattet nicht:
• die Addition von zwei Zeigern,
• eine Addition oder Subtraktion von float- oder double-Werten zum Zeigerwert,
• die Anwendung von Multiplikation und Division auf Zeiger.

Zusammenfassung

Der Datentyp Zeiger spielt in der Programmiersprache C eine zentrale Rolle. Er er-
möglicht einerseits die Laufzeiteffektivierung von Programmen, kann aber auch bei un-
sauberer, nicht sorgfältiger Programmierung zu Problemen mit fehlerhaftem Pro-
grammcode führen.
Nachdem Sie in diesem Kapitel die grundlegenden Elemente der Zeigernutzung kennen-
gelernt haben, werden Sie in den folgenden Lerneinheiten typische Anwendungsfälle
bei der praktischen Nutzung von Zeigern kennenlernen.

12 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Datentyp Zeiger 1

Aufgaben zur Selbstüberprüfung

1.1 Welche Ausgabe erfolgt in folgendem Programm?


//Aufgabe 1.1
# include <stdio.h>
int main(){
int i=7,*z_int;
z_int=&i;
printf("\n\n%i ",*z_int);
printf("\n\n%i ",*z_int+2);
printf("\n\n%i ",*(&i));
printf("\n\n%i ",(*z_int)++);
return 0;
}

1.2 Schreiben Sie ein Programm, das zwei float-Werte einliest, diese addiert und das
Ergebnis ausgibt.
Dabei sollen für die Formulierung der Addition als Operanden keine einfachen
Variablen, sondern Zeiger verwendet werden!
1.3 Erkennen Sie den schwerwiegenden Fehler im folgenden Programm:
//fehlerhaft
#include <stdio.h>
int main(){
int int_zahl1,int_zahl2,*int_zeiger;
int_zahl1=12345;
*int_zeiger=int_zahl1;
int_zahl2=*int_zeiger;
printf("\n%i %i",int_zahl1, int_zahl2);
return 0;
}

INM12 13
© HfB, 02.12.20, Berk, Eric (904709)

2 Zeiger zur Umsetzung des Modulkonzepts


Anspruchsvolle Softwarelösungen sind modular aufgebaut und ohne Modula-
risierung, d. h. ein Zerlegen in Softwareteile, nicht vorstellbar.
Die Theorie der Programmiersprachen benutzt dafür u. a. die Unterprogramm-
technologie. Inhaltlich abgrenzbare Problemlösungsschritte werden dabei in
selbstständige Programmeinheiten zusammengefasst.
Man unterscheidet in C formal zwischen Funktionen (hier werden Ergebnisse
über die return-Anweisung vermittelt) und Prozeduren (mit dem Schlüsselwort
void wird festgelegt, dass die Funktion keinen Rückgabewert liefert). Programm-
technisch werden beide Formen als Funktion realisiert.
Im folgenden Kapitel betrachten wir verschiedene Beispiele der Zeigernutzung
bei der Umsetzung des Sprachmittels Funktion mit dem Ziel, Ihnen auch hier
sichere Kenntnisse für die Anwendung zu vermitteln.

2.1 Parameterübergabe an Funktionen über Zeiger


Wenden wir uns nochmals unserem Beispiel Tauschen.c in Abschnitt 1.1 zu. Es wur-
de gezeigt, dass in C die Parameter als Werte an die Funktion übergeben werden. Das
bedeutet, dass die Inhalte der Variablen, die an die Funktionen übermittelt wurden, nicht
innerhalb der Funktion verändert werden können.
Um unser Problem zu lösen, müssen Zeiger als Funktionsparameter benutzt werden.
Man nennt dieses Prinzip der Werteübergabe „call by reference“. Die Funktion wird
dabei mit den Adressen der aktuellen Parameter aufgerufen. So kann auf die Variable,
deren Adresse übergeben wurde, zugegriffen und deren Inhalt geändert werden. Diesen
Gedanken finden Sie im Beispiel Tauschen_mit_Zeigern.c umgesetzt.

Beispiel 2.1:

//Programm Tauschen_mit_Zeigern.c
//Parameteruebergabe mit call by reference
#include <stdio.h>
void swap (int *a,int *b);

int main(){
int zahl1,zahl2;
zahl1=10;
zahl2=20;
printf("\nAusgangswerte: %2i\t%2i",zahl1,zahl2);
swap(&zahl1,&zahl2);
printf("\nErgebniswerte: %2i\t%2i",zahl1,zahl2);
return 0;
}

14 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger zur Umsetzung des Modulkonzepts 2

void swap(int *a,int *b){


int vhilf;
vhilf=*a;
*a=*b;
*b=vhilf;
}

Code 2.1: Tauschen_mit_Zeigern.c

Der Prototyp void swap(int *a,int *b); zeigt bereits an, dass Argumente der
Funktion vom Typ "Zeiger auf int" verwendet werden. Der Aufruf der Funk-
tion erfolgt mit den Adressen der Funktionsargumente swap(&zahl1,
&zahl2);. Das hat zur Folge, dass main und swap auf die gleichen Speicher-
plätze zugreifen.
Betrachtet man die Definition der Funktion und deren Aufruf, erkennt man die gül-
tige Initialisierung von Zeigern, die hier mithilfe des Adressoperators erfolgt.

Definition: void swap(a,b)

Aufruf: swap(&zahl1,&zahl2);

Abb. 2.1: Parametervermittlung „call by reference“

Durch die Parameterzuordnung gilt entsprechend: a = &zahl1, b = &zahl12.


Ausgabe:
Ausgangswerte: 10 20
Ergebniswerte: 20 10

Zusammenfassung:

Sollen durch eine C-Funktion mehr als ein Ergebnis an die aufrufende Programmeinheit
zurückgegeben werden, sind Zeiger als Funktionsparameter zu benutzen.
Eine weitere Anwendungsmöglichkeit, einen Zeiger als Parameter zu nutzen, besteht
dann, wenn größere Datenmengen übergeben werden sollen. Hier ist der Zeigereinsatz
unumgänglich. Auf diesen Aspekt kommen wir später zurück.

2.2 Rekursive Funktionsaufrufe


Aus der Mathematik bekannte rekursive Berechnungsvorschriften lassen sich in C durch
rekursive Funktionen umsetzen. Eine rekursive Funktion ruft sich selbst wieder auf, bis
eine Abbruchbedingung erfüllt ist.
Mithilfe der Rekursion lassen sich Algorithmen elegant und kurz darstellen. Allerdings
fällt ein sehr hoher interner Rechenaufwand an. Das Schlüsselproblem wird durch die
entstehende Rekursionstiefe gestellt. Die Parameter werden im Stack-Speicher (s. Ab-
schnitt 4.1) abgelegt. Ein wiederholter rekursiver Aufbau verlangt das Ablegen neuer
„privater“ Variablen auf dem Stack und damit auch das Abspeichern mehrerer Versionen
einer Variablen.

INM12 15
© HfB, 02.12.20, Berk, Eric (904709)

2 Zeiger zur Umsetzung des Modulkonzepts

Neben der Rekursionsvorschrift, die den rekursiven Aufruf steuert, muss auch eine
Abbruchbedingung zum Rekursionsende führen, sodass der Algorithmus terminiert.

Beispiel 2.2:
Ein klassisches Beispiel für die Programmierung der Rekursion ist sicherlich bei der
Berechnung der Fakultät n! = 1 * 2 * 3 * ... * n gegeben (s. INM11, Beispiel
in Kapitel 6).
Nach dem Wesen der rekursiven Nutzung wird die Funktion fakul im Algorith-
mus in sich selbst wieder aufgerufen. Als Ergebnis-Typ wurde long vereinbart.
Der Wert 12 dient als Ausgangsgröße für eine Beispielrechnung.
Die Rekursion lässt sich entsprechend dem in Abb. 2.2 dargestellten Schema ver-
folgen. Dabei wird für eine natürliche Zahl n deren Fakultätsberechnung nach der
Formel n! = n * (n-1)! realisiert. Zur Berechnung von (n-1)! dient die glei-
che Funktion fakul, die auch schon bei der Berechnung von n! verwendet wurde.
Am Ende der Rekursionskette steht 0!=1.

fakul(12)=
12*fakul(11)=
12*11*fakul(10)=
. . . =
12*11*10*9* . . . *2*fakul(1)=
12*11*10*9* . . . *2*1*fakul(0) mit fakul(0)=1

Abb. 2.2: Rekursionsablauf der Funktion fakul(…)

1 // Programm Fakultaet_1.c
2 #include <stdio.h>
3 long fakul(int zahl);
4
5 int main(){
6 int wert;
7 printf("\nFakultaetsberechnung fuer den Wert ");
8 scanf("%d",&wert);
9 printf("\nErgebnis: %d! = %ld",wert,fakul(wert));
10 return 0;
11 }
12
13 long fakul(int zahl){
14 long ergeb;
15 if(zahl>0)
16 ergeb=zahl*fakul(zahl-1);
17 else
18 ergeb=1;
19 return(ergeb);
20 }
21

Code 2.2: Programm Fakultaet_1.c

16 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger zur Umsetzung des Modulkonzepts 2

Erläuterung:
Zeilen 3, 12: Als Ergebnistyp der Funktion fakul wurde long int,
kurz long, festgelegt.
Zeilen 14, 15: Für Werte der Art zahl>0 erfolgt der Rekursionsaufruf.
Zeilen 16, 17: Für den Wert zahl=0 wird das Rekursionsende erreicht.

Beispiel 2.3:
Um den Mechanismus zwischen rekursivem Aufruf und Wertebereitstellung detail-
lierter darstellen zu können, wurden im Code 2.2 der Funktion fakul am Beginn
und Ende des Quelltextes Protokollausgaben hinzugefügt. In diesem Zusammen-
hang wurde die zur Funktion fakul lokale Variable ergeb zusätzlich definiert.

// Programm Fakultaet_2.c
#include <stdio.h>
long fakul(int zahl);

int main(){
int wert;
printf("\nFakultaetsberechnung fuer den Wert ");
scanf("%d",&wert);
printf("\nErgebnis: %d! = %ld",wert,fakul(wert));
return 0;
}

long fakul(int zahl){


long ergeb;
if(zahl>0)printf("zahl=%d:\t%d*fakul(%d)\n",
zahl,zahl,zahl-1);
if(zahl>0){
ergeb=zahl*fakul(zahl-1);
}
else
ergeb=1;
printf("Ergebnisrueckgabe fuer zahl=%d: %d\n",
zahl,ergeb);
return(ergeb);
}

Code 2.3: Programm Fakultaet_2.c

Man erhält bei Abarbeitung z. B. die folgende Ausgabe:


Fakultaetsberechnung fuer den Wert 6
zahl=6: 6*fakul(5)
zahl=5: 5*fakul(4)
zahl=4: 4*fakul(3)
zahl=3: 3*fakul(2)
zahl=2: 2*fakul(1)
zahl=1: 1*fakul(0)

INM12 17
© HfB, 02.12.20, Berk, Eric (904709)

2 Zeiger zur Umsetzung des Modulkonzepts

Ergebnisrueckgabe fuer zahl=0: 1


Ergebnisrueckgabe fuer zahl=1: 1
Ergebnisrueckgabe fuer zahl=2: 2
Ergebnisrueckgabe fuer zahl=3: 6
Ergebnisrueckgabe fuer zahl=4: 24
Ergebnisrueckgabe fuer zahl=5: 120
Ergebnisrueckgabe fuer zahl=6: 720

Ergebnis: 6! = 720

2.3 Nutzung modulglobaler Variabler


In C besteht die Möglichkeit des Gebrauchs globaler Variabler in einer Quellcodedatei.
Globale Deklarationen erfolgen außerhalb von Funktionen. Sie gelten daher für alle Pro-
grammeinheiten dieser Datei. Man kann auf diese Variablen von jeder Funktion aus mit
dem gleichen Variablennamen zugreifen.

Achtung:

Die Arbeit mit globalen Variablen birgt Gefahren in sich. Bei großen Programmen
bestehen die Gefahr von Unübersichtlichkeit und die Möglichkeit der ungewollten Ver-
änderung der Variablenwerte. Im Regelfall sollte man auf ihre Nutzung verzichten.

Beispiel 2.4:
Zur Umsetzung des bereits bekannten Vertauschungsalgorithmus werden die zwei
Variablen zahl1 und zahl2 global vereinbart. Die Parameterübergabe für die
Funktion ztausch entfällt, wie auch eine Ergebnisrückgabe. Der zu vereinbarende
Prototyp lautet daher: void ztausch (void).
Die Funktionen main und ztausch greifen auf die gleichen Speicherzellen
für zahl1 und zahl2 zu.

1 //Programm globale_Variable.c
2 #include <stdio.h>
3
4 void ztausch (void);
5 int zahl1,zahl2;
6
7 int main(){
8 zahl1=10;
9 zahl2=20;
10 printf("\nAusgangswerte: %i\t%i",zahl1,zahl2);
11 ztausch();
12 printf("\nErgebniswerte: %i%9i",zahl1,zahl2);
13 return 0;
14 }
15

18 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger zur Umsetzung des Modulkonzepts 2

16 void ztausch(void){
17 int vhilf;
18 vhilf=zahl1;
19 zahl1=zahl2;
20 zahl2=vhilf;
21 }

Code 2.4: Programm globale_Variable.c

Erläuterung:
Zeile 5: Globale Vereinbarung der Bezeichner zahl1 und zahl2.
Zeile 10: Der Abstand beider Ausgabewerte wird durch den Horizontaltabulator
(Backslash-Sequenz \t) gesteuert.
Zeile 12: Die Anordnung des zweiten Wertes erfolgt durch die Veränderung der
Feldbreite (%9i).
Ausgabe:
Ausgangswerte: 10 20
Ergebniswerte: 20 10

2.4 Verwendung von Kommandozeilenargumenten


Auch dem Hauptprogramm main() können beim Aufruf Argumente von der Kom-
mandozeile übergeben werden, um diese dann im Programm auszuwerten.
Dafür stehen vordefiniert eine int-Größe und ein Zeigervektor zur Verfügung.
Um die angegebenen Kommandozeilenparameter nutzen zu können, ist folgende
Schreibweise umzusetzen:

Nutzung von Kommandozeilenargumenten:

int main(int argc,char *argv[ ]){


...
return 0;
}

Dabei bedeuten:
int argc Anzahl der Parameter (mindestens einer) aus Zeigern auf Zeichen-
ketten
char *argv[ ] Zeiger auf ein Feld aus Zeigern auf Zeichen (d. h. ein Feld aus Zeigern
auf Zeichenketten)
Das erste vom Betriebssystem gelieferte Argument argc ist eine ganze Zahl, die der
Anzahl der gelieferten Kommandozeilenargumente entspricht. Dabei ist der Wert im-
mer um eins größer als die Anzahl der Argumente, da der erste Parameter immer den
Programmnamen enthält.

INM12 19
© HfB, 02.12.20, Berk, Eric (904709)

2 Zeiger zur Umsetzung des Modulkonzepts

Beispiel 2.5:
Das vorliegende Beispielprogramm listet die übergebenen Argumente zeilenweise
aus.

//Programm Kommandozeilenargumente_1.c
#include <stdio.h>

int main(int argc,char *argv[]){


int i;
printf("\nargc: %i",argc);
for(i=0;i<argc;i++)
printf("\nargv: %s",*(argv+i));
return 0;
}

Code 2.5: Kommandozeilenargumente_1.c

Wird das Programm ohne zusätzliche Argumente aufgerufen, ergibt sich z. B. folgen-
de Ausgabe:
argc: 1
argv: D:\Kommandozeilenargumente_1.exe

Bei Aufruf mit den Argumenten abc 12 d 4 etwa nach dem Betriebssystem-
Prompt in der Art >Kommandozeilenargumente_1 abc 12 d 4 oder durch
Eintrag in ein dafür vorgesehenes Fenster der Entwicklungsumgebung (beim
Dev-C++ zu finden unter Ausführen → Parameter) folgt beispielhaft die Ausgabe:
argc: 5
argv: D:\Kommandozeilenargumente_1.exe
argv: abc
argv: 12
argv: d
argv: 4

Beispiel 2.6:
Da es sich bei den übergebenen Argumenten um Zeichenketten handelt, muss der
Programmierer deren Aufbereitung, z. B. eine Umwandlung in Zahlenwerte, selbst
übernehmen. Dafür stehen u. a. die Funktionen atoi (String zu int), atol (String
zu long), atof (String zu double) in stdlib.h zur Verfügung. Allerdings wird
durch diese Funktionen keine Eingabeüberprüfung möglich. Mehr Möglichkeiten
bieten die ebenfalls in dieser Headerdatei vorhandenen Umwandlungsfunktionen
strtod (Konvertierung nach double), strtol (Konvertierung nach long)
oder strtoul (Konvertierung nach usigned), deren Aufruf jedoch aufwendiger ist.
Im Beispiel werden zwei Strings mit je einer ganzen Zahl als Argumente übergeben,
im Programm in int-Größen umgewandelt und deren Summe ausgegeben.

1 //Programm Kommandozeilenargumente_2.c
2 #include <stdio.h>
3 #include <stdlib.h>
4 int main(int argc, char *argv[]){
5 int zahl1, zahl2;

20 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger zur Umsetzung des Modulkonzepts 2

6
7 if(argc < 3) {
8 printf("Mindestens 2 Argumente eingeben!\n");
9 printf("Aufruf: %s <zahl1> <zahl2> ...\n",
10 *argv);
11 return -1;
12 }
13 zahl1=atoi(argv[1]);
14 zahl2=atoi(argv[2]);
15 printf("Die Summe der uebergebenen Zahlen %d",zahl1);
16 printf(" und %d betraegt: %d\n", zahl2, zahl1+zahl2);
17 return 0;
18 }

Code 2.6: Kommandozeilenargumente_2.c

Erläuterung:
Zeile 3: Die Funktion atoi verlangt das Einbinden von stdlib.h.
Zeilen 7–11: Die Anzahl der übergebenden Parameter wird geprüft. Ist daraus zu
schließen, dass zu wenige Werte für das Programm bereitgestellt
wurden, wird das Programm mit dem Rückgabecode -1 beendet.
Zeilen 13, 14: Die übergebenen Parameter argv[1] und argv[2] werden
nach int konvertiert.
Zeilen 15, 16: Aus drucktechnischen Gründen wird die Ergebnisausgabe auf zwei
separate Ausgabeanweisungen verteilt. Es entsteht dennoch nur eine
Ausgabezeile.
Werden beim Programmaufruf weniger als zwei Argumente übergeben, ergibt sich
als Ausgabe:
Mindestens 2 Argumente eingeben!
Aufruf: D:\Kommandozeilenargumente_2.exe <zahl1> <zahl2> ...

Für einen Aufruf mit wenigstens zwei Argumenten folgt als Ausgabe:
Die Summe der uebergebenen Zahlen 8 und 2 betraegt: 10

2.5 Zeiger auf Funktionen


Zeiger können nicht nur Variablen referenzieren, sondern auch Funktionen. Damit wird
eine noch flexiblere Programmgestaltung möglich. Der Compiler erzeugt in diesem Fall
aus dem Funktionsnamen als Parameter die Adresse der Funktion.
Insbesondere dann, wenn eine Funktion als Parameter einer anderen Funktion verwen-
det werden soll (funk2(... funk1, ...), ist ein Funktionszeiger zu nutzen.
Der formale Parameter wird als Zeiger auf die Funktion formuliert: Dabei leiten sich
Rückgabetyp und Variablenliste aus der Zielfunktion ab. Der Zeigername muss geklam-
mert werden.

Deklaration für Funktionszeiger:

datentyp (*fzeigerName) (parameterliste)

INM12 21
© HfB, 02.12.20, Berk, Eric (904709)

2 Zeiger zur Umsetzung des Modulkonzepts

Beispiel 2.7:
double(*fzeiger)(double) Funktionszeiger kann auf eine Funktion zeigen,
die ein Argument vom Typ double besitzt und
einen double-Wert zurückgibt.
void(*fzeiger)(int,int) Funktionszeiger kann auf eine Funktion zeigen,
die zwei Argumente vom Typ int besitzt, aber
selbst keinen Wert zurückliefert.
int(*fzeiger)( ) Funktionszeiger kann auf eine Funktion zeigen,
der keine Werte übergeben werden, die selbst
aber ein Ergebnis vom Typ int bereitstellt.
Im Gegensatz dazu:
int *funk() Funktion mit Rückgabe eines int-Zeigers.

Die Adresse der Funktion wird durch Zuweisung der Art fzeiger=funktion(...)
bzw. durch Parameterübergabe bereitgestellt.
Der Aufruf erfolgt in der Form (*fzeiger) (parameterliste).

Beispiel 2.8:
Beim Aufruf der Funktion berech wird jeweils als zweites Argument selbst eine
Funktion übergeben.
Der Aufruf von berech geschieht mit i=2. Diese Größe wird innerhalb dieser
Funktion durch Vermittlung der Variablen wert an funkl bzw. funk2 weiter-
gereicht.

1 //Programm Funktionszeiger.c
2 #include <stdio.h>
3
4 int berech(int wert,int(*fzeiger)(int wert));
5 int funk1(int x);
6 int funk2(int x);
7
8 int main (){
9 int i=2;
10 printf("\nFunktion1 benutzt: %i",berech(i,funk1));
11 printf("\nFunktion2 benutzt: %i",berech(i,funk2));
12 }
13
14 int berech(int wert, int(*fzeiger)(int wert)){
15 return(2*wert+(*fzeiger)(wert));
16 }
17
18 int funk1(int x){
19 return(x*x+10);
20 }
21
22 int funk2(int x){

22 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger zur Umsetzung des Modulkonzepts 2

23 return(5*x+50);
24 }

Code 2.7: Programm Funktionszeiger.c

Erläuterung:
Zeilen 4–6: Prototypanweisungen der beteiligten Funktionen.
Zeile 14: Statt der Funktion selbst wird in der Kopfzeile von berech der
Funktionszeiger benutzt.
Zeile 15: Der Aufruf der beiden Funktionen funk1 bzw. funk2 wird über
den Funktionszeiger gesteuert: (*zeiger)(wert).

Zusammenfassung

Funktionen sind das grundlegende Element modularer Programmierung. Eine Funktion


besteht aus dem Funktionskopf, der den Namen der Funktion, deren Übergabeparameter
und den Rückgabewert festlegt. Der Bereich, der durch die beiden geschweiften Klam-
mern gebildet wird, ist der Funktionskörper oder Rumpf und beinhaltet die Befehle, die
die Funktion abarbeiten soll.
Wenn man Funktionen innerhalb eines Quellcodeteils benutzen möchte, müssen diese
bekannt sein. Entweder ordnet man den Quellcode der aufzurufenden Funktionen vor
dem Quellcode der Funktion, die diese Funktion aufruft, an oder benutzt eine voran-
gestellte Prototypanweisung, die auch in Headerfiles ausgelagert sein kann.
Neben einer vollständigen Angabe des Funktionskopfes ist auch eine Form möglich, in
der nur die Typbezeichnungen angegeben werden.
Funktionen erhalten über Parameter Werte vom aufrufenden Programm übermittelt.
Begrifflich unterscheidet man zwischen formalen und aktuellen Parametern. Unter
formalen Parametern versteht man die Bezeichner, die beim Definieren des Programm-
codes benutzt werden. Aktuelle Parameter, in C auch Argumente genannt, stellen die
eigentlichen Werte beim Programmaufruf bereit.
Der Compiler nutzt den Prototyp, um die Argumenttypen beim Funktionsaufruf mit den
formalen Funktionsparametern zu vergleichen und gegebenenfalls die Argumenttypen
in die Parametertypen zu konvertieren.
Die besonderen Formen der praktischen Funktionsnutzung wurden unter den Begriffen
Parameterübergabe „call by value“ und „call by reference“, Felder als Funktionsparame-
ter sowie Funktionszeiger besprochen.
Ein Funktionsaufruf übergibt die Ausführungssteuerung von der aufrufenden Funktion
an die aufgerufene Funktion. Sind Argumente vorhanden, werden diese an die aufgeru-
fene Funktion übergeben. Die return-Anweisung gibt die Steuerung an das aufrufende
Programm und ggf. ein Ergebnis zurück.
Aufrufende und aufgerufene Programmeinheit besitzen unabhängige Speicherbereiche.
Gleichlautende Bezeichner beschreiben verschiedene Größen.

INM12 23
© HfB, 02.12.20, Berk, Eric (904709)

2 Zeiger zur Umsetzung des Modulkonzepts

Der Umgang mit Funktionen ist eine grundlegende Fähigkeit, die vom C-Programmierer
verlangt wird. Anhand der folgenden Programmieraufgaben können Sie die erworbenen
Kenntnisse an unterschiedlichen Sachverhalten überprüfen.

Aufgaben zur Selbstüberprüfung

2.1 Für einen Sachverhalt werden der Grundwert wert und der Prozentwert pwert
eingegeben. Aus diesen Werten ist der dazugehörige Prozentsatz psatz zu ermit-
teln.
Arbeiten Sie dazu
• mit einer Funktion prozent zur Berechnung des Prozentsatzes,
• mit einer weiteren Funktion ausgabe zur Ergebnisdarstellung und
• mit einer Programmstartroutine main.
Von hier aus sind die Funktionen prozent und ausgabe aufzurufen.
2.2 Für die Berechnung des Volumens eines Zylinders aus Durchmesser d und
Höhe h sind eine Methodendefinition und der Quelltext der Funktion anzugeben.
Der Funktionsprototyp sei:
double v_berech(double durchm, double hoehe)

2.3 Mit einer Funktion sign soll das Vorzeichen einer an diese Funktion überge-
benen ganzen Zahl gz ausgewertet werden. Die Funktion soll im Fall gz > 0
den Wert 1 und im Fall gz < 0 den Wert –1 zurückgeben.
Erstellen Sie einmal eine Variante, die nach dem Prinzip „call by value“, also ohne
Zeiger, arbeitet, und eine zweite Variante, die als Übergabe- und Rückgabepara-
meter nur Zeiger benutzt.
Testen Sie beide Funktionen mit einem geeigneten Testprogramm.
2.4 Schreiben Sie ein Unterprogramm arctan zur näherungsweisen Berechnung der
Funktion arctan(x) für |x| ≤ 1 unter Verwendung der Taylor-Entwicklung
arctan (x) = x – x3/3 + x5/5 – x7/7 + …

x 2i 1
Die Abbruchbedingung lautet ← eps *| si |,
2i 1
si … i-te Partialsumme, d. h. si = x – x3/3 + x5/5 – x7/7 + … ± x2i + 1/(2i + 1)
eps … relativer Fehler
Das Argument x und der relative Fehler eps sind als Parameter an das Unter-
programm zu übergeben.
In einem Hauptprogramm sind die Werte für x und eps einzulesen und der
ermittelte Wert auszugeben. Vergleichen Sie das Ergebnis Ihres Algorithmus mit
dem der C-Funktion "atan(x)" aus math.h.

24 INM12
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten


Zur Behandlung komplexer Sachverhalte werden bei der Programmerstellung
häufig statische Datenstrukturen verwendet.
Während es sich beim Datentyp Feld um Elemente des gleichen Datentyps
handelt, können beim Datentyp Verbund unterschiedliche Datentypen zur
Beschreibung der Datenstruktur benutzt werden.
Zum effektiven Umgang mit diesen Daten werden Zeiger benutzt.
Im folgenden Kapitel betrachten wir typische Nutzungsformen beim Umgang mit
diesen Daten.

3.1 Felder
Der statische Datentyp Feld (engl. array) gestattet die Zusammenfassung von Daten
gleichen Datentyps unter einem Namen. Der Zugriff erfolgt über diesen Namen, ver-
bunden mit einem Index. Sowohl bei der Feldvereinbarung als auch beim Feldzugriff
werden eckige Klammern gesetzt.
Der Feldname ist ein Platzhalter für die Anfangsadresse eines Feldes. Die Feldgröße
muss bereits zum Übersetzungszeitraum bekannt sein (bestimmte Compiler gestatten
jedoch auch eine Festlegung der Feldgröße zur Laufzeit).
Der Index eines Feldelementes beginnt immer mit null. Damit wird ein Feld mit n
Elementen mit den Indizes 0 bis n–1 durchnummeriert.
Die Syntax der Sprache C gibt folgende Möglichkeiten vor:

Felder deklarieren:

datentyp feldName [anzahl_der_feldelemente];

Beispiel 3.1: Deklarationen von Feldern:


int feld [10];

Es wird Speicherplatz für 10 Feldelemente vom Typ int bereitgestellt. Die Werte
der Feldelemente feld[0] bis feld[9] sind undefiniert.

Felder deklarieren und initialisieren:

datentyp feldName[anzahl_der_feldelemente]={element1, element2,


..., elementN};

Nicht angegebene Feldelemente werden mit dem Wert null initialisiert. Zu viele Feldele-
mente rufen eine Compilerfehlermeldung hervor.

INM12 25
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten

Beispiel 3.2: Deklaration von Feldern mit gleichzeitiger Initialisierung


int feld [10]{3,5,-1};

Es wird Speicherplatz für 10 Feldelemente von Typ int bereitgestellt. Die Feld-
elemente wurden in folgender Weise initialisiert:
feld[0]=3, feld[1]=5, feld[2]=-11, feld[3] bis feld[9] besitzen den
Wert 0.

Erfolgt bei der Felddeklaration gleichzeitig die Initialisierung, kann die Angabe der
Anzahl der Feldelemente entfallen. Diese wird dann aus der Anzahl der Initialisierungen
entnommen.

Beispiel 3.3: Deklaration von Feldern mit gleichzeitiger Initialisierung ohne Angabe der
Feldgröße
double feld [ ]={1.1, 8.4, -5.6, 4.5};

Es wird Speicherplatz für 4 Feldelemente von Typ double bereitgestellt. Die Feld-
elemente wurden in folgender Weise initialisiert:
feld[0]=1.1, feld[1]=8.4, feld[2]=-5.6, feld[3]=4.5.

Zugriff auf Feldelemente (in Indexschreibweise):

feldName[index]=ausdruck;

Beispiel 3.4:
Betrachten wir ein Programm zur Bestimmung von Primzahlen. Der benutzte Algo-
rithmus heißt „Sieb des Eratosthenes“ und ist bereits seit dem 3. Jahrhundert v. Chr.
bekannt. Das Prinzip besteht in der Berechnung aller Vielfachen einer jeden Zahl,
die demzufolge keine Primzahlen sein können.
Dieses einfache Programm zeigt die Deklaration und Verwendung eines Feldes a.
Dabei korrespondiert der Feldindex mit der zu untersuchenden Zahl.
Als Feldgrenze wird der Wert 100 gewählt und über die globale Konstante N bereit-
gestellt. Oft benutzt man dafür die Präcompiler-Direktive #define.
Das Feldelement a[1] wird mit dem Wert 0, der Kennung für keine Primzahl, be-
legt. Die Feldelemente 2 bis 100 werden mit dem Wert 1 initialisiert. Das Feldele-
ment mit dem Index 0 wird nicht weiter betrachtet und erhält auch den Wert 0 zu-
gewiesen.
Die geschachtelte Laufanweisung berechnet alle Zahlen, die keine Primzahlen sind,
und belegt die zugehörigen Feldelemente mit dem Wert 0.
Damit bleiben nur diejenigen Feldelemente mit a[i] = 1 übrig, deren Index i
eine Primzahl ist. Diese Elemente werden ausgegeben. Für if(a[i]) könnte
ausführlicher if(a[i]>0) stehen.

26 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger, Felder und Zeichenketten 3

Nach einer Kontrollausgabe des zu untersuchenden Feldes werden die gefundenen


Primzahlen jeweils auf einer neuen Zeile ausgegeben.

// Programm Primzahlen.c
#include <stdio.h>
#define N 100

int main(){

int i, j, a[N+1];

a[1] = 0;
for (i = 2; i<= N; i++) a[i] = 1;

for (i = 1; i <= N; i++)


if (a[i]) printf ("%d ", i);

for (i = 2; i <= N/2; i++)


for (j = 2; j <= N/i; j++)
a[i*j] = 0;

printf("\n\nBerechn. der Primzahlen < 100:\n");


for (i = 1; i <= N; i++)
if (a[i]) printf ("\n%d", i);

printf ("\nEnde des Programms\n");

return 0;
}

Code 3.1: Programm Primzahlen.c

Beispiel 3.5:
Um die Effektivität von C nicht zu vermindern, findet beim Zugriff auf die Feld-
elemente keine Feldgrenzenüberprüfung statt. Hier steht der Programmierer selbst
in der Verantwortung, um die Korrektheit seines Programms abzusichern.
Zum Illustrieren dieses Problems wird ein Array mit 5 ganzzahligen Elementen
definiert. Es erfolgen zwei Ausgaben, einmal für fünf und zusätzlich eine weitere
Ausgabe für fälschlicherweise sieben Feldelemente des gleichen Feldes.

1 //Programm Feldgrenzen.c
2 #include<stdio.h>
3 int main(){
4 int i, feld[]={1,2,3,4,5};
5
6 for(i=0;i<5;i++)
7 printf("Index: %d Wert: %d bei Adresse: %p\n",
8 i,feld[i],&feld[i]);
9 printf("\n");
10 for(i=0;i<8;i++)
11 printf("Index: %d Wert: %d bei Adresse: %p\n",
12 i,*(feld+i),feld+i);

INM12 27
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten

13
14 return 0;
15 }

Code 3.2: Programm Feldgrenzen.c

Ausgabe:
Index: 0 Wert: 1 bei Adresse: 0028FE98
Index: 1 Wert: 2 bei Adresse: 0028FE9C
Index: 2 Wert: 3 bei Adresse: 0028FEA0
Index: 3 Wert: 4 bei Adresse: 0028FEA4
Index: 4 Wert: 5 bei Adresse: 0028FEA8

Index: 0 Wert: 1 bei Adresse: 0028FE98


Index: 1 Wert: 2 bei Adresse: 0028FE9C
Index: 2 Wert: 3 bei Adresse: 0028FEA0
Index: 3 Wert: 4 bei Adresse: 0028FEA4
Index: 4 Wert: 5 bei Adresse: 0028FEA8
Index: 5 Wert: 5 bei Adresse: 0028FEAC
Index: 6 Wert: 8195304 bei Adresse: 0028FEB0
Index: 7 Wert: 79 bei Adresse: 0028FEB4

Erläuterung:
Zeile 4: Deklaration eines Arrays feld mit fünf Feldkomponenten (In-
dex 0 bis Index 4) sowie einer int-Variablen für den Feldindex.
Zeilen 6–8: Von den fünf Feldkomponenten werden in einer Laufschleife je-
weils Index, Wert und Adresse, hier mit dem Platzhalter für
Adressen %p, ausgegeben. Die Ausgabeanweisung wurde auf
zwei Zeilen aufgeteilt. Es wird die Indexschreibweise benutzt.
Zeilen 10–12: Die Laufanweisung beschreibt die Ausgabe von 7 Feldelementen
in Zeigerschreibweise.
Obwohl die Adressbereiche von Hardware- und Softwarebedingungen abhängig
sind, erkennt man jeweils die um 4 Byte anwachsenden Speicheradressen. Die
Ausgaben für die nicht vorhandenen Feldelemente werden entsprechend aus dem
Speicher als int-Größe ausgelesen: feld[6]: 8195304, feld[7]: 79. Dieser
fehlerhafte Arrayzugriff kann nicht nur lesend, sondern auch schreibend erfolgen.

3.2 Zeichenketten
Zeichenketten sind in C als spezielle Felder des Datentyps char aufzufassen. Sie wer-
den als Felder von Zeichen behandelt (in C++ wurde zusätzlich der Datentyp string
eingeführt).
Zur Bearbeitung von Zeichenketten wird intern ein spezielles Endeelement '\0'
(Nullbyte) angefügt. Dieses lässt sich bei einem Endetest abprüfen. Bestimmt man die
Länge einer Zeichenfolge (z. B. mit der Standardfunktion strlen aus string.h),
wird das Nullzeichen nicht mitgezählt.

28 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger, Felder und Zeichenketten 3

Bei der Definition von Zeichenketten kann sofort eine Initialisierung, wahlweise mit
einer Zeichenkette oder mit Einzelzeichen, erfolgen. Wird die Zeichenkette initialisiert,
kann die Angabe der Anzahl der Feldelemente entfallen.

Beispiel 3.6: Definition von Zeichenketten


char name [6] = "Franz";
char name [ ] = "Franz";
char name [6] = {'F','r','a','n','z','\0'};
char name [ ] = {'F','r','a','n','z','\0'};

In allen Fällen gilt:


name [0] = 'F'
name [1] = 'r'
name [2] = 'a'
name [3] = 'n'
name [4] = 'z'
name [5] = '\0'

3.3 Felder und Zeiger


In C ist jeder Feldname als konstanter (unveränderlicher) Zeiger auf die Anfangsadresse
des Feldes aufzufassen. Mit der Angabe des Feldnamens beschreibt man somit die Basi-
sadresse des Feldes. Der Zugriff auf die Feldelemente kann in klassischer Indexschreib-
weise und auch in Zeigerschreibweise erfolgen. Zusätzlich ist es auch möglich, weitere
Zeiger zu definieren und diese auf das Feld auszurichten. Der Datentyp des Zeigers muss
mit dem Datentyp des Feldes korrespondieren.

Beispiel 3.7:
In der folgenden Abbildung werden die ersten drei Komponenten eines
Feldes vektor dargestellt: vektor=(vektor[0], vektor[1], vektor[2]).
Felder stehen dabei in einem zusammenhängenden Bereich und in fester Reihenfol-
ge im Speicher. Zusätzlich lässt sich ein freier Pointer zeiger explizit vereinbaren.
Dieser Zeiger kann beliebige Feldelemente referenzieren.

vektor
zeiger
&vektor[0]

vektor[0] vektor[1] vektor[2]

wert0 wert1 wert2

Abb. 3.1: Zusammenhang Feld und Zeiger

INM12 29
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten

Ist p die Basisadresse des Feldes und zeigt auf das erste Feldelement vektor[0],
dann zeigt p+1 auf das zweite Feldelement mit dem Index 1 und p+2 auf das
dritte Feldelement mit dem Index 2 usw.
Somit sind a[i] und *(a+i) korrespondierende Schreibweisen. Daraus folgt:
Zugriff auf Feldelemente (in Zeigerschreibweise):
ausdruck = *(feldName+index);

Der folgende Quellcode fasst die dargestellte Problematik zusammen:

1 //Programm Feld_als_Zeiger.c
2 #include<stdio.h>
3
4 int main(){
5 int i;
6 int vektor[3];
7 int *zeiger;
8 int a=2;
9 *vektor=a;
10 *(vektor+1)=4;
11 zeiger=vektor;
12 //oder:
13 zeiger=&vektor[0];
14 *(zeiger+1)=a-3;
15 zeiger=&vektor[1];
16 *(zeiger+1)=a+1;
17 for(i=0;i<3;i++) //Ausgabe in Indexschreibweise
18 printf("\nvektor[%i]=%i",i,vektor[i]);
19 printf("\n");
20 for(i=0;i<3;i++) //Ausgabe in Zeigerschreibweise
21 printf("\n*(vektor+%i)=%i",i,*(vektor+i));
22
23 return 0;
24 }

Code 3.3: Feld als Zeiger.c

Das Programm erzeugt folgende Ausgabe:


vektor[0]=2
vektor[1]=-1
vektor[2]=3

*(vektor+0)=2
*(vektor+1)=-1
*(vektor+2)=3

Erläuterung:
Zeile 6: Deklaration eines Feldes vektor mit drei Feldkomponenten.
Automatisch wird intern der Zeiger vektor bereitgestellt.
Zeile 7: Vereinbarung des frei nutzbaren Zeigers zeiger.
Zeile 8: Vereinbarung einer Variablen a vom Typ int und Initialisierung
mit dem Wert 2.

30 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger, Felder und Zeichenketten 3

Zeile 9: Zeigerschreibweise: Komponente am Feldanfang (Index 0) erhält


den Wert 2 zugewiesen.
Zeile 10: Zeigerschreibweise: Komponente mit dem Index 1 (vom Feldanfang
das nächste Vektorelement) erhält als Wert die Zahl 4 zugewiesen.
Zeile 11: Initialisierung von zeiger. Die Adresse von vektor wird zuge-
wiesen.
Zeile 13: Nochmalige Initialisierung von zeiger durch Nutzung des Ad-
ressoperators. In beiden Fällen erhält zeiger als Wert die Adresse
von vektor[0] zugewiesen (erstes Vektorelement).
Zeile 14: Komponente mit dem Index 1 (vom Standpunkt des Zeigers (Feld-
anfang) das nächste Vektorelement) erhält denWert –1 zugewiesen.
Zeile 15: Der Zeiger referenziert nun das zweite Vektorelement mit dem
Index 1.
Zeile 16: Komponente mit dem Index 2 (vom Standpunkt des Zeigers das
nächste Vektorelement) erhält als Wert die Zahl 3 zugewiesen.
Zeilen 17–18: Ausgabe der Feldelemente in Indexschreibweise.
Zeilen 20–21: Ausgabe in Zeigerschreibweise.

3.4 Zeichenketten und Zeiger


Vereinbart seien:
char zk1[ ]="abc";
char zk2[ ]="xyz";
char *varz;

zk1 und zk2 sind Zeiger, die mit der Felddefinition zur Verfügung gestellt werden und
fest an die Anfangsadresse des Feldes gebunden sind.
varz ist ein Zeiger, der variabel zu benutzen ist.

Bei Zuweisungen der Art zeiger_a=zeiger_b; dürfen konstante Zeiger nicht auf
der linken Seite der Zuweisung erscheinen. Ihnen darf keine neue Adresse zugewiesen
werden.
Somit sind Zuweisungen wie zk1=zk2; und zk2=zk1; nicht zugelassen. Das heißt,
in der Programmiersprache C ist eine Zuweisung von Zeichenketten nicht gestattet!
Will man den Inhalt einer Zeichenkette in eine andere Zeichenkette kopieren, muss die
Standardfunktion strcpy (string copy) aus string.h benutzt werden.

Funktionsprototyp:

char* strcpy(char* Ziel, const char* Quelle);

Mit char* wird hier für den Datentyp Zeichenkette die Zeigerschreibweise benutzt.
Die Funktion kopiert die Zeichenfolge Quelle in die Zeichenfolge Ziel und liefert einen
Zeiger auf Ziel als Funktionswert.

INM12 31
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten

Beispiel 3.8:
Im Programm Zeichenketten.c wird der Umgang mit Zeichenketten zusam-
menfassend dargestellt:

1 //Programm Zeichenketten.c
2 #include <stdio.h>
3 #include <string.h>
4
5 int main(){
6 int i;
7 char zk1[18]="eine Zeichenkette";
8 char zk2[]="auch Zeichenkette";
9 char *varz3="noch eine Zeichenkette";
10 char *varz4;
11
12 printf("1: zk1: %s\n",zk1);
13 //zk1=zk2;
14 strcpy(zk1,zk2);
15 printf("2: zk1: %s\n",zk1);
16 printf("3: varz3: %s\n",varz3);
17 varz4=zk1;
18 printf("4: varz4: %s\n",varz4);
19 varz3=varz4;
20 while(*varz3)
21 printf("%c ",*varz3++);
22 return 0;
23 }

Code 3.4: Programm Zeichenketten.c

Das Programm erzeugt folgende Ausgabe:


1: zk1: eine Zeichenkette
2: zk1: auch Zeichenkette
3: varz3: noch eine Zeichenkette
4: varz4: auch Zeichenkette
a u c h Z e i c h e n k e t t e

Erläuterung:
Zeile 3: Um die Funktion strcpy nutzen zu können, muss das
Headerfile string.h eingebunden werden.
Zeile 7: Die Zeichenkette zk1 wird definiert und mit einem Wert belegt.
Dabei ist das Zeichenkettenendzeichen zu berücksichtigen.
Zeile 8: Der Zeichenkette zk2 wird bei der Definition ein Wert zugeord-
net. Dadurch kann die Angabe der Feldelemente entfallen.
Zeile 9: Die Vereinbarung erfolgt als Stringkonstante, die durch den
Zeiger varz3 referenziert wird. Dieser Zeiger zeigt auf die
Anfangsadresse der Konstanten, genauer auf die Adresse des
Buchstabens n'. Der Inhalt dieses Strings kann nicht verändert
werden.
Zeile 10: Vereinbarung des variablen Zeigers varz4 vom Typ char*.

32 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger, Felder und Zeichenketten 3

Zeile 12: Es erfolgt die Ausgabe der Zeichenkette zk1. Als Platzhalter in
der printf-Anweisung wird %s genutzt.
Zeile 13: Eine Zuweisung von Zeichenketten in C ist nicht zulässig.
Zeile 14: Mit der Bibliotheksfunktion strcpy aus string.h wird der
Inhalt von zk2 nach zk1 kopiert.
Zeile 15: Ausgabe von zk1.
Zeile 16: Ausgabe von varz3.
Zeile 17: Der variable Zeiger varz4 darf auf zk1 gestellt werden. varz4
erhält zk1 zugewiesen.
Zeile 18: Ausgabe von varz4.
Zeile 19: Der variable Zeiger varz3 darf auf varz4 gestellt
werden. varz3 erhält varz4 zugewiesen.
Zeilen 20, 21: Die Ausgabe von varz3 erfolgt zeichenweise und mit einem Zwi-
schenraumzeichen. Dafür wird schrittweise der Speicherbereich
der Zeichenkette so lange durchlaufen, wie die Wiederholungsbe-
dingung *varz3 wahr ergibt. In C ist jeder Ausdruck dann als
wahr aufzufassen, wenn sein Wert ungleich null ist. Der Ausdruck
*varz zeigt auf das aktuelle Zeichen des Stringspeicherbereiches.
Dieses wird erstmalig null, wenn das Zeichenkettenende '\0' er-
reicht wird. Solange noch Zeichen ungleich dem Zeichenkettenen-
dezeichen vorhanden sind, wird dieses ausgegeben und mit dem
postfix benutzten Inkrement-Operator das Weiterschalten zur
nächsten Stringkomponente gesteuert.

3.5 Felder als Funktionsparameter


Felder werden aus Effektivitätsgründen, unabhängig davon, ob der Programmierer die
Feld- oder Zeigerschreibweise benutzt, immer als Zeiger übergeben. Benutzt man ein
Feld als Funktionsparameter, wird bei einem Funktionsaufruf nur die Adresse des ersten
Feldelementes bereitgestellt.
Durch die damit automatisch erfolgende Übergabe per „call by reference“ können als
Argument übergebene Felder in Funktionen immer verändert werden.
Generell empfiehlt sich die Schreibweise mit [ ] anstelle von *, da das Feld als solches
deutlicher sichtbar wird.
Die folgenden Beispiele Sortieren1.c und Sortieren2.c stellen die beiden Im-
plementationsvarianten nochmals gegenüber. Dazu greifen wir das Beispiel zum Sor-
tieren eines eindimensionalen Feldes durch Nachbarschaftsvergleich aus Kapitel 4 des
Heftes INM11 nochmals auf und implementieren das Modul sort.

INM12 33
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten

Beispiel 3.9:

Sort (anz, f)
Input: anz … Anzahl der Elemente
f … Feld
Output: f … sortiertes Feld

fuer i:=anz(-1)2

fuer k:=1(1)i-1

f[k] < f[k+1]


Ja Nein

h:=f[k]

f[k]:=f[k+1]

f[k+1]:=h

Abb. 3.2: Moduldefinition Sort(anz,f)

Das Struktogramm in Abb. 3.2 enthält zwei geschachtelte Laufschleifen. Start- und
Endwerte dieser Schleifen sind einschließlich aufzufassen. Während in Strukto-
grammen Felder mit dem Index 1 beginnend beschrieben werden, besitzt in C jedes
Feld den kleinsten Index 0. Entsprechend muss bei der Übertragung des Algorithmus
in die Sprache C eine Anpassung erfolgen:
Aus für i:=anz(-1)2 folgt for(i=anz-1;i>=1;i--) oder besser
for(i=anz-1;i>0;i--).

Aus für k:=1(1)i-1 ergibt sich for(k=0;k<i;k++).

//Programm Sortieren_1.c
#include <stdio.h>

void sort( int vektor[],int anzahl);

int main(){
int i,feld[6]={12,34,4,17,33,9};
printf("\nAusgangsfeld:\n");
for(i=0;i<6;i++)
printf("%i ",feld[i]);
sort(feld,6);
printf("\nSortiertes Feld:\n");
for(i=0;i<6;i++)
printf("%i ",feld[i]);
return 0;
}

34 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger, Felder und Zeichenketten 3

void sort(int f[],int anz){


int i,k,h;
for(i=anz-1;i>0;--i){
for(k=0;k<i;k++)
if(f[k]<f[k+1]){
h=f[k];
f[k]=f[k+1];
f[k+1]=h;
}
}
}

Code 3.5: Programm Sortieren_1.c

Ausgabe:
Ausgangsfeld:
12 34 4 17 33 9
Sortiertes Feld:
34 33 17 12 4 9

Im Programm Sortieren_2.c ändert sich die Definition von sort und entsprechend
auch die Prototypanweisung.
Prototyp:
void sort(int *f, int anz) oder vereinfacht: void sort(int*,int);

Definition:

void sort(int *f, int anz){


int i,k,h;
for(i=anz-1;i>0;--i){
for(k=0;k<i;k++)
if(*(f+k)<*(f+k+1)){
h=*(f+k);
*(f+k)=*(f+k+1);
*(f+k+1)=h;
}
}
}

Code 3.6: Definition der Funktion sort(…) zum Programm Sortieren_2.c

Zusammenfassung:
Eine/r der wichtigsten Datenstrukturen/Datentypen sind Felder. Felder verkörpern
eine Aneinanderreihung von Daten gleichen Datentyps. Diese werden in C als Array
dargestellt. Dabei muss vom Programmierer die Anzahl der Feldelemente angegeben
werden. Die meisten Compiler gehen davon aus, dass die Feldgröße bereits zur Com-
pilezeit festliegen muss. Der C99-Standard gestattet jedoch auch diese Festlegung
zur Laufzeit. Der Zugriff auf Feldelemente erfolgt über ihren Index und ist in Feld-
und Zeigerschreibweise möglich. Dieser Index wird in C mit null beginnend gezählt.

INM12 35
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten

3.6 Datenstruktur Datensatz


Der Datentyp Datensatz, auch Verbund genannt, wird in C mit dem Schlüsselwort
struct umschrieben. Sie wissen, unter einem Datensatz wird die Zusammenfassung
verschiedenartiger Einzeldaten (Daten verschiedenen Typs) verstanden.
Wir greifen die Darstellung im Heft INM11, Kapitel 2 erneut auf und fassen den Um-
gang mit dem Datentyp Datensatz in der Programmiersprache C nochmals zusammen.
Um Datensätze in Programmen einsetzen zu können, müssen diese definiert und dekla-
riert werden. Man versteht darunter, wie auch z. B. bei einfachen Variablen:
• Deklaration des Datensatzes (Festlegung des Strukturtyps),
• Definition der Variablen vom Datensatztyp.
Betrachten wir noch einmal die Syntax für die Deklaration des Datentyps Datensatz:

Structure Type

(38)
struct { FieldList (39) ; } Ident (2) ;

TagName (5) ,

Abb. 3.3: Syntaxdiagramm Datensatz

Daraus leitet sich für die Programmiersprache C folgendes Muster ab:


struct [strukturname]{
typl komp_name_1;
typ2 komp_name_2;
...
typn komp_name_n;
} [strukturvariablenname, ...];

Aus den []-Klammern in der Syntaxbeschreibung lässt sich die Möglichkeit ableiten,
alternativ einen Stukturnamen oder Strukturvariablennamen anzugeben:
Betrachten wir mögliche Schreibweisen nun an dem konkreten Datensatz datum mit
den Komponenten tag, monat und jahr.

Beispiel 3.10:
Deklaration von Strukturen:
struct datum{
int monat;
int tag;
int jahr;
};
struct datum{
int tag;
int monat;
int jahr;
}termin;
struct datum termin;

36 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger, Felder und Zeichenketten 3

Der Zugriff auf die Komponenten des Datensatzes erfolgt durch den Punkt-Operator (.)
bzw., wenn ein Zeiger auf die Struktur vereinbart wurde, mit dem Operator für Ele-
mentzugriff (->).

Beispiel 3.11:
Zugriff auf Strukturkomponenten:
Mit
struct datum t={1,1,2000};
struct datum *z=&t;

sind folgende Zugriffe möglich:


printf("Termin: %d.%d.%d\n",
t.tag,t.monat,t.jahr);
printf("Termin: %d.%d.%d\n",
z->tag,z->monat,z->jahr);
printf("Termin: %d.%d.%d\n",
(*z).tag,(*z).monat,(*z).jahr);

//Programm Termin.c
#include <stdio.h>

struct datum{
int tag;
int monat;
int jahr;
};

int main(){
struct datum t={1,1,2000};
struct datum *z=&t;

printf("Termin: %d.%d.%d\n",
t.tag,t.monat,t.jahr);
printf("Termin: %d.%d.%d\n",
z->tag,z->monat,z->jahr);
printf("Termin: %d.%d.%d\n",(*z).tag,
(*z).monat,(*z).jahr);
return 0;
}

Code 3.7: Programm Termin.c

Bemerkung: In C++ darf das Schlüsselwort struct weggelassen werden. Je nach


Compiler und Dateiname (*.c, *.cpp) können aber beim Weglassen unterschiedliche
Reaktionen erfolgen.

INM12 37
© HfB, 02.12.20, Berk, Eric (904709)

3 Zeiger, Felder und Zeichenketten

Die Programmiersprache C bietet die Möglichkeit, mithilfe des Schlüsselworts typedef


zu einem Datentypnamen einen Alias zu vergeben. Für unseren Fall ändert sich der
Code in folgender Weise ab (s. Programm typedef.c):

Beispiel 3.12:
Die folgende typedef-Anweisung:
typedef struct{
int tag;
int monat;
int jahr;
}datum;

ermöglicht die Schreibweisen:


datum t={1,1,2000};
datum *z=&t;

//Programm typedef.c
#include <stdio.h>

typedef struct{
int tag;
int monat;
int jahr;
}datum;

int main(){
datum t={1,1,2000};
datum *z=&t;

printf("Termin: %d.%d.%d\n",t.tag,t.monat,t.jahr);
printf("Termin: %d.%d.%d\n",z->tag,z->monat,z->jahr);
printf("Termin: %d.%d.%d\n",(*z).tag,(*z).monat,(*z).jahr);
return 0;
}

Code 3.8: Programm typedef.c

Zusammenfassung

Zur Modellierung praktischer Sachverhalte innerhalb eines Computerprogrammes wird


es notwendig, eine problemadäquate Datenstruktur bereitzustellen, deren Konstruktion
durch den Programmierer erfolgt.
Diese in der Regel komplexeren Datentypen lassen sich aus einfacheren zusammenset-
zen. Gleichzeitig besteht die Möglichkeit eigene Synonyme für neue Typen festzulegen.
Die Nutzung der Datentypen Feld und Verbund, einschließlich dem Zugriff über Zeiger,
wurde an Beispielen gezeigt. Die vermittelten Grundlagen sollten Sie in die Lage verset-
zen, diese Datentypen in typischen Anwendungen sicher zu nutzen.

38 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Zeiger, Felder und Zeichenketten 3

Aufgaben zur Selbstüberprüfung

3.1 Gegeben seien Messwerte, die durch ein eindimensionales Feld mit definierter
Größe vom Elementtyp reelle Zahlen bereitgestellt sind. Von diesen Werten sind
der Minimalwert, der Maximalwert sowie der Mittelwert zu bestimmen.
Erstellen Sie ein Programm, das aus als vorgegeben aufzufassenden Messwerten
die Ergebnisse ermittelt! Erarbeiten Sie eine Lösung, die auf klassische Art auf die
Feldelemente in Indexschreibweise zugreift und auch alternativ einen Zeiger dafür
nutzt.
3.2 Schreiben Sie eine Funktion vsumme, die aus einem zu übergebenden eindimensi-
onalen Feld und der Anzahl der Feldelemente die Summe aller Feldelemente
ermittelt. Dabei sind 10 Werte vom Datentyp float vorzusehen.
Nutzen Sie diese Funktion in einem Hauptprogramm.
3.3 Entwickeln Sie eine Funktion ersetze_zeichen, die in einer Zeichenkette ein
bestimmtes Zeichen sucht und mit einem anderen austauscht! Benutzen Sie als
Prototypanweisung
void ersetze_zeichen(char*s,char zei_alt, char zei_neu);

3.4 Definieren Sie eine Funktion zeik_verbind, die aus zwei Zeichenketten, z. B.
PROGRAMM und ENTWICKLUNG, eine resultierende Zeichenkette, im Beispiel
PROGRAMMENTWICKLUNG, erzeugt.
Die zu erstellende Lösung darf nicht die in C vorhandene Standardfunktion
strcat benutzen. Beide Ausgangszeichenketten sollen unverändert erhalten blei-
ben. Die Teilzeichenketten und die Ergebniszeichenkette sollen jeweils als Parame-
ter übergeben werden, sodass folgender Funktionsprototyp zu benutzen ist:
void zeik_verbind(const char*,const char*,char*).

INM12 39
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung
Beim Arbeiten mit Feldern, einem Vertreter statischer Datentypen, haben Sie
bereits kennengelernt, dass der Programmierer über den Datentyp der Feldele-
mente und über deren Anzahl entscheidet. Für so zu deklarierende Felder steht
damit deren Größe bereits zur Compilezeit fest, sie ist nachträglich nicht mehr
veränderbar.
Da jedoch der für Daten benötigte Speicher während der Laufzeit eines Pro-
gramms nicht in jedem Fall exakt im Voraus bestimmbar ist, wird praktisch
sicherheitshalber hinreichend viel Speicherplatz reserviert. Ein solches Arbeiten
erweist sich als extrem unökonomisch.
Deshalb bietet die Programmiersprache C auch die Möglichkeit, Speicherbereiche
während des Programmlaufs zu reservieren und dem laufenden Programm zur
Verfügung zu stellen. Ein solcher Speicher ist außerdem effektiv über Modulgren-
zen ansprechbar.
Damit wird es möglich, den Speicherbedarf für Daten auf die wirklich benötigte
Größe anzupassen.
Zur Umsetzung dieses Konzepts werden Zeiger genutzt, die die Startadresse des
zugeteilten Speichers als Wert zugewiesen bekommen. Diese reservierten
Speicherbereiche müssen aber spätestens bis zum Programmende wieder frei-
gegeben werden.
Zum Umsetzen dieser Funktionalität stehen in der Standardbibliothek in der
Header-Datei stdlib.h definiert die Bibliotheksfunktionen malloc(), calloc(), real-
loc() und free() zur Verfügung.
Nach einem Überblick zu diesen Funktionen werden Sie am Beispiel der einfach
verketteten Liste an die Nutzung dynamischer Datenstrukturen herangeführt.
Aufbauend auf diesen Kenntnissen sollten Sie in der Lage sein, sich die Arbeit mit
weiteren dynamischen Datenstrukturen zu erschließen.

4.1 Speicherkonzept
Ein laufendes Programm benutzt vier Speicherbereiche:
Heap: Dynamisch reservierter Speicher. Bei einer Speicheranforderung
wird der Heap-Speicher vergrößert, bei Freigabe wird er wieder
verkleinert.
Stack: Er wird von der Hardware direkt unterstützt und dient der Ver-
waltung der Funktionsaufrufe und lokaler Variabler.
Datenbereich: Zur Speicherung statischer Daten, die bis zum Programmende
verfügbar sind (globale und statische Variablen).
Programmbereich: Er dient zur Ablage des Maschinencodes des Programms.
Für die dynamische Speichernutzung ist der Heap-Speicher, auch Halden- oder Haufen-
Speicher genannt, wichtig.

40 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

4.2 Bereitstellung von dynamischem Speicher


4.2.1 Speicher mit malloc() reservieren und mit free() freigeben
Das Bereitstellen von dynamischem Speicher wird als Speicherallokation bezeichnet.
Die bekannteste Funktion für die dynamische Zuteilung von Speicher ist malloc
(engl. memory allocation).
Die Funktion malloc reserviert einen Speicherbereich in einer gewünschten Größe.
Dabei wird die Anfangsadresse des Speichers oder der Wert NULL, falls kein Speicher
bereitgestellt werden konnte, zurückgegeben.
Da die Funktion malloc einen typenlosen void-Zeiger liefert, Zeigerzugriffe aber
über die Speicherbreite des durch den Zeiger referenzierten Datentyps gesteuert werden,
ist vor Nutzung des reservierten Speicherbereiches eine Typauswahl notwendig. Dieser
auch Typcasting genannter Vorgang der Typanpassung erfolgt in C automatisch, ist
aber in C++ explizit anzuweisen. Als Konsequenz daraus ergibt sich die Empfehlung,
immer mit Typcasting zu arbeiten.
Der Speicherbereich, der vom Betriebssystem zur Verfügung gestellt wird, wird nicht in-
itialisiert, sodass willkürliche, zufällige Werte in diesem Bereich stehen.

Funktionsprototyp:

void *malloc(size_t size);


Übergabeparameter size: Größe des Speicherbedarfs in Byte.
Rückgabetyp: void-Zeiger unabhängig von einem Datentyp.

Bemerkung:

Der in der Definition verwendete Datentyp size_t ist ein zur Angabe der Größe eines
Speicherbereichs speziell definierter, vorzeichenloser Ganzzahldatentyp. Die Definition
von size_t erfolgt in der Regel in stddef.h.

Beispiel 4.1: Reservieren von dynamischem Speicher mit malloc


int *str;
str=(char *)malloc(6); Bereitstellung von Speicher für
eine Zeichenkette mit 5 Zeichen
und '\0'
int *p;p=(int *)malloc(sizeof(int)); Bereitstellung von Speicher für
eine int-Variable
int z;

z=(int *)malloc(10*sizeof(int)); Bereitstellung von Speicher für


10 int-Variablen

Die Funktion free gibt den bereitgestellten Zeiger wieder frei. Dazu wird der Zeiger,
der auf den freizugebenden Speicherbereich zeigt, der Funktion free als Parameter
übergeben.

INM12 41
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

Funktionsprototyp:

void free (void *p);


Übergabeparameter p: Zeiger auf den Speicherbereich.
Rückgabetyp: void

Beispiel 4.2:
Freigabe von reserviertem Speicher:
free(str); free(p); free(z);

Das folgende Beispiel zeigt das prinzipielle Arbeiten mit den Funktionen malloc()
und free(). Hier wird dynamischer Speicher für eine int-Größe und eine Zeichen-
kette bereitgestellt.

Beispiel 4.3:

1 //Programm malloc.c
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 int main(){
7 char *str;
8 int *p;
9
10 p=(int *)malloc(sizeof(int));
11 if(p != NULL) {
12 printf("\nSpeicher ist reserviert\n");
13 }else {
14 printf("\nKein freier Speicher vorhanden.\n");
15 }
16
17 if(p != NULL){
18 free(p);
19 p=NULL;
20 printf("Speicher freigegeben.\n\n");
21 }
22
23 if ((str = (char *) malloc(6)) == NULL) {
24 printf("Kein freier Speicher vorhanden.\n");
25 exit(1);
26 }
27 strcpy(str, "Hallo");
28 printf("Zeichenkette: %s\n", str);
29
30 free(str);
31 p=NULL;
32 printf("Speicher freigegeben.\n");
33 return 0;
34 }

Code 4.1: Programm malloc.c

42 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

Erläuterung:
Zeilen 2–4: Um die Funktionen printf, malloc, free und strcpy
nutzen zu können, müssen die Headerfiles stdio.h, stdlib.h
und string.h eingebunden werden.
Zeilen 7–8: Die Zeiger str vom Typ char* sowie p vom Typ int*
werden definiert.
Zeile 10: Der Zeiger p wird auf einen reservierten Speicherbereich für
eine int-Größe im Heap gestellt.
Zeilen 11–15: Anhand des zurückgegebenen Zeigers p wird überprüft, ob der
angeforderte Speicher zur Verfügung gestellt wurde.
Zeilen 17–21: Bereitgestellter Speicher wird freigegeben. Es empfiehlt sich zu
überprüfen, ob nur der reservierte Speicherplatz freigegeben
wurde. Da in der Regel die Informationen in diesem Speicher noch
weiterhin über den Zeiger erreichbar sind, wird empfohlen, nach
einer Freigabe noch den Null-Zeiger zuzuweisen.
Zeilen 23–26: Der Zeiger str wird auf einen reservierten Speicherbereich mit
der Größe von 6 Byte gestellt. Die Überprüfung dieser Aktion
erfolgt in einer komplexeren Schreibweise. Konnte kein Speicher
bereitgestellt werden, erfolgt mit exit der Programm-Abbruch.
Zeile 27: Steht Speicher zur Verfügung, wird in diesem Speicherbereich die
Zeichenkette "Hallo" eingetragen und anschließend in Zeile 28
ausgegeben.
Zeilen 30–31: Der reservierte Speicherbereich wird freigegeben sowie die Adresse
des Zeigers auf den Wert NULL gesetzt.

4.2.2 Die Funktionen calloc() und realloc()


Mit der Funktion calloc (engl. cleared memory allocation) wird ein Feld von
Speicherblöcken reserviert, indem die Größe eines einzelnen Speicherblocks sowie die
Anzahl der benötigten Speicherblöcke anzugeben ist. Dabei wird der Speicherbereich
initialisiert, indem alle Bytes auf null gesetzt werden.

Funktionsprototyp:

void * calloc ( size_t num, size_t size );


Übergabeparameter num: Anzahl der Speicherblöcke.
Übergabeparameter size: Größe eines Speicherblocks.
Rückgabetyp: void-Zeiger unabhängig von einem Datentyp.

Beispiel 4.4:
Bereitstellung eines Feldes aus 10 Speicherblöcken der Speichergröße des Datentyps
int:
int *p;
p=(int *)calloc(10,sizeof(int));

INM12 43
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

Die Möglichkeit der Vereinbarung eines Feldes mit einer bestimmten Größe je nach
Bedarf zur Programmlaufzeit ist eine der gewünschten Anwendungen der dynamischen
Speichervereinbarung.
Sollen beispielsweise mit einem Programm ganze Zahlen in einer Datenstruktur Feld
verarbeitet und gespeichert werden, muss vor der Programmausführung die Feldgröße
festgelegt sein. Wenn die Anzahl der zu erwartenden Daten nicht genau bekannt ist,
muss vom Programmierer eine hinreichend große Anzahl von Feldelementen vorgese-
hen werden. Erschöpft sich dennoch die Speicherkapazität der definierten Datenstruktur
während der Programmabarbeitung, besteht keine Möglichkeit der Korrektur. Ein Über-
schreiten der Feldgrenze kann bis zum Programmabsturz führen.
Beispiel 4.5 illustriert das Vorgehen zum Bereitstellen eines dynamisch vereinbarten Fel-
des für ganze Zahlen unter Nutzung der Funktion calloc. In Beispiel 4.7 wird die
Möglichkeit gezeigt, die Größe eines dynamisch reservierten Speicherbereichs mit der
Funktion realloc zur Laufzeit nochmals zu verändern.

Beispiel 4.5:
Nach Definition des Zeigers *array wird diesem eine Folge von Speicherblöcken
des Datentyps int zugewiesen. Die Festlegung der Anzahl der Speicherblöcke ge-
schieht durch Eingabe der Größe size. Nach Nutzung des Speichers erfolgt am
Programmende seine Freigabe.

1 //Programm calloc.c
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 int main() {
7 int *array;
8 int size,wert,i=0;
9
10 printf("Feldgroesse: ");
11 scanf("%d", &size);
12 array = (int *)calloc(size,sizeof(int));
13 if(array == NULL) {
14 printf("Fehler bei calloc....\n");
15 exit(1);
16 }
17 while(i < size) {
18 printf("Wert fuer array[%d] eingeben : ", i);
19 scanf("%d", &array[i]);
20 i++;
21 }
22 printf("\nEingegebene Werte\n");
23 for(i=0; i < size; i++)
24 printf("array[%d] = %d\n", i, array[i]);
25
26 free(array);
27 array = NULL;
28 return 0;
29 }

Code 4.2: Programm calloc.c

44 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

Bei Abarbeitung des Programms entsteht z. B. folgende Ausgabe:


Feldgroesse: 5
Wert fuer array[0] eingeben : 1
Wert fuer array[1] eingeben : 2
Wert fuer array[2] eingeben : 3
Wert fuer array[3] eingeben : 4
Wert fuer array[4] eingeben : 5
Eingegebene Werte
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5

Mit der Funktion realloc() (engl. reallocate memory block) ist es möglich, einen
vorher mit malloc() oder calloc() reservierten Speicherbereich zu vergrößern
oder zu verkleinern. Damit hat der Programmierer ein extrem dynamisches Werk-
zeug zur Hand.
Funktionsprototyp:
void * realloc ( void *zgr, size_t nsize );
Übergabeparameter nsize: Neue Größe des Speichers in Byte.
Übergabeparameter zgr: Zeiger auf bisher allozierten Speicherplatz.
Rückgabetyp: void-Zeiger unabhängig von einem Datentyp.

Beispiel 4.6:
Größenveränderung des bisher über den Zeiger p angesprochenen Speicherbe-
reichs auf n* der Speichergröße des Datentyps int:
int *p;
p=(int *)realloc(p,n*sizeof(int));

Wir verändern das vorangegangene Beispiel 4.5.

Beispiel 4.7:

1 //Programm realloc.c
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 int main() {
7 int *array;
8 int size,wert,i=0;
9
10 printf("Feldgroesse : ");
11 scanf("%d", &size);
12 array = (int *)calloc(size,sizeof(int));

INM12 45
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

13 if(array == NULL) {
14 printf("Fehler bei calloc....\n");
15 exit(1);
16 }
17
18 else
19 printf("Speicher fuer %d Elemente reserviert\n",size);
20
21 printf("\nneue Feldgroesse : ");
22 scanf("%d", &size);
23 array = (int *)realloc(array,size*sizeof(int));
24 if(array == NULL) {
25 printf("Fehler bei realloc....\n");
26 exit(1);
27 }
28
29 else
30 printf("Speicher fuer %d Elemente reserviert\n",size);
31
32 while(i < size) {
33 printf("Wert fuer array[%d] eingeben: ", i);
34 scanf("%d", &array[i]);
35 i++;
36 }
37 printf("\nEingegebene Werte\n");
38 for(i=0; i < size; i++)
39 printf("array[%d] = %d\n", i, array[i]);
40
41 free(array);
42 array=NULL;
43 return 0;
44 }

Code 4.3: Programm realloc.c

Programmabarbeitung:
Feldgroesse : 2
Speicher fuer 2 Elemente reserviert

neue Feldgroesse : 4
Speicher fuer 4 Elemente reserviert
Wert fuer array[0] eingeben: 1
Wert fuer array[1] eingeben: 2
Wert fuer array[2] eingeben: 3
Wert fuer array[3] eingeben: 4

Eingegebene Werte
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4

46 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

4.2.3 Arbeiten mit dynamischen Datentypen


Die Charakteristik der bisher betrachteten Datentypen bestand darin, dass diese hin-
sichtlich der Anzahl der Elemente und deren Deklaration festgelegt sind. Sie werden
daher statisch genannt.
Wichtige Algorithmen der Informatik, wie sie z. B. in Betriebssystemen, Compilern,
Datenmanagement- und Informationssystemen angewandt werden, benutzen dynami-
sche Datentypen. Die dynamische Erzeugung und Veränderung dieser Datenstrukturen
erfordert zwingend die Anwendung des Datentyps Zeiger.
An einem einfachen Beispiel soll demonstriert werden, wie auf die Änderung des
Datenbestandes sehr variabel reagiert werden kann.
Wir verwenden hier die Datenstruktur lineare Liste. Das dargestellte Vorgehen soll Sie
in die Lage versetzen, sich die Handhabung weiterer dynamischer Datentypen darauf
aufbauend selbst zu erschließen.

4.2.4 Lineare Listen


Eine lineare Liste (auch einfach verkettete Liste) ist ein Datentyp, eine Datenstruktur,
deren Größe bei Bedarf zu- oder abnehmen kann. Im Gegensatz zu Feldern sind die ein-
zelnen Listenelemente nicht nacheinander im Speicher abgelegt, sondern die Speicher-
orte werden mit absoluten Adressen referenziert. Listen werden in C mithilfe des
Datentyps Zeiger und des Datentyps Datensatz realisiert.
Eine einfach verkettete Liste besteht aus Knoten (engl. nodes). Ein Knoten oder ein
Datensatz der Liste besteht aus Daten, Datenobjekten oder Datenstrukturen, und einem
Zeiger, der den nächsten Knoten der Liste referenziert.

Daten Zeiger

Abb. 4.1: Listenelement

Die Liste entsteht durch Verkettung mehrerer Listenelemente. Dabei besitzen der Listen-
anfang (oft Kopf genannt) und das Listenende (Schwanz) eine herausgehobene Bedeu-
tung. Abb. 4.2 zeigt den prinzipiellen Aufbau einer linearen Liste.

Listenanfang,
Listenkopf Listenende

NULL

Nutzdaten Zeiger nächstes Element

Abb. 4.2: Schematische Darstellung einer linearen Liste

INM12 47
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

Aus dieser Darstellung lassen sich folgende Eigenschaften für eine lineare Liste ableiten:

Aufbau einer Liste:

• Es gibt genau ein erstes Listenelement (ohne Vorgänger), den Kopf.


• Es gibt genau ein letztes Listenelement (ohne Nachfolger), den Schwanz.
• Alle anderen Elemente besitzen genau einen Vorgänger und genau einen Nachfolger.
Um das generelle Vorgehen beim Erzeugen linearer Listen darzustellen, betrachten wir
folgendes einfache Beispiel.

Beispiel 4.8:
Die grundlegende Idee der Listen ist, Listenelemente aneinanderzureihen, indem ein
Zeiger innerhalb eines Datensatzes auf das jeweils folgende Element verweist. Die-
ser Zeiger referenziert eine Adresse, die dem gleichen Typ wie der Datensatz selbst
entspricht. Ein Listenelement enthält einen Zeiger auf die nachfolgende Liste.
Als Beispiel betrachten wir eine lineare Liste, deren Daten nur aus einer ganzen Zahl
bestehen.
Dafür ergibt sich für ein Listenelement folgende Deklaration:
struct LListe {
int zahl;
struct LListe *next;
};

Es werden drei Datensätze isoliert durch dynamische Speicherreservierung auf dem


Heap bereitgestellt und jeweils ein int-Wert gespeichert. Schließlich erfolgt in Ein-
zelanweisungen eine Verkettung zur Liste, wobei der Listenkopf LKopf zusätzlich
markiert wird. Nachdem die Datenstruktur LListe vollständig aufgebaut wurde,
ist das Auflisten der Listenelemente möglich.

1 // Programm Liste_von_Hand.c
2 #include <stdio.h>
3 #include <stdlib.h>
4 struct LListe {
5 int zahl;
6 struct LListe *next;
7 };
8
9 int main(){
10
11 struct LListe *e1,*e2,*e3;
12 int i=1;
13 e1 = (struct LListe*) malloc
14 (sizeof(struct LListe));
15 e1->zahl = 11;
16 e1->next = NULL;
17
18 e2 = (struct LListe*) malloc
19 (sizeof(struct LListe));
20 e2->zahl = 22;
21 e2->next = NULL;

48 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

22
23 e3 = (struct LListe*) malloc
24 (sizeof(struct LListe));
25 e3->zahl= 33;
26 e3->next = NULL;
27
28 struct LListe *LAnfang;
29
30 LAnfang=e1;
31 e1->next = e2;
32 e2->next = e3;
33 e3->next = NULL;
34
35 struct LListe *temp=LAnfang;
36 while(temp != NULL){
37 printf("\n%i. Element mit Wert : %d",
38 i,temp->zahl);
39 temp=temp->next;
40 i++;
41 }
42 }

Code 4.4: Programm Liste_von_Hand.c

Bei Abarbeitung ergibt sich folgendes Ausgabebild:


1. Element mit Wert: 11
2. Element mit Wert: 22
3. Element mit Wert: 33

4.2.5 Einfache Listenoperationen


Die Beschreibung des Datentyps lineare Liste verlangt neben der Darstellung von
Anordnung und Struktur auch Aussagen zu möglichen Operationen mit den Daten-
objekten. Wir wenden uns diesen beiden Aspekten zu:
Es gibt mehrere Implementationsansätze zum Arbeiten mit linearen Listen. Im kommen-
den Beispiel folgen wir der Darstellung von Abb. 4.2. Der Listenanfang repräsentiert
zugleich das erste Listenelement. Das Listenende entspricht dem Listenelement, dessen
Zeiger auf das Folgeelement den Wert NULL besitzt. Besitzt eine Liste nur ein Element,
verkörpert dies gleichzeitig Listenanfang und Listenende.

Beispiel 4.9:
Wir nutzen die bereits in Beispiel 4.8 vorgenommene Deklaration für ein Listen-
element vom Datentyp struct LListe. Den Quelltext der im main-Modul ver-
wendeten Anweisungen finden Sie in Code 4.10 am Ende dieses Beispiels.
struct LListe {
int zahl;
struct LListe *next;
};

Darauf aufbauend lässt sich eine Liste erzeugen. Dieser Aufbau erfolgt schrittweise.
Zuerst stellen wir das erste Listenelement, den Listenkopf LKopf, bereit:

INM12 49
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

struct LListe *LKopf;


LKopf = (struct LListe*) malloc (sizeof(struct LListe));

Da der Listenkopf ein Element der Liste ist, wird auch er mit Daten belegt. Für das
erste Element tragen wir für zahl den Wert 1 ein. Der Zeiger next ist auf NULL
zu setzen, da kein weiteres Element in der Liste existiert:
LKopf->zahl=1;
LKopf->next=NULL;

Dieses Kopfelement lässt sich z. B. mit folgender Anweisung ausgeben:


printf("Ausgabe Kopfelement:\nzahl: %d next: %p\n\n",
LKopf->zahl,LKopf->next);

Ausgabe:
Ausgabe Kopfelement:
zahl: 1 next: 00000000

Dieses Kopfelement entspricht aktuell auch dem Listenende.


Listenelemente hinzufügen

Nachdem der Listenkopf erstellt wurde, sollen weitere Listenelemente an die Liste
angefügt werden. Wir definieren dazu die Funktion addLelem.
Damit eine solche Funktion sinnvoll arbeiten kann, wird der Bezug zur Liste herge-
stellt, indem das aktuell betrachtete Listenelement übergeben wird.
Dazu muss dieses aktuelle Listenelement deklariert und initialisiert werden. Für den
Entwicklungsstand der Liste gilt:
struct LListe *aktElem;
aktElem=LKopf;

An dieses Element ist der neue Listenknoten anzufügen, der nun selbst als neues
aktuelles Element als return-Parameter zurückgegeben wird.
Somit ergibt sich die Funktionsdeklaration als Schnittstelle mit den formalen
Parametern bezug vom Typ struct LListe und z vom Typ int:
struct LListe* addLelem(struct LListe *bezug, int z)

In der Funktion wird ein lokales neues Element durch dynamische Speicherreservie-
rung erzeugt, mit dem Wert z (input) belegt, in die Listenstruktur eingekettet und
diese schließlich zurückgegeben (output).

1 struct LListe* addLelem(struct LListe *bezug, int z){


2 struct LListe *neuesElement;
3
4 if(bezug->next == NULL) {
5 bezug->next =
6 (struct LListe*) malloc (sizeof(struct LListe));
7 neuesElement= bezug->next;
8 neuesElement->zahl=z;
9 neuesElement->next=NULL;
10 return neuesElement;

50 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

11 }
12 }

Code 4.5: Funktion addLelem zu Programm Liste.c

Erläuterung:

Zeile 1: Definiert das Funktionsinterface.


Zeile 2: Der Bezeichner neuesElement vom Typ struct LListe* wird
deklariert.
Zeilen 4–11: Nur wenn das Listenende vorliegt, kann ein Anfügen eines neuen Lis-
tenelementes erfolgen.
Zeilen 5, 6: Es wird nach dem aktuellen Element dynamisch Speicherplatz für
das neu anzufügende bereitgestellt.
Zeile 7: Dieser Speicherplatz kann jetzt über den Bezeichner neuesElement
angesprochen werden.
Zeilen 8, 9: Der übergebende int-Wert und der Zeigerbezug für das neue
Listenende werden gesetzt.
Zeile 10: Das neue Listenelement wird als Ergebnis über die return-Anwei-
sung bereitgestellt und liefert ein neues aktuelles Element zurück.
Um die Funktion wie besprochen zu nutzen, wird als Bezug zur Liste das aktuelle
Element übergeben. Zur Erinnerung: Der Variablen aktElem wurde der Wert vom
Listenkopf LKopf zugewiesen.
Mit diesem Vorgehen lassen sich jeweils weitere neue Listenelemente hinzufügen.
aktElem=addLelem(aktElem,11);
aktElem=addLelem(aktElem,22);
aktElem=addLelem(aktElem,33);

Listenelemente ausgeben

Bei Überlegungen zur Ausgabe aller Listenelemente findet man folgenden Funkti-
onsprototypen: void printListe(struct LListe *bezug).
Eine Funktion zum Auflisten von Werten hat in der Regel den Rückgabetyp void.
Die gesamte Liste muss übergeben werden, d. h., als Argument beim Aufruf wird der
Listenkopf benutzt. Wir benutzen zum Durchlauf durch die Liste einen lokal dekla-
rierten Zeiger akt. Dieser wird beim Ausgeben listenelementweise bis zum Liste-
nende durch die Liste bewegt. Die Ausgaben werden dabei um einen Zähler ergänzt.
Der Aufruf erfolgt mit dem Bezug zum Listenkopf: printListe(LKopf);

1 void printListe(struct LListe *bezug){


2 struct LListe *akt = bezug;
3 int zaehler=1;
4
5 while(akt != NULL){
6 printf("%d. Element mit Zahl: %d\n",
7 zaehler,akt->zahl);
8 akt=akt->next;

INM12 51
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

9 zaehler++;
10 }
11 }

Code 4.6: Funktion printListe zu Programm Liste.c

Erläuterung:

Zeile 1: Definiert den Funktionsprototyp.


Zeile 2: *akt: Zeiger zum Durchlaufen der Liste, der den übergebenen
Listenanfang referenziert.
Zeilen 5–10: Das Ausgeben der Listenelemente erfolgt so lange, bis das Listenen-
de erreicht ist.
Der Aufruf der printListe-Funktion führt aktuell zur Ausgabe:
1. Element mit Zahl: 1
2. Element mit Zahl: 11
3. Element mit Zahl: 22
4. Element mit Zahl: 33

Listenelemente suchen

Um Listenelemente zu suchen, wird ähnlich wie beim Ausgeben aller Elemente die
gesamte Liste durchlaufen und deren Knoten mit dem Suchkriterium verglichen.
Wird das Element gefunden, bricht die Suche ab, das Element wird zurückgegeben.
Ist das Listenelement nicht vorhanden, erfolgt die Rückgabe von NULL.
In der aufrufenden Funktion, in unserem Beispiel im main-Programm, wird ein
Bezeichner element zur Verfügung gestellt, um die weitere Verarbeitung des
gefundenen Elements zu sichern:
struct LListe *element;

1 struct LListe* getLelem(struct LListe *bezug,int such){


2 struct LListe *akt=bezug;
3
4 while(akt!=NULL) {
5 if (akt->zahl ==such)
6 return akt
7 akt=akt->next;
8 }
9 return NULL;
10 }

Code 4.7: Funktion getLelem zu Programm Liste.c

Erläuterung:

Zeile 1: Definiert das Funktionsinterface.


Zeile 2: Mit *akt wird ein Zeiger zum Durchlaufen der Liste bereitgestellt.
Zeilen 4–8: Die Suche wird so lange durchgeführt, bis das Listenende erreicht ist
oder das Element (s. Zeile 6) gefunden und zurückgegeben wurde.

52 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

Zeile 9: Der Rückgabewert ist NULL, wenn kein Element ermittelt werden
konnte.
Der Aufruf von getLelem erfolgt mit dem Bezug zum Listenkopf:
element = getLelem(LKopf,33);

Das gefundene Listenelement entspricht im dargestellten Aufruf dem Listenende.


Dies wird durch eine Kontrollausgabe mit folgender Anweisung deutlich:
printf("\nErgebnis Suche:\nzahl: %d next: %p\n\n",
element->zahl,element->next);

Ausgabe:

Ergebnis Suche:
zahl: 33 next: 00000000

Listenelemente nach einem Knoten ausketten und danach löschen

Beim Löschen von Listenelementen müssen verschiedene Sonderfälle beachtet wer-


den. Wir wenden uns dem Fall zu, dass ein Listenelement nach einem bestimmten
Listenknoten gelöscht werden soll.
Soll ein bestimmtes Element gelöscht werden, wird wieder mit der bereits bekannten
Schleife listenelementweise nach dem Vorgänger in der Liste gesucht. Ist er gefunden
worden, wird der Zeiger vom Vorgänger auf den Nachfolger des zu löschenden Ele-
ments gestellt. Nachdem das Element erfolgreich „ausgekettet“ wurde, kann es end-
gültig gelöscht werden.
Man kann sich das Vorgehen in folgender Weise verdeutlichen:

Vorgänger Löschkandidat Nachfolger

... ...

Finden des Vorgängerknotens

... ...

„Ausketten“ des zu löschenden Knotens

... ...

Löschen des „ausgeketteten“ Knotens

Abb. 4.3: Schematische Darstellung zum Löschen eines Knotens

INM12 53
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

1 void loescheLelem(struct LListe *bezug, struct LListe


*element){
2 struct LListe *akt=bezug;
3
4 while(akt->next != NULL) {
5 if(akt->next == element){
6 akt->next=element->next;
7 free(element);
8 break;
9 }
10 akt=akt->next;
11 }
12 }

Code 4.8: Funktion loescheLelem zu Programm Liste.c

Erläuterung:

Zeilen 1–2: Definiert den Funktionsprototyp.


Zeile 2: *akt : Zeiger zum Durchlaufen der Liste.
Zeilen 4–11: Die Suche wird so lange durchgeführt, bis das Listenende erreicht ist
oder das Element gefunden wurde.
Zeile 6: Wenn der Vorgängerknoten gefunden wurde, wird der Nachfolger-
knoten des zu löschenden Knotens zum Nachfolgerknoten des Vor-
gängers.
Zeile 7: Der Speicherplatz des zu löschenden Knotens wird freigegeben.
Zeile 8: Die Suche wird beendet.
Im Aufruf erfolgt mit dem Bezug zum Listenkopf die Übergabe des zu löschenden
Elements: loescheLelem(LKopf,element);
Das aktuelle Element nach der letzten Suche war das Listenende (zahl: 33
next: 00000000). Dieses Listenelement wird somit gelöscht.

Gibt man nach diesem Löschvorgang die Liste wieder aus, ergibt sich die Ausgabe:
1. Element mit Zahl: 1
2. Element mit Zahl: 11
3. Element mit Zahl: 22

Löschen am Listenanfang:

Da das erste Listenelement dem Listenkopf entspricht, kann man dieses Element
nicht einfach löschen, denn der Listenkopf gewährt den Listenzugang.
Der Algorithmus dafür ist relativ schnell gefunden. Der Nachfolger des zu löschen-
den Listenkopfes muss als neuer Listenkopf festgelegt werden.
Immer dann, wenn Parameter direkt verändert werden sollen, bietet sich an, die Pa-
rameterübergabe „call by reference“ zu nutzen und bei Aufruf die Adresse des Argu-
ments zu übergeben. Da es sich bei dem Listenkopf bereits um einen Zeiger handelt,
entsteht hier die Struktur Zeiger auf Zeiger.

54 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

1 void loescheKopf(struct LListe ** zz){


2 (*zz)=(*zz)->next;
3 }

Code 4.9: Funktion loescheKopf zu Programm Liste.c

In Zeile 2 erfolgt das Überschreiben der Adresse des Listenkopfs (erstes Element der
Liste) mit der Adresse des Nachfolgers vom Listenkopf (zweites Element der Liste).
Der Aufruf hat in folgender Art zu erfolgen: loescheKopf(&LKopf);
Die dargestellten Betrachtungen entsprechen den Anweisungen des folgenden main-
Programms:

1 int main(){
2 struct LListe *LKopf;
3 struct LListe *aktElem;
4 struct LListe *element;
5 LKopf = (struct LListe*) malloc (sizeof(struct
6 LListe));
7
8 LKopf->zahl=1;
9 LKopf->next=NULL;
10
11 printf("Ausgabe Kopfelement:\nzahl: %d next: %p\n\n",
12 LKopf->zahl,LKopf->next);
13 aktElem=LKopf;
14 aktElem=addLelem(aktElem,11);
15 aktElem=addLelem(aktElem,22);
16 aktElem=addLelem(aktElem,33);
17
18 printListe(LKopf);
19
20 element = getLelem(LKopf,33);
21 printf("\nErgebnis Suche:\nzahl: %d next: %p\n\n",
22 element->zahl,element->next);
23
24 loescheLelem(LKopf,element);
25 printListe(LKopf);
26
27 element = getLelem(LKopf,1);
28 printf("\nErgebnis Suche:\nzahl: %d next: %p\n\n",
29 element->zahl,element->next);
30
31 printf("Loeschen Kopfelement:\n");
32 loescheKopf(&LKopf);
33 printListe(LKopf);
34
35 return 0;
36 }

Code 4.10: Funktion main zu Programm Liste.c

Die Abarbeitung führt zum folgenden Ausgabebild:


Ausgabe Kopfelement:

INM12 55
© HfB, 02.12.20, Berk, Eric (904709)

4 Dynamische Speicherverwaltung

zahl: 1 next: 00000000

1. Element mit Zahl: 1


2. Element mit Zahl: 11
3. Element mit Zahl: 22
4. Element mit Zahl: 33

Ergebnis Suche:
zahl: 33 next: 00000000

1. Element mit Zahl: 1


2. Element mit Zahl: 11
3. Element mit Zahl: 22

Ergebnis Suche:
zahl: 1 next: 00640DE8

Loeschen Kopfelement:
1. Element mit Zahl: 11
2. Element mit Zahl: 22

Zusammenfassung

Mit dynamischen Datentypen werden in C Datenstrukturen realisiert, deren Größe oder


Form zur Übersetzungszeit noch nicht festliegen und die sich während der Programm-
ausführung verändern können.
Bei der Implementierung solcher Datenstrukturen benötigen die einzelnen Strukturele-
mente neben der eigentlichen Information zusätzlich eine Referenz auf ein oder mehrere
weitere Elemente desselben Typs. Dazu wird der Datentyp Zeiger/Pointer benötigt, mit
dessen Hilfe Verkettungen möglich sind.
Typische Operationen sind neben der Erzeugung der dynamischen Datenstruktur das
Hinzufügen und Löschen von Elementen.
Eine verkettete Folge von Elementen eines Datentyps nennt man eine lineare Liste.
Lineare Listen sind ein einfacher Vertreter dynamischer Datenstrukturen.
Am Beispiel der einfach verketteten Liste haben Sie das prinzipielle Herangehen
kennengelernt.

56 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Dynamische Speicherverwaltung 4

Aufgaben zur Selbstüberprüfung

4.1 Setzen Sie die folgende Datenstruktur aus Kapitel 2 des Heftes INM11 als lineare
Liste um.
struct datum{int tag, monat, jahr };

struct person { char Name[35], Vomame[15];


int Nummer;
datum geboren;
};

Implementieren Sie ein Programm, das einen Listenkopf erzeugt und die Funk-
tionen zum Anfügen eines Listenelements und zur Ausgabe der linearen Liste
enthält. Testen Sie die Funktionalität mit mindestens drei Listenelementen.
4.2 Erweitern Sie das Beispiel 4.9 um die Funktion zaehlLElement, die die Anzahl
der aktuellen Listenelemente ermittelt und als Ergebnis liefert. Überprüfen Sie Ihre
Implementation z. B. durch folgende Anweisung:
printf("\nAnzahl Listenelemente: %d\n",zaehlLElem(LKopf));

4.3 Erweitern Sie das Beispiel 4.9 um die Funktion addLelementAnf, die am
Listenkopf ein neues erstes Element einfügt. Orientieren Sie sich bei der Lösung
auch an der Funktion loescheKopf.

INM12 57
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Program-


mierung mit C++
Mit der Objektorientierung als Methode und Technologie wurde ein Paradigmen-
wechsel in der Informatik eingeleitet. Diese neue Herangehensweise beim Erstel-
len von Softwaresystemen dient der besseren Komplexitätsbewältigung, eröffnet
Möglichkeiten zur Wiederverwendung von Softwarekomponenten und gestattet
eine strikte Trennung zwischen Implementation und Anwendung und damit ins-
gesamt einen Qualitätsgewinn des Softwareprodukts.
Programmiersprachen wie C# und Java sind Vertreter objektorientierter Pro-
grammiersprachen. Auch C++ gestattet die Umsetzung dieser Programmier-
technologie. Die Sprache C wurde zu diesem Zweck um Sprachelemente für
objektorientiertes Programmieren erweitert.
Das Ziel von Kapitel 5 besteht darin, Sie an die grundlegenden Begriffe und
typischen Vorgehensweisen bei der objektorientierten Programmierung anhand
von C++ heranzuführen. Als durchgängiges Beispiel benutzen wir die Definition
einer Klasse Rechteck, die schrittweise erweitert werden soll.
Die folgenden Quelltexte sollen helfen, auf der Basis von jeweils kurz kommen-
tierten Beispielen einen praktischen Zugang zur Nutzung der Sprache C++ zur
objektorientierten Programmierung zu geben und erste Lösungen praktisch um-
zusetzen. Die gegebenen Erklärungen sind dabei nicht umfassend, sodass für die
objektorientierte Programmierung in ihrer ganzen Fülle weitere Informationen
notwendig sind.

5.1 Spracherweiterungen
C++ ist eine objektorientierte Weiterentwicklung von C. Die Sprache enthält neben
Spracherweiterungen als Voraussetzung für das objektorientierte Programmieren
Sprachelemente zur Nutzung der eigentlichen objektorientierten Funktionalität.
Die wesentlichsten nichtobjektorientierten Erweiterungen werden im Folgenden dem
objektorientierten Programmieransatz vorangestellt.

5.1.1 Ein- und Ausgabe

Beispiel 5.1:
Für die einfache Ein- und Ausgabe in C++ stehen die Funktionen cin und cout
aus der Headerdatei iostream zur Verfügung. Die veraltete Datei iostream.h
wird von aktuellen Compilern nicht mehr genutzt.
Das Grundgerüst eines C++-Programms besitzt folgendes Aussehen.

1 //Programm Ein-Ausgabe.cpp
2 #include <iostream>
3 using namespace std;
4
5 int main(void){
6 double zahl;

58 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

7 cout << "Geben Sie eine Zahl ein: ";


8 cin >> zahl;
9 cout<<"Sie haben die Zahl "<<zahl<<
10 " gerade eingegeben."<<endl;
11 return 0;
12 }

Code 5.1: Programm Ein_Ausgabe.cpp

Bemerkung:
Der Operator „<<“ zeigt auf die Ausgabe, „>>“ zeigt auf die Variable.
Die Standardbibliothek von C++ wird im Namensraum std verwaltet. Mit using
namespace std; werden diese Funktionen im Programm verfügbar gemacht.

Ohne diese Angabe in Zeile 2 müsste jeweils std::cin oder std::cout


geschrieben werden.
Der Manipulator endl (end-line) bzw. std::endl kann für eine Zeilenschaltung
benutzt werden.
Es wird die für C++-Quelltexte übliche Schreibweise int main (void) benutzt.

5.1.2 Schlüsselwort „const“

Beispiel 5.2:
Es wird möglich, konstante Werte mit dem Schlüsselwort const zu vereinbaren.
Der Compiler überwacht dann diese Festlegung und meldet eventuell auftretende
Schreibzugriffe.

1 //Programm const.cpp
2 #include <iostream>
3 using namespace std;
4
5 int main(void){
6 const int zahl=1;
7 zahl=2; // unzulässig
8 const int feld[] ={1,2,3};
9 feld[2]=6; // unzulässig
10 const int* z1=feld;
11 *z1=7; // unzulässig
12 z1=&zahl; // zulässig
13 const int *const z2=&feld[0];
14 z2=&feld[1]; //unzulässig
15 return 0;
16 }

Code 5.2: Programm const.cpp

INM12 59
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Erläuterung:
Zeilen 6, 8: Werteveränderungen der int-Variablen sind nicht zulässig.
Zeile 10: Vereinbarung eines Zeigers auf const int, die Werteveränderung
in Zeile 11 ist nicht zulässig.
Zeile 13: Vereinbarung eines const-Zeigers auf const int, die Werte-
veränderung in Zeile 14 ist nicht zulässig.

5.1.3 Standardargumente
Eine neue Möglichkeit bei der Nutzung von Funktionen besteht darin, bestimmten
Argumenten sogenannte Standardwerte zuzuordnen. Diese Angabe erfolgt bei der
Funktionsdeklaration.
Dabei müssen die als Standardargumente genutzten Funktionsparameter immer an das
Ende der Parameterliste gestellt werden.

Beispiel 5.3:
Im Beispiel erwartet funktion zwei Argumente. Wird die Funktion mit einem
Argument aufgerufen, wird dieses an den formalen Parameter x übergeben. Der
Parameter y erhält den float-Wert 1.5 zugewiesen.

1 //Programm_Standardargumente.cpp
2 #include <iostream>
3 using namespace std;
4
5 float funktion(float x, float y=1.5f);
6
7 int main(void){
8 float a=1.8f,b=-5.2f, ergebnis1,ergebnis2;
9
10 ergebnis1=funktion(a,b); //Aufruf mit 2 Argumenten
11 ergebnis2=funktion(a);
12
13 cout<<"Ergebnis1: " <<ergebnis1<<'\n'
14 <<"Ergebnis2:"<<ergebnis2;
15 return 0;
16 }
17
18 float funktion (float x, float y){
19 return x+y;
20 }

Code 5.3: Programm Standardargumente.cpp

Ausgabe:
Ergebnis1: -3.4
Ergebnis2: 3.3

60 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

5.1.4 Überladen von Funktionen

Beispiel 5.4:
Unter dem Überladen von Funktionen versteht man hier eine Möglichkeit, Funk-
tionen gleichen Namens, aber mit unterschiedlicher Anzahl und Art von Funktions-
argumenten parallel zu verwenden. Entsprechend dem Aufrufkontext entscheidet
der Compiler, welche Form benutzt werden muss.

1 // Programm F_Ueberladen.cpp
2 #include <iostream>
3 using namespace std;
4 float berechnung(float,float,int);
5 float berechnung(float,float);
6
7 int main(){
8 int c;
9 float a,b;
10 cout << "\nWerte-Eingabe fuer a,b und c: ";
11 cin>> a >>b >>c;
12
13 switch (c){
14 case 0: cout <<"undefiniert";
15 break;
16 case 1: cout <<"Ergebnis: "<< berechnung(a,b);
17 break;
18 default:cout <<"Ergebnis: "<< berechnung (a,b,c);
19 }
20 return 0;
21 }
22
23 float berechnung (float a, float b)
24 {return a*b;}
25
26 float berechnung (float a, float b, int c)
27 {return a*b/c;}

Code 5.4: F_Ueberladen.cpp

Diese Entscheidung wird im Beispiel abhängig vom Wert der Variablen c getroffen.
Ausgabe:
Werte-Eingabe fuer a,b und c: 1.2 2.0 1
Ergebnis: 2.4

Werte-Eingabe fuer a,b und c: 1.2 2.0 2


Ergebnis2: 1.2

INM12 61
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

5.1.5 Referenzen

Beispiel 5.5:
In C++ besteht nun die Möglichkeit, für vereinbarte Bezeichner Referenzvariablen
zu definieren. Eine Referenz ist ein Alias-Name für eine bereits existierende Variab-
le. Damit wird der gleiche Bezeichner (Adresse und Wert) angesprochen. Referenzen
sind auch als Parameter von Funktionen zu benutzen. Damit entfällt die von Zeigern
bekannte aufwendige Schreibweise.

1 // Programm referenz.cpp
2 #include <iostream>
3 using namespace std;
4 void ztausch (int&,int&);
5
6 int main(void){
7 int zahl1=10,&a=zahl1;
8 int zahl2=20,&b=zahl2;
9 cout << "\nAusgangswerte: "<<zahl1<< " "<<zahl2;
10 cout << "\nAusgangswerte: "<<a<< " "<<b;
11 ztausch(zahl1,zahl2);
12 cout<<endl;
13 cout << "\nErgebniswerte: "<<zahl1<<" "<< zahl2;
14 return 0;
15 }
16 void ztausch(int &ap,int &bp)
17 {
18 int vhilf;
19 vhilf=ap;
20 ap=bp;
21 bp=vhilf;
22 }

Code 5.5: Programm referenz.cpp

Erläuterung:
Zeile 4: Prototypanweisung für ztausch. Die beiden Parameter werden
als Referenz vermittelt.
Zeilen 7, 8: a wird als Referenz zu zahl1 und b als Referenz zu zahl2
deklariert.
Zeilen 9, 10: Beide Anweisungen erzeugen die gleiche Ausgabe.
Zeilen 16–22: Der Zugriff auf zahl1 und zahl2 erfolgt über ap und bp. Die
Bezeichner ap und bp sind wie zahl1 und zahl2 selbst zu
verwenden.
Ausgabe:
Ausgangswerte: 10 20
Ausgangswerte: 10 20

Ergebniswerte: 20 10

62 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Weitere Sprachergänzungen in C++:


• Es existiert ein Datentyp bool für logische Werte, der die Werte true
oder false annehmen kann.
• Um Speicher auf dem Heap zu benutzen, stehen neue Operatoren new
und delete zur Verfügung.
• Es ist möglich, Funktionen inline zu vereinbaren. Da jeder Funktionsaufruf
Systemressourcen beansprucht, werden inline vereinbarte Funktionen mit
ihrem Funktionskörper in den Programmcode eingefügt. Ein solches Vorgehen
wird auch als Zeilenexpansion bezeichnet. Eine inline-Funktion wird schneller
als eine „normale“ Funktion ausgeführt.

5.2 Objektorientierung als Programmieransatz


In der modernen Softwareentwicklung hat sich die objektorientierte Programmierung
durchgesetzt. Dieses Vorgehen gestattet die Modellierung von in der Realität ablaufen-
den Prozessen mit komplexem Verhalten auf einer hohen Abstraktionsstufe.
Im Mittelpunkt dieses Ansatzes stehen Objekte, die reale Gegenstände, Personen oder
abstrakte Sachverhalte sein können. Die Beschreibung eines Objektes erfolgt dabei
durch wesentliche Eigenschaften und Verhaltensweisen, die aus der Sicht einer Auf-
gabenstellung wichtig oder relevant sind.
Bei der bisherigen Nutzung von Datentypen, etwa bei der Verwendung von Feldern und
Listen, waren Operationen auf diesen Daten abhängig von der konkreten Datenstruktur
als zusätzliche Funktionen zu implementieren.
Der Objektbegriff vereint im Gegensatz dazu Datenstruktur und Operationen über den
Daten. Diese ganzheitliche Sicht auf Daten und Operationen (in der Software Funktio-
nen) ist typisch für den Ansatz eines Denkens in Objekten und Prozessen.
Die als Methoden bezeichneten Funktionen bestimmen das Verhalten des Objektes und
stellen die Schnittstellen nach außen, d. h. zum Anwender, dar. Dadurch wird es mög-
lich, die Implementation von der Anwendung zu trennen.
Objekte mit gleichen Eigenschaften und Verhaltensweisen gehören dem gleichen
Objekttyp an, der durch den Begriff Klasse beschrieben wird. Eine Klasse beschreibt die
Menge gleichartiger Objekte formal und stellt damit einen „Bauplan“ für Objekte dieses
Datentyps zur Verfügung.
Das Objekt (die Instanz) ist ein Modell einer konkret in der Realität existierenden
Person, Sache bzw. eines gedanklichen Konstrukts. Diese Instanzen/Objekte können im
Programm genutzt werden.
Die Deklaration einer Klasse liegt zur Übersetzungszeit des Programms vor. Zur Laufzeit
werden gemäß der in der Klasse hinterlegten Bauvorschrift Objekte instanziiert.

INM12 63
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Betrachten wir einen einfachen Sachverhalt:


Der Personalbestand einer Firma soll beispielsweise objektorientiert mithilfe einer
Klasse Person verwaltet werden. Je nach Zielstellung könnte jedes Objekt z. B. mit
den Attributen Name, Vorname, Postleitzahl, Wohnort, Straße, Hausnummer,
Geburtstag, Geburtsmonat, Geburtsjahr charakterisiert werden.

Notwendige Methoden, die eine konkrete Arbeit mit dieser Datenstruktur Person
ermöglichen, wären in den Funktionen ErfassePerson, SuchePerson,
DruckePerson, LöschePerson usw. zu implementieren.

Sowohl die Attribute als auch Methoden sind gemeinsam Bestandteil der Klassende-
klaration.
Dabei ist es auch möglich, die Attribute Postleitzahl, Wohnort, Straße,
Hausnummer nicht als Einzelattribute, sondern als Objekt einer weiteren Klasse,
etwa Anschrift, zu verwenden. In gleicher Weise könnte man für die Attribute
Geburtstag, Geburtsmonat, Geburtsjahr die Klasse Datum deklarieren. Hier
wird mit der Nachnutzung bereits bestehender Klassen ein weiteres Konzept der objekt-
orientierten Programmierung sichtbar.

Zusammenfassung:

Beim objektorientierten Programmieransatz wird mit dem Datentyp Klasse eine Daten-
kapsel zur Verfügung gestellt, die Daten (Attribute) und Operationen (Methoden) auf
diesen Daten vereint.

5.3 Klassendefinition, Konstruktoren, Destruktor


Die Klassendefinition gibt den Bauplan für konkrete Objekte einer Klasse vor. Der
Vorteil der zu definierenden Datenkapsel besteht in der Steuerung von Zugriffsmöglich-
keiten auf die Klassenbestandteile.
Man benutzt folgende Schreibweise:
class klassenname{
[zugriffsrecht: ] Attribute und Methoden
};

Zum Setzen der Zugriffsrechte stehen drei Schlüsselworte zur Verfügung:


• public: Das Element ist öffentlich (Zugriff von allen Programmteilen).
• protected: Das Element ist geschützt (Zugriff von der Klasse selbst oder einer davon
abgeleiteten Klasse).
• private: Das Element ist privat (Zugriff ist nur aus der Klasse selbst möglich).
Wird kein „zugriffsrecht“ angegeben, dann gilt „private“ als Standard.

64 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Etwas konkreter lässt sich schreiben:


class klassenname{
private:
//Deklaration der Attribute
public:
// Deklaration oder Definition der Methoden
};

Eine Klasse wird global definiert. Am Ende der Definition ist ein „;“ anzugeben.
Die Grundidee besteht in einer Datenkapselung. Ein direkter Zugriff auf die Attribute
der Klasse wird in der Regel unterbunden (private). Dieser Zugriff geschieht über die
entsprechenden Methoden (public). Die Methoden besitzen einen privilegierten Zugriff
auf die Datenelemente, d. h., es ist keine Parameterübergabe notwendig, um diese in den
Methoden zu nutzen.

5.3.1 Standardkonstruktor

Beispiel 5.6:
Betrachten wir eine Klasse Rechteck. Vereinfachend gehen wir davon aus, dass ein
Rechteck mit der Angabe zweier Seitenlängen a und b ausreichend beschrieben ist.
Gewöhnlich sollten die Werte der Attribute nur über Methoden – nach ihrer Auf-
gabe getter- (d. h. frage ab) und setter- (d. h. setze) Methoden genannt – verändert
werden können.
Die konkreten Objekte der Klasse Rechteck verfügen über zwei Attribute (a und b)
und über fünf Methoden:

1 //Programm Standardkonstruktor.cpp
2 #include <iostream>
3 using namespace std;
4
5 class Rechteck{
6 private:
7 float a,b;
8 public:
9 void setze_a(float x){a=x;}
10 float erhalte_a (void) {return a;}
11 void setze_b(float y){b=y;}
12 float erhalte_b (void) {return b;}
13 void ausgabe(void){
14 cout<<"\nRechteck: a= "<<erhalte_a()
15 <<" b= "<<erhalte_b()<<"\n";
16 }
17 };
18

INM12 65
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

19 int main(void){
20 Rechteck r;
21 r.ausgabe();
22 r.setze_a(3.1);
23 r.setze_b(8.0);
24 r.ausgabe();
25 return 0;
26 }

Code 5.6: Programm Standardkonstruktor.cpp

Die Objekte/Instanzen, d. h. die konkreten Vertreter der Klasse, werden mit der
Schreibweise Rechteck r erzeugt. Diese Bereitstellung erfolgt dabei über einen
sogenannten Konstruktor – eine spezielle Methode, die genauso benannt ist wie die
Klasse selbst, also Rechteck.
Da allerdings im Quelltext keine deklarierte Methode mit dem Namen Rechteck
als expliziter Konstruktor angegeben wurde, kommt hier der sogenannte Standard-
konstruktor zur Anwendung. Dieser Standardkonstruktor erzeugt das Objekt r,
initialisiert aber die Attribute nicht.
Die Anweisung r.ausgabe(); in Zeile 21 erzeugt z. B. folgende Ausschrift:
Rechteck: a= 3.76508e–039 b= 5.9539e–039.

Diese Initialisierung muss daher mit einer weiteren Funktion, hier z. B. 


setze_a bzw. setze_b, erfolgen.

Nach Abarbeitung der setter-Methoden stehen im Objekt r über r.a und r.b
Länge und Breite des Rechtecks zur Verfügung. Es wird ausgegeben: Rechteck:
a= 3.1 b= 8

Erläuterung:
Zeilen 6–7: Für die Aufnahme der Werte beider Seitenlängen sind
zwei float-Bezeichner vorgesehen. Diese werden im Sinne der
Datenkapselung private deklariert.
Zeile 8: Nach „außen“ bereitgestellte Methoden besitzen den
Zugriffsmodifizierer public.
Zeilen 9–12: Die Methoden zum Setzen und Abfragen der Klassenattribute
werden definiert.
Zeile 9: Das Attribut a erhält einen Wert.
Zeile 10: Der aktuelle Wert von a wird zurückgegeben.
Zeile 11: Das Attribut b erhält einen Wert.
Zeile 12: Der aktuelle Wert von b wird zurückgegeben.
Zeilen 13–16: Es erfolgt die Definition der Methode ausgabe. Die aktuellen
Werte der Attribute a und b werden nicht direkt, sondern über
die entsprechenden getter-Methoden bereitgestellt.
Zeile 20: Erzeugung der Instanz r durch Aufruf des Standardkonstruktors.

66 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Ausgabe:
Rechteck: a= 3.76508e-039 b= 5.9539e-039

Rechteck: a= 3.1 b= 8

5.3.2 Explizite Konstruktordefinition


Um Objekte von Klassen bereitzustellen, werden in der Regel die Konstruktoren explizit
angegeben. Dazu besitzen Klassen zur Verwaltung ihrer Objekte zwei spezielle Metho-
den: Konstruktoren und einen Destruktor.
Konstruktoren erhalten den gleichen Bezeichner wie die Klasse selbst. Diese Methode
wird zum Erzeugen einer Instanz, d. h. eines konkreten Objektes der Klasse, aufgerufen.
Sie sollte alle Daten mit einem Startwert initialisieren. Konstruktoren besitzen keinen
Rückgabetyp, sie erzeugen ein Objekt der Klasse.
Hat der Programmierer einen eigenen Konstruktor für die Klasse definiert, steht der
Standardkonstruktor, der automatisch bereitgestellt wurde, nicht mehr zur Verfügung.
Dieser Standardkonstruktor wird dadurch charakterisiert, dass er keine Parameter
besitzt. Man spricht daher auch vom parameterfreien Konstruktor.
Während es üblich ist, ein Objekt auf unterschiedliche Art und Weise durch unter-
schiedliche Konstruktoren zu erzeugen – in diesem Fall müssen mehrere Konstruktoren
definiert werden –, existiert nur ein Destruktor.
Destruktoren besitzen auch den gleichen Bezeichner wie die Klasse selbst, aber mit
einem vorangestellten „~“. Sie werden zum Zerstören der Instanz benötigt. Der von der
Klasse reservierte Speicher wird dabei freigegeben. Der Destruktor besitzt keinen Rück-
gabetyp und, im Gegensatz zum Konstruktor, niemals Parameter.
Der Destruktor wird automatisch aufgerufen. In der Praxis ist eine spezielle Imple-
mentation selten notwendig, z. B. aber dann, wenn ein Objekt während seiner Existenz
Ressourcen, z. B. dynamischen Speicher, angefordert hat. Dieser Speicher wäre vom
Destruktor wieder freizugeben.

Beispiel 5.7:
Die im Beispiel 5.6 vorliegende Definition der Klasse Rechteck erhält in diesem
Beispiel einen expliziten Konstruktor, der jeweils die Werte für die Attribute a
und b des Rechtecks setzt:
Rechteck(float x, float y)
{a=x;b=y;}

Die Nutzung erfolgt z. B. in der Art: Rechteck r1(5.5f,7.9f);


Weiterhin greifen wir auf einen Destruktor ohne Funktionalität zurück. In diesem
Fall hätte man seine Definition auch weglassen können: ~Rechteck(){}
Es ist darüber hinaus möglich, ein neues Objekt als Kopie eines bereits vorhandenen
zu erzeugen. Dazu wird ein Kopierkonstruktor automatisch zur Verfügung gestellt,
wenn dieser, wie in unserem Beispiel, nicht explizit definiert wurde. Unter bestimm-
ten Umständen (wenn sich z. B. Zeigervariablen unter den Attributen einer Klasse

INM12 67
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

befinden) ist dieser automatisch bereitgestellte Kopierkonstruktor jedoch nicht aus-


reichend, sodass in diesen Fällen der Programmierer selbst eine entsprechende Me-
thode zur Verfügung stellen muss.
Im Beispiel wird dieser Kopierkonstruktor mit der Anweisung in Zeile 25 Rechteck
r2=r1; benutzt.

Die Instanz r2 wird erzeugt und bekommt danach die Werte von r1 zugewiesen.
Eine weitere mögliche Schreibweise ist: Rechteck r2(r1);
Der Ausdruck r2=r1; (ohne Rechteck) stellt im Gegensatz dazu die Nutzung des
Zuweisungsoperators „=“ dar, der auch automatisch für Klassen bereitgestellt wird.

1 //Programm Konstruktoren.cpp
2 #include <iostream>
3 using namespace std;
4 class Rechteck{
5 private:
6 float a,b;
7 public:
8 Rechteck(float x, float y)
9 {a=x;b=y;}
10 ~Rechteck(){}
11 void setze_a(float x){a=x;}
12 float erhalte_a (void) {return a;}
13 void setze_b(float y) {b=y;}
14 float erhalte_b (void) {return b;}
15 void ausgabe(void){
16 cout<<"\nRechteck: a= "<<erhalte_a()
17 <<" b= "<<erhalte_b()<<"\n";
18 }
19 };
20 int main(void){
21 //Rechteck r nicht mehr moeglich
22 Rechteck r1(5.5f,7.9f);
23 //cout<<"\nRechteck: a= "<<r1.a<<" b= "<<r1.b<<"\n";
24 r1.ausgabe();
25 Rechteck r2=r1;
26 r2.setze_a(7.9f);
27 r2.ausgabe();
28 return 0;
29 }

Code 5.7: Programm Konstruktoren.cpp

Bemerkungen:
Zeilen 8, 9: Konstruktordefinition
Zeile 10: Destruktordefinition
Zeile 21: Der Standardkonstruktor kann nicht mehr genutzt werden, da we-
nigstens eine explizite Konstruktordefinition (Zeilen 8 und 9) erfolgt
ist.

68 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Zeile 23: Ein direkter Zugriff auf r1.a sowie r1.b ist in der main-Funk-
tion, also außerhalb der Klasse, nicht möglich. Der Compiler meldet
z. B.: [Error] 'float Rechteck::a' is private.
Zeile 25: Kopierkonstruktoraufruf, r2 entsteht als Kopie von r1.
In der Ausgabe des Programms sind die beiden Rechteckinstanzen erkennbar:
Rechteck: a= 5.5 b= 7.9

Rechteck: a= 7.9 b= 7.9

5.3.3 Initialisierungslisten
Initialisierungen von Attributen können auch in einem speziellen Bereich zwischen
Argumentliste und Funktionskörper erfolgen. Er wird durch einen Doppelpunkt ab-
getrennt und damit beginnt die sogenannte Initialisierungsliste. Sie kann zur Initiali-
sierung von Datenelementen genutzt werden.
Die Reihenfolge der Initialisierungen hängt von der Reihenfolge der Deklarationen der
Attribute der Klasse und nicht von der Reihenfolge in der Liste ab.
Während in einigen Fällen (Konstanten, Referenzen …) nur dieser Weg bleibt, ist die
Initialisierungsliste auch auf einfache Datentypen anwendbar.

Schreibweise:

Klassenname(Formalparameterliste): Initialisierungsliste {
Anweisungen
}

Beispiel 5.8:
In unserem Fall kann der Konstruktor für Rechteck unter Nutzung von einer Initia-
lisierungsliste auch folgende Notation besitzen (Zeile 9):
Rechteck(float x, float y): a(x), b(y) {}

Da die Initialisierung von a und b in der Initialisierungsliste erfolgt, bleibt der


Funktionsblock des Konstruktors leer.

1 // Programm Initialisierungsliste.cpp
2 #include <iostream>
3 using namespace std;
4
5 class Rechteck{
6 private:
7 float a,b;
8 public:
9 Rechteck(float x, float y): a(x), b(y) {}
10 ~Rechteck(){}
11 void setze_a(float x){a=x;}
12 float erhalte_a (void) {return a;}
13 void setze_b(float y) {b=y;}
14 float erhalte_b (void) {return b;}

INM12 69
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

15 void ausgabe(void){
16 cout<<"\nRechteck: a= "<<erhalte_a()
17 <<" b= "<<erhalte_b()<<"\n";
18 }
19 };
20
21 int main(void){
22 Rechteck r1(5.5 f.,7.9f);
23 r1.ausgabe();
24 Rechteck r2=r1;
25 r2.setze_a(7.9f);
26 r2.ausgabe();
27 return 0;
28 }

Code 5.8: Programm Initialisierungsliste.cpp

5.3.4 Verwendung von Konstruktoren mit Standardparametern


Der folgende Quelltext zeigt, dass mithilfe von Standardparametern von Funktionen
(vgl. Abschnitt 5.1.3), indem alle Parameter des Konstruktors wertmäßig vordefiniert
sind, der Standardkonstruktor bei der Angabe eines expliziten Konstruktors implizit mit
erzeugt werden kann.
Gleichzeitig wird im folgenden Beispiel die Klassendefinition in die zwei Teile Interface
und Implementation aufgeteilt. Es ist gängige Praxis, beide Teile in zwei Dateien mit
den Bezeichnungen klassenname.h und klassenname.cpp abzuspeichern
(s. Beispiel 5.10).
Um eine exakte Klassenzuordnung treffen zu können, wird der Klassenname vor dem
Methodennamen, durch den Sichtbarkeitsoperator/scope-Operator (::) getrennt, zu-
sätzlich angegeben:

Schreibweise:

Klassenname::Komponentenname

Beispiel 5.9:
Im Interface der Klasse Rechteck wird die Methode erhalte_a durch die Angabe
ihres Prototyps deklariert: float erhalte_a (void);
In der Implementation ist der Klassenname vor dem Methodennamen anzugeben:
float Rechteck::erhalte_a (void) {return a;}

Damit wird zum Ausdruck gebracht, dass es sich um die Methode erhalte_a aus
der Klasse Rechteck handelt.

1 //Programm Standardparameter.cpp
2 #include <iostream>
3 using namespace std;
4

70 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

5 //Interface der Klasse Rechteck


6 class Rechteck{
7 private:
8 float a,b;
9 public:
10 Rechteck(float x=1,float y=2);
11 ~Rechteck();
12 void setze_a(float);
13 float erhalte_a (void);
14 void setze_b(float);
15 float erhalte_b (void);
16 void ausgabe(void);
17 };
18
19 //Implementation der Klasse Rechteck
20 Rechteck::Rechteck(float x,float y){a=x;b=y;}
21 Rechteck::~Rechteck(){}
22 void Rechteck::setze_a(float x){a=x;}
23 float Rechteck::erhalte_a (void) {return a;}
24 void Rechteck::setze_b(float x){b=x;}
25 float Rechteck::erhalte_b (void) {return b;}
26 void Rechteck::ausgabe(void) {
27 cout<<"\nRechteck: a= "<<a<<
28 " b= "<<erhalte_b()<<"\n";
29 }
30
31 int main(void){
32 Rechteck r1;
33 r1.ausgabe();
34 Rechteck r2(1.1f);
35 r2.ausgabe();
36 Rechteck r3(5.5f,7.9f);
37 r3.ausgabe();
38 return 0;
39 }

Code 5.9: Programm Standardparameter.cpp

Bemerkungen:
Zeilen 6–17: Angabe des Klasseninterface der Klasse Rechteck.
Zeilen 20–29: Implementation der Klasse Rechteck.
Zeile 32: Es erfolgt der Konstruktoraufruf von Rechteck, es werden beide
Standardparameter benutzt.
Zeile 34: Es erfolgt der Konstruktoraufruf von Rechteck, es wird ein Stan-
dardparameter benutzt.
Zeile 36: Es erfolgt der Konstruktoraufruf von Rechteck, beide Parameter
werden gesetzt.

INM12 71
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Die Abarbeitung des Programms führt zu folgender Ausgabe:


Rechteck: a= 1 b= 2

Rechteck: a= 1.1 b= 2

Rechteck: a= 5.5 b= 7.9

5.3.5 Arbeit mit mehreren Dateien


In der Praxis werden Softwareprojekte arbeitsteilig bearbeitet. Oft besteht die Notwen-
digkeit, für ein vorgegebenes Interface die Implementation anzupassen. Die Abspeiche-
rung von Klasseninterface und Klassenimplementation in getrennten Dateien ist eine
wesentliche Voraussetzung für das effektive Update von Implementationen.
In einer Header-Datei mit dem Dateityp *.h bzw. *.hpp wird das Klasseninterface
bereitgestellt. Diese Datei ist dort einzubinden, wo die Schnittstelle, d. h. vorhandene
Attribute und Methoden der Klasse, bekannt sein müssen. Dies sind mindestens die Im-
plementationsdatei und das main-Modul.
Um zu verhindern, dass die Daten der Headerdatei nicht versehentlich mehrfach ein-
gebunden werden, wird der Präprozessor genutzt.
Die Grundidee besteht darin, in der Header-Datei einen Namen zu definieren und diese
Headerdatei nur dann einzubinden, wenn dieser Name erstmalig definiert wird. Wenn
die Datei erneut übersetzt wird, ergibt die #ifndef-Direktive false und die Zeilen bis
zur Direktive #endif werden ignoriert.

Beispiel 5.10:
In unserem Beispiel erstellen wir die Dateien rechteck.h sowie rechteck.cpp.
Die Datei rechteck.h besitzt folgendes Aussehen:

1 //Datei rechteck.h
2
3 #ifndef H_RECHTECK
4 #define H_RECHTECK
5
6 //Schnittstelle der Klasse Rechteck
7 class Rechteck{
8 private:
9 float a,b;
10 public:
11 Rechteck(float x=1,float y=2);
12 ~Rechteck();
13 void setze_a(float);
14 float erhalte_a (void);
15 void setze_b(float);
16 float erhalte_b (void);
17 void ausgabe(void);
18 };

72 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

19
20 #endif

Code 5.10: Headerdatei rechteck.h

Entsprechend beinhaltet die Datei rechteck.cpp die Implementation der


Klasse rechteck. Neben der Datei iostream ist hier das Klasseninterface
rechteck.h einzubinden. Eigene vom Programmierer erzeugte Headerdateien
werden mit " " gequotet. Da kein Pfad angegeben ist, wird diese Datei im aktuellen
Verzeichnis gesucht.

1 //Datei rechteck.cpp
2
3 #include <iostream>
4 using namespace std;
5
6 #include "rechteck.h"
7
8 //Implementation der Klasse Rechteck
9 Rechteck::Rechteck(float x,float y){a=x;b=y;}
10 Rechteck::~Rechteck(){}
11 void Rechteck::setze_a(float x){a=x;}
12 float Rechteck::erhalte_a (void) {return a;}
13 void Rechteck::setze_b(float x){b=x;}
14 float Rechteck::erhalte_b (void) {return b;}
15 void Rechteck::ausgabe(void) {
16 cout<<"\nRechteck: a= "<<a<<" b= "
17 <<erhalte_b()<<"\n";
18 }

Code 5.11: Implementationsdatei rechteck.cpp

Die dritte Datei wird schließlich durch das Testprogramm test.cpp bestimmt: Für
deren Übersetzung sind die Informationen aus rechteck.h notwendig.

1 //Datei Test.cpp
2
3 #include "rechteck.h"
4
5 int main(void){
6 Rechteck r1;
7 r1.ausgabe();
8 Rechteck r2(1.1f);
9 r2.ausgabe();
10 Rechteck r3(5.5 f.,7.9f);
11 r3.ausgabe();
12 return 0;
13 }

Code 5.12: main-Modul Test.cpp

Je nach Entwicklungsumgebung variierend ist es notwendig, ein Projekt zu erstellen,


das diese drei Dateien enthält und verwaltet.

INM12 73
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Für die Entwicklungsumgebung Dev-C++ ist über die Menüfolge „Datei → Neu →
Projekt“ ein C++-Projekt zu erzeugen, in das die genannten Dateien eingefügt wer-
den müssen. Im Menü „Projekt“ steht dafür u. a. der Menüpunkt „Zum Projekt hin-
zufügen“ zur Verfügung.

Abb. 5.1: Das Dev-C++-Projekt

5.4 Methoden
Die Methoden einer Klasse genießen privilegierten Zugriff auf die Datenelemente dieser
Klasse.
Betrachten wir weitere Methoden, die unsere Klasse Rechteck sinnvoll erweitern.

Beispiel 5.11:
Für die Klasse Rechteck sollen zusätzlich zwei Methoden flaeche und
ist_kleiner_als definiert werden. Dabei dient die Methode flaeche der Größenbe-
stimmung der Rechteckfläche. Die Methode ist_kleiner_als gestattet einen Größen-
vergleich von Rechtecken zueinander. Dabei soll ein Rechteck kleiner sein als ein an-
deres, wenn es eine kleinere Fläche besitzt.
Betrachten wir mögliche Umsetzungen:
float flaeche(void)

Der Wert der Rechteckfläche wird aus den Seitenlängen a und b berechnet. Das
Ergebnis wird als float-Größe zurückgegeben.
Die Methode flaeche lässt sich einfach in folgender Weise definieren:
float Rechteck::flaeche(void){
return a*b;
}

bool Rechteck::ist_kleiner_als (Rechteck r)

Die Methode prüft, ob ein Rechteck flächenkleiner als ein anderes Rechteck ist. Das
Ergebnis wird als logische Größe in der Deutung wahr oder falsch zurückgegeben.

74 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Die Methode ist_kleiner_als lässt sich in folgender Form umsetzen:


bool Rechteck::ist_kleiner_als (Rechteck r){
return (flaeche()<r.flaeche());
}

Dabei bezieht sich die Angabe mit vorangestelltem Parameter r auf das im
Parameterteil angegebene Objekt r (r.flaeche()). Angaben ohne Objektangabe
(flaeche()) beziehen sich auf das Objekt, das die Methode aufruft.

Mit dem Aufruf r1.ist_kleiner_als(r2) wird daher überprüft, ob das


Rechteck r1 flächenkleiner als das Rechteck r2 ist.
Die Umsetzung des erweiterten Sachverhalts führt zum Quelltext Code 5.13. Von
der Gestaltung her trennen wir Schnittstelle und Implementation der Klasse, organi-
sieren aber die Quelltextelemente für unseren Zweck in nur einer Datei.

1 // Programm Methoden.cpp
2 #include <iostream>
3 using namespace std;
4
5 class Rechteck{
6 private:
7 float a,b;
8 public:
9 Rechteck(float , float );
10 ~Rechteck();
11 void setze_a(float);
12 float erhalte_a (void);
13 void setze_b(float y);
14 float erhalte_b (void);
15 void ausgabe(void);
16 float flaeche(void);
17 bool ist_kleiner_als (Rechteck r);
18 };
19
20 Rechteck::Rechteck(float x , float y ){a=x;b=y;}
21 Rechteck::~Rechteck(void){}
22 void Rechteck::setze_a(float x){a=x;}
23 float Rechteck::erhalte_a (void) { return a;}
24 void Rechteck::setze_b(float y){b=y;}
25 float Rechteck::erhalte_b (void) { return b;}
26 void Rechteck::ausgabe(void)
27 {cout<<"\nRechteck: a= "<<a<<" b= "
28 <<erhalte_b()<<"\n";
29 }
30 float Rechteck::flaeche(void){
31 return a*b;
32 }
33 bool Rechteck::ist_kleiner_als (Rechteck r){
34 return(flaeche()<r.flaeche());
35 }
36

INM12 75
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

37 int main(void){
38 Rechteck r1(2.0f,2.0f);
39 r1.ausgabe();
40 Rechteck r2(5.0f,2.1f);
41 r2.ausgabe();
42 cout<<"\nFlaeche von r1: "<<r1.flaeche();
43 cout<<"\nFlaeche von r2: "<<r2.flaeche();
44 if(r1.ist_kleiner_als(r2))
45 cout<<"\n\nr1 ist kleiner r2"<<endl;
46 return 0;
47 }

Code 5.13: Programm Methoden.cpp

Im Verlauf des Programms entsteht das folgende Ausgabebild. Dabei erfolgt nur für
den hier zutreffenden Fall, dass r1 eine kleinere Fläche als r2 besitzt, eine Aus-
gabe.
Rechteck: a= 2 b= 2

Rechteck: a= 5 b= 2.1

Flaeche von r1: 4


Flaeche von r2: 10.5

r1 ist kleiner r2

5.5 Überladen von Operatoren

Beispiel 5.12:
Um die Größe zweier Rechtecke miteinander zu vergleichen, würde man sich doch
gern im Quelltext der Schreibweise r1<r2 statt der bisherigen Lösung
r1.ist_kleiner_als(r2) bedienen.
C++ sieht die Definition einer „<“-Relation für Rechtecke vor, indem man die
Methode des Überladens von Operatoren benutzt.
Mithilfe des Operatorüberladens können (fast) alle Operatoren für eine selbstde-
finierte Klasse mit einer neuen Bedeutung belegt werden. Das Prinzip besteht darin,
den Operator wie eine normale Funktion überladen zu können. Der Funktionsname
leitet sich aus dem festen Bezeichner operator, gefolgt vom Operatorzeichen
selbst, ab. In unserem Fall ergibt sich dann als Name operator<.
Die Definition der neuen Operator-Funktion oder Methode erfolgt in bereits
bekannter Weise, indem lediglich der Name anzupassen ist:
bool Rechteck::operator< (Rechteck r){
return(flaeche()<r.flaeche());
}

76 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Der Aufruf kann dabei sowohl in der Form r1.operator<(r2) – vgl.


r1.ist_kleiner_als(r2) – als auch in der Form r1<r2 erfolgen:

1 //Programm Operatoren.cpp
2 #include <iostream>
3 using namespace std;
4
5 class Rechteck{
6 private:
7 float a,b;
8 public:
9 Rechteck(float , float );
10 ~Rechteck();
11 void setze_a(float);
12 float erhalte_a (void);
13 void setze_b(float y);
14 float erhalte_b (void);
15 void ausgabe(void);
16 float flaeche(void);
17 bool ist_kleiner_als (Rechteck r);
18 bool operator< (Rechteck r);
19 };
20
21 Rechteck::Rechteck(float x , float y ){a=x;b=y;}
22 Rechteck::~Rechteck(void){}
23 void Rechteck::setze_a(float x){a=x;}
24 float Rechteck::erhalte_a (void) { return a;}
25 void Rechteck::setze_b(float y){b=y;}
26 float Rechteck::erhalte_b (void) { return b;}
27 void Rechteck::ausgabe(void){
28 cout<<"\nRechteck: a= "<<a<<" b= "
29 <<erhalte_b()<<"\n";
30 }
31 float Rechteck::flaeche(void){
32 return a*b;
33 }
34 bool Rechteck::ist_kleiner_als (Rechteck r){
35 return(flaeche()<r.flaeche());
36 }
37 bool Rechteck::operator< (Rechteck r){
38 return(flaeche()<r.flaeche());
39 }
40
41 int main(void){
42 Rechteck r1(2.0 f.,2.0f);
43 r1.ausgabe();v
44 Rechteck r2(5.0 f.,2.1f);
45 r2.ausgabe();
46 cout<<"\nFlaeche von r1: "<<r1.flaeche();
47 cout<<"\nFlaeche von r2: "<<r2.flaeche();
48 if(r1.ist_kleiner_als(r2))
49 cout<<"\n\nr1 ist kleiner r2"<<endl;

INM12 77
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

50 if(r1.operator<(r2))
51 cout<<"r1 ist kleiner r2"<<endl;
52 if(r1<r2)
53 cout<<"r1 ist kleiner r2"<<endl;
54 return 0;
55 }

Code 5.14: Programm Operatoren.cpp

Bei Abarbeitung werden mit allen drei Aufrufformen die gleichen Ausgaben erzielt:
Rechteck: a= 2 b= 2

Rechteck: a= 5 b= 2.1

Flaeche von r1: 4


Flaeche von r2: 10.5
r1 ist kleiner r2
r1 ist kleiner r2
r1 ist kleiner r2

5.6 Vererbung
Eines der tragenden Konzepte der objektorientierten Programmierung ist das Prinzip der
Vererbung. Aus einer bereits definierten Klasse (Oberklasse) können weitere Klassen
(Unterklassen) abgeleitet werden. Die neuen Klassen können die Oberklasse erweitern
oder einschränken.
Bevor die Vererbung am Beispiel der Klasse Rechteck gezeigt wird, verlassen wir unser
Beispiel kurz und betrachten zur Erklärung einen anderen Sachverhalt:

Beispiel 5.13:
Innerhalb einer Hochschule sollen Informationen zu Studenten und Dozenten
objektorientiert verwaltet werden. Eine erste einfache Lösung besteht darin, eine
Klasse zu definieren, die die Attribute Name und Vorname besitzt. Als Methoden
sollen der Konstruktor und eine Methode ausgabe vorgesehen werden.
Sie sollten in der Lage sein, den vorliegenden Quelltext selbstständig nachzuvoll-
ziehen.
Es wird der neu in C++ im gleichnamigen Header bereitgestellte Datentyp string
genutzt und in der Konstruktordefinition die Initialisierungslistenschreibweise ver-
wendet.

1 //Programm Person.cpp
2 #include <iostream>
3 #include <string>
4 using namespace std;
5
6 class Person {
7 private:
8 string vorname;
9 string nachname;

78 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

10 public:
11 Person(string, string);
12 void print(void);
13 };
14
15 Person::Person(string v, string n)
16 :vorname(v),nachname(n){}
17
18 void Person::print(void) {
19 cout <<endl<<nachname<<"," <<vorname;
20 }
21
22 int main(void){
23 Person p ("Alfred","Schulze");
24 p.print();
25 }

Code 5.15: Programm Person.cpp

Ausgabe:
Schulze,Alfred

Die UML (Unified Modeling Language) dient zur Beschreibung von Klassen mithilfe von
Klassendiagrammen. Eine Klasse wird dabei durch ein Rechteck mit maximal drei Be-
reichen, in denen der Klassenname, gefolgt von den Datenelementen und den Metho-
den, angegeben wird. Dabei kann zusätzlich die Zugriffsart (+ für public, - für private
und # für protected) angegeben werden.
Die Klasse Person lässt sich folgendermaßen angeben:

Person

-vorname:string
-nachname:string

+Person()
+print():void

Abb. 5.2: UML-Darstellung der Klasse Person

Unser Beispiel, die Klasse Person, soll nun in der Art erweitert werden, dass eine Un-
terscheidung zwischen Studenten und Dozenten erfolgen kann. Man könnte jetzt jeweils
eine neue Klasse erstellen und durch Kopieren der Daten sichern, dass die betreffenden
Vor- und Nachnamen in der neuen Klasse bereitstehen.
Dem Nutzer von Klassen bietet sich jedoch die Möglichkeit, durch Vererbung eine be-
reits definierte Klasse um Eigenschaften und Methoden zu erweitern.
Bei der Vererbung wird von einer Oberklasse (auch Super-, Vater- oder Basisklasse) eine
Unterklasse (auch Sub- oder Kindklasse) abgeleitet.

INM12 79
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Die entsprechende UML-Darstellung hat allgemein folgendes Aussehen:

Oberklasse

Unterklasse

Abb. 5.3: UML-Darstellung Vererbung

Die Unterklasse stellt eine Spezialisierung der Oberklasse dar, die Oberklasse ist als
Generalisierung der Unterklasse aufzufassen.
Für unser Beispiel soll ein Student zusätzlich durch die Angabe seines Studiengangs und
ein Dozent durch ein Lehrgebiet charakterisiert werden. Die durch Vererbung entste-
henden Klassen sollen Student und Dozent heißen. Die zu erzeugenden Klassendefi-
nitionen und die Vererbungsstruktur haben in der UML folgendes Aussehen:

Beispiel 5.14:

Person

#vorname:string
#nachname:string

+Person()
+print():void

Dozent Student

-lehrgebiet:string -studiengang:string

+Dozent() +Student()
+print():void +print():void

Abb. 5.4: UML-Darstellung Vererbungshierarchie der Klassen Person, 


Student und Dozent

Studenten werden durch vorname, nachname und studiengang beschrieben.


Die Methode ausgabe wird redefiniert. Studenten sind eine Spezialisierung von
Personen.
Dozenten werden durch vorname, nachname und lehrgebiet beschrieben.
Die Methode ausgabe() wird redefiniert. Dozenten sind eine Spezialisierung von
Personen.

80 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Die Ableitung von Klassen wird in C++ durch folgende Schreibweise ausgedrückt:
class Unterklasse: public Oberklasse {
...
};

Der Compiler wird damit angewiesen, alle Klassenelemente aus der Oberklasse in
die Unterklasse zu übernehmen.
Die durch das Schlüsselwort public durchgeführte Vererbung ist die üblichste Art
und ändert die Zugriffsspezifikationen nicht. Dies bedeutet, dass innerhalb der
Unterklasse nicht direkt auf private deklarierte Klassenelemente der Oberklasse
zugegriffen werden kann.
Hier besteht die Möglichkeit, das Schlüsselwort private durch protected aus-
zutauschen oder den Zugriff über die in der Regel public vereinbarten setter- und
getter-Methoden zu realisieren.
Im Gegensatz zu anderen Methoden werden die Konstruktoren beim Ableiten von
Klassen aber nicht vererbt. Sie sind aber notwendig, um das Klasseninventar der
Oberklasse bereitzustellen. Wenn nicht der implizit generierte Default-Konstruktor
verwendet werden soll, muss der Konstruktoraufruf für die Oberklasse explizit an-
gegeben werden.

1 class Student : public Person {


2 private:
3 string studiengang;
4 public:
5 Student(string, string, string);
6 void print(void);
7 };
8
9 Student::Student(string v, string n, string s)
10 :Person(v,n), studiengang(s){
11 }
12 void Student::print(){
13 cout <<endl<<nachname<<","<<vorname
14 <<",Student im Studiengang "<<studiengang;
15 }

Code 5.16: Programm Vererbung.cpp, Klasse Student

Erläuterung:
Zeile 1: Ableitung der Klasse Student von der Klasse Person.
Zeile 3: studiengang wird als zusätzliches Attribut vereinbart.
Zeilen 5–6: Der Konstruktor der Klasse und eine angepasste print-Funktion
sind zu definieren.
Zeilen 9–11: Der Konstruktor besitzt drei Parameter, die die Werte für vorname,
nachname und studiengang bereitstellen. Da die Datenelemente
vorname und nachname in der Klasse Person verwaltet werden,
sorgt der explizite Konstruktoraufruf für diese Klasse Person(v,n)

INM12 81
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

für deren Initialisierung. Schließlich erhält das Datenelement


studiengang als weiteres Argument über die Initialisierungsliste
seinen Wert. Der Anweisungsblock bleibt leer.
Zeile 13: Da direkt auf die Elemente der Klasse Person zugegriffen wurde,
muss der Zugriffspezifizierer dafür in der Klasse Person von
private auf protected gelockert werden.

1 class Dozent : public Person {


2 private:
3 string lehrgebiet;
4 public:
5 Dozent(string, string, string);
6 void print(void);
7 };
8
9 Dozent::Dozent(string v, string n, string l)
10 :Person(v,n), lehrgebiet(l){
11 }
12 void Dozent::print(){
13 Person::print();
14 cout<<",Dozent fuer "<<lehrgebiet;
15 }

Code 5.17: Programm Vererbung.cpp, Klasse Dozent

Erläuterung:
Zeile 1: Ableitung der Klasse Dozent von der Klasse Person.
Zeilen 3: lehrgebiet wird als zusätzliches Attribut vereinbart.
Zeilen 5, 6: Der Konstruktor der Klasse und eine angepasste print-Funktion
sind zu definieren.
Zeilen 9–11: Der Konstruktor besitzt drei Parameter, die die Werte für vorname,
nachname und lehrgebiet bereitstellen. Da die Datenelemente
vorname und nachname in der Klasse Person verwaltet werden,
sorgt auch hier der explizite Konstruktoraufruf für diese Klasse
Person(v,n) für deren Initialisierung. Schließlich erhält das
Datenelement lehrgebiet als weiteres Argument über die Ini-
tialisierungsliste seinen Wert. Der Anweisungsblock bleibt leer.
Zeile 13: In der für die Klasse zu redefinierenden print-Methode wird zur
Ausgabe von vorname und nachname explizit die Methode der
Klasse Person aufgerufen.

1 int main(void){
2 Person p ("Alfred","Schulze");
3 p.print();
4 Student s ("Hans","Meyer","Informatik");
5 s.print();
6 Dozent d ("Rainer","Lehmann","Programmierung");

82 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

7 d.print();
8
9 return 0;
10 }

Code 5.18: Programm Vererbung.cpp, main-Modul

Ausgabe:
Schulze,Alfred
Meyer,Hans,Student im Studiengang Informatik
Lehmann,Rainer,Dozent fuer Programmierung

Beispiel 5.15:
Kehren wir nochmals zum Sachverhalt Rechteck aus Beispiel 5.11 zurück. Betrachtet
man die Seitenlängen des Rechtecks r1 kann man nachvollziehen, dass ein Quadrat
auch als Spezialisierung eines Rechtecks aufgefasst werden kann. Geht man von
einer Klasse Rechteck aus, bietet sich die Klasse Quadrat als abgeleitete Klasse an.
Im Sinne der Spezialisierung wollen wir auch hier formal die Ableitung einer Klas-
se Quadrat von einer Klasse Rechteck betrachten. Worin besteht diese Spezialisie-
rung? Wir hatten gerade festgestellt, dass ein Quadrat als Rechteck mit gleich langen
Seiten aufzufassen ist.
Bei der Vererbung erbt die abgeleitete Klasse von der Oberklasse das aus Attributen
und allgemeinen Methoden bestehende Klasseninventar.
Für unser Vorhaben bedeutet dies, es muss kein zusätzlicher Speicherplatz für die
Seitenlänge des Quadrates vorgesehen werden, denn die Bezeichner a und b aus
der Klasse Rechteck stehen bereits zur Verfügung. Es ist aber noch dafür Sorge zu
tragen, dass die Seitenlängen gleich groß sind.
Soll die Klasse Quadrat von einer Klasse Rechteck abgeleitet werden, muss diese
Vererbungshierarchie in der Klassendefinition von Quadrat umgesetzt sein. Man
schreibt:
class Quadrat: public Rechteck{
...
};

Damit die Klassenbestandteile von Rechteck an Quadrat vererbt werden kön-


nen, müssen diese bereitgestellt werden. Das geschieht, wie schon aus dem vorheri-
gen Beispiel bekannt, durch den Konstruktoraufruf von Rechteck vor dem Aufruf
des Konstruktors von Quadrat.
Möchte sich der Programmierer nicht darum kümmern, laufen diese Prozesse auto-
matisch ab. Voraussetzung dafür ist, dass für die Oberklasse ein Standardkonstruktor
vorhanden ist. Da am Beispiel für Rechteck kein expliziter Konstruktor definiert
wurde, steht der Default-Konstruktor automatisch zur Verfügung.
Obwohl in der oben angegebenen Klassendefinition keine zusätzlichen Attribute
und Methoden für die Klasse Quadrat vereinbart wurden, lassen sich Instanzen
der Klasse Quadrat erzeugen und benutzen, indem die geerbten Methoden der
Klasse Rechteck zum Setzen und zum Ausgeben der Seitenlängen sinnvoll genutzt
werden.

INM12 83
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Zur Laufzeit des Programms finden drei Konstruktoraufrufe statt:


Aufruf des Standardkonstruktors von Rechteck zum Erzeugen des Objektes r,
Standardkonstruktoraufruf von Quadrat zum Erzeugen des Objektes q mit dem
vorangehenden automatischen zweiten Aufruf des Standardkonstruktors von
Rechteck, um die zu vererbenden Klassenbestandteile bereitzustellen:

1 //Programm Quadrat_1.cpp
2 #include <iostream>
3 using namespace std;
4
5 class Rechteck{
6 private:
7 float a,b;
8 public:
9 void setze_a(float x){a=x;}
10 float erhalte_a (void) {return a;}
11 void setze_b(float y){b=y;}
12 float erhalte_b (void) {return b;}
13 void ausgabe(void){
14 cout<<"\nRechteck: a= "<<a
15 <<" b= "<<erhalte_b()<<"\n";
16 }
17 };
18
19 class Quadrat: public Rechteck{
20
21 };
22
23 int main(void){
24 cout<<"\nRechteck:"<<endl;
25 Rechteck r;
26 r.setze_a(3.1f);
27 r.setze_b(8.0f);
28 r.ausgabe();
29 cout<<"\nQuadrat:"<<endl;
30 Quadrat q;
31 q.setze_a(1.4f);
32 q.setze_b(1.4f);
33 q.ausgabe();
34 return 0;
35 }

Code 5.19: Programm Quadrat_1.cpp

Erläuterung:
Zeilen 6–7: Die Basisklasse Rechteck besitzt nur private deklarierte Da-
tenelemente. Der Zugriff darauf kann deshalb nur über public
deklarierte Methoden erfolgen.
Zeile 25: Der Standardkonstruktor der Klasse Rechteck wird gerufen.
Zeilen 26–27: Die korrekte Initialisierung der Datenelemente erfolgt über die
Methoden setze_a und setze_b.

84 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Zeile 30: Der Standardkonstruktor der Klasse Quadrat wird aufgerufen.


Dieser ruft intern den Standardkonstruktor von Rechteck auf.
Zeilen 31–33: Für das erzeugte Objekt q stehen die aus Rechteck geerbten
Methoden zur Verfügung.
Ausgabe:
Rechteck:

Rechteck: a= 3.1 b= 8

Quadrat:

Rechteck: a= 1.4 b= 1.4

Beispiel 5.16:
Im folgenden Beispiel soll die Klassendefinition von Quadrat sinnvoll erweitert
werden, indem ein Konstruktor für die Klasse Quadrat explizit vereinbart wird
und auch der Aufruf des Konstruktors der Oberklasse explizit erfolgen soll.
Die Konstruktordefinition für die Klasse Quadrat hat folgendes Aussehen:
Quadrat(float x):Rechteck(x,x){}

Bei dessen Aufruf werden die beiden Seitenlängen eines Rechteckobjektes mit dem
gleichen, durch die Variable x bereitgestellten float-Wert initialisiert. Da damit
die Wertebereitstellung erfolgt ist, kann der eigentliche Anweisungsblock {} in
diesem Fall leer bleiben. Bedingung für diese Konstruktion ist jedoch, dass ein Kon-
struktor der Klasse Rechteck, der zwei Parameter verarbeitet, auch vorhanden ist.
Schließlich wird die Methode ausgabe aus der Klasse Rechteck in der
Klasse Quadrat überschrieben (redefiniert), damit bei Instanzen der
Klasse Quadrat nur die Ausgabe einer Seitenlänge erfolgt: void
ausgabe(){cout<<"\nQuadrat: a= "<<a<<"\n";}

Für ein Objekt q der Klasse Quadrat erfolgt deren Nutzung in der Form:
q.ausgabe();.

Möchte man auf die von der Klasse Rechteck geerbte gleichnamige Methode zu-
greifen, schreibt man: q.Rechteck::ausgabe();.

1 // Programm Quadrat_2.cpp
2 #include <iostream>
3 using namespace std;
4 class Rechteck{
5 protected:
6 float a,b;
7 public:
8 Rechteck(float x=1,float y=2){a=x;b=y;}
9 void setze_a(float x){a=x;}
10 float erhalte_a (void) {return a;}
11 void setze_b(float y){b=y;}
12 float erhalte_b (void) {return b;}
13 void ausgabe(void){
14 cout<<"\nRechteck: a= "<<a<<" b= "

INM12 85
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

15 <<erhalte_b()<<"\n";
16 }
17 };
18
19 class Quadrat: public Rechteck{
20 public:
21 Quadrat( float x):Rechteck(x,x){}
22 void ausgabe(){cout<<"\nQuadrat: a= "<<a<<"\n";}
23 };
24
25 int main(void){
26 cout<<"\nRechteck:"<<endl;
27 r.ausgabe();
28 r.ausgabe();
29 cout<<"\nQuadrat:"<<endl;
30 Quadrat q(9.9f);
31 q.ausgabe(); //Methode ausgabe von Quadrat
32 q.Rechteck::ausgabe(); //Methode ausgabe von Rechteck
33 return 0;
34 }

Code 5.20: Programm Quadrat_2.cpp

Erläuterung:
Zeile 8: Der explizite Konstruktor der Klasse Rechteck wird in der Form de-
finiert, dass beide Parameter Standardargumente zugewiesen bekom-
men. Somit steht dieser Konstruktor auch als Standardkonstruktor
bereit.
Zeile 21: In der expliziten Definition des Konstruktors von Quadrat erfolgt der
Aufruf des expliziten Konstruktors von Rechteck. Beide Rechteck-
seiten müssen mit dem Wert der Länge der Quadratseite initialisiert
werden.
Zeile 30: Aufruf des expliziten Konstruktors für Quadrat.
Zeile 31: Nutzung der Methode ausgabe von Quadrat.
Zeile 32: Nutzung der geerbten Methode ausgabe von Rechteck.
Ausgabe:
Rechteck:

Rechteck: a= 1 b= 2

Quadrat:

Quadrat: a= 9.9

Rechteck: a= 9.9 b= 9.9

5.7 Klassenhierarchien
In der Regel sind mehrere Klassen zur Beschreibung eines problemorientierten Lösungs-
ansatzes notwendig. Stehen diese Klassen in einem Vererbungsverhältnis, führen wei-
terführende Spezialisierungen zur Bildung von Klassenhierarchien. Gerade diese Spe-

86 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

zialisierungsmöglichkeit sichert die breiten Wiederverwendungsmöglichkeiten von


einmal definierten Klassen. In der abgeleiteten Klasse (Unterklasse) sind nur die Unter-
schiede (Differenzen) zur Oberklasse explizit anzugeben. Alle anderen Attribute und
Methoden werden geerbt.

Beispiel 5.17:
Im Folgenden wird beispielhaft eine Klassenhierarchie für geometrische Figuren auf-
gebaut. Als oberste Klasse in der Hierarchie steht die Klasse Figur. Hier werden als
Attribute die Seitenlängen a und b sowie setter- und getter-Methoden und die
Methode ausgabe bereitgestellt. Die schon bekannten Klassen Rechteck
und Quadrat ordnen sich in der ersten und zweiten Ableitungsebene ein. Für die
Klasse Quadrat sind nur der Konstruktor, der Destruktor und eine charakteristi-
sche Ausgabeanweisung notwendig. In der ersten Ableitungsebene wird außerdem
die Klasse Dreieck hinzugefügt, deren Objekte durch Grundseite, also a, und Hö-
he, also b, bestimmt werden. Methoden für die Flächenbestimmung, für den Grö-
ßenvergleich und eine Ausgabe sind analog zur Klasse Rechteck bereitzustellen.
Daraus entsteht die folgende Klassenhierarchie:

Figur

# a: float
# b: float
+Figur()
+~Figur()
+setze_a:void
+setze_b:void
+erhalte_a:float
+erhalte_b: float
+ausgabe(): void

Rechteck Dreieck

+ Rechteck() +Dreieck
+~ Rechteck +~Dreieck
+flaeche(): float +flaeche():void
+ausgabe(): void +ausgabe(): void
+operator<(): bool +operator<(): bool

Quadrat

+Quadrat
+~Quadrat
+ ausgabe():void

Abb. 5.5: UML-Diagramm der Vererbungshierarchie Figur

INM12 87
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Analysieren Sie die einzelnen Klassenbestandteile im Quelltext:

1 //Programm Klassenhierarchie.cpp
2 #include <iostream>
3 using namespace std;
4
5 class Figur{
6 protected:
7 float a,b;
8 public:
9 Figur(float x, float y);
10 ~Figur(void);
11 void setze_a(float x);
12 float erhalte_a (void);
13 void setze_b(float y);
14 float erhalte_b (void);
15 void ausgabe(void);
16 };
17
18 Figur::Figur(float x, float y){a=x;b=y;}
19 Figur::~Figur(void){}
20 void Figur::setze_a(float x){a=x;}
21 float Figur::erhalte_a (void) {return a;}
22 void Figur::setze_b(float y){b=y;}
23 float Figur::erhalte_b (void) {return b;}
24 void Figur::ausgabe(void){
25 cout<<"\na= "<<erhalte_a()
26 <<" b= "<<erhalte_b();
27 }
28 class Rechteck:public Figur{
29 public:
30 Rechteck(float x=1, float y=1);
31 ~Rechteck(void);
32 float flaeche(void);
33 bool operator< (Rechteck r);
34 void ausgabe(void);
35 };
36 Rechteck::Rechteck(float x, float y)
37 :Figur(x,y){}
38 Rechteck::~Rechteck(void){}
39 float Rechteck::flaeche(void){
40 return a*b;
41 }
42 bool Rechteck::operator< (Rechteck r){
43 return(flaeche()<r.flaeche());
44 }
45 void Rechteck::ausgabe(){
46 cout<<"\nRechteck mit den Seitenlangen:";
47 Figur::ausgabe();
48 cout<<" und Flaeche: "<<flaeche();
49 }
50
51 class Quadrat: public Rechteck{
52 public:
53 Quadrat(float x);
54 ~Quadrat(void);

88 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

55 void ausgabe(void);
56 };
57 Quadrat::Quadrat(float x):Rechteck(x,x){}
58 Quadrat::~Quadrat(void){}
59 void Quadrat::ausgabe(){
60 cout<<"\nQuadrat mit der Seitenlange:\n";
61 cout<<erhalte_a();
62 cout<<" und Flaeche: "<<flaeche();
63 }
64
65 class Dreieck:public Figur{
66 public:
67 Dreieck(float x=1, float y=1);
68 ~Dreieck(void);
69 float flaeche(void);
70 bool operator< (Dreieck d);
71 void ausgabe(void);
72 };
73 Dreieck::Dreieck(float x, float y)
74 :Figur(x,y){}
75 Dreieck::~Dreieck(void){}
76 float Dreieck::flaeche(void){
77 return 1.0/2.0*a*b;
78 }
79 bool Dreieck::operator< (Dreieck d){
80 return(flaeche()<d.flaeche());
81 }
82 void Dreieck::ausgabe(){
83 cout<<"\nDreieck mit Grundseite und Hoehe:";
84 Figur::ausgabe();
85 cout<<" und Flaeche: "<<flaeche();
86 }
87
88 int main(void){
89 Figur f(5.1f,3.3f);
90 cout<<"Figur mit der Seitenlange:";
91 f.ausgabe();
92 Rechteck r1;
93 r1.ausgabe();
94 Rechteck r2(4.1f);
95 r2.ausgabe();
96 r2.setze_b(8.0f);
97 r2.ausgabe();
98 Quadrat q(9.9f);
99 q.ausgabe();
100 Dreieck d(4.1f,3.0f);
101 d.ausgabe();
102 if(r1<r2) cout<<"\nr1<r2"<<endl;
103 if(r1<q) cout<<"r1<q"<<endl;
104
105 return 0;
106 }

Code 5.21: Programm Klassenhierarchie.cpp

INM12 89
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++

Erläuterung:
Zeilen 5–16: Interface der Basisklasse Figur.
Zeile 6: Die Klassenattribute sind in einer Vererbungshierarchie
protected zu setzen.
Zeile 9: Im Konstruktor werden keine Standardparameter vorgesehen.
Zeile 18–27: Methodendefinitionen zur Klasse Figur.
Zeilen 29–36: Interface der Klasse Rechteck.
Zeilen 37–50: Methodendefinitionen zur Klasse Rechteck.
Zeile 37, 38: Im Konstruktor der Klasse Rechteck, er benutzt Standardargu-
mente (Zeile 31), wird explizit der Konstruktor der Klasse Figur
aufgerufen.
Zeilen 40–45: Die Methoden flaeche und operator< werden für die
Klasse Rechteck definiert.
Zeilen 46–50: Die Methode ausgabe wird für die Klasse Rechteck redefi-
niert. Dabei wird die Methode ausgabe der Klasse Figur auf-
gerufen.
Zeilen 52–57: Interface der Basisklasse Quadrat.
Zeilen 58–64: Methodendefinitionen zur Klasse Quadrat.
Zeilen 60–64: Die Methode ausgabe der Klasse Quadrat wird redefiniert. Da-
bei wird die geerbte Methode erhalte_a der Klasse Figur und
die aus Rechteck geerbte Methode flaeche aufgerufen.
Zeilen 66–73: Interface der Klasse Dreieck.
Zeilen 74–87: Methodendefinitionen zur Klasse Dreieck.
Zeilen 74–75: Im Konstruktor der Klasse Dreieck, er benutzt Standardargumen-
te (Zeile 68), wird explizit der Konstruktor der Klasse Figur auf-
gerufen.
Zeilen 77–82: Die Methoden flaeche und operator< werden für die
Klasse Dreieck definiert.
Zeilen 83–87: Die Methode ausgabe der Klasse Dreieck wird redefiniert. Da-
bei wird die Methode ausgabe der Klasse Figur aufgerufen.
Zeilen 89–107: Nutzung der Klassen Figur, Rechteck, Quadrat, Dreieck.
Zeile 93: Aufruf des Standardkonstruktors von Rechteck.
Zeile 95: Aufruf des Konstruktors von Rechteck mit einem Parameter.
Zeile 99: Aufruf des Konstruktors von Quadrat.
Zeile 101: Aufruf des Konstruktors von Dreieck.
Zeile 103: Vergleich der Rechteckobjekte r1 und r2 mithilfe des
Operators <.
Zeile 104: Vergleich der Rechteckobjekte r1 und des Quadratobjekts q mit-
hilfe des Operators <.

90 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Ausgewählte Aspekte zur objektorientierten Programmierung mit C++ 5

Zusammenfassung

Das letzte Kapitel vermittelte Ihnen einen Überblick zum objektorientierten Program-
mieren mit C++. Die Begriffe Klasse und Objekt sind Grundlage für das objektorientierte
Programmiermodell. Mit der Definition einer Klasse wird ein neuer Datentyp erzeugt,
der Datenelemente (Attribute) und Methoden zum Verändern dieser Daten kapselt. Es
besteht die Möglichkeit, die Sichtbarkeit der Klassenbestandteile und damit den Zugriff
auf diese zu steuern.
Die Klassendefinition beschreibt als Bauplan die Struktur der zu instanzierenden
Objekte.
Objekte/Instanzen der Klasse werden mit einem Konstruktoraufruf erzeugt und mit dem
Destruktor wieder zerstört.
Konstruktoren besitzen den gleichen Bezeichner wie die Klasse und dienen dazu, die
Attribute mit Startwerten zu initialisieren. Konstruktoren besitzen keinen Rückgabetyp.
Destruktoren erhalten ebenfalls den gleichen Bezeichner wie die zugehörige Klasse. Die-
sem wird das Zeichen „~“ vorangestellt, er hat keinen Rückgabetyp und keine Parameter.
Wird kein Konstruktor explizit vereinbart, wird automatisch der Standardkonstruktor
zur Verfügung gestellt.
Ein wichtiges Konzept der objektorientierten Programmierung ist die Vererbung. Neue
Klassen werden auf Basis bestehender definiert. Dabei findet eine Spezialisierung von
der Ober- zur Unterklasse statt. Es entsteht eine Vererbungshierarchie. Die Unterklasse
erbt im Allgemeinen alle Eigenschaften/Attribute und Methoden der Oberklasse. Bei
diesem Vorgang können zusätzliche Attribute und Methoden definiert oder Methoden
redefiniert (überschrieben) werden.
Durch die dargestellten Beispiele sollten Sie in der Lage sein, eigene einfache Problem-
stellungen objektorientiert in C++ umzusetzen.

Aufgaben zur Selbstüberprüfung


 
5.1 Mit v  AB werde der Vektor zwischen den Punkten A und B mit dem Startpunkt
A (xa, ya) und dem Endpunkt B (xb, yb) im zweidimensionalen Raum verstanden
(xa, ya, xb, yb seien ganzzahlig).

Definieren Sie eine Klasse Vektor2P zur Benutzung solcher Vektoren mit folgen-
den Forderungen:
Definieren Sie für diese Klasse einen Konstruktor, der es ermöglicht, einerseits
durch die Angabe von vier ganzen Zahlen Instanzen der Klasse Vektor2P ent-
sprechend der oben angegebenen Definition zu erzeugen und andererseits durch
die Angabe von zwei ganzen Zahlen einen Ortsvektor zum Punkt B (xb, yb) bereit-
zustellen (xa = ya = 0).
Stellen Sie eine Methode betrag bereit, die den Betrag des Vektors (Länge der
Strecke AB) ermittelt.

INM12 91
© HfB, 02.12.20, Berk, Eric (904709)

5 Ausgewählte Aspekte zur objektorientierten Programmierung mit C++


Für den Betrag gilt hier: | AB | (x b  x a )2  ( y b  x a )2

Eine Methode ausgabe soll die Vektoreigenschaften in folgender Form ausschrei-


ben:
Vektor: (xa,ya) -->(xb,yb)

Für xa, ya, xb und yb sind die jeweils konkreten Werte vorzusehen.
Überladen Sie den Operator == der Klasse.
Dabei sollen zwei Vektoren dann gleich sein, wenn diese den gleichen Betrag
besitzen.
5.2 Definieren Sie eine Klasse Datum und davon abgeleitet eine Klasse Feiertag,
mit denen es möglich ist, folgende main-Routine abzuarbeiten:
int main(){
Feiertag T1(24,12,2012,"Heiligabend");
Feiertag T2(24,12,2012,"Christmas Eve");
T1.drucke();
T2.drucke();
if(T1==T2)printf("\nGleichheit");
return 0;
}

Definieren Sie dazu eine Klasse Datum, die auf Basis der drei Ganzzahlvariablen
tag, monat und jahr Terminangaben verwalten kann.

Stellen Sie einen Konstruktor der Klasse Datum bereit, der alle Klassenattribute
setzt.
Diese Klassenattribute sollen außerdem über getter- und setter-Methoden
(setzeTag, setzeMonat, setzeJahr, erhalteTag, erhalteMonat,
erhalteJahr) verfügbar gemacht werden.

Definieren Sie eine Methode ausgabeDatum, die ein Datum in der


Form Datum = tt.mm.jjjj anzeigt.
Für tt, mm bzw. jjjj sind die konkreten Werte einzufügen.
Überladen Sie den Gleichheits-Operator, um zwei Datumsangaben auf Gleich-
heit zu überprüfen.
Leiten Sie aus der Klasse Datum eine Klasse Feiertag ab, indem Sie eine Zei-
chenkette name zur Aufnahme der Bezeichnung des Feiertags als neues Klassen-
attribut vorsehen.
Definieren Sie für die Klasse Feiertag den Konstruktor.
Auch für das Klassenattribut name der Klasse Feiertag sind die Methoden
setzeName und erhalteName bereitzustellen.

In der Methode drucke der Klasse Feiertag soll die Methode ausgabeDatum
der Klasse Datum aufgerufen werden und auf der gleichen Zeile, mit einem Kom-
ma getrennt, die Bezeichnung des Feiertags hinzugefügt werden.

92 INM12
© HfB, 02.12.20, Berk, Eric (904709)

A. Lösungen der Aufgaben zur Selbstüberprüfung


1.1 printf("\n\n%i ",*z_int); Der Wert der vom Zeiger z referen-
zierten Variablen wird ausgegeben.
Ausgabewert: 7.
printf("\n\n%i ",*z_int+2); Zum Wert der vom Zeiger z referen-
zierten Variablen wird der Wert 2
addiert. Ausgabewert: 9.
printf("\n\n%i ",*(&i)); Dereferenzierung (*) und Referenzie-
rung (&) sind Umkehroperationen. Es
wird der Wert der Variablen i ausge-
geben. Ausgabewert: 7.
printf("\n\n%i ",(*z_int)++); Der Wert der vom Zeiger z referen-
zierten Variablen wird postfix, d. h.
nach der Abarbeitung der Ausgabe-
anweisung, inkrementiert. Ausgabe-
wert: 7.
1.2 //Aufgabe_1_2
#include <stdio.h>
#include <stdlib.h>
int main(){
float fzahl1,fzahl2,ergebnis;
float *z1,*z2,*e;
printf ("\nerste Gleitkommazahl: ");
scanf("%f",&fzahl1);
printf ("\nzweite Gleitkommazahl: ");
scanf("%f",&fzahl2);

z1=&fzahl1;
z2=&fzahl2;
e=&ergebnis;

*e=*z1+*z2;

printf("\nAusgabe mit Zeiger:");


printf("\n\nDie Summe von %f und %f 
ergibt %f.\n",
*z1,*z2,*e);

printf("\nAusgabe mit ohne Zeiger:");


printf("\n\nDie Summe von %f und %f 
ergibt %f.\n",
fzahl1,fzahl2,ergebnis);

return 0;
}

INM12 93
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

1.3 Im Programm fehlt die Initialisierung des Zeigers. Er referenziert keine definierte
Speicherzelle.
Die Anweisung *int_zeiger=int_zahl1; ist durch die Anweisung
int_zeiger=&int_zahl1; zu ersetzen. Das geänderte und nunmehr korrekte
Programm lautet:

//Aufgabe_1_3.c
//korrekt
#include <stdio.h>
int main(){
int int_zahl1,int_zahl2,*int_zeiger;

int_zahl1=123456;
int_zeiger=&int_zahl1;
int_zahl2=*int_zeiger;
printf("\n%i %i",int_zahl1,int_zahl2);

return 0;
}

2.1 //Aufgabe_2_1.c
#include <stdio.h>

float prozent(float ,float);


void ausgabe(float zahl);

int main(){
float wert, pwert, psatz;

printf("\nProzentwert eingeben: ");


scanf("%f",&pwert);
printf("\nGrundwert eingeben: ");
scanf("%f",&wert);
psatz=prozent(pwert,wert);
printf("\n");

ausgabe(psatz);

return 0;
}

float prozent(float Prozentwert,float Grundwert){


return Prozentwert/Grundwert*100;
}
void ausgabe(float prozentsatz){
printf("\nProzentsatz: %.2f %%",prozentsatz);
}

94 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

2.2 Struktogramm main:

Eingabe: durchm, hoehe

durchm>0.0 && hoehe>0.0


j n

volumen(durchm, hoehe,vol)
A: Hinweis
Ausgabe: vol

Prozedurdefinition volumen:

volumen( d, h, v)
{ Input: d, h (Durchmesser, Hoehe) … reell >=0
Output: v (Volumen)
}

v:=pi/4*d*d*h

Quelltext:

//Aufgabe_2_2.c
#include <stdio.h>

float volumen (float d, float h);

int main(){

float durchm, hoehe, vol;

printf("Durchmesser: "); scanf("%f",&durchm);


printf("Hoehe: "); scanf("%f",&hoehe);

if(durchm>0.0f && hoehe>0.0f) {


vol=volumen(durchm, hoehe);
printf("Zylinder");
printf(" mit dem Durchmesser %.2f ",durchm);
printf(" und der Hoehe %.2f",hoehe);
printf(" hat das Volumen %.2f\n",vol);
}
else
printf("d oder h duerfen nicht negativ sein!\n");

return 0;
}

INM12 95
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

float volumen(float d, float h){


float v;
float pi=3.14159f;
v=pi/4*d*d*h;
return v;
}

2.3 //Aufgabe_2_3.c
#include <stdio.h>

int main(){
int a=-33,b=0,c=12;

printf("Ergebniswert: %d\n",sign(a));
printf("Ergebniswert: %d\n",sign(b));
printf("Ergebniswert: %d",sign(c));
return 0;
}
int sign(int gz){
int vz=0;
if(gz<0) vz=-1;
if(gz>0) vz=1;
return vz;
}

//Aufgabe 2.3_Zeiger.c
#include <stdio.h>
void sign(int *gz,int *vz);
int main(){
int a=-33,b=0,c=12,e;

sign(&a,&e); printf("Ergebniswert: %d\n",e);


sign(&b,&e); printf("Ergebniswert: %d\n",e);
sign(&c,&e); printf("Ergebniswert: %d\n",e);
return 0;
}

void sign(int *gz,int *vz){


*vz=0;
if(*gz<0) *vz=-1;
if(*gz>0) *vz=1;
}

2.4 //Aufgabe_2_4.c

#include <stdio.h>
#include <math.h>

float arctan(float x, float eps);

int main(){
float x, eps;
printf("x= "); scanf("%f",&x);
printf("eps= ");scanf("%f",&eps);

96 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

printf("arctan(%f, %f) = %f\n",x,eps,arctan(x,eps));


printf("Zum Vergleich: atan(%f) = %f\n",x,atan(x));

system("pause");
return 0;
}

float arctan(float x, float eps){


float summe=0.f, z, s;
int i=1;

z=x;
summe=x;

do{
i=i+2;
z=-z*x*x;
s=z/i;
summe=summe+s;
}
while(fabs(s)>eps*fabs(summe));

return summe;
}

3.1 In den Lösungen wird die nicht behandelte, aber für Felder sinnvolle
Präcompilerdirektive #define zur Angabe der Anzahl der Feldelemente
benutzt, die hier in der Art #define bezeichner ersetzung verwendet
wird. Der Ausdruck #define N 5 sorgt dafür, dass im Quelltext das Zeichen N
durch das Zeichen 5 ersetzt wird. So bleibt die Behandlung der Feldgröße relativ
flexibel.
Felder sind zugleich Zeiger, die das erste Feldelement referenzieren. Somit ist der
Zugriff auf die Feldelemente in Feld- und Zeigerschreibweise möglich.
Entsprechend finden Sie drei mögliche Quelltexte für eine Lösung.
Variante 1: Indexschreibweise

//Aufgabe_3_1a_Feld.c
#include <stdio.h>
#define N 5

int main(){
float messwerte[N]={0.5f,1.72f,3.78f,-0.45f,11.8f};
float min,max,durchschnitt,summe,hilf;
int lauf;

summe=min=max=messwerte[0];
for (lauf=1;lauf<N;lauf++){
hilf=messwerte[lauf];
summe+=hilf;
if (hilf<min) min=hilf;
if (hilf>max) max=hilf;

INM12 97
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

}
durchschnitt=summe/N ;
printf("Ergebnisse:\nMinimalwert= %f\n",min);
printf("Maximalwert= %f\n",max);
printf("Durchschnitt= %f",durchschnitt);

return 0;
}

Variante 2: Zeigerschreibweise, Nutzung des vordefinierten Zeigers


messwerte.

//Aufgabe_3_1_vordef_Zeiger.c
#define N 5
#include <stdio.h>

int main(){
float messwerte[N]={0.5f,1.72f,3.78f,-0.45f,11.8f};
float min,max,durchschnitt,summe,hilf;
int lauf;

summe=min=max=*messwerte;
for (lauf=1;lauf<N;lauf++) {
hilf=*(messwerte+lauf);
summe+=hilf;
if (hilf<min) min=hilf;
if (hilf>max) max=hilf;
}
durchschnitt=summe/N ;
printf("Ergebnisse:\nMinimalwert= %f\n",min);
printf("Maximalwert= %f\n",max);
printf("Durchschnitt= %f",durchschnitt);

return 0;
}

Variante 3: Zeigerschreibweise, Nutzung eines eigendefinierten und damit zu be-


wegenden Zeigers flzeiger.

//Aufgabe_3_1_eigendef_Zeiger.c
#include <stdio.h>
#define N 5

int main(){
float messwerte[N]={0.5f,1.72f,3.78f,-0.45f,11.8f};
float min,max,durchschnitt,summe,hilf;
float *flzeiger;
int lauf=1;

flzeiger=messwerte;
summe=min=max=*flzeiger;
do {
flzeiger++;
hilf=*(flzeiger);
summe+=hilf;

98 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

if (hilf<min) min=hilf;
if (hilf>max) max=hilf;
lauf++;
}
while(lauf<N);

durchschnitt=summe/N ;
printf("Ergebnisse:\nMinimalwert= %f\n",min);
printf("Maximalwert= %f\n",max);
printf("Durchschnitt= %f",durchschnitt);

return 0;
}

3.2 Für die zu definierende Funktion ergibt sich der Rückgabetyp float. Die Sum-
me der Gleitkommazahlen führt wieder zu einer Gleitkommazahl. Zwei Argu-
mente werden übergeben. An erster Stelle der Parameterliste steht der Feldname.
Jedes Feld ist auch als Zeiger aufzufassen. Hieraus leitet sich der Typ float*
(Zeiger auf float) dafür ab. Der zweite Parameter beinhaltet die Anzahl der
Feldelemente und ist somit mit dem Typ int zu definieren. Die beiden lokalen
Parameter i und summe dienen der eigentlichen Summation. Die Beschrei-
bung dieses Algorithmenteiles erfolgt in klassischer Indexschreibweise, auch die
Zeigerschreibweise wäre möglich. Nach der letzten Summenbildung wird das
Ergebnis über die return-Anweisung an das rufende Programm vermittelt.
Im Hauptprogramm erfolgt der Aufruf der Funktion der Einfachheit halber in
einer printf-Anweisung. In der Gesamtheit von Prototypvereinbarung, Auf-
rufprogramm und Funktion ergibt sich die Lösung der Aufgabe.

//Aufgabe_3_2.c
#include <stdio.h>

float vsumme(float *feld,int anzahl);

int main() {

float vektor[]={1.5f,3.1f,8.6f,-7.05f,3.83f,
-4.0f,6.4f,11.09f,30.0f,-5.1f};

printf("\nSumme der Vektorkomponenten: %6.2f",


vsumme(vektor,10));
}

float vsumme(float *feld,int anzahl) {


int i;
float summe=0.0;
for(i=0;i<anzahl;i++)
summe+=feld[i];
return(summe);
}

INM12 99
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

3.3 Der Prototyp für die zu formulierende Ersetzungsfunktion war vorgegeben:


void ersetze_zeichen(char*s,char zei_alt, char zei_neu). Die
Funktion besitzt demnach einen leeren Rückgabewert und arbeitet mit den drei
Parametern
• der zu bearbeitenden Zeichenkette s (Typ char*),
• dem auszutauschenden Zeichen char zei_alt (Typ char),
• dem zum Austauschen bereitgestellten Zeichen char zei_neu
(Typ char).
Der Lösungsgedanke ist sicherlich schnell gefunden. Die Gesamtzeichenkette
muss durchlaufen werden. Immer wenn genau zei_alt auftritt, ist dieses
Zeichen durch zei_neu zu ersetzen.
Im Programm wurde eine Zeichenkette mit maximal 40 Zeichen vorgesehen. Die
Wertebelegung von MAX erfolgt über die Präprozessoranweisung #define.
Um diese als konstanter Vektor vereinbarte Zeichenkette mit Werten zu belegen,
wird die Standardfunktion strcpy benutzt (Einschließungsfile für Zeichenket-
ten angeben!), da eine andere Zuweisung bei dem als fester Zeiger aufzufassen-
den Vektor nicht möglich ist.
Die Prototypvereinbarung für die Funktion ersetze_zeichen erfolgt im
Programmteil main. Die Funktion wird mit den Argumenten "Ersetze e
durch x" sowie 'e' und 'x' aufgerufen.

Die Programmierung des Zeichentausches erfolgte zeigerorientiert. Mit *s


wird jeweils das aktuelle Zeichen angesprochen. Das Zeichenkettenende wurde
dann erreicht, wenn *s=='\0' gilt; solange *s größer null ist, arbeitet der
Algorithmus schrittweise.

//Aufgabe_3_3.c
#define MAX 40
#include <stdio.h>
#include <string.h>
void ersetze_zeichen(char *s,char zei_alt,char zei_neu);

int main(){
unsigned char zkette[MAX];

strcpy(zkette,"Ersetze e durch x");


printf("\n%s",zkette);

ersetze_zeichen(zkette,'e','x');
printf("\n%s",zkette);
return 0;
}

void ersetze_zeichen(char *s,char zei_alt,char zei_neu){


while(*s)
{
if(*s==zei_alt) *s=zei_neu;
s++;

100 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

}
}

3.4 Im main-Modul wird das Feld ergebnis mit 20 Elementen für die Aufnahme
der resultierenden Zeichenkette vereinbart. Damit kann der Aufruf
zeik_verbind("PROGRAMM","ENTWICKLUNG",ergebnis); erfolgen. Die
benutzen formalen Parameter der Funktion zeik_verbind sind t1, t2 und
erg.

Der Algorithmus lässt sich gut, unabhängig von der Art der Beschreibung der
Feldelemente, im folgenden Schema darstellen. Die Zeichenketten sind jeweils
ohne ihr Endezeichen lückenlos aneinanderzufügen und insgesamt mit dem Zei-
chenkettenende abzuschließen (erg19='\0').

t1: t10 t11 t12 … ‘\0’

erg: erg0 erg1 erg2 … erg8 erg9

t2= t20 t21

In einer ersten Lösungsvariante werden die einzelnen Zeichen der Zeichenketten


in Indexschreibweise angesprochen. Dabei wird das letzte zu übertragende Zei-
chen der ersten Zeichenkette t1 mithilfe der Funktion strlen bestimmt. Diese
Standardfunktion ermittelt die Anzahl der Zeichen einer Zeichenkette ohne das
Endezeichen zu berücksichtigen. Nach Verlassen der for-Schleife besitzt neui
den Indexwert des zuletzt übertragenen Zeichenkettenzeichens.
Im Folgeschritt sind jetzt die Zeichen des Feldes t2 zeichenweise in das Feld erg
zu übertragen. Dieser Vorgang erfolgt solange, wie das Zeichenkettenendezei-
chen ('\0') noch nicht erreicht wurde. Schließlich ist im Feld erg das abschlie-
ßende Zeichenkettenende hinzuzufügen.

//Aufgabe_3_4_Feld.c
#include <stdio.h>
#include <string.h>

void zeik_verbind(const char*,const char*,char*);

int main() {
char ergebnis[20];
zeik_verbind("PROGRAMM","ENTWICKLUNG",ergebnis);
printf("\n%s",ergebnis);
return 0;
}

void zeik_verbind(const char *t1,const char *t2,char* erg){


int i=0,neui;
for(i=0;i<strlen(t1);i++){
erg[i]=t1[i];
neui=i;

INM12 101
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

}
neui+=1;
i=0;
while(t2[i]!='\0'){
erg[neui+i]=t2[i];
i++;
}
erg[neui+i]='\0';
}

Im zweiten Lösungsbeispiel wird der Zugriff auf die Feldelemente in Zeiger-


schreibweise beschrieben, wobei der Zeiger fix positioniert bleibt und die Aus-
wahl der Feldelemente über die Adressberechnung feld+i bestimmt wird. Dabei
entsprechen die Schreibweisen feld[i]und *(feld+i) einander. Innerhalb
der while-Konstruktion muss der Felddurchlauf mit i++ organisiert werden. 
Die Wiederholungsbedingung wird in der verkürzten Schreibweise
while(*(t1+i)) angegeben. Der Ausdruck *(t1+i) ist in C dann logisch als
wahr aufzufassen, wenn dieser einen Wert ungleich null repräsentiert.

// Aufgabe_3_4_Zeiger_1.c
#include <stdio.h>

void zeik_verbind(const char*,const char*,char*);

int main() {
char ergebnis[20];
zeik_verbind("PROGRAMM","ENTWICKLUNG",ergebnis);
printf("\n%s",ergebnis);
return 0;
}

void zeik_verbind(const char *t1,const char *t2,char* erg){


int i=0,neui;
while(*(t1+i)){
*(erg+i)=*(t1+i);
i++;
}
neui=i;i=0;
while(*(t2+i)){
*(erg+neui+i)=*(t2+i);
i++;
}
*(erg+neui+i)='\0';
}

Die dritte Lösungsvariante dringt am tiefsten zur Zeigerschreibweise vor. Dabei


werden die Zeichenkettenelemente direkt über einen beweglichen Zeiger ange-
sprochen. Mit *erg++=*t1++; wird der Wert des durch die Zeiger t1 referen-
zierten Feldelements dem Wert des durch den Zeiger erg referenzierten Feldele-
ments zugewiesen. Postfix, d. h. nach dieser Zuweisung erfolgt die
Referenzierung des nächsten Feldelementes im Speicher.

102 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

// Aufgabe_3_4_Zeiger_2.c
#include <stdio.h>

void zeik_verbind(const char*,const char*,char*);

int main(){
char ergebnis[20];
zeik_verbind("PROGRAMM","ENTWICKLUNG",ergebnis);
printf("\n%s",ergebnis);
return 0;
}

void zeik_verbind(const char *t1,const char *t2,char *erg){


while(*t1) *erg++=*t1++;
while(*t2) *erg++=*t2++;
*(erg)='\0';
}

4.1 Im Vergleich zum Heftbeispiel 4.5 stellt die Aufgabe das Ansprechen der einzel-
nen Strukturkomponenten in den Fokus. Weiterhin ist zu beachten, dass die Zu-
weisung der Zeichenketten in C über strcpy (aus string.h) erfolgen muss.
Dies ist beim Bereitstellen der Listenstruktur und der Initialisierung des Listen-
kopfes bereits gut erkennbar.

//Aufgabe 4_1.c*/
#include <stdio.h>
#include <string.h>
struct datum {
int tag, monat, jahr;
};

struct person {
char Name[35], Vorname[15];
int Nummer;
struct datum geboren;
struct person *next;
};

int main(){
struct person *LKopf;
struct person *aktElem;

LKopf = (struct person*) malloc (sizeof(struct person));

strcpy(LKopf->Name, "Meyer");
strcpy(LKopf->Vorname, "Rainer");
LKopf->Nummer=1;
(*LKopf).geboren.tag=8;
(*LKopf).geboren.monat=12;
(*LKopf).geboren.jahr=2000;
LKopf->next=NULL;

aktElem=LKopf;
printListe(LKopf);

INM12 103
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

aktElem=addLElem(aktElem,2,"Schulze","Gerd",12,4,1990);
aktElem=addLElem(aktElem,3,"Lehmann","Karin",21,6,1994);
printListe(LKopf);

return 0;
}

Entsprechend diesem Zugriffsschema lassen sich die geforderten Funktionen in


folgender Weise definieren:

struct person* addLElem(struct person *bezug, int


p_Nummer,char p_Name[], char p_Vorname[], int p_tag,int
p_monat, int p_jahr){

struct person *neuesElement;

if(bezug==NULL) return NULL;


if(bezug->next == NULL) {
bezug->next = (struct person*) malloc (sizeof(struct
person));
neuesElement=bezug->next;

neuesElement->Nummer =p_Nummer;
strcpy(neuesElement->Name, p_Name);
strcpy(neuesElement->Vorname,p_Vorname);

(*neuesElement).geboren.tag=p_tag;
(*neuesElement).geboren.monat=p_monat;
(*neuesElement).geboren.jahr=p_jahr;

neuesElement->next=NULL;
return neuesElement;
}
}

void printListe(struct person *bezug){


struct person *akt = bezug;

if (bezug== NULL) return;


printf("\nDatensatzliste:\n===============\n");
while(akt != NULL){

printf("Nummer: %d\n",akt->Nummer);
printf("Name: %s\nVorname: %s\n",
akt->Name,
akt->Vorname);
printf("Geb.-Datum: %2i.%2i.%2i\n\n",
(*akt).geboren.tag,
(*akt).geboren.monat,
(*akt).geboren.jahr);
akt=akt->next;
}
}

104 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

4.2 Der zu implementierte Algorithmus beinhaltet folgende Schritte:


Die Liste wird als Parameter übergeben. Alle Listenelemente werden bis zum
Listenende schrittweise durchlaufen, indem jeweils der Nachfolger eines Listen-
elements angesprochen wird. Die Einzelschritte werden gezählt und repräsen-
tieren die Anzahl der Listenelemente.

int zaehlLElem(struct LListe * bezug) {


int zaehler=0;

if(bezug ==NULL) return 0;


while(bezug!=NULL) {
zaehler++;
bezug=bezug->next;
}
return zaehler;
}

4.3 Die Parametervermittlung erfolgt „call by reference“. Die Adresse des so über-
gebenen Listenanfangs wird gesichert. Das neu generierte Listenelement erhält
die Wertzuweisung für Zahl. Als Adresse des Nachfolgerelements wird alte
Anfangsadresse der Liste zugewiesen.

void addLElementAnf(struct LListe ** zz, int z){


struct LListe *sicherung = *zz;

*zz=(struct LListe *)malloc(sizeof(struct LListe));


(*zz)->zahl=z;
(*zz)->next=sicherung;
}

Um beide neuen Funktionen zu testen, müssen entsprechende Funktionsaufrufe


im Beispiel 4.9 z. B. am Ende der main-Funktion ergänzt werden:

printf("\nAnzahl Listenelemente: %d\n",zaehlLElem(LKopf));

addLElementAnf(&LKopf,111);
addLElementAnf(&LKopf,222);
addLElementAnf(&LKopf,333);

printf("\nAnzahl Listenelemente: %d\n",zaehlLElem(LKopf));


printListe(LKopf);

5.1 Aufgabe 5.1 erwartete in der Klassendefinition die Bereitstellung eines


Konstruktors mit 4 Größenangaben, die Start- und Endpunkte des Vektors fest-
legen, bzw. zwei Koordinatenangaben, die den Endpunkt eines vom Punkt (0, 0)
ausgehenden Vektors bestimmen. Neben der Möglichkeit, zwei verschiedene
Konstruktoren zu definieren, wurde hier die Lösung mit einem Konstruktor bei
Verwendung von Standardwerten genutzt.
Als Methoden sollten betrag und ausgabe bereitgestellt werden.

INM12 105
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

Der überladene Operator == bestimmt, wann zwei Vektoren gleich sind. In


unserem Fall ist zu überprüfen, ob jeweils Anfangs- und Endpunkte gleich sind.

//Aufgabe_5_1a.cpp
#include <iostream>
#include <cmath>
using namespace std;

class Vektor2P{
private:
int xa,ya,xb,yb;
public:
Vektor2P (int p1,int p2,int p3=0,int p4=0){
xb=p1; yb=p2;xa=p3;ya=p4;
}
float betrag(void){
return sqrt((xa-xb)*(xa-xb)+(ya-yb)*(ya-yb));
}
bool operator==(Vektor2P v){
return betrag()==v.betrag();
}
void ausgabe(void){
cout<<"\nVektor: A("<<xa<<","<<ya
<<")-->B("<<xb<<","<<yb<<")"<<endl;
}
};
int main(){
Vektor2P v1 (1,1,2,3);
Vektor2P v2 (2,2);
Vektor2P v3 (2,2,0,0);
v1.ausgabe();
v2.ausgabe();
v3.ausgabe();

cout<<"\n\nBetrag v1: "<<v1.betrag()<<endl;


cout<<"\nBetrag v2: "<<v2.betrag()<<endl;
cout<<"\nBetrag v3: "<<v3.betrag()<<endl;

if(v2==v3)
cout<<"\nv2 und v3 sind identisch."<<endl;
else
cout<<"\nv2 und v3 sind nicht identisch."<<endl;
if(v1==v3)
cout<<"\nv1 und v3 sind identisch."<<endl;
else
cout<<"\nv1 und v3 sind nicht identisch."<<endl;

return 0;
}

106 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

In der Programmierung sind Vergleiche zweier Größen von einem Gleitkomma-


datentyp immer problematisch. Die Lösung besteht darin, die Gleichheit in ei-
nem bestimmten Bereich zu prüfen. Somit könnte der Gleichheits-Operator in
folgender Weise implementiert werden:

bool operator==(Vektor2P v){


if(fabs(betrag()-v.betrag())<1E-5)
return true;
else
return false;
}

Benutzt man diese Variante und trennt die Schnittstelle und Implementation für
die Klasse Vektor, ergibt sich:

//Aufgabe_5_1b.cpp
#include <iostream>
#include <cmath>
using namespace std;class Vektor2P{
private:
int xa,ya,xb,yb;
public:
Vektor2P (int, int, int, int);
float betrag(void);
bool operator==(Vektor2P);
void ausgabe(void);
};

Vektor2P::Vektor2P (int p1,int p2,int p3=0,int p4=0){


xb=p1; yb=p2;xa=p3;ya=p4;
}
float Vektor2P::betrag(void){
return sqrt((xa-xb)*(xa-xb)+(ya-yb)*(ya-yb));
}
bool Vektor2P::operator==(Vektor2P v){
if(fabs(betrag()-v.betrag())<1E-5)
return true;
else
return false;
}

void Vektor2P::ausgabe(void){
cout<<"\nVektor: A("<<xa<<","<<ya
<<")-->B("<<xb<<","<<yb<<")"<<endl;
}

int main(){
Vektor2P v1 (1,1,2,3);
Vektor2P v2 (2,2);
Vektor2P v3 (2,2,0,0);
v1.ausgabe();
v2.ausgabe();
v3.ausgabe();

INM12 107
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

cout<<"\n\nBetrag v1: "<<v1.betrag()<<endl;


cout<<"\nBetrag v2: "<<v2.betrag()<<endl;
cout<<"\nBetrag v3: "<<v3.betrag()<<endl;

if(v2==v3)
cout<<"\nv2 und v3 sind identisch."<<endl;
else
cout<<"\nv2 und v3 sind nicht identisch."<<endl;
if(v1==v3)
cout<<"\nv1 und v3 sind identisch."<<endl;
else
cout<<"\nv1 und v3 sind nicht identisch."<<endl;

return 0;
}

5.2 Der folgende Quelltext erfüllt die Aufgabenstellung:

//Aufgabe 5_2.cpp
#include <iostream>
#include<string>

using namespace std;


class Datum {
private:
int tag;
int monat;
int jahr;
public:
Datum(int t, int m, int j) {
tag = t;
monat = m;
jahr = j;
}
int erhalteTag() {return tag;}
int erhalteJahr() {return jahr;}
void setzeTag(int d) {tag = d ;}
void setzeMonat(int m) {monat = m;}
void setzeJahr(int j) {jahr = j;}
void ausgabeDatum(){
cout<<"\nDatum = "
<<tag<<"."<<monat<<"."<<jahr<<" ";
}
bool operator==(Datum p){
return(tag=p.tag && monat==p.monat
&& jahr==p.jahr);
}
};

class Feiertag: public Datum {


private:
string Name;
public:
Feiertag(int t, int m, int j, string n)
: Datum(t, m, j), Name(n) {}
string erhalteName() {return Name;}

108 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

void setzeName(string n){


Name = n;
}
void drucke(){
ausgabeDatum();
cout<<erhalteName()<<endl;
}
};

int main(){
Feiertag T1(24,12,2012,"Heiligabend");
Feiertag T2(24,12,2012,"Christmas Eve");
T1.drucke();
T2.drucke();
if(T1==T2)cout<<"\nGleichheit\n";
return 0;
}

INM12 109
© HfB, 02.12.20, Berk, Eric (904709)

B. Literaturverzeichnis
Ernst, H. u. a. (2015). Grundkurs Informatik. Grundlagen und Konzepte für die erfolgrei-
che IT-Praxis. Eine umfassende, praxisorientierte Einführung. 
5. Aufl., Wiesbaden: Springer Vieweg.
Heiderich, N.; Meyer, W. (2016). Technische Probleme lösen mit C/C++. Von der Analyse
bis zur Dokumentation. 3. Aufl., München: Hanser.
Herold, H.; Lurz, B.; Wohlrab, J. (2012). Grundlagen der Informatik. 
2. Aufl., Halbergmoos: Pearson.
Horn, C.; Kerner, I. O.; Forbrig, P. (Hrsg.) (2003). Lehr- und Übungsbuch Informatik.
Grundlagen und Überblick. 
3. Aufl., München, Wien: Fachbuchverlag Leipzig, Hanser.
Nassi, I.; Shneiderman, B. (1973). Flowchart techniques for structured programming.
ACM SIGPLAN Notices.
Schneider, U. (Hrsg.) (2012). Taschenbuch Informatik. 
7. Aufl., München: Fachbuchverlag Leipzig, Hanser.

Internetlinks
http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/

110 INM12
© HfB, 02.12.20, Berk, Eric (904709)

C. Abbildungsverzeichnis
INM12

Abb. 1.1 Struktogramm zum Modul swap(…) ......................................................... 3


Abb. 1.2 Zuordnung Adresse und Wert einer Variablen ........................................ 5
Abb. 1.3 Zeiger auf Zeiger ......................................................................................... 7
Abb. 2.1 Parametervermittlung „call by reference“ ................................................ 15
Abb. 2.2 Rekursionsablauf der Funktion fakul(…) .................................................. 16
Abb. 3.1 Zusammenhang Feld und Zeiger ............................................................... 29
Abb. 3.2 Moduldefinition Sort(anz,f) ....................................................................... 34
Abb. 3.3 Syntaxdiagramm Datensatz ....................................................................... 36
Abb. 4.1 Listenelement .............................................................................................. 47
Abb. 4.2 Schematische Darstellung einer linearen Liste ........................................ 47
Abb. 4.3 Schematische Darstellung zum Löschen eines Knotens .......................... 53
Abb. 5.1 Das Dev-C++-Projekt ................................................................................. 74
Abb. 5.2 UML-Darstellung der Klasse Person ......................................................... 79
Abb. 5.3 UML-Darstellung Vererbung ..................................................................... 80
Abb. 5.4 UML-Darstellung Vererbungshierarchie der Klassen Person, 
Student und Dozent .................................................................................... 80
Abb. 5.5 UML-Diagramm der Vererbungshierarchie Figur ................................... 87

INM12 111
© HfB, 02.12.20, Berk, Eric (904709)

D. Quellcodeverzeichnis
INM12

Code 1.1 Programm Tauschen.c ................................................................................. 4


Code 1.2 Programm Zeiger.c ...................................................................................... 6
Code 1.3 Programm Zeiger_auf_Zeiger.c .................................................................. 7
Code 1.4 Programm Zeigernutzung.c ........................................................................ 8
Code 1.5 Programm Zeigervergleich.c ...................................................................... 11
Code 2.1 Tauschen_mit_Zeigern.c ............................................................................. 15
Code 2.2 Programm Fakultaet_1.c ............................................................................. 16
Code 2.3 Programm Fakultaet_2.c ............................................................................. 17
Code 2.4 Programm globale_Variable.c ..................................................................... 19
Code 2.5 Kommandozeilenargumente_1.c ................................................................ 20
Code 2.6 Kommandozeilenargumente_2.c ................................................................ 21
Code 2.7 Programm Funktionszeiger.c ...................................................................... 23
Code 3.1 Programm Primzahlen.c ............................................................................. 27
Code 3.2 Programm Feldgrenzen.c ............................................................................ 28
Code 3.3 Feld als Zeiger.c ........................................................................................... 30
Code 3.4 Programm Zeichenketten.c ......................................................................... 32
Code 3.5 Programm Sortieren_1.c ............................................................................. 35
Code 3.6 Definition der Funktion sort(…) zum Programm Sortieren_2.c .............. 35
Code 3.7 Programm Termin.c ..................................................................................... 37
Code 3.8 Programm typedef.c .................................................................................... 38
Code 4.1 Programm malloc.c ...................................................................................... 42
Code 4.2 Programm calloc.c ....................................................................................... 44
Code 4.3 Programm realloc.c ...................................................................................... 46
Code 4.4 Programm Liste_von_Hand.c ..................................................................... 49
Code 4.5 Funktion addLelem zu Programm Liste.c .................................................. 51
Code 4.6 Funktion printListe zu Programm Liste.c .................................................. 52
Code 4.7 Funktion getLelem zu Programm Liste.c ................................................... 52
Code 4.8 Funktion loescheLelem zu Programm Liste.c ........................................... 54
Code 4.9 Funktion loescheKopf zu Programm Liste.c .............................................. 55
Code 4.10 Funktion main zu Programm Liste.c .......................................................... 55
Code 5.1 Programm Ein_Ausgabe.cpp ...................................................................... 59
Code 5.2 Programm const.cpp .................................................................................... 59
Code 5.3 Programm Standardargumente.cpp ........................................................... 60
Code 5.4 F_Ueberladen.cpp ........................................................................................ 61

112 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Code 5.5 Programm referenz.cpp .............................................................................. 62


Code 5.6 Programm Standardkonstruktor.cpp ......................................................... 66
Code 5.7 Programm Konstruktoren.cpp ................................................................... 68
Code 5.8 Programm Initialisierungsliste.cpp ............................................................ 70
Code 5.9 Programm Standardparameter.cpp ............................................................ 71
Code 5.10 Headerdatei rechteck.h ............................................................................... 73
Code 5.11 Implementationsdatei rechteck.cpp ........................................................... 73
Code 5.12 main-Modul Test.cpp .................................................................................. 73
Code 5.13 Programm Methoden.cpp ........................................................................... 76
Code 5.14 Programm Operatoren.cpp ......................................................................... 78
Code 5.15 Programm Person.cpp ................................................................................. 79
Code 5.16 Programm Vererbung.cpp, Klasse Student ............................................... 81
Code 5.17 Programm Vererbung.cpp, Klasse Dozent ................................................ 82
Code 5.18 Programm Vererbung.cpp, main-Modul ................................................... 83
Code 5.19 Programm Quadrat_1.cpp .......................................................................... 84
Code 5.20 Programm Quadrat_2.cpp .......................................................................... 86
Code 5.21 Programm Klassenhierarchie.cpp .............................................................. 89

INM12 113
© HfB, 02.12.20, Berk, Eric (904709)

114 INM12
© HfB, 02.12.20, Berk, Eric (904709)

E. Einsendeaufgabe Typ A
Grundlagen der Algorithmierung und Programmierung Teil 2 Einsendeaufgabencode:
INM12-XX1-K03

Name: Vorname: Tutor/-in:

Postleitzahl und Ort: Straße: Datum:

Matrikel-Nr.: Studiengangs-Nr.: Note:

Heftkürzel: Druck-Code: Unterschrift:


INM12 0619K03
Bitte reichen Sie Ihre Lösungen über StudyOnline ein. Falls Sie uns diese per Post senden
wollen, dann fügen Sie bitte die Aufgabenstellung und den Einsendeaufgabencode hinzu.

1. Ermitteln Sie im Trockentest die Werte der Variablen i, j, k und b nach Ablauf
des Programms. Geben Sie außerdem in einer Tabelle die Veränderung dieser Vari-
ablenwerte während der Programmabarbeitung an.

/*Einsendeaufgabe INM02_1.c*/

int main(){
int i,j,k=0,n=12;
float b=2.0f;

for(i=3;i<n;i++) {
if(i%2){
j=i*i;
}
else{
b+=5.1f;
continue;
}
k=i+j;
if(k>81) break;
}

return 0;
}

20 Pkt.
2. Gegeben ist ein Feld f aus n (7  n  10) selbst vorzugebenden Gleitkommazahlen
vom Datentyp double.
Definieren Sie eine Funktion berechne, die dieses Feld übergeben bekommt und
die Anzahl derjenigen Feldelemente bestimmt, deren Werte größer als das arith-
metische Mittel der vorgegebenen Feldelemente sind.

INM12 115
© HfB, 02.12.20, Berk, Eric (904709)

E Einsendeaufgabe Typ A

Benutzen Sie als Prototyp int berechne (double *f, int n) bzw. int
berechne (double f[], int n).

Testen Sie die Funktion in einem Hauptprogramm.


25 Pkt.
3. Definieren Sie eine Funktion zeik_laeng, die die Länge einer vorgegebenen
Zeichenkette zkette bestimmt und zurückgibt. Das Zeichenkettenende-Zeichen
ist nicht mitzuzählen.
Als Prototyp gelte: int zeik_laeng (char* zkette) bzw. int zeik_laeng
(char[] zkette).

Nutzen Sie diese Funktion zur Ausgabe der Länge der Zeichenkette
"TESTZEICHENKETTE"!
25 Pkt.
4. Ein Kreis werde bestimmt durch die Koordinaten des Mittelpunkts xm und ym
sowie durch den Radius r, jeweils vom Datentyp float.
Erzeugen Sie eine Klasse Kreis mit folgenden Forderungen:
Definieren Sie für diese Klasse einen Konstruktor, der es ermöglicht, einerseits
durch die Angabe von drei Werten Instanzen der Klasse Kreis zu erzeugen und
andererseits durch die Angabe von einem Wert Kreise mit einem zu bestimmenden
Radius am festen Kreismittelpunkt xm=0 und ym=0 bereitzustellen.
Als setter- und getter-Methoden sind setze_Mittelpunkt, setze_Radius,
erhalte_xm, erhalte_ym sowie erhalte_Radius bereitzustellen.

Eine Methode ausgabe soll folgendes Ausgabebild erzeugen:


Mittelpunkt M(xm,ym); Radius r= r

Für xm, ym und r sollen die konkreten Werte eingefügt werden.


Implementieren Sie außerdem die Methoden umfang, flaeche und
durchmesser, die jeweils die Werte für den Kreisumfang und für die Kreisfläche
berechnen bzw. aus dem Wert des Radius den entsprechenden Durchmesser bestim-
men.
Benutzen Sie zum Zugriff auf die Attribute der Klasse Kreis konsequent nur
Methoden.
Testen Sie die definierte Klasse mit folgender main-Funktion:

int main(){
Kreis k1 (3.1f);
cout<<"Kreis k1:"<<endl;
k1.ausgabe();
cout<<"Durchmesser: "<<k1.durchmesser()<<endl;
cout<<"Flaeche: "<<k1.flaeche()<<endl;
cout<<"Umfang: "<<k1.umfang()<<endl;
Kreis k2(5.5 f., 1.2 f., 4.1f);
cout<<"\nKreis k2:"<<endl;

116 INM12
© HfB, 02.12.20, Berk, Eric (904709)

Einsendeaufgabe Typ A E

k2.ausgabe();
k2.setze_Mittelpunkt(0.5 f.,0.5f);
k2.ausgabe();
cout<<"Flaeche: "<<k2.flaeche()<<endl;
cout<<"Umfang: "<<k2.umfang()<<endl;

return 0;
}

30 Pkt.
Gesamt: 100 Pkt.

INM12 117
© HfB, 02.12.20, Berk, Eric (904709)

$
Studienheft

SEI11

Phasenmodelle und Planung von 


Softwareprojekten
0118K07
Das Studienheft und seine Teile sind urheberrechtlich geschützt.
Jede Nutzung in anderen als den gesetzlich zugelassenen Fällen
ist nicht erlaubt und bedarf der vorherigen schriftlichen
Zustimmung des Rechteinhabers. Dies gilt insbesondere für das
öffentliche Zugänglichmachen via Internet, Vervielfältigungen und
Weitergabe. Zulässig ist das Speichern (und Ausdrucken) des
Studienheftes für persönliche Zwecke.

$
SEI11

Phasenmodelle und Planung von


Softwareprojekten
0118K07

Prof. Dr. Peter Zöller-Greer


©

Werden Personenbezeichnungen aus Gründen der besseren Lesbarkeit nur in der männlichen oder
weiblichen Form verwendet, so schließt dies das jeweils andere Geschlecht mit ein.
Falls wir in unseren Studienheften auf Seiten im Internet verweisen, haben wir diese nach sorgfältigen
Erwägungen ausgewählt. Auf die zukünftige Gestaltung und den Inhalt der Seiten haben wir jedoch
keinen Einfluss. Wir distanzieren uns daher ausdrücklich von diesen Seiten, soweit darin rechtswid-
rige, insbesondere jugendgefährdende oder verfassungsfeindliche Inhalte zutage treten sollten.
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle und Planung von Softwareprojekten


SEI11

Inhaltsverzeichnis
0118K07

Vorwort ....................................................................................................................... 1

1 Einleitung .................................................................................................................. 3
1.1 Lebenszyklus einer Softwareentwicklung ................................................ 3
1.2 Methodische Ansätze und grundlegende Definitionen ........................... 5
Zusammenfassung .................................................................................................... 7
Aufgaben zur Selbstüberprüfung ............................................................................ 7

2 Phasenmodelle ......................................................................................................... 8
2.1 Klassisches Wasserfallmodell ..................................................................... 9
2.2 Spiralmodell ................................................................................................. 19
2.3 V-Modell ...................................................................................................... 21
2.4 Diamantmodell ............................................................................................ 24
2.5 Neuere Methoden ........................................................................................ 27
Zusammenfassung .................................................................................................... 33
Aufgaben zur Selbstüberprüfung ............................................................................ 33

3 Softwareergonomie ................................................................................................. 34
3.1 Definition und Rechtsgrundlagen ............................................................. 34
3.2 Softwareergonomische Dialoggestaltung ................................................. 37
3.3 Praktische Anwendungen .......................................................................... 41
Zusammenfassung .................................................................................................... 45
Aufgabe zur Selbstüberprüfung ............................................................................... 45

4 Planung eines Softwareprojekts ............................................................................ 47


4.1 Erste Analyse des Problems ....................................................................... 47
4.2 Verfeinerte Analyse des Problems: Erstellung des Pflichtenhefts .......... 49
4.2.1 Analyse Ist-/Sollzustände ........................................................................... 51
4.2.2 Semantische Datenmodellierung ............................................................... 60
4.2.2.1 Entscheidungstabellen ................................................................................ 60
4.2.2.2 Data Dictionary ........................................................................................... 63
4.2.2.3 Entity Relationship Diagram ..................................................................... 64
4.2.2.4 Objektorientierte Analyse und Design ..................................................... 66
4.2.3 Projektplan ................................................................................................... 80
Aufgaben zur Selbstüberprüfung ............................................................................ 80
0118K07

SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Inhaltsverzeichnis

Anhang
A. Lösungen der Aufgaben zur Selbstüberprüfung ....................................... 81
B. Literaturverzeichnis ..................................................................................... 84
C. Abbildungsverzeichnis ................................................................................ 85
D. Sachwortverzeichnis .................................................................................... 87
E. Einsendeaufgabe .......................................................................................... 89

SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Vorwort
SEI11Phasenmodelle und Planung von Softwareprojekten0118K07

Spätestens seit der Softwarekrise der 60er-Jahre ist der Ruf nach besser planbarem, sys-
tematischerem Vorgehen laut geworden. Nach und nach hat sich daraus das heutige
Software Engineering entwickelt. Weitgehend standardisiert, liefert es für alle an der
Entwicklung Beteiligten methodische Ansätze zur effektiven und ökonomischen Soft-
wareerstellung. Obwohl Software in der Regel nur recht kurzlebig ist, haben sich erst in
jüngerer Zeit die Methoden zu ihrer Erzeugung stabilisiert. Noch vor wenigen Jahren
standen viele verschiedene Ansätze in Konkurrenz, doch mit der Einführung der Unified
Modeling Language (UML) hat sich eine Methode etabliert, die alle anderen Konkurren-
ten weit hinter sich gelassen hat. Es besteht daher Aussicht, dass die beschriebenen Me-
thoden und Verfahren für die nächsten Jahre aktuell bleiben, da ihre Akzeptanz und Ver-
breitung sehr groß ist.
Software Engineering ist also eine selbstverständliche Methode zur effizienten Entwick-
lung von Programmen. Im Prinzip kann man zwei Arten Software unterscheiden: An-
wendungsprogramme und Steuerprogramme. Überlappungen sind möglich. Anwen-
dungsprogramme sind Programme, die von Usern benutzt werden können, die keine
tieferen DV-Kenntnisse besitzen. Sie müssen entsprechend benutzerfreundlich sein und
leicht bedienbar. Steuerprogramme dagegen bedürfen in der Regel keines Benutzers in
diesem Sinne, sondern sie laufen „von allein“ und steuern bestimmte (meist physikali-
sche) Systeme, z. B. einen Fahrstuhl.
Relativ unabhängig von dieser Unterscheidung sind die methodischen Ansätze bei der
Softwareentwicklung. Hier werden immer gewisse Entwicklungsphasen durchlaufen, in
denen systematisch Teilschritte realisiert werden, bis schließlich das fertige Produkt vor-
handen ist. Das vorliegende Heft deckt den ersten Teil dieser Entwicklungsphasen ab,
nämlich im Wesentlichen die Planung des Softwareprojekts. Zu diesem Zweck wird
nach einer Einleitung in die Problematik der Softwareentwicklung (Kapitel 1) einiges
über Phasenmodelle gesagt, es werden die wichtigsten dieser Modelle vorgestellt
(Kapitel 2). Kapitel 3 widmet sich den Erfordernissen der Softwareergonomie, und in
Kapitel 4 wird schließlich die Planungsphase eines Softwareprojekts genau durchleuch-
tet und es werden einige methodische Ansätze aus diesem Bereich beschrieben.
Der Planung kommt überhaupt eine grundlegende Bedeutung zu. Viele Softwareprojek-
te scheitern an unzureichender Planung. Es kann gar nicht oft genug darauf hingewiesen
werden, dass eine gute Planung das A und O des erfolgreichen Gelingens einer Syste-
mentwicklung ist. So wie ein Architekt zuerst den „Blueprint“ eines Bauvorhabens er-
stellt und Bauarbeiter nicht einfach drauflosmauern, so muss ein Softwareentwickler ei-
nen „Bauplan“ für das Anwendungsprogramm erstellen. Fehler in den Bauplänen
können sowohl für Hausbauer als auch für Softwareentwickler je nach Schwere glei-
chermaßen katastrophal sein. Deshalb kommt dem Fach Software Engineering eine so
wichtige Bedeutung zu.
Viel Erfolg beim Durcharbeiten dieses Heftes!

SEI11 1
© HfB, 02.12.20, Berk, Eric (904709)

2 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

1 Einleitung
Dieses Kapitel beschäftigt sich mit der Geschichte des Software Engineering so-
wie dessen Grundprinzipien. Sie sollen nach Durcharbeiten dieses Kapitels in der
Lage sein,
• die Probleme der Softwarekrise zu nennen,
• den Lebenszyklus eines Softwareentwicklungsprojekts zu beschreiben,
• methodische Ansätze zur Lösung der Probleme bei der Softwareentwicklung
zu nennen.
Es wird außerdem eine Definition von Software Engineering gegeben sowie
dessen Abdeckungsbereich untersucht.

1.1 Lebenszyklus einer Softwareentwicklung


In den 60er-Jahren des 20. Jahrhunderts verbreiteten sich Computer in vielen
Industrieunternehmen. Dabei steckte jedoch die systematische Entwicklung darauf lau-
fender Software noch in den Anfängen. Software wurde normalerweise in einem zent-
ralen Rechenzentrum für umliegende Fachabteilungen von einem Spezialistenteam ent-
wickelt. Wobei hier der Ausdruck „Team“ nicht unbedingt bedeutet, dass mehrere
Personen am gleichen Projekt zusammenarbeiteten, sondern es war häufig so, dass jeder
Programmierer ein Programm komplett entwickelte oder zumindest einen abgegrenzten
Programmteil, der relativ isoliert war. Hinzu kam die Tatsache, dass solche Programme
nicht in großem Umfang geplant wurden, sondern die Programmierer legten häufig ein-
fach mit der Programmierung los. Und dies zudem noch, bevor genau klar war, was
überhaupt der spätere Benutzer wollte. Dieses relativ unsystematische Vorgehen erzeug-
te natürlich hohen Entwicklungsaufwand und damit auch Unsicherheit hinsichtlich der
Kosten und der Entwicklungsdauer.
Es wurde schon bald die Notwendigkeit einer besseren Planung von Software erkannt
und man begann, sogenannte Pflichtenhefte zu schreiben. Diese enthielten mehr oder
weniger präzise die jeweiligen Anforderungen an die zu entwickelnde Software. Aufbau
und Form solcher Pflichtenhefte unterlagen allerdings keinem bestimmten Standard,
und es war dem jeweiligen Autor überlassen, wie genau das Problem und eventuelle Lö-
sungen beschrieben wurden.
Man ging zu dieser Zeit auch dazu über, die Planung der Software von ihrer Codierung
zu trennen. Sogenannte Systemanalytiker hatten die Aufgabe, das Problem zu analy-
sieren und eine algorithmische Lösung zu entwickeln, die dann einem Programmierer
zum Zweck der Codierung übergeben wurde. Im kommerziell-administrativen Bereich
waren dies auch tatsächlich verschiedene Personen. Im naturwissenschaftlich-techni-
schen Bereich dagegen wurden Systemanalyse und Programmierung häufig von ein und
derselben Person durchgeführt. Dies machte insofern Sinn, als hier die Probleme oft so
komplex waren, dass die Kommunikation zwischen Analytiker und Programmierer sehr
schwierig gewesen wäre.
Obwohl also einige Anstrengungen unternommen wurden, war der gesamte Entwick-
lungszyklus einer Software noch von vielen Unsicherheitsfaktoren begleitet. Abge-
schätzte Entwicklungskosten wurden regelmäßig erheblich überschritten, ebenso wie
die geplanten Entwicklungszeiten. Man fand heraus, dass dies in erster Linie an mangel-

SEI11 3
© HfB, 02.12.20, Berk, Eric (904709)

1 Einleitung

hafter Planung lag. Planungsfehler wurden oft erst sehr spät entdeckt und waren nur mit
erheblichem Aufwand zu korrigieren. Außerdem wiesen die Pflichtenhefte häufig In-
konsistenzen (Widersprüche) auf, die zunächst nicht bemerkt wurden. Diese Situation
bezeichnet man heute als die Softwarekrise der 60er-Jahre.
Es wurden viele Anstrengungen unternommen, um aus dieser Krise herauszukommen.
Es wurde z.B. zunächst versucht, die Planungsphase zu standardisieren, wobei ingeni-
eurwissenschaftliche Methoden als Vorbild dienen sollten. Die Grundidee war, besagte
Planungsphase systematisch zu erarbeiten und methodische Ansätze dafür zu entwi-
ckeln. Man erhoffte sich dadurch weniger Planungsfehler und eine Codierung, die Pro-
gramme lieferte, die die geforderten Spezifikationen so gut wie möglich erfüllten.

Entwicklungs-
aufwand

ideal

real

Zeit

Abb. 1.1: Lebenszyklus einer Softwareentwicklung

Abb. 1.1 stellt den idealen Lebenszyklus einer Softwareentwicklung dem seinerzeit rea-
len gegenüber. Im Idealfall steckt man den meisten Aufwand in die Planungsphase, so-
dass die sich anschließende Codierungsphase ohne großen Aufwand vonstattengeht und
auch keine wesentlichen Korrekturen erforderlich sind. Im Realfall dagegen war die Pla-
nungsphase nicht sonderlich intensiv durchdacht und die Codierung war demzufolge
fehlerhaft und korrekturanfällig. Die wohlbekannten Gesetze von Edward A. Murphy
finden leider auch hier ihre Anwendung.

Murphys Gesetze
• Alles dauert länger, als man denkt.
• Alles ist teurer, als man denkt.
• Alles ist komplizierter, als man denkt.
• Wenn ein Fehler passieren kann, dann passiert er auch.
(Murphy war Optimist.)

4 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Einleitung 1

Diese etwas scherzhafte Formulierung von Murphys Gesetzen weist auf die Pro-bleme
hin, die mit der Entwicklung von Software verbunden sein können.

1.2 Methodische Ansätze und grundlegende Definitionen


Die Softwarekrise der 60er-Jahre führte also zu der Notwendigkeit, Softwareapplikatio-
nen systematisch zu planen und zu entwickeln. Dies führte zu der wissenschaftlichen
Disziplin des Software Engineering. Allerdings ist es vor der genauen Definition dieses
Begriffs erforderlich, eine Abgrenzung der Schnittstellen vorzunehmen, auf die Soft-
wareentwicklung überhaupt bezogen wird.
Software kann immer nur einen Teil der Welt repräsentieren. Ein System ist eine An-
sammlung von Dingen, die genau abgrenzbar sind. Ein Softwaresystem ist demnach
eine genau definierte Ansammlung von Programmen, die durch den Computer von der
Umwelt und innerhalb des Computers von anderen Anwendungen klar abgegrenzt ist.
Ein solches System reagiert naturgemäß auf Eingaben durch Benutzer oder andere Sys-
teme. Und diese Reaktionen müssen vorhergesehen werden.

System mit geplanten Reaktionen


Unter einem System mit geplanten Reaktionen versteht man eine Menge von Regeln
oder Einheiten, die wohldefinierte Aktionen ausführen, auch wenn Ereignisse außerhalb
seines Einflussgebietes auftreten. Diese geplanten Reaktionen sind in einer symboli-
schen/formalen Sprache formulierbar.

Ergebnis Ergebnis
System mit externe
spontane System-
geplanten
Aktivität umgebung
Reaktionen
Reaktionen Reaktionen

Abb. 1.2: System mit geplanten Reaktionen

Ein System mit geplanten Reaktionen fängt also alle Reaktionen seiner Umgebung in
kontrollierter Weise ab. Stellt ein System z. B. ein Anwendungsprogramm auf einem
Rechner dar, also einen Ausschnitt aus der Realität, der DV-technisch mit einem entspre-
chenden Programm abgebildet wird, dann ist insbesondere die I/O-Schnittstelle eine
Quelle möglicher Probleme. Das kann z. B. eine unsinnige Tastenkombination auf der
Tastatur des Computers sein, während das Anwendungsprogramm läuft. Auf keinen
Fall darf es dann zum Absturz des Programms kommen, sondern es muss eine kontrol-
lierte Reaktion des Systems erfolgen.
Dies zu erreichen klingt zunächst einfacher, als es ist, denn meistens ist es schwierig, alle
Eventualitäten „vorherzudenken“. Deswegen ist z. B. auch der Programmieraufwand für
Plausibilitätsüberprüfungen einer Maskeneingabe häufig der aufwendigste Teil der je-
weiligen Maskenprogrammierung. Natürlich sind geplante Reaktionen auch in Rechen-
algorithmen zu berücksichtigen, beispielsweise muss eine mögliche Eingabe, die zur Di-
vision durch die Zahl Null führt, abgefangen werden.
Benutzen wir also zukünftig den Begriff Software, so wird nachfolgend immer davon
ausgegangen, dass es sich dabei um Systeme mit geplanten Reaktionen handelt.

SEI11 5
© HfB, 02.12.20, Berk, Eric (904709)

1 Einleitung

Dies ist allerdings noch lange nicht ausreichend, um effiziente Software zu entwickeln,
denn es sind weitere Probleme damit verbunden, die systematisch gelöst werden müs-
sen. Einige dieser Probleme sind:
• Komplexe Anforderungen sind oft schwer zu verstehen.
• Softwareentwicklung erfordert absolute Präzision, ein hohes Maß an Kreativität, Er-
fahrung im Entwickeln komplexer Programme und echtes Teamwork.
• Unter Stressbedingungen kann die Fehlerrate ansteigen.
• Es ist eine hohe Qualifikation der Mitarbeiter erforderlich.
An einen Entwickler werden zudem noch gewisse spezifische Anforderungen gestellt,
wie z. B. die Fähigkeit, Wichtiges von Unwichtigem zu trennen, oder die richtige Ein-
schätzung der Komplexität einer Anforderung.
Um komplexe und aufwendige Anforderungen einer systematischen Lösung zuführen
zu können, sind drei wichtige Prinzipien einzuhalten:
1. die Modellierung der Realität durch Abstraktion,
2. die Reduktion der Komplexität durch Strukturierung und
3. Fokussierung auf die Lösung von Teilproblemen.
Es gibt computergestützte Hilfsmittel zur Durchführung dieser Aufgaben, die man all-
gemein CASE-Tools nennt (CASE = Computer Aided Software Engineering). Es wird
dringend empfohlen, solche Tools wann immer möglich einzusetzen. Diese Werkzeuge
sind in der Lage, schon bei der Planung und dem Entwurf der Anwendung Inkonsisten-
zen zu entdecken. Dadurch reduziert sich die Fehlerrate beträchtlich. Außerdem können
viele dieser Tools das Planungsergebnis direkt in ein relationales oder objektorientiertes
Datenbankschema umsetzen und erzeugen so z. B. physikalische Tabellenstrukturen.
Nachfolgend soll nun eine allgemeine Definition des Begriffs Software Engineering ge-
geben werden.

Software Engineering
Unter Software Engineering versteht man die Wissenschaft, effiziente Software auf der
Basis ingenieurmäßiger Methoden und ökonomischer Gesichtspunkte zu entwickeln. Sie
umfasst die strukturierte Analyse, den Entwurf und die Realisierung sowie das Testen
und Warten eines Programms mittels methodischer Ansätze.
Bis zu den 80er-Jahren des 20. Jahrhunderts unterteilte man die Entwicklung eines Soft-
waresystems zunächst nur in die zwei Phasen Systemanalyse und Programmierung. Die
Systemanalyse umfasste damals die Problembeschreibung, die Beschreibung des Ist-
und Sollzustandes, die Input-/Output-Beschreibung des gewünschten Systems, einen
Projektplan, ein semantisches Datenmodell, ein logisches Datenmodell, Datenflussdia-
gramme/Programmstruktogramme und ein Lösungskonzept. Die sich daran anschlie-
ßende Programmierung beschäftigte sich mit der eigentlichen Codeerzeugung, ggf. ei-
nem „Tuning“ des Codes sowie schließlich dem Test und der Wartung der Software.

6 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Einleitung 1

Diese beiden Phasen sind heute allerdings nur Teile detaillierterer Phasenmodelle und
umfassen demzufolge auch nicht mehr alle oben genannten Komponenten, sondern die-
se sind ausgelagert und in andere, eigene Entwicklungsphasen integriert.
Gelegentlich wird eine Unterteilung des Software Engineering in zwei Komponenten
vorgeschlagen:
1. Konstruktionssystematik
Darunter versteht man die Ansammlung der benutzten Methoden, Werkzeuge und
Ergebnisse.
2. Produktionssystematik
Diese beschreibt die Entwicklungsschritte, deren Reihenfolge, gesetzte Termine so-
wie die anfallenden Kosten.
Es sei darauf hingewiesen, dass diese zwei Komponenten nichts mit den Phasen des Soft-
ware Engineering zu tun haben! Die Phasen fließen eher in die zweite Komponente ein,
während die erste einfach die Betriebsmittel zu ihrer Durchführung bereitstellt.
Diese Zweiteilung ist allerdings nicht unbedingt erforderlich, da die angewendeten Be-
triebsmittel entweder gewissen Standards entsprechen oder, falls nicht, in den jeweili-
gen Phasen beschrieben werden.
Die Lösung der Probleme der Softwarekrise des letzten Jahrhunderts erhoffte man sich
also durch Phasenkonzepte. Der Entwicklungszyklus einer Anwendung wird in einzelne
Phasen unterteilt und jede Phase enthält genau definierte Eingaben, Ausgaben und Tä-
tigkeiten. Man unterscheidet dabei zwischen linearen und nichtlinearen Phasenmodel-
len. Bei linearen Phasenmodellen muss eine Phase vollständig abgearbeitet sein, bevor
die nächste Phase begonnen werden kann. Bei nichtlinearen Phasenmodellen ist das
nicht so; hier können einzelne Phasen auch mehrmals durchlaufen werden, ohne immer
vollständig abgeschlossen zu sein. Solche Phasenmodelle sind Inhalt des nächsten Kapi-
tels.

Zusammenfassung

In diesem Kapitel wurden grundlegende Definitionen zum Software Engineering ange-


geben und die historische Entwicklung skizziert.

Aufgaben zur Selbstüberprüfung

1.1 Zeigen Sie auf, wo das systematische Vorgehen des Software Engineering seine
Wurzeln hat und welche Parallelen es auf anderen Gebieten, z. B. den Ingenieur-
wissenschaften, hat.
1.2 Es seien zunächst die beiden Unterteilungen des Software Engineering „Planung“
und „Realisierung“ einer Softwareentwicklung betrachtet. Kommentieren Sie da-
mit Abb. 1.1, indem Sie die unterschiedlichen Kurven („ideal“ und „real“) quanti-
tativ den beiden Unterteilungen zuordnen.

SEI11 7
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle
In diesem Kapitel werden einige der gängigen Phasenmodelle gezeigt. Es werden
die Vor- und Nachteile dieser Modelle sowie ihr Einsatzbereich erläutert. In der
Praxis sind viele Modelle im Einsatz, sodass wir uns hier nur auf die wichtigsten
beschränken.
Nach Durcharbeiten dieses Kapitels sollen Sie insbesondere folgende Kenntnisse
und Fertigkeiten besitzen:
• den Sinn und Zweck von Phasenmodellen aufzeigen
• die Phasen des klassischen Wasserfallmodells nennen und inhaltlich be-
schreiben
• das Spiralmodell nach Boehm beschreiben und dem Begriff des Prototypings
zuordnen
• das V-Modell beschreiben
• das Diamantmodell erläutern
• agile Methoden nennen

Die große Unsicherheit hinsichtlich Kosten und Entwicklungszeiten komplexer Soft-


ware wurde durch ingenieurmäßige Planung und methodische Ansätze allmählich we-
sentlich reduziert. Natürlich gab es auch früher schon effizient entwickelte Software,
aber dies war häufig von der Erfahrung und der individuellen Planungsfähigkeit der be-
teiligten Entwickler abhängig. Bevor ein Entwickler solche Fähigkeiten erworben hatte,
waren oft längst einige Projekte „in den Sand gesetzt“ worden und hatten sehr hohe Kos-
ten erzeugt.
Damit nun nicht immer wieder die gleiche bittere Erfahrung bei jeder Programm-
entwicklung neu gemacht werden musste, vor allem dann, wenn unerfahrene Program-
mierer ans Werk gingen, ergab sich mehr und mehr die Notwendigkeit einer methodi-
schen Vorgehensweise, gerade und vor allem in der Planungsphase von Software.
Möchte beispielsweise jemand ein Haus bauen, so beauftragt er ja auch nicht direkt ei-
nen Maurer, der dann ohne Plan drauflosmauert und dauernd den Kunden danach fragt,
ob es so oder so gerade recht ist. Gefällt es dem Kunden nicht, müssen halt ein paar Mau-
ern wieder eingerissen und von Neuem gebaut werden. Bis ein Haus schließlich, wenn
überhaupt jemals, den Vorstellungen des Kunden entspräche, wäre sicher viel Zeit und
Geld verbraucht. Aus diesem Grund gibt es Architekten, die zuerst einmal mit dem Kun-
den zusammen eine Planung durchführen. Erst wenn diese zur Zufriedenheit des Kun-
den abgeschlossen ist, wird normalerweise ein Auftrag zum Bau eines Hauses erteilt.
Was aber beim Hausbau selbstverständlich erscheint, setzte sich bei der Softwareent-
wicklung erst langsam durch. Dort war in der Tat in den 60er-Jahren (und auch später
noch) ein mit dem geschilderten „Drauflosmauern“ vergleichbares „Drauflosprogram-
mieren“ nicht unüblich, was die genannten hohen Kosten und Entwicklungszeiten zur
Folge hatte. Deswegen ist – wie beim Hausbau – eine Unterteilung der durchzuführen-
den Aufgaben in überschaubare Phasen notwendig.

8 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

2.1 Klassisches Wasserfallmodell


Erste methodische Ansätze in einer Unterteilung des Entwicklungsprozesses bestanden
in lediglich zwei Phasen: der Systemanalyse und der Programmierung. Dies entspricht
bei der Errichtung eines Hauses einer groben Einteilung in die zwei Phasen Planung und
Realisierung. Man musste jedoch schon bald feststellen, dass eine subtilere Unterteilung
erforderlich war. Innerhalb dieser beiden groben Phasen traten nämlich noch zu viele
Fehlerquellen auf, die dann aber durch weitere Systematisierung der einzelnen Arbeits-
schritte beseitigt werden konnten. Eine bis heute noch oft erfolgreich anwendbare Sys-
tematisierung ist das klassische Wasserfallmodell. In der Literatur findet man manchmal
Abweichungen hinsichtlich der Anzahl und des Inhalts der einzelnen Phasen, doch im
Prinzip handelt es sich immer um folgende Einteilung:

Initialisierung

DV-Konzept

DV-Entwurf

Rückkopplungen Implementierung
(unerwünscht)

Test

Installation

Wartung

Abb. 2.1: Wasserfallmodell

Das Wasserfallmodell zählt zu den linearen Phasenmodellen. Das heißt, eine Phase kann
erst begonnen werden, wenn die davorliegende vollständig beendet ist. Wir schauen uns
jetzt die einzelnen Phasen genauer an.

Phase 1: Initialisierung
Die Initialisierungsphase startet das geplante Entwicklungsprojekt. In der Regel wird da-
mit begonnen, dass ein Unternehmen das geplante Projekt ausschreibt oder sich direkt
an ein Softwareentwicklungsunternehmen wendet mit der Bitte um eine Angebotser-
stellung. Zur Erstellung eines Angebotes ist es natürlich erforderlich, dass eine erste
Analyse des Problems erfolgen muss, aus dem die notwendige Information für das An-
gebot ableitbar ist. Ein Problem des angebotserstellenden Unternehmens ist dabei, dass
einerseits ein detailliertes Angebot erst erfolgen kann, wenn hinreichende Informatio-
nen über das Projekt vorliegen, und andererseits eine solche erste Problemanalyse be-

SEI11 9
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

reits recht arbeitsaufwendig werden kann, je nachdem, welchen Umfang das geplante
Projekt haben soll. Lehnt der Kunde das Angebot ab, ist die für die erste Analyse inves-
tierte Zeit verloren. Deshalb wird ein Entwickler zunächst eine recht grobe Analyse
beim Kunden vornehmen, die aber trotzdem eine erste Kostenabschätzung sowie eine
erste zeitliche Abschätzung des Projektverlaufs möglich macht.
Eine sich mittlerweile immer mehr verbreitende Variante ist, dass sich ein Softwareent-
wicklungsunternehmen bereits die Angebotserstellung bezahlen lässt. Dies ist insbeson-
dere im Bereich Multimediaprojekte der Fall, z. B. bei der Entwicklung von E-Com-
merce-Anwendungen. Dies liegt u. a. daran, dass in diesem Bereich ein strukturiertes,
methodenbasiertes Vorgehen in Ermangelung geeigneter Modelle kaum zu finden ist.
Deshalb wird schon für eine Angebotserstellung oft eine umfangreiche Detailanalyse er-
forderlich, und dieser Aufwand muss dann auch honoriert werden. Der Kunde hat aber
auch einen Vorteil von dieser Vorgehensweise, denn er hat dann eine ausführliche Ana-
lyse des Entwicklers vorliegen, die er – selbst wenn er das Angebot ablehnt – weiterver-
werten kann (diese kann er z. B. einem anderen Systementwickler zur Verfügung stel-
len).
Die Initialisierungsphase enthält in jedem Fall mindestens folgende Punkte:
• Problembeschreibung, in der Zweck und Rechtfertigung für das geplante
Unterfangen genannt wird
• Projektziele (gewünschtes Ergebnis des Projekts)
• grobe Projektbeschreibung: welche bereits installierte Hard- und Software ist vor-
handen, welche zusätzliche Hard- und Software muss angeschafft werden, sind
Netzwerkkomponenten erforderlich etc.
• grober Projektplan, der eine personale und zeitliche Aufwandsplanung und erste
grobe Projektorganisation enthält
• Kostenabschätzung
• Angebot an den Kunden
Den Kunden interessiert dabei natürlich in erster Linie, was die Durchführung des Pro-
jektes kostet und wie lange die Entwicklungszeiten sind. Diese dürfen also auf gar kei-
nen Fall fehlen.
Eine zuverlässige Aufwands- und Kostenabschätzung birgt jedoch die größte Problema-
tik. Kunden bevorzugen in der Regel Festpreise, aber gerade die setzen eine realistische
Kostenabschätzung voraus und wie beschrieben würde das eine intensive Problemana-
lyse erfordern. Erfahrene Entwickler haben hier deutliche Vorteile, da sie oft ein recht
sicheres Gefühl für Art und Umfang anstehender Entwicklungsaufgaben haben.
Falls Festpreise gewünscht sind, dann ist es in jedem Fall ratsam, einen geeigneten Puffer
für nicht vorhersehbare Fälle (vgl. Murphys Gesetze aus Kapitel 1, Seite 4) einzuplanen.
Manche Entwickler veranschlagen z. B. einen Faktor 2 für die Entwicklungskosten im
Angebot an den Kunden gegenüber dem zunächst tatsächlich abgeschätzten Aufwand,
vor allem dann, wenn viele Unsicherheiten hinsichtlich der Problemanalyse bestehen.

10 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

Wenn keine Festpreise erforderlich oder wenigstens nicht verbindlich sind, so kann ver-
einbart werden, dass eine genaue Aufwandsschätzung erst nach Abschluss der nächsten
Phase vorgelegt werden muss, also wenn das DV-Konzept vorliegt. In diesem Fall möch-
te der Kunde jedoch häufig wenigstens einen Kostenvoranschlag für die Erstellung des
DV-Konzepts haben, der dann im Angebot angegeben wird.
Eine andere Möglichkeit, die Unsicherheiten bei Festpreisen wenigstens zu mildern, be-
steht darin, dass ein Festpreis mit einer „Unsicherheitsspanne“ vereinbart wird. So kann
beispielsweise ein Festpreis von 25.000 € verhandelt werden, wobei ein Spielraum von
± 15 % möglich ist. Damit besteht ein Spielraum von 3.750 €, der ggf. noch auf den Fest-
preis aufgestockt werden kann. Natürlich wünscht der Kunde in der Regel dann auch
eine genaue Begründung für solche Zusatzkosten.
Die zeitliche Dauer der Initialisierungsphase sollte so gewählt werden, dass für den Soft-
wareentwickler kein zu hohes Risiko für den Fall entsteht, dass der Kunde das Angebot
ablehnt. Da diese Phase der Angebotserstellung dient und normalerweise nicht bezahlt
wird, sollte der Entwickler hier seine Zeit angemessen investieren. So sollten selbst bei
Projekten mit einer finanziellen Größenordnung von beispielsweise 50.000 € höchstens
14 Tage für die Initialisierungsphase investiert werden; ein Dilemma kann dabei sein,
dass man einerseits einen möglichst niedrigen Festpreis ansetzen möchte (um unterhalb
eventueller Konkurrenzangebote zu liegen) und andererseits gerade für eine knappe Kal-
kulation eine ziemlich ausführliche Problemanalyse benötigt, deren Erstellung wieder-
um zeitaufwendig ist. Hier muss der Entwickler ein ausgewogenes Verhältnis finden. So
etwas ist aber oft nur durch einige Erfahrung zu gewinnen.
Ein Ergebnis der Initialisierungsphase ist die Erstellung eines Angebots an den Kunden.
Nachfolgend ist ein Beispiel für ein solches Angebot zu sehen.

SEI11 11
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

Supersoft AG 03.11.2014
Koboldweg 8
60321 Frankfurt am Main

Ventura & Co. KG


Abt. D
Herrn Ernst
Ernst-August-von-Halmackenreuther-Str. 10
43233 Kuhl
Angebot Entwicklung des Programms zur elektronischen Verwaltung von
Druckerzeugnissen

Sehr geehrter Herr Ernst,


Bezugnehmend auf Ihre Anfrage vom 22.10.2014 unterbreite ich Ihnen folgendes
Angebot:
Tätigkeit Aufwand (PT)
Problemanalyse und Erstellung eines Pflichtenhefts zur
Beschreibung der geplanten Anwendungsentwicklung 10
Entwurf der erweiterten Datenstrukturen und
Algorithmen/Funktionen gemäß dem erstellten Pflichtenheft 5

Implementierung der Software inkl. Modultes 5


Der Personentag wird mit 1.000,00 € abgerechnet.
Entwurfs- bzw. Implementierungsfehler, die unsererseits verursacht wurden, werden
innerhalb einer Anwendungstestphase durch Ihre Anwender bis zu vier Wochen nach
Programmübergabe von uns umgehend kostenfrei beseitigt.
Bei Auftragserteilung vor dem 10.11.2014 können die Arbeiten innerhalb von sechs
Wochen erledigt werden.

Ich hoffe, Ihnen mit diesem Angebot gedient zu haben.


Mit freundlichen Grüßen
Alfons Maurer, Geschäftsführer

Phase 2: DV-Konzept (Fachkonzept)


Diese Phase dient u. a. hauptsächlich zur Erstellung des „klassischen“ Pflichtenhefts. Die
in der Initialisierungsphase noch relativ groben Analysen müssen jetzt verfeinert wer-
den, sodass ein detailliertes Konzept entsteht, mittels dessen ein Programmentwickler
später seine Datenmodelle und Programmablaufpläne entwickeln kann.
Die Phase DV-Konzept (auch Fachkonzept oder Grobkonzept genannt) wird zumindest
teilweise von dem Systemanalytiker zusammen mit dem Kunden durchgeführt. Der
Kunde beschreibt dabei die Ausgangsverhältnisse und was das gewünschte Programm
später können soll. Dem Systemanalytiker kommt u. a. die Aufgabe zu, die „richtige“ In-

12 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

formation aus dem Kunden herauszuholen. Meistens kennt der Systemanalytiker das
Problemfeld des Kunden kaum, während der Kunde normalerweise nicht weiß, welche
Information für die genaue Systemanalyse wichtig ist. DV-Fachmann und Kunde müs-
sen daher einen gemeinsamen Ansatzpunkt finden. Grob unterteilt besteht ein Fachkon-
zept aus zwei Teilen, nämlich der Anforderungsanalyse, in der der Ist- und der Sollzu-
stand von Hard- und Software erarbeitet werden, und der Systemanalyse, in der eine
erste semantische Datenmodellierung vorgenommen wird. Letztere beschreibt, was
die Anwendung leisten soll, wie sie prinzipiell aufgebaut sein muss und wie das erreicht
werden kann. Das Ergebnis ist dann das Papier, das Pflichtenheft (je nach Sicht auch
manchmal Lastenheft) oder allgemein einfach nur Spezifikation genannt wird.
Im DV-Konzept wird damit also im Prinzip der Weg vom Istzustand zum Sollzustand in
einer für Kunde und Analytiker gleichermaßen verständlichen Sprache beschrieben. Da-
her ist das Pflichtenheft überwiegend verbal abgefasst und enthält wenig Abstraktionen.
Dies birgt jedoch die Gefahr, dass hier Inkonsistenzen entstehen können. Das heißt, es
könnte z. B. auf Seite 10 des Pflichtenhefts etwas stehen, was einer Anforderung auf Sei-
te 25 direkt widerspricht. Solche Widersprüche werden u. U. erst sehr spät bemerkt, im
ungünstigsten Fall erst beim Testen der Software. Dies führt dann zu den unerwünsch-
ten Rückkopplungen im Wasserfallmodell, denn es muss im Pflichtenheft eine Korrektur
gemacht werden, die alle nachfolgenden Phasen betreffen kann. So etwas ist dann meis-
tens auch relativ zeit- und kostenaufwendig.
Das Pflichtenheft sollte daher sehr genau und umsichtig entworfen werden. Manchmal
kann es hilfreich sein, hier sogar schon Elemente des DV-Entwurfs (z. B. ER- oder UML-
Diagramme) zu verwenden, sofern der Kunde in der Lage ist, diese zu verstehen.
Eine verbale Beschreibung von Daten und deren Zusammenhängen nennt man auch se-
mantisches Datenmodell.
Im Pflichtenheft sollten enthalten sein:
• (semantische) Modellierung der projektrelevanten Teilausschnitte der Realität
• Beschreibung des Istzustands, ggf. unter Zuhilfenahme von Organigrammen, Funk-
tionsabläufen und/oder bereits vorhandenen Datenmodellen
• Beschreibung des Sollzustands, ggf. unter Zuhilfenahme von Organigrammen,
Funktionsabläufen und/oder bereits vorhandenen Datenmodellen
• Beschreibung der Ein- und Ausgaben des zu entwickelnden Programms
• Beschreibung von Datenstrukturen (Länge und Art der Daten, z. B. Gleit- oder Fest-
kommazahlen, wie lang können maximal die jeweils abzuspeichernden Zeichenket-
ten sein)
• Beschreibung des Datenaufkommens, d. h. der Datenmengen, Datenherkünfte etc.
• Beschreibung des Wegs von der Dateneingabe zur Datenausgabe (macht beispiels-
weise dies im Istzustand bereits jemand „von Hand“, so ist diese Person zu befragen
und der genaue Funktionsablauf zu beschreiben; eventuelle Berechnungsformeln
etc. sind anzugeben)
• Beschreibung der geplanten Datenmanipulationsmöglichkeiten

SEI11 13
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

• Angaben über die voraussichtlichen Zugriffshäufigkeiten und den Benutzerkreis


(wer greift wie oft ggf. mit welchen anderen Benutzern gleichzeitig auf welche Da-
tenbestände zu)
• Angaben zu Sicherheitsaspekten (ist z. B. ein nach Hierarchien abgestufter Zugriff
einzelner Benutzer erforderlich, dürfen manche Benutzer nur bestimmte Daten an-
sehen/ändern, Passwörter, Administratorzugänge etc.)
• Angaben zum Datenschutz (ist z. B. der Betriebsrat mit einzubeziehen)
• Beschreibung von möglichen Validierungsprozeduren (Planung von Validierungs-
tests, wer testet wann was wie lange etc.)
• detaillierter Projektplan (wer macht wann was), z. B. in der Form eines
Balkendiagramms
• Art und Umfang evtl. neu anzuschaffender Hardware/Software/Personal
• Festlegung von Review-Terminen und evtl. Zahlungsbedingungen
• Festlegung von Projektpersonal und Verantwortlichkeiten auf Kundenseite
Im Hinblick auf eine möglichst effiziente Systementwicklung sollte das Pflichtenheft so
ausführlich wie möglich sein. Hier zu sparen wäre ein großer Fehler (wegen der u. U. zu
erwartenden Rückkopplungen). Diese Phase zusammen mit der nächsten Phase (DV-
Entwurf) sollte mind. 70 % der geplanten Entwicklungszeit des gesamten Projekts betra-
gen. Was hier versäumt wird, kann ein Vielfaches an Zeit bei der Implementierung kos-
ten.
Das fertiggestellte Pflichtenheft sollte am besten sowohl vom Kunden als auch vom Sys-
temanalytiker unterschrieben und damit von beiden Seiten als verbindlich anerkannt
werden. Sollte es später zu einem Rechtsstreit vor Gericht kommen, so kann dadurch
eine Beweispflicht (egal von welcher Seite) u. U. erleichtert werden. Außerdem kann da-
mit einer möglichen „Salamitaktik“ des Kunden vorgebeugt werden: Wenn der Kunde
nach und nach immer weitere Wünsche äußert, kann der Systementwickler sich auf das
gemeinsam festgelegte Pflichtenheft berufen und darüber hinausgehende Kundenwün-
sche als zusätzliche Aufwendungen in Rechnung stellen. Der Umfang des Pflichtenhefts
kann bis zu mehreren Hundert Seiten betragen, je nach Projektgröße.
Das Pflichtenheft sollte so gestaltet sein, dass der Softwareentwickler im Prinzip keine
weiteren Angaben vom Kunden benötigt, um das Anwendungsprogramm zu entwerfen
und zu programmieren. In der Praxis kann es natürlich trotzdem sein, dass auch in spä-
teren Phasen bei eventuellen Unklarheiten oder Widersprüchen der Kunde konsultiert
werden muss, um die Probleme beseitigen zu können.
Nachfolgendes Beispiel enthält bereits eine recht detaillierte semantische Datenbe-
schreibung, wie sie manchmal erst in der Nachfolgephase DV-Entwurf zu finden ist.
Häufig verschwimmen die Grenzen zwischen diesen beiden Phasen. Spätestens jedoch
in der Entwurfsphase müssen alle Angaben genau spezifiziert sein, aber je mehr Anga-
ben im Pflichtenheft gemacht werden und je genauer sie sind, desto besser und präziser
kann die Entwurfsphase durchgeführt werden. Hier also ein Beispiel für einen Aus-
schnitt aus einem Pflichtenheft, wobei eine detaillierte verbale Beschreibung eines rela-
tiv komplizierten Sachverhalts vorgenommen wird.

14 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

… Zunächst werden die Felder mit den Inhalten aus der Tabelle A90UMW ge-
füllt, und zwar alle Datensätze, bei denen die Felder KU_ZE und/oder
KENZZGES gefüllt sind. Dazu die Folgedatensätze, bei denen die Felder TEL
noch gefüllt sind, aber nur bis zum nächsten gefüllten Feld KU_ZE und/oder
zum nächsten Telefoneintrag. Nun wird von der Tabelle BUERO_90 aus gear-
beitet. Alle Datensätze, die in den Feldern BURO_ART, GB_KURZ,
KFZ_KENN und/oder ANSPRECH ein Kurzzeichen enthalten, werden mit den
Feldern KU_ZE und KENZZGES auf Übereinstimmung geprüft, und zwar je-
des Feld, das gefüllt ist. Ist Übereinstimmung vorhanden, muss noch der Ort
überprüft werden. Dazu Feld ORT_ aus Tabelle BUERO_90 mit dem Ortsna-
men im Feld NAMSITZ ab der Zeile mit dem gefundenen Code im Feld KU_ZE
bis zum nächsten Eintrag im Feld KU_ZE und innerhalb derselben Blocknum-
mer prüfen. Wird auch hier Übereinstimmung gefunden, dann werden die
Felder aus BUERO_90 in die Tabelle ANSPRE übernommen, und zwar in die
Zeile, in der der Code steht. Wird zu einem Datensatz in der Tabelle
BUERO_90 keine Übereinstimmung gefunden, wird der Datensatz an das
Ende der Tabelle ANSPRE angehängt, und zwar für jedes ausgefüllte Codefeld
ein Datensatz, hinter jedem hinzugefügten Datensatz werden drei weitere
Zeilen mit dem Firmenkurzzeichen, einer Block- und Zeilennummer gefüllt
und der relevante Ansprechpartnercode wird in das Feld KU_ZE eingetragen.
Nach diesem Abgleich werden alle Datensätze, in denen die Felder KU_ZE
und/oder KENZZGES gefüllt sind, aber denen noch keine Adresse zugeordnet
ist, mit der Adresse der jeweiligen Gesellschaft gefüllt (Straße, PLZ und Ort,
Postfach, PLZ zum Postfach und Ort aus Tabelle gesell). Da es in der Gesell-
schaftstabelle zwei Datensätze geben kann, ist zu prüfen, ob unter NAMSITZ
innerhalb des Blocks ein Ort aufgeführt ist (Feld NAME=1). Ist kein Ort auf-
geführt, dann ist immer die Adresse aus dem Datensatz 1/2 zu nehmen. Ist
ein Ort aufgeführt und er stimmt mit der Adresse von Satz 2/2 überein, dann
ist diese Adresse zu nehmen, sonst wieder die Adresse von Satz 1/2. Nun ist
jedem Datensatz, der mit einer Adresse gefüllt ist, aber noch keinen Eintrag
im A90UMW-Teil der Tabelle hat (außer dem Gesellschaftskurzzeichen), über
die Beziehung zur vertansprech-Tabelle ein Ansprechpartner aus dieser Ta-
belle zuzuordnen. Ist nur ein Ansprechpartner vorhanden, wird dieser ge-
nommen.
Sind mehrere Ansprechpartner vorhanden, wird der Ansprechpartnercode
(Feld abt) in Tabelle vertansprech mit dem Code in KU_ZE verglichen. Bei
Übereinstimmung erfolgt die Übernahme der Inhalte der Felder funktion, an-
sprech, telefon (telefax, funktel, email jeweils in die nächsten Zeilen, die be-
reits angelegt wurden). Ist keine Übereinstimmung vorhanden, wird der Da-
tensatz mit der niedrigsten Zeilennummer genommen, in dem ein Name
eingetragen ist. Ganz zum Schluss müssen noch die Zeilen, die nur Gesell-
schaftskurzzeichen, Block- und Zeilennummern enthalten, gelöscht werden

SEI11 15
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

Dieses semantische Datenmodell ist schon recht ausführlich und gibt bereits einige Vor-
schläge zur Implementierung. Das ist allerdings nicht immer so. Oft werden hier nur
Andeutungen gemacht, die dann erst im DV-Entwurf in dieser Detaillierungstiefe aus-
geführt werden. Doch die Faustregel, nämlich: „Je früher ins Detail gegangen wird, umso
besser“, sollte wann immer möglich angewendet werden.

Phase 3: DV-Entwurf
Die eigentliche Entwicklung des Anwendungssystems geschieht hier. Das Ergebnis wird
auch manchmal Feinkonzept genannt. Dabei spielt diese Phase die Rolle, die z. B. beim
Hausbau die Erstellung eines Bauplans, des „Blueprints“, spielt. So wie ein Architekt auf
dem Papier das zukünftige Haus plant (nach den Wünschen des Auftraggebers, bei uns
im Pflichtenheft niedergeschrieben), so plant der Entwickler in der Phase DV-Entwurf
die Einzelheiten der DV-Anwendung. Und ebenso wie beim Architekten ist das Ergebnis
auch in der Datenverarbeitung ein Bauplan im wahrsten Sinn des Wortes, der zum gro-
ßen Teil tatsächlich aus grafischen Elementen auf Papier, man könnte es ein Programm-
Blueprint nennen, besteht. Das Ergebnis dieser Phase ist also eine Programmspezifikati-
on, in der alle Einzelheiten beschrieben sind, die zur eigentlichen Programmierung des
Systems erforderlich sind. Dazu gehört übrigens auch eine detaillierte Planung von Test-
strategien zwecks Verifikation der entwickelten Programme. Diese Phase ist schwer-
punktmäßiger Gegenstand späterer Betrachtungen.

Phase 4: Implementierung
Wenn der Entwurf erfolgreich beendet ist, dann kann die Anwendung programmiert
werden. Diesen Vorgang nennt man Implementierung, da jetzt der Entwurf in codierter
Form auf den Computer gebracht wird. Je nach Anwendungsgebiet kann es sich dabei
um die berühmte „Knochenarbeit“ handeln, wenn z. B. ein Echtzeitsystem in C++ ge-
schrieben werden muss. Es kann aber auch sein, dass gar nicht viel im herkömmlichen
Sinne programmiert werden muss, sondern dass durch den geschickten Einsatz von
Werkzeugen vieles ohne Programmierarbeit erledigt werden kann. Gerade bei Daten-
bankanwendungen z. B. für betriebliche Informationssysteme sind sehr mächtige Tools
vorhanden. Im Idealfall kann der Programmcode sogar automatisch generiert werden.
Dazu ist es natürlich erforderlich, dass der DV-Entwurf hinreichend detailliert und for-
malisiert ist. Werkzeuge, die (fast) keine Programmierung mehr erforderlich machen,
sind ein hochaktuelles Forschungsgebiet des Software Engineering.
Die Vorgehensweise für die Implementierung ist im Allgemeinen „top-down“, d. h., es
werden zuerst die einzelnen Module geplant und diese dann jeweils verfeinert und im-
plementiert. Das Ergebnis der Phase Implementierung ist also ein ausführbares Pro-
gramm mit einer eventuell physikalisch implementierten Datenbasis. Auch diese Phase
wird erst später wieder genauer betrachtet.

16 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

Phase 5: Test
Der Implementierung des Programms folgt ein ausführlicher Test. Dabei sind zwei Test-
verfahren zu unterscheiden:
1) Programmtest
Hierunter versteht man den Test der einzelnen Programmmodule auf logische Kon-
sistenz und Übereinstimmung mit dem DV-Design. Dieser Test wird i. d. R. vom Pro-
grammierer selbst durchgeführt. Normalerweise beginnen diese Tests nicht erst am
Ende der Implementierungsphase, sondern bereits während der Codierung, d. h.,
während an den entsprechenden Modulen programmiert wird, werden auch gleich
die dazugehörigen Tests gemacht. Selbstverständlich wird nach Abschluss der Im-
plementierung noch einmal ein größerer Gesamttest durchgeführt, um das Zusam-
menspiel der einzelnen Module zu überprüfen und die Konsistenz mit dem DV-De-
sign festzustellen. Für diese Art des Testens gibt es kaum formale Methoden, da die
verschiedenen Möglichkeiten des Programmierstils einfach zu vielfältig sind. Als
Faustregel kann man allenfalls sagen, dass die Module jeweils für sich so weit wie
möglich ausgetestet werden sollten. Ist dies erfolgt, sollten einige zusammengehöri-
ge Module gemeinsam, d. h. deren Zusammenspiel, getestet werden. Man testet also
Gruppen von Modulen, bis schließlich das ganze Programm ausgetestet ist. Handelt
es sich z. B. um wissenschaftliche Anwendungen, so sollte eine verifizierbare Metho-
de im Voraus festgelegt werden. Soll ein Programm z. B. die numerische Lösung ei-
nes Differenzialgleichungssystems ermöglichen und sind dafür numerische Stütz-
stellen für eine Interpolations- oder Approximationsfunktion vorgesehen, so kann
ein sehr effektiver Test dadurch erfolgen, dass man einige der Stützstellen innerhalb
ihres Konvergenzintervalls leicht verschiebt. Damit hat man numerisch gesehen völ-
lig andere Zahlen als Input, aber das Ergebnis der Lösung müsste davon unabhängig
sein. Auch hilft es häufig, Extremfälle zu betrachten, deren Ergebnis im Voraus be-
kannt ist (z. B. triviale Fälle, wo alle Eingaben null sind).
2) Benutzertest
Ist das Programm durch den Programmtest als logisch richtig und fehlerfrei verifi-
ziert, muss ein Test unter Produktionsbedingungen erfolgen. Dieser Test kann zwar
auch vom Programmierer durchgeführt werden, es ist aber besser, wenn hier ausge-
wählte Benutzer testen. Wichtig ist dabei, dass keinesfalls schon echte Produktions-
prozesse gefahren werden, sondern es soll ja nur getestet werden in einer produkti-
onsidentischen Umgebung, aber nicht in der Produktion selbst. Es kann nämlich
hier noch zu folgenreichen Fehlern im Programm kommen, und das darf natürlich
nicht den Produktionsprozess behindern. Auch müssen die Benutzer, die diese Tests
durchführen, wissen, dass es sich hier um einen Test handelt, der u. U. zu unerwar-
teten Ergebnissen führen kann. Die Planung der Art und Weise dieser Tests sowie
der beteiligten Personen sollte bereits im Fachkonzept festgelegt werden.

Phase 6: Installation
Nach Abschluss der Testphase erfolgt die Installation der Anwendung in der Produkti-
onsumgebung. Es sollte dafür ein Zeitpunkt gewählt werden, der unkritisch für die lau-
fende Produktion ist. Das heißt, dass gleichzeitig keine kritischen Anwendungen dabei
laufen dürfen. Auch muss eine Datensicherung des Zielrechners vorhanden sein, sodass
ein Recovery möglich ist.

SEI11 17
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

Mit der Übergabe des Anwendungsprogramms wird normalerweise eine Installations-


routine mitgeliefert, die es einem unbedarften Anwender ermöglicht, das Programm
nach Starten einer Setup-Routine gemäß den Anweisungen auf dem Bildschirm zu ins-
tallieren.
Bei kritischen Produktionsumgebungen oder sehr komplizierten Programmsystemen
kann es sinnvoll sein, einen Installationstest auf einem produktionsidentischen System
zu machen, bevor man auf dem tatsächlichen Zielrechner installiert.

Phase 7: Wartung
Normalerweise ist die Wartungsphase – jedenfalls bei hinreichend getesteten Program-
men – wenig arbeitsintensiv. Manchmal werden Schulungsmaßnahmen (Anwender-
schulungen etc.) vorgenommen, die aber gewöhnlich zeitlich begrenzt sind. Läuft das
Programm erst einmal richtig, so bleibt hier nicht mehr viel zu tun. Manchmal kann es
vorkommen, dass kleinere Fehler erst nach längerer Zeit entdeckt werden oder dass Pro-
grammerweiterungen gewünscht werden. Solche Fälle können als Teil eines Wartungs-
vertrages im Voraus abgedeckt werden. Erfolgt dies nicht, dann muss in diesen Fällen
ein neues Angebot erstellt und das Ganze wie ein neues Projekt behandelt werden.

Rückkopplungen im Wasserfallmodell
Angenommen, beim Benutzertest stellt sich heraus, dass eine Ausgabe nicht das ge-
wünschte Ergebnis liefert. Der Benutzer geht damit dann zum Programmierer. Wenn es
sich nun um keinen Programmierfehler handelt, so wird der Programmierer den „Pla-
ner“, d. h. denjenigen, der den DV-Entwurf erarbeitet hatte, ansprechen. Hat auch dieser
keinen Fehler bei der Planung gemacht, so wird er schließlich im Fachkonzept nach-
schauen und dort den Fehler lokalisieren können. Das Fachkonzept aber wurde zusam-
men mit dem Auftraggeber nach Erstellung „abgesegnet“, d. h. beide, Entwickler und
Auftraggeber, hatten das Fachkonzept als verbindlich abgezeichnet. Jetzt muss also das
Fachkonzept korrigiert werden. Daraufhin müssen die Phasen DV-Entwurf, Implemen-
tierung und Test erneut durchlaufen werden. Dieser Sachverhalt stellt die unerwünsch-
ten Rückkopplungen dar.
Wenn man Pech hat, kann dies alles sogar mehrmals geschehen. Es existiert die Mei-
nung, dass ein Fachkonzept mit mehr als 20 Seiten immer an irgendeiner Stelle wider-
sprüchlich ist. Aus diesem Grund ist es gerade bei umfangreicheren Projekten sinnvoll,
schon frühzeitig computergestützte Werkzeuge einzusetzen. Es gibt bereits hilfreiche
Tools für die Erstellung von Fachkonzepten, die gewisse Inkonsistenzen frühzeitig ent-
decken können. Solche Tools setzen allerdings voraus, dass die Spezifikation sehr genau
durchgeführt wird. Es ist klar, dass bedingt durch den linearen Verlauf der Phasen im
Wasserfallmodell Fehler erst sehr spät entdeckt werden. Zudem bekommt ein Anwen-
der erst zu einem sehr späten Zeitpunkt das erste Mal etwas von dem Anwendungspro-
gramm zu sehen, nämlich frühestens beim Benutzertest. Bis dahin ist aber schon der
Hauptanteil der Entwicklungszeit verbraucht. Bei größeren Entwicklungsprojekten
kann es durchaus sein, dass ein Anwender erst ein Jahr nach Auftragserteilung zum ers-
ten Mal etwas auf seinem Computer zu sehen bekommt. Das ideale Wasserfallmodell
(ohne Rückkopplungen) setzt also etwas voraus, was es eigentlich nicht gibt: ideale Auf-
traggeber, die zum Zeitpunkt der Auftragsvergabe haargenau wissen, was sie wollen
und was das Programm später können soll, und ideale DV-Entwickler, die völlig fehler-
frei arbeiten und ebenfalls alle möglichen Probleme vorausgedacht haben. So etwas gibt

18 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

es natürlich nicht. Deswegen hat sich nach und nach eine weitere Planungsmethode eta-
bliert, in der die Not des realen (rückgekoppelten) Wasserfallmodells (nämlich dessen re-
ale Nichtlinearität) zur Tugend gemacht wurde: das Spiralmodell. Diese Vorgehensweise
sei als Nächstes näher betrachtet.

2.2 Spiralmodell
Die unerwünschten Rückkopplungen im Wasserfallmodell und die mittlerweile weitver-
breiteten Werkzeuge zur relativ schnellen Gestaltung von Programmoberflächen haben
dazu geführt, dass sich neben dem klassischen Wasserfallmodell noch ein weiteres eta-
bliert hat: das Spiralmodell. Der Grundgedanke ist, dass nicht die einzelnen Phasen je-
weils beendet sein müssen, bevor die nächste Phase begonnen wird, sondern dass die
Phasen in kleinere Teilphasen zerlegt werden und diese zunächst begonnen werden. Es
werden also die großen Phasen immer nur teilweise bearbeitet und danach wird gleich
zur nächsten Phase übergegangen. Das Ergebnis ist ein mehrmaliges, stückweises
Durchlaufen aller Phasen, bis ein gewisser Reifegrad erreicht ist. Betrachtet man z. B.
nur die vier Hauptphasen Analyse, Entwurf, Implementierung und Test, so könnte eine
einfache Version des Spiralmodells wie in Abb. 2.2 skizziert aussehen.

Analyse Entwurf

Test Implementierung

Abb. 2.2: Vereinfachtes Spiralmodell

Boehm hat diese Idee seiner verfeinerten Version des Spiralmodells zugrunde gelegt. Der
Vorteil einer solchen Vorgehensweise liegt auf der Hand: Durch die sukzessive Entwick-
lung des Systems bekommt der Anwender schon relativ früh Teile des zukünftigen Pro-
gramms zu sehen und es können so Fehler auch früher erkannt und damit rechtzeitig
korrigiert werden. Die ersten Programmteile, die durch die ersten, inneren Spiralläufe
im Quadranten der Implementierung repräsentiert werden, nennt man auch Prototy-
pen. Sie stellen eine Art Programmhülle dar, wobei hier hauptsächlich nur die Benutzer-
oberflächen realisiert sind ohne wirkliche Anwendungen oder Funktionen hinter den
Schaltflächen.

SEI11 19
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

kumulierte
Bestimmung von Zielen, Kosten Bewertung von Alternativen
Alternativen, Restriktionen Identifizierung und
Beseitigung von Risiken

Fortschritte Risikoanalyse

Risikoanalyse

Zustimmung
Risikoanalyse betriebs-
fähiger
Prototyp Prototyp
Risikoanalyse
P3
P2
P1
Start

Software- Software- Fein-


Anforderungsplan, Konzept für anforder- produkt- entwurf
Lebenszyklusplan den Betrieb ungsdefi- entwurf
nition

Modul-
codierung
Entwicklungs- Anforderungs-
plan prüfung
Modultest
Integration und Entwicklungs-
Testplan prüfung
Integration
und Test
Verbesserungs- Abnahmetest
plan und Einführung
Planung der nächsten Phase Entwicklung/Prüfung der
nächsten Produktstufe

Abb. 2.3: Spiralmodell nach Boehm

Eine alte Faustregel besagt, dass man für die endgültige Programmierung des Systems
alle Prototypen am besten wegwirft und den Zielzustand neu programmiert. Diese et-
was radikale Methode muss aber nicht immer angewendet werden, denn häufig liefert
das Prototyping recht brauchbare Programmmodule, die zumindest teilweise wieder-
verwendet werden können.
Das Spiralmodell birgt allerdings auch Nachteile. Insbesondere kann es dazu führen,
dass der Gesamtaufwand des Entwicklungsprojekts stark in die Höhe geht. Festpreise
sind bei so einer Vorgehensweise daher sehr gefährlich für den Entwickler. Leicht kann
es z. B. dazu kommen, dass der Anwender, wenn er die ersten Versionen der Prototypen
zu sehen bekommt, in Euphorie ausbricht und etliche Zusatzwünsche äußert, die so zu-
nächst im Budget gar nicht vorgesehen waren. Die Entwicklungsspirale kann so ins Un-
ermessliche wachsen, das Programm kommt nicht in der geplanten Zeit zum Abschluss.
In der Praxis erweist es sich daher häufig als sinnvoll, eine Kombination des klassischen
Wasserfallmodells mit dem Spiralmodell zu praktizieren. Die groben Planungen und das
Gerüst der Entwicklung sollten mit dem Wasserfallmodell gemacht werden, während
die Details dann nach dem Spiralmodell realisiert werden können.

20 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

2.3 V-Modell
Das V-Modell weist gewisse Ähnlichkeiten mit dem Wasserfallmodell auf. Es besteht
ebenfalls aus 7 Phasen, die linear angeordnet sind. Allerdings sind die Phasen anders ge-
gliedert und statt von oben nach unten sind sie v-förmig angeordnet 
(vgl. Abb. 2.4).

Spezifikation Validierung

Systementwurf Integrationstest

Komponentenentwurf Komponententest

Implementierung

Abb. 2.4: V-Modell

Der Entwicklungsverlauf beginnt links oben, geht dann nach unten und dann wieder
hoch und endet schließlich rechts oben. Dabei befinden sich auf dem absteigenden Ast
konstruktive Tätigkeiten, während auf dem aufsteigenden Ast Arbeitsschritte zur Qua-
litätssicherung vorhanden sind. Wie man sieht, wird zwischen dem Systementwurf und
dem Komponentenentwurf unterschieden. Diese Unterscheidung ist nicht ganz unprob-
lematisch, denn häufig sind die zu entwerfenden Komponenten bereits Teil des Systems.
Die Grenzen können hier also verwischen. Tendenziell kann man aber sagen, dass z. B.
UML-Diagramme eher in den Systementwurf und Funktionsablaufpläne eher in den
Komponentenentwurf gehören.
Es gibt mittlerweile eine Erweiterung des V-Modells, genannt V-XT. Gemäß der offizi-
ellen Dokumentation der Bundesrepublik Deutschland aus dem Jahr 20041 ist das V-Mo-
dell als Leitfaden zum Planen und Durchführen von Entwicklungsprojekten unter Be-
rücksichtigung des gesamten Systemlebenszyklus konzipiert. Die in einem Projekt zu
erstellenden Ergebnisse werden definiert und die konkreten Vorgehensweisen beschrie-
ben, mit denen diese Ergebnisse erarbeitet werden. Zusätzlich legt das V-Modell XT die
Verantwortlichkeiten jedes Projektbeteiligten fest. Es wird daher genau festgesetzt, wer
wann was in einem Projekt zu bewerkstelligen hat. Andere Richtlinien wie ISO-Stan-
dards sind zurzeit in Gebrauch, aber im Vergleich zum V-Modell XT weniger konkret,
da sie beispielsweise keine Produktvorlagen vorgeben.
Mit dem V-XT werden Projekte besser plan- und nachvollziehbar und erzielen zuverläs-
sige Ergebnisse von relativ hoher Qualität.

1. Vgl. https://www.cio.bund.de/Web/DE/Architekturen-und-Standards/V-Modell-XT/vmodell_xt_node.ht-
ml

SEI11 21
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

Selbst kleine und mittelständische Unternehmen profitieren vom V-Modell. Es bietet ih-
nen die Möglichkeit, auf standardisierte und erprobte Vorgaben für Entwicklungs- und
Managementprozesse zurückzugreifen. So können auch kleinere Unternehmen mit
überschaubarem Aufwand ihre eigenen Vorgehensweisen systematisieren und dadurch
zuverlässig hochwertige Entwicklungsergebnisse erzielen. Das V-Modell dient somit als
Vertragsgrundlage, Arbeitsanleitung und Kommunikationsbasis.
Das V-Modell XT besteht vereinfacht betrachtet aus einem Kern und einem Mantel, der
auf den Kern zurückgreift. Im Kern sind die grundlegenden Prinzipien des Modells ent-
halten. Der Mantel umfasst beispielsweise die Vorgehensbausteine, Durchführungsstra-
tegien und Entscheidungspunkte.
Das V-Modell XT beschreibt drei Projekttypen:
• Systementwicklungsprojekt eines Auftraggebers: Es wird im Projektverlauf eine
Ausschreibung erstellt und dann ein Auftragnehmer anhand der Angebote ausge-
wählt. Der Auftragnehmer entwickelt und liefert ein System. Der Auftraggeber muss
das Ganze am Ende abnehmen.
• Systementwicklungsprojekt eines Auftragnehmers: Hier wird im Projektverlauf
ein Angebot erstellt und dann (im Fall eines Vertragsabschlusses) das angebotene
System entwickelt.
• Einführung und Pflege eines organisationsspezifischen Vorgehensmodells: Die-
ser Projekttyp liefert den Rahmen für ein organisationsweites Qualitätsmanagement
von Entwicklungsprojekten. Zuvor müssen ggf. die aktuellen Vorgehensweisen un-
tersucht werden.
Bei jeder Systementwicklung gibt es eigentlich zwei Projekte: eines auf der Auftragge-
berseite, das man als „Systementwicklungsprojekt eines Auftraggebers“ bezeichnen
könnte, und eines auf der Seite des Auftragnehmers mit dem Projekttyp „Systement-
wicklungsprojekt eines Auftragnehmers“. Diese beiden Projekte sind nicht voneinander
losgelöst, sondern über die Auftraggeber-Auftragnehmer-Schnittstelle miteinander ver-
knüpft.
Die Begriffe im V-Modell XT werden durch Konventionsabbildungen mit Begriffen in
(Quasi-)Standards, Normen und Vorschriften in Beziehung gesetzt. Das V-Modell bein-
haltet unter anderem Abbildungen auf die Standards CMMI und ISO 15288.
Der Einsatz des V-Modells XT in Projekten bietet folgende Vorteile:
• Minimierung der Projektrisiken
• Verbesserung und Gewährleistung der Qualität der Entwicklungsergebnisse
• transparente Beherrschung der Gesamtkosten über den gesamten Systemlebenszyk-
lus
• Verbesserung der Kommunikation für alle Beteiligten

22 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

Abb. 2.5: V-Modell XT (Quelle: http://www.software-kompetenz.de/servlet/is/26381/Abb4_large.jpg)

SEI11 23
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

In Abb. 2.5 ist das V-Modell XT so dargestellt, dass im oberen Bildteil die jeweiligen
Phasen des „Auftraggeber-Projekts“ und im unteren Bildteil die des Auftragnehmer-Pro-
jekts“ sichtbar sind. Im mittleren Teil ist die Schnittstelle zwischen diesen beiden Projek-
ten zu sehen mit ihren Aktivitäten und Dokumenten.
Das V-Modell XT wird überwiegend (wie schon das V-Modell) im öffentlichen Dienst
eingesetzt.

2.4 Diamantmodell
Multimediaanwendungen stellen eine besondere Herausforderung für Entwicklungspla-
ner dar, denn hier sind teilweise künstlerische Elemente mit programmiertechnischen
verbunden. So findet die Integration von Sound (Sprache und Musik) und Bild (im Sinne
von Fotografien, Videoclips und Animationen) zunehmend statt. Die Verbreitung des In-
ternets trägt hier wesentlich bei. Eine Webseite, die nur aus reinem Text besteht, ist so
gut wie gar nicht zu finden. Die Vorteile solcher multimedialen (MME) Anwendungen
liegen auf der Hand: Der Mensch, der bekanntlich mehr Sinne besitzt als nur den Ge-
sichtssinn zum Lesen geschriebener Information, kann so pro Zeiteinheit gleichzeitig
mehr Information aufnehmen, wenn Bilder und Sprache mitgeliefert werden. Gerade im
pädagogischen Bereich (Lernprogramme etc.) ist dies eine große Hilfe.
Unabhängig von Ursachen und Zweck solcher multimedialer Anwendungen sind diese
jedenfalls stark verbreitet und die Tendenz ist rasant steigend. Die Verbreitung solcher
Applikationen geschieht schneller, als Ansätze und Konzepte für die Planung solcher
Systeme hinterherkommen. Dies führt zu ähnlicher Unsicherheit für die Entwickler wie
in den 60er-Jahren: Eine methodische Planung, Aufwandsschätzung und Durchführung
solcher Projekte ist oft nicht gegeben, was vor allem zu einer Kostenunsicherheit seitens
der potenziellen Auftraggeber solcher Multimediasysteme führt. Die Entwickler multi-
medialer Software sind somit an Entwicklungskonzepten interessiert, die in der Praxis
unkompliziert und effizient einsetzbar sind. Es sind Vorgehenskonzepte gewünscht, ähn-
lich wie z. B. beim Wasserfallmodell, die neben den Vorgehensabschnitten auch realisti-
sche Aufwands- und Ressourcenabschätzungen ermöglichen, und das möglichst ohne
das Studium dicker Bücher und das Verständnis darin beschriebener komplizierter Mo-
dellansätze.
Es ergibt sich also die Notwendigkeit, ein möglichst einfaches, in der Praxis einsetzbares
Phasenmodell zu haben, das die Planung des zeitlichen Aufwands, der technischen Res-
sourcen, des für die Entwicklung benötigten Personalaufwands und der Vorgehensweise
bei der Projektentwicklung und Durchführung liefert.
Für multimediale Systeme gibt es hier wenig Ansätze, jedenfalls kaum welche, die für
die industrielle Praxis schnell und leicht umsetzbar wären. Das liegt u. a. daran, dass
multimediale Systeme einfach viel mehr Komponenten besitzen als konventionelle be-
triebliche Informationssysteme. Neben einer Datenbank kommen eben noch die opti-
schen und akustischen Komponenten hinzu, deren Planung nicht so leicht formalisier-
bar ist.
Daher ist das Ziel hier eine Erweiterung bestehender Planungsmodelle um gerade die im
Multimediabereich hinzukommenden Komponenten. Es wird nachfolgend eine Refe-
renzliste erstellt, die dem Planer helfen soll, an alles zu denken und die Aufwände und
Ressourcen hierfür zu planen.

24 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

Planungs- und Entwicklungsphasen


Um die Möglichkeit zum Ausdruck zu bringen, dass einerseits einige der Multimedia-
komponenten eines zu entwickelnden Programmsystems gleichzeitig und relativ unab-
hängig voneinander entwickelt werden können und andererseits zu Beginn und zum
Abschluss eines solches Projektes i. d. R. nur eine oder wenige Personen beteiligt sind,
wurde für das nachfolgende Phasenkonzept eine 3-dimensionale Diamantform gewählt.
Dieser „Diamant“ ist im Raum mit den Achsen „Zeit“, „Personal“ und „technische Res-
sourcen“ platziert.

Zeit
techn. Ressourcen

Personal

Abb. 2.6: Diamantmodell

Personal-Zeit-Ebene
Nachfolgend werden Projektionen auf zwei Ebenen betrachtet. Es sei dabei ausdrücklich
darauf hingewiesen, dass die Relationen der Komponenten- und Phasengrößen nachfol-
gend nicht in der richtigen Proportion wiedergegeben sind. Die tatsächlichen Größen-
verhältnisse sind natürlich von Art und Umfang des Gesamtprojekts bzw. der einzelnen
Komponenten und Phasen abhängig und variieren daher von Projekt zu Projekt. Die
Darstellung ist also rein qualitativ, nicht quantitativ zu interpretieren.

SEI11 25
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

Zeit

Test &
Handing Over

Composing Components

Screen Layouts

Databases
Animation
Graphics

Photos
Videos
Sound
… …

Basic Planning & Design

Init

Personal

Abb. 2.7: Personal-Zeit-Ebene

Die Zeitachse repräsentiert die Projektphasen, die Personalachse die von den beteilig-
ten Entwicklern zu erstellenden Projektkomponenten. Dabei ist bezüglich der Perso-
nalachse die Breite des Diamanten im Allgemeinen ein Maß für die Menge an Perso-
naleinsatz (z. B. in Personentagen). Grundsätzlich können natürlich auch alle
Komponenten und Phasen von einer einzigen Person durchgeführt werden, was die Pro-
jektdauer entsprechend verlängert. Der Umstand, dass der Diamant „in die Breite“ geht,
soll andeuten, dass in einer breiteren Phase mehr Entwicklungsaufwand nötig ist, der
größtenteils unabhängig (und daher von mehreren Personen gleichzeitig) durchgeführt
werden kann.

Ressourcen-Zeit-Ebene
Für die Entwicklung der jeweiligen MME-Komponenten sind gewisse technische Res-
sourcen erforderlich, die nachfolgend näher beschrieben werden (vgl. Abb. 2.8). Der
Einsatz dieser Ressourcen sei so verstanden, dass als Ergebnis der Verwendung der Res-
sourcen-Komponenten immer eine Datei herauskommt, also ein Datenfile, das in dem
vom MME Composing Tool entsprechend verarbeitbaren Datenformat vorliegt.

26 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

Zeit

Composing Tool

Database Developer
Screen Design Tool
Animation Tool
Sound Studio

Photo Studio
Video Studio
… Graphic Tool …

Design Tool

techn. Resourcen

Abb. 2.8: Ressourcen-Zeit-Ebene

Eine ausführlichere Beschreibung des Diamantmodells bietet auch die Möglichkeit einer
konkreten Aufwandsabschätzung2.

2.5 Neuere Methoden


In letzter Zeit haben sich zusätzlich zur „klassischen“ Vorgehensweise diverse neuere
Methoden etabliert, von denen wir auf einige überblicksartig kurz eingehen wollen.

Unified Process (UP, RUP)


Die bekannten Autoren und „Erfinder“ der UML, Jacobson, Booch und Rumbaugh,
brachten 1999 ein Buch heraus mit dem Titel „Unified Software Development Process“.
Dieser Titel ist das Äquivalent des verkürzten Begriffes Unified Process. In diesem
Buch stellten verschiedene Autoren ihre Sicht solcher Prozesse dar. Der Begriff Unified
Process deckt alles ab, was man im Rahmen eines iterativen, inkrementellen Soft-
wareentwicklungsprozesses durchführt, z. B. nach dem Spiralmodell. Als Quasi-Stan-
dard hat sich die von Rational Software eingehend dokumentierte Variante behauptet,
die auch Rational Unified Process (RUP) genannt wird. Auf diese soll nun näher einge-
gangen werden.
Der Rational Unified Process (RUP) ist ein adaptives Prozessmodell und beschreibt, wer
was wann und wie in der Durchführung von Softwareprojekten zu tun hat. Der RUP ist
eng mit den eigens für ihn entwickelten Werkzeugen verwoben und wird von Rational
Software zusammen mit seinen Werkzeugen von der Firma IBM vertrieben.
Der RUP ist dadurch gekennzeichnet, dass er
• anwendungsfallgesteuert,

2. Siehe z. B. Zöller-Greer, 2010b

SEI11 27
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

• architekturzentriert (d. h. die Architektur des Produkts in den Vordergrund stellt),


• risikogesteuert und
• iterativ/inkrementell
vorgeht.
Dabei verfolgt der RUP folgende sechs Best Practices, die mit den vier genannten Cha-
rakteristiken einhergehen:
• iterative Entwicklung
• Anforderungsmanagement
• architekturzentrierte Entwicklung
• visuelle Modellierung (z. B. mit UML)
• Qualitätssicherung
• Änderungsmanagement
Diese Best Practices sind die Gestaltungsgrundlage für den RUP und finden sich somit
in den Abläufen wieder.
Abb. 2.9 zeigt einen Überblick über die verwendeten Disziplinen.

Abb. 2.9: Phasen und Tätigkeiten im RUP 


(Quelle: http://www-128.ibm.com/developerworks/rational/library/4721.html)

Der Lebenszyklus des RUP ist prinzipiell in vier Phasen unterteilt:


• Inception (Beginn: Projektumfang festlegen)
• Elaboration (Ausarbeitung: umsetzbare Architektur entwickeln)
• Construction (Konstruktion: Skelett der Architektur mit Funktionalität füllen)
• Transition (Umsetzung: Anwendung in die Nutzerumgebung überführen)

28 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

Die Disziplinen (auch als Workflows bezeichnet) orientieren sich an speziellen Rollen
innerhalb des Entwicklungsteams, die von bestimmten Personen oder Gruppen wahrge-
nommen werden. Diese Disziplinen sind im Einzelnen:
• Geschäftsprozessmodellierung
• Anforderungen
• Analyse und Design
• Implementierung
• Test
• Überführung in die Einsatzumgebung
• Konfigurations- und Änderungsmanagement
• Projektmanagement
• Projektumfeld
So simpel und einleuchtend die Ausführungen bisher auch sind, einen ersten Einblick in
die Komplexität des RUP gibt Abb. 2.10, wo das Zusammenspiel zwischen Rollen, Akti-
vitäten und Artefakten dargestellt wird.

Abb. 2.10: Zusammenspiel zwischen Rollen, Aktivitäten und Artefakten


(Quelle: http://www-128.ibm.com/developerworks/rational/library/4721.html)

SEI11 29
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

Der RUP versucht für jedes mögliche Projekt eine Vorgehensweise bereitzuhalten und
dies mit der in Abb. 2.10 dargestellten Notation zu beschreiben. Damit werden sehr viele
Abhängigkeiten zwischen den Elementen des RUP erzeugt, die zu der hohen Komplexi-
tät führen. Daher ist auch eine Anpassung („Tailoring“) des RUP an das jeweilige Projekt
unerlässlich.

Agile Softwareentwicklung
Agile Softwareentwicklung ist der Oberbegriff für den Einsatz von Agilität (lat. agilis:
flink; beweglich) in der Softwareentwicklung. Der Begriff unterscheidet – je nach Teil-
bereich der Softwareentwicklung – Agile Modeling, Agile Processing oder Agile Pro-
gramming. Agile Softwareentwicklung versucht mit geringem bürokratischem Auf-
wand und wenigen Regeln auszukommen.
In Abb. 2.11 sehen Sie das mittlerweile „berühmte“ Agility-Poster, das die wichtigsten
Grundsätze dieser Methode zeigt.
Zweck agiler Softwareentwicklung ist es, den Softwareentwicklungsprozess flexibel und
schlank zu machen. Der Fokus liegt dabei mehr auf den zu erreichenden Zielen sowie
auf technischen und sozialen Problemen bei der Softwareentwicklung. Die agile Soft-
wareentwicklung kann als eine Gegenbewegung zu den oft als schwergewichtig und bü-
rokratisch angesehenen traditionellen Softwareentwicklungsprozessen wie dem Ratio-
nal Unified Process oder dem V-Modell angesehen werden.
Erste Ansätze zu agiler Softwareentwicklung finden sich schon Anfang der 1990er- Jah-
re; wirkliche Popularität erreichte die agile Softwareentwicklung erstmals 1999, als Kent
Beck das erste Buch zu Extreme Programming veröffentlichte. Das Interesse an diesem
Thema ebnete den Weg auch für andere agile Prozesse und Methoden. Die Bezeichnung
„agil“ für diese Art der Softwareentwicklung tauchte erstmals im Februar 2001 bei ei-
nem Treffen in Utah auf, als Ersatz für das bis dahin gebräuchliche Wort „leichtgewich-
tig“ (engl. lightweight). Bei diesem Treffen wurde auch das sogenannte Agile Manifest
formuliert. Dieses lautet:
1) Individuen und Interaktionen sind wichtiger als Prozesse und Werkzeuge. – Zwar
sind wohldefinierte Entwicklungsprozesse und Entwicklungswerkzeuge wichtig,
wesentlicher sind jedoch die Qualifikation der Mitarbeitenden und eine effiziente
Kommunikation zwischen ihnen.
2) Funktionierende Programme sind wichtiger als ausführliche Dokumentation. – Gut
geschriebene und ausführliche Dokumentation kann zwar hilfreich sein, das eigent-
liche Ziel der Entwicklung ist jedoch die fertige Software.
3) Die stetige Abstimmung mit dem Kunden ist wichtiger als die ursprüngliche Leis-
tungsbeschreibung in Verträgen. – Statt sich an ursprünglich formulierten und mitt-
lerweile veralteten Leistungsbeschreibungen in Verträgen festzuhalten, steht viel-
mehr die fortwährende konstruktive und vertrauensvolle Abstimmung mit dem
Kunden im Mittelpunkt.
4) Der Mut und die Offenheit für Änderungen stehen über dem Befolgen eines festge-
legten Plans. – Im Verlauf eines Entwicklungsprojektes ändern sich viele Anforde-
rungen und Randbedingungen ebenso wie das Verständnis des Pro-blemfeldes. Das
Team muss darauf schnell reagieren können.

30 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

Abb. 2.11: Agile Softwareentwicklung3

Das Agile Manifest weist 17 Autoren und Erstunterzeichner aus, die auf unterschiedli-
chen Gebieten der agilen Softwareentwicklung tätig sind.

Agiles Prinzip
Ein agiles Prinzip ist ein Leitsatz für die agile Arbeit.
Manchmal werden agile Prinzipien auch als Methode bezeichnet.
Beispiele für agile Prinzipien:
• vorhandene Ressourcen mehrfach verwenden
• einfach (KISS-Prinzip)
• zweckmäßig
• kundennah
• gemeinsamer Code-Besitz (Collective Code Ownership)
• Der Übergang zwischen Prinzipien und Methoden ist fließend.

3. Quelle: Wikipedia.de

SEI11 31
© HfB, 02.12.20, Berk, Eric (904709)

2 Phasenmodelle

Agile Methode
Streng genommen bezeichnet der Begriff agile Methode eine an Agilität ausgerichtete
Methode zur Softwareentwicklung. Ein Kennzeichen agiler Methoden ist, dass sie in ei-
nem Prozess dazu dienen können, die Aufwandskurve möglichst gering zu halten. Als
Leitsatz gilt: Je mehr man nach Plan arbeitet, umso mehr bekommt man das, was man
geplant hat, aber nicht das, was man wirklich braucht. Daraus resultieren einige Prinzi-
pien des Agile Modelling und des Extreme Programming. Agile Methoden lassen sich in
zwei Gruppen unterteilen: die tatsächlichen Methoden und die den Methoden zugrunde
liegenden Prinzipien.
Beispiele für agile Methoden:
• Paarprogrammierung
• testgetriebene Entwicklung
• ständige Refaktorisierungen
• Story-Cards
• schnelle Codereviews

Agiler Prozess
Ziel der Vorgehensweise ist es, den Softwareentwicklungsprozess durch Abbau der Bü-
rokratisierung und durch die stärkere Berücksichtigung der menschlichen Aspekte ef-
fektiver zu gestalten. Den meisten agilen Prozessen liegt zugrunde, dass sie versuchen,
die reine Entwurfsphase auf ein Mindestmaß zu reduzieren und im Entwicklungsprozess
so früh wie möglich zu ausführbarer Software zu gelangen, die dann in regelmäßigen,
kurzen Abständen dem Kunden zur gemeinsamen Abstimmung vorgelegt werden kann.
Auf diese Weise soll es jederzeit möglich sein, flexibel auf Kundenwünsche einzugehen,
um so die Kundenzufriedenheit insgesamt zu erhöhen. Sie stellen damit einen Gegensatz
zu den klassischen Vorgehensmodellen wie dem V-Modell oder dem Rational Unified
Process (RUP) dar.
Allen agilen Prozessen ist gemeinsam, dass sie sich zahlreicher Methoden bedienen, die
Aufwandskurve möglichst flach zu halten. Inzwischen gibt es eine Vielzahl von agilen
Prozessen.
Beispiele für agile Prozesse:
• Adaptive Software Development (ASD)
Dies ist ein Softwareentwicklungsprozess, der auf das Rapid Application Develop-
ment zurückgeht; ASD ist eine Umsetzung des Prinzips der kontinuierlichen Anpas-
sung an immer neue Anforderungen (eher der Normalzustand) und ersetzt damit das
verbreitete Wasserfallmodell durch Zyklen von „Spekulieren“, „Zusammenarbeiten“
und „Lernen“.
• Crystal
Dabei handelt es sich um eine Familie von Softwareentwicklungsmethoden, die zu
den agilen Methoden der Softwareentwicklung gerechnet wird. Die Mitglieder die-
ser Familie sind in der Regel mit Farben bezeichnet. Die einfachste Variante heißt
hingegen Crystal Clear („glasklar“).

32 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Phasenmodelle 2

• Extreme Programming (XP)


Extremprogrammierung ist eine Methode, die das Lösen einer Programmieraufgabe
in den Vordergrund der Softwareentwicklung stellt und dabei einem formalisierten
Vorgehen geringere Bedeutung zumisst. Diese Vorgehensweise definiert ein Vorge-
hensmodell der Softwaretechnik, das sich den Anforderungen des Kunden in kleinen
Schritten annähert.
• Feature Driven Development (FDD)
Dies ist eine Sammlung von Arbeitstechniken, Strukturen, Rollen und Methoden für
das Projektmanagement.
Der Rational Unified Process (RUP) wird von vielen Vertretern agiler Methoden als nich-
tagiler, schwergewichtiger Prozess aufgefasst. Das ist allerdings umstritten. Es wurde so-
gar versucht, mit dem Agile Unified Process eine agile Variante von RUP zu entwickeln.

Zusammenfassung

Es wurden die zwei wichtigsten Phasenmodelle, das Wasserfallmodell sowie das Spiral-
modell, ausführlich besprochen. Zusätzlich wurden noch das V-Modell sowie das Dia-
mantmodell erläutert. Außerdem wurden die inhaltlichen Komponenten des Pflichten-
hefts besprochen; dazu gehört insbesondere das semantische Datenmodell als integraler
Bestandteil. Schließlich wurde noch auf neuere Methoden, wie RUP und agile Soft-
wareentwicklung, eingegangen.

Aufgaben zur Selbstüberprüfung

2.1 Wozu dienen Phasenmodelle?


2.2 In welcher Phase des Wasserfallmodells sind folgende Sachverhalte angesiedelt:
a) Angebotserstellung
b) Projektplan
c) Semantisches Datenmodell
d) ER-Diagramm
e) Anwendertest
2.3 Was sind Vor- und Nachteile des Spiralmodells gegenüber linearen Modellen?
2.4 Warum ist im Diamantmodell eine 3D-Darstellung gewählt?
2.5 Recherchieren Sie (z. B. im Internet oder in der Online-Bibliothek) und machen Sie
eine Untersuchung für Vorgehensmodelle, indem Sie versuchen herauszufinden,
wann welches Modell (z. B. agil vs. konventionell) bezüglich welcher Projektpara-
meter am geeignetsten erscheint.

SEI11 33
© HfB, 02.12.20, Berk, Eric (904709)

3 Softwareergonomie
In diesem Kapitel besprechen wir die Grundlagen und Anwendungen von Soft-
wareergonomie. Sie sollen nach Durcharbeiten dieses Kapitels in der Lage sein,
• den Zweck von Softwareergonomie zu nennen,
• die Aufgaben eines Usability Engineers zu kennen,
• softwareergonomische Dialoggestaltung zu produzieren.

3.1 Definition und Rechtsgrundlagen


W. Jastrzebowski definierte bereits 1857 in seinem Werk „Grundriss der Ergonomie“ den
Begriff der Ergonomie wie folgt:
„Ergonomie ist ein wissenschaftlicher Ansatz, damit wir aus diesem Leben die besten
Früchte bei der geringsten Anstrengung mit der höchsten Befriedigung für das eigene
und für das allgemeine Wohl ziehen.“
Etwas formaler klingt dagegen die Definition, bezogen auf Software, heutzutage:
Softwareergonomie (von griech. ergon = Arbeit und nomos = Gesetzmäßigkeit) ist die
Wissenschaft, die sich mit der Anpassung von Software an die Stärken und Schwächen
des Menschen befasst4.
Ergonomische Software kann man somit im weitesten Sinn als „gebrauchstaugliche“
Software bezeichnen, die einen Benutzer unterstützt, ohne ihm dabei (noch mehr) Ar-
beitsschritte oder Probleme aufzuhalsen, die durch die Software (und nicht durch die
Arbeitsaufgabe selbst) bedingt sind (vgl. Abb. 3.1).
Für den Betrieb von Bildschirmarbeitsplätzen fordert der Gesetzgeber in der Bild-
schirmarbeitsverordnung, dass gewisse „Grundsätze der Ergonomie“ eingehalten wer-
den müssen. Diese Grundsätze sind durch internationale Normen festgelegt. Seit 1999
gibt es ein offizielles deutsches Prüfverfahren, in dem der Nachweis dieser Grundsätze
(und zur Klärung von Streitfällen) beschrieben wird: das Prüfhandbuch „Gebrauchs-
tauglichkeit“ der Deutschen Akkreditierungsstelle Technik (DATech5).

4. Wir folgen in diesem Abschnitt u. a. den Ausführungen des Deutschen Delegationsleiters im internationalen
Normenausschuss für Softwareergonomie, Wolfgang Redtenbacher (vgl. www.redtenbacher.de) sowie dem
„Handbuch Softwareergonomie“ der Unfallkasse Post und Telekom.
5. www.datech.de

34 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Softwareergonomie 3

Abb. 3.1: Umfeld Softwareergonomie

Die EU-Bildschirmrichtlinie (90/270/EWG) wurde in Deutschland durch die Bild-


schirmarbeitsverordnung vom 20.12.1996 umgesetzt. Die Vorschriften sind seit dem
01.01.2000 für alle typischen Bildschirmarbeitsplätze rechtsverbindlich.
Im Anhang dieser Verordnung wird u. a. das „Zusammenwirken Mensch – Arbeitsmit-
tel“ behandelt. Dort heißt es in Abs. 20:
„Die Grundsätze der Ergonomie sind insbesondere auf die Verarbeitung von Informati-
onen durch den Menschen anzuwenden.“
Einzelheiten dieser „Grundsätze der Ergonomie“ sind in der Verordnung allerdings nur
ansatzweise beschrieben. Genaueres findet sich in der internationalen Normenreihe ISO
9241: „Ergonomische Anforderungen für Bürotätigkeiten mit Bildschirmgeräten“, die in
17 Teilen die verschiedenen Aspekte der menschengerechten (ergonomischen) Gestal-
tung von Bildschirmarbeitsplätzen behandelt. Für alle praktischen Zwecke ist daher die-
se Normenreihe zur Interpretation der Bildschirmarbeitsverordnung zu bevorzugen.
Was wenige wissen: Die Bildschirmarbeitsverordnung ist nur für Arbeitgeber und nicht
für die Hersteller von Software rechtsverbindlich! Deshalb werden Arbeitgeber bei allen
Beschaffungs- und Entwicklungsprojekten im Allgemeinen entsprechende softwareer-
gonomische Anforderungen in den Verträgen mit den jeweiligen Softwareherstellern
einfordern. Am einfachsten geht dies, indem für die Beurteilung der Gebrauchstauglich-
keit der zu beschaffenden Software das Prüfhandbuch „Gebrauchstauglichkeit“ der
Deutschen Akkreditierungsstelle Technik (DATech) vereinbart wird. Eine entsprechen-
de Zusage durch den Lieferanten wird dann natürlich vertraglich verlangt.
Um die Entwicklung eines softwareergonomischen „Aussehens“ der zu entwickelnden
Anwendung kümmern sich entweder der Entwickler selbst und/oder ein sogenannter
Usability Engineer. Erfahrungsgemäß ist es so, dass ein Usability Engineer selten einfa-
che Antworten geben kann, sondern eher Fragen stellt: z. B. warum sind Daten gerade
„so“ gruppiert, welche Daten stehen in einer Auswahlliste zur Verfügung und so weiter.
Von den Antworten auf diese Fragen hängt dann ab, wie die entsprechende Normanwei-
sung konkretisiert wird. Oft wissen die Entwickler auf diese fachlichen Fragen keine

SEI11 35
© HfB, 02.12.20, Berk, Eric (904709)

3 Softwareergonomie

Antworten oder wenn man die „Fachseite“ fragt, bekommt man unterschiedliche Aus-
sagen aus unterschiedlichen Abteilungen. Dies ist der Punkt, an dem sich herausstellt,
dass die Anforderungen nicht ausreichend mit Benutzern abgestimmt sind. Benutzer
können zwar sagen, was sie benötigen, aber nicht, wie sie es technisch realisiert haben
wollen. Dafür ist eine koordinierte Zusammenarbeit der Beteiligten erforderlich.

Abb. 3.2: Usability Engineering

In Abb. 3.2 sehen Sie den vom DIN geforderten Übergang vom Prozess zum Produkt.
Wie man erkennen kann, greifen die angesprochenen Punkte ineinander und besitzen
gewisse Schnittstellen, die vom Usability Engineer erkannt und berücksichtigt werden
müssen.
Zur „Gebrauchstauglichkeit“ einer Software stellt der deutsche Delegationsleiter im in-
ternationalen Normenausschuss für Softwareergonomie, Wolfgang Redtenbacher, fest6:
Die Gebrauchstauglichkeit einer Software wird dadurch bestimmt, in welchem Maße sie
es ermöglicht, Arbeitsziele in einem Nutzungskontext effektiv, effizient und zufrieden-
stellend zu erreichen.
Effektivität bedeutet dabei, ob der Benutzer die vorgesehenen Aufgaben mit der Soft-
ware erledigen kann und die benötigten Ergebnisse korrekt erreicht werden (z. B. dass
ein Textverarbeitungsprogramm einen geschriebenen Brief richtig ausdruckt oder ein
Übersetzungsprogramm den gewünschten Begriff richtig übersetzt).
Die Effizienz der Software wird durch den Aufwand bestimmt, den der Benutzer zur Er-
reichung der Arbeitsziele mit der Software treiben muss. Nützliche Software soll zu ei-
ner Arbeitserleichterung führen, d. h. zu einer Senkung des notwendigen Bedienauf-
wands und ggf. der aufzuwendenden Denkarbeit.

6. Quelle: www.redtenbacher.de

36 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Softwareergonomie 3

Unter der Zufriedenstellung der Benutzer versteht man schließlich das Ausmaß, wie
gern die Benutzer die Arbeitsaufgabe mit der Software als Werkzeug erledigen. Zufrie-
denstellung umfasst den subjektiven Eindruck der Benutzer von der Effizienz und der
Beeinträchtigungsfreiheit (z. B. durch verringerten Stress) bei ihrer Arbeit.
Eine Software ist also dann ergonomisch (gebrauchstauglich), wenn sie für die auszu-
führenden Aufgaben des Benutzers geeignet ist und dabei insbesondere die Hauptaufga-
ben und Benutzereigenschaften unterstützt. Wenn der Benutzer durch fehlende Funkti-
onalität, durch Mehraufwand oder durch fehlerhafte Ergebnisse wesentlich
beeinträchtigt wird, dann ist die Software nicht ergonomisch im Sinne der Bildschirm-
arbeitsverordnung.
Salopp übersetzt ist also „Usability“ (Gebrauchstauglichkeit) die Summe von Effektivi-
tät, Effizienz und Zufriedenstellung.

3.2 Softwareergonomische Dialoggestaltung


Redtenbacher nennt für die ergonomische Gestaltung der Arbeitsschritte einer Software
7 Grundsätze, die in der internationalen Norm ISO 9241-10, „Grundsätze der Dialogge-
staltung“, beschrieben sind7:
1. Aufgabenangemessenheit: Ein Dialog ist aufgabenangemessen, wenn er den Be-
nutzer bei der Erledigung seiner Arbeitsaufgaben unterstützt, ohne ihn durch Eigen-
schaften des Dialogsystems unnötig zu belasten.
Beispiel:
Eine aufgabenangemessene Software zeigt dem Benutzer nur solche Informationen,
die er im Zusammenhang mit der Erledigung seiner Arbeitsaufgabe braucht, und
lenkt ihn nicht durch irrelevante Informationen oder unnötige Dialogschritte ab, die
nichts mit der Arbeitsaufgabe zu tun haben.
2. Selbstbeschreibungsfähigkeit: Ein Dialog ist selbstbeschreibungsfähig, wenn jeder
Dialogschritt entweder unmittelbar verständlich ist oder dem Benutzer auf Anfrage
erklärt wird.
Beispiel:
Eine selbstbeschreibungsfähige Software könnte z. B. die Eingabe des Geburtsda-
tums mit folgendem Hinweis abfragen: „Geben Sie das Geburtsdatum bitte in folgen-
der Form ein: TT.MM.JJ.“ Dadurch erkennt der Benutzer, was er tun muss und wel-
ches Eingabeformat verlangt wird.
3. Erwartungskonformität: Ein Dialog ist erwartungskonform, wenn er einheitlich
aufgebaut (konsistent) ist und den Erwartungen der Benutzer entspricht, die diese
aufgrund ihrer Kenntnisse aus dem Arbeitsgebiet, ihrer Ausbildung sowie allgemein
anerkannter Konventionen hegen.
Beispiel:
Bei einer erwartungskonformen Software erfolgt die Bedienung auf eine einheitliche
Art, und das Programm benutzt die Fachausdrücke, die im Bereich der Arbeitsauf-
gabe des Benutzers tatsächlich verwendet werden.

7. www.redtenbacher.de

SEI11 37
© HfB, 02.12.20, Berk, Eric (904709)

3 Softwareergonomie

4. Lernförderlichkeit: Ein Dialog ist lernförderlich, wenn er den Benutzer in den Lern-
phasen unterstützt.
Beispiel:
Wenn eine Software aufgabenangemessen und selbstbeschreibungsfähig ist und sich
erwartungskonform verhält, ist bereits viel für leichte Erlernbarkeit getan. Eine lern-
förderliche Software würde z. B. zusätzlich ein Learning by Doing ermöglichen, d. h.
das Ausprobieren neuer Funktionen erlauben, ohne den Benutzer für Fehler gleich
durch Datenverlust o. Ä. zu „bestrafen“ und dadurch jede Experimentierfreude im
Keim zu ersticken.
5. Steuerbarkeit: Ein Dialog ist steuerbar, wenn der Benutzer in der Lage ist, den Dia-
logablauf zu starten sowie seine Richtung und Geschwindigkeit zu beeinflussen, bis
das Ziel erreicht ist.
Beispiel:
Wenn eine Software zur Verwaltung von Mietwohnungen auch an Arbeitsplätzen
eingesetzt wird, an denen telefonische Auskünfte erteilt werden, so sollte es dort
möglich sein, ein erst teilweise ausgefülltes Eingabeformular ohne Datenverlust zu
unterbrechen, um die Daten des Anrufers schnell auf den Bildschirm holen zu kön-
nen.
6. Fehlertoleranz: Ein Dialog ist fehlertolerant, wenn das beabsichtigte Arbeitsergeb-
nis trotz erkennbar fehlerhafter Eingaben entweder mit keinem oder mit minimalem
Korrekturaufwand durch den Benutzer erreicht werden kann.
Beispiel:
Wenn in einem Eingabeformular z. B. im PLZ-Feld eine ungültige Eingabe steht, so
würde dies bei einer fehlertoleranten Software nicht zu fehlerhafter Verarbeitung
führen, sondern das Programm würde den Fehler erkennen und den Cursor gleich
zur Korrektur in das betreffende Feld stellen.
7. Individualisierbarkeit: Ein Dialog ist individualisierbar, wenn er an persönliche
Anforderungen und Fähigkeiten des Benutzers angepasst werden kann.
Beispiel:
Bei einer individualisierbaren Software kann z. B. die Schriftgröße für sehbehinderte
Benutzer vergrößert werden oder die Zuordnung der Maustasten kann für Linkshän-
der angepasst werden.
Ähnlich wie es für die ergonomische Gestaltung der dynamischen Dialogabläufe einer
Software die 7 „Grundsätze der Dialoggestaltung“ aus der Norm ISO 9241-10 gibt, so
existieren auch für die statische Informationsdarstellung 7 Grundsätze, die in der inter-
nationalen Norm ISO 9241–12 „Informationsdarstellung“ beschrieben sind:
1) Erkennbarkeit (die Aufmerksamkeit des Benutzers wird zur benötigten Information
gelenkt)
2) Unterscheidbarkeit (die angezeigte Information kann genau von anderen Daten un-
terschieden werden)
3) Lesbarkeit (die Information ist leicht zu lesen)
4) Verständlichkeit (die Bedeutung ist leicht verständlich, eindeutig, vermittelbar und
erkennbar)

38 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Softwareergonomie 3

5) Klarheit (der Informationsgehalt wird schnell und genau vermittelt)


6) Kompaktheit/Prägnanz (den Benutzern wird nur jene Information gegeben, die für
das Erledigen der Aufgabe notwendig ist)
7) Konsistenz (gleiche Information wird innerhalb der Anwendung entsprechend den
Erwartungen des Benutzers stets auf die gleiche Art dargestellt)
Für die meisten praktischen Zwecke können diese Grundsätze zu zwei übergeordneten
Zielen zusammengefasst werden: die Gestaltung handlungsleitender Informationen und
die Minimierung der Augenbelastung.
„Handlungsleitende Informationen“ bedeutet, dass dem Benutzer unmittelbar klar sein
soll, was zu tun ist. Handlungsleitende Informationen dürfen nicht versteckt sein, son-
dern die Aufmerksamkeit des Benutzers sollte auf sie gelenkt werden, damit er erkennt,
wo er sich gerade im Dialog befindet und was sein nächster Schritt sein soll. Insofern
sind handlungsleitende Informationen bei der Informationsdarstellung die Entspre-
chung zum Grundsatz der „Selbstbeschreibungsfähigkeit“ aus der Dialoggestaltung.
Eine Minimierung der Augenbelastung wird hauptsächlich durch geeignete Schrift- und
Zeichengrößen sowie eine günstige Wahl von Farben bzw. Farbkombinationen erreicht.
Die Eignung der wichtigsten Farbtöne als Hintergrund- bzw. Vordergrund-/Schriftfar-
ben kann der deutschen Norm DIN 66234-5 „Bildschirmarbeitsplätze: Codierung von
Information, Farbkombinationen“ entnommen werden:

Vordergrund (Schrift)
Hintergrund schwarz weiß magenta blau zyan grün gelb rot

schwarz n.a. + + - + + + -
weiß + n.a. + + - - - +
magenta + + n.a. - - - - -
blau - + - n.a. + - + -
zyan + - - + n.a. - - -
grün + - - + - n.a. - -
gelb + - + + - - n.a. +
rot - + - - - - + n.a.

Abb. 3.3: Ergonomische Eignung von Farbkombinationen

In Abb. 3.4 sehen wir eine kleine Liste, die diverse Probleme bezüglich der Softwareer-
gonomie darstellt:

SEI11 39
© HfB, 02.12.20, Berk, Eric (904709)

3 Softwareergonomie

Abb. 3.4: Erschwerungen

Wer nun glaubt, Softwareergonomie beziehe sich „nur“ auf das Layout der Bildschirme
und deren Bedienung, der irrt. Obwohl dies natürlich ein entscheidender Faktor ist, ver-
birgt sich hinter ergonomischer Software doch viel mehr, wie man schon aus Abb. 3.4
erahnen kann. In Abb. 3.5 ist dies noch deutlicher zu erkennen.

Abb. 3.5: Einordnung visueller Aspekte der Softwareergonomie

Bereits bei der Modellierung der Daten ist die softwareergonomische Auswirkung er-
heblich; die Eingabe von Daten und deren logische Zusammenhänge werden hier ja vor-
definiert. Sind hier schon Daten so modelliert, dass ein Anwender später umständlich
und evtl. sogar redundant Daten eingeben muss, dann kann die Oberfläche noch so
schön sein, dieser Mangel wird jeden Anwender stören.

40 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Softwareergonomie 3

3.3 Praktische Anwendungen


Durch die zunehmende Bedeutung von Internetanwendungen und die damit einherge-
hende Darstellung in Webbrowsern ergibt sich u. U. das Problem, dass das Design des
Entwicklers in einem Browser nicht so ankommt wie ursprünglich vorgesehen. Insbe-
sondere bei multimedialen Anwendungen kann dies ein ernsthaftes Problem darstellen.
Deswegen sind solche Anwendungen mit allen gängigen Browsern zu testen und ggf.
entsprechende Maßnahmen zu ergreifen.
Bei einer Website kommt es auf drei Aspekte an: Inhalt, Form (Design) und Organisation
(Struktur, Navigation). Nur wenn alle Aspekte miteinander abgestimmt entwickelt wer-
den, kommt es zu einer qualitativ guten Website.
Wie bei konventionellen GUI-Applikationen ist ebenfalls zwischen der Oberflächen-
und der Navigationsgestaltung zu unterscheiden. GUI-Applikationen verwenden oft
Fenster- bzw. Dokumentenansichten zur Darstellung von Informationen. Browsersoft-
ware sorgt jedoch sowohl für die Darstellung der Inhalte als auch für die Navigation
zwischen den einzelnen Seiten.
Kritiker wie Jakob Nielsen (2000) bemängeln dies und sagen einen Untergang der Brow-
ser voraus. Nach Ansicht von Nielsen gibt es keinen Grund, Daten abhängig von deren
Speicherort Festplatte oder Internet unterschiedlich zu behandeln. Aber diese Sichtweise
ist Zukunftsmusik. Für die Oberflächengestaltung von Websoftware existieren inzwi-
schen zahlreiche öffentlich zugängliche Styleguides, an denen man sich orientieren
kann.
Dann ist gerade im Internetbetrieb zwischen der reinen Betrachtung von Inhalten durch
den „normalen“ Surfer und dem redaktionellen Verwalten der Inhalte durch besondere
Webredakteure zu unterscheiden.
Betrachten wir dies am Beispiel:
Die Vermittlungsseiten eines Tierheims werden von Surfern besucht und sollen in an-
sprechender Weise die zu vermittelnden Tiere vorführen. Der Surfer soll schnell das
Wichtigste erkennen und auch leicht zu anderen Seiten navigieren können 
(siehe Abb. 3.6).

SEI11 41
© HfB, 02.12.20, Berk, Eric (904709)

3 Softwareergonomie

Abb. 3.6: Darstellung im Browser für den Surfer

Eine softwareergonomische Version der gleichen Seite stellt sich für einen Redakteur na-
türlich gänzlich anders dar (Abb. 3.7):
Es ist durch die Verbreitung von Entwicklungswerkzeugen in neuerer Zeit glücklicher-
weise häufig so, dass diese Werkzeuge dem Entwickler bei der Entwicklung von GUIs
die wichtigsten softwareergonomischen Gesichtspunkte auf Wunsch schon vorgeben. So
bieten z. B. Entwicklungstools für Webseiten oft vorgefertigte Templates, die sich soft-
wareergonomisch bewährt haben. Der Entwickler ist gut beraten, sich an diesen Design-
mustern zu orientieren.

42 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Softwareergonomie 3

Abb. 3.7: Darstellung im Browser für den Webredakteur

In Abb. 3.7 sieht man, dass hier eine eher datenbezogene, etwas „sterilere“ Darstellung
gewählt wurde, die aber für den Redakteur zweckmäßiger ist. Beim Anklicken des Links
„Ändern“ erscheint dann eine Art „Zoom“ in den betreffenden Datensatz, der schließlich
die redaktionellen Einträge und Anpassungen ermöglicht (Abb. 3.8).
Es gibt natürlich auch Content-Management-Systeme (CMS), die das Layout für den Re-
dakteur so nachbilden, wie es der Surfer sieht. Doch das ist in vielen Fällen eher un-
zweckmäßig, da, wie gesagt, die Zielgruppen völlig verschieden sind.

SEI11 43
© HfB, 02.12.20, Berk, Eric (904709)

3 Softwareergonomie

Abb. 3.8: Änderungen durch den Redakteur

Was die reine Oberflächengestaltung betrifft, so ist dies oft eine Frage des Geschmacks.
Verschiedene Menschen empfinden die gleiche Benutzeroberfläche manchmal als sehr
angenehm, während andere wiederum damit nicht zurechtkommen. Dies sollte beim Er-
stellen eines Pflichtenhefts berücksichtigt werden. So helfen bereits in dieser Phase Skiz-
zen von zukünftigen Bildschirmmasken, die mit den Benutzern abgesprochen werden
sollen. Existiert bereits Software, die von den späteren Benutzern bereits benutzt wird

44 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Softwareergonomie 3

und gut ankommt, dann sollte man sich an diesem Bildschirmlayout orientieren, damit
die Einarbeitung und Benutzung der neu zu schreibenden Anwendung möglichst unpro-
blematisch wird.

Zusammenfassung

In diesem Abschnitt wurden die Grundlagen der Softwareergonomie besprochen. Es


wurden zunächst die rechtlichen Gegebenheiten dargestellt und der Begriff des Usability
Engineering erläutert. Danach wurden wichtige Grundsätze zur Dialoggestaltung auf-
gezeigt und praktische Hinweise dazu gegeben. Schließlich wurden anhand eines Bei-
spiel einige praktische Hinweise gegeben.

Aufgabe zur Selbstüberprüfung

3.1 Für die Wandlung von Video- und Audioformaten gibt es diverse Freeware. Wie
beurteilen Sie folgende beide Benutzeroberflächen unter dem Gesichtspunkt der
Softwareergonomie?

Abb. 3.9: SuperConverter

SEI11 45
© HfB, 02.12.20, Berk, Eric (904709)

3 Softwareergonomie

Abb. 3.10: MediaCoder

46 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts


Hier wird aufgezeigt, wie man ein Softwareprojekt ausgehend von der Initialisie-
rung plant. Dieses Kapitel stellt in gewissem Sinn eine „Verfeinerung“ von
Kapitel 2 dar, wo ein Gesamtüberblick über alle Entwicklungsphasen gegeben
wurde. Es sei allerdings darauf hingewiesen, dass im Rahmen dieses Studienhefts
im vorliegenden Kapitel nur bis zur Phase „Entwurf“ vorgedrungen werden kann,
während das Projekt selbst natürlich alle Phasen des Wasserfallmodells um-
spannt. Weiterführende Entwurfstechniken (z. B. UML) sowie eine Beschreibung
der sich anschließenden Projektphasen finden sich in fortführenden Studienhef-
ten.
Sie sollen nach Durcharbeiten dieses Kapitels in der Lage sein,
• ein Problem zu analysieren,
• ein Pflichtenheft zu erstellen,
• einfache Modellierungsverfahren zu nennen,
• ein semantisches Datenmodell zu entwerfen,
• Szenarien und Zustände zu beschreiben,
• einen einfachen Projektplan zu erstellen.
Es werden hier allerdings keine quantitativen Betrachtungen angestellt, diese
sind Inhalt des Projektmanagements.

4.1 Erste Analyse des Problems


Es sei zunächst folgendes Beispiel betrachtet:
Wir stellen uns vor, dass eine Schallplattenfirma (nachfolgend „Tonträgerhersteller“ ge-
nannt) ihre administrativen Tätigkeiten zukünftig computergestützt durchführen möch-
te. Das mit dieser Entwicklung beauftragte Softwareentwicklungsunternehmen hat na-
turgemäß keine Erfahrung mit den im Verlag anfallenden Aufgabenstellungen. Da
begegnet uns also schon das erste Problem, nämlich die Kommunikation zwischen Auf-
traggeber und Auftragnehmer. Der Auftraggeber würde schon gleich zu Beginn gern
wissen, was die zu entwickelnde Software kosten soll. Dagegen benötigt der Auftragneh-
mer, um dies beurteilen zu können, schon recht detaillierte Angaben. Daher wird der
Auftraggeber dem Auftragnehmer zunächst eine kurze, grobe Problembeschreibung ge-
ben. Diese könnte in unserem konkreten Fall z. B. so aussehen:

Projektbeschreibung (Beispiel)
Es ist eine Software zu entwickeln, die die Abrechnung von Tonträgerherstellern (nach-
folgend „Lizenznehmer“ genannt) mit ihren Lizenzgebern (Künstler oder Produzenten)
ermöglicht. Der Arbeitsablauf gestaltet sich wie folgt:
1) Der Tonträgerhersteller bekommt von einem Künstler oder Produzenten
(Lizenzgeber) ein Tonband oder einen sonstigen Träger des Mastertapes einer Mu-
sikaufnahme zur Verwertung angeboten. Entscheidet sich der Tonträgerhersteller
dazu, die Aufnahme(n) herauszubringen, so wird ein Lizenzvertrag abgeschlossen.

SEI11 47
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

2) Der Lizenzvertrag enthält Angaben über die Titel des Mastertapes, deren Komponis-
ten, Texter, Bearbeiter, Verwertungsgesellschaft (z. B. GEMA), Musikverlage, Spiel-
dauern, Interpreten, Lizenzgeber, Lizenznehmer, Exklusivität (ja/nein), Vertragsdau-
er, Erstauflage der VHS, ggf. geplante Folgeauflagen, Erstlabelangabe, Länder der
Verwertung, Prozentsatz der Lizenzgeberbeteiligung am Großhandelsverkaufspreis,
Abrechnungsturnus, Vorschuss, Sonstiges. Der Lizenzvertrag muss ausdruckbar
sein.
3) Alle Personen und Verlage sind in einer eigenen Tabelle (Adressverwaltung) nicht-
redundant zu verwalten.
4) Alle Tonträger sind in einer Tabelle zu verwalten, zusammen mit ihren Auflagen. Es
soll ein Zugriff auf die Verkaufszahlen (inkl. Historie) in vorgebbaren Zeiträumen
möglich sein.
5) Es soll möglich sein, Statistiken zu erstellen (auch grafisch), und zwar
• über alle verkauften Tonträger in einem angebbaren Zeitraum,
• über die Verkaufszahlen eines bestimmten Tonträgers in einem angebbaren Zeit-
raum,
• über die Verkaufszahlen aller Produkte auswählbarer Lizenznehmer in einem
angebbaren Zeitraum,
• über den Verkaufsverlauf einzelner Titel (also evtl. über mehrere Tonträger hin-
weg),
• über den Verkaufsverlauf einzelner Komponisten (also evtl. über mehrere Ton-
träger hinweg),
• über den Verkaufsverlauf der Werke einzelner Verlage (also evtl. über mehrere
Tonträger hinweg).
6) Es soll möglich sein, eine Lizenzabrechnung für die Lizenzgeber über einen festleg-
baren Zeitraum durchzuführen (inkl. Historie), die den Zahlbetrag abhängig vom Li-
zenzvertrag und den Verkaufszahlen der beteiligten Tonträger des Lizenzgebers an-
gibt. Eine Aufstellung, welche Tonträger wie oft verkauft wurden, sollte vorhanden
sein.
Diese Angaben sind allerdings noch zu lückenhaft, um daraus schon die fertige Software
entwickeln zu können. Diese Lücken müssen in einer genaueren Analyse durch weitere
intensive Kommunikation zwischen Auftraggeber und Auftragnehmer geschlossen wer-
den. Doch für eine erste grobe Kostenabschätzung sollte diese Problembeschreibung
ausreichend sein, zumindest dann, wenn der Auftragnehmer über hinreichend Erfah-
rung auf diesem Gebiet verfügt. Der nächste Schritt ist dann ein Angebot des Auftrag-
nehmers an den Auftraggeber. Oft wird dabei ein Festpreis angeboten. Es ist aber noch-
mals darauf hinzuweisen, dass so etwas nur gemacht werden sollte, wenn der
Auftragnehmer über hinlänglich Erfahrung und „Gefühl“ für die auf ihn zukommende
Arbeit verfügt.
Ist der Auftraggeber mit dem Angebot einverstanden, wird er eine Auftragsbestätigung
bzw. eine Bestellung an den Auftragnehmer schicken. Damit kommt juristisch ein Ver-
trag zustande, und beide Vertragspartner sind zur Einhaltung der vereinbarten Punkte
verpflichtet.

48 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Zusammenfassend kann man also sagen: Die erste Problemanalyse dient dem Zweck der
Auftragserstellung. Der Aufwand für diese erste Analyse sollte nicht mehr als 15 % des
voraussichtlichen Gesamtaufwands betragen, da es ja sein kann, dass der Auftraggeber
das Angebot ablehnt und die Zeit für die erste Problemanalyse in der Regel nicht vergü-
tet wird.
Naturgemäß schließt sich an diese erste Problemanalyse mit Auftragsvergabe (im Was-
serfallmodell wurde dies Initialisierung genannt) eine verfeinerte Analyse an, die zur
nächsten Phase des Wasserfallmodells führt: dem Fachkonzept.

4.2 Verfeinerte Analyse des Problems: Erstellung des Pflichtenhefts


Es wurde bereits über die Komponenten eines Pflichtenhefts gesprochen. Das Pflichten-
heft selbst ist dabei ein Ergebnis bzw. wichtiger Bestandteil der Phase „DV-Fachkon-
zept“. Manchmal wird es einfach auch nur „Spezifikation“ genannt. Das ist allerdings et-
was irreführend, da unter diesem Begriff auch häufig das Ergebnis der nächsten Phase,
also des DV-Entwurfs, verstanden wird. Um hier zu unterscheiden, werden manchmal
auch die Begriffe „Grobkonzept“ für das Pflichtenheft und „Feinkonzept“ für den Ent-
wurf benutzt. Aber auch diese Begriffsvergabe ist nicht immer eindeutig so durchgehal-
ten.
Bevor also schon an dieser Stelle Missverständnisse auftreten, sollten Auftraggeber und
Auftragnehmer über die benutzten Begriffe Einigkeit erzielen. Zudem wird auch in der
Fachliteratur nicht immer die gleiche Begriffsdefinition verwendet. Während manchmal
die Begriffe „Fachkonzept“ und „Pflichtenheft“ im Wesentlichen synonym verwendet
werden, machen andere Autoren hier Unterscheidungen. Für die Zwecke des vorliegen-
den Studienhefts werden wir allerdings auch keinen praktischen Unterschied zwischen
einem Fachkonzept und einem Pflichten-heft machen.
Das Pflichtenheft wird von Auftraggeber und Auftragnehmer gemeinsam entwickelt. Es
ist ratsam, dass beide es nach Fertigstellung unterschreiben und damit dessen Inhalt „ab-
segnen“. Dies ist für beide Parteien eine juristische Absicherung, was genau geleistet
werden soll. In diesem Sinne stellt das Pflichtenheft Teil eines juristischen Vertrages dar.
DIN 69901 legt die Standardgliederung eines Pflichtenhefts fest:
1. Unternehmenscharakteristik
1) Name und Adresse des Unternehmens
2) Branche, Produktgruppe, Dienstleistungen
3) Unternehmensstruktur, Zahl der Betriebsstätten, Niederlassungen
4) Unternehmensgröße, Wachstumsrate
5) Organisation und Datenverarbeitung
6) Weitere wesentliche Angaben zum Unternehmen
2. Istzustand der Arbeitsgebiete
1) Überblick und Zusammenhänge; evtl. Strukturorganisation
2) Bisherige Verfahren für die Arbeitsgebiete 1 … n
3) Bisherige Hilfsmittel
4) Vorhandenes Fachwissen, Computerwissen, Organisationsniveau

SEI11 49
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

5) Unternehmensspezifische Besonderheiten
6) Bewertung des Istzustandes – Aufwand und Nutzen – Stärken und Schwächen
3. Zielsetzungen
1) Erwarteter quantifizierbarer Nutzen
2) Sonstige erwartete Vorteile
4. Anforderungen an die geplante Anwendungssoftware
1) Fachliche Anforderungen
1. Überblick und Zusammenhänge
2. Detaillierte Anforderungen an die Arbeitsgebiete 1 … n
• wesentliche Verfahren
• wesentliche Ein- und Ausgabeinformationen
• Verarbeitungsarten und -häufigkeit
• unternehmensspezifische Besonderheiten
2) Technische Anforderungen
1. Qualitätsanforderungen – Integration
• Dateiorganisation
• Zugriffsberechtigung, Datensicherheit und -rekonstruktion
• Form der Programmauslieferung
2. Dokumentation und Schulung
[Differenzierung in funktional und nichtfunktional]
5. Mengengerüst
1) Kartei/Stammdaten
2) Bestandssätze, Belege/Zahl der Bewegungen
3) sonstige Mengen- und Häufigkeitsangaben
6. Anforderungen an Hardware und Systemsoftware
7. Mitarbeiter für die Umstellung
8. Zeitlicher Rahmen
Um die Punkte eines Pflichtenhefts alle hinreichend zu erfüllen, ist es natürlich erfor-
derlich, dass gewisse Analysen beim Auftraggeber durchgeführt werden.
Es handelt sich bei dieser DIN-Beschreibung um den Maximalfall. Bei kleineren Projek-
ten sind nicht alle Punkte dieser Festlegung zu berücksichtigen.
Wir betrachten nachfolgend einige Methoden, mit denen die genannten Unterpunkte ei-
nes Pflichtenhefts teilweise erstellt werden. Es sind dabei nicht alle diese Methoden in
jedem Projekt erforderlich, das muss man jeweils konkret mit dem Auftraggeber ent-
scheiden. Auch werden, um den Rahmen dieses Studienhefts nicht zu sprengen, nach-
folgend nicht alle vorhandenen Verfahren vorgestellt; wir haben eine Auswahl getroffen,
von der wir glauben, dass sie die meisten Fälle abdecken kann.

50 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

4.2.1 Analyse Ist-/Sollzustände


Ein Pflichtenheft muss Angaben darüber enthalten, wie der momentane Zustand ist und
wie derselbe nach Realisierung des DV-Projekts aussehen soll. Natürlich beschränkt
man sich dabei auf den für das Projekt relevanten Realitätsausschnitt. Betrachten wir
dies wieder anhand unseres Beispiels einer Schallplattenfirma. Dort könnte es folgen-
dermaßen aussehen:

Istzustand
Außer einer verbalen Beschreibung des Istzustandes bedient man sich häufig noch zu-
sätzlich sogenannter Use-Case-Diagramme (manchmal auch nur U-Case-Diagramme
genannt). Diese bestehen lediglich aus kleinen Strichmännchen, die die beteiligten Per-
sonen kennzeichnen, sowie aus beschrifteten Ellipsen, die Vorgänge darstellen sollen. In
unserem Beispiel der Lizenzabrechnung kann ein solches Diagramm wie folgt aussehen:

Authors

Offers Song Publishing Contract

Artists Contract File Song in Database

Publisher/
Producer
Payoff 3 GEMA Reg.

Performer

Song produced Payoff 2 Licence Contract

Release Record
Record Studio
Record Company

Payoff 1

Distributor

Abb. 4.1: U-Case-Diagramm des Istzustandes

SEI11 51
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Ein U-Case-Diagramm bedarf immer auch einer verbalen Erläuterung. Diese könnte
z. B. so lauten:
Ein Autor (in Abb. 4.1 Strichmännchen oben) offeriert eine
Komposition (Song) einem Musikverleger oder Produzenten
(Publisher/Producer, Strichmännchen oben Mitte). Gefällt dem
Verleger das Lied, so wird ein Verlagsvertrag gemacht (Publishing
Contract, oben rechts) und die relevanten Daten in einer Kartei
abgelegt (Files Song in Database, oben rechts) sowie eine
Registrierung bei der GEMA durchgeführt. Jetzt beauftragt der
Verleger einen Produzenten (bzw. manchmal sind Verleger und
Produzent identisch) mit der Suche nach einem Interpreten
(Performer/Artist, Strichmännchen Mitte links). Wurde ein solcher
gefunden, so wird mit demselben ein Künstlervertrag abgeschlossen
(Artist Contract). Danach geht der Produzent mit dem Künstler in
ein Tonstudio (Record Studio, Strichmännchen links unten) und
produziert (mind.) einen Song. Das Ergebnis ist ein sogenanntes
Mastertape, also eine Aufnahme des fertig produzierten Liedes. Der
Produzent macht sich jetzt auf die Suche nach einem
Tonträgerhersteller, also der eigentlichen Plattenfirma (Record
Company, Strichmännchen Mitte unten). Hat er diese gefunden, so
wird jetzt zwischen dem Produzenten und dem Tonträgerhersteller
ein weiterer Vertrag abgeschlossen, der sogenannte Lizenzvertrag
(License Contract, Mitte rechts). Die Plattenfirma lässt daraufhin
die Tonträger mit dem Song vervielfältigen und veröffentlicht
diesen (Release Record, rechts unten), indem sie einen Distributor
(Strichmännchen rechts unten) mit dem Vertrieb der Tonträger
beauftragt (der die Tonträger schließlich den Schallplatten-
geschäften verkauft). In regelmäßigem Turnus rechnet der
Distributor mit der Plattenfirma über die verkauften Tonträger ab,
d. h., er zahlt nach Abzug seiner Provision einen bestimmten
Betrag (Payoff 1, ganz unten Mitte) an die Plattenfirma. Diese
wiederum zahlt an den Produzenten die im Lizenzvertrag vereinbarte
Provision (Payoff 2, Bildmitte). Der Produzent schließlich zahlt
daraufhin an den Künstler die im Künstlervertrag vereinbarte
Beteiligung (Payoff 3). Zu beachten ist dabei, dass zwar eine
Abrechnung nach Anzahl verkaufter Tonträger erfolgt, jedoch sind
ggf. nicht alle Songs eines Tonträgers abrechnungsfähig, sondern
nur diejenigen, die dem entsprechenden Lizenzvertrag zugrunde
liegen. Es ist also bei der Lizenzabrechnung nur der tatsächliche
Anteil an Liedern eines Tonträgers zu berücksichtigen. Der Autor
erhält seine Zahlungen entweder direkt von der GEMA (an die
Rundfunkunternehmen ihre Sendetantieme entrichten müssen und der
Tonträgerhersteller für jede gepresste Schallplatte eine
Schutzgebühr zahlen muss) oder, falls er selbst nicht GEMA-
Mitglied ist, vom Musikverlag (gemäß den Vereinbarungen im
Verlagsvertrag), der in der Regel dann GEMA-Mitglied ist und von
dort die entsprechenden Anteile erhält.

Diese Istzustandsbeschreibung ist immer noch relativ ungenau. Sie enthält beispielswei-
se keine genauen Angaben über die Attribute (z. B. welche Daten in den jeweiligen Ver-
trägen in welchem Format erfasst werden) oder in welchen Zeiträumen wie genau die
einzelnen Abrechnungen erfolgen etc.; so etwas kann entweder noch im Pflichtenheft
erfolgen, in dem z. B. ein entsprechendes Entity Relationship Model erzeugt wird (was
am besten wäre), oder dies erfolgt während der DV-Entwurfsphase (wo es spätestens

52 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

vorliegen muss). Des Weiteren wären der Istanalyse noch Beispielexemplare der betei-
ligten Verträge sowie ein Muster der Abrechnungen beizufügen. Diese sind also dem
Auftragnehmer auszuhändigen. Beispiele dafür siehe Abb. 4.2 bis Abb. 4.5.

SEI11 53
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Musikverlagsvertrag

zwischen

(Name des Autors)

zurzeit Mitglied/Bezugsberechtigter der Verwertungsgesellschaft/en (z. B. GEMA)


als Urheber des/der Werkes/Werke

(Liedtitel, Komponist/en, Texter)

zugleich für seine/ihre Erben und Rechtsnachfolger, im folgenden URHEBER genannt, und dem Verlag

(Musikverlag)

zurzeit Mitglied der Verwertungsgesellschaft/en (z. B. GEMA)


zugleich für seine Rechtsnachfolger, im folgenden VERLAG genannt.
1. Der/Die URHEBER räumt/räumen dem VERLAG die Nutzungsrechte an seinem/ihrem/ihren Werk/
Werken für alle Nutzungsarten ein soweit und solange diese nicht sowohl für den/die URHEBER als
auch für den VERLAG von einer Verwertungsgesellschaft treuhänderisch wahrgenommen werden. Die
Einräumung der Nutzungsrechte an den VERLAG ist, soweit nicht anders vereinbart, ausschließlich
und räumlich, zeitlich und inhaltlich unbeschränkt erfolgt.
2. Der Verlag ist insbesondere verpflichtet:
a) das/die Werk/Werke innerhalb einer angemessenen Frist nach Erhalt des vervielfältigungsreifen
Manuskriptes mit Nennung des Namens des URHEBERS und/oder MITURHEBER(S) in
handelsüblicher Weise zu vervielfältigen und es zu verbreiten;
b) sich für die Nutzung der ihm nach Ziffer 1. eingeräumten Rechte in handelsüblicher Weise
einzusetzen;
c) dem/den URHEBER/N von zum Verkauf bestimmten Ausgaben ............ Freiexemplare und auf
dessen/deren Verlangen dem/den URHEBER/N Exemplare zum Nettopreis direkt zu liefern. Ihr
Weiterverkauf darf nur zu dem vom Verlag gebundenen jeweiligen Ladenpreis erfolgen. Ausgaben
ohne Ladenpreis erhält/erhalten der/die URHEBER auf Verlangen zum Entstehungspreis des
VERLAGES. Werden Ausgaben vom VERLAG nur leihweise abgegeben, so dürfen sie weder
entgeltlich oder unentgeltlich veräußert noch zu Aufführungen, Aufzeichnungen, Herstellung von
Kopien u. a. benutzt werden.
d) soweit zum Schutz des Urheberrechtes an dem/den Werk/Werken besondere Formalitäten
erforderlich sind, diese in handelsüblicher Weise zu erfüllen. Für den Fall, dass ein Staat den Schutz
des Urheberrechtes oder seine Erneuerung oder Verlängerung von einer Anmeldung oder
Eintragung abhängig macht, so bevollmächtigt/bevollmächtigen der/die URHEBER hiermit den
VERLAG, dieses im eigenen Namen als Copyright-Eigentümer durchzuführen, und
verpflichtet/verpflichten sich der/die URHEBER zugleich für seine/ihre Rechtsnachfolger zur
Abgabe aller Erklärungen, die erforderlich und zweckmäßig sind, um die erforderlichen und
zweckmäßigen Anmeldungen, Erneuerungen, Verlängerungen und/oder Eintragungen
durchzuführen.
3. Der VERLAG ist insbesondere berechtigt:
a) die Höhe der Auflagen, die Art der Ausstattung, den Ladenverkaufspreis zu bestimmen und auch
abzuändern und Lagerbestände des/der Werkes/Werke unter Aufhebung des Ladenpreises
aufzulösen, wenn die Erträgnisse eine Verwaltung und Lagerung nicht mehr rechtfertigen (etwaige
Erlöse aus der Auflösung sind von einer Beteiligung am Notenabsatz ausgenommen). Der
VERLAG muss den/die URHEBER rechtzeitig vor der Auflösung der Lagerbestände
benachrichtigen, um ihm/ihnen Gelegenheit zum Erwerb der Bestände zu geben. Dem VERLAG
steht das Recht zu, die Auswertung des/der Werkes/Werke zu den in diesem Vertrag

Abb. 4.2: Musikverlagsvertrag (Ausschnitt)

54 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Künstlervertrag

zwischen

nachstehend kurz Künstler genannt,


und

nachstehend Tonträgerhersteller genannt.


§ 1 ALLGEMEINES
1. Gegenstand des Vertrages ist das Recht, Schallaufnahmen mit Darbietungen des Künstlers
auszuwerten.
2. Zu diesem Zweck verpflichtet sich der Künstler, wahrend der Vertragsdauer Titel zur
Herstellung von Schallaufnahmen darzubieten.
3. Der Künstler erklärt mit der Unterzeichnung des Vertrages ausdrücklich:
a) nicht und durch keinen irgendwie gearteten Vertrag am Abschluss dieses Vertrages
gehindert zu sein.
b) das Recht am persönlichen Vortrag der unter diesen Vertrag fallenden Aufnahmen
niemandem übertragen zu haben.
§ 2 RECHTSÜBERTRAGUNG
1. Der Künstler überträgt dem Tonträgerhersteller und seinen Lizenznehmern ohne
Einschränkungen und für die ganze Welt seine sämtlichen Leistungsschutzrechte und
-ansprüche sowie alle sonstigen Rechte, die er während der Vertragsdauer an seinen
aufgenommenen Darbietungen erwirbt, soweit diese Rechte übertragbar sind. Er räumt dem
Tonträgerhersteller mithin das ausschließliche und übertragbare Recht ein, seine
aufgenommenen Darbietungen in der ganzen Welt in jeder beliebigen Weise und unter jeder
Marke zu verwerten und/oder verwerten zu lassen.
Zur Sicherung der Exklusivverpflichtung (§ 3) überträgt der Künstler auch alle Rechte und
Ansprüche, die durch seine Darbietungen bei etwaigen Schallaufnahmen und Mitschnitten
Dritter entstehen.
2. Die Rechtsübertragung schließt insbesondere ein: das Recht zur und den Anspruch aus der
öffentlichen Aufführung und Sendung sowie das Recht zur Verwertung durch Film, Funk,
Fernsehen und andere Speicherverfahren.
3. Der Künstler ist jedoch berechtigt, Aufnahmen, die lediglich für Rundfunk- und
Fernsehübertragungen dienen, durchführen zu lassen. Er verpflichtet sich aber, während der
Vertragsdauer und während der in § 3 Pkt. 3 bestimmten Zeit stets zu verbieten, dass seine
Vorträge bei einer Rundfunk- oder Fernsehübertragung von dem Rundfunk- oder
Fernsehsender oder von Dritten zwecks Weiterverarbeitung auf Filmen, Schallplatten oder
sonstigen Wiedergabemitteln irgendwie festgehalten werden. Die Weitergabe einer
Aufnahme durch den aufnehmenden Rundfunk- oder Fernsehsender an einen anderen
Rundfunk- oder Fernsehsender ausschließlich für Sendezwecke ist von dem Verbot der
unzulässigen Weiterverbreitung ausgenommen. Der Künstler bevollmächtigt den
Tonträgerhersteller, Rechte und Verstöße gegen dieses Verbot in seinem Namen geltend zu
machen.

Abb. 4.3: Künstlervertrag (Ausschnitt)

SEI11 55
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

License Agreement

This agreement entered Into between (“LESSOR”) and (hereinafter called LICENSEE).
WHEREAS LESSOR represents and warrants that it has the exclusive rights for all the recordings
(hereinafter called “The Master Recordings”) and WHEREAS LICENSEE is an a position directly
or indirectly to provide manufacturing and marketing faculties for phonograph records in ( ) only
(hereinafter called “The Licensed Territory”), and warrants a release of such phonograph records.
Now therefore, in consideration of the foregoing and of the mutual promises hereinafter set forth,
it is agreed:
1.) LESSOR grants to LICENSEE the exclusive right and license to use THE MASTER
RECORDINGS for the purpose of manufacturing and selling phonograph records and/or
prerecorded tapes well known as MusiCasettes and/or 8-Track Cartridges therefrom in the
LICENSED TERRITORY.
a) LICENSEE is not authorized to transfer the right and license to use THE MASTER
RECORDINGS to a third party.
2.) LICENSEE agrees to release THE MASTER RECORDINGS under the label as a trademark
and in the form as described in the riders hereto only.
3.) a) In consideration of the rights herein granted, LICENSEE agrees to pay to LESSOR the
royalties stated in the rider A hereto as percentage of the retail list price (exclusive of sales
taxes) for all soundcarriers without any other deduction manufactured from THE
MASTER RECORDINGS leased hereunder and shipped In THE LICENSED
TERRITORY.
b) LICENSEE agrees to inform LESSOR of such retail list price in force in THE
LICENSED TERRITORY within 30 (thirty) days from uk date of countersigning of this
contract and will notify LESSOR of any changes thereof within 14 (fourteen) days of any
such change.
c) LICENSEE, agrees that it will not sell, directly or indirectly THE MASTER
RECORDINGS under the normal retail list price In THE LICENSED TERRITORY
without written permission by LESSOR.
4.) LICENSEE further agrees to pay to LESSOR Fifty (50) percent of all public performance and
broadcasting and tv fees, if any received in respect of all records manufactured, leased and sold
hereunder. provided, however, that whenever such fees are not computed and paid in direct
relation to the public perfomances and broadcasts and tv’s of such records, they shall be
computed for the purpose of the agreement by determining the proportion of any such fee
paid to LICENSEE as the number of records produced hereunder and sold in the area from
which the fee is derived bears to the total number of records sold in that area by LICENSEE.
5.) I. Statements in reasonable detail by LICENSEE to LESSOR of royalties and fees due,
pursuant to paragraph 3a and 4 hereof, shall be made every six month calendar period and
mailed to LESSOR within fourtyfive (45) days following the dose of such a period. Each
statement shall show at least the following;
a) catalogue-number of LICENSEE, title and artist(s) of each such sound-carrier
b) the quantity of sound-carriers distributed on behalf of LICENSEE during that
period
c) the quantity of sound-carrier manufactured from each master throughout the
applicable accounting period
d) the retail list price of each such sound-carrier
e) the royalty rate paid on each such sound-carrier
f ) the total royalties earned by each such sound-carrier
II. Together with the statements the payable royalties have to be received by LESSOR.
6.) In the event that by law any royalty-payment due to LESSOR hereunder cannot be
transmitted from the country in which such royalties are earned, LICENSEE agrees to notify
LESSOR within one week after such period as mentioned in paragraph 5 by registered written
letter and to put such payment at the disposal of LESSOR by depositing same at a bank-
account to be opened by LICENSEE at ist expense but in the name of LESSOR and of which
account LESSOR shall have the power of disposing only.

Abb. 4.4: Lizenzvertrag (Auschnitt)

56 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Abb. 4.5: Lizenzabrechnung (Ausschnitt)

Als Nächstes muss der Sollzustand beschrieben werden. Dieser soll ja später einen be-
stimmten Teil (oder alles) des beschriebenen Istzustands durch ein Anwendungspro-
gramm ersetzen. Es geht dabei gerade darum, dass durch Einsatz einer zu entwickelnden
Software gewisse Abläufe des Istzustands erleichtert bzw. automatisiert werden sollen.
Daher ist es wichtig, dass mit Bezug auf den Istzustand genau beschrieben wird, welche
Teile durch Software abgedeckt werden sollen und welche Inputs und Outputs dabei ge-
nau auftreten. Dies erfolgt durch die Beschreibung des Sollzustands.

SEI11 57
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Sollzustand
Eine erste Beschreibung des Sollzustands erfolgte bereits in der ersten Problem-
spezifikation, die (in der Initialisierungsphase) der Angebotserstellung diente. Nun geht
es darum, diese Beschreibung zu verfeinern, sodass daraus (ggf. zusammen mit den
nächsten Kapiteln) später ein DV-Entwurf entwickelt werden kann.
Ist der Istzustand hinreichend beschrieben, dann geht es bei der Sollzustands-
beschreibung eigentlich nur noch darum, welche Teile der Realität wie auf dem Compu-
ter abgebildet werden sollen. Je nachdem, wie das Pflichtenheft strukturiert ist, kann
jetzt bereits eine ausführliche Beschreibung erfolgen, oder, falls dies später im Pflichten-
heft vorgenommen wird, eine grobe, mehr prinzipielle Darstellung. Dies sei in das Er-
messen bzw. die Gepflogenheiten der beteiligten Unternehmen gestellt. Für unsere Zwe-
cke wählen wir den Weg, dass zunächst eine allgemeine Sollbeschreibung vorgenommen
wird. Die nachfolgenden Abschnitte geben dann weitere, detailliertere Informationen.
Eine Sollzustandsbeschreibung bezogen auf unser Beispiel könnte demgemäß z. B. so
aussehen:
Das zu entwickelnde Softwareprogramm soll den Teil des Istzustands
implementieren, der die administrativen Arbeiten bei der
Vertragserstellung der Künstler- und Lizenzverträge abwickelt
sowie die Lizenzabrechnungen automatisch erstellt. Die Arbeiten
der Musikverlagsverwaltung werden dabei ausgekoppelt und evtl. zu
einem späteren Zeitpunkt ebenfalls automatisiert (eigenes
Projekt). Es sollen sowohl (freie) Produzenten als auch
Schallplattenfirmen mit dem Programm arbeiten können. In ersterem
Fall werden Künstlerverträge erstellt und abgerechnet und im
zweiten Fall Schallplattenverträge. Grundlage der Abrechnung ist
in ersterem Fall die Abrechnung des Produzenten mit dem Künstler
und in zweitem Fall die Lizenzabrechnung der Plattenfirma an den
Produzenten. Die elektronisch abzuwickelnden Realitätsausschnitte
sind nachfolgendem U-Case-Diagramm (Abb. 4.6) zu entnehmen. Die
Benutzer des zu entwickelnden Programms werden dort mit User_1
(für den ersten Fall) und User_2 (für den zweiten Fall)
bezeichnet.

58 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Abb. 4.6: U-Case-Diagramm Sollzustand (Abdeckungsbereich der Software)

Als Input für das zu schreibende Programm dienen also zum Zweck der Vertragserstel-
lung alle Angaben aus dem Künstler-/Lizenzvertrag (insbesondere die für die spätere
Abrechnung notwendigen Daten wie Vertragspartner, Vertragsdauer, prozentuale Betei-
ligungen etc.) und für die Abrechnung dann die Daten des Distributors (Payoff 1) und/
oder der Plattenfirma (Payoff 2) (z. B. über Anzahl und Verkaufspreis der verkauften
Tonträger). Der Output des Programms liefert dann die jeweiligen Verträge bzw. Ab-
rechnungen. Damit verbunden ist die Möglichkeit gewisser statistischer Auswertungen
(welcher Künstler hat wie viele Tonträger verkauft etc., siehe Problembeschreibung).
Natürlich müssen alle bereits erfolgten Abrechnungen gespeichert und doppelte Ab-
rechnungen verhindert werden. Außerdem ist eine Adressverwaltung für alle beteiligten
Firmen und Personen zu integrieren wie auch eine Tonträgerverwaltung, da sich in der
Regel auf einem Tonträger mehrere Songs (Mastertapes) befinden, die von verschiede-
nen Interpreten sein können (d. h. sich auf verschiedene Künstler-/Lizenzverträge bezie-
hen). Einzelheiten hierzu sind z. B. dem semantischen Datenmodell (vgl.
Abschnitt 4.2.2) zu entnehmen.

SEI11 59
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

4.2.2 Semantische Datenmodellierung


Dies ist vielleicht der wichtigste Teil des Pflichtenhefts überhaupt. Der Begriff Semantik
steht für Bedeutung oder Inhalt. Unter einer semantischen Datenmodellierung versteht
man eine Beschreibung der Datenstrukturen, die noch nicht den strengen Kriterien des
DV-Entwurfs entspricht und in einer Form geschieht, die auch vom DV-technisch nicht
geschulten Auftraggeber verstanden werden kann. In Abschnitt 2.1 wurde im Rahmen
des Wasserfallmodells in der Erläuterung der Phase DV-Konzept bereits ein Beispiel für
eine rein verbale Beschreibung als mögliche semantische Datenmodellierung gegeben.
Dies muss aber nicht immer nur rein verbal geschehen, sondern es können bereits Ele-
mente eines ER-Diagramms benutzt werden, wie es z. B. in Abb. 4.7 dargestellt ist.
Wichtig ist aber, dass der Auftraggeber diese Syntax vollständig versteht; ist dies nicht
der Fall, sollte eine rein verbale Beschreibung bevorzugt werden.
Die semantische Datenmodellierung kann in verschiedenen Detaillierungsstufen erfol-
gen. Grundsätzlich gilt: Je ausführlicher, umso besser. Allerdings kann es manchmal
auch so sein, dass durch zu detaillierte Beschreibungen (gerade bei größeren Projekten)
der Überblick verloren geht. Ein erfahrener Systemanalytiker wird hier in der Lage sein,
das Wesentliche vom Unwesentlichen zu trennen, und die Details dann erst im DV-Ent-
wurf modellieren.

4.2.2.1 Entscheidungstabellen
Mithilfe dieser Technik ist es möglich, dynamische Abläufe in kompakter Form zu be-
schreiben. Eine Entscheidungstabelle enthält in den Zeilen die jeweiligen Funktionen
und in den Spalten die Erfüllungsregeln bzw. die Reaktion, wenn ein Kriterium erfüllt
ist oder nicht.
Betrachten wir zur Erläuterung den Fall eines Programms, das zum Verleih von DVDs
in einer Videothek eingesetzt werden soll.

Beispiel 4.1:
Zunächst werden die jeweiligen Regeln („Wenn-dann-Beziehungen“) entworfen:
• Wenn der Kunde im Kundenstamm erfasst und die DVD verfügbar ist, dann soll
die DVD ausgegeben werden.
• Wenn der Kunde nicht im Kundenstamm erfasst ist, dann soll die DVD nicht
ausgegeben werden.
• Wenn der Kunde bereits 5 DVDs ausgeliehen hat, dann soll die DVD nicht aus-
gegeben und der Kunde informiert werden.
• Wenn der Kunde zum aktuellen Zeitpunkt eine oder mehrere DVDs länger als 60
Tage ausgeliehen hat, dann soll bis zur Rückgabe der ausgeliehenen DVDs keine
weitere DVD mehr ausgegeben und der Kunde informiert werden.
• Wenn die DVD nicht verfügbar ist, dann wird die DVD nicht ausgegeben und der
Kunde wird informiert.
• Eine Reservierung von DVDs ist nicht zulässig und soll daher nicht berücksich-
tigt werden.

60 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Aktionen ermitteln:
A1: DVD ausgeben
A2: DVD nicht ausgeben
A3: Kunde informieren „DVD nicht verfügbar“
A4: Kunde informieren „Anzahl überschritten“
A5: Kunde informieren „Leihdauer überschritten“
Bedingungen ermitteln:
B1: Kunde im Kundenstamm
B2: DVD verfügbar
B3: Anzahl ausgeliehener DVDs nicht größer als 5 Stück
B4: Leihdauer nicht größer als 60 Tage
Ausgangssituation:

VHS ausgeben R1 R2 R3 R4 R5 R6 R7 R8 R9

B1 Kunde im Kundenstamm J J J J J J J J N

B2 DVD verfügbar J J J J N N N N –

B3 auszugebende DVDs  5 J J N N J J N N –

B4 Leihdauer  60 Tage J N J N J N J N –

A1 DVD ausgeben 

A2 DVD nicht ausgeben        

A3 Kundeninfo 
   
„DVD nicht verfügbar“

A4 Kundeninfo 
 
„Anzahl überschritten“

A5 Kundeninfo 
 
„Leihdauer überschritten“

Nun ist es häufig so, dass nicht von vornherein die optimale Darstellung gefunden wird.
Es können dann gewisse Optimierungen erforderlich sein. Zum Beispiel liegen bei R5/
R6 und bei R7/R8 jeweils eigentlich die gleichen Aktionen zugrunde. Dies lässt sich so
vereinfachen:

SEI11 61
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

1. Erste Optimierung

DVD ausgeben R1 R2 R3 R4 R5/R6 R7/R8 R9

B1 Kunde im Kundenstamm J J J J J J N

B2 DVD verfügbar J J J J N N –

B3 auszugebende DVDs  5 J J N N J N –

B4 Leihdauer  60 Tage J N J N – – –

A1 DVD ausgeben 

A2 DVD nicht ausgeben      

A3 Kundeninfo 
 
„DVD nicht verfügbar“

A4 Kundeninfo 
 
„Anzahl überschritten“

A5 Kundeninfo
 
„Leihdauer überschritten“

Auch hier kann offenbar vereinfacht werden:


2. Zweite Optimierung

DVD ausgeben R1 R2 R3 R4 R5/R6/R7/R8 R9

B1 Kunde im Kundenstamm J J J J J N

B2 DVD verfügbar J J J J N –

B3 auszugebende DVDs  5 J J N N – –

B4 Leihdauer 60 Tage J N J N – –

A1 DVD ausgeben 

A2 DVD nicht ausgeben     

A3 Kundeninfo 

„DVD nicht verfügbar“

A4 Kundeninfo 
 
„Anzahl überschritten“

A5 Kundeninfo 
 
„Leihdauer überschritten“

62 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

3. Einfache Entscheidungstabelle nach der Optimierung

DVD ausgeben R1 R2 R3 R4 R5 R6

B1 Kunde im Kundenstamm J J J J J N

B2 DVD verfügbar J J J J N –

B3 auszugebende DVDs  5 J J N N – –

B4 Leihdauer  60 Tage J N J N – –

A1 DVD ausgeben 

A2 DVD nicht ausgeben     

A3 Kundeninfo 

„DVD nicht verfügbar“

A4 Kundeninfo 
 
„Anzahl überschritten“

A5 Kundeninfo 
 
„Leihdauer überschritten“

4.2.2.2 Data Dictionary


Unter einem Data Dictionary versteht man die Auflistung von Datenkomponenten und
ihren Beziehungen in einem Informationssystem. Hier werden sowohl elementare als
auch zusammengesetzte Komponenten erfasst. Die Beschreibung der Daten wird häufig
mit folgender Syntax durchgeführt:
= Komponente wird definiert als
+ Verknüpfung von Komponenten (Sequenz)
(...) wahlfreie Komponente
{...} Wiederholung von Komponenten (Iteration), wobei die Zahl vor den Klammern
die Anzahl der Wiederholungen und die Zahl hinter den Klammern die maximale
Anzahl an Wiederholungen bedeutet und in der Klammer die zu wiederholende
Komponente steht
[...] Auswahl einer Komponente
| Trennung alternativer Komponenten
@ Schlüsselfeld
*...* Kommentare

SEI11 63
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Beispiel:

name = anrede + vorname + (mittelname) + familienname

name ist eine Komponente, mittelname ist wahlfrei. Die rechts stehenden Komponenten
müssen aber auch definiert werden. Dies kann z. B. geschehen durch:

anrede = [Herr | Frau | Prof. | Dr.]


vorname = …

familienname = 3{Alphazeichen}32

Der Familienname muss also mindestens drei Zeichen und darf höchstens 32 Zeichen
lang sein.

4.2.2.3 Entity Relationship Diagram


Chen entwickelte 1976 das sogenannte Entity Relationship Model (ERM), dessen grafi-
sche Darstellung dann Entity Relationship Diagram (ERD) heißt. Es findet praktische
Anwendung hauptsächlich beim Entwurf von relationalen Datenbanken. Dieses Modell
hat u. a. den Vorteil, dass es sowohl in der Analysephase für das Pflichtenheft sowie in
verfeinerter Form im DV-Entwurf benutzt werden kann. Obwohl die Komponenten des
ERM wie z. B. der Begriff einer Entität, einer Beziehung etc. erst im nächsten Studienheft
exakt definiert werden, soll hier so weit über den Aufbau schon gesprochen werden,
dass wenigstens die grobe Version des ERD, wie sie in Pflichtenheften zur semantischen
Datenmodellierung häufig vorkommt, benutzt werden kann. Dazu betrachten wir fol-
gende Komponenten:
Entity = Objekt der Realität oder Anschauung
Relationship = Beziehung zwischen Entities, z. B. „… ist Mitarbeiter von …“
In relationalen Datenbanken werden Entitäten und Beziehungen in der Regel durch Ta-
bellen dargestellt, wobei die Namen der Spaltenüberschriften auch Attribute genannt
werden. Solche Attribute besitzen einen Wertebereich, engl. Domain, wie z. B. integer
für ganze Zahlen oder Character für Buchstaben. Entities und Relationships fasst man
bei Bedarf auch zu Mengen zusammen:
Entity set = Menge von Objekten, z. B. PERSON, PROJEKT etc.
Relationship set = Menge von Beziehungen
Bei Beziehungen spielt noch der sogenannte Assoziationstyp eine wichtige Rolle. Er
gibt an, in welchem numerischen Verhältnis die Einträge der beteiligten Entitäten ste-
hen. Die wichtigsten Assoziationstypen sind 1 : n (sprich „1 zu n“) und n : m. Beispiels-
weise hat 1 Unternehmen n Abteilungen, während in n Projekten m Mitarbeiter beschäf-
tigt sein können (siehe Abb. 4.7).

64 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Für die grafische Darstellung sind die wichtigsten Symbole:

Entity (Entität)

Relationship (Relation, Beziehung)

Attribut

Verbindungslinie

Nachfolgend sei ein Beispiel angegeben, wie es typischerweise in einem Pflichtenheft


vorkommen könnte.

1 n
Unternehmen hat Abteilung

umfasst

Mitarbeiter
m

betreut

Projekt

Abb. 4.7: Beispiel für ein Entity Relationship Diagram

Die n : m-Beziehung in Abb. 4.7 bedeutet konkret, dass an einem Projekt m Mitarbeiter
beteiligt sein können und dass jeder Mitarbeiter an n Projekten beteiligt sein kann. Zu
betonen wäre noch, dass für die Beschriftungen der Entitäten und Beziehungen der Sin-
gular verwendet wird, während in Datenflussdiagrammen üblicherweise der Plural be-
nutzt wird.

SEI11 65
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

4.2.2.4 Objektorientierte Analyse und Design

Objekte, Klassen und Instanzen


Es setzen sich objektorientierte Ansätze gegenüber relationalen immer mehr durch. Dies
liegt u. a. daran, dass eine objektorientierte Systemanalyse (OOA) eine bessere Abbil-
dung der Realität ermöglicht, denn dort hat man es ja gerade mit Objekten zu tun. Eine
präzise Definition des Objektbegriffs ist dabei sehr schwer. Zunächst definieren wir den
Objektbegriff ganz allgemein:

Objekt:
Unter einem Objekt versteht man eine Person oder einen Gegenstand (der Realität
oder Anschauung), auf die/den das Denken und Handeln bzw. jemandes Interesse ge-
richtet ist.

Diese Definition ist noch unkonkret, doch man versteht, dass es sich bei einem Objekt
um eine „Sache“ handelt, die wahrgenommen und hinlänglich beschrieben und über die
„nachgedacht“ werden kann. Im Software Engineering interessiert in erster Linie, wie
Objekte aus dem Leben auf den Computer abgebildet werden können. Natürlich gibt es
dabei auch Objekte, die ausschließlich auf einem Computer existieren, man denke z. B.
an eine Schaltfläche oder den Mauszeiger etc.; auch das sind Objekte, denn sie besitzen
unbestreitbar zumindest eine virtuelle Realität und man kann sein Interesse auf sie rich-
ten. Möchte man Objekte auf einem Computer abbilden, so müssen diese in Form von
Daten abgelegt werden. Und Daten auf dem Computer erfordern ein Datenformat, also
eine Datenstruktur. Die Beschreibung von Datenstrukturen und ihren Beziehungen
nennt man auch Datenmodell. Das ist aber noch nicht alles. Auf Daten muss man zu-
greifen können, d. h., man benötigt Zugriffsoperationen wie Edit, Delete, Add usw.; au-
ßerdem fasst man ähnliche Daten häufig zusammen. Es muss also Kriterien geben, die
die Zugehörigkeit (auch Integrität genannt) festlegen. Solche Zugehörigkeitskriterien
nennt man auch Integritätsbedingungen. Grob kann man also sagen, was für ein Objekt
im datentechnischen Sinn gelten muss:

Objekt = Datenstruktur + Operationen + Integritätsbedingungen


Objektmodell = Datenmodell + Operationen + Integritätsbedingungen

Dadurch, dass also auch Operationen zu einem Objekt gehören, sind die Daten eines Ob-
jekts selbst nur über diese Operationen zugänglich. Man redet in diesem Zusammen-
hang von Datenkapselung. Die Operationen werden dabei als Funktionen implementiert
und dann Methoden genannt (vgl. Abb. 4.8). Ein erstelltes Datenmodell wird manchmal
auch Schema genannt.

66 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Zugriff

Methode 1 Methode 2

Methode 3 ...
Daten

... ...

... Methode n

Abb. 4.8: Datenkapselung in einem Objekt

Der Vorteil solch einer Vorgehensweise liegt auf der Hand: Die Methoden zum Zugreifen
auf die Objektdaten basieren in der Regel auf bereits vorhandenen Standardoperationen,
die z. B. von einem Datenbankmanagementsystem zur Verfügung gestellt werden. Diese
muss man also nicht erst selbst programmieren, was die Fehleranfälligkeit eines Pro-
gramms natürlich deutlich reduziert. Natürlich ist es auch möglich, Methoden selbst zu
schreiben, doch diese benutzen dann meistens wieder bereits vorhandene Standardme-
thoden und komponieren daraus die gewünschte Funktionalität.
Objekte, die in irgendeinem Sinne zusammengehörig sind, werden in sogenannte Klas-
sen zusammengefasst. Es muss also ein Kriterium geben, mit dem die Zusammengehö-
rigkeit von Objekten bestimmbar ist. In der 5. Schulklasse einer Grundschule beispiels-
weise sitzen Kinder (Objekte), denen gemeinsam ist, dass sie alle die 4. Klasse hinter sich
gebracht haben, einer bestimmten Altersgruppe angehören, einen bestimmten (Klassen-
)Lehrer haben, in einem bestimmten (Klassen-)Zimmer sitzen, einen bestimmten Stun-
denplan haben usw.; in der Mathematik kennt man u.a. Restklassen. Darunter versteht
man die Zusammenfassung von denjenigen ganzen Zahlen, die bei Division durch eine
festgelegte Zahl den gleichen Rest besitzen. Betrachtet man z. B. die Menge {..., 7, 11, 15,
19...}, so handelt es sich hierbei um die Klasse der ganzen Zahlen, welche bei Division
durch die Zahl 4 alle den gleichen Rest, nämlich 3, besitzen. Dieser Klassenbegriff führt
zur nächsten Definition.

SEI11 67
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Klasse:
Unter einer Klasse versteht man eine Menge von zusammengehörigen Objekten. Eine
atomare Klasse ist eine Klasse mit nur einfachen (also nicht zusammengesetzten) Ob-
jekten.
Instanz:
Die Objekte einer Klasse werden auch Instanzen oder Exemplare der Klassen genannt.

Instanzen oder Exemplare einer Klasse können prinzipiell selbst wieder Klassen sein. Es
sei bemerkt, dass sich die Menge der Exemplare einer Klasse mit der Zeit verändern
kann. Daher sind eine Klasse und ihre Exemplarmenge i. A. nicht dasselbe.

UML-Diagramme
In einem Pflichtenheft können bereits grafische Elemente zur Datenmodellierung einge-
setzt werden. Ein Beispiel hatten wir schon gesehen, das Entity-Relationship-Diagramm
(ERD).
Seit geraumer Zeit hat sich allerdings die sogenannte Unified Modeling Language
(UML) etabliert. Dabei handelt es sich um Sammlung von Modellierungstechniken, die
sich durch eine Vielzahl von Diagrammtypen auszeichnet. Diese werden in nachfolgen-
den Studienheften ausführlich beschrieben und sind vor allem für den DV-Entwurf ein-
gesetzt. Allerdings setzen sich mehr und mehr auch im Pflichtenheft einige rudimentäre
UML-Ansätze durch. Wichtig ist dabei immer, dass auch der Kunde des Pflichtenhefts
die Ausführungen verstehen kann. Sollte er dazu nicht in der Lage sein, sollte man diese
Techniken nicht einsetzen und stattdessen die Datenstrukturen und Funktionsabläufe
verbal so präzise wie möglich beschreiben. Allerdings sollten auch die Diagramme,
wenn sie denn verwendet werden, noch einmal durch Text erläutert werden.
Klassendiagramme:
Diese Modellierungstechnik wird zur Beschreibung statischer Sachverhalte benutzt (wie
auch schon das ERD). Im Gegensatz zum Standard-ERD sind bei UML-Klassendiagram-
men weitergehende Sachverhalte darstellbar (z. B. Vererbungen, Aggregationen, siehe
nachfolgend).
Wir hatten definiert, dass Klassen eine Zusammenfassung zusammengehöriger Objekte
darstellen8. Die Zusammengehörigkeit wird dabei über Eigenschaften, sogenannte Inte-
gritätsbedingungen, definiert. Eine Schulklasse einer Grundschule besteht beispielswei-
se aus den Objekten „Schüler“, die alle gemeinsame Eigenschaften besitzen: Sie sind
(meistens) ungefähr gleich alt, gehören zum gleichen Einzugsgebiet einer Stadt, haben
bereits die vorhergehende Klasse erfolgreich absolviert (Versetzung) und so weiter. Von
einer gewissen Abstraktionsstufe aus gesehen sind diese „Objekte“ (bezüglich ihrer In-
tegritätsbedingungen) sogar „ununterscheidbar“. Jedes Objekt stellt dabei einen Reprä-
sentanten der ganzen Klasse dar. Objekte werden auch Instanzen oder Exemplare einer
Klasse genannt.

8. Wir folgen hier teilweise Zöller-Greer, 2010b

68 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Klassen werden in UML durch Rechtecke dargestellt, die wieder in 3 Abteilungen unter-
teilt sind: Oben steht der Klassenname, in der Mitte stehen die Attribute und im unteren
Teil die Methoden, mit denen auf die Objekte der Klasse zugegriffen werden kann. Au-
ßer dem Klassennamen sind alle anderen Einträge optional. Die Beziehungen zwischen
Klassen werden durch Verbindungslinien ausgedrückt, die auch beschriftet werden kön-
nen („Rolle“ der Beziehung).
In Abb. 4.9 sehen wir ein Beispiel für ein solches Klassendiagramm und in Abb. 4.10 für
ein Objektdiagramm. Objektdiagramme sehen ähnlich wie Klassendiagramme aus, je-
doch beziehen sich die Beschriftungen innerhalb der Rechtecke und der Verbindungsli-
nien jetzt auf konkrete Objekte der Klassen (je nach Literatur wird manchmal auch im
Objektnamen zuerst das Objekt, dann der Doppelpunkt und dann der Klassenname ge-
nannt).
An den Verbindungslinien werden bei dem Beziehungstyp „Assoziation“ noch die soge-
nannten Kardinalitätsbeschränkungen mit angegeben. Dabei handelt es sich um die Ma-
ximalzahlen, mit denen ein konkretes Objekt einer Klasse mit Objekten einer weiteren
(oder sogar der gleichen) Klasse eine Beziehung (Link) eingehen kann (siehe auch weiter
unten den Begriff der Rolle). Die wichtigsten sind:
* für kein oder viele
1..* für ein oder viele (auch lesbar als: mindestens 1, maximal viele)
0..1 für kein oder ein (auch lesbar als: mindestens 0, maximal 1)
1 für genau ein (diese 1 kann auch ganz weggelassen werden)
2..4 für numerische Angaben, hier: 2, 3 oder 4.

Abb. 4.9: Klassendiagramm mit Assoziation und Kardinalitätsbeschränkungen

Eine Assoziation wird als eine Gruppe von Links definiert, wobei ein Link eine Verbin-
dung zwischen einander zugeordneten Objekten bezeichnet (bei 2 an einer Assoziation
beteiligten Klassen werden dann auf Instanzenebene jeweils immer genau 2 Objekte ei-
nander zugeordnet). Auf Instanzenebene werden daher im Objektdiagramm die Kardi-
nalitäten aller Links immer genau 1 betragen, und diese 1 wird in der Regel in den Dia-
grammen weggelassen.
Man kann sich eine Klasse häufig als eine Tabelle vorstellen; die Attribute der Klassen
stellen dann die Spalten einer solchen Tabelle dar, die Zeilen der Tabelle entsprechen den
Objekten. Die Assoziationen zwischen den Klassen werden in Tabellen in der Regel so
implementiert, dass man die entsprechenden sogenannten Schlüsselattribute einer Ta-
belle (das sind diejenigen Attribute, die einen Datensatz, d. h. also eine Zeile in der Ta-
belle, eindeutig identifizieren und deren Anzahl minimal ist, z. B. einfach eine fortlau-
fende Nummer, „ID“ für „Identität“) als Spalte in der anderen Tabelle einfügt (bei Eins-

SEI11 69
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

zu-viele-Beziehungen) oder die Schlüsselattribute aus beiden zueinander in Beziehung


stehenden Tabellen in eine weitere, neue Tabelle schreibt und die gewünschten Daten-
sätze der beiden Tabellen zueinander in Beziehung setzt. In Abb. 4.11 sind z. B. die At-
tribute P_ID und M_ID fortlaufende Nummern, die jedes Projekt und jeden Mitarbeiter
eindeutig identifizieren. Macht man jetzt aus jeder der Klassen Tabellen, so ist die Klasse
M_P eine „Beziehungstabelle“, wo die „passenden“ P_IDs und M_IDs kombiniert wer-
den.

Abb. 4.10: Objektdiagramm mit Links

Manchmal besitzen Assoziationen eigene Attribute. Diese werden dann Linkattribute


genannt und die dazugehörige Klasse als Assoziationsklasse bezeichnet (vgl. Abb. 4.11).

Abb. 4.11: Klassendiagamm mit Assoziationsklasse

70 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Der Grund dafür ist folgender: Es kann Attribute geben, wie z. B. „Tätigkeit“ in
Abb. 4.11, die weder in die Klasse „Mitarbeiter“ aufgenommen werden können (da die
Tätigkeit eines bestimmten Mitarbeiters in verschiedenen Projekten jeweils eine andere
sein kann) noch der Klasse „Projekt“ zugeordnet werden können (da im gleichen Projekt
verschiedene Mitarbeiter verschiedene Tätigkeiten ausüben können). Deswegen bezieht
sich das Attribut „Tätigkeit“ nur auf den konkreten Link. Die Klasse um diese Link-At-
tribute „herum“ ist dann die Assoziationsklasse.
Ich möchte an dieser Stelle einem weitverbreitenden Irrtum entgegentreten, und zwar in
Bezug auf die Beschriftung der Kardinalitäten in Zusammenhang mit den Rollen. Be-
trachten wir zu diesem Zweck folgendes Beispiel: Eine Schallplattenfirma schließt einen
Vertrag mit einem Künstler ab. In der Musikbranche ist es dabei üblich, dass ein Künstler
immer exklusiv an eine (Schall-) Plattenfirma gebunden ist, während eine Plattenfirma
viele Künstler unter Vertrag haben kann. In Abb. 4.12 sehen wir dazu ein mögliches
Klassendiagramm:

Abb. 4.12: Beispiel Plattenfirma, Sicht 1

In dieser Kardinalitätenschreibweise wird folgende Sicht vertreten:

Sicht 1 (Rolle):
Es wird an der Klasse aufgezählt, wie oft ein Exemplar dieser Klasse an einem Link
mit einem Exemplar der gegenüberliegenden Klasse teilnehmen kann.

Nun gibt es aber verwirrenderweise eine zweite Sichtweise, die zwar denselben Sach-
verhalt zum Ausdruck bringt, aber eine andere Sicht auf die Assoziation darstellt
(Abb. 4.13):

Abb. 4.13: Beispiel Plattenfirma, Sicht 2

Wie man sieht, sind jetzt die Kardinalitätsbeschränkungen vertauscht. In dieser Sicht-
weise gilt:

SEI11 71
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Sicht 2 (inverse Rolle):


Es wird an der gegenüberliegenden Klasse aufgezählt, wie viele Links ein Exemplar
der aktuellen Klasse mit Exemplaren der gegenüberliegenden Klasse eingehen kann.

Die Sicht der inversen Rolle hat sich interessanterweise mehr verbreitet als die der Rolle.
Um Mehrdeutigkeiten zu vermeiden, kann es nicht schaden, wenn man bei der Erstel-
lung von Klassendiagrammen z. B. mithilfe einer Legende hinschreibt, welche Sicht man
gerade einnimmt.
Während Assoziationen „hat“-Beziehungen zwischen Klassen realisieren, stellen die so-
genannten Spezialisierungen (je nach Sichtweise auch Generalisierungen oder Verer-
bungen genannt) Variationen einer Klasse im Sinne einer „ist-ein“-Beziehung dar.
Abb. 4.14 liefert dazu ein Beispiel: Es gibt eine Klasse „Person“ mit den beiden Variati-
onen „Kunde“ und „Lieferant“. Die Verbindungslinien besitzen einen holen Pfeil am
Ende der Basisklasse (auch Oberklasse genannt); die Variationen werden auch abgelei-
tete Klassen, Subklassen oder Unterklassen genannt. Letztere ererben von der Basisklas-
se eventuelle Attribute, Methoden und Assoziationen. Optional kann in der Nähe des
Pfeils noch in geschweiften Klammern ein Eintrag vorgenommen werden, der Folgendes
bedeutet:
complete heißt, die Basisklasse enthält keine eigenen Instanzen
incomplete heißt, die Basisklasse kann eigene Instanzen haben
overlapping heißt, gleiche Objekte können gleichzeitig in mehreren Unterklassen
vorkommen
disjoint heißt, ein Objekt einer Subklasse kann nicht noch in einer weiteren
Subklasse vorkommen

Abb. 4.14: Generalisierung (Spezialisierung, Vererbung)

72 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Konkret ist es also so, dass man Personen hat, die Kunde, Lieferant, keines von beiden
oder beides sein können. Hätte man statt {incomplete, overlapping} die Vererbungstypen
{complete, overlapping}, so würde das bedeuten, dass jede Person Kunde oder Lieferant
(oder beides) ist, d. h., es gibt keine Person, die nicht mindestens eines von beidem ist. In
diesem Fall nennt man die Basisklasse auch „abstrakt“, weil sie ja keine eigenen Instan-
zen besitzt (diese finden sich erst in den Subklassen). Würde im hinteren Teil statt „over-
lapping“ jetzt „disjoint“ stehen, so würde das bedeuten, dass ein Kunde nicht gleichzeitig
Lieferant sein kann und umgekehrt.
Noch ein Wort zu den drei „fast“ synonymen Begriffen Spezialisierung, Generalisierung
und Vererbung. Das Ergebnis ist in allen drei Fällen das gleiche, z. B. das Diagramm aus
Abb. 4.14. Der Unterschied besteht nur in der Sichtweise bzw. in der praktischen Vorge-
hensweise, also wie man zu dem Diagramm kommt. Beginnt man seine Systemanalyse
damit, dass man zuerst die Klasse Person modelliert und danach feststellt, dass es davon
zwei Variationen, nämlich Mitarbeiter und Lieferanten, gibt, so „spezialisiert“ man diese
Klasse in die beiden genannten Subklassen. Fängt man dagegen so an, dass man zuerst
die Klassen Mitarbeiter und Lieferant erstellt und dann feststellt, dass diese gleichlau-
tende Attribute haben (wie z. B. Name, Ort etc.), dann „zieht“ man diese gemeinsamen
Attribute „nach oben“ in eine eigens dafür neu einzuführende Klasse, die man dann z. B.
Person nennt. Dieser Vorgang wäre die Generalisierung. Betrachtet man jetzt aber, was
welche Klasse anderen Klassen zur Verfügung stellt („vererbt“), wie z. B. die Attribute
aus der Basisklasse den Subklassen, so nennt man den Mechanismus des „Zurverfügung-
stellens“ eine „Vererbung“. Vererbt werden können neben den Attributen auch die Me-
thoden und evtl. Assoziationen an die Basisklasse.
Im Beispiel aus Abb. 4.14 könnte die Klasse Person z. B. die Attribute Name, PLZ, Ort
haben. Diese werden dann an die Subklassen vererbt und werden daher dort nicht noch
einmal genannt (obwohl es sie dort gibt!). In den Subklassen werden nur noch diejenigen
Attribute genannt, die der jeweiligen Subklasse allein zu eigen sind.
Man muss aber aufpassen, wenn man aus solchen Gebilden wie in Abb. 4.14 Tabellen
machen will. Dann macht man zwar – wie üblich – auch aus jeder Klasse zunächst eine
Tabelle, doch die Vererbung muss zuvor in eine Assoziation „umgewandelt“ werden,
was möglich ist (allerdings unter Verlust der Vererbungstypisierung wie complete und
overlapping etc.). Um Vererbungen als Assoziationen darzustellen, muss man zwischen
der Basistabelle und jeder Subtabelle eine 1-zu-0..1-Assoziation herstellen. „Philoso-
phisch“ besteht aber dann ein deutlicher Unterschied: Während z. B. eine Instanz von
„Person“ mit der Variation „Kunde“ ein einziges Objekt liefert, entstehen bei der 1-zu-
0..1-Assoziation 2 Objekte (die über einen Link in Beziehung zueinander stehen). In Ta-
bellenform gibt es dann also keine abstrakten Klassen mehr, denn die Tabelle „Person“
hat dann natürlich konkrete Einträge in ihren Zeilen stehen, auf die in den beiden Sub-
tabellen Mitarbeiter und Lieferant passend referenziert wird.
Im Rahmen der Klassendiagramme spielt noch ein Spezialfall der Assoziation eine große
Rolle: die Aggregation. Aggregationen sind Assoziationen, von denen man weiß, dass
sie zusätzlich eine Gesamtheits-Teil-Beziehung darstellen (vgl. Abb. 4.15).

SEI11 73
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Abb. 4.15: Aggregationen

Aggregationen besitzen wieder selbst einen Spezialfall: sogenannte Kompositionen. Dies


sind Aggregationen, die sich auf einen „lebensnotwendigen“ Teil der Gesamtheit bezie-
hen, der also nicht weggelassen werden kann. Bei Kompositionen wird die kleine Raute
dann schwarz ausgefüllt.
Zustandsdiagramme:
Zustandsdiagramme dienen zur Beschreibung dynamischer Sachverhalte. Dazu ist es
häufig hilfreich, wenn der Ablauf der zu automatisierenden Arbeitsgänge durch gewisse
Szenarien ausgedrückt wird. Dadurch werden die zeitlichen Abläufe und Reihenfolgen
gewisser Aktionen deutlich. Dies ist insbesondere manchmal wichtig für gewisse Ab-
hängigkeiten der Daten untereinander. So können z. B. manche Berechnungen erst dann
ausgeführt werden, wenn zeitlich vorher bestimmte Daten eingegeben wurden.
Die Beschreibung solcher Zeitabhängigkeiten kann – wie erwähnt – durch gewöhnliche
verbale Beschreibung der Abfolgen geschehen und/oder durch standardisierte Zu-
standsdiagramme. Letztere beschreiben die zeitliche Abfolge wichtiger Änderungen am
Zustand des Systems. Jede Eingabe, Ausgabe oder sonstige Aktion der Software, wie z. B.
eine Berechnung, verändert den aktuellen Zustand der Software bzw. der Hardware
(z. B. des Bildschirms oder des Druckers oder der Festplatte). Ein Beispiel für eine verbale
Szenenbeschreibung in Verbindung mit einem entsprechenden U-Case-Diagramm fin-
det man in der Ist-/Sollzustandsbeschreibung in Abschnitt 4.2.1.
Ein Zustandsdiagramm beschreibt eine hypothetische Maschine, die sich zu jedem Zeit-
punkt in einer Menge endlicher Zustände befindet. Sie besteht aus:
• einem Anfangszustand
• einer endlichen Menge von Zuständen
• einer endlichen Menge von Ereignissen
• einer endlichen Anzahl von Transaktionen, die den Übergang des Objektes vom ei-
nen zum nächsten Zustand beschreiben
• einem oder mehreren Endzuständen

74 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Abb. 4.16: Notation Zustandsdiagramme

In Zustandsdiagrammen werden Zustände als abgerundete Rechtecke, verbunden durch


Pfeile, auf denen die Transaktionen stehen, dargestellt. Anfangszustand ist ein gefüllter
Kreis, der Endzustand ist ein leerer Kreis mit einem kleinen gefüllten Kreis in der Mitte.
Sequenzdiagramme:
Mittels Sequenzdiagrammen beschreibt man die Interaktionen zwischen den Modellele-
menten. Jedoch steht beim Sequenzdiagramm der zeitliche Verlauf des Nachrichtenaus-
tausches im Vordergrund. Die Zeitlinie verläuft senkrecht von oben nach unten, die Ob-
jekte werden durch senkrechte Lebenslinien beschrieben und die gesendeten
Nachrichten waagerecht entsprechend ihrem zeitlichen Auftreten eingetragen.

SEI11 75
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Abb. 4.17: Notation Sequenzdiagramm

Die Objekte werden durch Rechtecke visualisiert. Von ihnen gehen die senkrechten Le-
benslinien aus, dargestellt durch gestrichelte Linien. Die Nachrichten werden durch
waagerechte Pfeile zwischen den Objektlebenslinien beschrieben.
Auf diesen Pfeilen werden die Nachrichtennamen in der Form
nachricht(argumente)
notiert. Nachrichten, die als Antworten deklariert sind, erhalten die Form
antwort: =nachricht(...).
Den Nachrichten können Bedingungen der Form
[bedingung] nachricht(...)
zugewiesen werden.
Iterationen von Nachrichten werden durch ein Sternchen „*“ vor dem Nachrichtenna-
men beschrieben. Objekte, die gerade aktiv an Interaktionen beteiligt sind, werden
durch einen Balken auf ihrer Lebenslinie gekennzeichnet. Objekte können während des
zeitlichen Ablaufs des begrenzten Kontextes erzeugt und gelöscht werden.
Ein Objekt wird erzeugt, indem ein Pfeil mit der Aufschrift neu() auf ein neues Objekt-
symbol trifft.

76 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Abb. 4.18: Beispiel für ein Sequenzdiagramm im CASE-Tool Innovator

Aktivitätsdiagramme:
Aktivitätsdiagramme ähneln den Zustandsdiagrammen. Der Schwerpunkt liegt jedoch
im Verhalten des Systems und beschreibt hauptsächlich die Vernetzung elementarer Ak-
tionen. Dabei sind Kontroll- und Datenflüsse ablesbar. Oft wird damit der Ablauf eines
Anwendungsfalls beschrieben (wie schon bei U-Case-Diagrammen, es können sogar die
Strichmännchen von dort zum Einsatz kommen). Ein Aktivitätsdiagramm kann aber
auch zur Modellierung aller Aktivitäten eines Systems benutzt werden.
Nun gibt es zwei Ausprägungen des Aktivitätsdiagramms: In der aktuellen, neueren
Form von UML (UML 2 genannt) wird die Darstellung sogenannter nebenläufiger Sys-
teme durch die Einbindung von asynchronen Kommunikationsmechanismen (Signal
senden und empfangen, Ausnahmebehandlung) ermöglicht. Ein Aktivitätsdiagramm
spezifiziert also die eigentlichen Aktivitäten. Im Prinzip stellt ein Aktivitätsdiagramm
die objektorientierte Adaption eines Programmablaufplans dar. Ein Beispiel sehen wir
in Abb. 4.19.

SEI11 77
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

Abb. 4.19: Aktivitätsdiagramm (UML 2)

In Abb. 4.19 sehen wir ein einfaches Aktivitätsdiagramm mit einem Kopf- und einem
Inhaltsbereich. Am rechten und linken Rand sieht man zwei Rechtecke. Diese bezeichnet
man als „Aktivitätsparameterknoten“. Die Aktionen sind die Rechtecke mit abgerunde-
ten Ecken. Dort sind kleine Quadrate an den Rändern zu erkennen. Sie beschreiben die
Ein- und Ausgabe-Pins und deuten an, dass hier ein Objektfluss vorliegt. Der Rest stellt
Kontrollflüsse dar. Der schwarze Kreis ist der Startknoten und gehört damit zu den Kon-
trollknoten.
Aus Gründen der Historie sei noch ein Beispiel für die ältere UML-Variante der Aktivi-
tätsdiagramme (UML 1) angegeben (Abb. 4.20).

78 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Planung eines Softwareprojekts 4

Abb. 4.20: Aktivitätsdiagramm (UML 1)

Abgerundete Rechtecke stellen hier allerdings Zustände dar (wie im Zustandsdia-


gramm), während bei UML 2 damit Aktionen gekennzeichnet werden. Die dicken hori-
zontalen Balken werden Parallelisierungs- und Synchronisationsknoten (oder -balken)
genannt und können Transitionen verzweigen oder zusammenführen. Eine Raute wird
ebenfalls dazu benutzt, die Verzweigung einer Transition zu visualisieren; die Verzwei-
gungsbedingungen stehen dann an der jeweiligen Verzweigung.
Ob und welche der genannten Diagrammtypen im Pflichtenheft zum Einsatz kommen,
hängt dann – wie gesagt – von der Art des Projekts ab und davon, ob auch der Kunde
(Auftraggeber) diese Diagramme lesen kann.

SEI11 79
© HfB, 02.12.20, Berk, Eric (904709)

4 Planung eines Softwareprojekts

4.2.3 Projektplan
Schließlich muss im Pflichtenheft noch eine Zeitplanung enthalten sein, die eine realis-
tische Entwicklungszeit wiedergibt. Je nach Komplexität des Projekts kann die Darstel-
lung unterschiedlich sein. Reicht eine tabellenförmige Aufstellung nicht aus, können
auch computergestützte Werkzeuge wie z. B. MS Project® benutzt werden. Ein Projekt-
plan muss auf jeden Fall enthalten, welche Personen zu welchem Zeitpunkt welche Tei-
laufgabe lösen. Dabei sollten Meilensteine eingebaut werden, die durch Reviews beim
Auftraggeber mit demselben „abgenommen“ werden. Durch eine solche Bestätigung des
Erreichens von Teilzielen durch den Auftraggeber erhöht sich die Sicherheit, dass die
Programmentwicklung auf dem richtigen Weg ist.

Abb. 4.21: Beispiel für einen einfachen Projektplan

Aufgaben zur Selbstüberprüfung

4.1 Welches sind die wichtigsten Daten bei der Planung eines Softwareprojekts?
4.2 Welche Punkte des Pflichtenhefts nach DIN 69901 sind Ihrer Meinung nach bei
kleineren Projekten nicht so wichtig?
4.3 Entwerfen Sie ein semantisches Datenmodell für den in Abb. 4.7 gezeigten Sach-
verhalt. Erfinden Sie einige Attribute zu den Entitäten und geben Sie eine verbale
Version des semantischen Datenmodells an.

80 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

A. Lösungen der Aufgaben zur Selbstüberprüfung


1.1 Bei den Ingenieurwissenschaften ist schon immer selbstverständlich, dass zuerst
ein Bauplan von einem Architekten gemacht wird und anschließend, wenn der
Kunde damit zufrieden ist, das Haus gemauert wird. Im SE war das nicht immer
so. Häufig wurde „drauflosgemauert“, also kein ausreichender Bauplan für die zu
entwickelnde Software entworfen. Ziel des SE ist es in erster Linie, das Entwi-
ckeln eines „Softwarebauplans“ zu ermöglichen. Dazu werden ingenieurwissen-
schaftliche Methoden eingesetzt.
1.2 Der „Realzustand“, der vor allem in den 60er-Jahren des 20. Jahrhunderts vor-
herrschte, hatte eine vorausschauende Planung im Hintergrund. Es wurde daher
häufig sehr viel Zeit für das eigentliche Programmieren und wenig für eine Sys-
temanalyse verwendet. So kam es, dass aufwandsmäßig häufig max. 30 % für die
Planung und 70 % für die Programmierung verwendet wurden. Der Gesamtauf-
wand, der als Flächeninhalt unter der Kurve aufgefasst werden kann, war zudem
sehr hoch. Wird ein softwareengineeringtechnisches Vorgehen nach einem Pha-
senmodell durchgeführt, so kann erwartet werden, dass für die Planungs- und
Analysephasen ca. 70 % und für die reine Programmierung nur noch 30 % auf-
gewendet werden müssen. Außerdem ist die Fläche unter der Kurve, also der Ge-
samtaufwand, deutlich niedriger.
2.1 Phasenmodelle dienen der systematischen Planung eines Softwareprojekts. Da-
mit soll erreicht werden, dass der Gesamtaufwand minimal und ökonomisch
wird.
2.2 a) Initialisierungsphase
b) Fachkonzept
c) Fachkonzept
d) Fachkonzept für semantisches Datenmodell, DV-Entwurf für logisches Da-
tenmodell
e) Planung im Fachkonzept, Durchführung in der Testphase
2.3 Vorteil des Spiralmodells ist seine Flexibilität gegenüber Planungsfehlern. Wäh-
rend z. B. im Wasserfallmodell ein Fehler des Fachkonzepts oft erst in der Test-
phase erkannt wird und damit eine Rückkopplung über mehrere Phasen hinweg
erforderlich ist, so ist dies im Spiralmodell relativ unproblematisch, da immer
„stückweise“ analysiert, entworfen, implementiert und getestet wird. Nachteil
des Spiralmodells ist sein offenes Ende, was zu unkontrolliertem Gesamtauf-
wand führen kann.
2.4 Das Diamantmodell dient zur Erstellung von Multimediaprogrammen. Hierfür
ist wegen der verhältnismäßig teuren Hard- und Softwareressourcen eine eigene
Planung erforderlich. Daher wird in drei Dimensionen geplant: über die Perso-
nal-Zeit-Ebene (was der traditionellen Planung entspricht) sowie über die Res-
sourcen-Zeit-Ebene (wo die Einteilung dafür vorgenommen wird, welche Res-
source zu welchem Zeitpunkt wem zur Verfügung steht).

SEI11 81
© HfB, 02.12.20, Berk, Eric (904709)

A Lösungen der Aufgaben zur Selbstüberprüfung

2.5 Da es sich hier um eine „Kreativ-Aufgabe“ handelt, kann hier keine feste Lösung
angegeben werden. Sie können aber gern Ihre Lösung an den zuständigen Tutor
leiten, dieser wird sie dann gern kommentieren. Jede „wohlbegründete“ Lösung
ist richtig!
3.1 Wie schon erwähnt bleibt die Bedienoberfläche letztlich Geschmackssache; al-
lerdings kann man objektiv feststellen, dass im SuperConverter die wichtigsten
Parameter für eine Formatwandlung auf „einen“ Blick sichtbar sind. Im oberen
Teil wählt man Ziel-Container und Ziel-Formate aus einer vorgegebenen Liste
aus und stellt dann die Parameter per Mausklick in den darunterliegenden Ab-
schnitten ein, die je nach Formatauswahl oben bereits die möglichen Einstellun-
gen angepasst anbieten. Dies ist sehr effektiv und vermeidet vor allem, hier Pa-
rameter auf Werte einzustellen, die das Zielformat gar nicht verarbeiten kann.
Insgesamt ist diese Oberfläche allerdings etwas gewöhnungsbedürftig.
Der MediaCoder hingegen kommt fast „klassisch“ daher: Es finden sich zur Aus-
wahl der Formate die üblichen Tabs, wo man neben den Formaten auch die je-
weiligen Parameter angeben kann. Dies geht aber auf Kosten der Übersicht, wes-
wegen ein eigenes kleines Fenster „Properties“ rechts oben eingeblendet wird,
wo ansatzweise die Parameter sichtbar sind. Allerdings kommt es hier oft zu
„Fehlläufen“, denn erst beim Start wird festgestellt, ob die ausgewählten Parame-
ter zu dem gewünschten Zielformat „passen“, was sehr ärgerlich sein kann. Es
erscheint auch nur der Hinweis, dass die Parameter so nicht zusammenpassen,
es wird aber kein Hinweis gegeben, welche Werte die Parameter annehmen dür-
fen. Dieses Problem stellt sich beim SuperConverter nicht, da hier nach Format-
auswahl nur die zulässigen Möglichkeiten eingeblendet werden.
Im Ergebnis sind beide Oberflächen softwareergonomisch, doch liefert der Su-
perConverter – hat man sich einmal an diese Oberfläche gewöhnt – den schnel-
leren und auch bedienerfreundlicheren Überblick
4.1 Es muss klar sein, welche Funktionen ein Programm ausführen soll. Dazu gehört
insbesondere, dass der Realitätsausschnitt, den das Programm später abdecken
soll, z. B. mit einem U-Case-Diagramm beschrieben wird. Die Entitäten in dem
U-Case-Diagramm sollten inhaltlich über die wichtigsten Attribute beschrieben
sein. Weiterhin sollen die Input-/Output-Inhalte klar sein sowie ein grobes Lö-
sungskonzept existieren, das angibt, wie vom Input zum Output gelangt werden
kann. Eine zumindest grobe Personal-Einsatzplanung darf auch nicht fehlen
(wer macht wann was).
4.2 Die Unternehmenscharakteristik spielt i. d. R. keine besondere Rolle. Auf ein
Mengengerüst kann oft auch verzichtet werden, da heute Datenbanksysteme
oft „von Hause aus“ mit Millionen von Datensätzen problemlos umgehen kön-
nen.

82 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

Lösungen der Aufgaben zur Selbstüberprüfung A

4.3 Ein Unternehmen ist gekennzeichnet durch einen Namen und einen Sitz (Straße,
PLZ, Postfach, PLZ-Ort, Straßen-Ort, Telefon, Fax, E-Mail). Es sollen mehrere
Unternehmen erfasst werden können. Jedes Unternehmen hat mehrere Abtei-
lungen. Jede Abteilung hat einen Namen, ein Abteilungskürzel sowie eine Tätig-
keitsbeschreibung. Jede Abteilung umfasst mehrere Mitarbeiter. Ein Mitarbeiter
ist gekennzeichnet durch seinen Namen und eine Personalnummer, sein Ge-
burtsdatum, seinen Wohnort (Straße, PLZ, Ort, Tel.-Nummer, Fax, E-Mail) sowie
das Datum seines Eintritts in die Firma. Ein oder mehrere Mitarbeiter betreuen
ggf. ein oder mehrere Projekte. Jedes Projekt besitzt einen Namen, ein Projekt-
kürzel, eine Projektbeschreibung, ein Startdatum sowie ein geplantes Enddatum.

SEI11 83
© HfB, 02.12.20, Berk, Eric (904709)

B. Literaturverzeichnis

Allgemein:
Greenfield, J. und Short, K. (2006). Software Factories. 
Heidelberg: Redline GmbH.
König, W. u. a. (1999). Taschenbuch der Wirtschaftsinformatik und Wirtschaftsmathe-
matik. Frankfurt am Main: Harri Deutsch.
Nielsen, J. (2000). Erfolg des Einfachen. Markt+Technik.
Rumbaugh, J. u. a. (1991). Object Oriented Modeling and Design. 
Englewood Cliffs, Prentice Hall Inc.
Zöller-Greer, P. (2002). Softwareengineering für Ingenieure und Informatiker. 
Wiesbaden: Vieweg.
Zöller-Greer, P. (2010). Multi Media Systeme. 
Wächtersbach: Composia.
Zöller-Greer, P. (2010). Software Architekturen-Grundlagen und Anwendungen. 
Wächtersbach: Composia.

Zum Testen:
Meyers, G. J. (1979). The Art of Software Testing. 
New York: John Wiley & Sons.

Realzeitsysteme:
Gomaa, H. (1999). Software Design Methods for Concurrent and Real-Time-Systems.
Addison-Wesley.

Software-Ergonomie:
Herczeg, M. (2009). Theorien, Modelle und Kriterien für gebrauchstaugliche interaktive
Computersysteme. 
Oldenbourg, München.

84 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

C. Abbildungsverzeichnis
SEI11
Sachwortverzeichnis

Abb. 1.1 Lebenszyklus einer Softwareentwicklung ................................................ 4


Abb. 1.2 System mit geplanten Reaktionen ............................................................. 5
Abb. 2.1 Wasserfallmodell ......................................................................................... 9
Abb. 2.2 Vereinfachtes Spiralmodell ......................................................................... 19
Abb. 2.3 Spiralmodell nach Boehm .......................................................................... 20
Abb. 2.4 V-Modell ...................................................................................................... 21
Abb. 2.5 V-Modell XT ................................................................................................ 23
Abb. 2.6 Diamantmodell ............................................................................................ 25
Abb. 2.7 Personal-Zeit-Ebene .................................................................................... 26
Abb. 2.8 Ressourcen-Zeit-Ebene ............................................................................... 27
Abb. 2.9 Phasen und Tätigkeiten im RUP ................................................................ 28
Abb. 2.10 Zusammenspiel zwischen Rollen, Aktivitäten und Artefakten .............. 29
Abb. 2.11 Agile Softwareentwicklung ........................................................................ 31
Abb. 3.1 Umfeld Softwareergonomie ....................................................................... 35
Abb. 3.2 Usability Engineering ................................................................................. 36
Abb. 3.3 Ergonomische Eignung von Farbkombinationen ..................................... 39
Abb. 3.4 Erschwerungen ............................................................................................ 40
Abb. 3.5 Einordnung visueller Aspekte der Softwareergonomie .......................... 40
Abb. 3.6 Darstellung im Browser für den Surfer .................................................... 42
Abb. 3.7 Darstellung im Browser für den Webredakteur ....................................... 43
Abb. 3.8 Änderungen durch den Redakteur ............................................................ 44
Abb. 3.9 SuperConverter .......................................................................................... 45
Abb. 3.10 MediaCoder ................................................................................................ 46
Abb. 4.1 U-Case-Diagramm des Istzustandes .......................................................... 51
Abb. 4.2 Musikverlagsvertrag (Ausschnitt) ............................................................. 54
Abb. 4.3 Künstlervertrag (Ausschnitt) ...................................................................... 55
Abb. 4.4 Lizenzvertrag (Auschnitt) ........................................................................... 56
Abb. 4.5 Lizenzabrechnung (Ausschnitt) ................................................................. 57
Abb. 4.6 U-Case-Diagramm Sollzustand (Abdeckungsbereich der Software) ..... 59
Abb. 4.7 Beispiel für ein Entity Relationship Diagram .......................................... 65
Abb. 4.8 Datenkapselung in einem Objekt .............................................................. 67
Abb. 4.9 Klassendiagramm mit Assoziation und Kardinalitätsbeschränkungen . 69
Abb. 4.10 Objektdiagramm mit Links ........................................................................ 70
Abb. 4.11 Klassendiagamm mit Assoziationsklasse .................................................. 70

SEI11 85
© HfB, 02.12.20, Berk, Eric (904709)

C Abbildungsverzeichnis

Abb. 4.12 Beispiel Plattenfirma, Sicht 1 ..................................................................... 71


Abb. 4.13 Beispiel Plattenfirma, Sicht 2 ..................................................................... 71
Abb. 4.14 Generalisierung (Spezialisierung, Vererbung) .......................................... 72
Abb. 4.15 Aggregationen .............................................................................................. 74
Abb. 4.16 Notation Zustandsdiagramme .................................................................... 75
Abb. 4.17 Notation Sequenzdiagramm ....................................................................... 76
Abb. 4.18 Beispiel für ein Sequenzdiagramm im CASE-Tool InnovatorÒ .............. 77
Abb. 4.19 Aktivitätsdiagramm (UML 2) ..................................................................... 78
Abb. 4.20 Aktivitätsdiagramm (UML 1) ..................................................................... 79
Abb. 4.21 Beispiel für einen einfachen Projektplan ................................................... 80

86 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

D. Sachwortverzeichnis
SEI11

A F
Agile Methode ................................. 32 Fachkonzept ............................... 12, 13
Agile Modeling ................................ 30 Feinkonzept ............................... 16, 49
Agile Processing .............................. 30
Agile Programming ......................... 30 G
Agile Softwareentwicklung .............. 30 Grobkonzept .............................. 13, 49
Agiler Prozess .................................. 32
Agiles Prinzip .................................. 31 I
Anforderungsanalyse ....................... 13 Implementierung ............................. 16
Assoziationstyp ............................... 64 Initialisierung .................................... 9
Attribut ...................................... 64, 65 Ist-/Sollzustände .............................. 51
Istzustand ........................................ 51
B
Benutzertest ..................................... 17 M
Beziehung ........................................ 65 Multimediaprojekt ........................... 10
Murphys Gesetz ................................. 4
C
CASE ................................................ 6 P
CMS ................................................ 43 Pflichtenheft ................................ 3, 49
Content-Management-System .......... 43 Problemanalyse ......................... 10, 47
Programmtest .................................. 17
D Projektplan ...................................... 80
Data Dictionary ............................... 63 Prototyp .......................................... 19
Datenmodell, semantisches .............. 13 Prototyping ...................................... 20
Datenmodellierung, semantische . 13, 60
Dialoggestaltung .............................. 37 R
Domain ........................................... 64 Relation ........................................... 65
DV-Entwurf ..................................... 16 Relationship ..................................... 64
DV-Konzept ..................................... 12 Relationship set ................................ 64
RUP ................................................. 27
E
E-Commerce .................................... 10 S
Entität ............................................. 65 Software Engineering ......................... 6
Entity .............................................. 64 Softwareergonomie .......................... 34
Entity Relationship Diagram ............ 64 System mit geplanter Reaktion ........... 5
Entity set ......................................... 64 Systemanalyse ............................. 6, 13
Entwicklungszyklus ........................... 3 Systemanalytiker ............................... 3
Extreme Programming ..................... 30

SEI11 87
© HfB, 02.12.20, Berk, Eric (904709)

D Sachwortverzeichnis

T
Test .................................................. 17

U
U-Case-Diagramm ........................... 51
Unified Process ................................ 27
UP ................................................... 27
Usability Engineer ........................... 35
Use-Case-Diagramm ........................ 51

V
V-Modell XT .................................... 21

W
Wartung .......................................... 18
Wasserfallmodell ......................... 9, 18
Wertebereich ................................... 64

88 SEI11
© HfB, 02.12.20, Berk, Eric (904709)

E. Einsendeaufgabe
Phasenmodelle und Planung von Softwareprojekten Einsendeaufgabencode:
SEI11-XX2-K07

Name: Vorname: Tutor/-in:

Postleitzahl und Ort: Straße: Datum:

Matrikel-Nr.: Studiengangs-Nr.: Note:

Heftkürzel: Druck-Code: Unterschrift:


SEI11 0118K07
Bitte reichen Sie Ihre Lösungen über StudyOnline ein. Falls Sie uns diese per Post senden
wollen, dann fügen Sie bitte die Aufgabenstellung und den Einsendeaufgabencode hinzu.

1. Ein Konzern hat folgende Hierarchie:


Eine Holding wacht über Geschäftsbereiche. Jeder Geschäftsbereich ist in Hauptab-
teilungen und diese sind jeweils in Abteilungen unterteilt. Jede Abteilung beschäf-
tigt Mitarbeiter, im Außendienst wie im Innendienst. Sowohl im Außen- als auch im
Innendienst gibt es freie und feste Mitarbeiter. Innerhalb der Mitarbeiter gibt es Vor-
gesetzte und Untergebene.
Erstellen Sie als Softwarehersteller ein Angebot für die Entwicklung einer Verwal-
tungssoftware für das Personal des Konzerns, die die obigen Hierarchien berücksich-
tigt.
10 Pkt.
2. Erstellen Sie ein Pflichtenheft für eine geplante Softwareentwicklung Ihrer Wahl (er-
finden Sie geeignete Annahmen) inkl. eines semantischen Datenmodells. Berück-
sichtigen Sie dabei DIN 69901 sowie Erkenntnisse der Softwareergonomie. Der Um-
fang des abzugebenden Pflichtenhefts soll 5–10 Seiten betragen.
70 Pkt.
3. Wählen Sie ein Phasenmodell und geben Sie kurz die jeweiligen Inhalte jeder Phase
bezogen auf Ihr Softwareentwicklungsprojekt aus Aufgabe 2 wieder.
20 Pkt.

SEI11 89
© HfB, 02.12.20, Berk, Eric (904709)

Das könnte Ihnen auch gefallen