07.10.2013 Aufrufe

Vorlesungsskript - Hochschule Emden/Leer

Vorlesungsskript - Hochschule Emden/Leer

Vorlesungsskript - Hochschule Emden/Leer

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

Informatik I/II<br />

Studiengänge Photonik, IBS – 1./2. Semester<br />

WS 2010/11, SS 2011 <br />

Inhaltsverzeichnis<br />

Rumpfskript<br />

c○ Prof. Dr. B. Bartning<br />

<strong>Hochschule</strong> <strong>Emden</strong>/<strong>Leer</strong><br />

http://www.bartning.org<br />

Stand: 13. September 2011<br />

0 Vorbemerkungen 4<br />

0.1 Legende, Sprachhinweise, Bildsymbole . . . . . . . . . . . . . . . . . . . . . . 4<br />

0.2 Skripte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4<br />

0.3 Hinweise zum Umgang mit älteren C ++-Compilern . . . . . . . . . . . . . . . 5<br />

I Informatik I (1. Semester) 6<br />

1 Rechnersysteme (Kurzüberblick) 6<br />

1.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

1.1 Grundlegende Begriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

1.2 Rechneraufbau, Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />

1.3 Software, Betriebssysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

1.4 Zahlen, Zeichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />

2 Algorithmenstrukturen, Operatoren<br />

(sprachunabhängige Betrachtung) 17<br />

2.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17<br />

2.1 Einführung in Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17<br />

2.2 Folge (Sequenz) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />

2.3 Auswahl (Selektion) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />

2.4 Wiederholung (Iteration) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />

2.5 Schachtelung der drei Algorithmenstrukturen . . . . . . . . . . . . . . . . . . 28<br />

2.6 Operatoren, logische Verknüpfungen . . . . . . . . . . . . . . . . . . . . . . . 30<br />

3 Erste Schritte mit C ++ 33<br />

3.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33<br />

3.1 Die ersten Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 2<br />

3.2 Einfache Datentypen, Zeichenketten, Operatoren . . . . . . . . . . . . . . . . 34<br />

3.3 Ausdrücke, Seiteneffekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />

4 Algorithmenstrukturen in C ++ 38<br />

4.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

4.1 Folge (Sequenz), Gültigkeitsbereich in Blöcken . . . . . . . . . . . . . . . . . 38<br />

4.2 Boolesche Ausdrücke, Datentyp bool . . . . . . . . . . . . . . . . . . . . . . . 38<br />

4.3 Auswahl (Selektion) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39<br />

4.4 Wiederholung (Iteration) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />

4.5 Die Bedingung in Kontrollstrukturen, Gültigkeitsbereiche bei neueren Compilern<br />

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42<br />

4.6 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43<br />

4.7 Empfehlungen für Namensgebung und Lay-out,<br />

symbolische Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44<br />

5 Ein- und Ausgabe, Typ Array und String, Ergänzungen 47<br />

5.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

5.1 Übersicht Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

5.2 Standardstreams, Fehlerbehandlung . . . . . . . . . . . . . . . . . . . . . . . 50<br />

5.3 Umgang mit Textdateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

5.4 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58<br />

5.5 Strings (C-Strings) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

5.6 Übersicht Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />

5.7 Präprozessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62<br />

6 Konstruktion von Baueinheiten,<br />

Problem der Trennung in Verborgenheit und Öffentlichkeit<br />

(sprachunabhängige Betrachtung) 65<br />

6.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />

6.1 Entwurf von Systemen: Baueinheiten und Geheimnisprinzip . . . . . . . . . . 65<br />

6.2 Prozedurale Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 67<br />

6.3 Speicherklassen, Modulare Programmierung . . . . . . . . . . . . . . . . . . . 71<br />

6.4 Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . . . 73<br />

7 Prozedurale Programmierung 76<br />

7.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76<br />

7.1 Funktionsdefinition und Funktionsaufruf, Gültigkeitsbereich in Blöcken . . . . 76<br />

7.2 Funktionsdeklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80<br />

7.3 Referenztyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81<br />

7.4 Parameterübergabearten Wert und Referenz, konstante Referenz . . . . . . . 82<br />

7.5 Globale und lokale Namen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86<br />

7.6 Überladen von Funktionsnamen, Standardargumente . . . . . . . . . . . . . . 87<br />

7.7 Rekursive Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88<br />

7.8 Leitlinien zur Entwicklung von Funktionen . . . . . . . . . . . . . . . . . . . . 90


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 3<br />

7.9 Agile Methoden in der Softwareentwicklung . . . . . . . . . . . . . . . . . . . 91<br />

II Informatik II (2. Semester) 92<br />

8 Speicherklassen, modulare Programmierung 92<br />

8.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />

8.1 Statische und automatische Speicherklasse . . . . . . . . . . . . . . . . . . . . 92<br />

8.2 Modulare Programmierung: Aufteilung in mehrere Übersetzungseinheiten . . 94<br />

9 Objektorientierte Programmierung:<br />

Kapselung von Daten und Funktionen mit Zugriffskontrolle 102<br />

9.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102<br />

9.1 Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102<br />

9.2 Konstruktor und Destruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104<br />

9.3 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />

10 Einige C ++-Ergänzungen, Zeigertyp, Freispeicher 109<br />

10.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109<br />

10.1 Symbolische Konstanten, Makros und inline-Funktionen . . . . . . . . . . . 109<br />

10.2 Datentyp Zeiger, Typinterpretation, Array und Zeiger . . . . . . . . . . . . . 110<br />

10.3 Freispeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113<br />

11 Objektorientierung:<br />

Ergänzungen, Vererbung, Polymorphie, statische Klassenelemente 116<br />

11.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116<br />

11.1 Klassen und Objekte: Ergänzungen . . . . . . . . . . . . . . . . . . . . . . . . 116<br />

11.2 Überladen von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122<br />

11.3 Einfache Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125<br />

11.4 Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128<br />

11.5 Statische Klassenelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131<br />

12 Operatoren, Typen, Ergänzungen zu Zeiger, Binärdateien 136<br />

12.0 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />

12.1 Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />

12.2 Aufzählungstyp, Typumwandlungen . . . . . . . . . . . . . . . . . . . . . . . 139<br />

12.3 Zeigerarithmetik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141<br />

12.4 Zeiger als Funktionsparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . 142<br />

12.5 Anwendungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144<br />

12.6 Umgang mit Binärdateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148<br />

12.7 Mehrdimensionale Arrays und zugehörige Zeiger,<br />

Kommandozeilenparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155<br />

12.8 Typlose Zeiger, C-Bibliotheksfunktionen . . . . . . . . . . . . . . . . . . . . . 159


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 4<br />

0 Vorbemerkungen<br />

0.1 Legende, Sprachhinweise, Bildsymbole<br />

Erläuterungen<br />

xyz<br />

abc<br />

<br />

terminales xyz“ (unverändert zeichenweise zu übernehmen)<br />

”<br />

metasprachl. Symbol: für abc“ Einsetzen der Definition von abc<br />

”<br />

Definitionszeich. (ggf. auch innerh. einer Alternative) – dagegen terminales Gleichheitsz.: =<br />

|<br />

| metasprachl. Alternativ-Zeichen – dagegen terminaler senkrechter Strich: |<br />

[- ]- metasprachl. Klammer – dagegen terminale eckige Klammern: [ ]<br />

abc0..n null oder mehrere Male abc“, Folge beliebig vieler abc“<br />

” ”<br />

abc1..n ein oder mehrere Male abc“, Folge mit mindestens einem abc“<br />

” ”<br />

abcopt optionales abc“ (gleiche Bedeutung wie: abc0..1)<br />

<br />

”<br />

- abc<br />

<br />

def - gleichwertig zu: [- abc |<br />

| def ]-<br />

Op2 Op2 [NV] Definition nicht vollständig<br />

[NErl] nicht erlaubt (in dieser Vorlesung)<br />

Bezug auf Operator(-Hierarchiestufe)<br />

〈Text〉 Fachausdruck in englisch<br />

10<br />

Bezug auf Syntaxelement<br />

[. . . ] Erläuterung (nicht zur Syntax gehör.)<br />

Sprachhinweise:<br />

(nichts) in C und C ++<br />

C++ nur in C ++<br />

C nur in C<br />

C/C++ in C; auch in C ++ möglich und üblich (meist als Gegensatz zu C++ gebraucht)<br />

C (C++) in C; in C ++ zwar auch möglich, aber nicht empfohlen: dort bessere Konstrukte vorhanden<br />

C++(neu) C ++, neu, ggf. noch nicht in gängigen Compilern implementiert<br />

C++(alt) C ++, alt, ggf. gerade noch in gängigen Compilern implementiert<br />

Bildsymbole:<br />

△! Warnung: Bemerkungen zu Gefahren; aufpassen, bitte ernst nehmen!<br />

Empf Empfehlung: meist Vorschlag, aus mehreren Möglichkeiten eine zu bevorzugen, da andere (ggf. inzwischen)<br />

weniger üblich oder eher zu Fehlern führen können – zumindest beim aktuellen Wissensstand<br />

Anm Anmerkung: zusätzliche Bemerkung zum laufenden Text<br />

↑↑ Hinweis: Erläuterung in größerem Zusammenhang oder zusätzliche Bemerkungen, nicht relevant für die<br />

Klausur zur laufenden Vorlesung<br />

↗ Vorwärtsverweis: Bezug auf spätere Inhalte der laufenden Vorlesung<br />

↙ Wiederholung<br />

NEU Neu – als Gegensatz zur Wiederholung<br />

Zus Zusammenfassung<br />

Übb Überblick<br />

Bsp Beispiel<br />

Bew Beweis<br />

0.2 Skripte<br />

Copyright: Bitte beachten Sie, dass alle Skripte nur für eigene Studien benutzt werden dürfen.<br />

Weitergehende Verbreitung oder Nutzung ist untersagt.<br />

Das zu dieser Vorlesung gehörige Skript ” Sprache C ++“ wird hier in der Form (Cpp/Kap. kapitel.punkt) öfter<br />

zitiert. Es ist in der jeweils neuesten Fassung als PDF-Datei auf dem bekannten Server verfügbar.<br />

Dieses Skript – ohne eigene Zusätze – ist als Hilfsmittel zur Klausur zugelassen, das vorliegende Skript<br />

der Vorlesung natürlich nicht.<br />

Beim Zitieren von Vorlesungspunkten wird die Form (kapitel.punkt) gewählt.<br />

Die <strong>Vorlesungsskript</strong>e sind sog. Durchläufen zugeordnet; ein Student bleibt bei planmäßigem Studium<br />

der Durchlaufkennung treu. Die zum vorliegenden Skript gehörige Durchlaufkennung ist (vgl.<br />

Kopfzeile).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 5<br />

Beispiele:<br />

Durchlauf : Beginn WS 2008/09, 2. Semester SS 2009<br />

Durchlauf : Beginn WS 2009/10, 2. Semester SS 2010<br />

Durchlauf : Beginn WS 2010/11, 2. Semester SS 2011<br />

Ggf. werden auch Bücher zitiert:<br />

K&R2/ Kernighan/Ritchie, The C Programming Language, 2. Auflage, Prentice Hall 1988;<br />

dt.: Programmieren in C, 2. Auflage, Hanser 1990<br />

Str2/ Stroustrup, The C ++ Programming Language, 2. Auflage, Addison-Wesley 1991;<br />

dt.: Die C ++ Programmiersprache, 2. Auflage, Addison-Wesley 1992 u. später<br />

(dt. Auflage: Vorsicht! Viele Fehler!)<br />

Str3/ Stroustrup, The C ++ Programming Language, 3. Auflage, Addison-Wesley 1997;<br />

dt.: Die C ++ Programmiersprache, 3. Auflage, Addison-Wesley 1998<br />

EffCpp/ Meyers, Effective C ++: 50 Specific Ways ..., 2. Auflage, Addison-Wesley 1998;<br />

dt.: Effektiv C ++ programmieren, 3. Auflage, Addison-Wesley 1998<br />

MEffCpp/ Meyers, More Effective C ++: 35 Specific Ways ..., Addison-Wesley 1997;<br />

dt.: Mehr Effektiv C ++ programmieren, Addison-Wesley 1997<br />

D&E/ Stroustrup, Design and Evolution of C ++, AT&T Bell Lab. 1994;<br />

dt.: Design und Entwicklung von C ++, Addison-Wesley 1994<br />

ARM/ Ellis/Stroustrup, The Annotated C ++ Reference, Addison-Wesley 1990<br />

0.3 Hinweise zum Umgang mit älteren C ++-Compilern<br />

Diese Vorlesung und auch die Übungen sind für den Umgang mit neuen Compilern gedacht.<br />

Wenn Ihnen ein älterer C ++-Compiler zur Verfügung steht:<br />

• Der Compiler kennt vielleicht den Datentyp bool nicht; dann bitte folgende Zeilen einfügen:<br />

typedef int bool;<br />

const bool true=1, false=0;<br />

• Bei Definition von Variablen im Initialisierungsteil einer for-Anweisung: ggf. gesamte for-<br />

Anweisung in Block einfügen, s. (4.51c).<br />

• Er kennt ggf. die Include-Zeilen in der angegebenen Form noch nicht. Dann bitte folgende (beispielhaft<br />

aufgezeigten) Ersetzungen vornehmen:<br />

// C++-Include-Dateien<br />

#include <br />

// Statt: #include (d. h. ".h" hinzufügen)<br />

// C-Include-Dateien:<br />

#include <br />

// Statt: #include (d. h. führendes "c" weglassen<br />

// und ".h" hinzufügen)<br />

Zusätzlich muss dann auch die folgende Zeile weggelassen werden:<br />

using namespace std;<br />

Näheres dazu ist in (8.23c) kurz angedeutet.<br />

Sehr wichtig: Sie sollten auf keinen Fall in einem Projekt diese beide Schreibweisen der Include-<br />

Zeilen mischen!


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 6<br />

Teil I<br />

Informatik I (1. Semester)<br />

1 Rechnersysteme (Kurzüberblick)<br />

1.0 Überblick<br />

Dieses Kapitel befasst sich mit den Hardware- und Software-Komponenten eines Rechners<br />

und ihrem Zusammenspiel. Es handelt sich um die kurze Aufzählung der wichtigsten Zusammenhänge.<br />

Der Stoff wird hier kaum erläutert; hier ist nur kurz zusammengestellt, welches<br />

Wissen für den weiteren Verlauf des Kurses erwartet wird. Für Studierende, die noch nicht<br />

sehr viel über Rechner wissen, kann dieser Überblick (selbst zusammen mit den Erläuterungen<br />

in der Vorlesung) zu knapp und daher unverständlich sein; bitte nehmen Sie dann zu<br />

einzelnen Themen einführende Büchern über Informatik in die Hand (Bibliothek!).<br />

1.1 Grundlegende Begriffe<br />

(1.10) Übb Die grundlegenden Begriffe (die immer wieder in der Vorlesung vorkommen werden)<br />

müssen verstanden werden: Hardware, Software, Programm, analog und digital, Zeichen,<br />

Bit, Byte und die Vorsatzzeichen K, M, G, T.<br />

(1.11) Einige Begriffe – teilweise als vorläufige Erklärungen:<br />

(a) Hardware 〈hardware〉: physikalisch-technisch realisierte Bestandteile eines Rechnersystems.<br />

(b) Software 〈software〉: Gesamtheit aller Programme.<br />

Programm 〈program〉: Sammlung von Arbeitsanweisungen zur Lösung einer bestimmten<br />

Aufgabe in einer Form, die ein Rechner direkt oder indirekt versteht – vgl. auch (2.11).<br />

(c) Daten 〈data〉: Informationen (Angaben über Sachverhalte oder Vorgänge) aufgrund bekannter<br />

Abmachungen in maschinell verarbeitbarer Form.<br />

(1.12) Die Darstellung und Übermittlung von Informationen geschieht meist in Form von physikalische<br />

Größen.<br />

Bsp Elektrische Spannung, Weg (räumliche Verschiebung), Helligkeit, Magnetfeld, Temperatur und viel andere.<br />

Nimmt die physikalische Größe hierbei nur gewisse, eindeutig voneinander unterscheidbare<br />

Werte an, spricht man von digitaler Information(sdarstellung). Kann sie – innerhalb eines<br />

bestimmten Bereichs – jeden beliebigen Wert annehmen (kontinuierlich), spricht man von<br />

analoger Information(sdarstellung).<br />

Bsp Analog: Zeigeruhr (ohne Schrittwerk), physikalische Schwingung, kontinuierliche Bewegung.<br />

Digital: Digitaluhr, Schalten ein/aus, Bewegung durch Schrittmotor.<br />

Digitalrechner/Analogrechner: Rechner, der die Daten intern digital/analog verknüpft.<br />

(1.13) Zeichen 〈character〉: Element aus einer endlichen Menge zur Darstellung von Informationen<br />

(für digitale Informationsdarstellung). Zur Codierung siehe (1.45).<br />

In der Datenverarbeitung sind üblich:<br />

• Ziffern (0..9),<br />

• Buchstaben (A..Z, a..z, dazu auch nationale Sonderbuchstaben, dt. z. B. ÄÖÜäöüß),<br />

• Sonderzeichen (z. B. + - . , ; * / @ $ u. a.).<br />

(1.14) Bit (aus ” binary digit“), Binärziffer: kleinste Informationeinheit, kann nur zwei Zustände<br />

annehmen (Interpretation z. B. 0/1, Ja/Nein, Wahr/Falsch, Strom/kein Strom, hell/dunkel).<br />

Byte = 8 Bit (heutzutage). Pro Byte gibt es 2 8 = 256 verschiedene Bitmuster. Heute wird<br />

ein Zeichen (1.13) meist in einem Byte dargestellt, neuere Codes (z. B. Unicode) nehmen 2<br />

Byte.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 7<br />

1 k = 10 3 (z. B. 1 km, 1 kg) – jedoch:<br />

1 K = 2 10 = 1024 1000<br />

Bsp 1 KByte = 1024 Byte<br />

1 KBit = 1024 Bit<br />

Größenordnung: eine Schreibmaschinenseite hat etwa 2000–3000 Zeichen, d. h. 2–3 KByte<br />

1 MByte (Mega) = 2 20 Byte = (1024) 2 Byte = 1 048 576 Byte 1 Million Byte<br />

1 GByte (Giga) = 2 30 Byte = (1024) 3 Byte = 1 073 741 824 Byte 1 Milliarde Byte<br />

1 TByte (Tera) = 2 40 Byte = (1024) 4 Byte = 1 099 511 627 776 Byte 1 Billion Byte<br />

△! In der Physik sind jedoch exakte Zehnerpotenzen gemeint (so wie oben bei 1 k angegeben),<br />

z. B. 1 MHz = 1 000 000 Hz, 1 GHz = 1 Mrd. Hz.<br />

1.2 Rechneraufbau, Hardware<br />

(1.20) Übb Der grundsätzliche Aufbau eines heutigen Rechners (mit sog. von-Neumannscher<br />

Architektur) wird zunächst kurz vorgestellt, und zwar i. w. die Hardware-Seite (1.21). Punkt<br />

(1.22) zählt die wichtigsten Peripheriegeräte auf, (1.23) stellt Begriffe zu Charakterisierung<br />

verschieder Aspekte von Speicher vor.<br />

(1.21)<br />

(a) Prinzipieller Rechneraufbau:<br />

Prozessor:<br />

Rechenwerk,<br />

Steuerwerk,<br />

Register<br />

Zentraleinheit<br />

Hauptspeicher<br />

Peripherie<br />

Rechenwerk 〈ALU ” arithmetic logical unit“〉: arithmetische Operationen, Vergleiche, Adressenberechnungen.<br />

Leitwerk, Steuerwerk 〈CU ” control unit“〉: Steuerung und Überwachung der Befehlsverarbeitung,<br />

Entschlüsselung der gespeicherten Befehle.<br />

Register: wenige, sehr schnelle Zwischenspeicher, Anzahl z. B. 16.<br />

Prozessor: Zusammenfassung von Rechnerwerk, Leitwerk, Register.<br />

Hauptspeicher, Zentralspeicher, Arbeitsspeicher 〈main memory〉, RAM (s. u.): ” Schubladenschrank“<br />

mit nummerierten Fächern ( ” Adressen“). Fassungsvermögen je Adresse heute<br />

meist 1 Byte.<br />

Zentraleinheit 〈CPU ” central processing unit“〉: Zusammenfassung von Prozessor und<br />

Hauptspeicher.<br />

△! Bei Mikroprozessoren andere Begriffsbildung: CPU = Prozessor ohne Hauptspeicher<br />

Peripherie, periphere Geräte, Ein-/Ausgabewerke 〈input/output devices〉: alles außerhalb<br />

der Zentraleinheit.<br />

(b) Größenordnung für Übertragungszeiten:<br />

• Rechenwerk–Register: 1–2 ns,<br />

• Rechenwerk–Hauptspeicher: etwa 10 ns,<br />

• Rechenwerk–Festplatte: etwa 10 ms,<br />

• Rechenwerk–Magnetband: bis sec.<br />

Achtung: Faktor 10 6 zwischen 10 ns und 10 ms! Bei der Festplatte ist jedoch die Übertragung<br />

von Folgebytes wesentlich schneller – im Gegensatz zum Hauptspeicher.<br />

Zur Überbrückung großer Geschwindigkeitsunterschiede werden meist Zwischenspeicher, sog.<br />

” Cachespeicher“, eingesetzt, z. B. Platten-Cache (meist innerhalb des Festplattengeräts),<br />

externer und interner Cache (zwischen Hauptspeicher und Rechenwerk).<br />

(c) Die originale Bedeutung von RAM und ROM ist missverständlich:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 8<br />

(1.22) Peripherie<br />

• RAM (aus ” random access memory“, dt. Speicher mit wahlfreiem Zugriff) hat heute<br />

die Bedeutung Schreib-Lese-Speicher in Form des Hauptspeichers, er hat die Zugriffsart<br />

direkt (synonym: wahlfrei),<br />

• ROM (aus ” read only memory“, dt. Nur-Lese-Speicher) hat die ursprüngliche Bedeutung<br />

Nur-Lese-Speicher behalten (ROM als Gegensatz zum Schreib-Lese-Speicher RAM),<br />

Zugriffsart auch hier normalerweise direkt (wahlfrei).<br />

(a) Externe Speicher, Massenspeicher: alle Speicher außerhalb der Zentraleinheit:<br />

• Magnetbandspeicher, Streamer (Zugriffsart: sequentiell)<br />

• Magnetplattenspeicher, Festplatte;<br />

Organisation: mehrere Plattenoberflächen, Sektor 〈sector〉, Spur 〈track〉, Zylinder<br />

〈cylinder〉 (letzteres: Zusammenfassung aller Spuren derselben Nummer);<br />

Zugriffsart: halbdirekt (Spur/Zylinder direkt, Sektor indirekt)<br />

• Diskette 〈floppy disk〉<br />

• CD ( ” compact disk“)<br />

△! SEHR WICHTIG: Sicherung aller wichtigen Daten i. a. täglich; eine Sicherung, wenn<br />

sie benötigt wird, ist im nachhinein nicht mehr möglich!!<br />

(b) Eingabegeräte: Tastatur, Maus, Klarschriftleser, Balkencodeleser, Prozessfühler (Prozessrechner),<br />

Mikrofon, Tablett, Lichtgriffel, Scanner, CD-Lesegerät;<br />

früher: Lochstreifenleser, Lochkartenleser.<br />

〈Keyboard, mouse, optical character reader, bar code reader, sensor, microphone, (graphics)<br />

tablet, electronic pen, scanner, CD read device; paper tape reader, punched card reader.〉<br />

(c) Ausgabegeräte: Bildschirm, Drucker, Plotter, Lautsprecher, CD-Gerät ( ” Brenner“);<br />

früher: Lochstreifenstanzer, Lochkartenstanzer.<br />

〈Screen, printer, plotter, loud speaker, CD device (burner); paper tape punch, card punch.〉<br />

(1.23) Speicherkategorien<br />

bezogen auf . . . Kategorien z. B.<br />

Zugriff (auf eine spezielle Stelle) wahlfreier Zugriff,<br />

direkter Zugriff<br />

〈random/direct access〉<br />

(Zugriffszeit unabhängig vom<br />

Ort des letzten Zugriffs)<br />

sequentieller Zugriff,<br />

indirekter Zugriff<br />

〈sequential/indirect access〉<br />

(Zugriff nacheinander)<br />

Hauptspeicher<br />

Mischung Festplatte,<br />

(halbdirekter Zugriff) Diskette,<br />

auch CD<br />

Richtung des Informationsflusses nur lesen ROM-chip<br />

CD-ROM<br />

1.3 Software, Betriebssysteme<br />

Streamer (Magnetband)<br />

lesen und schreiben Hauptspeicher,<br />

Festplatte,<br />

Streamer<br />

Mischung WORM, EPROM<br />

(1.30) Übb Dieses Unterkapitel befasst sich überblickartig mit der Software eines Rechnersystems.<br />

Es werden die verschiedenen Sprachgenerationen vorgestellt, danach der Weg vom


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 9<br />

Quelltext zu einem ausführbaren Programm. Dann werden die verschiedenen Arten von<br />

Software eingeordnet, dabei wird auch der Begriff ” Betriebssystem“ definiert wird. In (1.34)<br />

wird ein hierarchisches Dateisystem erläutert (DOS/Windows und andere, i. w. auch Unix);<br />

das Navigieren in einer solchen Hierarchie, das Benennen von Pfaden (absolut und relativ)<br />

gehört zum wichtigen Grundverständnis. Durch Umleitung, Datenübergabe, Filter können<br />

man mit Hilfe des Betriebssystems recht interessante Wirkungen erzielen, häufig einfacher<br />

als über direkte Programmierung.<br />

(1.31) Verschiedene Sprachgenerationen<br />

Anm Die Zählung in Generationen ist heute kaum noch üblich mit Ausnahme der ” 4GL“.<br />

(a) Maschinensprache (Sprache der ersten Generation), kann Prozessor direkt verstehen.<br />

Bsp Ausführbare Programme.<br />

(b) Assembler, maschinenorientierte Sprache (Sprache der zweiten Generation): je Maschinenbefehl<br />

ein Assemblerbefehl, sehr prozessorabhängig, für Menschen besser lesbar als<br />

Maschinensprache.<br />

Übersetzungsprogramm für in dieser Sprache geschriebenen Quellcode: Assembler (doppeldeutiger<br />

Name!), Assemblierer.<br />

(c) Höhere Programmiersprache, Hochsprache, problemorientierte Sprache (Sprache<br />

der dritten Generation): orientiert sich am zu lösenden Problem und weniger an dem ausführenden<br />

Prozessor.<br />

Bsp Fortran, Cobol, Algol, Basic, Pascal, C, C ++.<br />

Übersetzungsprogramm für in dieser Sprache geschriebenen Quellcode: Compiler, Kompilierer,<br />

Übersetzer.<br />

(d) Sprache der vierten Generation ( ” 4GL“): Sprache, bei der der ausführenden Einheit mitgeteilt<br />

wird, was für ein Ergebnis der Benutzer haben möchte, aber nicht, auf welchem Weg<br />

dieses erhalten werden soll.<br />

Bsp Manche Datenbankabfragesprachen.<br />

(1.32) Übergang vom Quelltext zur Programmausführung:<br />

(1.33)<br />

• Schreiben des Quelltextes in einer Quellsprache (Assembler, Hochsprache, 4GL) mit<br />

einem Editorprogramm: Klartext ohne Textformatierungen – außer einem guten Layout<br />

durch Zeilenumbrüche und Einrückungen.<br />

• Übersetzen dieses Quellprogramms mit einem Compiler oder Assembler, Ergebnis:<br />

Objektprogramm (teils in Maschinensprache, dazu Tabellen mit unaufgelösten [d. h.<br />

benötigten] und angeboteten Referenzen).<br />

• Binden des/der Objektprogramms/e mit dem Binder, Linker 〈linker〉 unter Zuhilfenahme<br />

von Bibliotheken, Erfüllen der objektprogramm-übergreifenden Referenzwünsche,<br />

Erstellen eines ausführbaren Programms auf der Platte.<br />

• Laden des ausführbaren Programms in den Hauptpeicher durch den Lader und Anstoß<br />

zur Ausführung.<br />

In einer integrierten Entwicklungsumgebung ( ” IDE“, integrated development environment)<br />

bemerkt man diese einzelnen Schritte kaum.<br />

Ein anderer Ablauf ist möglich in manchen Programmiersprachen durch einen Interpreter:<br />

der Quelltext (Hochsprache) wird durch den Interpreter sofort in Maschinencode übertragen<br />

und zur Ausführung gegeben, dann wird der erzeugte Maschinencode wieder verworfen.<br />

Vorteil: sofortiges Testen aus dem Quellcode heraus. Nachteil: ungünstige Performanz. Entscheidender<br />

Nachteil: verleitet zum Programmieren ohne Nachdenken (Basic!).<br />

(a) Je nach Verwendungszweck kann man Software unterteilen in verschiedene Arten:<br />

• Verarbeitungsprogramme:<br />

◦ Anwendungsprogramme (Gehaltsabrechnung, NC-Programm, Textverarbeitung,<br />

Datenbankprogramm),<br />

◦ Übersetzer,


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 10<br />

◦ Dienstprogramme 〈utilities〉 (Sortierpgramm, Testprogramm, Datenübertragungsprogramm);<br />

• Systemprogramme (= Betriebssystem).<br />

(b) Betriebssystem 〈operating system〉: Gesamtheit aller Systemprogramme; diese Programme<br />

steuern die Abwicklung der Verarbeitungsprogramme, bilden zusammen mit der Hardware<br />

die funktionelle Struktur und Betriebsart des Systems.<br />

(1.34) Hierarchisches Dateisystem (DOS, Windows, i. w. auch Unix)<br />

(a) Datei 〈file〉: Sammlung von zusammengehörigen Informationen.<br />

Verzeichnis 〈directory〉, dt. manchmal auch Ordner 〈folder〉: Behälter für Dateien und<br />

Verzeichnisse.<br />

Ein Verzeichnis kann Dateie(en) und/oder Verzeichnis(se) enthalten, eine Datei enthält weder<br />

eine andere Datei noch ein Verzeichnis.<br />

Grafische Darstellung (UML, s. (2.14b ↑↑)):<br />

Erläuterung:<br />

AbstrakteDatei<br />

❅ ❅<br />

NormaleDatei Verzeichnis<br />

NormaleDatei und Verzeichnis haben viele Gemeinsamkeiten; das Gemeinsame kann man sich in<br />

AbstrakteDatei zusammengefasst denken. Die Art der Beziehung (Pfeil mit hohlem Dreieck) ist eine<br />

Spezialisierung: NormaleDatei und Verzeichnis sind jeweils eine spezielle Art von AbstrakteDatei.<br />

Die Bezeichnung ” abstrakt“ entspricht der Terminologie in der Objektorientierung; dieses bedeutet,<br />

dass AbstrakteDatei selbst real nicht existieren kann, sondern immer nur in der spezialisierten Form<br />

NormaleDatei oder Verzeichnis.<br />

Zusätzlich gibt es eine Ganzes-Teile- oder Enthält-Beziehung (Linie mit Raute) zwischen Verzeichnis<br />

und AbstrakteDatei (diese wiederum als NormaleDatei oder als Verzeichnis); der Stern deutet an,<br />

dass diese Beziehung zu beliebig vielen (0..n) Elementen AbstrakteDatei bestehen kann.<br />

↑↑ Die Art der Darstellung (UML) und die darauf fußende Erläuterung sind für diesen Kurs nicht<br />

relevant, deren Bedeutung (normale Dateien, Verzeichnisse, rekursive Schachtelungsmöglichkeit)<br />

ist jedoch wichtig. In Unix gibt es noch weitere Dateiarten als Spezialisierung, z. B.<br />

die ” spezielle Datei“ (Gerätedatei). Korrekt ist das obige Bild für Unix nur, wenn keine<br />

zusätzlichen Links existieren. Näheres zur Spezialisierungsbeziehung (Objektorientierung:<br />

Vererbung) s. auch (11.31).<br />

(b) Aufbau einer Verzeichnishierarchie ( ” Baum“)<br />

Stammverzeichnis, Wurzelverzeichnis 〈root directory〉: Verzeichnis, welches in keinem anderen<br />

enthalten ist.<br />

*<br />

✁❆<br />

❆✁


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 11<br />

Bsp (Stammverzeichnis)<br />

✘✘✘<br />

❳❳<br />

✘✘<br />

❳❳❳<br />

✘✘✘<br />

❳❳❳<br />

✘✘✘<br />

❳❳❳<br />

✘<br />

✘<br />

❳❳❳<br />

Math<br />

Phys<br />

Inf<br />

<br />

<br />

Sem1 Sem2<br />

Vorl Uebg ZuTun<br />

❅ ❅<br />

✟<br />

✟❳<br />

✟✟<br />

❅<br />

❳❳ ❳❳❳❳<br />

❅<br />

<br />

<br />

Vorl Uebg<br />

Alt Neu Plan<br />

❅ ❅<br />

✟<br />

✟❳<br />

✟✟<br />

❅<br />

❳❳ ❳❳❳❳<br />

❅<br />

<br />

<br />

VorJahr LfdJahr<br />

Sommer Winter Uebg<br />

❅ ❅<br />

✟<br />

✟❳<br />

✟✟<br />

❅<br />

❳❳ ❳❳❳❳<br />

❅<br />

Pfad 〈path〉: Weg von einem Verzeichnis(punkt) zu einem anderen Verzeichnis(punkt) innerhalb<br />

eines Verzeichnisbaumes.<br />

Ein Betriebssystem erlaubt es i. a., ein Verzeichis als Standardverzeichnis (aktuelles Verzeichnis)<br />

〈default directory〉 festzulegen (und auch zu ändern). Daher gibt es zwei Arten von<br />

Pfaden:<br />

• relativer Pfad: Pfad mit dem Standardverzeichnis als Quellpunkt,<br />

• absoluter Pfad: Pfad mit dem Stammverzeichnis als Quellpunkt – unabhängig vom<br />

Standardverzeichnis.<br />

(c) DOS/Windows und Unix<br />

Trennzeichen zwischen Pfadbestandteilen: DOS/Windows ” \“, Unix ” /“.<br />

Unterscheidung der beiden verschiedenen Pfadarten:<br />

• vorangestelltes Trennzeichen bedeutet absoluter Pfad,<br />

• kein Trennzeichen am Anfang bedeutet relativer Pfad.<br />

Angabe des Weges:<br />

• abwärts: Angabe des Verzeichnisnamens,<br />

• aufwärts: ” ..“ (DOS/Windows und Unix).<br />

Zusätzlich gibt es noch die Angabe des Standardverzeichnisses als Punkt ” .“ in DOS/Windows<br />

und Unix.<br />

Bsp Pfadangaben zur Abbildung in (b):<br />

Quellpunkt Zielpunkt Betriebssystem Relativpfad Absolutpfad<br />

Vorl VorJahr Dos/Windows ..\..\..\Inf\VorJahr \Inf\VorJahr<br />

(links unten) Unix ../../../Inf/VorJahr /Inf/VorJahr<br />

Neu Phys Dos/Windows ..\.. \Phys<br />

(Mitte unten) Unix ../.. /Phys<br />

Stammverzeichnis Sem2 Dos/Windows Math\Sem2 \Math\Sem2<br />

Unix Math/Sem2 /Math/Sem2<br />

Inf Stammverzeichnis Dos/Windows .. \<br />

Unix .. /<br />

Angabe einer Datei (in [ ] wahlfreie Angabe):<br />

• Unix: [pfad]dateiname<br />

• DOS/Windows: [laufwerk:][pfad]dateiname<br />

Falls bei nichtleerer Pfadangabe der Pfad nicht mit einem Trennzeichen endet (d. h. wenn er nicht<br />

das Stammverzeichnis als Absolutpfad ist), muss zwischen Pfad und Dateiname ein zusätzliches<br />

Trennzeichen gesetzt werden.<br />

Dateinamenbildung:<br />

• DOS/Windows: Buchstaben unabhängig von Groß-/Kleinschreibung,<br />

• Unix: Groß- und Kleinbuchstaben gelten als unterschiedliche Zeichen.<br />

Eine Dateinamenangabe oder ein Dateiname (i. w. S.) ist i. a. zusammengesetzt aus Dateiname<br />

(i. e. S.) und wahlfreier Erweiterung, getrennt durch einen Punkt. Die Dateinamenerweiterung<br />

sollte die Art der Datei angeben.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 12<br />

Bsp1 .EXE ausführbare Datei, .CPP C ++-Quelltextprogramm, .BAT Stapelverarbeitungsdatei, .OBJ Objektdatei,<br />

.SYS Systemdatei, .INI Initialisierungsdatei, .DOC Dokumentendatei, .TXT Textdatei.<br />

Anm1 In Unix gilt der Punkt als normales Zeichen innerhalb eines Dateinamens, er darf also mehrfach<br />

vorkommen.<br />

Bei manchen Betriebssystembefehlen ist es erlaubt, Globalzeichen 〈wild card characters〉<br />

als Platzhalter zu benutzen (gültig für DOS/Windows und für Unix):<br />

? für genau ein Zeichen,<br />

* für beliebig viele Zeichen (auch null).<br />

In Unix (nicht in DOS/Windows) werden auch Zeichen nach einem * verglichen. In DOS/<br />

Windows gelten Globalzeichen für jeden der beiden Bestandteile Dateinamen und Erweiterung<br />

getrennt, in Unix gilt auch der Punkt als durch Globalzeichen ersetzbares Zeichen.<br />

Anm2 Neuere Windowsversionen können bezüglich Globalzeichen schon das Unix-Verhalten zeigen.<br />

Bsp2 ?t4*<br />

DOS/Windows, möglich: at4 zt489 zt4wert (alle OHNE Dateinamenerweiterung)<br />

DOS/Windows, nicht: at4.txt zt489.exe t4<br />

Unix, möglich: wie DOS/Windows, dazu auch: at4.txt zt489.exe<br />

r*.t*<br />

DOS/Windows (auch Unix), möglich: alle Dateien mit Dateinamenbeginn r und Erweiterungsbeginn t<br />

Alle Dateien des aktuellen Verzeichnisses in DOS/Windows: *.*<br />

Das gleiche in Unix: * (Bedeutung in DOS: nur alle Dateien ohne Dateinamenerweiterung)<br />

(1.35) Umleitung, Datenübergabe, Filter 〈indirection, piping, filter〉<br />

(a) In Betriebssystemen ist i. a. eine Standardeingabe (ein Standardeingabegerät) und eine<br />

Standardausgabe (ein Standardausgabegerät) definiert. Meist gilt als Standardeingabe die<br />

Tastatur, als Standardausgabe der Bildschirm.<br />

Programme kommunizieren häufig mit dem Betriebssystem, indem sie Zeichen aus der Standardeingabe<br />

von ihm anfordern oder ihm Zeichen für die Standardausgabe übergeben. Was<br />

nun die Standardeingabe oder -ausgabe tatsächlich ist, legt nicht das Verarbeitungsprogramm<br />

fest, sondern das Betriebssystem.<br />

(b) In DOS/Windows und in Unix kann die Zuordnung auf der Befehlszeilenebene geändert<br />

werden (Umleitung 〈redirection〉):<br />

befehl >datausopt >“, es<br />

bedeutet bei Dateien, dass der bisherige Inhalt nicht gelöscht wird, sondern Neues angehängt<br />

wird.<br />

(c) Datenübergabe 〈piping〉:<br />

Eine Verkettung von Befehlen auf der Befehlszeilenebene ist möglich (DOS/Windows und<br />

Unix), wobei die Standardausgabe des ersten Befehls die Standardeingabe des nächsten<br />

Befehls wird. Das Verkettungssysmbol ist ” |“, z. B.:<br />

befehl1 | befehl2<br />

In DOS/Windows ist eine solche Verkettung selten, in Unix wird sie sehr häufig angewendet.<br />

(d) Ein Filter 〈filter〉 ist ein Programm, das aus der Standarddateneingabe eine (veränderte)<br />

Standarddatenausgabe erzeugt. Es wird viel in Verkettungen eingesetzt.<br />

Bsp Sortierprogramm sort, seitenweise Ausgabe auf dem Bildschirm more.<br />

(1.36) Stapelverarbeitungdatei 〈batch file〉, Unix: Skriptdatei 〈script file〉: eine Textdatei, deren<br />

einzelne Zeilen aus Betriebssystembefehlen bestehen. Diese Datei kann zur Ausführung<br />

gelangen; die zugehörigen Befehle werden so abgearbeitet, als wenn sie direkt auf der Befehlszeilenebene<br />

eingegeben würden.<br />

1.4 Zahlen, Zeichen<br />

(1.40) Übb Um manche Wirkungen von Programmen richtig zu verstehen und ggf. auch zu<br />

steuern, ist es nötig, die Darstellung von Zahlen und Zeichen zu kennen. Das polyadische<br />

Zahlensystem ist Ihnen in Form des Zehnersystems bekannt:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 13<br />

(1.41)<br />

Bsp 7305 = 7 · 10 3 + 3 · 10 2 + 0 · 10 1 + 5 · 10 0<br />

Hier wird dieses System auf andere Basen ausgedehnt; insbesondere die Basis 2 werden Sie<br />

immer wieder antreffen, aber auch die Basis 16, seltener die Basis 8.<br />

Um die überdeckbaren Zahlenbereiche bei vorzeichenbehafteten Typen verstehen zu können,<br />

wird in (1.42) die Darstellung negativer Ganzzahlen erläutert. Hierbei ist die Zweierkomplementdarstellung<br />

sehr wichtig, zumal sehr viele Prozessoren und C ++-Compiler diese Darstellung<br />

benutzen.<br />

Bei der Darstellung der sog. ” reellen Zahlen“ (1.43) muss die Festkomma- und die Gleitkommadarstellung<br />

unterschieden werden, und zwar einmal als rechnerinterne Darstellung,<br />

zum andern aber auch – unabhängig von dieser internen Repräsentation – als externe Darstellung<br />

für Eingabe und Ausgabe.<br />

Problematisch kann eine Fehlersuche in einem Programm werden, wenn zur Laufzeit Überläufe<br />

oder Unterläufe (beide: Überschreiten des darstellbaren Zahlenbereichs) geschehen,<br />

weil dadurch meist unentdeckt völlig falsche Zahlen entstehen (1.44).<br />

Ein sehr kurzer Überblick über verschiedene Zeichencodes (1.45) schließt das Unterkapitel<br />

ab. Hierbei sollten Sie den ASCII-Aufbau im Prinzip verstehen; zum andern sollten Sie sich<br />

merken, an dieser Stelle verschiedene Codierungen für die deutschen Sonderzeichen finden<br />

zu können.<br />

(a) Polyadisches Zahlensystem zur Basis B (B ∈ N {1}):<br />

Zahlensystem mit den insgesamt B Symbolen ( ” Ziffern“) der Wertigkeiten 0, 1, . . . , B − 1<br />

zur Darstellung nichtnegativer Ganzzahlen in der Form:<br />

bnB n + bn−1B n−1 + · · · + b1B 1 + b0B 0<br />

mit bi ∈ {0, 1, · · · B − 1} Ziffern.<br />

Dieser Ausdruck in der Stellenschreibweise (bei bekannter Basis B):<br />

b bn−1 · · · b1 b0<br />

Auch gebrochene Zahlen lassen sich so formulieren:<br />

· · · + b0B 0 + b−1B −1 + b−2B −2 + · · ·<br />

· · · b0 , b−1 b−2 · · · (mit dem Komma als Dezimalkennzeichen)<br />

Gebräuchlich: B = 10 Dezimalsystem<br />

B = 2 Dualsystem; Ziffern: 0 und 1 (sehr häufig bei Rechnern)<br />

B = 16 Hexadezimalsystem; Ziffern: 0. . . 9, dazu A. . . F bzw. a. . . f<br />

B = 8 Oktalsystem; Ziffern: 0. . . 7<br />

Wichtig für diese Vorlesung: neben Dezimalsystem das Dual- und das Hexadezimalsystem.<br />

Mit n Stellen sind insgesamt B n verschiedene Zahlen darstellbar.<br />

Bsp B = 2, n = 8: 256 verschiedene Bitmuster je Byte.<br />

(b) Ein immer anwendbarer Algorithmus zur Umrechnung von einem beliebigen polyadischen<br />

Zahlensystem in ein anderes: fortwährende ganzzahlige Division der Zahl durch die neue<br />

Basis (diese Division kann in einem beliebigen polyadischen Zahlensystem geschehen); die<br />

zugehörigen Teilerreste ergeben – in umgekehrter Reihenfolge – die umgerechnete Zahl in<br />

Stellenschreibweise.<br />

Bsp 14010 = 120123 (die tiefgestellte Zahl gebe die zugehörige Basis an); hier die zugehörige Berechnung:<br />

140:3 = 46 + 2:3<br />

46:3 = 15 + 1:3<br />

15:3 = 5 + 0:3<br />

5:3 = 1 + 2:3<br />

1:3 = 0 + 1:3 (Aufhören bei Divisionsergebnis 0)<br />

Die Umrechnung in unser Zehnersystem kann auch so vorgenommen werden: Multiplikation<br />

jeder Ziffer mit der zugehörigen Wertigkeit und Summation. (Dieses kann sinngemäß auch<br />

in einem anderen als dem Zehnersystem geschehen.)<br />

Bsp 120123 = 1 · 3 4 + 2 · 3 3 + 0 · 3 2 + 1 · 3 1 + 2 · 3 0 = 140<br />

Umwandlung von Zahlen zur Basis 2 in Zahlen zur Basis 8 bzw. 16 (entspr. auch umgekehrt):<br />

Zusammenfassung – von hinten her – von jeweils 3 bzw. 4 Bit zu einer Ziffer.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 14<br />

Bsp 14010 = 100011002 = 8C16 (aus 1000 1100) = 2148 (aus 10 001 100)<br />

(c) Für Spezialfälle gibt es andere Codes:<br />

• ” BCD“ 〈binary coded decimal〉: jede Dezimalziffer wird binär kodiert. Man benötigt<br />

je Ziffer 4 Bits (eine Tetrade). Es gibt sechs unerlaubte Bitmuster (Pseudotetraden):<br />

schwieriges Rechnen, manchmal aber nötig, da hierbei Rundungen wie im Zehnersystem.<br />

Anwendung: kaufmännisches Rechnen.<br />

• Beim Abtasten von Weg- oder Winkelunterschieden ist das Dualsystem unbrauchbar:<br />

bei etwas schiefliegendem Abtaster oder unscharfen Bitübergängen gibt es zwischen<br />

den Zahlen sehr stark abweichende Fehlerwerte. Abhilfe: Codes, bei denen sich beim<br />

Übergang zwischen aufeinanderfolgenden Zahlen nur ein Bit ändert. Beispiel: Gray-<br />

Code.<br />

(1.42) Negative Ganzzahlen<br />

(a) Negative Ganzzahlen lassen sich auf sehr unterschiedliche Weise darstellen:<br />

• Zusätzliches Vorzeichenbit:<br />

unsere gewohnte Schreibweise im Zehnersystem; für Rechner allgemein ungünstig, jedoch<br />

bei Gleitkommazahlen für die Mantisse benutzt (1.43b).<br />

• (B − 1)-Komplement, bei B = 2: Einerkomplement 〈one’s complement〉<br />

Die betragsgleiche Zahl mit umgekehrten Vorzeichen erhält man durch Ergänzen jeder<br />

Ziffer auf B − 1, d. h. auf die höchste mögliche Ziffer – oder durch Subtraktion der Zahl<br />

von B s − 1 bei s Anzahl der mitgeführten Ziffern.<br />

Bei B = 2: Invertieren jeder Ziffer (jedes Bits); oberstes Bit ( ” MSB“, most significant<br />

bit) 0 bedeutet positive Zahl (oder Null), oberstes Bit 1 bedeutet negative Zahl (oder<br />

Null).<br />

Ungünstiges Rechnen (früher manche Rechner); Kuriosität: es gibt zwei Nullen ( ” positive<br />

Null“ und ” negative Null“).<br />

• B-Komplement, bei B = 2: Zweierkomplement 〈two’s complement〉<br />

Die betragsgleiche Zahl mit umgekehrten Vorzeichen erhält man durch durch Subtraktion<br />

der Zahl von B s bei s Anzahl der mitgeführten Ziffern – oder durch Bildung des<br />

(B − 1)-Komplements und anschließende Addition von 1.<br />

Bei B = 2: Invertieren jeder Ziffer (jedes Bits), dann Addition 1; oberstes Bit 0 bedeutet<br />

positive Zahl (oder Null), oberstes Bit 1 bedeutet negative Zahl. Weitere Eigenschaften<br />

und Beispiele s. (b)<br />

Heute sehr häufig anzutreffen bei Rechnern.<br />

(b) Zweierkomplement (zu Zahlensystem der Basis 2):<br />

Häufig benutzte Akronyme:<br />

• MSB 〈most significant bit〉: werthöchstes Bit,<br />

• umgekehrt: LSB 〈least significant bit〉 wertniedrigstes Bit.<br />

Umrechnungsschema, vgl. (a):<br />

Nichtnegative Zahl<br />

(betragsgleich)<br />

– MSB 0 –<br />

✛<br />

✲<br />

Einerkomplement<br />

(Inversion jedes Bits),<br />

danach Addition +1<br />

✛<br />

✲<br />

Negative Zahl<br />

(betragsgleich)<br />

im Zweierkomplement<br />

– MSB 1 –<br />

Der Zahlenbereich bei s Ziffern: −2 s−1 . . . 0 . . . (2 s−1 − 1). Es gibt gleich viele nichtnegative<br />

Zahlen (positive und Null) wie negative Zahlen.<br />

Beispiele für positive und negative Zahlen im Zweierkomplement, mit größter positiver und<br />

kleinster negativer Zahl (s sei Anzahl der Ziffern):


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 15<br />

Zifferndarstellung Wert Bsp s = 8 (1 Byte) Wert<br />

01. . . 11 2 s−1 − 1 01111111 127<br />

01111110 126<br />

00000100 4<br />

00000010 2<br />

00. . . 01 1 00000001 1<br />

00. . . 00 0 00000000 0<br />

11. . . 11 -1 11111111 -1<br />

11. . . 10 -2 11111110 -2<br />

10000001 -127<br />

10. . . 00 −2 s−1 10000000 -128<br />

(1.43)<br />

” Reelle“ Zahlen<br />

(a) Festkommadarstellung 〈fixed point notation〉: eine Zahlendarstellung, bei der jede Ziffer<br />

allein aufgrund ihrer Stellung eine fest vorgegebener Wertigkeit besitzt, d. h. das Dezimalkomma<br />

steht fest an vorgegebener bzw. vereinbarter Stelle.<br />

Bsp Technisch-wissenschaftliche Rechnungen: Darstellung von Ganzzahlen; das (gedachte) Dezimalkomma<br />

liegt hinter der letzten (wertniedrigsten) Stelle.<br />

Kommerzielle Rechnungen, z. B. Rechnen mit Geldbeträgen (meist zwei Dezimale).<br />

Nachteil: sehr betragskleine und sehr betragsgroße Zahlen nicht nebeneinander darstellbar.<br />

(b) Gleitkommadarstellung, auch Fließkommadarstellung oder halb-logarithmische Darstellung<br />

〈floating point notation〉: Darstellung als Produkt aus Mantisse m und Potenz zur Basis<br />

B mit Exponent n; der Zahlenwert beträgt m · B n . Der Name der Darstellung rührt daher,<br />

dass das wertrichtige Dezimalkomma in der Mantisse nicht an vorgegebener Stelle stehen<br />

kann, sondern vom Exponenten abhängt.<br />

Bei bekannter Basis werden nur Mantisse und Exponent abgespeichert. Bei Digitalrechnern<br />

werden beide binär gespeichert (Mantisse mit Extra-Vorzeichenbit, Exponent meist als<br />

Charakteristik, entstanden aus Exponent mit additivem Korrekturterm, der Vorgabe, damit<br />

nicht negativ).<br />

Für möglichst hohe relative Genauigkeit muss eine Gleitkommazahl normalisiert werden;<br />

dabei ist in der Mantisse die werthöchste Ziffer ungleich Null (es sei denn, die Mantisse ist<br />

hat Wert Null).<br />

(c) △! Das Rechnen mit Fließkommazahlen kann infolge von impliziten Rundungsfehlern<br />

zu völlig falschen Ergebnissen führen, wenn man einen ungünstigen Algorithmus benutzt:<br />

Unterschied zwischen ” reiner“ und numerischer Mathematik. Dazu können auch Fehler durch<br />

Bereichsüberschreitungen erfolgen, s. (1.44).<br />

(1.44) Bereichsüberschreitungen beim Rechnen mit Zahlen<br />

(a) Überlauf 〈overflow〉: der Betrag des Rechenergebnisses ist zu groß ist für die gewählte<br />

Darstellung; kann auftreten bei Festkommadarstellung und bei Gleitkommadarstellung.<br />

Praxis: je nach Art der Rechenoperation Weiterrechnen mit falschem Bitmuster oder Laufzeitfehler;<br />

siehe auch (c).<br />

(b) Unterlauf 〈underflow〉: der Betrag des Rechenergebnisses ist zu klein für die gewählte Darstellung;<br />

kann auftreten bei der Gleitkommadarstellung und bei der Festkommadarstellung<br />

(dort aber nur, wenn Dezimalkomma vor der wertniedrigsten Ziffer steht, also nicht bei<br />

Ganzzahlen).<br />

Praxis: je nach Art der Rechenoperation stillschweigendes Setzen auf Null, nur sehr selten<br />

mit Laufzeitfehler versehen.<br />

(c) △! Bei C ++/C im vorzeichenlosen Ganzzahlbereich gilt der Überlauf – in beide Richtungen<br />

– nicht als Fehler! Bei Überlauf nach oben geschieht ein Überschlag zur Null und<br />

aufwärts, bei Überlauf nach unten ein Überschlag zur werthöchstem Zahl und abwärts.<br />

(1.45) Codes zur Zeichendarstellung<br />

(a) Der ASCII 〈American standard code for information interchange〉 ist ein weitverbreiterter<br />

7-Bit-Code zur Zeichendarstellung.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 16<br />

Aufbau des Codes:<br />

Code (hex) Code (dez) Bedeutung<br />

00. . . 1F 00. . . 31 Steuerzeichen<br />

Beispiele:<br />

0A 10 LF 〈line feed〉 – Zeilenvorschub<br />

0C 12 FF 〈form feed〉 – Seitenvorschub<br />

0D 13 CR 〈carriage return〉 – ” Wagenrücklauf“<br />

30. . . 39 48. . . 57 Dezimalziffern 0. . . 9<br />

41. . . 5A 65. . . 90 Großbuchstaben A. . . Z<br />

61. . . 7A 97. . . 122 Kleinbuchstaben a. . . z<br />

Rest Rest Sonderzeichen<br />

Beispiel:<br />

20 32 SP 〈space〉 – <strong>Leer</strong>zeichen<br />

Die deutschen Sonderlaute sind nicht enthalten; in der sog. deutschen Referenzversion des<br />

ASCII verdrängen diese einige Sonderzeichen, s. Tabelle (c).<br />

(b) Günstiger – und heute praktisch überall angewendet – ist die Erweiterung dieses Codes auf 8<br />

Bit: zusätzliche 128 Zeichen, Wert (dez.) 128. . . 255. Leider gibt es sehr viele unterschiedliche<br />

Erweiterungen; die wichtigsten:<br />

• ANSI-Zeichensatz (auch von Windows benutzt),<br />

• IBM-Erweiterung des ASCII (von DOS benutzt), z. B. die sog. Codetabelle 437<br />

(älteste IBM-Erweiterung) oder Codetabelle 850 (inzwischen für Deutschland als Standard<br />

vorgeschlagen).<br />

(c) Die deutschen Sonderlaute einschließlich des Paragraph-Zeichens in den wichtigsten Codierungen<br />

(Code jeweils hexadezimal, in [ ] dezimal):<br />

Zeichen ANSI IBM-erweiterter ASCII Dt. Referenzversion<br />

(Windows) (DOS) des ASCII *)<br />

Ä C4 [196] 8E [142] 5B [91] [<br />

Ö D6 [214] 99 [153] 5C [92] \<br />

Ü DC [220] 9A [154] 5D [93] ]<br />

ä E4 [228] 84 [132] 7B [123] {<br />

ö F6 [246] 94 [148] 7C [124] |<br />

ü FC [252] 81 [129] 7D [125] }<br />

ß DF [223] E1 [225] 7E [126] ∼<br />

§ A7 [167] F5 [245] 1 ) bzw. 15 [21] 2 ) 40 [64] @<br />

Anmerkungen: *) hinter den [ ]: verdrängtes Originalzeichen des ASCII<br />

1 ) nur Codetabelle 850,<br />

2 ) beide Codetabellen 437 und 850 – Codewert jedoch im Bereich<br />

der ASCII-Steuerzeichen(!!), vgl. (a)


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 17<br />

2 Algorithmenstrukturen, Operatoren<br />

(sprachunabhängige Betrachtung)<br />

2.0 Überblick<br />

Dieses Kapitel befasst sich mit den grundlegenden Möglichkeiten, wie Rechnerprogramme<br />

formuliert werden können. Diese Erläuterungen werden unabhängig von einer bestimmten<br />

Programmiersprache geboten; in späteren Kapiteln werden diese Grundgedanken auf die<br />

spezielle Sprache C ++ angewandt.<br />

Der zentrale Begriff ” Algorithmus“ und dessen Bestandteile, nämlich die ” Anweisungen“<br />

in ihren verschiedenen Arten, bilden das Thema bis zum Unterkapitel 5. Es ist wichtig<br />

für die Studierenden, diese Grundstrukturen kennenzulernen und sie – sprachunabhängig –<br />

benutzen zu können. Dabei werden eine Text- und zwei Grafikformen zu ihrer Darstellung<br />

eingeführt.<br />

Die zu diesem Kapitel angebotenen Übungsaufgaben (s. Internet) sollen sorgfältig wahrgenommen<br />

werden. Das Ziel ist es, Verständnis für vorformulierte Algorithmen und Kreativität<br />

zum ” Erfinden“ von Algorithmus zu fördern. Diese Kreativität ist sicher nicht ohne weiteres<br />

erlernbar; ein Üben und Probieren, ferner das Nach-Denken von Beispielalgorithmen sollte<br />

sie jedoch hervorlocken können.<br />

Das letzte Unterkapitel führt den Begriff des Operators ein, dazu die wichtigsten Operatoren,<br />

die auf logische Ausdrücke ( ” Aussagen“) angewandt werden.<br />

2.1 Einführung in Algorithmen<br />

(2.10) Übb Zunächst wird der für die Informatik sehr zentrale Begriff Algorithmus eingeführt<br />

(2.11, 2.12). In diesem Kurs spielt er bis einschließlich Kap. 7 eine wesentliche Rolle. Ein<br />

Algorithmus besteht aus Anweisungen; die drei wichtigen Arten von Anweisungen werden<br />

kurz vorgestellt (2.13). Zur Darstellung von Algorithmen werden hier eine Textform und zwei<br />

grafische Formen eingeführt (2.14). In den kommenden Unterkapiteln werden die Begriffe noch<br />

ausführlicher beschrieben.<br />

(2.11)<br />

(a) Algorithmus (hier): vollständige, eindeutige, nicht widerspüchliche, durchführbare, (statisch)<br />

endliche Verfahrensvorschrift zur Lösung einer bestimmten Klasse gleichartiger Probleme<br />

in endlich vielen Schritten.<br />

Anm In Spezialfällen, z. B. bei einer Prozesssteuerung, ist eine dynamische Endlichkeit ( ” in endlich<br />

vielen Schritten“) unerwünscht.<br />

(b) Programm (hier): Algorithmus, formuliert in einer für einen Rechner direkt oder indirekt<br />

verständlichen Sprache.<br />

(2.12) Die Abarbeitung eines Algorithmus wird auch Prozess genannt. Die Einheit, die einen<br />

Algorithmus abarbeitet, heißt Prozessor.<br />

(2.13)<br />

Anm Der genannte Prozess ist nicht zu verwechseln mit dem (natürlichen oder technischen) Prozess<br />

für eine Prozesssteuerung (z. B. für einen Prozessrechner).<br />

(a) Bei der Formulierung von Algorithmen unterscheidet man zwei verschiedene Arten von Anweisungen:<br />

• Anweisungen, die direkt durchgeführt werden sollen,<br />

• Steueranweisungen oder Kontrollstrukturen: diese geben an, ob, unter welcher Bedingung,<br />

wie oft o. ä. eine oder mehrere Anweisungen ausgeführt werden sollen.<br />

Hierbei gibt es zwei (Unter-)Arten:<br />

◦ Auswahl: die Abarbeitung unterliegt einer Bedingung,<br />

◦ Wiederholung: die Abarbeitung wird – abhängig von einer Bedingung – ggf. mehrfach<br />

durchgeführt.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 18<br />

Anm Der Ausdruck Kontroll. . . (-Struktur) ist eine etwas unglückliche Übersetzung des englischen<br />

Wortes 〈control . . . 〉, der Ausdruck hat sich jedoch leider durchgesetzt. Besser wäre: Steuer(ung).<br />

. .<br />

(b) Entsprechend dieser insgesamt drei Arten von Anweisungen gibt es drei wichtige Grundstrukturen<br />

zur Formulierung von Algorithmen:<br />

• Folge (Sequenz),<br />

• Auswahl (Selektion),<br />

• Wiederholung (Iteration).<br />

Man kann beweisen, dass jede Problemlösung, die überhaupt als Algorithmus formulierbar<br />

ist, immer so gestaltet werden kann, dass nur diese drei Algorithmenstrukturen benutzt werden.<br />

Insbesondere kann immer auf direkte Sprunganweisungen ( ” goto“) verzichtet werden.<br />

(2.14) Beschreibungsarten für Algorithmen<br />

(a) Beschreibung mit formalisiertem deutschen Text ( ” textuell Form“, ” Pseudocode“). Diese<br />

Beschreibungsform ist nicht genormt. In dieser Vorlesung wird sie benutzt, um Zusammenhänge<br />

sprachnah, aber unabhängig von einer speziellen Programmiersprache zu formulieren.<br />

(b) Grafische Darstellungen:<br />

• Programmablaufplan (PAP),<br />

• Struktogramm oder Nassi-Shneiderman-Diagramm.<br />

Anm Beide grafische Beschreibungsarten sind genormt (DIN). Der Programmablaufplan ist leichter<br />

herzustellen und abzuändern, führt aber meist zu schwer verständlichen und kaum wartbaren<br />

Programmen. Das Struktogramm hat diesen Nachteil nicht, ist aber schwieriger bei der<br />

Darstellung und Änderung. Für den Übergang zum Programmieren ist das Struktogramm<br />

auf jeden Fall vorzuziehen.<br />

↑↑ Zur Entwicklung und Darstellung sehr komplexer Systeme gibt es andere grafische Formen.<br />

Entwickelt man objektorientiert, so bieten sich u. a. Anwendungsfalldiagramme, Objektdiagramme<br />

und Klassendiagramme an. Eine weltweitweit verbreitete grafische Beschreibungssprache<br />

hierfür ist genormt (ANSI), sie wird UML 〈unified modeling language〉 genannt.<br />

2.2 Folge (Sequenz)<br />

(2.20) Übb Bei einer Folge (Sequenz) werden die einzelnen Bestandteile (Anweisungen) hintereinander<br />

ohne Änderung der Reihenfolge und ohne eine Auslassung abgearbeitet. Diese sehr<br />

einfache Algorithmen-Grundstruktur wird im folgenden mit den drei Darstellungsformen<br />

(Text und Grafik) erläutert. Ein ausführliches Beispiel (2.25) – bitte dort den Entwicklungsweg<br />

mitdenken! – rundet dieses Unterkapitel ab.<br />

(2.21) Die Folge in den drei Darstellungsarten:<br />

(a) Pseudocode, Beschreibung s. (2.22),<br />

(b) Programmablaufplan, Beschreibung s. (2.23),<br />

(c) Struktogramm, Beschreibung s. (2.24).<br />

Anweisung 1<br />

Anweisung 2<br />

Anweisung 3<br />

(a) (b) (c)<br />

❄<br />

Anweisung 1<br />

❄<br />

Anweisung 2<br />

❄<br />

Anweisung 3<br />

❄<br />

Anweisung 1<br />

Anweisung 2<br />

Anweisung 3


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 19<br />

(2.22) Pseudocode<br />

(a) Die Anweisungen werden – laut Vereinbarung, für diese Vorlesung geltend – in einen Kasten<br />

geschrieben. Die für die Abarbeitung benötigten Datenspeicherplätze ( ” Kästchen“, ” Variable“<br />

mit ” Typ“) werden ebenfalls angegeben, vgl (2.25a).<br />

Der Name für den Algorithmus wird, wenn vorhanden, in einem darübergestellten Kasten<br />

festgelegt, s. (2.25a).<br />

(b) Empf Wichtige Lay-out-Empfehlung (für Studierende derzeit ein Muss), vgl. (2.51):<br />

Jede Anweisung sollte jeweils (i. a.) in eine Zeile geschrieben werden. Ist eine Anweisung zu<br />

lang, wird sie – eingerückt – in der Folgezeile fortgesetzt.<br />

(2.23)<br />

(a) Beim Programmablaufplan unterscheidet man (zunächst) die folgenden Arten von Symbolen<br />

(siehe (2.21b) und (b)):<br />

• (normale) Anweisungen: jeweils im rechteckigen Kasten, meist in konstanter Größe,<br />

• Programm-Ablauflinien: Pfeile für die Ablaufreihenfolge (für den Kontrollfluss oder<br />

Steuerfluss),<br />

• Anfangs- und Endesymbole: abgerundete Kästen,<br />

• spezielle Anweisungen für Ein- und Ausgabe 〈input, output〉: Parallelogramme.<br />

(b) Symbole für Anfang/Ende und für Ein-/Ausgabe, jeweils mit Ablauflinien gezeichnet:<br />

✛<br />

Anfang<br />

✚<br />

❄<br />

✘<br />

✙<br />

✛ ❄<br />

Ende<br />

✚<br />

✘<br />

✙<br />

✄ ✄<br />

✄<br />

✄✄<br />

✄<br />

❄<br />

Lies . . .<br />

❄<br />

Schreibe . . .<br />

(2.24) Ein Struktogramm besteht immer aus einem (großen) Rechteck mit interner feinerer Unterteilung.<br />

Jede Anweisung darin steht jeweils wiederum innerhalb eines Rechtecks.<br />

Ein Struktogramm-Rechteck(-Teil) wird immer von der oberen Begrenzungslinie her betreten<br />

und durch die untere Linie wieder verlassen.<br />

Im Struktogramm dieser Vorlesung werden ab und zu zusätzlich – außerhalb der Norm –<br />

zwei Kästen darübergestellt: Name des Algorithmus und Aufzählung der benötigten Speicherplätze<br />

( ” Variable“), siehe (2.25c).<br />

(2.25) Bsp Berechnung des Volumens eines Kreiskegels<br />

(a) Pseudocode:<br />

In dem in dieser Vorlesung vorgestellten Pseudocode sollen die sog. ” Schlüsselwörter“ (Wörter,<br />

die in der jeweils verwendeteten Sprache in dem vorhandenen Kontext eine fest vorgegebene<br />

Bedeutung haben) in Großbuchstaben erscheinen, damit sie mehr auffallen: hier<br />

zunächst die Wörter<br />

(a1) ALGORITHMUS, ANFANG, ENDE;<br />

(a2) VARIABLE, TYP;<br />

(a3) KONSTANTE, WERT.<br />

Später werden weitere Schlüsselwörter hinzukommen.<br />

(1) Vorläufige Formulierung:<br />

❄<br />

✄ ✄<br />

✄<br />

✄✄<br />


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 20<br />

ALGORITHMUS VolKreisKegel<br />

ANFANG<br />

Verwende die Kästchen Radius, Höhe, Pi, Volumen<br />

Lies die erste Zahl, speichere sie in Radius ab<br />

Lies die nächste Zahl, speichere sie in Höhe ab<br />

Speichere 3,14159 in Pi ab<br />

Berechne 1<br />

3 ·Radius·Radius·Pi·Höhe,<br />

speichere Ergebnis in Volumen ab<br />

Schreibe Volumen<br />

ENDE<br />

(2) Endgültiger Pseudocode – so in dieser Vorlesung häufig für die textuelle Darstellung von<br />

Algorithmen benutzt:<br />

ALGORITHMUS VolKreisKegel<br />

ANFANG<br />

VARIABLE radius, höhe, pi, volumen TYP Fließkommazahl<br />

Lies radius, höhe<br />

pi ← 3,14159<br />

volumen ← radius∗radius∗pi∗höhe/3<br />

Schreibe volumen<br />

ENDE<br />

Für ” VARIABLE“ wird hier zusätzlich der ” TYP“ angegeben. Damit wird dem Prozessor<br />

mitgeteilt, welcher Art die Speicherung und Codierung der angegebenen Variablen sein soll<br />

– und damit verbunden die Speicherplatzgröße und die erlaubten Rechenoperationen. Als<br />

Zuweisungszeichen wird hier bewusst das Zeichen ” ←“ genommen, um sprachenunabhängig<br />

zu sein. Die Variablennamen beginnen mit einem Kleinbuchstaben; die genauere Regel als<br />

Empfehlung s. (4.71b).<br />

(3) Eine Variation der Algorithmusbeschreibung (a2): Da die Zahl π bei jedem Algorithmusdurchlauf<br />

denselben Wert hat (Konstante), kann auf eine Zuweisung verzichtet werden; statt<br />

dessen werde zu Beginn ein Speicherplatz mit richtiger Belegung ( ” Initialisierung“) bereitgestellt:<br />

ALGORITHMUS VolKreisKegel<br />

(4)<br />

ANFANG<br />

VARIABLE radius, höhe, volumen TYP Fließkommazahl<br />

// Die Zahl pi wird hier als Konstante eingeführt:<br />

KONSTANTE pi TYP Fließkommazahl WERT 3,14159<br />

Lies radius, höhe<br />

volumen ← radius∗radius∗pi∗höhe/3<br />

Schreibe volumen<br />

ENDE<br />

Es wird vereinbart, wie schon im obigen Kasten zu sehen, dass hinter dem Doppel-Schrägstrich<br />

” //“ beliebiger Text als Kommentar stehen kann; der Prozessor soll alle Zeichen ab<br />

einschließlich ” //“ bis zum Zeilenende ignorieren.<br />

Zur Umgang mit den Variablen (bzw. Kästchen) – WICHTIG zum Erfassen des Sinns<br />

eines unbekannten Algorithmus:<br />

Zum Durchspielen eines Algorithmus unbedingt die Variablen-Werte notieren, und<br />

zwar am besten in Form einer Tabelle (je Variable eine Zeile); bei Wertänderung alten<br />

Wert jeweils leserlich durchstreichen, neuen Wert daneben schreiben.<br />

(bc) Die zu (a2) gehörigen Darstellungen Programmablaufplan (b) und Struktogramm (c):


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 21<br />

✄✄<br />

✄<br />

✄✄<br />

✄<br />

(b) (c)<br />

✛ ✘<br />

Anfang<br />

✚<br />

❄<br />

Lies radius, höhe<br />

❄<br />

pi ← 3,14159<br />

❄<br />

volumen ←<br />

radius∗radius∗pi<br />

∗höhe/3<br />

❄<br />

Schreibe volumen<br />

✛ ❄<br />

Ende<br />

✚<br />

✙<br />

✄✄<br />

✄<br />

✄✄<br />

✄<br />

✘<br />

✙<br />

ALGORITHMUS<br />

VolKreisKegel<br />

Verwende radius, höhe,<br />

pi, volumen<br />

Lies radius, höhe<br />

pi ← 3,14159<br />

volumen ←<br />

radius∗radius∗pi∗höhe/3<br />

Schreibe volumen<br />

(d) Ohne weitere Kommentierung hier der Algorithmus (a3) als C ++-Programm:<br />

// Berechnung des Volumens eines Kreiskegels<br />

#include <br />

using namespace std;<br />

int main()<br />

{<br />

double radius, hoehe, volumen;<br />

const double pi=3.14159;<br />

}<br />

cin >> radius >> hoehe;<br />

volumen=radius*radius*pi*hoehe/3;<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 22<br />

◦ Mehrfachauswahl als WENN-SONSTWENN-Kette,<br />

◦ Mehrfachauswahl mit Selektorwert (FALLUNTERSCHEIDUNG ANHAND ...).<br />

Anm Die Mehrfachauswahl mit Selektorwert wird nicht durch alle Programmiersprachen unterstützt.<br />

(2.31) Die Bedingung bei der Auswahl und bei der Wiederholung (Kap. 2.4) entspricht einer Aussage,<br />

wie sie in der formalen Logik bzw. Logistik definiert wird:<br />

Eine Aussage (im Sinne der Logik) ist ein sprachliches Gebilde, dem man (im Prinzip)<br />

eindeutig die Qualität WAHR oder FALSCH zuordnen kann. Die Qualität WAHR bzw.<br />

FALSCH heißt Wahrheitswert der Aussage.<br />

Näheres zu Aussagen, insbesondere zu Verknüpfungen durch Operatoren, siehe (Kap. 2.6).<br />

(2.32) Bei der zweiseitigen Auswahl wird zunächst die Bedingung geprüft; hat sie den Wert<br />

WAHR, wird die Dann-Anweisung ausgeführt, beim Wert FALSCH wird die Sonst-Anweisung<br />

abgearbeitet.<br />

Die zweiseitige Auswahl in den drei verschiedenen Darstellungsarten:<br />

(a) Pseudocode, (b) Programmablaufplan, (c) Struktogramm:<br />

(a)<br />

❄<br />

✟<br />

✟❍<br />

W ❍<br />

✟ ❍ F<br />

❍Bedingung✟<br />

❍<br />

❍✟<br />

✟<br />

❄ ❄<br />

Dann-Anweisung<br />

(ggf. Folge)<br />

✲✛<br />

❄<br />

WENN Bedingung DANN<br />

Dann-Anweisung(s-Folge)<br />

SONST<br />

Sonst-Anweisung(s-Folge)<br />

(b) (c)<br />

Sonst-Anweisung<br />

(ggf. Folge)<br />

✏✏✏✏✏✏✏✏✏✏ <br />

<br />

Bedingung<br />

W <br />

F<br />

<br />

Dann-Anweisung Sonst-Anweisung<br />

(ggf. Folge) (ggf. Folge)<br />

Zu (a): Die Wörter WENN, DANN, SONST sind weitere Schlüsselwörter (2.25a).<br />

Zum Lay-out (Studierende: als Muss): Obwohl SONST die Anweisung fortsetzt, sollte es –<br />

entgegen (2.22b) – nicht eingerückt werden, sondern direkt unter dem korrespondierendem<br />

WENN stehen, vgl. Regeln in (2.51). Begründung dafür s. (2.34 ↑↑)<br />

Zu (b): Im PAP wird eine Bedingung in Form einer Raute gezeichnet, die je nach Beantwortung<br />

zwei (oder auch mehr (2.37b)) Ausgänge hat – hier W (wahr) und F (falsch).<br />

Zu (c): Im Struktogramm tritt man von oben zunächst in die Bedingung ein und verlässt<br />

das Dreieck entweder in den Ja-Zweig oder in den Nein-Zweig.<br />

(2.33) Bsp für die zweiseitige Auswahl in den drei Darstellungsarten:<br />

(a)<br />

WENN es schneit DANN<br />

Zieh Schneeschuhe an<br />

SONST<br />

Zieh Turnschuhe an


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 23<br />

✟<br />

❄<br />

❍<br />

W ✟ ❍<br />

✟ ❍ F<br />

❍ es schneit ✟<br />

❍<br />

❍✟<br />

✟<br />

❄ ❄<br />

Zieh Schneeschuhe an Zieh Turnschuhe an<br />

✲✛<br />

❄<br />

(b) (c)<br />

✏✏✏✏✏✏✏✏✏✏ <br />

<br />

es schneit<br />

W <br />

F<br />

<br />

Zieh Schneeschuhe an Zieh Turnschuhe an<br />

(2.34) Die einseitige Auswahl ist eine Verkürzung der zweiseitigen Auswahl durch Weglassen des<br />

Sonst-Zweiges.<br />

Die einseitige Auswahl in den drei verschiedenen Darstellungsarten:<br />

(a) Pseudocode, (b) Programmablaufplan, (c) Struktogramm:<br />

(a)<br />

WENN Bedingung DANN<br />

Dann-Anweisung(s-Folge)<br />

(b) (c)<br />

✟❄❍<br />

W ✟ ❍<br />

✟ ❍<br />

❍Bedingung✟<br />

❍<br />

❍✟<br />

✟<br />

F<br />

❄<br />

Dann-Anweisung<br />

(ggf. Folge)<br />

✲❄<br />

❄<br />

✟ ✟✟✟✟✟✟<br />

<br />

<br />

Bedingung<br />

<br />

W <br />

F<br />

<br />

Dann-Anweisung<br />

(ggf. Folge)<br />

↑↑ Die grammatische Zweideutigkeit des Konstruktes ” WENN Bedingung DANN Anweisung“<br />

– nämlich einmal als vollständige einseitige Auswahl, zum andern als erster Teil der zweiseitigen<br />

Auswahl – wird hier im Pseudocode bewusst zugelassen, da sie (leider!) auch in<br />

vielen Programmiersprachen besteht – auch in C ++/C. Um für den Menschen die Übersichtlichkeit<br />

zu erhöhen, wurde auch in (2.32) die Empfehlung (Stud.: die Regel) gegeben, das<br />

unterscheidende SONST nicht einzurücken, damit es auffällt.<br />

(2.35) Bsp für die einseitige Auswahl:<br />

(a)<br />

WENN es regnet DANN<br />

Zieh Regenmantel über


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 24<br />

❄<br />

Zieh Regenmantel über<br />

(b) (c)<br />

✟❄<br />

✟<br />

❍<br />

W ❍<br />

✟ ❍<br />

❍ es regnet ✟<br />

❍<br />

❍✟<br />

✟<br />

F<br />

✲❄<br />

❄<br />

✟ ✟✟✟✟✟✟<br />

<br />

<br />

es regnet<br />

<br />

W <br />

F<br />

<br />

Zieh Regenmantel über<br />

(2.36) Die Mehrfachauswahl als WENN-SONSTWENN-Kette wird durch eine mehrstufige<br />

Schachtelung einer zweiseitigen Auswahl (2.32) gebildet, und zwar jede jeweils im SONST-<br />

Zweig der übergeordneten zweiseitigen Auswahl (Näheres zur Schachtelung s. Kapitel 2.5).<br />

Empf Zum Lay-out (für Stud. ein Muss): Da die Schachtelung bei mehreren Stufen zu<br />

unübersichtlich wird und da mit SONST WENN nie eine Anweisung beginnen kann, wird<br />

empfohlen, jedes SONST WENN und das letzte SONST direkt unter das erste (führende)<br />

WENN zu schreiben, vgl. (2.51), entgegen (2.22b)<br />

Pseudocode (links mit exakter Schachtelungs-Einrückung, rechts mit empfohlenem übersichtlicherem<br />

Lay-out):<br />

WENN y < 0 DANN<br />

Schreibe ” y negativ“<br />

SONST<br />

WENN y < 5 DANN<br />

Schreibe ” 0 y < 5“<br />

SONST<br />

WENN y < 10 DANN<br />

Schreibe ” 5 y < 10“<br />

SONST<br />

WENN y < 20 DANN<br />

Schreibe ” 10 y < 20“<br />

SONST<br />

Schreibe ” 20 y“<br />

WENN y < 0 DANN<br />

Schreibe ” y negativ“<br />

SONST WENN y < 5 DANN<br />

Schreibe ” 0 y < 5“<br />

SONST WENN y < 10 DANN<br />

Schreibe ” 5 y < 10“<br />

SONST WENN y < 20 DANN<br />

Schreibe ” 10 y < 20“<br />

SONST<br />

Schreibe ” 20 y“<br />

Man achte darauf, dass hier die einzelnen Bedingungen nacheinander geprüft werden, die<br />

Lösungsmengen brauchen daher nicht disjunkt zu sein, d. h. sie dürfen sich teilweise überlappen.<br />

(2.37) Bei der Mehrfachauswahl mit Selektorwert wird anhand des Wertes eines Ausdrucks,<br />

des sog. Selektorwertes, entschieden, zu welchem Verzweigungspunkt gegangen wird. Allen<br />

Verzweigungen enden jeweils beim folgenden Verzweigungspunkt.<br />

Anm Die Mehrfachauswahl wird durch die Sprache Pascal (und Visual Basic) unterstützt, nicht<br />

jedoch durch C ++ oder C; dort gibt es jedoch ein Konstrukt, das zur Nachahmung einer<br />

Mehrfachauswahl genutzt werden kann.<br />

(a) Pseudocode:<br />

(b) Programmablaufplan:<br />

FALLUNTERSCHEIDUNG ANHAND Jahreszeit:<br />

Frühling: Freu dich auf Blumen<br />

Sommer: Geh baden<br />

Herbst: Ernte Äpfel<br />

Winter: Zieh dich warm an


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 25<br />

❄<br />

Frühling<br />

Freu dich<br />

auf Blumen<br />

❄<br />

Geh baden<br />

❄<br />

✟<br />

✟❍<br />

❍<br />

✟ ❍<br />

❍Jahreszeit=<br />

✟<br />

❍<br />

❍✟<br />

✟<br />

Sommer<br />

❄<br />

❄<br />

Herbst<br />

Ernte Äpfel<br />

(c) Struktogramm:<br />

❳ ❳❳❳<br />

❳❳❳<br />

❳❳❳<br />

❳❳❳ Jahreszeit =<br />

❳❳❳<br />

❳❳❳<br />

❳❳❳<br />

❳❳❳<br />

Frühling Sommer Herbst Winter ❳ ❳<br />

Freu dich<br />

auf Blumen<br />

Geh baden Ernte Äpfel Zieh dich<br />

warm an<br />

(2.38) Manchmal ist es sinnvoll, einen SONST-Zweig zu formulieren.<br />

❄<br />

Zieh dich<br />

warm an<br />

Anm Nicht alle Programmiersprachen. die eine Mehrfachauswahl unterstützen, kennen den<br />

SONST-Zweig.<br />

Beispiel für Mehrfachauswahl mit SONST-Zweig:<br />

(a) Pseudocode:<br />

FALLUNTERSCHEIDUNG ANHAND Wetter:<br />

Sonne: Bleib im T-Shirt<br />

Trübtrocken: Zieh Pullover über<br />

Regen: Zieh Regenmantel an<br />

SONST: Verkriech dich zu Hause<br />

(b) Struktogramm:<br />

❳ ❳❳❳<br />

❳❳❳<br />

✚<br />

❳❳❳<br />

✚<br />

❳❳❳ Wetter = ✚<br />

❳❳❳<br />

✚<br />

❳❳❳<br />

✚<br />

Sonne Trübtrocken Regen<br />

❳❳<br />

✚<br />

✚ SONST<br />

Bleib<br />

im T-Shirt<br />

Zieh<br />

Pullover<br />

über<br />

Zieh<br />

Regenmantel<br />

an<br />

Verkriech dich<br />

zu Hause<br />

(2.39) Wie aus dem Pseudocode ersichtlich, muss ein Prozessor zur Verwirklichung der Auswahlstruktur<br />

fähig sein, Vorwärtssprünge im Befehlstext durchzuführen, d. h. Anweisungen in<br />

Vorwärtsrichtung zu überspringen, vgl. (2.26).<br />

2.4 Wiederholung (Iteration)<br />

(2.40) Übb Bei der Wiederholung (Iteration), der zweiten der beiden Kontrollstrukturen<br />

(2.13a), wird eine Anweisung, die sog. Schleifenanweisung, mehrfach ausgeführt.<br />

Man unterscheidet zunächst zwei Arten von Schleifen:<br />

Winter


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 26<br />

• Prüfen der Eintrittsbedingung vor Schleifeneintritt:<br />

kopfgesteuerte Schleife oder abweisende Schleife; hierbei kann es vorkommen,<br />

dass die Schleifenanweisung gar nicht durchlaufen wird.<br />

• Prüfen der Bedingung nach dem Schleifendurchlauf:<br />

fußgesteuerte Schleife oder nicht-abweisende Schleife; hierbei wird die Schleife<br />

mindestens einmal durchlaufen.<br />

Je nach Sprache unterscheidet man bei der fußgesteuerten Schleife zwei Formulierungen:<br />

◦ Formulierung der Bedingung als Wiedereintrittsbedingung,<br />

◦ Formulierung der Bedingung als Austrittsbedingung.<br />

Die Bedingung bei der ersten Art ist logisch invers zur zweiten Art (vgl. Nicht-Operator<br />

(2.63)).<br />

Anm In dieser Vorlesung wird die fußgesteuerte Schleife immer in der Form mit Wiedereintrittsbedingung<br />

benutzt, da diese Art durch C ++ und C unterstützt wird; die Formulierung<br />

als Austrittsbedingung entspricht der Sprache Pascal.<br />

Gemeinsam ist bei der kopf- und bei der fußgesteuerten Schleife, dass die Anzahl der Wiederholungen<br />

(Schleifendurchläufe) nicht unbedingt beim ersten Schleifeneintritt feststeht.<br />

Bei einer weiteren Schleifenart steht die Anzahl der Wiederholungen bei Schleifeneintritt<br />

fest: Zählschleife (2.44).<br />

(2.41) Die kopfgesteuerten Schleife (abweisende Schleife) wird folgendermaßen gedeutet:<br />

Zunächst die Bedingung geprüft: ist sie FALSCH, wird die Schleife gar nicht betreten, ist<br />

sie WAHR, wird die Schleife einmal durchlaufen. Danach wird die Bedingung wieder geprüft;<br />

falls die Bedingung den Wert WAHR ergibt, wird die Schleife nochmals durchlaufen -<br />

usw. Sehr ” beliebt“ – nicht nur bei Programmieranfängern – sind Endlosschleifen bei falsch<br />

formulierter Bedingung: wird sie nie FALSCH, wird die Schleife endlos oft durchlaufen.<br />

Daher wichtig: die Schleifenbedingung muss (i. a.) in irgendeiner Weise durch die Schleifendurchläufe<br />

verändert werden. (Andere Möglichkeit, nicht in dieser Vorlesung genutzt: die<br />

Bedingung hängt von äußeren Ereigissen ab, z. B. Tastendruck.)<br />

Die kopfgesteuerte Schleife in den drei verschiedenen Darstellungsarten:<br />

(a) Pseudocode, (b) Programmablaufplan, (c) Struktogramm:<br />

Schleifen-Anweisung<br />

(ggf. Folge)<br />

✛<br />

(a)<br />

SOLANGE Bedingung TUE<br />

Schleifen-Anweisung(s-Folge)<br />

(b) (c)<br />

✲❄<br />

✟❄<br />

✟<br />

❍<br />

W ❍<br />

✟ ❍<br />

❍Bedingung✟<br />

❍ ✟<br />

❍✟<br />

F<br />

❄<br />

SOLANGE Bedingung<br />

Schleifen-Anweisung<br />

(ggf. Folge)<br />

(2.42) Die fußgesteuerte Schleife (nicht-abweisende Schleife) – formuliert in der C ++/Cüblichen<br />

Art als Wiedereintrittsbedingung – wird folgendermaßen interpretiert: Zunächst<br />

wird die Schleife einmal durchlaufen. Danach wird geprüft, ob sie nochmals durchlaufen<br />

wird, nämlich dann, wenn die Wiedereintrittsbedingung den Wert WAHR ergibt. Nach dem<br />

nächsten Schleifendurchlauf wird die Bedingung wieder geprüft – usw. Auch hierbei besteht<br />

wie in (2.41) die Gefahr einer Endlosschleife.<br />

Die fußgesteuerte Schleife (in der C ++/C-üblichen Art) in den drei verschiedenen Darstellungsarten:<br />

(a) Pseudocode, (b) Programmablaufplan, (c) Struktogramm:<br />

(a)<br />

TUE<br />

Schleifen-Anweisung(s-Folge)<br />

SOLANGE Bedingung


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 27<br />

❄ ✛<br />

❄<br />

Schleifen-Anweisung<br />

(ggf. Folge)<br />

(b) (c)<br />

W<br />

✟<br />

✟❍<br />

❍<br />

✲✟<br />

❍<br />

❍Bedingung✟<br />

❍ ✟<br />

❍✟<br />

F<br />

❄<br />

Schleifen-Anweisung<br />

(ggf. Folge)<br />

SOLANGE Bedingung<br />

Anm Im Pseudocode ist das Schlüsselwort SOLANGE gewählt, wie es auch bei der kopfgesteuerten<br />

Schleife vorkommt. Hier handelt es sich jedoch nicht um einen grammatischen Konflikt<br />

wie bei der Auswahlstruktur (2.34 ↑↑), die Grammatik ist hier eindeutig. Man wird jedoch<br />

bei nur flüchtigem Anschauen des Algorithmus zu Fehlinterpretationen verleitet: nämlich<br />

Verwechselung von kopf- und fußgesteuerter Schleife. Diese Verwechselungsgefahr wird hier<br />

bewusst in Kauf genommen, da sie (leider!) in gleicher Weise auch in den Sprachen C ++/C<br />

besteht. Die empfohlene Nicht-Einrückung von SOLANGE – entgegen (2.22b) – ist daher<br />

etwas problemtisch; zur Problemlösung in C ++/C s. (4.73).<br />

(2.43) Die fußgesteuerte Schleife in der Pascal-Art (Bedingung als Austrittsbedingung formuliert)<br />

in den drei verschiedenen Darstellungsarten:<br />

❄ ✛<br />

❄<br />

Schleifen-Anweisung<br />

(ggf. Folge)<br />

(a)<br />

TUE<br />

Schleifen-Anweisung(s-Folge)<br />

BIS Bedingung<br />

(b) (c)<br />

F<br />

✟<br />

✟❍<br />

❍<br />

✲✟<br />

❍<br />

❍Bedingung✟<br />

❍<br />

❍✟<br />

✟<br />

W<br />

❄<br />

BIS Bedingung<br />

Schleifen-Anweisung<br />

(ggf. Folge)<br />

(2.44) Die Zählschleife wird meist mit einer Zählvariablen, in der die Schleifendurchläufe mitgezählt<br />

werden, verwirklicht. In (a) ist die Zählschleife ohne eine solche Variable, in (bc) mit<br />

einer Zählvariablen formuliert.<br />

(a)<br />

Lies zahl<br />

WIEDERHOLE 5-mal<br />

Multipliziere zahl mit sich selbst,<br />

speichere Ergebnis in zahl ab<br />

(b) (c)<br />

Lies zahl<br />

WIEDERHOLE FÜR i VON 1 BIS 5<br />

zahl ← zahl∗zahl<br />

Lies zahl<br />

WIEDERHOLE FÜR i VON 1 BIS 5<br />

zahl ← zahl∗zahl<br />

(2.45) Aus dem Pseudocode ist ersichtlich, dass ein Prozessor zur Verwirklichung der Wiederholungsstruktur<br />

fähig sein muss, neben Vorwärtssprüngen auch zusätzlich Rückwärtssprünge<br />

im Befehlstext durchzuführen, d. h. Anweisungen in Rückwärtsrichtung zu überspringen,<br />

vgl. (2.26).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 28<br />

2.5 Schachtelung der drei Algorithmenstrukturen<br />

(2.50) Übb Diese drei Algorithmenstrukturen Folge, Auswahl, Wiederholung können beliebig<br />

ineinander geschachtelt werden. (Die Schachtelung einer Folge innerhalb einer Auswahl oder<br />

Wiederholung ist bereits in früheren Diagrammen angedeutet worden.)<br />

Nur durch Benutzung dieser drei Strukturen mit der Möglichkeit, sie ineinander zu schachteln,<br />

können beliebig komplizierte Algorithmen gebaut werden. Wie bereits in (2.13b) erwähnt,<br />

reichen diese Grundstrukturen aus; insbesondere ist es nicht nötig, direkte Sprunganweisungen<br />

( ” goto“) zu benutzen. Solche Sprunganweisungen sind im Rahmen dieses Kurses für die<br />

Studierenden verboten; daher werden sie in C ++ auch nicht eingeführt.<br />

Theoretisch kann die Schachtelung in beliebige Tiefe gehen. Eine für die Praxis äußerst<br />

wichtige Grenze liegt in der Forderung, dass die Algorithmenstruktur für den menschlichen<br />

Leser noch überschaubar bleiben muss. Daher wird in diesem Kurs auf das Lay-out von<br />

Programmtext (als Pseudocode oder in einer Programmiersprache) sehr großer Wert gelegt.<br />

Im folgenden Punkt (2.51) werden die Grundregeln erläutert; dieselben Regeln, dort noch<br />

etwas ausführlicher, werden in (4.73) auf C ++ angewendet.<br />

Die Punkte (2.52) und (2.53) bringen Beispiele für Schachtelungen. Das zweite Beispiel, der<br />

Algorithmus ” Bubble-Sort“, kann ggf. auch für spätere Anwendungen benötigt werden; ein<br />

Auswendiglernen ist nicht erforderlich, Erinnern und Wiederauffinden reichen aus.<br />

(2.51) Empf – für Studierende nicht nur eine Empfehlung, sondern ein Muss –<br />

Anfang und Ende der verschiedenen Schachtelungsebenen müssen streng beachtet werden.<br />

Im Pseudocode geschieht dieses durch Einrückungen, vgl. (2.52a).<br />

Wichtig wird es beim Programmieren sein, selbst so viel Disziplin beim Lay-out des<br />

Programmtextes zu zeigen, dass die Einrückungen für die einzelnen Schachtelungsebenen<br />

genau vorgenommen werden (je Schachtelungsstufe mit einem bestimmten<br />

Einrückabstand) – und zwar nur wegen der Übersichtlichkeit für den betrachtenden<br />

Menschen. Ein C ++-Compiler überliest die Einrückungen. da die Sprache ein beliebiges<br />

Lay-out gestattet.<br />

Auch hierbei gilt weiterhin (2.22b): Jede Anweisung sollte jeweils (i. a.) in eine Zeile<br />

geschrieben werden. Ist eine Anweisung zu lang: Fortsetzung eingerückt in der<br />

Folgezeile.<br />

Ausnahmen, und zwar keine Einrückung, obwohl Fortsetzung einer Anweisung (einer<br />

Kontrollstruktur):<br />

• Zweiseitige Auswahl: SONST direkt unter WENN, nicht eingerückt (2.32, 2.34 ↑↑),<br />

• WENN-SONSTWENN-Kette (2.36),<br />

• Fußgesteuerte Wiederholung: SOLANGE direkt unter TUE, nicht eingerückt –<br />

obwohl problematisch (2.42 Anm); zu C ++/C s. (4.73).<br />

Im Struktogramm werden die Schachtelungsebenen durch ein echtes Ineinander-Schachteln<br />

der Rechtecke deutlich, vgl. (2.52c).<br />

(2.52) Bsp Zu den Schachtelungsebenen des folgenden Beispiels: In der obersten (ersten) Schachtelungsebene<br />

gibt es – neben der Angabe der benötigten Speicherplätze – insgesamt drei Anweisungen.<br />

Die zweite unter ihnen hat eine Unterstruktur, sie ist eine zweiseitige Auswahl;<br />

in der nächsten Schachtelungsebene sieht man, dass sowohl der DANN-Zweig als auch der<br />

SONST-Zweig wieder aus je einer zweiseitigen Auswahl (jeweils zweite Schachtelungsebene)<br />

bestehen. Die einzelnen Zuweisungen stehen in der dritten Schachtelungsebene.<br />

(a) Pseudocode – einmal normal mit Einrückungen, einmal mit Extrakennzeichnung der Schachtelungsebenen:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 29<br />

ALGORITHMUS WasMachtEr<br />

ANFANG<br />

VARIABLE x, y, z, w TYP Ganzzahl<br />

Lies x, y, z<br />

WENN x > y DANN<br />

WENN x > z DANN<br />

w ← x<br />

SONST<br />

w ← z<br />

SONST<br />

WENN y > z DANN<br />

w ← y<br />

SONST<br />

w ← z<br />

Schreibe w<br />

ENDE<br />

(b) Programmablaufplan: Übung selbst, Bespr. in Vorlesung<br />

(c) Struktogramm:<br />

ALGORITHMUS WasMachtEr<br />

VARIABLE x, y, z, w TYP Ganzzahl<br />

Lies x, y, z<br />

❳<br />

❳ ❳ ❳<br />

✘✘✘<br />

❳ ❳ ❳ ❳ x > y<br />

✘✘✘<br />

❳ ❳<br />

✘✘✘<br />

W ❳ ❳ ❳ ✘✘✘✘✘ F<br />

✏✏✏✏✏✏✏ <br />

x > z<br />

W <br />

F<br />

<br />

✏✏✏✏✏✏✏ <br />

y > z<br />

W <br />

F<br />

<br />

w ← x w ← z w ← y w ← z<br />

Schreibe w<br />

ALGORITHMUS WasMachtEr<br />

ANFANG<br />

| VARIABLE x, y, z, w TYP Ganzzahl<br />

| Lies x, y, z<br />

| WENN x > y DANN<br />

| | WENN x > z DANN<br />

| | | w ← x<br />

| | SONST<br />

| | | w ← z<br />

| SONST<br />

| | WENN y > z DANN<br />

| | | w ← y<br />

| | SONST<br />

| | | w ← z<br />

| Schreibe w<br />

ENDE<br />

Die einzelnen Schachtelungsebenen:<br />

[n] bedeute: Anweisung in der Schachtelungsebene n; diese so gekennzeichneten Anweisungen sind<br />

jeweils in der Folgeabbildung in ihrer inneren Struktur dargestellt.<br />

Lies x, y, z<br />

[1]<br />

Schreibe w<br />

W F<br />

❳<br />

❳ ❳ ❳<br />

✘✘✘<br />

❳ x > y<br />

❳ ❳ ❳ ✘✘✘ ✘✘✘<br />

W F<br />

[2] [2]<br />

x > z ✏ <br />

✏✏✏<br />

y > z ✏<br />

W ✏✏✏F<br />

[3] [3] [3] [3] w ← x w ← z w ← y w ← z


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 30<br />

(2.53) Algorithmus zum Sortieren:<br />

Bubble-Sort, deutsch sinngemäß:<br />

Sortieren durch Nachbarvertauschungen.<br />

Der Typ<br />

Ganzzahl-FELD[anzahl]<br />

bedeute ein Feld oder Array<br />

von anzahl Ganzzahl-Elementen;<br />

die Nummerierung laufe<br />

von 0 bis (anzahl-1).<br />

Entwicklung dieses Algorithmus<br />

und nähere Besprechung s. Vorlesung.<br />

Aufgabe:<br />

Zeichnen Sie ein Struktogramm<br />

zum nebenstehenden Algorithmus.<br />

ALGORITHMUS BubbleSort<br />

ANFANG<br />

VARIABLE nr TYP Ganzzahl<br />

VARIABLE vertauschung TYP Boolesch<br />

KONSTANTE anzahl TYP Ganzzahl WERT . . .<br />

VARIABLE zahlFeld TYP Ganzzahl-FELD[anzahl]<br />

Lies anzahl Zahlen, besetze damit zahlFeld<br />

TUE<br />

vertauschung ← falsch<br />

nr ← 0<br />

SOLANGE nr < anzahl−1 TUE<br />

nr ← nr + 1<br />

WENN zahlFeld[nr-1] < zahlFeld[nr] DANN<br />

Vertausche beide Zahlen<br />

vertauschung ← wahr<br />

SOLANGE vertauschung<br />

Schreibe zahlFeld<br />

ENDE<br />

(2.54) Prinzip der schrittweisen Verfeinerung 〈top-down-design〉: eine Zerlegungsform, die<br />

sich am Ablauf zur Problemlösung orientiert. Je Verfeinerungsstufe wird der Ablauf in kleinere<br />

Handlungseinheiten zerlegt, bis er nur noch aus Elementaralgorithmen besteht (d. h.<br />

Algorithmen, die der Prozessor direkt ausführen kann). In jedem Verfeinerungsschritt können<br />

die drei Algorithmenstrukturen Folge, Auswahl und Wiederholung eingesetzt werden.<br />

↑↑ Für manche Autoren sind ” schrittweise Verfeinerung“ 〈wörtlich: stepwise refinement〉 und<br />

” top-down-design“ nicht synonym.<br />

2.6 Operatoren, logische Verknüpfungen<br />

(2.60) Übb Dieses Unterkapitel befasst sich zunächst mit der Erläuterung von Operatoren (d. h.<br />

Symbole für Funktionen) und ihren Anwendungen (2.61). Danach werden spezielle Ausdrücke<br />

näher betrachtet, und zwar die logischen Ausdrücke oder Aussagen (sie können nur ” Wahr“<br />

oder ” Falsch“ sein). Hierbei werden insbesondere die zugehörigen Operatoren vorgestellt<br />

(2.62ff.). Diese Begriffe werden in den Folgekapiteln auf die Sprache C ++ angewandt, sie werden<br />

dort für das Verständnis unbedingt benötigt.<br />

(2.61)<br />

(a) Ein Operator 〈operator〉 ist ein Symbol für eine Funktion, nämlich für eine eindeutige<br />

Zuordnungsvorschrift, und zwar zwischen einem oder mehreren Elementen, den sog. Operanden,<br />

und dem Ergebniswert.<br />

(b) Ein Ausdruck 〈expression〉 kann bestehen aus:<br />

• einer Konstanten (ohne Operator oder als Operand) oder<br />

• einer Variablen (ohne Operator oder als Operand) oder<br />

• einem Operator einschließlich der zugehörigen Operanden,<br />

dieses rekursiv beliebig tief geschachtelt.<br />

(c) Man unterscheidet die Operatoren z. B. anhand der Anzahl ihrer Operanden:<br />

(1) Ein Operator mit genau einem Operand heißt unär oder monadisch 〈unary〉.<br />

Unterschiedliche Schreibweise in Bezug auf Reihenfolge Operator und Operand (Operatorsymbol<br />

sei ⊛, Operand a):<br />

⊛a präfix 〈prefix〉 Bsp f(a), −a (Vorzeichen als Operator),<br />

a⊛ postfix 〈postfix〉 Bsp<br />

¬p (log. Negation, s. (2.63))<br />

4! (Fakultät)<br />

(2) Ein Operator mit genau zwei Operanden binär oder dyadisch 〈binary〉.<br />

Schreibweise (Operatorsymbol sei ⊛, Operanden a, b):<br />

⊛ab präfix Bsp f(a, b), +(3, 4)<br />

a ⊛ b infix 〈infix〉 Bsp 5 + 6, 4 · 3<br />

ab⊛ postfix Bsp 5 6 +, vgl. manche HP-Taschenrechner ( ” umgekehrte polnische Notation“)


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 31<br />

Manchmal wird ein binärer Operator gar nicht geschrieben, z. B. ab für a · b, oder nur<br />

durch unterschiedliche Schriftart der Operanden dargestellt, z. B. a b für den Operator<br />

Potenziation.<br />

(3) Ein Operator mit genau drei Operanden heißt ternär 〈ternary〉.<br />

(d) Treten mehrere unterschiedliche Operatoren in einem Ausdruck auf, so müssen Prioritäten<br />

oder Hierarchiestufen definiert sein; Operatoren höherer Priorität binden ihre Operanden<br />

stärker als die geringerer Priorität; diese Bindungen können allerdings durch explizite Klammerung<br />

() durchbrochen werden.<br />

Bsp Operatoren verschiedener Hierarchie: 7 + 5 · 3 ( ” Punktrechnung geht vor Strichrechnung“) – dagegen mit<br />

Klammerung anders: (7 + 5) · 3<br />

(e) Beim Auftreten von mehreren Operatoren gleicher Hierarchiestufe (oder der gleichen Operatoren)<br />

nebeneinander muss die Assoziativität (Bindungsrichtung) vereinbart sein,<br />

nämlich die Richtung, in der zusammengefasst werden soll: linksassoziativ (linksbindend),<br />

d. h. von links nach rechts zusammenzufassen (meist), oder auch rechtsassoziativ (rechtsbindend),<br />

d. h. von rechts nach links (seltener, in C ++/C jedoch bei einigen Operator-Hierarchiestufen).<br />

(2.62)<br />

(a) Definition der Begriffe Aussage und Wahrheitswert siehe unter (2.31).<br />

Die Wahrheitswerte WAHR und FALSCH sollen mit W und F abgekürzt werden.<br />

Bsp Aussage? Wahrheitswert, wenn Aussage?<br />

Die Sonne scheint jetzt in <strong>Emden</strong>.<br />

Kein Studierender lernt Informatik.<br />

1 = 2<br />

16 + 3 · 7<br />

Komm bitte her!<br />

Regnet es jetzt?<br />

(a + b) 2 = a 2 + 2ab + b 2<br />

16 > 17<br />

Am 1. 1. 2010 um 12 Uhr scheint in <strong>Emden</strong> die Sonne.<br />

Abkürzung für Aussagen (häufig): lat. Buchstaben (p, q, r, . . .).<br />

(b) Der TYP von Variablen, die einen Wahrheitswert annehmen können, soll logisch“ oder<br />

”<br />

” Boolesch“ genannt werden (nach Boole, brit. Mathematiker, vgl. Begriff Boolesche Alge-<br />

”<br />

bra“, d. h. Algebra mit Booleschen Werten).<br />

(2.63) Angewendet auf Aussagen, gibt es insgesamt vier verschiedene unäre Operatoren.<br />

↑↑ Es gibt zwei verschiedene Ausgangswerte W und F, jeder Wert kann auf zwei verschiedene<br />

Ergebnisse abgebildet werden: 2 2 verschiedene Operatoren.<br />

Interessant – neben der Identität – ist hier nur der Operator Nicht bzw. Verneinung, (logische)<br />

Inversion: er invertiert den Wahrheitswert seines Operanden.<br />

Zeichen: ¬p oder p.<br />

Wahrheitswerttabelle:<br />

p ¬p<br />

W F<br />

F W<br />

(2.64) Angewendet auf Aussagen, gibt es insgesamt 16 verschiedene binäre Operatoren.<br />

↑↑ Es gibt vier verschiedene Ausgangswert-Paare (W,W), (W,F), (F,W), (F,F); jedes Paar kann<br />

auf zwei verschiedene Ergebnisse abgebildet werden: 2 4 verschiedene Operatoren.<br />

(a) Vier dieser Operatoren sind hier wichtig:<br />

• Konjunktion, Und, Zeichen: ∧<br />

• Disjunktion, (inklusives) Oder, Zeichen: ∨<br />

• Antivalenz, exklusives Oder, Zeichen: ≻−≺ oder XOR oder ⊕<br />

• Äquivalenz, Zeichen: ←→


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 32<br />

(b)<br />

Zugehörige Wahrheitswerttabellen:<br />

p q p ∧ q p ∨ q p XOR q p ←→ q<br />

W W W W F W<br />

W F F W W F<br />

F W F W W F<br />

F F F F F W<br />

Die Und-Beziehung ist genau dann WAHR, wenn beide Operanden WAHR sind.<br />

Die (Inklusiv-)Oder-Beziehung ist genau dann FALSCH, wenn beide Operanden FALSCH<br />

sind.<br />

Die Antivalenz- oder Exklusiv-Oder-Beziehung ist genau dann WAHR, wenn beide Operanden<br />

verschiedene Wahrheitswerte haben. In deutsch genauer: entweder . . . oder . . .<br />

Die Äquivalenz-Beziehung ist genau dann WAHR, wenn beide Operanden die gleichen<br />

Wahrheitswerte haben.<br />

(1) Alle vier Operatoren aus (a) sind – soweit ohne Kurzschlussverfahren (b2) – kommutativ<br />

und, wenn ungemischt, assoziativ.<br />

(2) Bei den Operatoren Und und (Inlusiv-)Oder wird in manchen Programmiersprachen das<br />

Kurzschlussverfahren angewendet: in einem Und-Ausdruck bzw. einem Oder-Ausdruck<br />

wird der rechte Operand nur dann ausgewertet, wenn der Ergebniswert nicht schon bereits<br />

durch den Wert des linken Operanden feststeht:<br />

• Bei dem Operator Inklusiv-Oder steht der Ergebniswert fest (W), wenn der erste Operand<br />

W ist; der Wert des zweiten Operanden spielt keine Rolle mehr.<br />

• Bei dem Operator Und steht der Ergebniswert fest (F), wenn der erste Operand F ist;<br />

auch hier spielt der Wert des zweiten Operanden keine Rolle mehr.<br />

In Falle der Ausnutzung des Kurzschlussverfahrens sind daher die beiden Operatoren Und<br />

und Oder nicht kommutativ △! – im Gegensatz zu (b1).<br />

Bsp Die Hierarchiestufen der in diesem Beispiel benutzten Operatoren seien folgendermaßen festgelegt (fallende<br />

Hierarchie, je Zeile mit gleicher Priorität):<br />

/ (Division)<br />

> = = (= sei der Operator für den Test auf Gleichheit)<br />

∧ ∨<br />

Der Ausdruck a = 0 ∧ b/a > 3 ist nur dann immer auswertbar, wenn das Kurzschlussverfahren<br />

garantiert ist, da sonst ggf. eine Division durch Null versucht wird, nämlich bei a = 0 FALSCH.<br />

Der Ausdruck a = 0 ∨ b/a > 3 ist ebenfalls nur bei garantiertem Kurzschlussverfahren immer<br />

auswertbar, da sonst eine Division durch Null (bei a = 0 WAHR) vorkommen würde.<br />

△!<br />

Schwierig wird die Übersicht bei garantiertem Kurzschlussverfahren, wenn der rechte Operand<br />

zusätzliche Wirkungen hat ( Seiteneffekte“, (Kap. 3.3)), z. B. bei folgendem Ausdruck:<br />

”<br />

b > 0 ∧ (liesZahl()< 10)<br />

(liesZahl() lese eine Zahl ein und gebe deren Wert zurück),<br />

da im Falle b > 0 WAHR die Zahl gelesen würde, im Falle FALSCH nicht.<br />

Anm Die Sprachen C ++/C garantieren bei den Operatoren Und und (Inklusiv-)Oder das Kurzschlussverfahren;<br />

bei Pascal darf der Compiler so verfahren, muss es aber nicht.<br />

(c) De-Morgansche Gesetze:<br />

p ∧ q ←→ p ∨ q p ∨ q ←→ p ∧ q<br />

Die Verneinung einer Und-Beziehung wird zur Oder-Beziehung der verneinten Operanden<br />

und umgekehrt.<br />

(d) Vereinfachung von komplexeren Booleschen Ausdrücken: Anwenden von Logik-Regeln (s.<br />

Lehrbücher Formale Logik) oder Karnaugh-Veit-(KV-)Diagramme (s. Digitale Elektronik).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 33<br />

3 Erste Schritte mit C ++<br />

3.0 Überblick<br />

Dieses Kapitel soll einen ersten Einstieg in sehr einfache C ++-Programm bieten. Hier wird begonnen,<br />

die C ++-Syntax zu erläutern, ferner die grundlegenden Datentypen. Das Unterkapitel<br />

3 erläutert mögliche Wirkungen von Ausdrücken zur Laufzeit (Haupteffekt, Seiteneffekt);<br />

die häufige Benutzung dieser Begriffe ist sicher eine Eigenart dieses Kurses (im Gegensatz<br />

zu fast allen Lehrbüchern).<br />

3.1 Die ersten Programme<br />

(3.10) Übb Nach der Vorstellung von zwei sehr einfachen, aber vollständigen C ++-Programmen<br />

wird die formale Seite dieser Programme, nämlich die Syntax, näher beschrieben. Es ist für<br />

Sie später sehr viel einfacher, wenn Sie sich gleich mit dieser Art der Syntax-Beschreibung<br />

(einer modifizierten BNF-Notation, s. (Kap. 0.1)) vertraut machen und anhand der Syntaxbeschreibung<br />

die formale Seite der Beispielprogramme nachvollziehen können. In der Vorlesung<br />

wird dieses noch ausführlicher erläutert als hier angegeben.<br />

(3.11) // Mein erstes Programm in C++ (Beispieldatei D03-11.CPP)<br />

#include <br />

using namespace std;<br />

int main()<br />

{<br />

cout radius >> hoehe;<br />

volumen=radius*radius*pi*hoehe/3;<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 34<br />

(b) 54 ZusammengesetzteAnweisung { Anweisung0..n }<br />

50T eil Anweisung <br />

ZusammengesetzteAnweisung<br />

51 AusdrucksAnweisung [NV] <br />

Variable = Ausdruck ; |<br />

|<br />

C++ cout [- > Variable ]- 1..n ;<br />

|<br />

| AusdrucksAnweisung<br />

|<br />

| DeklarationsAnweisung<br />

Anm1 Semantik des Zuweisungsausdrucks (oben erste Alternative): Der Ausdruck rechts des Zuweisungsoperators<br />

= wird im Wert berechnet und der Variablen links des Operators zugewiesen.<br />

Die Denkrichtung ist demnach hier von rechts nach links – ähnlich wie bei aufgelösten Formeln<br />

in der Mathematik oder den Naturwissenschaften.<br />

Anm2 Die Ausgabe auf Bildschirm geschieht mit einer cout-Anweisung (oben zweite Alternative).<br />

Zur Erzeugung eines Zeilenvorschubs kann als spezieller Ausdruck endl gegeben werden.<br />

↗ Genaueres s. (5.22).<br />

Anm3 Die Eingabe von der Tastatur wird mit einer cin-Anweisung durchgeführt (oben dritte Alternative).<br />

↗ Genaueres s. (5.21).<br />

Beispiele für 57/11usf . DeklarationsAnweisung:<br />

int VarName ;<br />

double NochEinVarName ;<br />

double VarName1, VarName2 ;<br />

Anm4 Durch die obigen DeklarationsAnweisungen wird zur Laufzeit Speicherplatz für eine int-<br />

Variable und für drei double-Variablen unter den angegebenen Namen bereitgestellt. Genaueres<br />

zu den Typen int und double s. (Kap. 3.2).<br />

(c) Name, Bezeichner 〈name, identifier〉 Buchstabe [- Buchstabe |<br />

|<br />

Anm C/C++ unterscheidet Klein- und Großbuchstaben; aufpassen!<br />

|<br />

| Dezimalziffer ]- 0..n<br />

Empf Auch zu Anfang eines Namens zwar Unterstrich erlaubt; besser jedoch nicht, da sonst Kollision<br />

mit compiler-internen Namen möglich.<br />

Buchstabe A<br />

|<br />

| B<br />

|<br />

| C<br />

|<br />

| ... |<br />

| Z<br />

Dezimalziffer 0 |<br />

| 1 |<br />

| 2 |<br />

| ... |<br />

| 9<br />

|<br />

| a<br />

|<br />

| b<br />

|<br />

| ... |<br />

| z<br />

(3.14) Kommentare, werden vom Compiler ignoriert, dienen aber der Übersichtlichkeit für den<br />

menschlichen Leser:<br />

C++ // . . . Zeilenende-Kommentar<br />

C/C++ /* . . . */ allg. Kommentar, auch über mehrere Zeilen oder Zeilenteil<br />

(3.15) Zur Anweisung return 0; am Ende des Programms:<br />

Rücksprung mit Wert 0 (meist die Bedeutung: ” ohne Fehler“) zum programmaufrufenden Prozess,<br />

i. a. zum Betriebssystem. Ein Fehlen wird bei älteren Compilern (fälschlicherweise) ignoriert, bei<br />

[NErl] C++(neu) wird die Anweisung automatisch hinzugefügt, wenn nötig.<br />

3.2 Einfache Datentypen, Zeichenketten, Operatoren<br />

(3.20) Übb (Daten-)Typ: ein Name oder eine Beschreibung für Wertemenge mit zugehörigen<br />

erlaubten Operationen, vgl. Bemerkungen zu TYP in (2.25a2).<br />

In diesem Unterkapitel werden einfache (d. h. nicht zusammengesetzte) Datentypen beschrieben;<br />

wichtig ist es, dass Sie bei den vielen möglichen Typen das Grundprinzip der<br />

Namensbildung und die Bedeutung verstehen, ferner eine Vorstellung erhalten (und sie<br />

behalten) über die zugehörigen Wertebereiche. In diesem Zusammmenhang lohnt es sich,<br />

einen Wiederholungs-Blick auf (Kap. 1.4) zu werfen. Ferner werden hier die wichtigsten Operatoren,<br />

die auf diese Grundtypen angewandt werden können, kurz erläutert. Ohne nähere<br />

Typ-Erläuterung (die erst viel später kommen wird) wird auch die Zeichenketten-Konstante<br />

vorgestellt.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 35<br />

(3.21)<br />

(a) Beipiele für einfache Datentypen:<br />

• Ganzzahlen:<br />

◦ ” Zeichen“, meist als sehr kleine Ganzzahlen:<br />

char<br />

signed char<br />

unsigned char<br />

◦ kleine Ganzzahlen (selten benutzt):<br />

Synonyme: short int, short, signed short, signed short int<br />

Synonyme: unsigned short int, unsigned short<br />

◦ ” normale“ Ganzzahlen:<br />

Synonyme: int, signed int, signed<br />

Synonyme: unsigned int, unsigned<br />

◦ große Ganzzahlen:<br />

Synonyme: long int, long, signed long int, signed long<br />

Synonyme: unsigned long int, unsigned long<br />

◦ △! Achtung bei vorzeichenlosen Typen: große Gefahr (Typumwandlungen, Bereichsüberschreitungen),<br />

vgl. (1.44c)!! ↑↑ Näheres s. (Cpp/Kap. 7).<br />

• Gleitkommazahlen (in aufsteigender Genauigkeit):<br />

◦ float<br />

◦ double (als ” normaler“ Gleitkommatyp)<br />

◦ long double<br />

(b) Speicherbreite, Zahlenbereich<br />

Typ Speicherbreite vorzeichenbehaftet vorzeichenlos<br />

(i. a.) (Annahme Zweierkomplement)<br />

char 1 Byte −128 . . . 127 0 . . . 255<br />

short int, 2 Byte −2 15 . . . 2 15 − 1 0 . . . 2 16 − 1<br />

int (Anm1) (−32 768 . . . 32 767) (0 . . . 65 535)<br />

long int, 4 Byte −2 31 . . . 2 31 − 1 0 . . . 2 32 − 1<br />

int (Anm1) (−2 147 483 648 . . . 2 147 483 647) (0 . . . 4 294 967 295)<br />

Anm1 Der Typ int – als Standard-Ganzzahltyp – ist (i. a.) je nach Implementierung eine 2-Byte-<br />

Zahl (wie oben short int) oder eine 4-Byte-Zahl (wie oben long int).<br />

Anm2 Die oben angegebenen Speicherbreiten sind jeweils die geforderten Mindestbreiten; die Implementierung<br />

darf genauere Darstellungen wählen.<br />

Anm3 signed char ist vorzeichenbehaftet, unsigned char ist vorzeichenlos; char jedoch je nach<br />

Implementation bzw. Prozessor vorzeichenbehaftet (meist) oder vorzeichenlos (seltener). Alle<br />

drei Typen gelten als verschieden.<br />

Bei Compilern für Intelprozessoren ist der Typ char i. a. vorzeichenbehaftet.<br />

Anm4 Die drei char-Typen haben eine gewisse Zwitterstellung: arithmetisch sind sie schmale Ganzzahltypen,<br />

bei Ein-/Ausgabe (mit cin/cout) sind sie Zeichen.<br />

(3.22) GanzzahlKonstante - + <br />

- - opt ZiffF Suffixopt<br />

ZiffF Dezimalziffernfolge<br />

Suffix: u oder U: unsigned, l oder L: long, beides: unsigned long,<br />

fehlend: int, wenn vom Zahlenbereich her passend<br />

↑↑ Genaueres s. (Cpp/7.5)<br />

Interpretation (NUR bei Ganzzahl, nicht bei Gleitkommazahl):<br />

• dezimal, wenn ohne führende Null,<br />

• △! oktal, wenn mit führender Null,<br />

• hexadezimal, wenn mit führendem 0x oder 0X.<br />

(3.23) GleitkommaKonstante <br />

+ <br />

- - - opt - ZiffF.ZiffFopt<br />

<br />

- ExpTeilopt Suffixopt<br />

<br />

.ZiffF<br />

+ <br />

- - - opt ZiffF ExpTeil Suffixopt<br />

|<br />

|


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 36<br />

ExpTeil - e E<br />

<br />

- -<br />

+<br />

-<br />

<br />

- opt ZiffF<br />

Suffix: fehlend: double, f oder F: float, l oder L: long double<br />

(3.24) Operatoren – Begriffe s. (2.61c):<br />

• unär präfix für Ganz- und Gleitkommazahlen:<br />

+ - (Op3ef: Vorzeichenoperatoren)<br />

• binär infix für Ganzzahlen:<br />

* / % (Op5abc: Multiplikation, Division, Divisionsrest)<br />

+ - (Op6ab: Addition, Subtraktion)<br />

• binär infix für Gleitkommazahlen:<br />

* / (Op5ab: Multiplikation, Division)<br />

+ - (Op6ab: Addition, Subtraktion)<br />

Hierarchie: eine kleinere Op-Nummer (z. B. Op5) soll hier stärker als eine größere Nummer<br />

(z. B. Op6) binden; diese Nummern beziehen sich auf die Hierarchiestufen, allg. Übersicht s.<br />

(Cpp/Kap. 2) und (5.11).<br />

Zum Durchbrechen von Zusammenfassungsregeln werden runde Klammern benutzt.<br />

Bsp 5 + 7 ∗ 3 bedeutet: zu 5 wird das Produkt aus 7 und 3 addiert.<br />

(5 + 7) ∗ 3 bedeutet: die Summe 5+7 wird mit 3 multipliziert.<br />

Implizite Typanpassung bei binären Operatoren:<br />

Wenn nicht beide Operanden vom gleichen Typ, so wird einer der Typen in den Typ des<br />

anderen umgewandelt, das Ergebnis ist vom umgewandelten Typ; Umwandlungsrichtung:<br />

Ganzzahl zu Gleitkommazahl, schmalerer Typ zu breiterem Typ.<br />

↑↑ Genauere Regeln s. (Cpp/Kap. 7) – △! bei unsigned-Typen!!<br />

(3.25) ZeichenKonstante ’ Zeichen ’<br />

ZeichenkettenKonstante " Zeichen0..n "<br />

Zeichen: (fast) jedes darstellbare Zeichen (auch <strong>Leer</strong>zeichen), dazu:<br />

\" statt " (insbesondere bei ZeichenkettenKonstante)<br />

\’ statt ’ (insbesondere bei ZeichenKonstante)<br />

\\ statt \<br />

\n als ” NeueZeile-Zeichen“ – unabhängig von betriebssysteminterner Darstellung<br />

dazu einige andere ” Zeichen“<br />

(3.26) Spezialfall der DeklarationsAnweisung:<br />

KonstantenDefinition [NV] const Typ KonstantenName = KonstAusdruck ;<br />

Anm Initialisierung MUSS erfolgen! Spätere Zuweisung nicht möglich – würde der Konstantheit<br />

widersprechen!<br />

(3.27) Zusammengesetzte Zuweisungsoperatoren, s. Op16b-k:<br />

Es gilt: a op= b<br />

ist äquivalent zu: a = a op ( b ) mit op einer der angegebenen binären Operatoren.<br />

3.3 Ausdrücke, Seiteneffekte<br />

(3.30) Übb Dieses Unterkapitel wird Ihnen vermutlich auch nach der (ersten) Erläuterung in der<br />

Vorlesung immer noch schwierig erscheinen. Den Begriff Seiteneffekt finden Sie in fast allen<br />

C ++/C-Lehrbüchern, den Begriff Haupteffekt (als den dazugehörigen anderen Effekt)<br />

jedoch vermutlich nicht. Wenn Sie jedoch begriffen haben, dass ” Haupteffekt“ nur ein anderer<br />

Name für ” Wert (eines Ausdrucks)“ ist und dass ” Seiteneffekt“ jede ggf. vorhandene<br />

zusätzliche Wirkung zur Laufzeit ist, dann werden Sie erkennen, dass viele Besonderheiten<br />

der Sprachen C ++/C sehr einfach beschreibbar sind, die sonst – ohne diese Begriffe –<br />

wesentlich umständlicher zu erläutern sind.<br />

↗ Einfachere Beschreibung (wenn ” Haupteffekt“ und ” Seiteneffekt“ verstanden sind) z. B. an<br />

folgenden Stellen (bitte noch nicht beim ersten Lesen nachverfolgen, jedoch beim späteren<br />

Wiederholen sicher lohnend!): (2.64b2 Bsp, 3.32b,d,e, 4.31c, 4.43, 5.12, 5.13, 5.21 Anm3, 5.22 Anm3, 5.24d,<br />

5.25c) und weitere Stellen.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 37<br />

(3.31) In C ++/C ist ein Ausdruck definiert durch (Definition nicht vollständig, vgl. (2.61b)):<br />

(3.32) Bsp<br />

Ausdruck <br />

Konstante<br />

|<br />

| Variable<br />

( Ausdruck ) |<br />

|<br />

|<br />

FunktionsAufruf (7.12) |<br />

UnärPräfixOperator Ausdruck<br />

Ausdruck BinärOperator Ausdruck<br />

|<br />

|<br />

|<br />

| Ausdruck UnärPostfixOperator<br />

Bei Ausdrücken sind (häufig) zwei verschiedene Wirkungen zu unterscheiden:<br />

• Haupteffekt – als anderes Wort für Wert des Ausdrucks (fast immer vorhanden).<br />

Bsp Ausdrücke mit Haupteffekt (Wert), aber ohne Seiteneffekt (i sei eine initialisierte Variable):<br />

34<br />

i<br />

(i-17)/5<br />

• Seiteneffekt, Nebeneffekt – sonstige Wirkung(en) des Ausdrucks (nicht immer vorh.),<br />

z. B. Wertänderung Variable, Ausgabe Bildschirm, Einlesen, Aufruf Funktion.<br />

Der Begriff ” Seiten. . .“ bedeutet nicht, dass es sich hierbei um unwichtige, nicht gewollte<br />

Wirkungen handelt; nein, wenn Ausdrücke Seiteneffekte haben, sind diese gewollt!<br />

Bsp Ausdrücke mit Seiteneffekt (die durch ein nachgestelltes Semikolon zu einer Ausdrucksanweisung<br />

(3.32a) gemacht werden können):<br />

cout > i (Eingabe)<br />

(a) 51 AusdrucksAnweisung Ausdruckopt ;<br />

• Haupteffekt unwichtig (wird verworfen),<br />

daher Ausdrucksanweisung i. a. nur sinnvoll, wenn Seiteneffekt vorhanden (häufig: Zuweisungsausdruck,<br />

Funktionsaufruf),<br />

andere Benutzung: als <strong>Leer</strong>anweisung (Ausdruck fehlend), wenn Anweisung syntaktisch gefordert,<br />

aber nichts zu tun ist.<br />

(b) ZuweisungsAusdruck Variable = Ausdruck ( = Op16a )<br />

• Haupteffekt: Wert (und Typ) der Variablen nach der Zuweisung,<br />

• Seiteneffekt: Variable erhält Wert des Ausdrucks.<br />

(c) ZuweisungsAnweisung Variable = Ausdruck ;<br />

(Spezialfall der 51 AusdrucksAnweisung)<br />

• Haupteffekt (der gesamten Zuweisung) unwichtig,<br />

• Seiteneffekt wichtig: Wertzuweisung.<br />

(d) Zuweisungskette: Var1 = Var2 = Var3 = 25 ;<br />

Assoziativität von rechts nach links (Op16), daher Interpretation:<br />

Var1 = (Var2 = (Var3 = 25)) ;<br />

Beim inneren Zuweisungsausdruck Var3 = 25 ist auch der Haupteffekt wichtig, da er den Ausdruck-<br />

Wert für die Zuweisung an Var2 darstellt; der Haupteffekt des Zuweisungsausdrucks Var2 = (. . . )<br />

ist der Ausdruck-Wert für die Zuweisung an Var1. Nur der Haupteffekt des Zuweisungsausdrucks<br />

Var1 = (. . . ) ist unwichtig, er wird verworfen.<br />

(e) Inkrement-, Dekrementoperator (unär präfix Op3ab und postfix Op2fg): ++ --<br />

• Seiteneffekt: Wertänderung des Operanden um +1 bzw. -1 – unabhängig, ob präfix oder<br />

postfix!!<br />

• Haupteffekt (hierbei streng Präfix- und Postfixstellung zu unterscheiden!!):<br />

◦ postfix: alter Wert des Operanden,<br />

◦ präfix: neuer Wert des Operanden.<br />

(3.33) △! Der Zeitpunkt der Ausführung von Seiteneffekten ist nicht festgelegt (implementationsabhängig)!!<br />

Sicher sind alle Seiteneffekte abgearbeitet, wenn Anweisung beendet<br />

ist.<br />

△! Keine Ausdrücke formulieren, deren Wert vom Zeitpunkt bzw. der Reihenfolge der<br />

Ausführung von Seiteneffekten abhängt!<br />

|<br />

|


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 38<br />

4 Algorithmenstrukturen in C ++<br />

4.0 Überblick<br />

Die in (Kap. 2) sprachunabhängig eingeführten drei wichtigen Algorithmus-Grundstrukturen<br />

werden hier auf die Sprache C ++ übertragen. Sie lernen hier alle durch C ++ unterstützten<br />

Formen von Folge, Auswahl und Wiederholung kennen. Ohne dieses Rüstzeug ist es<br />

kaum möglich, ein C ++-Programm zu schreiben.<br />

Nach einem Beipielprogramm werden dann in Unterkapitel 7 äußerst wichtige Grundregeln<br />

für das Schreiben von Programmen erläutert, die das Lesen beträchtlich erleichtern, nämlich<br />

Regeln für das Erzeugen von Namen und das Lay-out. Diese Regeln werden den ganzen<br />

Kurs über und auch für mögliche Folgekurse Gültigkeit haben.<br />

4.1 Folge (Sequenz), Gültigkeitsbereich in Blöcken<br />

(4.10) Übb Es wird der Begriff Block im Sinne einer zusammengesetzten Anweisung eingeführt.<br />

Das zugehörige geschweifte Klammernpaar ist in C ++/C sehr häufig anzutreffen. In diesem<br />

Zusammenhang werden weitere Begriffe (Gültigkeitsbereich, Verdeckung und lokale Namen)<br />

erläutert; ausführlicher wird dieses später in (7.15) geschehen.<br />

(4.11) Die (schachtelungsfähige) Folge (Kap. 2.2) wird in C ++ durch den Block verwirklicht:<br />

54 ZusammengesetzteAnweisung (oder Verbundanweisung, Block) C++ <br />

{Anweisung0..n}<br />

(4.12) Innerhalb eines Blocks deklarierte Namen werden lokale Namen genannt. Ihr Gültigkeitsbereich<br />

〈scope〉 (Bereich, in dem dieser Name existiert), erstreckt sich vom Deklarationspunkt<br />

bis zum Ende dieses Blocks.<br />

Ein Name kann in untergeordneten Blöcken verdeckt werden, indem er dort neu deklarariert<br />

wird; nach Austritt aus einem solchen untergeordneten Block ist der Name in der alten<br />

Bedeutung (bei Variablen: auch mit altem Wert) wieder verfügbar.<br />

↗ Einzelheiten s. (7.15).<br />

(4.13) Lokale Variable (Variable, die innerhalb eines Blocks definiert werden z. B. innerhalb von<br />

main()) haben meist keinen bestimmten Anfangswert, sondern ein ” zufälliges“ Bitmuster.<br />

Daher müssen solche Variable vor einem lesenden Zugtiff einen definierten Wert erhalten.<br />

↗ Genauer: eigentlich sind oben automatische Variable (8.13) gemeint. Ausnahmen sind Objekte<br />

mit sinnvoll gebauten Konstruktoren (9.21), ferner statische Variable (8.12).<br />

4.2 Boolesche Ausdrücke, Datentyp bool<br />

(4.20) Übb Im Gegensatz zu C und C++(alt) gibt es in C++(neu) den Booleschen Datentyp; er<br />

hat nur zwei möglich Werte, nämlich WAHR und FALSCH. Sie sollten diesen Typ viel<br />

benutzen, wenn nur zwei verschiedene Werte möglich sind, und nicht den Ersatztyp int für<br />

C-Programmierer.<br />

Die Boolschen Ausdrücke und ihre Verknüpfungen treten häufig auf; sie sind zur Formulierung<br />

von Auswahl- und Wiederholungsanweisungen nötig.<br />

(4.21) Datentypen für Bedingung bzw. Aussage, vgl. (2.31, 2.62).<br />

(a) C++(neu) Datentyp bool; der Wertevorrat besteht nur aus den beiden Konstanten true und<br />

false.<br />

Wenn nötig, geschieht eine implizite Umwandlung eines arithmetischen Ausdrucks in bool,<br />

und zwar ein Wert ungleich 0 in true und ein Wert gleich 0 in false, vgl. (4.23).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 39<br />

Umgekehrt wird ein Ausdruck des Typs bool, wenn nötig, in eine Ganzzahl des Typs int<br />

umgewandelt, und zwar true in 1 und false in 0.<br />

(b) Dagegen C C++(alt) : Ein Boolescher Ausdruck hat den Typ int, und zwar bei WAHR den<br />

Wert 1, bei FALSCH den Wert 0.<br />

(4.22) C/C++ : Verknüpfung von numerischen Ausdrücken zu Booleschen Ausdrücken:<br />

Op8: NumAusdruck < NumAusdruck NumAusdruck NumAusdruck NumAusdruck >= NumAusdruck<br />

Op9: NumAusdruck == NumAusdruck NumAusdruck != NumAusdruck<br />

(== Test auf Gleichheit) (!= Test auf Ungleichheit)<br />

△! Ein ” beliebter“ Fehler ist die Verwechselung der beiden Operatoren = und ==:<br />

Op16a: = Operator für Zuweisung (und auch häufig für Initialisierung)<br />

Op9a: == Operator für Test auf Gleichheit<br />

(4.23) C/C++ : Jeder numerische Ausdruck kann logisch (Boolesch), d. h. als Bedingung interpretiert<br />

werden; ein Wert ungleich 0 gilt als WAHR, ein Wert gleich 0 als FALSCH.<br />

(4.24) C/C++ : Boolesche Werte (und logisch zu bewertende numerische Ausdrücke) können weiter<br />

zu Booleschen Ausdrücken kombiniert werden (2.63, 2.64):<br />

Op13: BoolAusdruck1 && BoolAusdruck2 logisches Und<br />

Op14: BoolAusdruck1 || BoolAusdruck2 logisches Inklusiv-Oder<br />

Op3d: ! BoolAusdruck logische Negation<br />

Zu den beiden binären Operatoren && und || (Op13/14):<br />

Anm1 Hierbei ist die Berechnung nach dem Kurzschlussverfahren garantiert, d. h. der rechte Ausdruck<br />

BoolAusdruck2 wird nur dann bewertet, wenn der Wert des Gesamtausdrucks nicht<br />

schon durch die Bewertung des linken Ausdrucks BoolAusdruck1 feststeht. Wegen des Kurzschlussverfahrens<br />

sind diese beiden Operatoren nicht kommutativ; ohne Kurzschlussverfahren<br />

wären sie dagegen kommutativ. Vgl. auch die allgemeine Erörterung in (2.64b2).<br />

Anm2 Ausnahme zu (3.33): Es ist garantiert, dass auch sämtliche Seiteneffekte von BoolAusdruck1<br />

abgearbeitet sind, bevor – gegebenenfalls – BoolAusdruck2 bewertet wird.<br />

(4.25) Ein Ausdruck beispielsweise der Form<br />

12 < x


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 40<br />

Anm1 Eine 99 Bedingung kann jeder Boolesche (logische) Ausdruck sein; zusätzliche Möglichkeit s.<br />

(4.51).<br />

Anm2 Als 50 Anweisung kann wiederum jede Anweisung genommen werden: beliebige Schachtelung<br />

– vgl. Kapitel 2.5! Zur Auflösung von Mehrdeutigkeiten s. a. (b).<br />

Anm3 Bitte beachten: Anweisung steht im Singular, d. h. es muss (jeweils) genau ein Anweisung<br />

stehen.<br />

• Falls mehrere Anweisungen benötigt werden, werden sie zu einem Block (4.11) umschlossen;<br />

dieser Block wirkt syntaktisch als eine Anweisung.<br />

• Falls keine Anweisung benötigt wird, ist eine <strong>Leer</strong>anweisung zu nehmen, z. B.:<br />

; // Ausdrucksanweisung ohne Ausdruck (3.32a)<br />

{ } // Block mit null Anweisungen (4.11)<br />

(b) Schachtelt man ein- und zweiseitige Auswahl, so wird in der textuellen Form die Zugehörigkeit<br />

des SONST-Zweiges durch Einrückung unterschieden: bei Version A gehört der SONST-<br />

Zweig zum äußeren WENN, bei Version B zum inneren WENN:<br />

// Version A<br />

WENN Bedingung-Außen DANN<br />

WENN Bedingung-Innen DANN<br />

Dann-Anweisung-Innen<br />

SONST<br />

Sonst-Anweisung-Außen<br />

// Version B<br />

WENN Bedingung-Außen DANN<br />

WENN Bedingung-Innen DANN<br />

Dann-Anweisung-Innen<br />

SONST<br />

Sonst-Anweisung-Innen<br />

Da der C ++-Compiler jedoch unabhängig vom Lay-out arbeitet, wird die Mehrdeutigkeit<br />

durch folgende Regel aufgelöst:<br />

Ein else gehört immer zum letzten if, welches selbst noch kein else hatte.<br />

△! Daher wird – durch den C ++-Compiler – Version A genauso interpretiert wie Version<br />

B: der SONST-Zweig gehört zum inneren WENN. Um dem C ++-Compiler die Interpretation<br />

entsprechend dem Lay-out von Version A aufzuzwingen, muss die innere einseitige Auswahl<br />

maskiert werden, so dass sie formal nach außen nicht wie eine Auswahl wirkt:<br />

// Version C – nötig für C ++-Compiler<br />

WENN Bedingung-Außen DANN<br />

ANWEISUNGSKLAMMER<br />

WENN Bedingung-Innen DANN<br />

Dann-Anweisung-Innen<br />

ENDE ANWEISUNGSKLAMMER<br />

SONST<br />

Sonst-Anweisung-Außen<br />

Diese Klammerung von Anweisungen geschieht in C ++ durch die ZusammengesetzteAnweisung<br />

(4.11), d. h. durch Einschließen in ein Paar geschweifter Klammern { }.<br />

(c) △! Als Bedingung ist hier wegen seines Haupteffektes (3.32b) auch ein Zuweisungsausdruck<br />

erlaubt; davon wird für Anfänger dringend abgeraten. Meist wird unabsichtlich eine<br />

Zuweisung statt eines Vergleichs ( ” =“ statt ” ==“) geschrieben.<br />

Bsp int i; cin >> i;<br />

if (i = -1) cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 41<br />

- default : Anweisung1..n<br />

<br />

- opt<br />

}<br />

Semantik:<br />

• Zuerst Bewertung Ausdruck,<br />

• dann Sprung zur case-Marke mit gleichem Wert, wenn vorhanden,<br />

• sonst Sprung zur Marke default, wenn vorhanden,<br />

• sonst sofort Fortsetzung hinter switch-Anweisung.<br />

△! Die switch-Anweisung ist in C ++ und in C NUR ein Sprungschalter zum Hineinspringen,<br />

nicht zum Hinausspringen. Zur Nachbildung einer Mehrfachauswahl darf die<br />

Heraussprung-Anweisung 53 break; nicht vergessen werden!! Näheres zu dieser Anweisung<br />

s. (4.44a).<br />

Bsp Herausfiltern der Zahlen 0 bis 3 und sonstiger Eingaben:<br />

int zahl;<br />

cin >> zahl;<br />

switch (zahl) {<br />

case 1: cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 42<br />

◦ Haupteffekt unwichtig,<br />

◦ sinnvoll daher nur, wenn Seiteneffekt vorhanden, z. B. Zuweisung,<br />

◦ wird genau einmal zu Beginn durchgeführt,<br />

◦ darf fehlen.<br />

• Danach Test von Bedingung2 (Einsprungbedingung)<br />

◦ Haupteffekt wichtig:<br />

bei Wert true Einsprung in Schleifenanweisung Anweisung (kopfgesteuerte Schleife!),<br />

sonst Verlassen der for-Anweisung,<br />

◦ fehlend: Bedeutung true (Endlosschleife).<br />

• Nach jedem Schleifendurchlauf Bewertung von Ausdruck3 (Reinitialisierung)<br />

◦ Haupteffekt unwichtig,<br />

◦ sinnvoll daher nur bei Seiteneffekt,<br />

◦ wird immer nach jedem Schleifendurchlauf bewertet, ehe (erneut) Bedingung2 geprüft<br />

wird,<br />

◦ darf fehlen.<br />

Anm1 Eine for-Anweisung ist daher äquivalent zu folgendem Konstrukt (Ausnahme nur bei Benutzung<br />

der Anweisung continue;, s. (4.44b)):<br />

Ausdruck1 ;<br />

while ( Bedingung2 ) {<br />

Anweisung<br />

Ausdruck3 ;<br />

}<br />

Anm2 Erweiterung für Ausdruck1 s. (4.51c): EinfacheDeklaration.<br />

Bsp Programmfragment für die zweimalige Ausgabe der Zahlen 0 bis 99:<br />

int i;<br />

for (i=0; i


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 43<br />

(4.51)<br />

(a) C/C++ Bei der 60 if-Anweisung und bei allen drei 56 Wiederholungsanweisungen ist für Bedingung<br />

bzw. Ausdruck als Einstiegsbedingung ein BoolescherAusdruck oder ein logisch zu<br />

bewertender NumerischerAusdruck (4.23) einsetzbar.<br />

(b) C++(neu) Bei den Anweisungen<br />

• 60 if-Anweisung,<br />

• 61 switch-Anweisung,<br />

• 70 while-Anweisung,<br />

• 72 for-Anweisung,<br />

d. h. bei allen Kontrollstrukuren außer bei der 71 do-while-Anweisung gibt es eine weitere<br />

Möglichkeit, die 99 Bedingung:<br />

TypSpezifizierer1..n Deklarator = Ausdruck<br />

– mit anderen Worten eine Definition einer Variablen mit Initialisierung.<br />

Der Gültigkeitsbereich dieser Variable ist der Bereich nur innerhalb der Kontrollstruktur.<br />

Diese Variablendefinition ist bei der 71 do-while-Anweisung nicht erlaubt; eine solche Möglichkeit<br />

wäre auch sinnlos, da die Definition nach der Benutzung erfolgen müsste.<br />

(c) C++ Zusätzlich ist als Initialisierungsteil einer for-Anweisung eine 11 EinfacheDeklaration<br />

erlaubt.<br />

4.6 Beispiel<br />

△! Der Gültigkeitsbereich dieser Variablen ist je nach C++(alt) oder C++(neu) verschieden:<br />

• C++(alt) normale Einführung einer Variablen, Gültigkeitsbereich auch hinter der Kontrollstruktur,<br />

• C++(neu) Gültigkeitsbereich nur innerhalb der Kontrollstruktur, vgl. (Str3/Kap. 6.3.3.1).<br />

△! Der Compiler Microsoft Visual C ++ 6.0 gilt in diesem Fall – wohl ausnahmsweise – als<br />

C++(alt) .<br />

Bei C++(alt) ist eine Nachahmung des neuen Verhaltens einfach möglich durch Eingrenzen<br />

der gesamten Kontrollstruktur in einen Block.<br />

(4.60) Übb Es lohnt sich, das Beispielprogramm genauer anzusehen und den Algorithmus Zeile<br />

für Zeile mit dem Pseudocode zu vergleichen (s. Aufg. 1 in den Übungen zu Kap. 2,<br />

auch im Internet verfügbar). Wenn Sie das Programm ausprobieren wollen: wie alle Skript-<br />

Beispielprogramme ist auch dieses im Internet verfügbar.<br />

(4.61) // Gregorianischer Kalender (Beispieldatei D04-61.CPP)<br />

// s. Übungen zu Kap. 2 (Algorithmen), Aufg. 1<br />

// Eingabe als Ganz-Zahlen: Tag Monat Jahr<br />

#include <br />

using namespace std;<br />

int main()<br />

{<br />

int tag, monat, jahr,<br />

jahrH, jahrR, hilf, woTagZahl;<br />

cin >> tag >> monat >> jahr;<br />

if (monat < 3) {<br />

monat += 10;<br />

--jahr;<br />

}<br />

else monat -= 2;<br />

jahrH = jahr/100;<br />

jahrR = jahr%100;


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 44<br />

}<br />

hilf = tag + jahrR - 2*jahrH;<br />

hilf += (13*monat-1)/5;<br />

hilf += jahrR/4 + jahrH/4;<br />

while (hilf < 0) hilf += 7;<br />

woTagZahl = hilf%7;<br />

switch (woTagZahl) {<br />

case 0: cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 45<br />

(4.72) Symbolische Konstanten sind für eine gute Programmierung sehr wichtig, da sie ” magische<br />

Zahlen“ vermeiden. Magische Zahlen verschleiern ihre eigene Bedeutung, selbstdokumentierende<br />

Namen dagegen betonen sie.<br />

Bsp Nicht jeder ahnt bei einem Auftreten der Konstanten 6.28, dass vielleicht 2π gemeint sein könnte; bei<br />

zweiPi ist dieses jedoch klar. Ein späteres Einsetzen eines genaueren Zahlenwertes für 6.28 ist fast<br />

unmöglich, bei der symbolischen Konstante jedoch ganz einfach.<br />

Symbolische Konstanten werden am besten mit einer Konstantendefinition (const . . . (3.26))<br />

eingeführt. Makros (5.72a) sollten vermieden werden; Aufzählungskonstanten (12.21) dagegen<br />

können manchmal gut eingesetzt werden, s. a. Zusammenstellung in (10.11).<br />

Zu symbolischen Konstanten als Arraygrenzen s. (5.44).<br />

(4.73) Empf Lay-out, Textgestaltung, vgl. (2.51) – für Sie ein Muss! –<br />

(a) Allgemeine Regeln für das Lay-out:<br />

• Ein gutes Lay-out ist für den Compiler unwichtig, es ist jedoch überlebenswichtig für<br />

den Programmierer! Sinn: Übersichtlichkeit!<br />

• Jede Anweisung in einer eigenen Zeile mit passender Einrückung.<br />

• Wenn die Anweisung zu lang für eine Zeile ist: Fortsetzung einrücken;<br />

Ausnahme: else nicht einrücken, sondern unter zugehöriges if positionieren, auch sogar<br />

mehrfach bei if-else-if-Kette.<br />

• Sehr problematisch ist die do-while-Schleife; das while sollte nicht am Zeilenanfang<br />

stehen, da sonst sehr leicht Verwechselung mit while-Anweisung, vgl. (4.42Anm2).<br />

• Einrückungen: je Stufe 3 bis 4 Spalten (Zeichenpositionen), später mit größerer Erfahrung<br />

nur 2 Spalten.<br />

• Block (ZusammengesetzteAnweisung):<br />

ÜbergeordneterText {<br />

//......<br />

//......<br />

}<br />

Öffnende Klammer: am Zeilen-Ende von ÜbergeordneterText,<br />

schließende Klammer: am Zeilen-Anfang in gleicher Position wie Anfang von ÜbergeordneterText.<br />

Ausnahme: Funktionsblöcke, z. B. main(): öffnende Klammer am Zeilenanfang der Folgezeile.<br />

(b) Bsp<br />

Legende: (...) Bedingung (bei for ausführlicherer Inhalt),<br />

Anweisung beliebige Anweisung, z. B. Ausdrucksanweisung (3.32a) oder<br />

auch ein (geschachtelter) Block (4.11)<br />

Einrückung je Schachtelungsebene hier jeweils 3 Spalten (3 Zeichenpositionen).<br />

int main()<br />

{<br />

int a, b, c;<br />

double d;<br />

// einseitige Auswahl<br />

if (...) Anweisung<br />

if (...)<br />

Anweisung<br />

if (...) {<br />

Anweisung<br />

Anweisung<br />

}<br />

// zweiseitige Auswahl<br />

if (...) Anweisung<br />

else Anweisung<br />

if (...) {


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 46<br />

}<br />

Anweisung<br />

Anweisung<br />

}<br />

else {<br />

Anweisung<br />

Anweisung<br />

}<br />

// if-else-if-Kette<br />

// (log. Struktur eigentl. anders, s. (2.36))<br />

if (...)<br />

Anweisung<br />

else if (...)<br />

Anweisung<br />

else if (...)<br />

Anweisung<br />

else<br />

Anweisung<br />

// switch-Anweisung<br />

switch (...) {<br />

case ...: Anweisung break;<br />

case ...: // no break<br />

case ...: Anweisung<br />

Anweisung<br />

break;<br />

default: Anweisung<br />

}<br />

// while-Schleife<br />

while (...) Anweisung<br />

while (...)<br />

Anweisung<br />

while (...) {<br />

Anweisung<br />

Anweisung<br />

}<br />

// for-Schleife<br />

for (....) Anweisung<br />

for (....)<br />

Anweisung<br />

for (....) {<br />

Anweisung<br />

Anweisung<br />

}<br />

// do-while-Schleife (kann problematisch sein)<br />

// wichtig: "while" nicht am Zeilenanfang!<br />

do Anweisung while (...);<br />

do {<br />

Anweisung // sollte ein Block sein ...<br />

Anweisung // ... selbst wenn nur eine Anweisung<br />

} while (...);<br />

return 0;


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 47<br />

5 Ein- und Ausgabe, Typ Array und String, Ergänzungen<br />

5.0 Überblick<br />

Dieses Kapitel stellt eine Sammlung sehr unterschiedlicher Themen dar, die besprochen werden<br />

müssen, bevor in den Folgekapiteln (ab Kap. 6) verschiedene Programmierstile erläutert<br />

werden.<br />

Zunächst werden die Operatoren als Gesamtheit vorgestellt (Unterkap. 1); die zugehörige<br />

Tabelle sollte häufig – auch später noch – zu Rate gezogen werden!<br />

Unterkap. 2 erläutert die äußerst wichtigen Eigenschaften der sog. Streams ( ” Ströme“). Mit<br />

Hilfe dieser Ströme geschieht in C ++ die Eingabe und Ausgabe. Da wohl kaum ein Programm<br />

ohne Ein- oder Ausgabe auskommt, ist die Beschäftigung hiermit notwendig.<br />

Anm Eine Anwendung der Ein- oder Ausgabefunktionen aus C (5.21Anm4, 5.22Anm4) wird in diesem<br />

Kurs nicht geduldet, da sie sehr fehleranfällig ist, da zum anderen die C ++-Streams sehr<br />

einfach zu benutzen sind, sobald man das Verhalten verstanden hat.<br />

Das Verallgemeinern des Strom-Konzepts auf den Umgang mit Textdateien wird in Unterkap.<br />

3 vorgestellt.<br />

Der in naturwissenschaftlich-technischen Anwendungen häufig anzutreffende Arraytyp wird<br />

in Unterkap. 4 eingeführt und im Folge-Unterkapitel auf Strings (Zeichenfolgen) angewendet.<br />

Eine Kurzeinführung in Typen und insbesondere ihren Umwandlungen ineinander stellt<br />

Unterkap. 6 dar; eine Nichtbeachtung solcher Regeln kann manchmal überraschende und<br />

schwierig eingrenzbare Fehler bewirken.<br />

In Unterkap. 7 wird das Präprozessor-Konzept erläutert, das in C sehr viel, in C ++ weniger<br />

benutzt wird. Jedoch gibt es einige auch in C ++ sehr wichtige Anwendungsfälle.<br />

5.1 Übersicht Operatoren<br />

(5.10) Übb Hier werden sämtliche Operatoren von C ++/C zusammengefasst. Sie werden diese Tabelle<br />

sicher auch später noch öfters nachschlagen. Für Klausuren gibt es im Skript ” Sprache<br />

C ++“ (Kap. 0.2) eine Kurzfassung dieser Tabelle. Wichtig ist es, dass Sie anhand der Tabelle<br />

die grundlegende Operatoren-Struktur (die in einer solchen Reichhaltigkeit kaum in anderen<br />

Sprachen verfügbar ist) erlernen und behalten.<br />

Danach werden zwei bisher noch nicht beprochene Operatoren vorgestellt: Bedingungsoperator<br />

und Kommaoperator. Diese beiden Operatoren werden je nach eigenem Programmierstil<br />

sehr selten oder auch häufiger benutzt. Man kann sehr häufig auf sie verzichten, sie können<br />

jedoch ein Programm übersichtlicher machen. Die Kenntnis, wie der Kommaoperators in<br />

einem for-Schleifenkopf (5.13 Bsp) benutzt werden kann, ist obligatorisch.<br />

(5.11) Auflistung aller C ++-Operatoren mit Verweisen, falls sie in dieser Vorlesung besprochen<br />

werden (nicht hier besprochen: ↑↑)<br />

Operatorstufen 1 bis 17: fallende Hierarchie (2.61d)<br />

– ein Durchbrechen der Hierarchie ist durch runde Klammern ( ) möglich –<br />

Assoziativität (2.61e): linksassoziativ −→;<br />

Ausnahmen ( ” @“, Stufen 3, 15, 16): rechtsassoziativ ←−


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 48<br />

Stufe Operator Bedeutung Verweis<br />

1 a :: C++ unär präfix: global (7.52)<br />

b :: C++ binär: Bereichsauflösung (9.12a)<br />

2 a [ ] Index (5.41)<br />

b ( ) Funktionsaufruf (7.12)<br />

c ( ) C++ Umw. EinfTypName (5.64b)<br />

d . Elementzugriff (5.23a, 9.13)<br />

e -> Elementzugriff über Zeiger (10.29)<br />

fg ++ -- Postfix-Inkrement/Dekrem. (3.32e)<br />

h static cast() C++(neu) Typumwandlung (5.64a)<br />

i dynamic cast() C++(neu) Typumwandlung ↑↑<br />

j const cast() C++(neu) Typumwandlung ↑↑<br />

k reinterpret cast() C++(neu) Typumwandlung (10.24 Anm4),<br />

(12.63, 5.64)<br />

l typeid C++(neu) Typidentifizierung ↑↑<br />

3@ ab ++ -- Präfix-Inkrement/Dekrem. (3.32e)<br />

unär c ∼ Bitinversion (12.11)<br />

präfix d ! logische Negation (4.24)<br />

ef + - Vorzeichen (3.24)<br />

g & Adresse (10.21b)<br />

h * Dereferenzierung (10.21b)<br />

i ( ) Typumwandlung (5.64c)<br />

j sizeof Speichergröße (5.14, 12.11)<br />

kl new delete C++ Freispeicher (10.31ff.)<br />

4 ab ->* .* C++ Elementzugriff ↑↑<br />

5 a * Multiplikation (3.24)<br />

b / Division (3.24)<br />

c % Teilerrest (3.24)<br />

6 ab + - Addition, Subtraktion (3.24)<br />

7 ab > Bitverschiebung (12.11)<br />

Streams: Ausgabe, Eingabe (5.22, 5.21)<br />

8 abcd < >= Vergleich (4.22)<br />

9 ab == != (Un-)Gleichheit (4.22)<br />

10 & bitw. Und (12.11)<br />

11 ∧ bitw. Exklusiv-Oder (12.11)<br />

12 | bitw. Inklusiv-Oder (12.11, 5.33b)<br />

13 && logisches Und (4.24)<br />

14 || logisches Inklusiv-Oder (4.24)<br />

15@ ? : Bedingungsoperator (5.12)<br />

ternär<br />

16@ a = (einfache) Zuweisung (3.13b)<br />

bcde *= /= %= += zusammenges. Zuweisung (3.27)<br />

fgh -= >>=


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 49<br />

↑↑2 Manchmal wird auch throw als Operator aufgeführt (für die sog. Ausnahmebehandlung),<br />

vgl. (Str3/Kap. 6.2).<br />

(5.12) Der Bedingungsoperator (Op15) ist die Übertragung einer zweiseitigen Auswahl (2.32),<br />

(4.31) auf Ausdrücke:<br />

Ausdruck1 ? Ausdruck2 : Ausdruck3<br />

(entspricht:) WENN Ausdruck1 DANN Ausdruck2 SONST Ausdruck3<br />

Semantik:<br />

• Zunächst wird Ausdruck1 im Booleschen Sinn bewertet (Kap. 4.2), dazu werden auch<br />

sämtliche ggf. vorhandene Seiteneffekte (3.31) von Ausdruck1 ausgeführt,<br />

• dann:<br />

◦ entweder Bewertung von Ausdruck2 (wenn Ausdruck1 WAHR)<br />

◦ oder Bewertung von Ausdruck3 (wenn Ausdruck1 FALSCH),<br />

• Haupteffekt des Gesamtausdrucks ist entweder Ausdruck2 oder Ausdruck3 – je nachdem,<br />

welcher Ausdruck bewertet wurde.<br />

Empf Da Ausdruck1 Boolesch bewertet wird, kann man ihn zur Erinnerung daran in Klammern<br />

( ) schreiben.<br />

Bsp Zuweisung des Betrags von i an k: k = (i>=0) ? i : -i;<br />

(5.13) Kommaoperator (Op17): Ausdruck1 , Ausdruck2<br />

Semantik:<br />

• Zunächst Bewertung von Ausdruck1 einschließlich Durchführung sämtlicher zugehöriger<br />

Seiteneffekte,<br />

• dann Bewertung Ausdruck2,<br />

• Haupteffekt des Gesamtausdrucks: Ausdruck2.<br />

Anwendung: Wenn mehrere Ausdrücke (mit Seiteneffekten) benötigt werden, wo syntaktisch<br />

nur einer erlaubt ist.<br />

Bsp Häufig wird dieser Operator in der for-Anweisung (4.43) benutzt, z. B. wenn mehrere Schleifenvariable<br />

benötigt werden:<br />

int i,j;<br />

for (i=0,j=10; j>0; ++i,--j) ...<br />

Andere Anwendung: (5.26d Abwandlung).<br />

(5.14) Operator sizeof (Op3j)<br />

Dieser Operator gibt die Speichergröße (in Bytes) seines Operanden zurück. Er kann auf<br />

zwei Arten benutzt werden:<br />

sizeof(Typ)<br />

sizeof(Ausdruck)<br />

Es wird berechnet: die Speichergröße, die ein Element des Typs Typ benötigt, bzw. die<br />

Speichergröße, die Ausdruck beim Abspeichern hätte – nicht der Wert von Ausdruck.<br />

Bsp – siehe auch (5.55b) und (12.63a)<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 50<br />

5.2 Standardstreams, Fehlerbehandlung<br />

– siehe auch (Cpp/Kap. 3) –<br />

(5.20) Übb Dieses Unterkapitel führt genauer in die Welt der Streams ( ” Ströme“) ein. Daher<br />

wird es in der Vorlesung ausführlich erläutert. Eine genaue Kenntnis der hier abgehandelten<br />

Punkte ist für die Programmierung unerlässlich. Sie lernen die genaueren Eigenschaften<br />

der Ausgabe- und Eingabestromobjekte kennen (5.21, 5.22), danach den Zugriff über Elementfunktionen<br />

und Manipulatoren (5.23), wobei dadurch auch eine bequeme Formatierung der<br />

Ausgabe erfolgen kann.<br />

Es ist sehr wichtig, die zwei verschiedenen Arten des Einlesens zu unterscheiden: das Einlesen<br />

mit Hilfe des Eingabeoperators (>>), wo zunächst führende Zwischenraumzeichen überlesen<br />

werden (5.21 Kasten), und das Einlesen jedes einzelnen Zeichens mit der get-Funktion und<br />

verwandten Funktionen (5.24).<br />

Der Punkt (5.25) bietet eine Einführung in die Fehlerbehandlung durch Stromobjekte; ohne<br />

diese Kenntnisse ist es Sie kaum möglich, gut laufende Programme zu schreiben; denn<br />

fehlerhafte Eingaben durch Benutzer sind immer möglich. Da ein Stromobjekt im Fehlerfall<br />

(fast) nichts mehr tut, muss man wissen, dass (und wie) dieser Zustand behandelt werden<br />

muss.<br />

In (5.26) sehen Sie Beispielprogramme (im Internet jeweils als vollständige Programm erhältlich);<br />

es ist sehr wichtig, die Wirkung dieser Programm zu verstehen.<br />

(5.21) Variablenname für die Standardeingabeeinheit: cin (character in)<br />

Standardeingabe: meist Tastatur, kann auf der Betriebssystemebene umgeleitet werden, s.<br />

(1.35).<br />

” Dateiende-Zeichen“ bei Eingabe von Tastatur unter DOS/Windows: Strg-Z (Ctrl-Z, ASCII dez.<br />

26), unter Unix je nach Systemeinstellung ebenfalls Strg-Z oder anders, unter Linux standardmäßig<br />

Strg-D.<br />

Anweisung zum Einlesen: cin [- >> Variable ]- 1..n ;<br />

Anm1 Genauer eigentlich [- . . . ]- 0..n, jedoch nullmal wenig sinnvoll.<br />

Anm2 Haupteffekt des obigen Ausdrucks: s. (5.25c) (Streamobjekt als Referenz).<br />

Anm3 Die Eingabe über cin wird i. a. durch das BS (Betriebssystem) gepuffert:<br />

Anforderung C ++ an BS bzw. dessen Tastaturpuffer: zeichenweise,<br />

Einlesen vom Benutzer durch BS in BS-Tastaturpuffer: zeilenweise.<br />

Bei dieser gepufferten Eingabe über cin gibt der Ausdruck<br />

cin.rdbuf()->in avail()<br />

die Anzahl der im Augenblick im Puffer befindlichen Zeichen an (einschließlich ggf. Zeilenendezeichen).<br />

Bsp s. (5.26e).<br />

Anm4 Zum Einlesen bei C (C++) s. folgende Bibliotheksfunktionsfamilien aus :<br />

scanf, gets, getchar.<br />

Grundsätzlich sind zwei verschiedene Arten des Einlesens über cin zu unterscheiden:<br />

• Lesen mithilfe des Eingabeoperators ” >>“ – so wie oben angegeben:<br />

◦ Zunächst Überlesen aller möglicherweise vorhandenen Zwischenraumzeichen;<br />

als Zwischenraumzeichen (oder Trennungszeichen) 〈white spaces〉 gelten hier<br />

<strong>Leer</strong>zeichen, horizontTab, CR ( ” Wagenrücklauf“), LF (NeueZeile), NeueSeite,<br />

vertikTab.<br />

◦ Beginn des eigentlichen Lesens, d. h. der Übernahme/Umwandlung von Zeichen<br />

in die zugehörige Variable: mit dem ersten Nicht-Zwischenraumzeichen.<br />

◦ Ende des Einlesens: beim ersten zum Variablentyp nicht passenden Zeichen<br />

(beim Typ String ist es ein Zwischenraumzeichen);<br />

wichtig: dieses unpassende Zeichen bleibt im Lesepuffer, ist also beim nächsten<br />

Lesevorgang das als erstes zu lesende Zeichen.<br />

• Benutzung von Elementfunktionen, s. (5.24), (5.23b): keine Sonderbehandlung<br />

von Zwischenraumzeichen, jedes Zeichen gilt als ein zu lesendes Zeichen.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 51<br />

(5.22) Variablenname für die Standardausgabeeinheit: cout (character out)<br />

Variablenname für die Standardfehlerausgabeeinheit:<br />

cerr und C++(neu) clog (character error, character log)<br />

Standardausgabe: meist Bildschirm, kann auf der Betriebssystemebene umgeleitet werden.<br />

Standardfehlerausgabe: meist Bildschirm, kann unter DOS nicht umgeleitet werden, unter<br />

Unix ist eine Umleitung möglich; cerr Ausgabe ungepuffert, C++(neu) clog gepuffert.<br />

(5.23)<br />

Anweisung zum Ausgeben: [- cout<br />

|<br />

| cerr<br />

|<br />

| clog ]- [- verfügbar (5.22, 5.21).<br />

Bei dieser Ausgabeart sind weitere Formen für Ausdruck (rechter Operand in (5.22)) möglich,<br />

nämlich die sog. Manipulatoren: diese manipulieren den Datenstrom, geben aber (meist)<br />

selbst nichts aus. Sie werden meist benutzt zum Formatieren der Ausgabe. Für die meisten<br />

Manipulatoren gibt es äquivalente Elementfunktionen. Alle Setzungen gelten bis zur nächsten<br />

Änderung; Ausnahme: ein Setzen der Mindestausgabebreite wird nach der nächsten<br />

Ausgabe sofort wieder zurückgesetzt.<br />

Anm Auch für die Eingabe gibt es Manipulatoren; sie werden jedoch seltener eingesetzt.<br />

Die wichtigsten Manipulatoren und gleichwertige Elementfunktionen:<br />

Manipulator Element-Fkt. Std.-Wert Bedeutung<br />

endl — Ausgabe Zeilenvorschub und<br />

<strong>Leer</strong>en des Ausgabepuffers<br />

flush flush() <strong>Leer</strong>en des Ausgabepuffers<br />

dec, oct, hex — dec Basis für Ganzzahlausgabe setzen<br />

setw(Breite) ⊛ width(Breite) 0 Mindestausgabebreite setzen ▽<br />

setfill(Zeichen) ⊛ fill(Zeichen) ’ ’ Füllzeichen setzen<br />

▽ nach nächster Ausgabe automatisch wieder auf Standardwert 0<br />

– alle anderen Setzungen bleiben erhalten, bis sie geändert werden –<br />

⊛ Einfügen von notwendig (immer bei Manipulatoren mit Parameter(n))


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 52<br />

(d) Bsp<br />

Für das Formatieren der Ausgabe von Fließkommazahlen gibt es u. a. folgende Möglichkeiten:<br />

Manipulator bzw. Bedeutung<br />

E Elementfunktion<br />

Setzen Ausgabeformat für Gleitkommazahlen<br />

(Standard: wechselnd je nach Wert):<br />

ios::scientific wissenschaftlich<br />

(Gleitkommadarstellung mit Exponent),<br />

ios::fixed Festkommadarstellung<br />

E setf(0,ios::floatfield) Rücksetzen Ausgabeformat wieder auf Standard<br />

setprecision(GanzWert) ⊛ (bei sci./fixed:) Anzahl Nachkommastellen<br />

(sonst:) Anzahl der führenden Ziffern insgesamt<br />

⊛ Einfügen von notwendig (immer bei Manipulatoren mit Parameter(n))<br />

↑↑ Weitere Übersicht siehe (Cpp/Kap. 3,4)<br />

#include // Beispiel D05-23.CPP<br />

#include <br />

using namespace std;<br />

int main()<br />

{<br />

// Nur zum Abzählen der Schreibpositionen bei der Ausgabe:<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 53<br />

• Seiteneffekt: Zeichen/Zeichenkette wird eingelesen, Parameter wird damit belegt.<br />

• Haupteffekt: Streamobjekt (als Referenz) im Zustand nach dem Einlesen, vgl. (5.25c).<br />

↑↑ Genaueres siehe auch (Cpp/Kap. 4).<br />

△! Auch cin.get() (ohne Parameter) ist möglich, es wird der Wert des Zeichens<br />

zurückgegeben, dieser sollte an Zeichen-Variable zugewiesen werden. Aufpassen bei<br />

Nicht-Standardzeichen, z. B. bei Umlauten oder ” ß“: überraschende Effekte! (Mögliche<br />

Lösung: nach (nicht vor) dem Test auf Dateiende eine Typumwandlung auf<br />

char durchführen!) Besser: nicht benutzen. Die oben erwähnte Benutzung von<br />

get(ZeichenVariable) nach (a) ist auch bei Umlauten oder ” ß“ unproblematisch.<br />

(5.25) Fehlerbehandlung bei Streams<br />

(a) Den internen Stream-Fehlerzustand charakterisieren drei Bits, genannt<br />

eofbit, failbit, badbit,<br />

dazu noch das Pseudobit goodbit als Abwesenheit der Fehlerbits:<br />

• eofbit wird gesetzt beim Leseversuch hinter Dateiende (EOF 〈end of file〉)<br />

– zusätzlich wird dabei failbit gesetzt –,<br />

• failbit wird gesetzt bei Fehler, der vermutlich reparabel ist<br />

(z. B. Leseversuch hinter EOF, Einleseversuch Buchstabe auf int),<br />

• badbit wird gesetzt bei Fehler, der den Strom nicht mehr reparabel erscheinen lässt<br />

(z. B. Versuch, eine nicht existierende Datei zu öffnen).<br />

(b) Die Abfrage auf den Fehlerzustand erfolgt beispielsweise mit der Elementfunktion fail();<br />

sie ergibt true, wenn failbit oder badbit gesetzt ist, sonst false.<br />

Einfacher ist die Boolesche Abfrage des Streamobjekts selbst; sie ergibt den invertierten<br />

fail()-Wert:<br />

• if (StreamobjektName) ... entspricht if (!StreamobjektName.fail()) ...<br />

• if (!StreamobjektName) ... entspricht if (StreamobjektName.fail()) ...<br />

(c) Viele Ausdrücke mit einem Streamobjekt als Operand ergeben als Haupteffekt das Streamobjekt<br />

selbst (genauer: Referenz darauf (7.31)), und zwar im Zustand nach der Operation.<br />

Also sind auch diese Ausdrücke Boolesch abfragbar, um den Fehlerzustand des Streamobjekts<br />

zu erfahren. Dadurch ergibt sich die elegante Möglichkeit, Lesen und Abfragen des<br />

Fehlerzustandes danach in einem Ausdruck zu formulieren.<br />

Bsp1 Links: drei Ausdrucksanweisungen; der Haupteffekt des Gesamtausdrucks vor dem Semikolon (wird hier<br />

verworfen (3.32a)) ist jeweils das Streamobjekt selbst im Zustand nach der Operation – zu get() s. (5.24a);<br />

Mitte: Fehlerabfrage mit fail (WAHR bei Fehler);<br />

Rechts: Kombination beider Teile mit negierter Boolescher Abfrage (ebenfalls WAHR bei Fehler):<br />

cin >> Variable; if (cin.fail()) ... if (!(cin >> Variable)) ...<br />

cout zahl;<br />

while (cin.fail()) {<br />

// Fehlerbehandlung nach (d)<br />

cin >> zahl;<br />

}<br />

/* 2 */ cin >> zahl;<br />

while (!cin) {<br />

// Fehlerbehandlung nach (d)<br />

cin >> zahl;


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 54<br />

}<br />

/* 3 */ while (cin >> zahl, cin.fail()) {<br />

// Fehlerbehandlung nach (d)<br />

}<br />

/* 4 */ while (!(cin >> zahl)) {<br />

// Fehlerbehandlung nach (d)<br />

}<br />

(d) △! Im Fehlerfall (eines der drei Fehlerbits gesetzt) tut der Strom nichts mehr, was man<br />

auch immer ihm befiehlt; daher unbedingt (im Falle eines reparablen Zustands) das Löschen<br />

des/der Fehlerbit(s) nicht vergessen! Dies geschieht durch Aufruf der Elementfunktion<br />

clear(), vgl. (5.26d).<br />

△! Zusätzlich muss i. a. die Fehlerursache beseitigt werden, da sonst bei Wiederholung<br />

der Operation wieder der gleiche Fehler erzeugt wird. Beim Einlesen z. B. bleibt das fehlerverursachende<br />

Zeichen im Strom! Es muss demnach mindestens ein Zeichen verworfen<br />

werden, beispielsweise durch ignore (5.23b). Meist ist es sinnvoll, die ganze Eingabezeile zu<br />

verwerfen, vgl. (5.26d).<br />

↑↑ Weitere Informationen zur Fehlerfindung und -behandlung bei Streams s. (Cpp/Kap. 4).<br />

(e) Die Elementfunktion zum Testen auf Dateiende-Bedingung (d. h. ob eofbit gesetzt ist) ist<br />

die eof-Funktion:<br />

StreamobjektName.eof()<br />

Sie gibt true zurück, wenn eofbit gesetzt ist (d. h. nach einem Leseversuch hinter das<br />

Dateiende), sonst false. Diese Funktion ist wichtig, wenn man mit Dateien umgeht (s.<br />

nächstes Unterkapitel), insbesondere wenn man einen allgemeinen Fehler von dem speziellen<br />

Dateieende-Fehler unterscheiden möchte.<br />

(f) Wenn das Einlesen auf eine Variable fehlschlägt, sollte ihr bisheriger Wert unverändert<br />

bleiben. Dies ist nach (Str3/Kap. 21.3.3) auch bei den vordefinierten Stromobjekten der Fall.<br />

Leider hält sich der Compiler Microsoft Visual C ++ 6.0 nicht an diese Regel, er setzt im<br />

Fehlerfall die Variable auf den Wert Null.<br />

(5.26) Bsp<br />

Bsp Bei folgendem Programmfragment sollte im Fehlerfalle die Zahl 35 ausgegeben werden; bei Kompilation<br />

mit MS VC++ 6.0 wird jedoch der Wert 0 ausgegeben.<br />

int zahl=35;<br />

cout zahl)) {<br />

// Bei Einlesefehler:<br />

cout c) cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 55<br />

}<br />

(c) Filterprogramm:<br />

while (cin.get(c)) cout zahl)) {<br />

cin.clear(); // unbedingt nötig!!<br />

cin.ignore(1000,’\n’); // Zeile im BS-Puffer leerlesen;<br />

// andere Möglichkeit: s. (e)<br />

cerr zahl, !cin) ...<br />

(e) Programmfragment: <strong>Leer</strong>en des Eingabepuffers C++(neu) , vgl. (5.21 Anm3); zur Variablendefinition<br />

siehe (4.51b)<br />

#include // Beispiel D05-265.CPP<br />

using namespace std;<br />

// ...<br />

if (int anz=cin.rdbuf()->in_avail())<br />

cin.ignore(anz);<br />

// ...<br />

Änderung für C++(alt) s. Vorlesung.<br />

5.3 Umgang mit Textdateien<br />

(5.30) Übb Wenn Sie den Umgang mit den Standardstrom-Objekten (s. voriges Unterkapitel)<br />

verstanden haben, werden Sie bemerken, dass der Umgang mit Textdateien in C ++ sehr<br />

einfach ist. Die Benutzung von Dateien mit Hilfe von Stromobjekten ist genauso wie Sie es<br />

schon von cout und cin her kennen; neu ist hier nur, dass Sie sich für die Kommunikation mit<br />

einer Datei zuerst ein Stromobjekt erzeugen und es mit einer Datei verknüpfen müssen (5.33);<br />

am Ende sollten Sie diese Verknüpfung wieder lösen (5.34). Das Dateikopier-Beispielprogramm<br />

(5.35) zeigt alle diese Schritte.<br />

Der Umgang mit Binärdateien wird später besprochen (Kap. 12.6).<br />

(5.31) Zwei Arten von Dateien (bzw. zwei Arten, Dateien zu bearbeiten) sind zu unterscheiden:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 56<br />

• Textdateien: sie geben normalen Text wieder, sie sind in Zeilen gegliedert, besondere<br />

Formatierungen sind nicht erlaubt – außer den Zwischenraumzeichen (5.21 Kasten) und<br />

(ggf.) einem Dateiendezeichen (ASCII dez. 26, vgl. (5.21)).<br />

Hierbei übernimmt das C ++-Laufzeitsystem beispielsweise die ggf. notwendige Transformation<br />

zwischen der Betriebssystemdarstellung eines Zeilenendes und der C ++-Darstellung<br />

’\n’.<br />

Bsp In Unix wäre dafür i. a. keine Transformation nötig; unter DOS/Windows wird ein Zeilenende durch<br />

das Doppelzeichen CR/LF (ASCII dez. 13/10, s. (1.45a)) dargestellt, unter C ++ durch ’\n’ (meist<br />

als Zeichen LF, ASCII dez. 10).<br />

• Binärdateien: Dateien mit beliebigen Bytes; diese Bytes werde beim Bearbeiten nicht<br />

speziell interpretiert, sondern unverfälscht zwischen Betriebssystem und C ++-Programm<br />

übertragen.<br />

Hier wird nur der Umgang mit Textdateien besprochen; zu Binärdateien s. (Kap. 12.6).<br />

(5.32) Umgang mit Textdateien<br />

In C ++ ist der Umgang mit (Text-)Dateien sehr einfach, nämlich ähnlich wie die Benutzung<br />

der Standardeingabe cin und der Standardausgabe cout.<br />

(a) Gar keine Änderung zum Bisherigen ist nötig, wenn eine Umleitung der Standardeingabe<br />

oder der Standardausgabe auf der Betriebssystemebene erfolgt: normale Benutzung von cin<br />

und cout.<br />

(b) Anders beim direkten Umgang mit Dateien; hierbei sind folgende Schritte nötig:<br />

• Vorbereitungen, s. (5.33):<br />

◦ Erzeugen eines Streamobjekts passender Bauart durch Variablendefinition,<br />

◦ Verknüpfen dieser Variablen mit der gewünschten Datei: Öffnen der Datei.<br />

Diese beiden Schritte können meist auch mit einem einzigen Befehl durchgeführt werden.<br />

• Arbeiten mit dieser Datei wie mit der Standardeingabe bzw. -ausgabe: Lesen bzw.<br />

Schreiben (s. voriges Unterkapitel); statt der Namen cin, cout ist jeweils der Streamobjektname<br />

(Variablenname) anzugeben.<br />

• Nachbereitung, s. (5.34): Schließen der Datei und Zerstören des Streamobjekts.<br />

(5.33) Vorbereitung von Streamobjekten<br />

(a) Typ des benötigten Streamobjekts (Klassenname):<br />

ifstream bei beabsichtigtem Lesen,<br />

ofstream bei beabsichtigten Schreiben,<br />

fstream bei Lesen und Schreiben.<br />

Damit die Namen verfügbar sind, ist die passende Headerdatei einzuschließen:<br />

#include <br />

Das passende Streamobjekt wird beispielsweise durch folgende Variablendefinition erzeugt:<br />

StreamobjektTyp StreamobjektName ;<br />

(b) Das Verknüpfen dieses Streamobjekts mit der gewünschten Datei geschieht durch Aufruf<br />

der open()-Elementfunktion:<br />

StreamobjektName.open(DateiName);<br />

StreamobjektName.open(DateiName,Modus);<br />

Der DateiName ist in betriebssystemspezifischer Weise anzugeben, ggf. einschließlich Pfad.<br />

△! Bei Pfadangaben in DOS/Windows, wenn Angabe als Stringkonstante, ist das Doppelzeichen<br />

” \\“ statt des Einzelzeichens ” \“ zu nehmen, vgl. (3.25) – dagegen (5.74)!<br />

Als Modus sind angebbar:<br />

ios::in Lesemodus (Standard bei ifstream)<br />

ios::out Schreibmodus (Standard bei ofstream)<br />

ios::app beim Schreiben anhängen<br />

ios::ate ans Dateiende positionieren 〈at end〉<br />

ios::trunc bisherigen Inhalt löschen<br />

ios::binary Binärzugriff, s. (5.31, 5.36), ohne Angabe: Textmodus<br />

Diese Einzelangaben sind verknüpfbar mit Hilfe des Operators ” |“ Op12 (5.11), z. B.<br />

ios::in | ios::out.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 57<br />

Bei bisherigen Compilern – nicht unbedingt mehr bei sehr neuen – sind zusätzlich auch die<br />

Flags ios::nocreate und ios::noreplace verfügbar.<br />

Öffnen zum Lesen ohne Modusangabe, wenn Datei nicht vorhanden:<br />

C++(neu) Fehlermeldung, Datei wird nicht (leer) erzeugt.<br />

C++(alt) Datei wird leer erzeugt; eine Modusangabe ios::nocreate verhindert dieses.<br />

Empf Der Erfolg oder Misserfolg des Öffnens sollte vor weiteren Operationen als Streamzustand<br />

(5.25b) abgefragt werden.<br />

(c) Beide Schritte (a) und (b) sind auch in einer einzigen Anweisung möglich (ggf. jedoch nicht<br />

bei fstream):<br />

StreamobjektTyp StreamobjektName(DateiName);<br />

StreamobjektTyp StreamobjektName(DateiName,Modus);<br />

(5.34) Das Schließen des Streams erfolgt durch den Aufruf<br />

StreamobjektName.close();<br />

Das Zerstören des Objekts geschieht wie das Zerstören anderer Variablen, z. B. durch Verlassen<br />

des zugehörigen Blocks.<br />

Falls der Stream zuvor noch nicht geschlossen wurde, geschieht dieses implizit durch das<br />

Zerstören des Streamobjekts (Destruktoraufruf (9.23a)). Jedoch:<br />

Empf Gewöhnen Sie sich das explizite Schließen durch die Elementfunktion close() an.<br />

(5.35) Bsp Kopieren einer Textdatei – nicht für binäre Dateien geeignet!<br />

#include // Beispiel D05-35.CPP<br />

#include <br />

using namespace std;<br />

int main()<br />

{<br />

ifstream ein;<br />

ein.open("DATEI.TXT");<br />

// Angabe ios::in nicht nötig, da Typ ifstream<br />

// Oder mit nur einer Anweisung:<br />

// ifstream ein("DATEI.TXT");<br />

}<br />

if (!ein) {<br />

cerr


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 58<br />

(5.36) Die obigen Erläuterungen sind nur teilweise auf den Umgang mit Binärdateien übertragbar.<br />

5.4 Arrays<br />

↗ Bei Binärdateien ist unbedingt der Modus ios::binary (5.33b) zu setzen; das Lesen bzw.<br />

Schreiben erfolgt am besten mit den Elementfunktionen read und write, siehe (Kap. 12.6) und<br />

(Cpp/Kap. 4).<br />

(5.40) Übb Array: Zusammenfassung mehrerer Komponenten des gleichen Typs unter einem<br />

einzigen Typ.<br />

Mathematik:<br />

• eindimensional: Vektor, n-Tupel (bei letzterem: Komp. jeweils gleichen Typ),<br />

• zweidimensional: Matrix.<br />

Um Verwirrungen zu vermeiden, wird dieses Konstrukt hier bewusst nicht Vektor genannt<br />

(so dagegen in den meisten C ++- und C-Büchern), da Vektor als mathematischer Begriff<br />

anders gefasst ist.<br />

In Programmen für naturwissenschaftliche und technische Anwendungen wird das Array<br />

sehr häufig benutzt. Da manche Autoren vermutlich mehr von anderen Anwendungsgebieten<br />

kommen, wird das Gebiet manchmal in Lehrbüchern zu kurz gehalten. Den Problemen, die<br />

sich durch den äußerst schwerwiegenden Fehler eines Indexüberlaufs ergeben (5.41△! ), kann<br />

man entgehen, indem man auf Typen der neuen C ++-Laufzeitbibliothek übergeht (nicht in<br />

diesem Kurs besprochen). Das ist jedoch in wissenschaftlich-technischen Anwendungen nicht<br />

immer sinnvoll und bei genauer (nachdenkender) Programmierung auch nicht nötig.<br />

(5.41) Deklaration 10/11 eines Array-Typs (eindimensional) <br />

Typ ArrayName [ KonstanterAusdruck ] ;<br />

ArrayKomponente (als Variable) ArrayName [ Ausdruck ]<br />

• Der angegebene Typ ist der Typ der einzelnen Komponente des Arrays.<br />

• KonstanterAusdruck gibt die Anzahl der Komponenten des Arrays an (Ausdruck muss<br />

zur Kompilationszeit berechenbar sein).<br />

• Die Nummerierung der einzelnen Komponenten läuft von 0 bis KonstanterAusdruck-1.<br />

• △! Eine Index-Bereichsüberschreitung wird bei ArrayKomponente nicht geprüft!<br />

Bsp int vektor[20]; // Definition: Array mit 20 int-Komponenten<br />

vektor[0]=-12; // Zuweisung an das erste Element<br />

vektor[19]=23; // Zuweisung an das letzte(!) Element<br />

// Jetzt seien alle Elemente des Arrays belegt; Ausgabe aller Elemente der Reihenfolge nach:<br />

int lauf;<br />

for (lauf=0; lauf


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 59<br />

Werden zuwenige Elemente angegeben, werden die restlichen automatisch mit Null oder<br />

zugehörigen Nullwerten aufgefüllt; zu viele Elemente erzeugen einen Kompilierfehler.<br />

Bsp int arr[20] = { 2, 5, 7, -23 }; Anm.: die restlichen 16 Werte werden mit 0 gefüllt.<br />

double matrix[3][2] = { { 1.0, 7.3 }, { -0.7, 6. }, { 3.7, 5.3 } };<br />

Im Falle der Initialisierung ist es erlaubt, die (innerste) Dimension wegzulassen, da dann der<br />

Compiler die Anzahl Elemente selbst zählt, s. (5.51, 12.71c).<br />

(5.44) Gerade auch bei Arraygrenzen sollten i. a. symbolische Konstanten (4.72) genommen werden,<br />

da dadurch ein Programm besser wartbar wird; denn die Grenzen können später viel leichter<br />

geändert werden.<br />

Bsp Bei einer Arraygrenze von 50 müsste bei einer späteren Änderung jedes Auftreten von 50 und auch von<br />

49 (als letzter erlaubter Index) geprüft werden; eine automatische Ersetzung ist nicht möglich, da diese<br />

beiden Zahlen auch in anderer Bedeutung auftreten können. Eine Änderung des Wertes einer Konstanten<br />

maxAnz (und dadurch implizit des Wertes maxAnz-1) ist jedoch in seiner Definition einfach möglich.<br />

5.5 Strings (C-Strings)<br />

(5.50) Übb Strings (Zeichenketten) sind in der Programmierpraxis sehr häufig anzutreffen. In<br />

diesem Kurs sollen die Strings nur in der (älteren) Art der sog. C-Strings besprochen werden.<br />

Diese Strings sind zwar schwieriger zu handhaben, da sie Arrays aus Zeichen sind und daher<br />

die große Gefahr des Speicherüberlaufs bieten. Wenn man jedoch gewohnt ist, allgemein mit<br />

Arrays umzugehen (s. voriges Unterkapitel), werden die C-Strings wenig Schwierigkeiten<br />

bereiten.<br />

Diese speziellen Zeichen-Arrays haben eine zusätzliche Eigenschaft, die über die allgemeinen<br />

Array-Eigenschaften hinausgeht: den Nullbyte-Abschluss (5.51). Wie allgemein bei Arrays<br />

kann nicht durch Zuweisung kopiert werden (5.52). Die wichtigsten Stringfunktionen stellt<br />

Punkt (5.53) vor. Die Besonderheiten beim Einlesen und Ausgeben werden in (5.55) erläutert.<br />

Anm In C++(neu) gibt es eine Bibliotheks-Klasse string; Objekte dieses Typs sind einfacher zu<br />

handhaben als C-Strings, insbesondere sind Speicherplatzüberläufe kaum möglich. Wie oben<br />

erwähnt, wird dieser Typ jedoch in dieser Vorlesung nicht besprochen.<br />

(5.51) String (genauer: C-String), Zeichenkette:<br />

spezielles Array aus char mit Kennzeichnung der aktuellen Länge: hinter letztem Nutzzeichen<br />

steht das Zeichen ’\0’ (Nullbyte, d. h. alle Bits 0 – nicht die Ziffer ’0’, diese im ASCII<br />

Wert dez. 48 (1.45a)).<br />

Daher ist der Mindest-Speicherbedarf eines Strings um 1 höher als die Anzahl der Nutzzeichen.<br />

Beispiele:<br />

char zeile[81];<br />

char meldung[20]="Hallo!";<br />

Das char-Array zeile wird nicht initialisiert, es muss später sinnvoll belegt werden. Das<br />

Array meldung wird initialisiert (keine Zuweisung, sondern Angabe der Erstbelegung, vgl.<br />

(5.52)), abschließendes Nullbyte wird automatisch hinzugefügt.<br />

△! Dimensionierung muss mindestens AnzahlZeichen+1 sein!!<br />

↑↑ Bei der Initialisierung in C ist auch AnzahlZeichen als Dimensionierung erlaubt, jedoch sehr<br />

gefährlich △! , da Nullbyte fehlt. In C++ ist diese Fehlerquelle nicht mehr erlaubt.<br />

Das Zählen der benötigten Anzahl kann man auch dem Compiler überlassen, jedoch nur bei<br />

der Inititalisierung; es wird automatisch die richtige Länge einschließlich Nullbyte eingesetzt:<br />

char neuMeldung[ ]="Hallo!";<br />

(5.52) Kopieren eines Strings auf einen anderen ist nicht durch Zuweisung möglich, sondern nur<br />

durch Kopieren aller Zeichen einschließlich des Nullbytes, s. auch (5.53a). Dies gilt allgemein<br />

für Arrays, vgl. (5.65b). Ein Initialisieren dagegen (mit konstanten Werten, nicht mit Variablen)<br />

ist möglich (5.51).<br />

(5.53) Vier wichtige Stringfunktionen (für alle jeweils nötig: #include ):<br />

(a) strcpy(Zielarray, Quellarray);


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 60<br />

Funktion kopiert auf Zielarray, und zwar alle Nutzzeichen von Quellarray bis einschließlich<br />

des (ersten) Nullbytes, d. h. es wird der aktuelle Quellstring kopiert.<br />

△! Ein Prüfen, ob Zielarray genügend Speicherplatz hat, kann nicht stattfinden! Verantwortung<br />

des Programmierers! Wichtige Fehlerquelle!<br />

(b) strcat(Zielarray, Quellarray);<br />

Anhängen (Konkatenieren 〈concatenate〉) des Quellarrays an das Zielarray, d. h. das bisherige<br />

Nullbyte des Zielarrays wird durch das erste Zeichen des Quellarrays überschrieben usf.<br />

Auch hier keine Prüfung des benötigten Speicherplatzes!<br />

(c) strlen(ZeichenArray)<br />

gibt die aktuelle Stringlänge zurück, d. h. die Anzahl der Nutzzeichen ohne das Nullbyte;<br />

die Funktion muss die Länge jeweils berechnen.<br />

△! Wert ist ein unsigned-Typ, daher aufpassen bei Mischung mit (normalen) signed-<br />

Typen, vgl. (12.23△! )! Falls der Wert in einem Ausdruck gebraucht wird: besser vorher<br />

diesen Wert einer int-Variablen zuweisen und dann diese Variable benutzen!<br />

(d) strcmp(Array1, Array2);<br />

vergleicht 〈compare〉 beide Strings, gibt die alphabetische Sortierung der beiden Strings an<br />

(entsprechend der internen Zeichendarstellung):<br />

• Rückgabewert < 0, wenn Array1 0, wenn Array1>Array2, d. h. Array1 im Alphabet hinter Array2 steht.<br />

(5.54) <strong>Leer</strong>string, Nullstring: String der (Nutz-)Länge Null, d. h. mit Nullbyte als erstem Zeichen;<br />

<strong>Leer</strong>string als Konstante: "" (zwei Zeichen Doppelanführungsstriche ohne Zwischenraum).<br />

(5.55) Ergänzungen zu (Kap. 5.2):<br />

arr sei eine gültige Stringvariable.<br />

(a) cin >> arr liest genau ein ” Wort“: zunächst Überlesen aller Zwischenraumzeichen, danach<br />

Lesen der Zeichenfolge, Ende bei nächstem unpassenden Zeichen, d. h. hier bei Zwischenraumzeichen;<br />

dieses Zwischenraumzeichen bleibt im Lesepuffer.<br />

Es findet keine Überprüfung auf Speicherüberschreitung statt.<br />

↑↑ Ein Begrenzen der Anzahl der mit dem Operator >> zu übernehmenden Zeichen ist z. B.<br />

möglich durch cin.width(AnzahlZeichen). Dadurch werden – jedoch nur bei dem nächsten<br />

Einlesen – maximal AnzahlZeichen-1 übernommen. Empf besser (b,c).<br />

(b) cin.getline(arr,AnzahlZeichen) liest genau eine Zeile, jedoch maximal AnzahlZeichen-1<br />

Zeichen, dazu Erzeugung Nullbyteabschluss. Bei richtiger Größenangabe geschieht daher<br />

keine Speicherüberschreitung. Empfehlung: Als AnzahlZeichen am besten die richtige Speichergröße<br />

durch sizeof(arr) nehmen (Op3j, s. (5.14)).<br />

Das Zeilenendezeichen ’\n’ wird zwar gelesen (wenn nicht vorher Lesen zu Ende), aber<br />

nicht in den String kopiert.<br />

(c) Nur für spezielle Anwendungen, s. △! !<br />

cin.get(arr,AnzahlZeichen) arbeitet wie getline; jedoch wird das Zeilenendezeichen im<br />

Lesepuffer gelassen. Dadurch kann anschließend geprüft werden, ob eine ganze Zeile gelesen<br />

wurde.<br />

△! Bei mehrfacher Benutzung dieser Funktion hintereinander ohne andere Lesebefehle<br />

dazwischen: das Lesen tritt auf der Stelle! Gefahr einer Endlosschleife!<br />

(d) Arrayname arr als formaler Funktionsparameter:<br />

△! Innerhalb der Funktion wird der Ausdruck sizeof(arr) nämlich anders interpretiert;<br />

statt dessen sollte sizeof(ArrayTyp) genommen werden, s. a. (7.46 ↑↑).<br />

(e) cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 61<br />

5.6 Übersicht Typen<br />

(5.60) Übb Ein Typ ist ein Name oder eine Beschreibung für eine Wertemenge mit zugehörigen<br />

erlaubten Operatoren, vgl. (2.25a2), (3.20).<br />

Es wird nun gezeigt, wie ein Benutzer (ein Programmierer) eigene neue Typ(namen) erzeugen<br />

und benutzen kann (5.61, 5.62).<br />

Häufig muss ein Ausdruck eines Typs in einen anderen Typ umgewandelt werden. Dieses<br />

kann implizit geschehen (5.63), d. h. ohne ausdrückliche Angabe durch den Programmierer.<br />

Aber auch eine explizite Typumwandlung ist möglich. Typumwandlungen ( ” Typ-Casts“<br />

〈type casts〉) sind in der Sprache C eine häufige Fehlerquelle, wenn der Programmierer nicht<br />

genügend Kenntnisse hat (oder, falls er sie hat, sie beim Programmieren nicht anwendet).<br />

Daher hat C++(neu) weniger fehleranfällige Typumwandlungs-Operatoren eingeführt (insbesondere<br />

static cast, s. (5.64a) und Hinweis (5.64 ↑↑2)). Diesen neuen Operatoren ist der<br />

Vorrang gegenüber den älteren C-Umwandlungen (5.64c) und der auch fehleranfälligen alten<br />

C ++-Umwandlung (5.64b) zu geben, obwohl diese neuen Operatoren in der Schreibweise an<br />

Schwerfälligkeit kaum zu überbieten sind (äußerst lange Namen!).<br />

Mit (5.65) wird der Zeigertyp kurz angesprochen. Zunächst ist nur wichtig, seine prinzipielle<br />

Bedeutung zu kennen, da der Begriff des Zeigers für manche Erläuterungen benötigt wird.<br />

Die wichtigste Erläuterung wird schon in (5.65b) angedeutet (automatische Umwandlung Arrayname<br />

in Zeiger), die Konsequenzen werden bald genannt (7.46). Direkt angewendet wird<br />

der Zeigertyp erst viel später, und zwar ab (Kap. 10.2).<br />

(5.61) In vielen Programmiersprachen – so auch in C ++ – gibt es Typen, die dem Compiler von<br />

vornherein bekannt sind, sog. eingebaute Typen.<br />

Bsp int, float, unsigned char.<br />

Zusätzlich hat der Benutzer (Programmierer) die Möglichkeit, eigene Typen zu konstruieren:<br />

benutzerdefinierte Typen. Diese Typen müssen dem Compiler in ihrem inneren Aufbau<br />

bekanntgegeben werden, bevor sie benutzt werden können. Bei einfach gebauten benutzerdefinierten<br />

Typen geschieht die Typdefinition meist zusammen mit einer Variablendefinition.<br />

Bsp int arr[34];<br />

Eine Variable mit Namen arr wird erzeugt, ihr Typ ist ” Array mit 34 int-Komponenten“.<br />

Bei komplizierteren Typen ist es sinnvoll, teilweise auch notwendig, die Typen dem Compiler<br />

vor der Benutzung explizit bekanntzugeben (Typdefinition). Dieses kann mit einem<br />

typedef (5.62) geschehen, bei Klassen (o. a.) auch ohne typedef (9.11).<br />

(5.62) Definition eines Typs mit typedef in C ++/C:<br />

Einführung eines neuen Namens, der synonym zum angegebenen Typ ist; keine Einführung<br />

eines neuen (einzigartigen) Typs.<br />

TypDefinition typedef DefinitionEinerVariablen<br />

Der neu eingeführte Name – ohne typedef wäre er ein Variablenname – wird durch das<br />

typedef zu einem Typnamen für diesen Typ.<br />

Bsp Synonyme: int und Ganz, ferner Zeil und ” char-Array mit max(=81) Elementen“<br />

// ...<br />

typedef int Ganz;<br />

Ganz neuZahl; neuZahl=32; cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 62<br />

Gefährlicher sind implizite Umwandlungen, wenn dadurch Informationen verlorengehen,<br />

z. B. durch Zuweisung eines double-Wertes an ein int (gebrochener Anteil wird abgeschnitten,<br />

ggf. Bereichsüberschreitung) oder eines int-Wertes an ein char (Gefahr der Bereichsüberschreitung).<br />

Meist warnt der Compiler dabei; dann sollte eine explizite Typumwandlung<br />

durchgeführt werden (5.64), um diese Warnungen zu vermeiden.<br />

(5.64) Explizite Typumwandlungen 〈type cast〉<br />

(a) C++(neu) Neuer Operator (Op2h):<br />

static cast(Ausdruck)<br />

Hierdurch wird die Umwandlung von Ausdruck in Typ durchgeführt, und zwar mit Prüfung<br />

schon zur Kompilationszeit, ob eine solche Umwandlung überhaupt sinnvoll ist.<br />

Bsp int i; double d=34.9; i=static cast(d);<br />

(b) C++ △! Op2c; hierdurch können auch ” unsinnige“ Umwandlungen durchgeführt werden,<br />

daher gefährlicher! Schreibweise des neuen Typs wie ein Funktionsname; nur möglich, wenn<br />

der neue Typ ein Name ist.<br />

Bsp int i; double d=34.9; i=int(d);<br />

(c) C/C++ △! Op3i, Wirkung wie Op2c, jedoch nicht so übersichtlich; hier kein TypName wie bei<br />

Op2c erforderlich.<br />

Bsp int i; double d=34.9; i=(int)d;<br />

△! Wegen der Gefährlichkeit und Unübersichtlichkeit gelten die beiden letzten Umwandlungen<br />

(b,c) inzwischen als ” missbilligt“ (Str3/Kap. B.2.3).<br />

Es gibt viele Umwandlungen, die zumindest für den Anfänger sehr gefährlich sind, da<br />

er die Folgen nicht übersehen kann. Daher unbedingt die Empfehlung im folgenden<br />

Kasten beherzigen!<br />

Bei Compilerwarnungen in Hinsicht auf Typumwandlungen sollen Sie zunächst überlegen,<br />

ob der Compiler das meint, was Sie meinen. Falls ja, benutzen Sie static cast<br />

(a); dann sollte die Warnung nicht mehr erscheinen.<br />

↑↑1 Genaueres über Typumwandlungen, insbesondere implizite, siehe (Cpp/Kap. 7).<br />

↑↑2 Andere Typumwandlungsoperatoren Op2i-k siehe C ++-Bücher, zu reinterpret cast s. a.<br />

(10.24 Anm4) und (12.63a, 12.64a).<br />

(5.65) Datentyp Zeiger 〈pointer〉<br />

(a) Der Datentyp Zeiger ist dadurch charakterisiert, dass sein Wert als Adresse einer anderen<br />

Variablen interpretiert wird, der Zeiger ” zeigt“ auf einen anderen Speicherplatz.<br />

↑↑ Näheres hierzu, insbesondere die Definiton, Benutzung mit zugehörigen Operatoren s. (Kap.<br />

10.2)<br />

(b) WICHTIG jedoch in diesem Zusammenhang:<br />

Ein Arrayname durch den Compiler automatisch umgewandelt in einen Zeiger auf das erste<br />

Array-Element (Element mit Index 0). Überraschende Folgerungen bei Funktionen mit<br />

Array-Parametern siehe (7.46); dieses ist auch der Grund, warum Arrays nicht durch Zuweisung<br />

kopiert werden können (5.52).<br />

5.7 Präprozessor<br />

(5.70) Übb Der Präprozessor ist ein Textprozessor, der den Quelltext bearbeitet, bevor der Compiler<br />

den Text sieht. Er arbeitet zeilenorientiert – unabhängig von der C ++/C-Syntax. In<br />

älteren Programmen, insbesondere in C, werden Präprozessoreigenschaften sehr intensiv benutzt.<br />

In C++(neu) ist das meist nicht mehr nötig, da bessere Konstrukte zur Verfügung<br />

stehen.<br />

Geblieben jedoch ist die Benutzung des #include (5.74), ferner die bedingte Kompilierung<br />

(5.75): jede später durch Sie erstellte Headerdatei (8.24) muss einen Include-Wächter haben


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 63<br />

(5.75c). Warum das Vermeiden eines Mehrfacheinschlusses nötig ist, wird später erläutert<br />

werden, insbesondere ab (Kap. 9).<br />

Syntax für Zeile mit einer Präprozessordirektive:<br />

Zeilenbeginn mit # (nach ggf. Zwischenraumzeichen).<br />

(5.71) Zeilenfortsetzungszeichen (für Präprozessorzeile; für Compilerzeile unnötig): \ mit sofort<br />

anschließendem ” Zeilenendezeichen“ (kein <strong>Leer</strong>zeichen); Präprozessor entfernt \ und Zeilenendezeichen,<br />

bevor er die Gesamtzeile untersucht.<br />

(5.72) Makro ohne Parameter<br />

C (C++) sehr viel benutzt für symbolische Konstanten; C++ besser: const ...<br />

C/C++ benutzt bei Definitionen für bedingte Übersetzung, s. (5.75) .<br />

(a) #define Name Textersatz<br />

Semantik: Ab dieser Zeile wird Name (als lex. Bestandteil, nicht innerhalb eines Strings)<br />

ersetzt durch Textersatz ( ” dummer“ Ersatz, unabhängig von Syntax); Textersatz darf auch<br />

<strong>Leer</strong>zeichen enthalten. Wenn Textersatz fehlt, wird Name gelöscht, d. h. durch nichts ersetzt.<br />

Bsp<br />

// Ersatz für Empfehlung in Kap. 0.3<br />

#define bool int<br />

#define true 1<br />

#define false 0<br />

(b) Beenden des Ersetzens: Quelltextende oder Auftreten von:<br />

#undef Name<br />

Danach ist Name nicht mehr als zu ersetzender Bestandteil bekannt.<br />

Ein #undef bei unbekanntem Name ist unschädlich, ein #define bei bekanntem Namen<br />

bewirkt (i. a.) einen Fehler.<br />

(5.73) Makro mit Parameter ↗ (10.12b)<br />

C++ besser: inline ... (10.12a) – wird hier noch nicht besprochen.<br />

(5.74) Einfügungen<br />

// Form 1:<br />

#include <br />

// Form 2:<br />

#include "DateiName"<br />

Einfügen von der Datei DateiName (i. a. eine sog. Header-Datei) an dieser Stelle. Befehl ist<br />

schachtelungsfähig.<br />

• Form 1: Suchen an den dem Präprozessor bekannten Stellen (i. a. Verzeichnis/se für<br />

allg. Headerdateien)<br />

• Form 2: Zunächst Suche im aktuellen Verzeichnis, dann wie Form 1; diese Form ist<br />

geeignet für eigene Headerdateien. Falls DateiName eine Pfadangabe einschließt (DOS/<br />

Windows: Zeichen ” \“ nicht doppelt im Gegensatz zu (3.25) und (5.33b)), dann Suche nur<br />

im angegebenen Pfad.<br />

△! Leider sind <strong>Leer</strong>zeichen innerhalb von < > und " " signifikant, daher Vorsicht!<br />

(5.75) Bedingte Übersetzung<br />

(a) #if KonstanterAusdruck1<br />

Textzeilen1<br />

#elif KonstanterAusdruck2<br />

Textzeilen2<br />

#else<br />

Textzeilen3<br />

#endif


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 64<br />

Semantik:<br />

• Wenn KonstanterAusdruck1 WAHR (ungleich 0), werden nur Textzeilen1 genommen,<br />

Textzeilen2 und Textzeilen3 werden ignoriert.<br />

• Sonst: wenn #elif-Zeile vorhanden und KonstanterAusdruck2 WAHR (ungleich 0),<br />

werden nur Textzeilen2 genommen, Textzeilen3 (und natürlich auch Textzeilen1) werden<br />

ignoriert.<br />

• Sonst: wenn #else-Zeile vorhanden, werden nur Textzeilen3 genommen.<br />

• Sonst: keine Textzeile zwischen #if und #endif wird genommen.<br />

(b) Statt der #if-Zeile in (a) kann auch eine der folgenden Zeilen genommen werden:<br />

#ifdef Name<br />

#ifndef Name<br />

Semantik: WAHR, wenn Name (durch ein #define) definiert bzw. nicht definiert (auch<br />

<strong>Leer</strong>definition ohne Textersatz gilt als Definition); weitere Präprozessorzeilen (#endif, ggf.<br />

auch #else) wie bei (a).<br />

(c) Anwendung als Beispiel: Ausschluss von Mehrfacheinschluss einer Header-Datei ( ” Include-<br />

Wächter“). Hierbei kann der benutzte Name (im Beispiel BEISPIEL_H_) beliebig gewählt<br />

werden, er muss nur eindeutig über alle Programmdateien sein und nicht mit irgendwelchen<br />

C ++-Namen kollidieren. Sinnvoll ist daher ein Name, der aus dem Dateinamen zusammengesetzt<br />

ist.<br />

// Beispiel-Headerdatei BEISPIEL.H<br />

#ifndef BEISPIEL_H_<br />

#define BEISPIEL_H_<br />

// Definitionen dieser Headerdatei<br />

// ...<br />

#endif<br />

Ein doppeltes #include schließt die Definitionen der Datei nur einmal ein:<br />

#include "BEISPIEL.H"<br />

#include "BEISPIEL.H" // unschädlich, selbst wenn dopp. Def. unerlaubt<br />

Weitere Anwendung: schachtelungsfähiges Auskommentieren größerer Textteile (auch bei<br />

enthaltenen Kommentaren), vgl. Übungen:<br />

#if 0<br />

Textzeilen – werden ignoriert<br />

#endif<br />

(5.76) Weitere Tätigkeiten des Präprozessors (z. B.)<br />

• Durch Zwischenraumzeichen (auch Zeilenende) getrennte Stringkonstanten werden verkettet;<br />

auf diese Weise sind auch lange Stringkonstanten über mehrere Zeilen möglich.<br />

Bsp cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 65<br />

6 Konstruktion von Baueinheiten,<br />

Problem der Trennung in Verborgenheit und Öffentlichkeit<br />

(sprachunabhängige Betrachtung)<br />

6.0 Überblick<br />

Dieses Kapitel – ebenso wie Kap. 2 unabhängig von einer speziellen Programmiersprache –<br />

stellt die Grundprinzipien dar, wie man bei größeren Programmen verfahren muss, um sie<br />

übersichtlich zu erhalten.<br />

In Unterkap. 1 wird erläutert, dass diese Übersichtlichkeit für den Menschen grundsätzlich<br />

nur möglich ist, indem er das zu bearbeitende Problemfeld in Teile unterteilt. Diese Zerlegungsteile<br />

– unabhängig davon, in welcher Art sie tatsächlich konstruiert werden – werden<br />

hier Baueinheiten genannt. Dieser Begriff ist kein feststehender Begriff in der Informatik;<br />

er wurde hier bewusst gewählt, um die grundlegenden Eigenschaften der Zerlegungsteile unabhängig<br />

von der Realisierung entsprechend eines gewählten Programmierstils zu beschreiben.<br />

Keine Softwareentwicklung ist möglich ohne ein Verständnis für das Prinzip, Baueinheiten<br />

zu konstruieren und sie zu benutzen. Eine sinnvoller Entwurf solcher Baueinheiten<br />

nutzt dabei die Chance aus, dass die Außensicht und die Innensicht dieser Baueinheiten<br />

durch Kapselung weitgehend entkoppelt werden kann. Die Sicht von außen beantwortet die<br />

Frage nach dem ” Was?“, die Sicht von innen die Frage nach dem ” Wie?“: Was leistet die<br />

Baueinheit? Wie verwirklicht sie es?<br />

Anschließend wird gezeigt, wie in drei unterschiedlichen Programmierstilen solche Baueinheiten<br />

gebaut und benutzt werden.<br />

• In der Prozeduralen Programmierung (Unterkap. 2) baut man sog. Funktionen<br />

oder Prozeduren, die jeweils einen kleinen oder auch größeren Teil des Lösungsweges<br />

beinhalten, nämlich die Kapselung einer Reihe von Aktionen.<br />

• Die Modulare Programmierung (Unterkap. 3) kapselt mehrere zusammengehörige<br />

Funktionen mit den zugehörigen Daten. Diese Daten sind je Modul genau einmal<br />

vorhanden.<br />

• Die Objektorientierte Programmierung (Unterkap. 4) generiert ebenso Kapseln<br />

aus Funktionen und Daten, deren Bauplan hier in Form sog. Klassen festgelegt wird.<br />

Zur Laufzeit können aus diesen Klassen (Bauplänen) beliebig viele Objekte erzeugt<br />

werden (mit jeweils einem eigenen Datensatz), die unabhängig voneinander benutzt<br />

und zerstört werden können.<br />

Außerdem wird in Unterkap. 3 der Begriff der Speicherklasse zur Charakterisierung der<br />

Art und Dauer der Speicherplatzreservierung für Daten zur Laufzeit eingeführt. Es werden<br />

dabei zwei verschiedene Speicherklassen vorgestellt: “automatisch“ (begrenzte Lebensdauer<br />

zur Laufzeit mit automatischer Verwaltung durch das Laufzeitsystem) und ” statisch“<br />

(Speicherplätze während der gesamten Programmlaufzeit vorhanden).<br />

6.1 Entwurf von Systemen: Baueinheiten und Geheimnisprinzip<br />

(6.10) Übb Größere Programme sind für den Menschen nur dann beherrschbar, wenn das Problemfeld<br />

in Teile aufgeteilt wurde; die Art dieser Zerlegung hängt stark vom Programmierstil<br />

ab ((6.11). Diese Zerlegungsteile werden hier Baueinheiten genannt.<br />

Völlig unabhängig vom Programmierstil ist es sinnvoll (eigentlich sogar zwingend), diese<br />

Baueinheiten gekapselt zu konstruieren; dadurch wird es möglich, dass die zwei Sichten auf<br />

sie (nämlich die von außen und die von innen) weitgehend entkoppelt sind. Die erste Sicht<br />

beantwortet die Frage nach dem ” Was?“ (d. h. Was tut die Baueinheit?), die zweite nach<br />

dem ” Wie?“ (d. h. Wie verwirklicht sie es?), s. (6.12). Durch die weitgehende Entkopplung<br />

der Außen- von der Innensicht (6.13) ist die Benutzung der Baueinheit und ihre Konstruktion<br />

unabhängig voneinander; sie kann z. B. auch durch verschiedene Personen(gruppen)<br />

geschehen. Für die Benutzung (Anwendung) ist nur die Kenntnis des Was nötig, für die<br />

Konstruktion benötigt man die Beantwortung des Wie.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 66<br />

(6.11) Entwurf von Systemen<br />

Entsprechend der Eigenart des Menschen, nur einen begrenzten Bereich gleichzeitig erfassen<br />

und beschreiben zu können, gibt es für ihn nur eine einzige Möglichkeit, komplexe Systeme<br />

zu beherrschen, nämlich diese logisch (und auch physikalisch) zu unterteilen, um sie<br />

anschließend wieder zusammenzusetzen.<br />

Anhand des Kriteriums, wie man unterteilt, unterscheiden sich die verschiedenen Programmierstile;<br />

dieses Kriterium kann sogar einer ” Weltanschauung“ unterliegen, nämlich einer<br />

Art, wie man die Welt sieht und begreift. Zwei Arten seien herausgegriffen, da sie für diese<br />

Vorlesung wichtig sind:<br />

• Ablauforientierte Zerlegung, ” strukturierte Programmierung“<br />

(auch algorithmische oder prozedurale Zerlegung):<br />

Die Zerlegung geschieht anhand des Handlungsablaufs, des Tuns.<br />

• Objektorientierte Zerlegung, ” objektorientierte Programmierung“:<br />

Die Zerlegung geschieht anhand der in der nachzubildenden Welt anzutreffenden, mit<br />

gewisser Eigenständigkeit versehenen Einheiten ( ” Objekte“ wie Dinge, Menschen, Ereignisse,<br />

gedachte Einheiten, z. B. Organisationseinheiten). Diesen Objekten übergibt man<br />

weitgehende Eigenverantwortung für ihr Handeln; sie kommunizieren miteinander, indem<br />

sie Nachrichten austauschen und so andere Objekte bitten, gewisse (Teil-)Aufgaben<br />

zu übernehmen, die diese ggf. weiter delegieren dürfen.<br />

Ferner gibt es die datenorientierte Zerlegung, dazu auch unterschiedliche Mischformen.<br />

(6.12) Bei jeder Zerlegungart – unabhängig vom Zerlegungskriterium (6.11) – ist es zweckmäßig, in<br />

sich abgeschlossene Baueinheiten zu erstellen. Diese Baueinheiten enthalten häufig auch<br />

wieder Baueinheiten feinerer Granularität oder sind in solchen größerer Granularität enthalten.<br />

(6.13)<br />

Die Baueinheiten (Steckerkomponenten, Prozeduren/Funktionen, Module, Objekte) kann<br />

man, wenn sie sinnvoll entworfen sind, in zwei weitgehend voneinander entkoppelten Aspekten<br />

betrachten:<br />

• Von außen: ” Was?“<br />

der Benutzer oder Anwender der Baueinheit muss wissen, was die Baueinheit leistet,<br />

nicht wie sie es verwirklicht ( ” black box“). Ferner kann er diese Baueinheit beliebig<br />

oft einsetzen, ohne sie jeweils neu konstruieren zu müssen. Notwendig für den richtigen<br />

Einsatz: genaue Kenntnis der Übergabestelle (Schnittstelle).<br />

• Von innen: ” Wie?“<br />

der Erbauer oder Konstrukteur der Baueinheit muss wissen, wie er die Anforderungen<br />

verwirklicht, die der Benutzer erwartet, z. B. welchen Algorithmus er einsetzt, welche<br />

Daten er verwendet. Er muss nicht die Anwendungsfälle kennen, solange er die Schnittstellenvereinbarung<br />

beachtet.<br />

Anm Entsprechend dieser beiden Betrachtungsweisen soll jetzt der Erbauer einer Baueinheit von<br />

dem Benutzer dieser Baueinheit (ein Programmierer mit nicht unbedingt minder guten Programmierkenntnissen)<br />

unterschieden werden.<br />

Bsp In vielen Programmiersprachen ist die Sinusfunktion implementiert:<br />

• Von außen: der Benutzer von sin(x) muss nur wissen, dass x im Bogenmaß anzugeben ist.<br />

• Von innen: der Erbauer muss ein geeignetes Näherungspolynom für die Sinus-Berechnung implementieren.<br />

Damit dieses Zusammenspiel zwischen Außen- und Innenbetrachtungsweise (zwischen Benutzer<br />

und Erbauer) richtig läuft, muss die Schnittstelle genau festgelegt sein, nämlich die<br />

Übergabestelle der Informationen zwischen außen und innen, zwischen der Benutzer- und<br />

der Erbauer-Sicht.<br />

(a) Es hat große Vorteile, wenn das Innere einer Baueinheit dem Benutzer gegenüber weitgehend<br />

verborgen ist (Geheimnisprinzip), wenn ihm nur ein kleiner Teil direkt zugänglich ist<br />

(Öffentlichkeit):


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 67<br />

• Hierdurch können Benutzer und Erbauer verschiedene Personen oder gar verschiedene<br />

Programmierergruppen sein, die mehr oder weniger unabhängig voneinander arbeiten<br />

können.<br />

• Das Innere kann später geändert werden, ohne dass die vielen Stellen der Benutzung<br />

der Baueinheit geändert werden müssen. Es kann sich zudem beispielsweise später herausstellen,<br />

dass das Innere – inzwischen – nicht gut genug ist oder bestimmte, zunächst<br />

unwichtige Spezialfälle nicht berücksichtigt.<br />

Anm Das Jahr-2000-Problem ist ein gutes Beispiel hierfür. Allerdings konnten Programmierer<br />

der sechziger und siebziger Jahre kaum ahnen, dass ihre Programme bis zum Jahr 2000<br />

immer noch genutzt werden; ferner gab es damals kaum das Problembewusstsein Verborgenheit/Öffentlichkeit<br />

und auch kaum Unterstützung von Lösungsmöglichkeiten durch Programmiersprachen,<br />

dazu war Speicherplatz zum temporären und zum dauerhaften Abspeichern<br />

sehr teuer.<br />

(b) Dieser Schutz des Inneren vor dem Benutzer wird – je nach Möglichkeiten der Programmiersprache<br />

und je nach (Er-)Kenntnis des Erbauers – mehr oder weniger gut sein:<br />

(1) Gut ist ein Schutz, den der Benutzer nicht aus Gedankenlosigkeit oder Unachtsamkeit umgeht.<br />

Anm Auch Benutzer-Programmierer sind Menschen, die nicht immer an alles denken!<br />

(2) Gegebenenfalls wäre ein Schutz noch besser, der dem Benutzer auch ein gewolltes Eindringen<br />

in das Innere verwehrt – und sei der Zweck des Eindringens auch noch so hehr: z. B.<br />

” Tricks“ beim Programmieren, damit die Ausführung (in diesem gerade betrachteten Anwendungsfall)<br />

schneller läuft.<br />

Anm<br />

” Tricks“ ehren zwar zunächst den Programmierer, danach stellen sich aber meist große Nachteile<br />

ein, wenn nämlich ein anderer Programmierer (oder nach einiger Zeit er selbst) Fehler<br />

suchen oder auch nur die Funktionalität erweitern soll.<br />

Heutzutage ist die Hardware meist so schnell, dass häufig eine noch schnellere Ausführung<br />

weniger wichtig ist als die gute Wartbarkeit.<br />

(c) In Bezug auf spätere Wartbarkeit der Software (Änderbarkeit, Fehlersuche), dazu Erweiterungsfähigkeit,<br />

Wiederverwendbarkeit in anderen Projekten kann man folgende Regel<br />

aufstellen:<br />

• Verborgenheit: so viele Einzelheiten wie irgend möglich sollten dem Benutzer gegenüber<br />

versteckt sein. Hierfür wurde der Begriff ” information hiding“ geprägt.<br />

• Öffentlichkeit: nur so wenig wie gerade noch nötig sollte dem Benutzer zugänglich<br />

sein, und zwar (i. a.) nur über die vereinbarte Schnittstelle.<br />

(6.14) Die Konsequenzen aus (6.12, 6.13) sind sehr verschieden. Sie hängen ab<br />

• vom Anwendungsfall,<br />

• von der ” Weltanschauung“ (6.11) des Entwicklers,<br />

• von der Weitsichtigkeit des Programmierers,<br />

• von der eingesetzten Programmiersprache,<br />

• von den Kenntnissen innerhalb der benutzten Programmiersprache.<br />

Drei wichtige Programmierstile und ihre zugehörigen Baueinheiten sollen näher betrachtet<br />

werden:<br />

• die Prozedurale Programmierung (Kap. 6.2),<br />

• die Modulare Programmierung (Kap. 6.3),<br />

• die Objektorientierte Programmierung (Kap. 6.4).<br />

6.2 Prozedurale Programmierung<br />

(6.20) Übb In der Prozeduralen Prgrammierung bestehen die Baueinheiten jeweils aus einem<br />

Satz von Aktionen. Sie beschreiben daher das Tun, um einen kleinen oder größeren Teil der<br />

Problemlösung auszuführen.<br />

Je nachdem, ob der Satz der Aktionen an den Benutzer einen Wert zurückgibt oder nicht,<br />

unterscheidet man zwei verschiedene Arten dieser Baueinheiten: Funktionen und Prozeduren


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 68<br />

(6.21)<br />

(6.21). Der Punkt (6.22) beschreibt, wie die Konstruktion von Funktionen oder Prozeduren<br />

( ” Definition“ genannt) und deren Benutzung ( ” Aufruf“ genannt) als Pseudocode dargestellt<br />

werden soll.<br />

Die Schnittstelle zwischen der Außen- und Innenansicht wird in Form von Parametern (6.23)<br />

verwirklicht (zusätzlich zur Möglichkeit der Wertrückgabe). Die Parameter beim Aufruf<br />

(d. h. bei der Benutzung der Funktion) heißen aktuelle Parameter, die bei der Definition<br />

formale Parameter. Sehr wichtig ist es hierbei, die beiden hier vorgestellten Arten der Parameterübergabe<br />

zu verstehen und anwenden zu können: Wertübergabe (Kopieren des Wertes<br />

des aktuellen Parameters auf den formalen) und Referenzübergabe (der formale Parameter<br />

wird zu einem Synonym für den aktuellen).<br />

Ein Beispiel für die (geschachtelte) Zerlegung einer Problems in Prozeduren und Funktionen<br />

zeigt (6.24).<br />

(a) Wendet man die ablauforientierte Zerlegung (6.11) an – das kann bei kleineren Problemkreisen<br />

vernünftig sein –, so ist es häufig sinnvoll, Teile des Handlungsablaufs zu kapseln und mit<br />

einem Namen zu belegen, um diese mehrfach benutzen zu können. Solche Baueinheiten nennt<br />

man hierbei – je nach Sprache – Funktionen, Prozeduren, Unterprogramme.<br />

(b) Wie in den meisten Programmiersprachen wollen wir zwei verschiedene Arten dieser<br />

Baueinheiten unterscheiden:<br />

• Funktion: Die Baueinheit liefert – nach Durchführung ihrer Aufgabe – einen Wert<br />

zurück, der dem Benutzer zur Verfügung steht, wie z. B. in der Mathematik bei Funktionen<br />

üblich (Funktionswert). Dieser Wert wird üblicherweise ” Rückgabewert“ genannt.<br />

• Prozedur: Die Baueinheiten liefert – nach Durchführung ihrer Aufgabe – keinen Wert<br />

an der Benutzungsstelle zurück, beispielsweise ein Druckauftrag. Hierbei gibt es daher<br />

keinen Rückgabwert.<br />

Die Namen für diese zwei Arten sind in den Programmiersprachen unterschiedlich, z. B.:<br />

• Pascal (Namen wie schon oben benutzt): Funktion, Prozedur 〈function, procedure〉,<br />

• Fortran: Funktion, Unterprogramm 〈function, subroutine〉,<br />

• C ++ und C: Funktion 〈function〉 für beide Arten.<br />

Bei den sprachunabhängigen Betrachtungen übernehmen wir die Bezeichnungen Funktion<br />

und Prozedur. Als gemeinsamer Oberbegriff soll ” Funktion“ dienen.<br />

(c) Das Geheimnisprinzip soll dadurch gewahrt sein, dass als Schnittstelle, d. h. für die Kommunikation<br />

zwischen außen und innen (6.12), nur die sog. Parameterliste dienen soll, dazu<br />

(wenn vorhanden) der Rückgabewert der Funktion; Näheres s. (6.22, 6.23).<br />

(d) Einen Programmierstil, der im wesentlichen nur solche Baueinheiten einsetzt, nennt man<br />

Prozedurale Programmierung.<br />

Gute Beispiele für die Anwendung dieses Programmierstils sind Berechnungen aus Eingabewerten,<br />

z. B. sin(x). Der Berechnungsalgorithmus ist völlig von der Benutzung entkoppelt.<br />

(6.22) Vereinbarung über die Formulierung von Prozeduren und Funktionen in der textuellen Darstellung:<br />

(a) Bau(-plan) der Baueinheit, meist Definition 〈definition〉 genannt:<br />

PROZEDUR neuProz(formalPar1 TYP typ1, formalPar2 TYP typ2)<br />

// . . .<br />

// optional:<br />

WENN . . . DANN<br />

RÜCKSPRUNG<br />

// . . .<br />

// optional:<br />

RÜCKSPRUNG<br />

ENDE PROZEDUR


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 69<br />

FUNKTION sonderFkt(formalPar1 TYP typ1, formalPar2 TYP typ2) TYP typFkt<br />

// . . .<br />

// optional:<br />

WENN . . . DANN<br />

RÜCKSPRUNG WERT . . .<br />

// . . .<br />

RÜCKSPRUNG WERT . . .<br />

ENDE FUNKTION<br />

Beide Baueinheitarten können sog. formale Parameter haben, durch die mit ihnen kommuniziert<br />

werden kann; oben sind jeweils zwei Parameter angegeben. Näheres dazu s. (6.23).<br />

Bei beiden Baueinheiten gibt es das neue Schlüsselwort RÜCKSPRUNG; es soll bedeuten,<br />

dass der Steuerfluss an dieser Stelle die Baueinheit verlässt.<br />

Bei der Prozedur kann am Ende (direkt vor ENDE PROZEDUR) auf RÜCKSPRUNG<br />

verzichtet werden, da dort sowieso die Prozedur verlassen wird.<br />

Bei der Funktion muss mit RÜCKSPRUNG auch der Wert angegeben werden, der zurückgegeben<br />

werden soll; daher kann am Ende (direkt vor ENDE FUNKTION) nicht auf RÜCK-<br />

SPRUNG verzichtet werden, das sonst kein Rückgabewert vorhanden wäre. Außerdem hat<br />

der Funktionsname – nicht der Prozedurename – einen TYP, nämlich den Typ des Rückgabewertes,<br />

im Beispiel ” typFkt“.<br />

(b) Die Benutzung oder Anwendung der Baueinheit wird Aufruf 〈call〉 genannt; sie geschieht<br />

durch Nennung des Namens, dazu, wenn nötig, mit Angabe der zugehörigen sog. aktuellen<br />

Parameter:<br />

(6.23) Parameter<br />

neuProz(aktPar1, aktPar2)<br />

sonderFkt(aktPar1,aktPar2)<br />

ANFANG<br />

VARIABLE x, y TYP Ganzzahl<br />

x ← 23<br />

neuProz(16, 2∗x)<br />

y ← sonderFkt(x, 34) + 3<br />

Schreibe x, y<br />

ENDE<br />

(a) Es muss streng zwischen den beiden Parameterarten unterschieden werden:<br />

• formale Parameter – sie werden bei der Definition eingeführt, sie sind Formen, Hüllen,<br />

noch ohne Inhalt,<br />

• aktuelle Parameter – sie werden beim Aufruf angegeben, sie sind die Inhalte zu den<br />

Hüllen der formalen Parameter.<br />

Jedem aktuellen Parameter (beim Aufruf) entspricht genau ein formaler Parameter (in der<br />

Definition); Anzahl und Reihenfolge, ferner (nach ggf. erlaubter Anpassung) jeweils auch<br />

der Typ müssen übereinstimmen.<br />

(b) Denkbar sind viele verschiedene Arten der Verknüpfung oder der Kopplung zwischen einem<br />

aktuellen Parameter und seinem zugehörigen formalen Parameter. Daher unterschiedet<br />

man in der Informatik verschiedene Parameterübergabearten. In dieser Vorlesung sollen<br />

zwei von ihnen besprochen werden, nämlich die durch die Sprache C ++ unterstützten<br />

Parameterübergabearten:<br />

• Übergabe per Wert ( ” CALL BY VALUE“): der Wert des aktuellen Parameters<br />

wird kopiert auf den eigenen Speicherplatz des formalen Parameters, danach gibt es<br />

keine Verbindung mehr zwischen aktuellem und formalem Parameter.<br />

• Übergabe per Referenz ( ” CALL BY REFERENCE“): der formale Parameter<br />

wird zu einem Synonym des aktuellen Parameter, zu einer sog. Referenz; alles, was<br />

mit dem formalen Parameter geschieht, geschieht in Wirklichkeit mit dem aktuellen<br />

Parameter.<br />

Die Auswirkungen dieser beiden Parameterübergabearten sind je nach Zugriff innerhalb der<br />

Prozedur/Funktion unterschiedlich:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 70<br />

• Greift die Funktion nur lesend auf den formalen Parameter zu, so gibt es im Ergebnis<br />

keinen Unterschied zwischen den beiden Übergabarten. Es gibt lediglich Unterschiede<br />

im Speicher- und Zeitverhalten.<br />

• Wird jedoch der formale Parameter innerhalb der Baueinheit verändert (Zugriff schreibend),<br />

z. B. durch Zuweisung, so bemerkt im Falle der Wertübergabe der aktuelle<br />

Parameter nichts davon, anders jedoch im Falle der Referenzübergabe: geändert wird<br />

nämlich in Wirklichkeit der aktuelle Parameter, da der formale Parameter ein Synonym<br />

für ihn ist.<br />

(6.24) Bsp Ein Roboter soll folgende Aufgabe erfüllen:<br />

Zeichnen der untenstehenden Figur<br />

(zwei geschachtelte Quadrate):<br />

20 cm<br />

(Ruhestellung)<br />

Das gleiche<br />

mit Bemaßung:<br />

4 cm<br />

20 cm<br />

15 cm<br />

Der Roboter soll folgende elementare Algorithmen (2.54) kennen:<br />

neu fährt zum definierten Anfangspunkt<br />

senken senkt Schreibstift<br />

heben hebt Schreibstift<br />

längs Bewegung um eine Position des Längsschrittmotors in aktueller Richtung<br />

dreh Bewegung um eine Position des Drehschrittmotors in Linksrichtung<br />

Der Algorithmus:<br />

ALGORITHMUS ZweiQuadrate<br />

PROZEDUR bewegung (länge TYP Ganzzahl)<br />

// Bewegt Schreibstift um länge cm in aktueller Richtung<br />

VARIABLE n TYP Ganzzahl<br />

n ← länge/(ein Schritt des Längsschrittmotors in cm)<br />

WIEDERHOLE n-mal<br />

längs<br />

ENDE PROZEDUR<br />

PROZEDUR drehung (winkel TYP Ganzzahl)<br />

// Bewegt Bewegungsrichtung um winkel Grad (in math. pos. Richtung)<br />

VARIABLE n TYP Ganzzahl<br />

n ← winkel/(ein Schritt des Drehschrittmotors in Grad)<br />

WIEDERHOLE n-mal<br />

dreh<br />

ENDE PROZEDUR<br />

PROZEDUR zeichneQuadrat (kante TYP Ganzzahl)<br />

// Zeichnet Quadrat der Kantenlänge kante cm<br />

senken<br />

WIEDERHOLE 4-mal<br />

bewegung(kante)<br />

drehung(90)<br />

heben<br />

ENDE PROZEDUR


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 71<br />

PROZEDUR schrägBewegung (deltaX TYP Ganzzahl, deltaY TYP Ganzzahl)<br />

// Bewegt Schreibstift um deltaX cm in x-Richtung,<br />

// um deltaY cm in y-Richtung<br />

bewegung(deltaX)<br />

drehung(90)<br />

bewegung(deltaY)<br />

drehung(270)<br />

ENDE PROZEDUR<br />

ANFANG<br />

neu<br />

schrägBewegung(20,15)<br />

zeichneQuadrat(20)<br />

schrägBewegung(8,8)<br />

zeichneQuadrat(4)<br />

neu<br />

ENDE<br />

Da bei den formalen Prozedurparametern nur lesender Zugriff geschieht, ist die Parameterübergabeart<br />

(Wert oder Referenz) unerheblich.<br />

Die beiden ersten Prozeduren sind am wenigsten komplex, die beiden anderen benutzen<br />

diese einfacheren Baueinheiten. Das (Haupt-)Programm (zwischen ANFANG und ENDE)<br />

benutzt fast nur noch die komplexeren Prozeduren. Eine solche Schachtelung von Baueinheiten<br />

geschieht in der Praxis sehr häufig.<br />

(6.25) Zur Verwirklichung von Funktions-Baueinheiten in C ++ siehe (Kap. 7).<br />

6.3 Speicherklassen, Modulare Programmierung<br />

(6.30) Übb Es ist zur Lösung mancher Aufgaben sinnvoll, mindestestens zwei verschiedene Speicherklassen<br />

zu unterscheiden (6.31), d. h. verschiedene Arten der Lebensdauer und Verwaltung<br />

von Datenspeicherplätzen. Speicherplätze, die während der gesamten Programmlaufzeit bestehen<br />

bleiben, gehören zur statischen Speicherklasse. Daten der automatischen Speicherklasse<br />

benutzen Speicherplatz nur während eines Teils der Programmlaufzeit; die Verwaltung<br />

(Reservierung und Freigabe) des Speichers erfolgt hierbei automatisch durch das Laufzeitsystem.<br />

In (6.32) wird beispielhaft gezeigt, wie statischer und automatischer Speicherplatz<br />

sinnvoll benutzt werden kann.<br />

(6.31)<br />

Danach wird die Modulare Programmierung (6.33) kurz vorgestellt. Hierbei fasst man mehrere<br />

Funktionen mit den Daten zusammen, die durch sie bearbeitet werden sollen. In C ++/C werden<br />

solche Module durch einzelne Programmdateien (Übersetzungseinheiten) verwirklicht.<br />

Ein Beispiel (6.34) in Pseudocode verdeutlicht diese Modulbildung.<br />

(a) Mit der Prozeduralen Programmierung ist jedoch beispielsweise der Aufbau eines Kellerspeichers<br />

oder (synonym) Stapelspeichers 〈stack〉 schwierig. Will man die Daten dem äußeren<br />

(ungewollten oder auch gewollten) Zugriff entziehen, so ist das nur möglich, wenn man eine<br />

” Funktion mit Gedächtnis“ bauen kann. Man muss daher statischen Speicherplatz zulassen,<br />

der unabhängig von der Möglichkeit des aktuellen Zugriffs über die gesamte Programmlaufzeit<br />

erhalten bleibt: statische Speicherklasse.<br />

△! Dadurch wird allerdings das Funktionskonzept aus dem vorigen Unterkapitel abgeändert<br />

bzw. erweitert: ein Funktionsaufruf hängt nicht mehr nur von den Parameterwerten,<br />

sondern nunmehr ggf. auch von früheren Funktionsaufrufen ab. Das<br />

kann bei unübersichtlicher Programmierung gefährlich sein.<br />

(b) Speicherplätze, die nur während des Durchlaufs einer Funktion reserviert bleiben, unterliegen<br />

der sog. automatischen Speicherverwaltung: automatische Speicherklasse; dies ist


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 72<br />

eine Form der dynamischen Speicherverwaltung. Der Programmierer braucht sich um Reservierung<br />

und Freigabe dieser Speicherplätze nicht zu kümmern, das geschieht automatisch<br />

durch das (Laufzeit-)System. Die Variablen aller bisherigen Programme gehören zu dieser<br />

Speicherklasse.<br />

(6.32) In der textuellen Form der Programmbeschreibung soll das Wort STATISCH die Reservierung<br />

des zugehörigen Speicherplatzes während der ganzen Programmlaufzeit bewirken<br />

(statische Speicherklasse). Die Beschränkung des Zugriffs auf Anweisungen nur innerhalb<br />

der Funktion – völlig unabhängig vom Statisch-Sein – soll durch Ansiedlung des Speicherplatzes<br />

innerhalb der Funktion erfolgen.<br />

Bsp Kellerspeicher, gebaut mit statischen Variablen (zum Schlüsselwort FELD s. (2.53)):<br />

// Definition der Funktion kellerSpeicher<br />

FUNKTION kellerSpeicher(schiebenJaNein TYP Boolesch,<br />

wert TYP Ganzzahl) TYP Ganzzahl<br />

STATISCH VARIABLE speich TYP Ganzzahl-FELD[100]<br />

STATISCH VARIABLE zähler TYP Ganzzahl (Erstbelegung 0)<br />

WENN schiebenJaNein DANN<br />

// Schieben hier ohne Fehlerbehandlung (Ueberlauf)<br />

speich[zähler] ← wert<br />

zähler ← zähler+1<br />

SONST<br />

// Holen hier ohne Fehlerbehandlung<br />

zähler ← zähler-1<br />

wert ← speich[zähler]<br />

RÜCKSPRUNG WERT wert<br />

ENDE FUNKTION<br />

// Benutzung der Funktion kellerSpeicher<br />

ANFANG<br />

VARIABLE zahl TYP Ganzzahl<br />

Lies zahl<br />

kellerSpeicher(wahr,zahl)<br />

kellerSpeicher(wahr,199)<br />

Schreibe kellerSpeicher(falsch,0) // 199<br />

Schreibe kellerSpeicher(falsch,0) // eingeles. Zahl<br />

ENDE<br />

(6.33) Ein Nachteil der Funktion kellerSpeicher aus (6.32) ist es, dass das statische Array nicht zwei<br />

getrennten Funktionen zur Verfügung steht, z. B. schiebe(wert) und hole() 〈push, pop〉, wobei<br />

die erste dem Aufbau, die zweite dem Abbau des Speichers dienen soll. Eine Auslagerung<br />

des Speichers (hier als Array speich) aus der Funktion würde dieses Problem zwar lösen;<br />

dann wäre es jedoch möglich, auf den Speicher ungewollt oder gewollt zuzugreifen, ohne<br />

diese Zugriffsfunktionen (die auch die zugehörige Fehlerbehandlung einschließen sollten) zu<br />

benutzen.<br />

Das Problem kann man dadurch beheben, dass man eine Möglichkeit bereitstellt, mehrere<br />

Funktionen und zugehörige Daten zu kapseln in der Art, dass nur Teile daraus öffentlich,<br />

andere Teile verborgen sind. Dieses wird durch die Modulare Programmierung erreicht,<br />

nämlich die Aufteilung von Funktionen und der Daten auf mehrere Module mit zusätzlicher<br />

Angabe der Zugriffserlaubnis auf jeden der Bestandteile. Diese veränderte Programmiersichtweise<br />

ergibt sich demnach durch Verlagern des Schwerpunkts der Aufmerksamkeit vom<br />

Entwurf von Funktionen auf die Organisation von Daten, die zugehörige Zerlegung ist mehr<br />

datenorientiert (6.11).<br />

Ein solches Modul besteht aus einem Satz von verwandten Funktionen und den von ihnen<br />

zu manipulierenden Daten. Nun sind die Funktionen meist nicht mehr allein zu gebrauchen,<br />

nicht mehr ohne weiteres aus dem Modul herauslösbar (z. B. für andere Programme), sondern<br />

nur im Rahmen des gesamten Moduls.<br />

In der Praxis werden solche Module meist in Form von Übersetzungseinheiten 〈translation<br />

units〉 verwirklicht.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 73<br />

(6.34) In der textuellen Form der Programmbeschreibung soll ein Modul durch einen eigenen Kasten<br />

dargestellt werden. Zusätzlich werden die Zugriffsspezifizierungen VERBORGEN und<br />

ÖFFENTLICH eingeführt. Innerhalb des Kastens kann auf jedes Element unabhängig von<br />

der Zugriffsspezifizierung zugegriffen werden. Von außen sind nur die öffentlichen Bestandteile<br />

(direkt) zugänglich. Die Gesamtheit der öffentlichen Bestandteile heißt Schnittstelle<br />

des Moduls.<br />

Bsp Kellerspeicher aus (6.32), als Modul formuliert:<br />

VERBORGEN STATISCH VARIABLE speich TYP Ganzzahl-FELD[100]<br />

VERBORGEN STATISCH VARIABLE zähler TYP Ganzzahl (Erstbelegung 0)<br />

ÖFFENTLICH PROZEDUR schiebe(wert TYP Ganzzahl)<br />

// hier ohne Fehlerbehandlung (Überlauf)<br />

speich[zähler] ← wert<br />

zähler ← zähler+1<br />

RÜCKSPRUNG<br />

ENDE PROZEDUR<br />

ÖFFENTLICH FUNKTION hole() TYP Ganzzahl<br />

// hier ohne Fehlerbehandlung<br />

zähler ← zähler-1<br />

RÜCKSPRUNG WERT speich[zähler]<br />

ENDE FUNKTION<br />

// Benutzung des Kellerspeichers<br />

ANFANG<br />

VARIABLE zahl TYP Ganzzahl<br />

Lies zahl<br />

schiebe(zahl)<br />

schiebe(199)<br />

Schreibe hole() // 199<br />

Schreibe hole() // eingeles. Zahl<br />

ENDE<br />

(6.35) Zur Verwendung verschiedener Speicherklassen und zur Verwirklichung von Modulen (Übersetzungseinheiten)<br />

in C ++ siehe (Kap. 8).<br />

6.4 Objektorientierte Programmierung<br />

(6.40) Übb Die Objektorientierte Programmierung erlaubt es wie die Modulare Programmierung,<br />

Funktionen und Daten zu kapseln. Im Gegensatz zur Modularen Programmierung,<br />

wo jedes Modul genau einen Satz von Daten enthält, wird hier zwischen der Beschreibung<br />

der Struktur von Daten und Funktionen, der sog. Klassendefinition, und der tatsächlichen<br />

Erzeugung des zugehörigen Speicherplatzes zur Laufzeit, der Erzeugung der sog. Objekte,<br />

unterschieden. Hier können beliebig viele Objekte der gleichen Bauart (derselben Klasse)<br />

unabhängig voneinander erzeugt, benutzt und wieder vernichtet werden.<br />

Eine gute Kapselung liegt vor, wenn Daten ( ” Attribute“) von außen niemals direkt zugänglich<br />

sind, sondern immer nur über zugehörige Funktionen ( ” Methoden“), die dann auch die<br />

Kontrolle über den Zugriff ausüben, z. B. zur Wahrung der Konsistenz der internen Daten.<br />

Zusätzlich wird die Objektorientierung anhand eines Beispiels (6.43) näher erläutert.<br />

(6.41) Die Modulare Programmierung (voriges Unterkapitel) ermöglicht es zwar, eine Zugriffskontrolle<br />

von außen zu definieren. Ein wichtiger Nachteil ist dadurch jedoch immer noch nicht<br />

behoben: es können nicht mehrere Kellerspeicher nebeneinander existieren. Ein Notbehelf<br />

wäre zwar die Einführung mehrerer verborgener Arrays; jedoch müsste dann die Maximalzahl<br />

der jemals gleichzeitig benötigten Kellerspeicher von vornherein feststehen.<br />

(6.42)<br />

(a) Die Objektorientierte Programmierung gibt eine Lösung hierfür: es wird ein neuer Typ<br />

Klasse eingeführt, der sowohl Daten als auch zugehörige Zugriffsfunktionen beinhaltet, und


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 74<br />

zwar mit der Möglichkeit der expliziten Zugriffskontrolle VERBORGEN oder ÖFFENT-<br />

LICH für jedes seiner Daten- und Funktions-Elemente. Innerhalb der Klasse kann auf jedes<br />

Element, auch auf ein verborgenes, zugegriffen werden, von außen direkt nur auf öffentliche<br />

Elemente. Eine Variable dieses Typs nennt man Objekt. Jedes Objekt hat seinen eigenen<br />

Satz an Daten. Die Zugriffsfunktionen sind dagegen i. a. nur einmal je Klasse zentral<br />

vorhanden.<br />

Sprachenunabhängig werden die Daten einer Klasse meist Attribute, die Funktionen meist<br />

Methoden genannt. In C ++ nennt man sie auch (Daten-)Elemente bzw. Elementfunktionen.<br />

(b) Sinnvoll im Sinne einer guten Trennung zwischen Verborgenheit und Öffentlichkeit (6.13) ist<br />

es hierbei, alle Attribute als VERBORGEN zu kennzeichnen und den Zugriff darauf nur<br />

über öffentliche Zugriffsfunktionen zu erlauben.<br />

(c) Die oben beschriebenen Abstraktionen reichen eigentlich noch nicht aus, um den Namen Objektorientierung<br />

zu vergeben. Sehr wesentlich ist noch die in (6.45a) angedeutete Erweiterung<br />

durch Vererbung.<br />

↑↑1 Seit den Anfängen geistert bei den Progammierern die Idee der Trennung der Daten von<br />

den zugehörigen Funktionen durch die Köpfe. Ein nicht durch Programmiertechnik Infizierter<br />

käme nie auf die Idee, dieses zu tun. In der realen Welt bilden (fast) immer Daten und<br />

zugehörige Funktionen (d. h. die Beschreibung des Zustands und die Funktionalität) eine<br />

Einheit, z. B. hat ein Auto eine Höchstgeschwindigkeit, ein maximales Tankvolumen, einen<br />

Durchschnittsverbauch (Daten); zusätzlich fährt es, hält es, verbraucht Treibstoff (Funktionen).<br />

Zu einem realen Auto gehören sowohl die Daten als auch die Funktionen.<br />

↑↑2 Der Übergang zur Objektorientierung (oder Objekttechologie) sollte sich nicht nur auf die<br />

Objektorientierte Programmierung (OOP) beschränken. Eine sinnvolle Ausnutzung der<br />

zugehörigen Ideen ist nur möglich, wenn schon weit vor der Programmierung (Implementation)<br />

der Ausschnitt der realen Welt, der mit der zu erstellenden Software beeinflusst werden<br />

soll, im Sinne der neuen Sichtweise beschrieben wird (OOA, Objektorientierte Analyse)<br />

und das daraus erhaltene Modell auf Software-Erfordernisse verändert bzw. erweitert wird<br />

(OOD, Objektorientierter Entwurf 〈design〉). Manche Autoren sprechen berechtigterweise<br />

von einer Änderung der Sichtweise der Welt, der Weltanschauung (wörtlich gemeint), von<br />

einem Wechsel des ” Weltbildes“ beim Entwickler: ” Paradigmenwechsel“, vgl (6.11).<br />

↑↑3 In der Modellierung der Objekttechologie geht es hauptsächlich darum, die reale Welt als ein<br />

Netz von interagierenden Objekten zu beschreiben. Jedes Objekt (als ein Exemplar, gebaut<br />

nach dem Baumuster einer Klasse) hat eine gewisse Eigenständigkeit und Eigenverantwortung.<br />

Eine an ihn gerichtete Nachricht oder Botschaft 〈message〉 versteht das Objekt als<br />

eine (höfliche) Aufforderung, entsprechend seiner Funktionalität zu reagieren. Dazu führt es<br />

eine seiner Methoden aus; die Art der Reaktion ist seiner Eigenverantwortung unterstellt, da<br />

die Methode zu ihm gehört, vgl. (6.11).<br />

↑↑4 Die Sichtweise der Welt in der Objekttechologie ist ganzheitlich; sie lässt Objekte als Einheiten<br />

miteinander kommunizieren. Viele Programmierer, die der herkömmlichen Programmiersichtweise<br />

unterliegen, haben große Schwierigkeiten, den Paradigmenwechsel zu vollziehen.<br />

↑↑5 Leider kann in der aktuellen Vorlesung nur wenig auf dieses ” Weltbild“ (Paradigma) eingegangen<br />

werden; intensiver kann dieses in entsprechenden Kursen des Hauptstudiums geschehen.<br />

(6.43) Bsp Kellerspeicher aus (6.34), objektorientiert entworfen:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 75<br />

KLASSE Keller<br />

VERBORGEN VARIABLE speich TYP Ganzzahl-FELD[100]<br />

VERBORGEN VARIABLE zähler TYP Ganzzahl (Erstbelegung 0)<br />

ÖFFENTLICH PROZEDUR schiebe(wert TYP Ganzzahl)<br />

// hier ohne Fehlerbehandlung (Überlauf)<br />

speich[zähler] ← wert<br />

zähler ← zähler+1<br />

RÜCKSPRUNG<br />

ENDE PROZEDUR<br />

ÖFFENTLICH FUNKTION hole() TYP Ganzzahl<br />

// hier ohne Fehlerbehandlung<br />

zähler ← zähler-1<br />

RÜCKSPRUNG WERT speich[zähler]<br />

ENDE FUNKTION<br />

ENDE KLASSE<br />

// Benutzung des Kellerspeichers:<br />

ANFANG<br />

VARIABLE keller1, keller2 TYP Keller<br />

VARIABLE zahl TYP Ganzzahl<br />

Lies zahl<br />

keller1.schiebe(zahl)<br />

keller2.schiebe(199)<br />

Schreibe keller1.hole() // eingeles. Zahl<br />

Lies zahl<br />

keller2.schiebe(zahl)<br />

Schreibe keller2.hole() // eingeles. Zahl<br />

Schreibe keller2.hole() // 199<br />

ENDE<br />

Neu ist das Schlüsselwort KLASSE (und ENDE KLASSE); hierdurch werde eine Klasse<br />

definiert. Der Zugriff auf eine Methode eines Objekts geschehe durch die Aufrufsyntax<br />

ObjektName.MethodenName ( ... ) .<br />

(6.44) Zur Verwirklichung von Klassen-Baueinheiten in C ++ siehe (Kap. 9).<br />

(6.45) ↑↑<br />

(a) Die Einführung des Klassentyps, d. h. die Kapselung von Daten und Funktionen, reicht noch<br />

nicht aus, um die reale Welt genügend einfach und übersichtlich beschreiben zu können.<br />

Manche Autoren sprechen daher bei den bisher geschilderten Gedanken von objektbasierter<br />

Programmierung, die zugehörigen Datentypen werden auch manchmal abstrakte Datentypen<br />

genannt. Die Objektorientierung erhält ihre Mächtigkeit erst durch die Einführung von<br />

Beschreibungsmöglichkeiten für Ähnlichkeiten von Klassen: die Vererbung 〈inheritance〉.<br />

Eine Oberklasse (〈super class〉, in C ++ Basisklasse genannt 〈base class〉) vererbt ihre Merkmale<br />

(Daten und Funktionen) an eine Unterklasse (〈subclass〉, in C ++ abgeleitete Klasse<br />

genannt 〈derived class〉). Diese erhält die Möglichkeit, diese Merkmale zu erweitern oder zu<br />

überschreiben (ersetzen).<br />

(b) Auch zeigt es sich, dass es manchmal hilfreich ist, Klassen zu formulieren, deren Elemente<br />

vom Typ her noch nicht festgelegt sind; eine Festlegung erfolgt erst, wenn die Klasse eingesetzt<br />

wird: Schablone 〈template〉. Schablonen werden insbesondere bei Containerklassen<br />

(oder kurz Containern) wichtig, bei Klassen, die eine Ansammlung von Elementen eines<br />

Typs verwalten.<br />

Die Vererbung wird in (Kap. 11.3) eingeführt, Schablonen können leider nicht besprochen werden.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 76<br />

7 Prozedurale Programmierung<br />

7.0 Überblick<br />

Die prozedurale Programmierung ist – sprachunabhängig – in (Kap. 6.2) näher beschrieben.<br />

Sie setzt im wesentlichen nur Prozeduren oder Funktionen als Baueinheiten ein.<br />

Eine Funktion oder Prozedur – beides in C ++ Funktion genannt – ist eine Zusammenfassung<br />

von Programmcode unter einem Namen, so dass man diesen Code öfters nur unter Nennung<br />

des Namens ausführen kann; hierzu gehören meist auch aktuell immer neu besetzbare<br />

Parameter. Unterkap. 1 beschreibt, wie eine solche Funktion definiert (gebaut) und wie sie<br />

aufgerufen (benutzt) werden kann.<br />

Da in C ++ der Grundsatz gilt, dass dem Compiler vor der Benutzung eines Namens dieser ihm<br />

erst einmal vorgestellt werden muss, kann die richtige Reihenfolge (Definition einer Funktion<br />

vor ihrem ersten Aufruf) manchmal recht schwierig sein. Daher wendet man sehr häufig die<br />

Möglichkeit an, Funktionen anzukündigen, ohne sofort den Funktionscode zu zeigen. Dieses<br />

wird Funktionsdeklaration genannt (Unterkap. 2).<br />

Die Referenz erlaubt es, für bereits vorhandene Variable synonyme Namen zu vergeben<br />

(Unterkap. 3). Dieses wird allgemein nicht sehr häufig angewendet, jedoch im speziellen Fall<br />

der Funktionsparameter häufiger (Unterkap. 4). Hier wird ausführlich beschreiben, dass es<br />

in C ++ zwei streng zu unterscheidende Übergabearten für Funktionsparameter gibt (Wert<br />

und Referenz) und wie diese beiden Arten sinnvoll angewendet werden.<br />

Der Gültigkeitsbereich von Namen kann lokal (blockbezogen) oder global (dateibezogen)<br />

sein. Unterkap. 5 beschreibt diesen Sachverhalt und gibt sehr wichtige Programmierhinweise,<br />

welche Namen global und welche lokal sein sollen.<br />

Nach einigen Ergänzungen zu Funktionen in Unterkap. 6 führt Unterkap. 7 in das Gebiet der<br />

Rekursion bei Funktionen ein. Eine solche Rekursion kann in manchen Fällen die Programmierung<br />

sehr erleichtern. Es kann sein, dass es hier zunächst Probleme beim Verständnis<br />

gibt, daher sollten Sie den Erläuterungen in der Vorlesung genau folgen.<br />

Das Kapitel wird abgeschlossen mit einigen Hinweisen, wie man Funktionen erstellen sollte,<br />

damit sie gut sind.<br />

7.1 Funktionsdefinition und Funktionsaufruf, Gültigkeitsbereich in Blöcken<br />

(7.10) Übb Zunächst lernen Sie zu unterscheiden:<br />

(7.11)<br />

• Funktionsaufruf: Benutzung einer Funktion, hierzu benötigt man nur die Antwort auf<br />

die Frage ” Was?“ (Was leistet die Funktion?), s. (7.12), vgl. (6.12).<br />

• Funktionsdefinition: Konstruktion/Bau einer Funktion, hierbei geschieht die Beantwortung<br />

der Frage nach dem ” Wie?“ (Wie verwirklicht die Funktion das, was von ihr<br />

verlangt wird?), s. (7.11).<br />

In Punkt (7.13) wird ein Beispielprogramm vorgestellt mit Funktionsdefinitionen und Funktionsaufrufen.<br />

Eine Funktion aufzurufen bedeutet für den Prozessor zur Laufzeit eine große Verwaltungsarbeit.<br />

Die einzelnen Schritte, wie dieses geschieht, sind in (7.14) beschrieben. Es ist nützlich,<br />

dieses zu wissen; für das Programmieren selbst ist es meist weniger wichtig.<br />

Die Aktionen bei der Ausführung einer Funktion stehen in einer ZusammengesetztenAnweisung,<br />

dem Funktionsblock. (7.15) beschreibt daher Regeln für den Gültigkeitsbereich von<br />

Namen in Blöcken, auch z. B. in geschachtelten Blöcken.<br />

(a) 10/33 FunktionsDefinition (spezielle Deklaration, vereinfacht) <br />

12,14 RückgabeTyp 30,32(Alt.4) FunktionsBezeichner ( Parameterdeklarations-LISTEopt )<br />

54 ZusammengesetzteAnweisung


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 77<br />

Parameterdeklarations-LISTE (vereinfacht) <br />

Typ FormalerParameter [- , Typ FormalerParameter ]- 0..n<br />

FormalerParameter Bezeichner<br />

Der RückgabeTyp gibt den Typ des Rückgabewertes der Funktion an; entsprechend (6.21b)<br />

unterscheidet man zwei Arten von Funktionen:<br />

• Funktion mit Rückgabewert: ” normaler“ Rückgabetyp, der Funktionsname hat am Aufrufort<br />

den Typ und den zugehörigen Wert (letzteres: Haupteffekt des Funktionsausdrucks).<br />

• Funktion ohne Rückgabewert: Typ void.<br />

Bsp s. (7.13)<br />

Anm Die ZusammengesetzteAnweisung ist der Funktionsblock.<br />

↑↑ C △! In der Sprache C sollte eine Funktion ohne Parameter statt mit einer leeren Parameterliste<br />

mit dem Schlüsselwort void definiert werden, Näheres s. (7.23 Anm4).<br />

(b) Der Rücksprung aus der Funktion wird durch eine zusätzliche Anweisung bewirkt, durch<br />

eine 53 SprungAnweisung; sie kann an mehreren Stellen der Funktion auftreten:<br />

return Ausdruckopt ;<br />

Führt der Programmfluss auf eine return-Anweisung, wird die Funktion direkt verlassen:<br />

• Bei einer Funktion mit Rückgabewert darf der Ausdruck nicht fehlen. Er gibt nämlich<br />

den aktuellen Rückgabewert an.<br />

Anm Der Ausdruck wird aus Übersichtlichkeitsgründen häufig in ( ) gesetzt; dieses ist<br />

syntaktisch erlaubt, aber nicht notwendig.<br />

• Bei einer void-Funktion muss Ausdruck fehlen. In diesem Fall darf eine return-<br />

Anweisung am Ende der Funktion fehlen, da sie dann implizit durch den Compiler<br />

eingefügt wird.<br />

(7.12) Der Aufruf (d. h. die Benutzung) der Funktion geschieht durch einen Ausdruck:<br />

FunktionsaufrufAusdruck FunktionsBezeichner ( Parameter-LISTEopt )<br />

Parameter-LISTE AktuellerParameter [- , AktuellerParameter ]- 0..n<br />

AktuellerParameter Ausdruck<br />

Beim Aufruf einer Funktion springt der Kontrollfluss in die Funktion (und zwar zum Anfang<br />

des Funktionsblocks); er kehrt zum Aufrufort zurück, sobald er auf ein return oder auf das<br />

Ende des Funktionsblocks trifft.<br />

Der FunktionsaufrufAusdruck kann zwei Effekte haben, vgl. (3.31):<br />

• Seiteneffekt (immer vorhanden): Sprung in die Funktion, Ausführung des Codes,<br />

• Haupteffekt (Wert des Ausdrucks):<br />

◦ Haupteffekt ist vorhanden bei Funktion mit Rückgabewert, nämlich der Wert des<br />

Ausdrucks der return-Anweisung, mit der die Funktion verlassen wurde.<br />

◦ Haupteffekt ist nicht vorhanden bei Funktion ohne Rückgabewert, d. h. bei einer<br />

void-Funktion. Daher ist ein Funktionsaufruf ohne Rückgabewert nur dort erlaubt,<br />

wo der Ausdruckswert ignoriert wird, z. B. bei der Ausdrucksanweisung (3.32a).<br />

Korrespondenzprinzip bezüglich der Parameter-LISTE: Anzahl, Typ, Reihenfolge der aktuellen<br />

Parameter müssen zu den formalen Parametern passen – wenn nötig, nach impliziter<br />

Typanpassung.<br />

Bsp s. (7.13)<br />

(7.13) Der Kontrollfluss eines C ++/C-Programms beginnt immer am Anfang der main-Funktion.<br />

Bsp Beispielprogramm für Funktionsdefinition und Funktionsaufruf<br />

#include // Beispielprogramm D07-13.CPP<br />

using namespace std;<br />

int wertPlus1(int wert) // -- kürzer, aber gleichwertig: --<br />

{<br />

int ergebnis; // int wertPlus1(int wert)


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 78<br />

ergebnis=wert+1; // {<br />

return ergebnis; // return wert+1;<br />

} // }<br />

int summe(int par1, int par2) // int summe(int par1, int par2)<br />

{ // { return par1+par2; }<br />

int ergebnis;<br />

ergebnis=par1+par2;<br />

return ergebnis;<br />

}<br />

int zahlFest() // int zahlFest()<br />

{ // { return 4711; }<br />

int wert;<br />

wert=4711;<br />

return wert;<br />

}<br />

void intDruck(int wert) // Aufruf ist Ausdruck ohne Haupteffekt<br />

{ cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 79<br />

(f) Wenn return-Ausdruck vorhanden: Auswerten des Ausdrucks, typmäßige Anpassung<br />

an den Rückgabetyp der Funktion (wenn nötig), temporäres Abspeichern dieses Wertes;<br />

vorher Ausführen aller Seiteneffekte des Ausdrucks (wenn vorhanden).<br />

(g) Wenn lokale (automatische) Variablen vorhanden waren: Freigabe des zugehörigen Speicherplatzes,<br />

vgl. (d).<br />

(h) Rücksprung zur gemerkten Aufrufstelle, vgl. (b).<br />

(i) Wenn Parameter vorhanden waren: Freigabe des Speicherplatzes der formalen Parameter,<br />

vgl. (a2).<br />

(j) Bei Funktion mit Rückgabewert (f): der Wert des Funktionsnamens an der Aufrufstelle<br />

ist der gemerkte return-Wert.<br />

(7.15) Regeln für Gültigkeitsbereich in Blöcken, Auflösung von Namenskonflikten<br />

(Ergänzung zu (4.12), Zusammenfassung s. (Kap. 7.5); Ausnahmen zu mancher dieser Regeln s. in den<br />

Anmerkungen)<br />

(a) Namen, die in einem Block ( 54 ZusammengesetzteAnweisung, z. B. Funktionsblock, aber auch<br />

in einem untergeordneten Block) deklariert werden, heißen lokale Namen.<br />

Der Gültigkeitsbereich 〈scope〉 eines lokalen Namens erstreckt sich vom Deklarationspunkt<br />

(siehe [Dekl.-Punkt] in 11 ) bis zum Ende dieses Blocks.<br />

Innerhalb der Blockebene, in dem der Name deklariert ist, muss er eindeutig sein, er darf<br />

nicht mehrfach unterschiedlich deklariert sein (genauer: s. (Anm1)).<br />

(b) Ein Name wird in untergeordneten Blöcken verdeckt, indem er dort neu deklariert wird;<br />

nach Austritt aus diesem Block ist der Name in der alten Bedeutung (bei Variablen: auch<br />

mit altem Wert) wieder verfügbar.<br />

(c) △! Variablen, die innerhalb eines Blocks definiert sind, haben beim Einsprung in diesen<br />

Block keinen definierten Wert! Daher ist eine Wertbelegung – sie kann durch eine Initialisierung<br />

oder z. B. auch durch eine Wertzuweisung erfolgen – vor erstem Lesegebrauch<br />

unbedingt nötig!!<br />

↗ Ausnahmen hiervon sind Objekte, wenn sie gut gebaute Konstruktoren haben (9.21),<br />

ferner auch statische Variablen (8.12).<br />

Bsp Unsinnige Funktion, da gefahr einen zufälligen Wert erhält:<br />

int unsinn(int wert)<br />

{ int gefahr;<br />

return wert+gefahr; // Unsinn! Gefährlich!! Nein!!<br />

}<br />

(d) Die Speicherplatzreservierung für lokale Variable der hier besprochenen Art erfolgt automatisch<br />

beim Einsprung in den zugehörigen Block, der Platz wird bei Verlassen des Blocks<br />

automatisch wieder freigegeben – daher der Name ” automatische“ Variable (vgl. (7.14 d,g) bei<br />

Funktionsblöcken) bzw. automatische Speicherklasse (6.31b). Die Reservierung bleibt auch in<br />

untergeordneten Blöcken erhalten – auch bei Überdeckung des Namens.<br />

↗ Zur Speicherplatzreservierung für lokale statische Variable siehe (8.11, 8.12), dazu auch<br />

(6.31a).<br />

(e) Die formalen Parameter gelten bzgl. dieser Regeln (Gültigkeitsbereich, Eindeutigkeit,<br />

Überdeckungsmöglichkeit) als im äußeren Funktionsblock definiert.<br />

Wichiger Unterschied zu den automatischen Variablen: die formalen Parameter werden durch<br />

die aktuellen Parameter initialisiert, sie haben demnach beim Einsprung in die Funktion<br />

Anfangswerte.<br />

Anm1 Mehrfache, nicht widersprüchliche Deklarationen sind erlaubt – wie bei Funktionen, s.<br />

(7.23 Anm3).<br />

Anm2 Eine Definition darf in derselben Blockebene nur einmal erfolgen.<br />

↑↑ Bei Aufzählungen und Klassen/Strukturen sind Ausnahmen hiervon möglich, s. (Cpp/<br />

5.1).<br />

Anm3 Es gibt keinen Namenskonflikt mit ggf. gleichlautenden Namen innerhalb anderer Funktionen.<br />

(f) Bsp Das folgende Beispielprogramm ist kein gutes Programmierbeispiel, da es sehr un-


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 80<br />

übersichtlich ist: unnötige Blockschachtelung und Mehrfachdefinition desselben Namens.<br />

Trotzdem ist es wichtig, es zu verstehen; es dient daher nur zum Lernen, zur Übung. Die<br />

Kennzeichnungen *xxx* in den Kommentaren deuten die verschiedenen Speicherplätze mit<br />

demselben Namen an.<br />

#include // Beispielprogramm D07-15.CPP<br />

using namespace std;<br />

void druck(int wert)<br />

{ cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 81<br />

muss jedoch wissen, was der Name bedeuten soll – spätestens beim ersten Funktionsaufruf.<br />

Dazu dient eine Funktionsdeklaration; sie führt den Namen ein, teilt dem Compiler mit,<br />

welche Parameter und welchen Rückgabetyp die Funktion hat.<br />

Funktionsdeklarationen werden in C ++/C sehr häufig benutzt, selbst dann wenn sie vermeidbar<br />

wären, weil die Definition vor dem ersten Gebrauch geschehen könnte.<br />

(7.23) 10/11 FunktionsDeklaration <br />

12/14 RückgabeTyp 30/32,Alt.4 FunktionsBezeichner ( ParameterDekl.-LISTEopt ) ;<br />

Eine Funktionsdeklaration ist syntaktisch einer Funktionsdefinition sehr ähnlich, s. (7.11a),<br />

nur statt des Funktionsblockes (ZusammengesetzteAnweisung) steht ein Semikolon.<br />

Mit der Funktionsdeklaration erhält der Compiler die Information über:<br />

• Funktionsname,<br />

• Signatur der Funktion (Anzahl, Typ, Reihenfolge der Parameter),<br />

• Rückgabetyp.<br />

Es fehlt ihm hier nur noch die Information über die Einsprungadresse in die Funktion, s.<br />

(7.14c). Diese Einsprungadresse muss später eingesetzt werden, und zwar<br />

• durch den Compiler, wenn die Definition später in der gleichen Programmdatei erfolgt,<br />

• durch den Linker, wenn die Funktion woanders definiert wird.<br />

Bsp Deklaration aller Funktionen aus Beispielprogramm (7.13):<br />

int wertPlus1(int wert);<br />

int summe(int par1, int par2);<br />

int zahlFest();<br />

void intDruck(int wert);<br />

Anm1 In den Headerdateien sind viele Funktionsdeklarationen vorhanden, z. B.<br />

: get, getline;<br />

: strcpy, strcat, strlen.<br />

Anm2 In einer Deklaration (i. a. nicht in einer Definition) ist es erlaubt, den Namen – nicht den<br />

Typ – der formalen Parameter wegzulassen.<br />

Empfehlung: Namen nicht weglassen, da (hoffentlich!) die Bedeutung der Parameter beschreibend<br />

( ” selbstdokumentierender Name“).<br />

Beispiel:<br />

double potenz(double basis, double exp); // empfohlen<br />

double potenz(double, double); // erlaubt - aber Bedeutung der Par.?<br />

Anm3 Eine Funktionsdeklaration darf mehrfach vorhanden sein, solange sich die Deklarationen<br />

nicht widersprechen (Übereinstimmung Name, Signatur, Rückgabetyp – Parameternamen<br />

dürfen unterschiedlich sein; eine unterschiedliche Signatur führt zu unterschiedlichen Funktionen,<br />

vgl. (7.61)). Eine Deklaration ist auch erlaubt, wenn die Funktion nicht aufgerufen<br />

wird. Eine Funktionsdefinition dagegen darf nur genau einmal auftreten; wenn die Funktion<br />

nicht benutzt wird, darf sie auch fehlen.<br />

↑↑ Bei virtuellen Elementfunktionen (einer Klasse) muss auch dann eine Definition vorhanden<br />

sein, wenn die Elementfunktion nicht benutzt wird, siehe (11.42 ↑↑1).<br />

Anm4 C △! Wenn kein Parameter vorhanden ist, dann sollte in der Definition und in der Deklaration<br />

auf jeden Fall statt<br />

RückgabeTyp FunktionsName() // leere Parameterliste<br />

besser folgendes geschrieben werden:<br />

RückgabeTyp FunktionsName(void)<br />

7.3 Referenztyp<br />

Die Schreibweise mit void ist auch in C++ erlaubt, aber nicht nötig; in C ist sie sehr<br />

wichtig, da die Schreibweise mit leerem Klammernpaar aus historischen Gründen eine andere<br />

Bedeutung hat (Funktion mit einer nicht näher spezifizierten Anzahl von Parametern).<br />

(7.30) Übb Durch eine Referenz wird zu einer bereits existierenden Variablen ein Synonym erzeugt,<br />

d. h. ein zusätzlicher Name, der auf denselben Speicherplatz wie der Originalname<br />

verweist. In diesem Unterkapitel wird allgemein gezeigt, wie in C ++ eine Referenz erzeugt


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 82<br />

und benutzt wird. In der Praxis geschieht dieses fast nur in Verbindung mit Funktionen,<br />

nämlich als Funktionsparameter; dieses beschreibt das darauf folgende Unterkapitel.<br />

(7.31) C++ Neuer Typ: Referenztyp.<br />

(a) Mit einer Referenz wird ein Synonym (Zweitname, Aliasname) für eine bereits vorhandene<br />

Variable erzeugt. Daher sind unbedingt die beiden Schritte zu unterscheiden:<br />

• Initialisierung der Referenz, d. h. Setzen der Referenz; hierdurch wird der neue Name<br />

mit einer bereits existierenden Variablen verknüpft.<br />

• Benutzen der Referenz, und zwar als Synomym für diese andere Variable.<br />

DefinitionReferenzMitInitialisierung [NV] Typ &Bezeichner = OriginalVariable ;<br />

Typ der Referenz: wie Typ ohne das Zeichen &.<br />

Bsp Programmfragment mit Definition und Benutzung Referenz:<br />

int i;<br />

int &j=i; // Setzen der Referenz, ab jetzt i und j synonym<br />

// Beide Definitionen kürzer: int i, &j=i;<br />

i=5;<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 83<br />

wird, braucht der aktuelle Parameter keine Datenspeicher-Adresse zu haben, er kann daher<br />

auch ein beliebiger (Rechen-)Ausdruck sein. Nach dem Kopieren existiert keine Verknüpfung<br />

mehr zwischen formalem und aktuellem Parameter. Eine Änderung des Wertes des formalen<br />

Parameters (eigener Speicherplatz!) ändert nicht den aktuellen Parameter.<br />

Bsp s. (7.45).<br />

(7.43) Übergabeart Referenz ( ” CALL BY REFERENCE“) C++<br />

(7.44)<br />

Der formale Parameter wird innerhalb der Funktion zu einem Synonym des aktuellen Parameters<br />

(das ist in diesem Fall die Bedeutung von Initialisierung). Jede Änderung des Wertes<br />

des formalen Parameter wirkt implizit auf den aktuellen Parameter. Wie allgemein zum<br />

Referenztyp in (7.31) beschrieben, sind auch hier die beiden Schritte Initialisierung und Benutzung<br />

zu unterscheiden:<br />

• Initialisierung (Setzen) der Referenz: der formale Parameter wird bei jedem Funktionsaufruf<br />

mit dem (jeweils) aktuellen Parameter initialisiert.<br />

Bei dieser Übergabeart muss der aktuelle Parameter eine Variable sein, d. h. er muss eine<br />

Datenspeicher-Adresse haben; der aktuelle Parameter darf kein (allgemeiner) Ausdruck<br />

sein. Ausnahme: konstante Referenz (7.44b).<br />

• Benutzung der Referenz: Benutzen des formalen Parameters innerhalb des Funktionsblockes.<br />

DefinitionFormalerParameterReferenztyp [NV] Typ &Bezeichner<br />

Die Initialisierung geschieht durch den Funktionsaufruf, vgl. dagegen (7.31).<br />

Bsp s. (7.45).<br />

Anm Mit dieser Übergabeart ist es möglich, mehr als einen Wert aus einer Funktion zurückzuerhalten.<br />

↑↑ C/C++ Eine andere Möglichkeit, veränderte Werte aus der Funktion zurückzuerhalten, ist die<br />

(explizite) Benutzung von Zeigern – trotz CALL BY VALUE, vgl. (5.65a).<br />

Bei der Benutzung von Arrays als Funktionsparameter sind einige wichtige Besonderheiten<br />

zu beachten, s. (7.46).<br />

(a) Die Syntax eines Funktionsaufrufs lässt leider nicht erkennen, ob eine Wert- oder Referenzübergabe<br />

geschieht; das ist nur bei der Funktionsdeklaration oder -definition ersichtlich.<br />

Daher ist die Übersichtlichkeit und Nachvollziehbarkeit am Ort des Funktionsaufrufs nicht<br />

gegeben, wenn man nicht weiß, ob die aktuellen Parameter durch den Funktionsaufruf<br />

geändert werden können – es sei denn, der aktuelle Parameter ist nicht oder nicht nur<br />

eine Variable (dann keine änderbare Referenz möglich). Es gibt daher Autoren, die empfehlen,<br />

eine änderbare Referenz zu vermeiden. Dies ist jedoch für Personen, die C ++ gelernt<br />

haben, ohne vorher tiefe C-Kenntnisse zu haben, nicht problemvoll, da sie gewohnt sind, mit<br />

Referenzen zu rechnen.<br />

(b) Welche Übergabeart gewählt werden soll, hängt zunächst davon ab, ob der Parameter durch<br />

den Funktionausfurf nicht verändert wird bzw. werden soll (1) oder ob man einen geänderten<br />

Wert zurückerhalten will (2).<br />

(1) Wenn ein Parameter durch die Funktion nicht geändert werden soll (auch nicht durch<br />

einen versehentlichen Programmierfehler), ist folgendes zu beachten:<br />

• Die normale Übergabeart, insbesondere bei eingebauten Typen, sollte CALL BY<br />

VALUE sein.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 84<br />

• C++ Jedoch manchmal, insbesondere wenn die Variable großen Speicherbedarf beinhaltet,<br />

möchte man zur Laufzeit den Extraspeicher und die Kopierzeit eines CALL BY<br />

VALUE sparen, trotzdem aber sicher sein, dass der aktuelle Parameter nicht geändert<br />

werden kann, auch nicht bei einem ungewollten Programmierfehler in der Funktion.<br />

Dazu bietet sich die konstante Referenz an (7.31b). Dieses wird sehr oft bei Objekten<br />

getan (Kap. 9).<br />

Syntax:<br />

DeklarationKonstanteReferenz [NV] 14/20 const Typ &Variable<br />

Bei konstanter Referenz ist als aktueller Parameter ein Ausdruck, ggf. sogar anderen<br />

Typs, erlaubt (Zwischenspeicherung in temporärer Variable).<br />

(2) Wenn ein Parameter durch die Funktion geändert werden soll, ist folgendes zu tun:<br />

• Anwenden der (nichtkonstanten) Referenzübergabe<br />

• oder einer anderen Möglichkeit (Zeiger), weitere Diskussion siehe (12.42, 12.43).<br />

Bsp s. (7.45).<br />

(7.45) Bsp Beispielprogramm zu Parameterübergabe Wert und Referenz (nichtkonstant und konstant);<br />

in der Vorlesung wird die Speicherbelegung besprochen.<br />

#include // Beispielprogramm D07-45.CPP<br />

using namespace std;<br />

// Funktionsdeklarationen; Definitionen s. unten<br />

void tauschDenkste(int par1, int par2);<br />

void tausch(int &par1, int &par2);<br />

int neuWert(int par1, const int &par2, int &par3);<br />

int main()<br />

{<br />

int i, j, k;<br />

}<br />

// Vertauschen nein und ja:<br />

i=5; j=7;<br />

tauschDenkste(i,j);<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 85<br />

}<br />

int merk;<br />

merk=par1;<br />

par1=par2;<br />

par2=merk;<br />

int neuWert(int par1, const int &par2, int &par3)<br />

{<br />

par1+=5; // erlaubt, aktueller Parameter jedoch unverändert<br />

}<br />

//par2+=8; // wäre NICHT erlaubt, da konstant;<br />

// aktueller Parameter würde nämlich verändert<br />

par3+=2; // erlaubt, da nichtkonstante Referenz<br />

return par1+par2+par3;<br />

(7.46) Besonderheiten zu Arrays als Funktionsparameter C/C++<br />

Bei Benutzung eines Arraynamens als Parameter liegt scheinbar eine Referenzübergabe<br />

vor (Genaueres s. ↑↑), d. h. die Wirkung ist wie eine Referenzübergabe. Aus diesem Grund<br />

ist es möglich, Arrays als Funktionsparameter zu verändern, vgl. strcpy, strcat (VP5.53ab).<br />

Ein echtes CALL BY VALUE eines Arrays (im Sinne einer lokalen Kopie innerhalb der<br />

Funktion) ist in C ++/C nicht möglich. Daher sollte, wenn die Funktion das Array nicht<br />

verändern soll, unbedingt ein const benutzt werden.<br />

Als Typ des formalen Paramters beim Array kann der tatsächliche Arraytyp angegeben<br />

werden (ohne das Zeichen &), die Anzahl Elemente (nicht aber das Paar eckiger Klammern)<br />

kann dabei ausgelassen werden. Im einzelnen wird dieses in (12.44a) diskutiert, dort wird auch<br />

die üblichere Art der Typangabe beschrieben.<br />

Bsp Funktionsdeklaration, der Parameter ist ein int-Array mit 20 Elementen (bei der Funktionsdefinition<br />

müsste statt des Semikolons der Funktionsblock stehen):<br />

void tuWas(int arr[20]); // Funktionsdeklaration<br />

Gleichbedeutend wäre folgende Deklaration (die Zahl 20 ist weggelassen):<br />

void tuWas(int arr[]);<br />

Ein Aufruf der Funktion (z. B. innerhalb von main()) mit vorheriger Definition eines passenden Arrays<br />

könnte so aussehen:<br />

int vieleGanzzahl[20];<br />

tuWas(vieleGanzzahl);<br />

Wesentlich besser als obiger Programmtext wäre allerdings die Benutzung einer symbolischen Konstante<br />

(5.44):<br />

const int anzIntArr=20;<br />

void tuWas(int arr[anzIntArr]);<br />

oder Anzahl innerhalb des eckigen Klammerpaares weggelassen wie oben:<br />

void tuWas(int arr[]);<br />

Aufruf, z. B. innerhalb main():<br />

int vieleGanzzahl[anzIntArr];<br />

tuWas(vieleGanzzahl);<br />

Soll das Array in der Funktion nicht verändert werden, so ist ein const dem Arraytyp voranzustellen:<br />

void tuWasOhneAend(const int arr[20]); // Funktionsdeklaration<br />

oder (die Zahl 20 ist weggelassen):<br />

void tuWasOhneAend(const int arr[]);<br />

Beim Funktionsaufruf ändert sich nichts:<br />

int vieleGanzzahl[20];<br />

tuWasOhneAend(vieleGanzzahl);


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 86<br />

↑↑ Nähere Begründung, vgl. (10.26) und (12.44):<br />

Da Arraynamen nach ihrer Deklaration automatisch (fast) immer in Zeiger auf ihr Element<br />

Nummer 0 umgewandelt werden, sieht ein Arrayname-Gebrauch wie ein CALL BY REFE-<br />

RENCE aus, ist aber tatsächlich ein CALL BY VALUE eines Zeigers, vgl. (5.65b).<br />

In Wirklichkeit wird demnach ein Zeiger übergeben. Dieses ist auch der Grund dafür, dass<br />

innerhalb einer Funktion der Ausdruck<br />

sizeof(ParameterArrayName)<br />

anders interpretiert wird als vielleicht zunächst gedacht: es ist nämlich der Speicherplatzbedarf<br />

des Zeigers (z. B. 4 Byte) – und nicht der Speicherplatzbedarf für das Original-Array,<br />

s. (12.44d).<br />

7.5 Globale und lokale Namen<br />

(7.50) Übb Zunächst werden die Begriffe lokaler Name (nur innerhalb eines Blocks gültig)<br />

und globaler Name (gültig praktisch in der ganzen Programmdatei) vorgestellt (7.51). Eine<br />

einfache Schachtelung von Blöcken wird durch die Verdeckung (7.52) ermöglicht, da durch sie<br />

Namenskonflikte weitgehend vermieden werden können. Für einen guten Programmierstil<br />

ist es unerlässlich zu wissen, dass Variable (fast) nie global sein sollen; globale Konstante<br />

und Typen sind dagegen meist sehr sinnvoll (7.53).<br />

(7.51) Globale und lokale Namen<br />

(a) Namen (z. B. von Variablen oder Typen) heißen lokal (früher auch: intern), wenn sie innerhalb<br />

eines Blocks deklariert werden, vgl. (4.12) und (7.15a). Ihr Gültigkeitsbereich 〈scope〉<br />

reicht von ihrem Deklarationspunkt (direkt hinter den Namen, genauer: hinter 11 dem Deklarator)<br />

bis zum Ende des Blocks, in dem sie deklariert sind. Die formalen Parameter<br />

einer Funktion gelten hierbei als im äußersten Funktionsblock deklariert, d. h. es sind lokale<br />

Namen (7.15e).<br />

△! Aufpassen – bereits in (4.13) erwähnt: im allgemeinen werden lokale Variablen (wenn<br />

sie automatisch sind, s. (8.13)) nicht automatisch initialisiert. Daher sollten Sie sie<br />

explizit initialisieren oder ihnen kurz nach der Definition einen Wert zuweisen.<br />

(b) Namen (z. B. von Variablen oder Typen) heißen global (früher auch: extern), wenn sie<br />

außerhalb von Blöcken deklariert werden. Ihr Gültigkeitsbereich reicht von ihrem Deklarationspunkt<br />

bis zum Ende der Übersetzungseinheit.<br />

(c) Klassenelemente unterliegen dem Gültigkeitsbereich Klasse, s. (9.12a).<br />

(7.52) Namen müssen – in der Ebene ihrer Deklaration – eindeutig sein. Dagegen wird ein (lokaler<br />

oder globaler) Name durch die Deklaration des gleichen Namens in einem untergeordneten<br />

Block verdeckt; bei Verlassen dieses Blocks ist der Name in der alten Bedeutung wieder<br />

verfügbar, vgl. (7.15b).<br />

C++ Auf globale Namen kann – unabhängig von einer Verdeckung – immer zugegriffen<br />

werden mit Hilfe des Bereichsauflösungsoperators :: (Op1a); die Verdeckung von lokalen<br />

Namen kann nicht durchbrochen werden.<br />

Anm Zusammenfassung s. a. (Cpp/5.1); ↑↑ in dortiger Anmerkung sind auch Ausnahmen von obiger<br />

Eindeutigkeitsregel vermerkt.<br />

(7.53) Allgemeine Richtlinien für die Definition und Benutzung globaler Namen:<br />

• Globale Variablen sind in einem Programm kaum durchschaubar, da sie überall<br />

geändert werden können. Daher sind sie (für diesen Kurs) nicht erlaubt. Allgemein<br />

sollten sie vermieden werden wann immer möglich.<br />

• Dagegen machen globale Konstanten häufig Sinn; ihr Wert kann ja nirgendwo<br />

geändert werden.<br />

Übrigens, zur Wiederholung: benutzen Sie symbolische Konstanten anstatt konstanter Zahlen<br />

wo immer möglich, s. (4.72) und (5.44).<br />

• Typnamen sind oft global, so dass sie in der ganzen Programmdatei benutzt werden<br />

können. Die Bedeutung kann nach der Definition nicht geändert werden, daher sind<br />

globale Typen problemlos.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 87<br />

• Funktionsnamen sind immer global, da C ++/C keine lokalen Funktionen erlaubt (im<br />

Gegensatz z. B. zu Pascal).<br />

↗ Wenn mehrere Textdateien benutzt werden (Kap. 8.2), sollten Funktionsnamen soweit<br />

möglich verborgen werden. Genauere allgemeine Betrachtungen s. (8.21), dazu Anwendung<br />

für Headerdateien in Hinsicht auf Konstanten, Typen, Funktionen usw. s. (8.24).<br />

7.6 Überladen von Funktionsnamen, Standardargumente<br />

(7.60) Übb In diesem Unterkapitel werden zwei weitere C ++-Besonderheiten vorgestellt: Man<br />

kann mehrere Funktionen mit demselben Namen deklarieren, die Unterscheidung für den<br />

Compiler geschieht anhand der Signatur. Ferner ist es möglich, formalen Parametern Standardwerte<br />

zu geben, die automatisch eingesetzt werden, wenn man die aktuellen Parameter<br />

auslässt.<br />

(7.61) Identifizieren einer bestimmten Funktion:<br />

C Der Funktionsname muss (innerhalb seines Gültigkeitsbereichs) eindeutig sein.<br />

C++ Der Funktionsname einschließlich der Signatur (d. h. Anzahl, Typ, Reihenfolge der Parameter)<br />

dient der Unterscheidung; hier also Funktionen gleichen Namens erlaubt, wenn sie<br />

unterschiedliche Signatur haben (Überladen von Funktionsnamen). Von dieser Eigenschaft<br />

wird häufig Gebrauch gemacht, z. B. auch bei Konstruktoren (9.21) – aber nicht nur dort.<br />

Bsp<br />

#include // Beispielprogramm D07-61.CPP<br />

using namespace std;<br />

void ausgabe(int wert);<br />

void ausgabe(double wert);<br />

void ausgabe(int wert1, double wert2);<br />

int main()<br />

{<br />

ausgabe(3);<br />

ausgabe(3.);<br />

ausgabe(9,5.3);<br />

// 2x implizite Typanpassung, ggf. erste mit Compilerwarnung:<br />

ausgabe(5.6,2); // wie: ausgabe(5,2.0);<br />

return 0;<br />

}<br />

void ausgabe(int wert)<br />

{ cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 88<br />

Falls ein solcher Wert beim Aufruf fehlt (immer nur Parameter von hinten her fehlend), wird<br />

Standardwert eingesetzt. Dieses bedeutet demnach kein Überladen von Funktionsnamen<br />

(7.61), da der Compiler beim Aufruf automatisch fehlende Werte einsetzt.<br />

Bsp1 Funktionsdefinition mit Standardparameter:<br />

void druck(int wert=4711)<br />

{ cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 89<br />

muss sie irgendwann aufhören; das bedeutet, dass der Selbst-Aufruf innerhalb der Funktion<br />

normalerweise unter einer Bedingung steht, so dass nach einer endlichen Zahl geschachtelter<br />

Aufrufe kein Selbst-Aufruf mehr geschieht.<br />

↗ Automatische Variable sind lokale Variable, deren Speicherplatz beim Einsprung in einen<br />

(Funktions-)Block automatisch erzeugt und beim Verlassen automatisch wieder freigegeben<br />

wird (6.31b, 8.11b, 8.13). (Andere Arten von Variablen sind bisher noch nicht eingeführt.) Die<br />

formalen Parameter einer Funktion haben in dieser Beziehung dasselbe Verhalten.<br />

Bei genügend Zeit in der Vorlesung wird anhand des einfachen Beispiels (7.71) die aufwendige<br />

Speicher-Verwaltungsarbeit für den Prozessor bei der Rekursion erläutert (Verwaltung eines<br />

Stacks oder Kellerspeichers (7.72a)).<br />

(7.71) Was bewirkt der folgende Programm?<br />

#include // Beispielprogramm D07-71.CPP<br />

using namespace std;<br />

const char ende=’$’;<br />

void merkwuerdig()<br />

{<br />

char c;<br />

cin.get(c);<br />

if (c!=ende) merkwuerdig();<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 90<br />

Aufgabe: Man verlege den Scheibenturm von ort1 nach ort2, und zwar unter folgenden<br />

Nebenbedingungen:<br />

• es darf immer nur eine Scheibe bewegt werden,<br />

• eine Scheibe darf nur auf einen der drei Orte abgelegt werden,<br />

• sie darf immer nur auf einer größeren oder direkt unten liegen,<br />

• ort3 und die anderen Orte dürfen beliebig als Zwischenablage benutzt werden, solange<br />

die obigen Bedingungen nicht verletzt werden.<br />

(b) Lösungsansatz:<br />

const int maxHoehe=64; // Wert 64: dauert ggf. zu lange??<br />

typedef int Ort; // Ort als Typ<br />

const Ort ort1=1, ort2=2, ort3=3; // zur besseren Lesbarkeit 3 Orte<br />

void bewegeScheibe(Ort vonOrt, Ort nachOrt)<br />

// Oberste Scheibe soll vom Ort vonOrt zum Ort nachOrt bewegt werden<br />

{<br />

// muss implementiert werden (Roboter, Bildschirmanzeige o. a.)<br />

}<br />

void transpTurm(int aktHoehe, Ort vonOrt, Ort nachOrt, Ort hilfOrt)<br />

// Turm der Höhe aktHoehe (von oben aus gesehen: Turm der Tiefe<br />

// aktHoehe) soll vom Ort vonOrt zum Ort nachOrt transportiert<br />

// werden; Ort hilfOrt darf als Ablage benutzt werden<br />

{<br />

if (aktHoehe==1)<br />

bewegeScheibe(vonOrt,nachOrt);<br />

else {<br />

transpTurm(aktHoehe-1,vonOrt,hilfOrt,nachOrt);<br />

bewegeScheibe(vonOrt,nachOrt);<br />

transpTurm(aktHoehe-1,hilfOrt,nachOrt,vonOrt);<br />

}<br />

}<br />

int main()<br />

{<br />

transpTurm(maxHoehe,ort1,ort2,ort3);<br />

return 0;<br />

}<br />

(c) Berechnung der Anzahl Scheibenbewegungen:<br />

– s. Vorlesung –<br />

7.8 Leitlinien zur Entwicklung von Funktionen<br />

(7.80) Übb Um gute Funktionen zu entwerfen, sollte man gewisse Regeln beachten, von denen<br />

hier einige vorgestellt werden. Gut ist eine Funktion, wenn sie unter anderem effektiv, übersichtlich<br />

(schnell einsichtiger Algorithmus) und leicht wartbar (korrigierbar und änderbar)<br />

ist.<br />

(7.81) Es gibt einige allgemeine Leitlinien, wie man Funktionen schreiben sollte. Die Anwendung<br />

dieser Leitlinien führt zu einem verbesserten Programmierstil, vgl. auch die kurzen<br />

allgemeinen Bemerkungen, wie Baueinheiten gebildet werden sollten (6.12).<br />

• Funktionen sollten nicht groß sein, z. B. nicht mehr als etwa 100 Zeilen. Es ist besser,<br />

kleinere Funktionen zu erstellen, die sich gegenseitig aufrufen.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 91<br />

• Oft ist ein geschachtelter (kaskadierter) Entwurf nützlich:<br />

◦ Funktionen für die grundlegenden Arbeiten,<br />

◦ Funktionen für komplexere Tätigkeiten, die die grundlegenden Funktionen aufrufen,<br />

◦ Funktionen, die die komplexeren und vielleicht auch die grundlegenden Funktionen<br />

aufrufen.<br />

Vorteil: wenn Sie eine Funktion erstellen (den Funktionsprogrammtext schreiben),<br />

müssen Sie nur über einen kleinen Problembereich nachdenken.<br />

• Es sollten verschiedene Arten von Funktionen unterschieden werden:<br />

◦ Funktionen mit Benutzerinteraktion oder Ein-/Ausgabe; dabei wenn möglich Lesen<br />

und Schreiben in getrennten Funktionen (z. B. Lesefunktionen, Schreibfunktionen),<br />

◦ Funktionen zur Durchführung von Berechnungen – diese Funktionen sollten keine<br />

Benutzerinteraktion/Eingabe/Ausgabe haben! Sonst sind diese Funktionen nicht<br />

allgemein nutzbar, z. B. bei Berechnungen mit einem woanders berechneten Wert<br />

anstatt mit einem eingegebenen.<br />

◦ manchmal (selten!) ist eine Mischung nötig.<br />

Allgemeine Regel: entweder Benutzerinteraktion oder Berechnungen!<br />

• Eine Funktion sollte mit dem Rest des Programms nur über die Parameterliste und den<br />

Rückgabewert der Funktion kommunizieren – sehr wichtig: nicht über globale Variablen.<br />

Dagegen sind globale Konstanten (und globale Typen) sinnvoll, vgl. auch (7.53).<br />

• Keine Benutzung der exit-Funktion ( (12.82c), beendet das Programm sofort),<br />

insbesondere nicht in Berechnungsfunktionen. Grund: manchmal läuft eine Funktion in<br />

einen Fehler hinein, es kann jedoch nicht hier entschieden werden, wie zu reagieren<br />

ist. Anstatt das ganze Programm (z. B. eine CAD-Programm) zu beenden, sollte ein<br />

Fehlerindikator gesetzt werden, so dass die aufrufende Stelle entscheiden kann, was zu<br />

tun ist.<br />

7.9 Agile Methoden in der Softwareentwicklung<br />

(7.90) Übb Moderne Softwareentwicklungsmethoden, die unter dem Oberbegriff ” Agile Methoden“<br />

zusammengefasst werden, versuchen, Software größerer Qualität schneller dem Kunden<br />

liefern zu können. In diesem Kurs werden zwei Aspekte näher erläutert und teilweise auch<br />

in der Praxis gezeigt.<br />

(7.91) Teilweise im Gegensatz zu den Ausführungen in (Kap. 6.4) wird seit mehreren Jahren in der<br />

Softwareindustrie versucht, Programme für Kunden schneller, genauer, kundenorientierter<br />

zu entwickeln ohne großen Dokumenationsüberbau. Diese Methoden fasst man unter Agile<br />

Methoden zusammen. Im sog. ” Extreme Programming“ (Kent Beck) gibt es u. a. zwei<br />

Praktiken, die in diesem Kurs näher erläutert werden:<br />

• Pair Programming, das Programmieren zu zweit vor einem Rechner; dieses wird auch<br />

in den Übungen zu diesem Kurs als Option angeboten.<br />

• Test Driven Development (TDD), das Schreiben von Test-Programmcode vor dem<br />

Schreiben des zugehörigen produktiven Programmcodes. In der Praxis wird dieses durch<br />

Werkzeugunterstützung erleichtert.<br />

Im Kurs wird auf die beiden genannten Aspekte näher eingegangen; hier sollen sie nur kurz<br />

als Erinnerung erwähnt werden.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 92<br />

Teil II<br />

Informatik II (2. Semester)<br />

8 Speicherklassen, modulare Programmierung<br />

8.0 Überblick<br />

Dieses Kapitel befasst sich mit der Verwirklichung der Grundgedanken, die in (Kap. 6.3) sprachunabhängig<br />

vorgestellt wurden: Speicherklassen und der Programmierstil der modularen<br />

Programmierung.<br />

Unterkapitel 1 beschreibt die von C ++ unterstützten Speicherklassen (d. h. der verschiedenen<br />

Arten von Lebensdauer und Verwaltung von Datenspeicherplätzen): statisch, automatisch<br />

und dynamisch-kontrolliert. Die beiden ersten werden hier auch genauer vorgestellt, die<br />

dritte wird später behandelt (Kap. 10.3).<br />

Das Unterkapitel 2 zeigt, wie die modulare Programmierung in C ++ verwirklicht wird,<br />

nämlich mit Hilfe von Programmdateien. Hierbei kennzeichnet es einen guten Programmierer,<br />

möglichst viele der Namen aus einer Programmdatei zu verbergen, nämlich alle, die<br />

von außen nicht direkt benötigt werden. Da dieses in C ++/C leider nur durch einen Extra-<br />

Zusatz möglich ist, unterlassen viele Programmierer meist aus Gedankenlosigkeit das sehr<br />

wichtige Verbergen, was dazu führen kann, dass solche Programme sehr schlecht zu warten<br />

sind. Weiterhin wird das Konzept der Headerdateien beschrieben, das zur Wahrung der<br />

Konsistenz von ” öffentlichen“ Namen eingeführt wurde.<br />

In manchen Fällen muss man sowohl C- als auch C ++-Programmdateien innerhalb eines<br />

Programms zusammenbinden. Die dabei zu beachtenden Besonderheiten sind in (8.27) zusammengestellt.<br />

8.1 Statische und automatische Speicherklasse<br />

(8.10) Übb In diesem Kapitel werden – als Erweiterung zur sprachunabhängigen Einführung (6.31)<br />

– die drei Speicherklassen erläutert, die durch C ++ unterstützt werden (8.11): die statische,<br />

die automatische und die dynamisch-kontrollierte Speicherklasse. In den beiden folgenden<br />

Punkten wird genauer gezeigt, wie die statische und die automatische Speicherklasse in C ++<br />

verwirklicht werden. (8.14) zeigt ein vollständiges Programmbeispiel. Für einen Programmierer<br />

ist es unerlässlich, die Eigenschaften und die Anwendungen dieser Speicherklassen zu<br />

kennen.<br />

(8.11) Eine Speicherklasse definiert die Lebensdauer eines Datenspeicherplatzes, die Art der Reservierung<br />

und der Freigabe. Die Sprache C ++ unterstützt direkt drei Speicherklassen, die<br />

Sprache C direkt nur zwei. Allg. Beschreibung der ersten beiden Speicherklassen s. a. (6.31).<br />

(a) Variable der statischen Speicherklasse 〈static storage class〉 ” leben“ die gesamte Programmlaufzeit,<br />

ihr Speicherplatz wird einmal zu Beginn des Programmslaufs reserviert und<br />

am Ende des Programmlaufs wieder freigegeben. Sie können eine Erstbelegung ( ” Initialisierung“)<br />

erhalten, jedoch i. a. nur mit einem konstanten Ausdruck (d. h. zur Kompilationszeit<br />

auswertbar); falls nicht explizit initialisiert, werden sie implizit mit einem zugehörigen Nullwert<br />

initialisiert.<br />

(b) Variable der automatischen Speicherklasse 〈automatic storage class〉 ” leben“ nur während<br />

der Durchlaufzeit durch einen Block; ihr Speicherplatz wird bei Eintritt in den Block<br />

automatisch angelegt und bei Verlassen automatisch wieder freigegeben. Sie können mit<br />

beliebigem Ausdruck initialisiert werden; eine implizite Initialisierung findet i. a. nicht statt.<br />

(c) C++ Der dritten Speicherklasse dynamisch-kontrolliert gehören Speicherplätze an, die<br />

durch den Programmierer mit dem Operator new Op3k explizit angefordert werden und mit<br />

dem Operator delete Op3l explizit freigegeben werden – und zwar völlig unabhängig von<br />

der Blockstruktur des Programms. Der Bereich für diese Speicherplätze heißt Freispeicher


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 93<br />

(dynamischer Speicher, gelegentlich auch Name Heap, Näheres s. (Kap. 10.3)). Eine implizite<br />

Initialisierung beim Anlegen des Speicherplatzes findet i. a. nicht statt.<br />

↑↑ In C dienen Bibliotheksfunktionen – meist malloc(size t) und free() – dem gleichen Zweck<br />

(10.34).<br />

Anm Zu dem i. a.“ bei der Beschreibung der Initialisierungen in den drei Speicherklassen: Das<br />

”<br />

” i. a.“ kann durch Konstruktoren (Elementfunktionen zum Erzeugen und Initialisieren von<br />

Objekten) durchbrochen werden. Näheres dazu s. (9.21).<br />

(8.12) Statische Speicherklasse<br />

Eigenschaften (s. a. (Cpp/5.2)):<br />

Lebensdauer während ganzer Programmlaufzeit<br />

Initialisierung<br />

- explizit nur durch konstanten Ausdruck ⋆△<br />

- implizit mit zugehörigem Nullwert ⋆<br />

- wie oft nur einmal, meist zu Beginn des Programmlaufs △<br />

⋆ Besonderheiten bei Objekten s. (8.11Anm) und (9.21).<br />

△ ↑↑ Globale Variablen werden einmal zu Beginn des Programmlaufs initialisiert, statische lokale<br />

Variable ebenfalls einmal, jedoch C++ erst dann, wenn der Programm-Kontrollfluss die zugehörige<br />

Definition erstmals erreicht. Der Initialisierungsausdruck braucht in C++ nicht konstant zu sein, er<br />

muss nur zur Laufzeit bei der Initialisierung auswertbar sein.<br />

Syntax für Definition einer Variable mit statischer Speicherklasse:<br />

globale Variable DeklarationNormaleForm ▽<br />

lokale Variable<br />

13 static DeklarationNormaleForm<br />

▽ Globale Variablen sind immer statisch.<br />

(8.13) Automatische Speicherklasse<br />

Eigenschaften (s. a. (Cpp/5.2)):<br />

Lebensdauer nur während Block durchlaufen wird<br />

Initialisierung<br />

- explizit durch einen beliebigen Ausdruck<br />

- implizit KEINE! ⋆<br />

- wie oft jedesmal bei Eintritt in den Block<br />

⋆ Besonderheiten bei Objekten s. (8.11 Anm) und (9.21).<br />

Spezialfall formaler Funktionsparameter:<br />

Lebensdauer nur während Funktionsblock durchlaufen wird<br />

Initialisierung immer, und zwar durch aktuellen Parameter<br />

- wie oft jedesmal beim Funktionsaufruf<br />

Syntax für Variable mit automatischer Speicherklasse:<br />

globale Variable – nicht möglich –<br />

lokale Variable DeklarationNormaleForm △⊙<br />

Spezialfall<br />

formaler Funktionsparameter 14 TypSpezifizierer1..n 30 Deklarator ⊙<br />

↑↑<br />

△ 13<br />

Vorsetzen des Speicherklassen-Spezifizierers auto erlaubt, wird aber ohne Bedeutungsänderung<br />

üblicherweise weggelassen.<br />

⊙ 13<br />

Ein Vorsetzen des Speicherklassen-Spezifizierers register veranlasst den Compiler, die<br />

Variable oder den formalen Parameter soweit möglich im Register abzulegen; diese Variable<br />

hat keine Datenspeicheradresse, d. h. die Anwendung des Adressoperators & (Op3g (10.21b))<br />

ist nicht erlaubt.<br />

(8.14) Bsp Kellerspeicher, ähnlich (6.32):<br />

#include // Beispiel D08-14.CPP<br />

using namespace std;


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 94<br />

void fehlerSchieben(); // Fehlermeldung Schieben<br />

void fehlerHolen(); // Fehlermeldung Holen<br />

int kellerSpeicher(bool schiebenJaNein,int wert)<br />

{<br />

static const int maxanz=100; // "static": statische SpKl<br />

static int speich[maxanz], // "static": statische SpKl<br />

zaehler=0;<br />

}<br />

if (schiebenJaNein) {<br />

if (zaehler==maxanz) fehlerSchieben(); // Fehler<br />

else speich[zaehler++]=wert;<br />

}<br />

else {<br />

if (zaehler)<br />

wert=speich[--zaehler];<br />

else {<br />

fehlerHolen(); // Fehler<br />

wert=0; // damit definierter Wert<br />

}<br />

}<br />

return wert;<br />

void fehlerSchieben()<br />

{ cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 95<br />

(8.21)<br />

es eigentlich sein sollte; das Verbergen wird durch zusätzliche Kennzeichung erreicht. Ohne<br />

diese zusätzliche Kennzeichnung hat ein Name externe Bindung und ist daher öffentlich.<br />

In C++(neu) gilt es jedoch als missbilligt, Namen durch interne Bindung zu verbergen. Die<br />

neue und zu bevorzugende Vorgehensweise geschieht mit unbenannten Namensbereichen<br />

(8.23a). Das allgemeine Konzept der Namensbereiche wird in (8.23c) kurz erläutert.<br />

Um bei den Namen, die öffentlich sein sollen, nicht in sehr schwer handhabbare Konsistenzprobleme<br />

zu geraten, hat sich schon in C ein recht interessantes Konzept entwickelt,<br />

das auch in C ++ ausgiebig angewandt wird: die Abspaltung der Deklarationen von Namen<br />

mit externer Bindung in sog. Headerdateien. Diese Headerdateien werden dann mit Hilfe<br />

einer Include-Direktive in jede Programmdatei eingefügt, in der die dort erwähnten Namen<br />

in irgendeiner Weise auftreten, d. h. benutzt oder auch definiert werden. Eine genaue Auflistung,<br />

welche Bestandteile zu einer Header- und welche zu einer Programmdatei gehören,<br />

ist in (8.24) ausführlich beschrieben.<br />

Anm Eine Ausnahme bilden die Typdefinitionen, meist in Form von Klassendefinitionen (Kap. 9);<br />

hier erscheinen i. a. sogar die Definitionen in Headerdateien.<br />

Zwei ausführliche C ++-Beispielprogramme (8.25, 8.26) erläutern den Umgang mit mehreren<br />

Programmdateien.<br />

Die Besonderheiten beim Zusammenbinden von C- und C ++-Programmdateien sind in (8.27)<br />

zusammengestellt.<br />

(a) Um ein Zusammenspiel zwischen verschiedenen Übersetzungseinheiten zu erlauben, müssen<br />

dem Linker in den Objektdateien (d. h. kompilierten Dateien) zu verknüpfende Namen angegeben<br />

werden. Die Stellen, an denen Referenzen benötigt werden (z. B. ein Funktionsaufruf),<br />

werden durch den Linker verknüpft ( ” aufgelöst“) mit Referenzangeboten (z. B. eine Funktionsdefinition).<br />

Diese Namen, die der Linker in den Objektdateien erhält, unterliegen der<br />

sog. externen Bindung 〈external linkage〉. Zu den Begriffen Objektdatei oder Objektprogramm,<br />

Linker oder Binder s. (1.32).<br />

Dagegen haben Namen einer Übersetzungseinheit, die für den Linker versteckt sind, die sog.<br />

interne Bindung 〈internal linkage〉. Daher können Namen mit interner Bindung nie mit<br />

gleichlautenden Namen anderer Übersetzungseinheit kollidieren.<br />

Eine bessere Möglichkeit, Namen zu verbergen, wird in C++(neu) durch einen unbenannten<br />

Namensbereich 〈unnamed namespace〉 angeboten. Dieses wird im Folgenden normalerweise<br />

eingesetzt.<br />

(b) Beim Zusammenspiel mehrerer Übersetzungseinheiten sollte daher folgende Regel beachtet<br />

werden:<br />

• Namen sollten normalerweise verborgen sein (durch interne Bindung oder besser durch<br />

Einschluss in einen unbenannten Namensbereich), und zwar wegen der Übersichtlichkeit;<br />

dann gibt auch keine Namenskonflikte mit anderen Übersetzungseinheiten.<br />

• Nur wenn eine Kommunikation mit anderen Übersetzungseinheiten nötig ist, sollte die<br />

externe Bindung vergeben werden.<br />

Dieses sollte bei jedem Namen geprüft werden! Diese Regel unterstützt auch das Geheimnisprinzip<br />

(6.13c).<br />

(c) Beispiele: Namen von Funktionen, die in anderen Übersetzungseinheiten aufgerufen werden<br />

sollen, müssen externe Bindung erhalten. Dagegen sollten Funktionen, die für andere<br />

Funktionen der gleichen Übersetzungseinheit Serviceberechnungen durchführen, verborgen<br />

werden. Variable sollten (fast) immer verborgen sein!<br />

(d) In diesem Zusammenhang ist die Regel genau einer Definition 〈one definition rule<br />

(ODR)〉 wichtig: Zu externen Namen darf und muss genau eine Definition (Referenzangebot)<br />

gehören, alle anderen Übersetzungseinheiten dürfen nur Deklarationen dieses Namens<br />

haben. Die ODR gilt natürlich auch für interne Namen.<br />

↑↑ Erlaubt nach den Regeln von C ++/C ist es, dass ein Linker ggf. keine Groß-/Kleinschreibung<br />

unterscheidet oder auch nur eine gewisse Anzahl von Zeichen für einen Namen als<br />

unterscheidend erkennt. Diese Beschränkung trifft heutzutage selten zu.<br />

Bei deklarierten Funktionen kann auf eine Definition verzichtet werden, wenn sie nirgendwo<br />

aufgerufen werden (7.23 Anm3).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 96<br />

(8.22) Externe Bindung<br />

Ein globaler Name erhält externe Bindung:<br />

Funktionsname Definition NormaleFunktionsDefinition<br />

Deklaration externopt NormaleFunktionsDeklaration<br />

Variablenname Definition DefinitionGlobaleVariableOhneSpklassenSpez ⋆<br />

Deklaration extern VariablenDeklaration<br />

Klassenname ⊙ Def./Dekl. – immer externe Bindung –<br />

Konstantenname Def./Dekl. – s. ↑↑2 –<br />

⋆ i. a. mit Initialisierung<br />

↑↑1 Genauere Regeln s. (Cpp/5.3).<br />

⊙ s. (9.12b)<br />

↑↑2 Auch Konstantennamen können externe Bindung haben ( ” extern const ...“, und zwar als<br />

Def., wenn initialisiert, sonst als Dekl.), dieses ist aber unüblich. Statt dessen definiert man<br />

häufig z. B. den Konstantennamen mit interner Bindung (8.23) in jeder Übersetzungseinheit,<br />

in der er benötigt wird, d. h. in der zugehörigen Headerdatei (8.24c1).<br />

(8.23) Um Namen innerhalb einer Programmdatei verborgen zu halten, kann man sie in einen<br />

unbenannten Namensbereich stellen (C++(neu), s. (a)) oder ihnen interne Bindung geben<br />

(ältere Methode in C/C ++, s. (b)).<br />

(a) C++(neu)<br />

Die in C++(neu) geschaffene und jetzt zu bevorzugende Möglichkeit des Verbergens von Namens<br />

auf den Bereich innerhalb einer Programmdatei (d. h. eines Moduls) ist der unbenannte<br />

Namensbereich.<br />

Das allgemeine Konzept der Namensbereiche wird in (c) kurz erläutert. Daher wird der<br />

unbenannte Namensbereich hier nur formal ohne nähere Erläuterung der Wirkungsweise<br />

eingeführt: jede Deklaration und jede Definition, auf die außerhalb einer Programmdatei<br />

nicht zugegriffen werden soll, erscheint in einer solchen Namensbereich-Definition:<br />

40T eil DefinitionUnbenannterNamensbereich <br />

namespace { DeklarationenUndOderDefinitionen }<br />

Wieviele solcher unbenannten Namensbereiche innerhalb einer Programmdatei gebildet werden,<br />

ist gleichgültig; der Compiler betrachtet alle unbenannten Namensbereich-Definitionen<br />

einer Datei als eine einzige. Bei zu verbergenden Funktionen ist es wichtig, dass sowohl die<br />

Deklaration als auch die Definition in einem solchen unbenannten Namensbereich liegen.<br />

Die Wirkung ist genau, was gewünscht wird: wenn ein Name in einer solchen unbenannten<br />

Namensbereich-Definition erscheint, kann auf diesen Namen von einer anderen Datei aus<br />

nicht mehr zugegriffen werden. Er ist demnach verborgen im Sinne von (6.32, 6.33), obwohl<br />

er rein formal der externen Bindung unterliegt. Innerhalb derselben Programmdatei kann<br />

jedoch beliebig auf ihn zugegriffen werden, und zwar sowohl von Stellen außerhalb als auch<br />

von Stellen innerhalb des unbenannten Namensbereichs.<br />

In (8.25) wird gezeigt, wie ein unbenannter Namensbereich benutzt wird.<br />

(b) Interne Bindung<br />

Ein globaler Name erhält interne Bindung:<br />

Funktionsname Definition static NormaleFunktionsDefinition<br />

Deklaration static NormaleFunktionsDeklaration<br />

Variablenname Definition static DefinitionGlobaleVariableOhneSpklassenSpez ⋆<br />

Deklaration ⊙ static VariablenDeklaration<br />

Konstanten- Definition const DefinitionKonstanteMitInitialisiserung<br />

name (ohne Spezifizierer extern)<br />

– vgl. auch (8.22 ↑↑2) –<br />

⋆ i. a. mit Initialisierung<br />

↑↑ Genauere Regeln s. (Cpp/5.3)<br />

⊙ selten vorkommend


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 97<br />

Das Beispiel (8.26) zeigt diese Verwendung von static.<br />

Das Schlüsselwort static ist sehr schillernd, es hat (leider) verschiedene Bedeutungen. Es<br />

kann statische Speicherklasse bedeuten (8.12) oder auch interne Bindung. Um diese Doppelbedeutung<br />

nicht mehr zu unterstützen, sollte static nur noch in der Bedeutung statische<br />

Speicherklasse und nicht mehr in der Bedeutung interne Bindung benutzt werden. Das<br />

letztere gilt daher in C++(neu) als missbilligt (Str3/Kap. B.2.3). An dessen Stelle sollte man daher<br />

einen unbenannten Namensbereich (a) nehmen.<br />

(c) ↑↑ Namensbereiche C++(neu)<br />

Ein Namensbereich dient der logischen Gruppierung von Namen. Zur Vermeidung von Namenskonflikten<br />

und damit der ” globale“ Namensbereich (alle Namen mit externer Bindung über alle<br />

Programmdateien hinweg) nicht überfrachtet wird, kann man eine Gruppe von Namen in einem<br />

Namensbereich ablegen.<br />

40 NamensbereichDefinition namespace NamensbereichNameopt { 10 Deklaration0..n }<br />

Auf Namen eines Namensbereiches kann innerhalb des Bereichs direkt zugegriffen werden, außerhalb<br />

durch explizite Spezifizierung mit dem Operator :: Op1b<br />

NamensbereichName :: Bezeichner<br />

Statt der expliziten Spezifizierung können durch eine using-Direktive sämtliche Namen eines Namensbereiches<br />

zugreifbar gemacht werden, und zwar für den Geltungsbereich, in dem die Direktive<br />

steht. (Bei Namenskonflikten hat der Name des Geltungsbereichs Vorrang vor dem aus dem Namensbereich;<br />

hier hilft dann nur die explizite Spezifizierung.)<br />

UsingDirektive using namespace NamensbereichName ;<br />

Alle Namen der Standardbibliotheken sind neuerdings im Namensbereich std abgelegt, daher die<br />

Direktive ” using namespace std;“. Beim Compiler MS Visual C ++ 6.0 sind in den Headerdateien<br />

ohne .h diese Namen in std abgelegt, bei denen mit .h im globalen Namensraum.<br />

Wenn man NamensbereichName in der Namensbereichdefinition weglässt ( ” unbenannter Namensbereich“,<br />

s. (a)), setzt der Compiler einen eigenen eindeutigen Namensbereich-Namen ein, dazu<br />

eine UsingDirektive für diesen Namen des Namensbereichs. Da für den Programmierer dieser Name<br />

nicht bekannt ist, kann außerhalb der Programmeinheit auf darin deklarierte Namen nicht zugegriffen<br />

werden. Diese Eigenschaft wird entspr. (a) zum Verbergen von Namen benutzt.<br />

(8.24) Headerdateien<br />

(a) Es ist sehr sinnvoll, die Deklarationen für Namen mit externer Bindung von den zugehörigen<br />

Definitionen zu trennen: Headerdatei 〈header file〉 und Programmdatei 〈program file〉. Erstere<br />

hat meist die Dateinamenerweiterung .H oder .h, letztere meist die Erweiterung .CPP<br />

oder .cpp, auch .CXX oder .cxx.<br />

Der wichtigste Grund dafür ist die Konsistenz zwischen Deklaration und Definition: Da<br />

in der Praxis die Programme ” leben“, d. h. immer wieder verändert werden, kann bei einer<br />

Änderung des Namens oder der Signatur einer Funktion vergessen werden, die (ggf. an vielen<br />

Stellen vorhandenen) Deklarationen der neuen Definition anzupassen. Der Linker bemerkt<br />

solche Änderungen nicht immer, so dass sich sehr schwer handhabbare Fehler einschleichen<br />

können. Viel besser ist es daher – zudem auch verbunden mit wesentlich weniger Aufwand<br />

–, genau eine Deklaration in genau eine Headerdatei zu schreiben und diese Datei überall<br />

einzuschließen, wo die Deklaration benötigt wird. Sehr wichtig ist es dann, dass diese Headerdatei<br />

auch in die Programmdatei eingeschlossen wird, die die Definition enthält, damit<br />

der Compiler die Konsistenzprüfung durchführen kann!<br />

(b) Empfehlung:<br />

• Bei nur wenigen Programmdateien (Praxis: bis zu etwa fünf Dateien) genügt es meist,<br />

die Deklarationen aller Programmdateien in eine einzige Headerdatei zusammenzufassen<br />

und diese überall einzuschließen.<br />

• Sonst ist es üblich, je Programmdatei eine Headerdatei (gleicher Dateinamen, Erweiterung<br />

.H oder .h) zu erstellen; diese wird dann, wo nötig, eingeschlossen.<br />

• Manche Bestandteile einer Headerdatei (z. B. Klassendefinitionen (9.11)) dürfen nicht<br />

mehrfach in derselben Übersetzungseinheit eingeschlossen sein. Wenn jedoch eine Headerdatei<br />

eine andere einschließt (z. B. wegen benötigter Typendefinition), kann es leicht<br />

zu einem (indirekten) Doppeleinschluss kommen. Durch die Möglichkeiten der bedingten<br />

Kompilierung kann man jedoch leicht einen Include-Wächter einfügen; Näheres s.<br />

(5.75c) und die Beispiele (8.25a) und (9.31a).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 98<br />

• Grundsätzliche Aufteilung, Näheres s. (c):<br />

◦ Deklarationen, die in mehreren Programmdateien benötigt werden, z. B. Deklarationen<br />

von Funktionen mit externer Bindung, sollen in genau einer Headerdatei<br />

erscheinen,<br />

◦ die Definitionen dieser Funktionen müssen in genau einer Programmdatei geschehen,<br />

◦ jedoch Funktionen in einem unbenannten Namensraum (oder solche mit interner<br />

Bindung) – die ja nur in einer einzigen Programmdatei bekannt sein sollen – dürfen<br />

nicht in einer Headerdatei erscheinen, die Deklaration und die Definition geschieht<br />

in einer Programmdatei.<br />

(c) Auftrennung in Header- und Programmdatei:<br />

(1) Headerdatei:<br />

• Einschluss anderer (Standard- oder eigener) Headerdateien – jedoch aus Übersichtlichkeitsgründen<br />

nur der Dateien, die für diese Headerdatei direkt benötigt werden<br />

• Deklaration von Funktionen mit externer Bindung<br />

• Deklaration von Variablen mit externer Bindung<br />

• Folgende Definitionen, soweit sie in mehr als einer Programmdatei benötigt werden<br />

(wenn dagegen nur in einer einzigen: s. (2)):<br />

◦ Typdefinitionen (typedef) (5.62)<br />

◦ Klassen (9.11)<br />

◦ Makros (5.72, 5.73, 10.11b, 10.12b)<br />

◦ (ggf.) Konstanten (10.11a)<br />

◦ inline-Funktionen (10.12a)<br />

• Nie: Definition eines unbenannten Namensbereichs (8.23b)<br />

• Nie: Definitionen von Funktionen (außer inline)<br />

• Nie: Definitionen von globalen Variablen<br />

• Daran denken: Sinnvoll ist eine Verriegelung gegen Mehrfacheinschluss durch einen<br />

Include-Wächter (5.75c)!<br />

(2) Programmdatei:<br />

• Einschluss der (Standard- und eigenen) Headerdateien, die in dieser Programmdatei<br />

benötigt werden<br />

• Wichtig: Einschluss der zu dieser Programmdatei gehörigen Headerdatei (Konsistenzprüfung<br />

durch Compiler!)<br />

• Deklaration von Funktionen und von Variablen jeweils mit interner Bindung<br />

• Definition eines unbenannten Namensbereichs (8.23b)<br />

• Definition von Funktionen<br />

• Definition von Klassenelementen, z. B. Elementfunktionen (soweit sie nicht inline<br />

(10.12a) sein sollen)<br />

• Definition von globalen Variablen<br />

• Folgende Definitionen, soweit sie nur in einer einzigen Programmdatei benötigt<br />

werden (wenn dagegen in mehreren: s. (1)):<br />

◦ Typdefinitionen (typedef) (5.62)<br />

◦ Makros (5.72, 5.73, 10.11b, 10.12b)<br />

◦ (ggf.) Konstanten (10.11a)<br />

◦ inline-Funktionen (10.12a)<br />

↑↑ Häufig werden die beiden Begriffe Programmdatei und Übersetzungseinheit als synonym angesehen.<br />

Die genauere Definition: Eine Programmdatei ist eine von Programmierer geschriebene<br />

Datei mit Quelltext; durch den Präprozessorlauf (Kap. 5.7, im wesentlichen Einfügen<br />

der Einschlüsse (#include) und Makroerweiterungen (#define)) entsteht daraus die Übersetzungseinheit,<br />

die dem Compiler angeboten wird.<br />

(8.25) C++(neu) Beispiel Kellerspeicher, ähnlich (6.34): drei Dateien: (a,b,c). Hier wird die Benutzung<br />

des unbenannten Namensbereichs vorgestellt. Das gleiche Programm wird in (8.26) mit<br />

der älteren Form des Verbergens von Namen in einer Programmdatei durch interne Bindung<br />

(static) gezeigt.<br />

(a) // Beispieldatei D08-25A.H<br />

#ifndef D08_25A_H_ // Verriegelung gegen Mehrfacheinschluss


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 99<br />

#define D08_25A_H_<br />

// Kellerspeicher-Zugriffsfunktionen:<br />

void schiebe(int wert);<br />

int hole();<br />

#endif<br />

(b) // Beispieldatei D08-25B.CPP<br />

#include // wegen cout-Benutzung in fehler...-Funktionen<br />

using namespace std;<br />

(c)<br />

// Deklarationen dieser Datei: Einbindung hier zur Konsistenzprüfung!!<br />

#include "d08-25a.h"<br />

namespace { // unbenannter Namensbereich<br />

void fehlerSchieben();<br />

void fehlerHolen();<br />

}<br />

const int maxanz=100;<br />

int speich[maxanz],<br />

zaehler=0;<br />

void schiebe(int wert)<br />

{<br />

if (zaehler==maxanz) fehlerSchieben(); // Fehler<br />

else speich[zaehler++]=wert;<br />

}<br />

int hole()<br />

{<br />

if (zaehler) return speich[--zaehler];<br />

}<br />

fehlerHolen(); // Fehler<br />

return 0; // damit definierter Wert<br />

namespace { // Fortsetzung unbenannter Namensbereich<br />

void fehlerSchieben()<br />

{<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 100<br />

}<br />

schiebe(i);<br />

schiebe(-23); // Fehler<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 101<br />

eine in C ++ kompilierte Funktion in C ++ und entsprechend eine in C kompilierte Funktion<br />

in C aufgerufen werden kann.<br />

In C ++ sieht die Headerdatei folgendermaßen aus:<br />

// C++-Headerdatei<br />

extern "C" DeklarationCCppFunktion<br />

extern "C" {<br />

DeklarationCCppFunktion1..n<br />

}<br />

// Anm.: obigen Block { ... } nennt man Bindungsblock 〈linkage block〉<br />

Die C ++-Programmdatei sieht so aus (normale Funktionsdefinition):<br />

// C++-Programmdatei<br />

#include "C ++-Headerdatei "<br />

DefinitionCppFunktion // wie in C++ üblich<br />

In C ist folgendes zu tun (Headerdatei):<br />

// C-Headerdatei<br />

extern DeklarationCCppFunktion // auch ohne "extern" möglich<br />

// ggf. weitere Male: DeklarationCCppFunktion<br />

Anm Hier unbedingt auf (7.23Anm4) achten, d. h. bei parameterloser Funktion void als Parameter!<br />

Die C-Programmdatei sieht so aus:<br />

// C-Programmdatei<br />

#include "C-Headerdatei "<br />

DefinitionCFunktion // wie in C üblich<br />

Die obigen Zusätze für C ++ sind in oder auch den anderen C-Headerdateien bereits<br />

enthalten.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 102<br />

9 Objektorientierte Programmierung:<br />

Kapselung von Daten und Funktionen mit Zugriffskontrolle<br />

9.0 Überblick<br />

Die Ideen für die Objektorientierte Programmierung sind – sprachunabhängig – in (Kap.<br />

6.4) näher beschrieben. Das aktuelle Kapitel dient dazu, diese Ideen nach C ++ zu übertragen.<br />

Unterkapitel 1 zeigt, wie Klassen (in C ++ als Datentypen) und Objekte (Variable eines solchen<br />

Klassentyps) erzeugt und benutzt werden. Bestimmte Methoden, nämlich Konstruktoren,<br />

werden benötigt, ein Objekt zur Laufzeit zu erzeugen (Unterkapitel 2). Daher kann<br />

(für Sie als Teilnehmer dieses Kurses: muss) man Konstruktoren zur sinnvollen Initialisierung<br />

bauen. In Unterkapitel 3 wird ein ausführliches Beispiel gezeigt, dazu wird dort die<br />

Forderung begründet, Datenelemente einer Klasse immer zu verbergen.<br />

9.1 Klassen und Objekte<br />

(9.10) Übb Eine Klasse dient der Kapselung von Daten und (Zugriffs-)Funktionen mit der<br />

Möglichkeit der expliziten Zugriffskontrolle (VERBORGEN oder ÖFFENTLICH). In C ++ ist<br />

die Klasse ein (Daten-)Typ. Dieser muss dem Compiler in seiner internen Struktur bekanntgegeben<br />

werden. (9.11) erläutert, wie diese Klassendefinition durchgeführt wird. Der Punkt<br />

(9.12) befasst sich mit dem Gültigkeitsbereich der Klassenelemente und des Klassennamens.<br />

(9.11)<br />

(a)<br />

Variable des Typs Klasse werden Objekte genannt. Punkt (9.13) zeigt, wie Objekte in C ++<br />

gebildet und benutzt werden.<br />

Die Klasse ist in C ++ ein Typ, s. (2.25a2), (3.20) und (5.60, 5.61), und zwar ein benutzerdefinierter<br />

Typ; der Typ wird durch den Benutzer selbst definiert. Im Gegensatz zu<br />

den ” eingebauten“ Typen – wie z. B. int, double – kennt der Compiler von sich aus<br />

nicht den internen Aufbau einer Klasse; daher muss ihm der Aufbau mitgeteilt werden.<br />

Dieses geschieht durch die Klassendefinition.<br />

Anm Andere benutzerdefinierte Typen sind beispielsweise Arrays (Kap. 5.4), da der Benutzer selbst<br />

den Komponententyp und die Anzahl der Komponenten angibt.<br />

10,11,12,14,21 Klassen(Typ)Definition [NV] <br />

class KlassenName {<br />

<br />

-<br />

22 ZugriffsSpezifizierer :<br />

ElementDeklaration<br />

22 ZugriffsSpezifizierer [NV] private |<br />

| public<br />

Bsp siehe unter (b).<br />

<br />

- 0..n } ;<br />

↑↑ Da der Compiler mit obiger Klassenbeschreibung die gesamte Information zum Aufbau von<br />

Variablen dieses Typs erhält, handelt es sich tatsächlich um eine Definition, vgl. (7.21). Diese<br />

Definition muss allen Übersetzungseinheiten zur Verfügung stehen, in denen die Klasse<br />

gebraucht wird, daher sollte sie in einer Headerdatei stehen (8.24c1). Trotz des (durch<br />

Einschluss implizit) mehrfachen Vorhandenseins der Klassendefinition in den verschiedenen<br />

Übersetzungseinheiten gilt die ODR (8.21d); sich gleichende Definitionen derselben Klasse in<br />

verschiedenen Übersetzungseinheiten gelten als eine einzige.<br />

(b) Eine Klassendefinition besteht demnach aus:<br />

• KlassenName – er gibt den Namen der Klasse (Typnamen) an.<br />

• Folge von ElementDeklarationen; diese Elemente können sein:<br />

◦ Datenelemente – syntaktisch wie Variablendefinitionen,<br />

◦ Elementfunktionen (Funktionen, die i. a. etwas mit den Datenelementen tun) –<br />

syntaktisch meist Funktionsdeklarationen,


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 103<br />

(9.12)<br />

◦ dazu weitere Deklarationen, beispielsweise Typen.<br />

• Optional beliebig oft ZugriffsSpezifizierer: (mit Doppelpunkt), und zwar private:<br />

(dann sind die nachfolgenden Elemente verborgen) und public: (dann sind sie öffentlich);<br />

steht direkt hinter der öffnenden geschweiften Klammer kein Spezifizierer, so wird<br />

dort implizit private: gesetzt.<br />

Natürlich müssen die Elementfunktionen – zumindest wenn sie irgendwo benutzt werden –<br />

zusätzlich noch definiert werden; dieses geschieht i. a. außerhalb der Klassendefinition, und<br />

zwar in der zur Klasse gehörigen Programmdatei.<br />

Anm1 Eine Funktionsdefinition innerhalb der Klassendefinition ist auch erlaubt; in der Praxis wird<br />

dieses nur bei sehr kleinen Funktionsblöcken gemacht. Hierbei fasst der Compiler die Funktion<br />

als inline (10.12a, 11.11b) auf.<br />

Anm2 Statt des Schlüsselworts class ist auch das Schlüsselwort struct erlaubt, Näheres dazu s.<br />

(11.11c).<br />

Bsp – siehe auch (9.12 Bsp, 9.13 Bsp, 9.21 Bsp) –<br />

class Komplex {<br />

double re, im;<br />

public:<br />

// "Markierung" für Konstruktoren, s. Punkt (21)<br />

void setzKompl(double realT, double imagT);<br />

double real() { return re; } // Dekl. und Definition von real()<br />

double imag(); // Deklaration von imag()<br />

};<br />

(a) Der Gültigkeitsbereich 〈scope〉 der Klassenelemente ist der Bereich der gesamten Klasse<br />

(genauer: jeweils ab Deklarationspunkt), jedoch nicht außerhalb der Klasse. Um jedoch<br />

z. B. bei der Definition einer Elementfunktion, wenn sie (so der Normalfall!) außerhalb der<br />

Klassendefinition erfolgt, den Klassen-Gültigkeitsbereich zu öffnen, muss man den Bereichsauflösungsoperator<br />

:: (Op1b) benutzen:<br />

KlassenName::ElementName<br />

Hierbei gilt der gesamte Funktionsblock – dazu auch die Parameterliste – ebenfalls als<br />

zum Klassen-Gültigkeitsbereich zugehörig; hier braucht man den Bereichsauflösungsoperator<br />

nicht zu benutzen. Innerhalb dieses Bereichs darf auf jedes Klassenelement zugegriffen<br />

werden – unabhängig von einer Zugriffsbeschränkung.<br />

↑↑1 Auch bei Definition von Elementfunktionen innerhalb der Klassendefinition (9.11b Anm1) kann<br />

im Funktionsblock auf alle Elementnamen zugegriffen werden, auch auf solche, die in der<br />

Klassendefinition erst folgen.<br />

↑↑2 Es ist erlaubt, auch innerhalb des Funktionsblocks von Elementfunktionen den Bereichsauflösungsoperator<br />

zu benutzen (KlassenName::ElementName); dies ist eine der Möglichkeiten,<br />

ggf. Namenskonflikte aufzulösen. Eine andere ähnliche Möglichkeit ist die Benutzung<br />

des this-Zeigers (this->ElementName), s. (11.13).<br />

(b) Klassennamen haben immer externe Bindung, s. (8.22).<br />

(9.13)<br />

Bsp – zu (9.11 Bsp), siehe auch (9.13 Bsp, 9.21 Bsp) –<br />

// Definition von Elementfunktionen der Klasse Komplex:<br />

void Komplex::setzKompl(double realT, double imagT)<br />

{ re=realT; im=imagT; }<br />

double Komplex::imag()<br />

{ return im; }<br />

Ein Objekt ist in C ++ eine Variable eines Klassentyps. Die Anweisung zur Erzeugung<br />

eines solchen Objekts (einer solchen Variablen) erfolgt genauso wie bei Variablen<br />

von eingebauten Typen (z. B. bei int) durch eine Variablendefinition.<br />

Allgemeine Variablendefinition: Typ VariablenName ;<br />

Spezielle Variablendefinition (Objekt): KlassenName ObjektName ;


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 104<br />

Näheres zur Initialisierung von Objekten s. (9.21).<br />

Der Zugriff auf ein Element einer Klasse (Datenelement oder Elementfunktion) kann i. a.<br />

nur über ein zu dieser Klasse gehöriges Objekt, d. h. über eine Variable dieses Klassentyps<br />

geschehen; dieser Zugriff erfolgt mit dem Operator . (Op2d), wie schon in (5.23) für Elementfunktionen<br />

erläutert:<br />

ObjektName.ElementName<br />

Anm1 Anders innerhalb von Elementfunktionen selbst: diese können direkt auf die Elemente zugreifen,<br />

da sie ja beim Aufruf implizit mit einem Objekt verbunden sind.<br />

Anm2 Zum Öffnen des Klassen-Scopes bei der Definition von Elementfunktionen s. (9.12a).<br />

↑↑ Es gibt auch Zugriffe ohne ein Objekt, und zwar bei sog. statischen Elementen. Diese werden<br />

erst in (Kap. 11.5) besprochen.<br />

Bsp – zu (9.11 Bsp, 9.12 Bsp), siehe auch (9.21 Bsp) –<br />

Komplex z; z.setzKompl(-1.7,4.2);<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 105<br />

(9.23)<br />

// Beispiel D09-22.CPP<br />

#include <br />

using namespace std;<br />

class Test {<br />

int x, y;<br />

public:<br />

Test(); // Standardkonstruktor (kein Par.)<br />

Test(int zahl); // Konstruktor mit 1 Parameter<br />

Test(int zahlX,int zahlY); // Konstruktor mit 2 Parametern<br />

int gibX(); // Auslesen x<br />

int gibY(); // Auslesen y<br />

};<br />

Test::Test() { x=y=0; } // Def. Std.-Konstr.<br />

Test::Test(int zahl) { x=y=zahl; } // Def. Konstr. 1 Par.<br />

Test::Test(int zahlX,int zahlY) { x=zahlX; y=zahlY; } // Def. K. 2 Par.<br />

int Test::gibX() { return x; } // Rückgabe Datenelement x<br />

int Test::gibY() { return y; } // Rückgabe Datenelement y<br />

int main()<br />

{ // SYNTAX FÜR KONSTRUKTORAUFRUFE je nach Anzahl der Parameter<br />

// (0): Standardkonstruktor, (1): Konstr. 1 Par., (2): Konstr. 2 Par.<br />

// Empfehlung: jeweils erste Form jeder Zeile<br />

// impliziter, expliziter, impliziter Konstruktoraufruf<br />

Test a; Test b=Test(); // (0)<br />

Test c(4); Test d=Test(4); Test e=4; // (1)<br />

Test f(1,3); Test g=Test(1,3); // (2)<br />

}<br />

Test h(3,4),i,j=2,k=Test(0,-1),l(3),m=Test(),n=Test(5);<br />

// VarName KonstrAnzahlPar: h 2; i 0; j 1; k 2; l 1; m 0; n 1<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 106<br />

class Keller {<br />

public:<br />

Keller(); // Standardkonstruktor<br />

~Keller(); // Destruktor<br />

void schiebe(int wert);<br />

int hole();<br />

private:<br />

void fehlerSchieben();<br />

void fehlerHolen();<br />

enum { maxanz=100 }; // Konstante mit Klassen-Scope (12.21)<br />

int speich[maxanz],<br />

zaehler;<br />

};<br />

#endif<br />

(b) // Beispieldatei D09-31B.CPP<br />

#include <br />

using namespace std;<br />

#include "d09-31a.h" // Klassendefinition Keller<br />

(c)<br />

Keller::Keller() // Standardkonstruktor<br />

{<br />

zaehler=0; // wichtige Erstbelegung!!<br />

}<br />

// Nur zum Demonstrieren des Konstruktors - NICHT FÜR DIE PRAXIS!<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 107<br />

#include "d09-31a.h" // Klassendefinition Keller<br />

int main()<br />

{<br />

Keller kellerVar;<br />

int zahl;<br />

}<br />

cin >> zahl;<br />

kellerVar.schiebe(zahl);<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 108<br />

};<br />

int gibJahr() { return jahr; } // s. Punkt (11b Anm.1) in ds. Kap.<br />

// ...<br />

Eine Änderung der internen Repräsentation der Daten muss die Schnittstelle nicht ändern.<br />

Wenn oft die Anzahl Tage zwischen zwei Datumwerten desselben Jahres ausgerechnet werden<br />

muss (z. B. mit der Elementfunktion differenz(Datum &anderTag) im Beispiel unten oder<br />

als Differenz datum1-datum2, empfiehlt sich die Datumdarstellung in Form TagDesJahres<br />

statt Tag und Monat (dazu die Jahreszahl):<br />

class Datum {<br />

int tagJahr; // statt: int tag; int monat;<br />

int jahr;<br />

};<br />

public:<br />

Datum();<br />

Datum(int t,int m, int j);<br />

~Datum() {} // leerer Destruktor<br />

int gibTag(); // muss jetzt berechnet werden: Def. El.-Fkt. woanders<br />

int gibMonat(); // muss jetzt berechnet werden: Def. El.-Fkt. woanders<br />

int gibJahr() { return jahr; }<br />

// jetzt neue Funktion differenz (Anzahl Tage zu anderTag):<br />

int differenz(Datum &anderTag);<br />

// ...<br />

Wenn dagegen sehr häufig Datumdifferenzen als Anzahl Tage über noch größere Zeiträume<br />

berechnet werden sollen (Astronomie), empfiehlt sich die interne Repräsentation als ” Julianisches<br />

Datum“ (JD, nach Julius Caesar Scaliger, 16. Jh., it. Naturforscher und Humanist):<br />

gezählt werden die Tage seit dem 01.01.4713 v. Chr. (Nullpunkt um 12 Uhr Weltzeit) –<br />

oder das Modifizierte Julianische Datum (MJD) mit dem Nullwert am 17.11.1858 um 0 Uhr<br />

Weltzeit. Der 01.01.2000 0 Uhr UT entspricht 51 544 MJD bzw. 2 451 544,5 JD.<br />

(9.33) Weitere wichtige Möglichkeiten der Objektorientierung werden in (Kap. 11) besprochen.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 109<br />

10 Einige C ++-Ergänzungen, Zeigertyp, Freispeicher<br />

10.0 Überblick<br />

In diesem Kapitel werden einige wenige Ergänzungen in C ++ eingeschoben, die sinnvoll sind,<br />

um sich vertieft mit der Objektorientierung (Kap. 11) befassen zu können. Die weiteren sinnvollen<br />

C ++-Vertiefungen, die dazu nicht unbedingt notwendig sind, sind auf (Kap. 12) verschoben.<br />

Die hier vorgestellten Ergänzungen sind wichtig für das Verständnis der Folgekapitel.<br />

Unterkapitel 1 stellt zusammen, wie symbolische Konstanten gebildet werden können und<br />

wie die Benutzung sehr kurzer Funktionen für die Laufzeit beschleunigt wird.<br />

In Unterkapitel 2 wird der Datentyp Zeiger genauer erläutert. Dazu werden wichtige Regeln<br />

für die Interpretation zusammengesetzter Typen, ferner die Benutzung des const vorgestellt.<br />

Den in der Praxis sehr häufig anzutreffenden Umgang mit dem Freispeicher (oder dynamischen<br />

Speicher) stellt Unterkapitel 3 vor.<br />

10.1 Symbolische Konstanten, Makros und inline-Funktionen<br />

(10.10) Übb Dieses Unterkapitel zeigt zunächst, wie ein Programm für Programmierer lesbarer<br />

gemacht werden kann: statt wörtliche Konstanten ( ” Literale“) zu benutzen, sollen symbolische<br />

Konstanten (d. h. Namen) genommen werden. Ebenso werden Hinweise gegeben, wie<br />

die Ausführung kurzer Funktionen zur Laufzeit effizienter gemacht werden kann.<br />

(10.11) Symbolische Konstanten statt ” magischer Zahlen“ sind für eine gute Programmierung<br />

sehr wichtig, wie schon früher in (4.72) und (5.44) erwähnt wurde. Folgende Möglichkeiten<br />

bestehen hierbei:<br />

(a) C++ Konstante – häufig mit eigenem Speicherplatz,<br />

z. B. const Typ KonstantenName = Initialisierer ;<br />

Solche Konstanten haben globalen, lokalen oder Klassen-Gültigkeitsbereich – ja nach Definitionsort.<br />

In C bereits bekannt, jedoch erst in C++ auch als Indexgrenzen o. ä. erlaubt,<br />

d. h. als Bestandteil eines konstanten Ausdrucks.<br />

↑↑ In C++(alt) bei Klassen als statische Konstanten etwas schwierig handhabbar, in C++(neu) bei<br />

integralen Typen besser, da übersichtlichere Initialisierungsmöglichkeit und bessere Verwendbarkeit.<br />

(b) C/C++ Makros ohne Parameter (5.72) (sollten in C ++ vermieden werden).<br />

Nachteil: ” dummer“ Textersatz, kein Block- oder Klassen-Gültigkeitsbereich, kaum mit Debugger<br />

zu bearbeiten, da Ersetzung bereits durch Präprozessor.<br />

(c) C++ Aufzählungskonstanten, Näheres s. (12.21)<br />

Auch hier globaler, lokaler oder Klassen-Gültigkeitsbereich; auch diese Konstanten sind für<br />

konstante Ausdrücke zugelassen, z. B. als Arraydimensionierung.<br />

(10.12) Funktionen ohne Funktionsaufruf-Überbau, generische Funktionen<br />

(a) C++ inline-Funktionen<br />

Durch den 15 FunktionsSpezifizierer inline erhält der Compiler einen Hinweis, dass er jeden<br />

Aufruf der Funktion durch Einfügen des entsprechenden Funktionsblock-Codes ersetzen<br />

soll. Der Compiler darf diesen Hinweis ignorieren. Ein inline ist in der Regel nur bei sehr<br />

einfachen Funktionen sinnvoll, da sonst bei häufigem Funktionsaufruf der Codeumfang beträchtlich<br />

erhöht wird. Zum Einsatz in Klassen s. (11.11b).<br />

Im Gegensatz zu Makros (a) gibt es hier keine ” unerwarteten“ Effekte bei Parametern mit<br />

Seiteneffekten.<br />

Der Nachteil gegenüber Makros (a), dass nämlich für jeden Typ eine eigene Funktion geschrieben<br />

werden muss (z. B. MAX für int, für double usw.), wird bei (c) behoben.<br />

(b) ↑↑ C/C++ Makros mit Parameter<br />

#define Name( ParameterListe ) Textersatz mit Parametern


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 110<br />

Bsp #define MAX(a,b) ((a)>(b)?(a):(b))<br />

MAX(a+2,4)*12 wird ” erweitert“ zu: . . . (s. Vorlesung).<br />

Dieses Makro kann typunabhängig zur Maximumbestimmung für sämtliche arithmetischen Typen benutzt<br />

werden. Diese kann bequem sein, ist jedoch auch gefährlich, insbesondere wenn die Parametertypen<br />

verschieden sind.<br />

Anm1 Zwischen Name und der öffnenden Klammer der Parameterliste darf kein Zwischenraumzeichen<br />

stehen, da sonst Interpretation als Makro ohne Parameter.<br />

Anm2 Zeilenfortsetzung möglich, s. (5.71).<br />

Anm3 Die Textersetzung des Präprozessors wird ” Makroerweiterung“ genannt.<br />

Anm4 Wichtig: Klammerung sowohl der formalen Parameter als auch des Gesamtausdrucks, damit<br />

intuitiv angenommene Vorrangregeln beachtet werden:<br />

Bsp #define SUM SCHLECHT(x,y) x+y<br />

#define SUM GUT(x,y) ((x)+(y))<br />

Führen Sie die Makroerweiterung der Ausdrücke SUM(6,x)*3 und SUM((a>=0)?a:-a,z)<br />

für SUM als SUM SCHLECHT und als SUM GUT durch! Unterschied? Achten sie auf die Operator-<br />

Hierarchiestufen!<br />

△! Sehr gefährlich ist eine Makrobenutzung, wenn Parameter Seiteneffekte haben; diese<br />

Seiteneffekte werden dann ggf. mehrfach durchgeführt, da der Präprozessor nur ” dummen“<br />

Textersatz vornimmt ( ” unerwartete“ Effekte).<br />

Ein Makro mit Parameter wird durch den Präprozessor erweitert, der keine Kenntnisse von<br />

der Sprache C ++ hat; daher geschieht eine Typprüfung durch den Compiler erst beim schon<br />

erweiterten Programmtext – vgl. auch Bemerkung in Bsp oben.<br />

In C ++ gibt es bessere Konstrukte, nämlich (b,c), die diese Nachteile vermeiden und trotzdem<br />

typsicher sind.<br />

(c) ↑↑ C++(neu)<br />

” Generische“ Funktionen (für viele Typen eine gemeinsame Definition):<br />

Parametrisierte Funktionen (Template-Funktionen).<br />

10.2 Datentyp Zeiger, Typinterpretation, Array und Zeiger<br />

(10.20) Übb In Fortsetzung zu (Kap. 5.6) wird hier der Datentyp Zeiger genauer erläutert (10.21 bis<br />

10.24). In der Praxis werden Zeiger sehr häufig benutzt. Der sinnvolle Einsatz eines solchen<br />

Typs wird später in diesem Kurs dargestellt.<br />

In diesem Zusammenhang werden in (10.25) sehr allgemeine Regeln erläutert, wie komplizierter<br />

zusammengesetzte Typen zu interpretieren sind. Diese Regeln werden später auf komplexe<br />

Typen angewendet. Die Kenntnis dieser im Grunde sehr einfachen Regeln erleichtert<br />

das Verständnis für zusammengesetzte Datentypen sehr!<br />

(10.26) gibt eine für C ++/C typische Umwandlungsregel von Arraynamen in Zeiger an; diese<br />

Kenntnis ist grundlegend für das nähere Verständnis im Umgang mit Arrays.<br />

Durch die Einführung von Zeigern wird die Benutzung von const etwas komplizierter. Das<br />

Nähere erläutert (10.28).<br />

Da der Zugriff auf Elementfunktionen in der Praxis sehr häufig über Zeiger auf Objekte<br />

geschieht, macht die in (10.29) eingeführte abkürzende Operator-Schreibweise Programmtext<br />

übersichtlicher.<br />

(10.21) Datentyp Zeiger 〈pointer〉, vgl. Kurzerläuterung in (5.65)<br />

(a) Der Datentyp Zeiger ist dadurch charakterisiert, dass sein Wert als Adresse einer anderen<br />

Variablen interpretiert wird, der Zeiger ” zeigt“ auf einen anderen Speicherplatz.<br />

ZeigerVariablenDefinition [NV] Typ *ZeigerName ;<br />

Hierbei ist ZeigerName eine Zeigervariable, sie soll auf eine Variable des Typs Typ zeigen.<br />

(b) Zwei neue Operatoren in Zusammenhang mit Zeigern (unär präfix, Op3gh):<br />

& Adressoperator, berechnet die Speicheradresse des Operanden<br />

* Inhalts- oder Verweisoperator, betrachtet Operanden als Adresse und gibt Wert der<br />

Variablen, die sich an dieser Adresse befindet, zurück.<br />

(c) Möglichkeiten der gültigen Wertbelegung von Zeigern:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 111<br />

• Zuweisen der Adresse einer bereits vorhandenen Variablen mit z. B. &Variable<br />

• Zuweisen des Werts einer bereits gültig belegten Zeigervariable gleichen Typs<br />

• Zuweisen einer typrichtigen Adresse aus dem Freispeicher mit dem Operator new, s. (Kap.<br />

10.3)<br />

• Zeiger 0, s. (10.23)<br />

(10.22) △! bei Umgang mit Zeigern: im allgemeinen müssen zwei Speicherplätze reserviert werden,<br />

da zwei Variable zu unterscheiden sind:<br />

(1) Speicherplatz für die Zeigervariable selbst (Wert: Adresse) – geschieht meist durch Variablendefinition.<br />

(2) Speicherplatz für Variable, auf die der Zeiger zeigt, die sog. ” dereferenzierte Variable“:<br />

dieser Speicherplatz muss i. a. extra bereitgestellt werden, s. (10.21c).<br />

(10.23) Häufig ist eine Wertbelegung eines Zeigers sinnvoll, die abfragbar ist, aber die Bedeutung<br />

hat: ” nicht belegt“ oder ” zeigt auf nichts“. Dieses geschieht mit<br />

C++ Zeiger 0 oder<br />

C (C++) Zeiger NULL (als Makro).<br />

Es ist garantiert, dass diese Adresse nie gültiger Daten- oder Code-Speicherplatz ist.<br />

Da die Adresse 0 nie eine valide Adresse ist, darf der Zeiger 0 (bzw. ein beliebiger Zeigerausdruck<br />

mit Wert Adresse 0) nie dereferenziert werden.<br />

char *zeiger=0; // Zuweisung Adresse 0: OK<br />

// Fehler, NICHT erlaubt:<br />

*zeiger=’j’; // weder ein schreibender Zugriff auf Adresse 0 ...<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 112<br />

(10.25)<br />

(a)<br />

(b)<br />

Allgemeine Regeln für die Interpretation von Typen bei Deklarationen:<br />

VariablenDefinition [NV] Typ Deklarator [- , Deklarator ]- 0..n ;<br />

Typ (hier) 12 DeklSpezifizierer1..n<br />

30 Deklarator [NV] 32 DirekterDeklarator<br />

|<br />

|<br />

31 - * &<br />

32DirekterDeklarator Bezeichner (d. h. Name)<br />

32DirekterDeklarator[KonstAusdropt] <br />

- 30 Deklarator<br />

|<br />

| ( 30Deklarator ) |<br />

|<br />

• (Ohne Referenz-Deklarationsoperator &:)<br />

Tritt der Deklarator nach dem Deklarationspunkt auf, so ist er vom angegebenen<br />

” Typ“.<br />

• (Bei Vorhandensein des Referenz-Deklarationsoperators &:)<br />

Tritt der Deklarator (OHNE den &-Deklarationsoperators) nach dem Deklarationspunkt<br />

auf, so ist er vom Typ ” Referenz auf Typ“.<br />

Regeln zur Bestimmung des Typs eines Namens innerhalb eines Deklarators:<br />

Je Stufe wird genau einer der Deklarationsoperatoren (* bzw. [ ] [NV] ) weggelassen, und<br />

zwar immer der Operator mit der augenblicklich niedrigsten Bindung. Es gelten dabei folgende<br />

Regeln:<br />

• Ist *A vom Typ ” T“, so ist A vom Typ ” Zeiger auf T“.<br />

• Ist A[KonstAusdropt] vom Typ ” T“, so ist A vom Typ ” Array, bestehend aus Komponenten<br />

des Typs T“ (Anzahl der Komponenten entspr. KonstAusdr; wenn fehlend,<br />

dann unbestimmte Anzahl).<br />

Regel zur Unterscheidung zwischen Typ und Deklarator (zweite Zeile in obigem Kasten):<br />

Man beginne hinten beim Semikolon und finde, falls vorhanden, das vorderste Komma (bis<br />

dahin: nur Deklaratoren). Nun gehe man weiter nach links und suche und überspringe, falls<br />

vorhanden:<br />

• einen Namen (ggf. vorhandene eckige Klammernpaare überspringen),<br />

• dann ein oder mehrere Zeichen * oder & (mit ggf. dazwischenliegenden const’s, s. (10.28)),<br />

• dann, falls runde schließende Klammern übersprungen wurden, die zugehörigen öffnenden<br />

Klammern.<br />

Jetzt ist man an dem Trennpunkt zwischen Typ und Deklarator.<br />

Bei einer Definition wird nicht Speicherplatz für den Deklarator, sondern für den<br />

Namen reseviert.<br />

In ähnlicher Weise wird eine ggf. vorhandene Initialisierung auf den Namen (der den<br />

Speicherplatz erhält) angewendet, nicht auf den Deklarator.<br />

Bsp char *zeig=0; // zeig (d. h. Zeiger auf char) wird initialisiert mit 0, nicht *zeig.<br />

(10.26) Wichtige Regel zum Umgang mit Arrays (Zusammenhang zwischen Array und Zeiger) –<br />

bereits in (5.65b, 7.46 ↑↑) erwähnt:<br />

Ein Arrayname bzw. eine Arraybezeichnung ( ” Array, bestehend aus Elementen des<br />

Typs T“) wird durch den Compiler automatisch umgewandelt in ” Zeiger auf T“, und<br />

zwar die Adresse des ersten Elements (Element mit Index 0).<br />

Implizit wird demnach ArrayBezeichnung umgewandelt in &ArrayBezeichnung[0]<br />

Insbesondere findet diese Umwandlung immer bei formalen und bei aktuellen Funktionsparametern<br />

statt.<br />

Die (wesentlichen) Ausnahmen von obiger Regel sind:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 113<br />

&ArrayBezeichnung Adresse des Arrays<br />

sizeof ArrayBezeichnung Speicherbedarf des Arrays<br />

(wichtige Bedeutungsänderung dieses Ausdrucks<br />

bei formalem Funktionsparameter s. (12.44d))<br />

Zusätzliche Einzelheiten s. (12.32). Wichtige Folgerungen für Funktionsparameter s. (12.44). Da<br />

hierbei ” Array aus Elementen T“ und ” Zeiger auf T“ äquivalente Typen sind, wird bei Arrays<br />

als Funktionsparameter meist der Zeigertyp genommen, z. B. statt char arr[anzahl]<br />

oder (äquivalent) char arr[] wird meist – ohne Bedeutungsänderung – char *arr geschrieben.<br />

(10.27) Den Typ einer Variablen erhält man, indem man die Variablendeklaration nimmt und –<br />

unter Beibehaltung ggf. vorhandener Klammern – den Variablennamen weglässt.<br />

Bsp Deklaration Typ: In Worten:<br />

(ohne schließendes Semikolon):<br />

char a[17] char[17] Array mit 17 char-Komponenten<br />

char *b char* Zeiger auf char<br />

Näheres zu den beiden folgenden Typen s. a. (12.71, 12.72):<br />

int *c[3] int*[3] Array mit 3 Zeigern auf int<br />

int (*d)[3] int(*)[3] Zeiger auf Array mit 3 int-Komponenten<br />

Zwischen den einzelnen Token können beliebig Zwischenraumzeichen gesetzt werden; folgende Typen sind<br />

beispielsweise gleich:<br />

int * [3] oder int *[3] oder int* [3] oder int*[3]<br />

(10.28) Zur Interpretation des Schlüsselwortes const:<br />

Steht const vor dem Deklarator (d. h. als DeklSpezifizierer 12,14,25 ), so ist der Deklarator<br />

konstant. Ist const Bestandteil des Deklarators ( 30,31,25 ), so ist nur der hinter diesem const<br />

stehende Deklaratorteil konstant. In beiden Fällen denkt man sich zur Bestimmung des<br />

konstanten Deklator(teil)s alle ggf. vorhandenen weiteren consts als gestrichen.<br />

Bsp const char *zeig Deklarator *zeig ist konstant, d. h. das char, worauf zeig zeigt<br />

char const *zeig dto.<br />

char *const zeig Deklaratorteil zeig ist konstant, d. h. der Zeiger zeig selbst<br />

const char *const zeig sowohl *zeig als auch zeig sind konstant<br />

char const *const zeig dto.<br />

Die zugehörige Typen lauten (in der gleichen Reihenfolge):<br />

const char*<br />

char const*<br />

char *const<br />

const char *const<br />

char const *const<br />

Auch hier können zwischen den Token beliebig Zwischenraumzeichen gesetzt werden; nur zwischen<br />

Schlüsselwörtern/Namen muss mindestens ein Zwischenraumzeichen stehen. Daher jeweils gleichwertig:<br />

const char * const zeig<br />

const char* const zeig<br />

const char *const zeig<br />

const char*const zeig<br />

Nicht erlaubt beispielsweise, da Schlüsselwörter nicht trennbar: constchar . . .<br />

(10.29) Beim Zugriff auf Klassen/Objekte über Zeiger gibt es die abkürzende Schreibweise mit dem<br />

Operator -> (Op2e), vgl. Operator . (Op2d) (9.13):<br />

KlassenZeigerVariable->ElementName<br />

Es gilt: a->b<br />

ist äquivalent zu (*a).b<br />

10.3 Freispeicher<br />

(10.30) Übb Die in (8.11c) kurz erläuterte dritte Speicherklasse dynamisch-kontrolliert wird<br />

hier näher erläutert. Der hierfür vom Laufzeitsystem benutzte sog. Freispeicher (dynamischer<br />

Speicher, Heap) ist ein gesonderter Speicherbereich. In der Praxis geschieht die<br />

Reservierung von Freispeicher und dessen Freigabe, wenn er nicht mehr benötigt wird, sehr<br />

häufig. Hierdurch wird erstmals die dynamische Speicheranforderung ermöglicht, d. h. die


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 114<br />

(10.31) C++<br />

Anforderung von Speicherplatz, dessen genaue Größe erst zur Laufzeit feststeht, z. B. Arrays<br />

mit erst zur Laufzeit feststehender Anzahl Elemente. Dagegen muss bei der statischen<br />

und automatischen Speicherklasse die Größe des zu reservierenden Speicherplatzes schon zur<br />

Kompilationszeit feststehen.<br />

Reservierung und Freigabe erfolgt nur durch ausdrückliche Anweisung durch den Programmierer;<br />

beides ist nicht an Blockgrenzen gebunden. In C ++ wird diese Speicherklasse durch<br />

die Operatoren new und delete (10.31, 10.32, 10.33) direkt unterstützt, in C nur über Bibliotheksfunktionen<br />

(10.34).<br />

(a) FreispeicherReservierungsAusdruck [NV] new Typ<br />

Bedeutung dieses Ausdrucks mit dem Operator new Op3k :<br />

• Seiteneffekt: Laufzeitsystem reserviert Speicherplatz, und zwar (mindestens) so viel, wie<br />

für Typ benötigt wird. Dieser Speicherplatz ist bei einem eingebauten Typ nicht initialisiert;<br />

bei einem KlassenTyp wird der Standardkonstruktor aufgerufen. Eine explizite<br />

Initialisierung ist mit anderer Syntax möglich, s. (b).<br />

• Haupteffekt (Wert): Die (Anfangs-)Adresse des reservierten Speicherplatzes, und zwar<br />

typrichtig als ” Zeiger auf Typ“<br />

• Wenn nicht genügend Speicherplatz vorhanden ist:<br />

◦ C++(alt) Rückgabe der Adresse 0, s. (10.23); es ist garantiert, dass diese Adresse nie<br />

gültiger Daten- oder Code-Speicherplatz ist.<br />

◦ C++(neu) Auswerfen einer Ausnahme des Typs bad alloc (wird hier nicht besprochen).<br />

Das Verhalten von C++(alt) kann jedoch imitiert werden durch Hinzufügen von<br />

(nothrow), d. h. durch Ersetzen von new durch new(nothrow); hierbei muss zusätzlich<br />

der Header eingefügt werden.<br />

• Eine Klammerung innerhalb eines komplizierteren Typs (z. B. in (12.71, 12.72)) wird aus<br />

Gründen von Vorrangregeln falsch interpretiert; in diesem Fall müssen zusätzlich Gesamtklammern<br />

gesetzt werden: new ( Typ ).<br />

(b) Andere Form des FreispeicherReservierungsAusdrucks:<br />

new Typ ( Initialisierer-LISTEopt )<br />

• Ist Typ ein eingebauter Typ, so darf die Liste nur ein Element enthalten oder leer<br />

sein. Der Speicherplatz wird reserviert und, wenn vorhanden, mit dem angegebenen<br />

Wert initialisiert. Ein leeres Klammernpaar wirkt wie fehlende Klammern (s. (a)): keine<br />

Initialisierung.<br />

• Ist Typ eine Klasse, so wird der zugehörige Konstruktor aufgerufen, im Fall des leeren<br />

oder auch fehlenden (s. (a)) Klammernpaares der Standardkonstruktor.<br />

(10.32) C++ FreispeicherFreigabeAusdruck delete Zeiger<br />

Bedeutung dieses Ausdrucks mit dem Operator delete Op3l :<br />

• Seiteneffekt: Speicherplatz, auf den Zeiger zeigt, wird wieder freigegeben. (Das Laufzeitsystem<br />

” weiß“, wieviel Speicherplatz zu dieser Adresse gehört.)<br />

△! Der Speicherplatz MUSS vorher mit new reserviert worden sein!<br />

△! Dieser Speicherplatz darf NICHT mehrmals freigegeben werden!<br />

• Aufruf mit Adresse 0 (10.23) ist unschädlich; daher Empf (dringende Empfehlung): nach<br />

Freigabe Zuweisung Adresse 0 an Zeiger, wenn Zeiger noch länger in Gebrauch ist.<br />

• Haupteffekt: keiner (void)<br />

(10.33) C++ Reservierung und Freigabe von Speicherplatz für eindimensionale Arrays:<br />

new Typ [ GanzzahlAusdruck ]<br />

delete [ ] Zeiger<br />

Bedeutung:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 115<br />

• Reservierung: GanzzahlAusdruck gibt die Anzahl Elemente des Arrays an, für die<br />

Speicherplatz reserviert wird. Der Typ des ErgebnisAusdrucks ist ” Zeiger auf Typ“ –<br />

wie bei Array üblich (10.26), aber auch wie bei Reservierung für Nicht-Array (10.31).<br />

GanzzahlAusdruck braucht erst zur Laufzeit auswertbar zu sein, d. h. darf Variable<br />

enthalten (dynamisches Array) – im Gegensatz zu bisheriger Arraydefinition (Kap.<br />

5.4), dort muss Speicherplatzbedarf bereits zur Kompilationszeit feststehen!<br />

Im Gegensatz zur Form (10.31b) darf und kann hier keine Initialisiererliste angegeben<br />

werden; im Falle eines Klassentyps muss daher ein expliziter oder impliziter (9.21b) Standardkonstruktor<br />

existieren (und zugreifbar sein); dieser Standardkonstruktor wird für<br />

jede Arraykomponente aufgerufen.<br />

• Freigabe: Wie bereits oben erwähnt, kann der Compiler bei der Angabe von Zeiger<br />

nicht entscheiden, ob es sich hierbei um einen Zeiger auf genau ein Element Typ im<br />

Sinne von (10.31) oder um einen Zeiger auf das erste Element eines Arrays, bestehend<br />

aus Typ-Komponenten, im Sinne von (10.33) handelt, da beidesmal Zeiger den Typ Typ*<br />

hat, d. h. Zeiger auf Typ.<br />

Daher ist bei der Freigabe die Angabe wichtig, dass es sich um Array-Speicherplatz<br />

handelt (d. h. [ ] nicht vergessen!); sonst kann das Programm zur Laufzeit ggf. zusammenbrechen.<br />

Der Compilerhersteller darf nämlich die Implementation von ” new Typ“<br />

und von ” new Typ[...]“ so unterschiedlich machen, dass ein Verwechseln von ” delete“<br />

und ” delete[ ]“ in einer Katastrophe endet. Außerdem – im Falle von Objekten eines<br />

Klassentyps – ist es nämlich häufig wichtig, dass für jede Arraykomponente der Destruktor<br />

aufgerufen wird – und das kann nur geschehen, wenn der Compiler weiss, dass<br />

es sich um ein Array handelt.<br />

Der Zugriff auf ein Arrayelement geschieht genauso wie bei anderen Arrays; der Name des<br />

Zeigers, dem der new-Ausdruck zugewiesen wurde, wird als Arrayname betrachtet:<br />

ArrayName[ElementNummer].<br />

(10.34) ↑↑ zu C :<br />

Bsp int anz=25;<br />

int *p= new int[anz]; // Erzeugung eines Arrays aus anz (hier: 25) int’s<br />

p[12]=27; // Zuweisung an Element Nummer 12 (d. h. an das 13. Element)<br />

↑↑ Zur Erzeugung dynamischer mehrdimensionaler Arrays s. (12.74).<br />

↑↑1 Für den Umgang mit dem dynamischen Speicher stehen u. a. die Bibliotheksfunktionen<br />

malloc und free () zur Verfügung. Sie sind zwar auch in C ++ verfügbar, sollten<br />

aber wegen der fehlenden Typsicherheit nicht genommen werden.<br />

↑↑2 △! Auf keinen Fall in C++ die Freispeicherverwaltung mit den genannten Bibliotheksfunktionen<br />

und die mit den Operatoren new/delete vermischen!!


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 116<br />

11 Objektorientierung:<br />

Ergänzungen, Vererbung, Polymorphie, statische Klassenelemente<br />

11.0 Überblick<br />

Dieses Kapitel vertieft die Einführung in die Objektorientierung (Kap. 9). Nach einigen ergänzenden<br />

Bemerkungen in Unterkapitel 1 (Freund-Konzept, this-Zeiger, Kopier-Konstruktor)<br />

wird die sehr hilfreiche Möglichkeit aufgezeigt, wie die formale Benutzung und die Bedeutung<br />

( ” Syntax“ und ” Semantik“) der in C ++ vorhandenen Operatoren auf Klassentypen (oder<br />

andere benutzerdefinierte Typen) ausgedehnt werden können (Unterkapitel 2).<br />

Unterkapitel 3 beschreibt das äußerst mächtige Konzept der Vererbung, der Möglichkeit,<br />

Ähnlichkeiten zwischen Klassen zu beschreiben. Ohne dieses wäre die Objektorientierung<br />

kaum wichtig geworden. Im Folgekapitel wird die Polymorphie eingeführt. Sie erlaubt es,<br />

Funktionen mit derselben Signatur zu redefinieren, so dass der auszuführende Programmcode<br />

trotz gleichen Namens je nach Objekttyp verschieden sein kann. Dieses ist insbesondere<br />

in Zusammenhang mit später Bindung von großer Bedeutung; es erlaubt, die auszuführende<br />

Aktion (den zugehörigen Funktionscode) erst zur Laufzeit zu bestimmen, und zwar in<br />

Abhängigkeit vom dann erst feststehenden Objekttyp.<br />

Unterkapitel 5 beschreibt die Einführung statischer Klassenelemente. Statische Datenelemente<br />

sind pro Klasse genau einmal vorhanden, und zwar unabhängig von der Existenz von<br />

Objekten.<br />

11.1 Klassen und Objekte: Ergänzungen<br />

(11.10) Übb Nach einigen Vorbemerkungen (11.11) wird die Möglichkeit des gezielten Aufbrechens<br />

der Kapselung von Klassen über friend gezeigt (11.12). Dieses ist normalerweise unerwünscht,<br />

in (seltenen!) Spezialfällen aber sinnvoll.<br />

(11.11)<br />

Da der Code von Elementfunktionen zur Laufzeit nur einmal pro Klasse (und nicht in jedem<br />

Objekt) vorhanden ist, muss die Elementfunktion wissen, mit welchem Objekt (d. h. mit<br />

welchem Datensatz) sie aufgerufen wurde. Dieses geschieht durch die implizite Übergabe des<br />

Zeigers this (11.13). Die Kenntnis über diesen Mechanismus ist im folgenden Unterkapitel<br />

unbedingt nötig (Operator-Überladung, und zwar als Elementfunktion).<br />

Nach der kurzen Erläuterung der Initialisiererliste (11.14) wird der Kopier-Konstruktor eingeführt<br />

(11.15). Die explizite Definition eines solchen Konstruktors ist wichtig, wenn man statt<br />

einer ” flachen Kopie“ (Standardverhalten: die einzelnen Elemente eines Objekts werden kopiert)<br />

eine ” tiefe Kopie“ erzeugen muss: hierbei werden, wenn Zeiger (oder auch Referenzen)<br />

vorhanden sind, zusätzlich auch Kopien von den Daten erzeugt, auf die diese Zeiger (oder<br />

Referenzen) zeigen.<br />

(a) Das Erzeugen eines Objekts einer Klasse heißt manchmal auch Instantiierung, ein Objekt<br />

wird auch Instanz oder Exemplar 〈instance〉 einer Klasse genannt.<br />

(b) Wenn eine Elementfunktion schon innerhalb der Klassendefinition definiert (nicht nur deklariert)<br />

wird, so wird diese Definition implizit als inline (10.12a) aufgefasst, vgl. Hinweis in<br />

(9.11b Anm).<br />

(c) Wird ein Klassentyp statt mit class mit dem Schlüsselwort struct eingeleitet, so ist die<br />

Voreinstellung für die Zugriffsspezifizierung public. Wegen (6.13c) sollte daher struct i. a.<br />

nicht genommen werden, damit private als das ” Normale“ nicht vergessen wird.<br />

↑↑ In C gibt es nur das struct, hier sind als Elemente nur Daten möglich, keine Funktionen<br />

und keine Zugriffsspezifizierer. Manchmal benutzt man auch in C ++ solche structs, z. B.<br />

wegen der einfacheren konstanten Initialisierung. Weitere Gründe s. Vorlesung InfHaupt.<br />

Außerdem ist in C der Name hinter struct kein Typname, sondern ein sog. Etikett 〈tag〉.<br />

Zur Typnennung ist daher die Kombination struct mit Etikett nötig, oder man definiert<br />

mit typedef einen Typnamen (5.62). Dieses ist in C ++ jedoch nicht nötig.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 117<br />

(11.12) Freunde<br />

Globale Funktionen (11.13), Elementfunktionen oder auch alle Elemente einer Klasse können<br />

Freund einer Klasse sein, d. h. den Zugriff auf alle Bestandteile erhalten.<br />

Der Ort der 12 friend-Spezifizierung ist beliebig innerhalb der Klassendefinition wählbar, er<br />

ist unabhängig von Zugriffsspezifizierern:<br />

class TeilOffen {<br />

// Deklaration von Elementen<br />

// ...<br />

friend void ansicht();<br />

friend double AndereKlasse::zugriff(int a);<br />

friend class Neugierig;<br />

// ggf. Fortsetzung der Elementdeklarationen<br />

// ...<br />

};<br />

Semantik:<br />

Auf alle Elemente dieser Klasse TeilOffen – unabhängig von Zugriffsbeschränkungen –<br />

dürfen zugreifen:<br />

die (globale, d. h. nicht einer Klasse angehörige) Funktion ansicht,<br />

die Elementfunktion zugriff der Klasse AndereKlasse,<br />

alle Elemente der Klasse Neugierig.<br />

Anm1 Das Auftreten von friend deutet wegen des Aufbrechens des Geheimnisprinzips meist einen<br />

Entwurfsfehler an. Es gibt nur wenige Fälle, in denen ein friend sinnvoll und gerechtfertigt<br />

ist, s. z. B. (Kap. 11.2). Es ist wichtig, dass man sehr sparsam mit dieser Möglichkeit umgeht<br />

und zunächst immer überlegt, ob man nicht auch ohne friend auskommen kann.<br />

Anm2 Die Spezifizierung friend ist nicht symmetrisch. Zum obigen Beispiel:<br />

Damit die Klasse TeilOffen auch auf verborgene Elemente der Klasse Neugierig zugreifen<br />

kann, muss sie in Neugierig als Freund eingetragen sein.<br />

Anm3 Die Spezifizierung friend ist auch nicht transitiv. Zum obigen Beispiel:<br />

Ist die Klasse Weiter ein Freund von Neugierig (d. h. in Neugierig als Freund eingetragen),<br />

so ist nicht automatisch Weiter ein Freund von TeilOffen.<br />

Anm4 Die Spezifizierung friend wird auch nicht vererbt.<br />

Anm5 Zum obigen Beispiel: Die Klasse AndereKlasse mit der Elementfunktion zugriff muss dem<br />

Compiler an der Stelle der friend-Deklaration bekannt sein.<br />

Anm6 Zum obigen Beispiel: Falls Neugierig als Klassenname bekannt ist, kann man das Schlüsselwort<br />

class weglassen, es genügt folgende Angabe:<br />

friend Neugierig;<br />

Anm7 Zum Umgang des Compilers MS Visual C ++ 6.0 mit friend-Operatorfunktionen s. (11.23d).<br />

(11.13) Zeiger this<br />

(a) Eine Elementfunktion erhält bei jedem Aufruf als impliziten zusätzlichen ersten Parameter<br />

einen Zeiger auf das Objekt, zu dem sie aufgerufen wird. Dieser Zeiger hat den Namen this.<br />

Ist das Objekt, deren Elementfunktion aufgerufen wird, vom Typ T, so ist this vom Typ<br />

T *const,<br />

d. h. der Zeiger this zeigt auf T und ist konstant, vgl. (10.28, 10.25). Eine Änderungserlaubnis<br />

von this ergäbe auch keinen Sinn.<br />

(b) Wegen (a) muss genau zwischen Elementfunktionen und globalen Funktionen unterschieden<br />

werden: erstere können nur in Zusammenhang mit einem Objekt aufgerufen werden<br />

(auf das der implizite Zeiger this zeigt), die zweiten ohne Objekt.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 118<br />

Deklaration<br />

(ggf. auch Def.)<br />

Impliziter<br />

zusätzlicher<br />

Parameter<br />

Elementfunktion<br />

– nichtstatisch, s. (e) –<br />

Globale Funktion<br />

– auch Freundfunktion, s. (Anm) –<br />

innerhalb der Klassendefinition außerhalb einer Klasse<br />

– als Freund auch innerhalb –<br />

this (an erster Stelle) kein impliziter Parameter<br />

Bsp – T sei zugehörige Klasse –<br />

Funktionskopf Programmtext (in Klassendef.):<br />

Typ fkt(TypPar namePar)<br />

Abänderung durch Compiler:<br />

Typ fkt(T *const this,<br />

TypPar namePar)<br />

Aufruf Programmtext:<br />

objektName.fkt(par)<br />

Abänderung durch Compiler:<br />

T::fkt(&objektName, par)<br />

Typ fkt(TypPar namePar)<br />

fkt(par)<br />

Anm friend-Funktionen erscheinen zwar in der Klassendefinition, sind aber ” normale“ globale<br />

Funktionen (ohne this-Zeiger), die allerdings unbeschränkten Zugriff auf die Klassenelemente<br />

haben (11.12). Elementfunktionen anderer Klassen, die friend-Funktionen sind, haben<br />

ebenfalls keinen this-Zeiger auf ein Objekt dieser Klasse, statt dessen (natürlich) einen Zeiger<br />

zu einem Objekt der anderen Klasse, s. Beispielcode (11.12): die Elementfunktion zugriff<br />

hat keinen this-Zeiger auf ein Objekt der Klasse TeilOffen, wohl aber auf AndereKlasse.<br />

(c) Den Zeiger this darf der Programmierer in der Definition der Elementfunktion benutzen:<br />

this->element ist beispielsweise äquivalent zu element<br />

falls der Name element nicht verdeckt ist (Bedeutung des Operators -> s. (10.29)). Im<br />

Falle der Verdeckung kann über den this-Zeiger trotzdem darauf zugegriffen werden. Eine<br />

andere Möglichkeit (Bereichsauflösungsoperator ::) war in (9.12a) vorgestellt worden.<br />

(d) Soll das Objekt, zu dem eine Elementfunktion aufgerufen wird, innerhalb dieser Funktion<br />

als konstant behandelt werden, d. h. soll *this konstant sein, so muss this vom Typ<br />

const T *const<br />

sein (10.28). Dieses zusätzliche const kann der Benutzer jedoch nicht direkt benennen, da<br />

er this nicht selbst einträgt. Statt dessen muss er dieses const hinter die Parameterliste<br />

stellen, vgl. (Cpp/Kap. 1, 32 DirektorDeklarator, 3. Zeile).<br />

Bsp (Nichtkonstantes Objekt) Benutzer: Typ fkt(TypPar namePar)<br />

– vgl. Tabelle oben – Compiler: Typ fkt(T *const this, TypPar namePar)<br />

(Konstantes Objekt) Benutzer: Typ fkt(TypPar namePar) const<br />

Compiler: Typ fkt(const T *const this, TypPar namePar)<br />

Beispiel Programmtext: Elementfunktion text in (11.15a)<br />

(e) Werden hier Elementfunktionen erwähnt, sind immer nichtstatische Elementfunktionen<br />

gemeint. Statische Elementfunktionen haben keinen this-Zeiger; Näheres s. (Kap. 11.5).<br />

(11.14) Initialisiererliste bei der Definition eines Konstruktors<br />

(a) Einfache Datenelemente eines Objekts können beim Konstruktoraufruf direkt initialisiert<br />

werden, anstatt im Konstruktorrumpf (ZusammengesetzteAnweisung) entsprechende Zuweisungen<br />

auszuführen. Diese Initialisierungen werden bei der Konstruktordefinition gesetzt.<br />

Syntax (Cpp/Kap. 1, 33 FunktionsDefinition, 2. Zeile, vereinfacht/angepasst):<br />

KonstruktorName ( ParameterDeklarations-LISTEopt )<br />

: ElementName(Initialisierer) [- , ElementName(Initialisierer) ]- 0..n<br />

54 ZusammengesetzteAnweisung<br />

Auf die gleiche Weise ist es in der Initialisiererliste möglich, Elemente, die selbst Objekte<br />

sind, durch einen Konstruktoraufruf zu initialisieren; ohne eine solche (explizite) Initialisierung<br />

wird vor Eintritt in den Konstruktorblock jeweils der zugehörige Standardkonstruktor


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 119<br />

aufgerufen. Näheres s. (11.34).<br />

Bsp1<br />

class BspElem {<br />

float elem;<br />

public:<br />

BspElem(); // Standardkonstruktor<br />

BspElem(float el); // Konstruktor mit einem Parameter<br />

};<br />

BspElem::BspElem() : elem(0) { }<br />

// statt: BspElem::BspElem() { elem=0; }<br />

BspElem::BspElem(float el) : elem(el) { }<br />

// statt: BspElem::BspElem(float el) { elem=el; }<br />

Bsp2 – hierbei Benutzung der Klasse BspElem des obigen Beispiels –<br />

class Beispiel {<br />

int x,y;<br />

double d;<br />

BspElem bspElem;<br />

public:<br />

Beispiel(int parY, float bsp); // Konstruktor mit zwei Parameter<br />

// ...<br />

};<br />

Beispiel::Beispiel(int parY, float bsp)<br />

: x(30), y(parY), d(3.14159), // Initialis.-Liste f. eingeb. Typen<br />

bspElem(bsp) // .. und f. Objekt (Konstr. 1 Par.)<br />

{ } // im Konstruktorrumpf bleibt hier nichts mehr zu tun, daher leer<br />

Bsp3 s. (11.15), Definition des Standardkonstruktors; dort auch Gegenüberstellung zu Zuweisungen.<br />

(b) Hat man in einer Klasse konstante Elemente (const . . . ), so lassen sich diese nicht mehr<br />

durch Zuweisung im Konstuktorrumpf mit einem definierten Wert belegen (dann sind die<br />

Speicherplätze nämlich schon erzeugt), sie müssen in der Initialisiererliste belegt werden<br />

(sonst Compilerfehler!).<br />

(11.15) Kopier-Konstruktor<br />

(a) Wenn ein Objekt mit einem schon bestehenden Objekt desselben Typs initialisiert wird, so<br />

geschieht dieses normalerweise automatisch durch elementweises Kopieren. Dies kann große<br />

Probleme verursachen:<br />

// Datei D11-151.CPP<br />

#include // wegen strlen, strcpy<br />

#include <br />

using namespace std;<br />

class Zeile {<br />

int deflen;<br />

char *txt;<br />

public:<br />

Zeile();<br />

Zeile(const char *str);<br />

~Zeile();<br />

const char *text() const;<br />

// ...<br />

};<br />

Zeile::Zeile() : deflen(0), txt(0) {}<br />

// Ähnlich, nur ohne Initialisiererliste, sondern mit Zuweisungen:<br />

// Zeile::Zeile() { deflen=0; txt=0; }


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 120<br />

Zeile::Zeile(const char *str)<br />

{<br />

deflen=strlen(str)+1;<br />

txt=new char[deflen]; // zu new bei Arrays s. (10.33)<br />

// beides zusammen in einer Zeile:<br />

// txt=new char[deflen=strlen(str)+1];<br />

}<br />

strcpy(txt,str);<br />

Zeile::~Zeile() { delete [] txt; } // ein "delete 0;" wäre unschädlich,<br />

// zu delete [] s. (10.33)<br />

const char *Zeile::text() const<br />

{<br />

const static char null[]="[leer]";<br />

return (txt)?txt:null;<br />

}<br />

int main()<br />

{<br />

Zeile leer, // Std-Konstruktor (oh. Parameter)<br />

gefuellt("Teststring"); // Konstruktor mit Parameter char*<br />

}<br />

{<br />

Zeile problem1(gefuellt), // Problem!<br />

// Initialisierungszeile ggf. auskommentieren (zum Testen!)<br />

problem2<br />

=(gefuellt) // Und noch ein Problem!<br />

; // Anm.: beide Initialisierungsarten sind gleichwertig<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 121<br />

Anm Ein ähnliches Problem ergibt sich bei einer Zuweisung eines Objekts des Typs Zeile an ein<br />

Objekt desselben Typs; Lösung dafür: Überladen des Zuweisungsoperators, s. (11.24b).<br />

(b) Ein Kopier-Konstruktor steuert die Initialisierung eines Objektes mit einem bereits bestehenden<br />

Objekt derselben Klasse; ist ein solcher Konstruktor nicht definiert, werden die<br />

Daten elementweise kopiert ( ” flache Kopie“). In vielen Fällen reicht dieses aus. In einigen<br />

Fällen kann dieses Verhalten jedoch unerwünscht sein, vgl. Beispiel in (a); dort benötigt man<br />

eine ” tiefe Kopie“. Reicht die Erzeugung einer flachen Kopie nicht aus, muss ein Kopier-<br />

Konstruktor definiert werden, in dem das gewünschte Verhalten festgelegt wird, z. B. eine<br />

tiefe Kopie.<br />

(11.16)<br />

FunktionskopfKopierKonstruktorKlasseT T(const T&)<br />

d. h. Initialisierung eines Objekts der Klasse T mit der konstanten Referenz auf ein bereits<br />

bestehendes Objekt dieser Klasse.<br />

Ein Kopier-Konstruktor für das Beispiel in (a):<br />

// Teil aus Datei D11-152.CPP<br />

// ...<br />

class Zeile {<br />

int deflen;<br />

char *txt;<br />

public:<br />

// ...<br />

Zeile(const Zeile &z); // Kopier-Konstruktor<br />

// ...<br />

};<br />

Zeile::Zeile(const Zeile &z)<br />

{<br />

txt=new char[deflen=z.deflen]; // eigener Speicherplatz!<br />

strcpy(txt,z.txt);<br />

}<br />

// ...<br />

(a) Ein Konstruktoraufruf kann dazu benutzt werden, eine temporäre Variable ohne eigenen<br />

Namen zu erzeugen: Nutzung beispielsweise bei Parametern einer Funktion bzw. bei<br />

Operanden eines Operators.<br />

Bsp Zu Beispiel (11.15a); folgende Ausgabezeile ist möglich:<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 122<br />

↑↑ △! Manchmal kann eine solche implizite Typkonversion unerwünscht sein. Sie kann unterbunden<br />

werden durch den 15 FunktionsSpezifizierer explicit C++(neu) (nur in der Funktionsdeklaration<br />

innerhalb der Klassendefinition, nicht noch einmal bei einer Funktionsdefinition<br />

außerhalb zu wiederholen). Explizite Typkonversion ist dann weiterhin noch möglich.<br />

(c) ↑↑ Auch die umgekehrte Typkonversion – von dem vorgegebenen Klassentyp in einem<br />

beliebigen Typ – kann definiert werden durch eine sog. Typkonversionsfunktion; dieses ist<br />

in (Cpp/Kap. 1, 32 DirekterDeklarator, 5. Zeile) unter KonversionsFunktionsName angedeutet.<br />

11.2 Überladen von Operatoren<br />

(11.20) Übb Um benutzerdefinierte Typen den eingebauten Typen gleichzustellen, gibt es in C ++<br />

das äußerst mächtige Mittel, Operatoren zu überladen, d. h. ihnen in Zusammenhang mit<br />

einem (oder zwei) benutzerdefinierten Typen eine sinnvolle Bedeutung zu geben. Dadurch<br />

ist es beispielsweise möglich, sich einen Typ Komplex (vgl. (9.11Bsp)) zu definieren und dann<br />

wie in der Mathematik üblich die Gültigkeit der binären Operatoren +, -, *, / auf die<br />

Addition, Subtraktion, Multiplikation und Division solcher komplexer Zahlen auszudehnen.<br />

Diese Überladung kann die übliche Bedeutung der Operatoren in Zusammenhang mit eingebauten<br />

Typen nicht verändern, sondern nur für neue benutzerdefinierte Typen (d. h. in<br />

der Praxis für Klassen und/oder für Aufzählungstypen (12.21)).<br />

(11.21)<br />

Um die Überladung zu definieren, wird für Operatoren eine Funktionsschreibweise eingeführt<br />

(11.22), d. h. der Funktions- ” Name“ mit Parametern und Rückgabetyp (Ergebnistyp der Operation).<br />

Diese Funktion kann als globale Funktion eingeführt werden (11.23). Dieses wird beispielsweise<br />

häufig für den Eingabeoperator >> und den Ausgabeoperator


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 123<br />

Anm Die Überladung bei Operator-Elementfunktionen s. (11.24a).<br />

(a) Für einen Operatorausdruck (Operatoraufruf) wird in den Fällen, in denen eine solche Überladung<br />

definiert ist (11.23), optional eine Funktionsschreibweise eingeführt.<br />

Bsp a+34 wird zu operator+(a,34)<br />

!tt wird zu operator!(tt)<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 124<br />

int main()<br />

{<br />

Zeile zeil1("Dieses "),<br />

zeil2("ist ein "),<br />

zeil3("Teststring.");<br />

}<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 125<br />

Zeile &operator=(const Zeile &z); // Zuweisungsoperator<br />

// ...<br />

};<br />

Zeile &Zeile::operator=(const Zeile &z)<br />

{<br />

if(this!=&z) { // Verriegelung gegen Selbstzuweisung: wichtig!<br />

delete [] txt;<br />

txt=new char[deflen=z.deflen];<br />

strcpy(txt,z.txt);<br />

}<br />

return *this;<br />

}<br />

// ...<br />

int main()<br />

{<br />

Zeile zeil1("Dieses "),<br />

zeil2("ist ein "),<br />

zeil3("Teststring.");<br />

}<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 126<br />

Darstellung der Vererbung in UML, vgl. (1.34a):<br />

Oberklasse<br />

❅<br />

Unterklasse<br />

Die Vererbung ist ein sehr mächtiges Instrument der Objekttechnologie. Die logische Beziehung<br />

von der Ober- zur Unterklasse ist eine Spezialisierung 〈specialization〉, die von<br />

der Unter- zur Oberklasse eine Generalisierung 〈generalization〉. Erst durch Einführung<br />

einer solchen Spezialisierungs- bzw. Generalisierungshierarchie wird es möglich, die reale<br />

Welt einfach zu modellieren. Es kommt der Eigenart des menschlichen Denkens sehr entgegen<br />

(Denken in Hierarchien), dass Beziehungen in Form solcher Abhängigkeiten formuliert<br />

werden können.<br />

(b) Eine Unterklasse kann wiederum Oberklasse für andere Klassen sein. Auf diese Weise wird<br />

eine Klassen-Vererbungshierarchie aufgebaut.<br />

(c) Die Modellierung kann sogar so weit gehen, dass Ähnlichkeiten zwischen Klassen (reale Welt:<br />

Mengen von gleichartigen Objekten) ausgelagert und für alle ähnliche Klassen gemeinsam<br />

formuliert werden, und zwar in der Art, dass es zu der gemeinsamen Klasse in der realen<br />

Welt keine Objekte gibt bzw. geben kann. Man spricht hierbei von einer sog. abstrakten<br />

Klasse 〈abstract class〉. Zur Unterscheidung davon nennt man eine Klasse, zu der Objekte<br />

erzeugt werden können, konkrete Klasse 〈concrete class〉. Zur Definition einer abstrakten<br />

Klasse in C ++ s. (11.43).<br />

Bsp Bei der Modellierung von Mann und Frau gibt es viele Gemeinsamkeiten (weite Teile der Anatomie, des<br />

Stoffwechsels usf.). Diese können ausgelagert werden in eine Klasse Mensch. Hiervon erben die Klassen<br />

Mann und Frau und fügen ihre geschlechtspezifischen Merkmale hinzu. In der realen Welt gibt es jedoch<br />

kein Objekt der Klasse Mensch, sondern immer nur Objekte der Klasse Mann oder Frau.<br />

(d) Man kann in der Modellierung auch eine sog. Mehrfachvererbung 〈multiple inheritance〉<br />

zulassen: hierbei erbt eine Unterklasse gleichzeitig die Eigenschaften von mehreren Oberklassen.<br />

Diese Art der Beziehung kann, sparsam eingesetzt, ein gutes Hilfsmittel zur Beschreibung<br />

der realen Welt sein.<br />

Bsp Ein Amphibienfahrzeug erbt die Bewegungsmöglichkeit auf dem Land vom Landfahrzeug, die auf dem<br />

Wasser vom Wasserfahrzeug.<br />

Nicht alle objektorientierten Sprachen unterstützen die Mehrfachvererbung. Die Sprache C ++<br />

unterstützt sie zwar; dennoch soll sie im Rahmen dieser Vorlesung nicht bespochen werden.<br />

Zur Unterschiedung von der Mehrfachvererbung nennt man die Vererbung von nur einer<br />

Oberklasse einfache Vererbung oder Einfachvererbung 〈single inheritance〉.<br />

(e) Ein weiteres mächtiges Werkzeug, bei den meisten Sprachen in Verbindung mit der Vererbung<br />

eingesetzt, ist die Polymorphie oder der Polymorphismus 〈polymorphism〉. Hierfür<br />

ist ein eigenes Unterkapitel eingerichtet (Kap. 11.4).<br />

(f) In der Sprache C ++ wird die Vererbung meist Ableitung 〈derivation〉 genannt. Man nennt<br />

hier die Oberklasse – ohne Bedeutungsveränderung – Basisklasse 〈base class〉, die Unterklasse<br />

abgeleitete Klasse 〈derived class〉.<br />

(11.32) Im Rahmen der Vererbung wird – zusätzlich zu private (verborgen) und public (öffentlich)<br />

(9.11a) und (Kap. 6.4) – ein dritter Zugriffsspezifizierer eingeführt: protected (geschützt). Er<br />

nimmt eine gewisse Mittelstellung zwischen verborgen und öffentlich ein:<br />

• Auf ein private-Element dürfen nur die Elementfunktionen der eigenen Klassen und<br />

Freunde (11.12) direkt zugreifen.<br />

• Auf ein protected-Element dürfen zusätzlich die Elementfunktionen von abgeleiteten<br />

Klassen und deren Freunde zugreifen.<br />

• Auf ein public-Element kann von überall zugegriffen werden.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 127<br />

Anm Hiermit wird nur die Zugriffs-Erlaubnis beschrieben. Zusätzlich muss natürlich der Name<br />

auch im Gültigkeitsbereich liegen (ggf. Klassenspezifizierer angeben).<br />

(11.33) Syntax zur Beschreibung der Ableitung:<br />

DefinitionAbgeleiteteKlasse [NV] <br />

class KlassenName : 22 ZugriffsSpezifiziereropt BasisKlasse<br />

{ BeschreibungZusätzeUndÄnderungenGegenüberBasisklasse } ;<br />

Normalerweise sollte der ZugriffsSpezifizierer für die Basisklasse public sein. Allgemein kann<br />

er – wie ein Zugriffsspezifizierer innerhalb der Klassendefinition, d. h. für Klassenelemente<br />

– einer der drei Spezifizierer private, protected oder public sein. Wenn er fehlt, wird<br />

private angenommen. Dieser Zugriffsspezifizierer gibt an, wie innerhalb der abgeleiteten<br />

Klasse auf Elemente der Basisklasse zugegriffen werden darf:<br />

Element- Bei dem Basisklassen-Zugriffsspezifizierer<br />

Zugriffsspezifizierer public protected private<br />

in der erhalten die Elemente der Basisklasse<br />

Basisklasse bei Zugriff aus der abgeleiteten Klasse heraus<br />

folgende Element-Zugriffsspezifikation:<br />

public public protected private<br />

protected protected protected private<br />

private – kein Zugriff – – kein Zugriff – – kein Zugriff –<br />

Im Rahmen dieser Vorlesung wird als Basisklassenspezifizierer immer public genommen,<br />

wie bereits oben erwähnt.<br />

↑↑ Bei dem Schlüsselwort struct (11.11c) wird standardmäßig die public-Vererbung genommen.<br />

(11.34) Definition von Konstruktoren abgeleiteter Klassen<br />

Die Initialisiererliste (11.14) kann bei einer abgeleiteten Klasse zusätzlich einen Konstruktoraufruf<br />

für die Basisklasse aufnehmen. Dieses ist häufig sogar sinnvoll.<br />

Die Reihenfolge der Durchführung der Initialisierungen hängt nicht von der Reihenfolge in<br />

der Initialisiererliste ab, sondern obliegt folgenden Regeln:<br />

• Zuerst wird die Basisklasse initialisiert – falls kein Konstruktoraufruf in der Initialisiererliste<br />

vorhanden, mit dem Standardkonstruktor.<br />

↑↑ Bei Mehrfachvererbung: Initialisierung aller direkten Basisklassen, und zwar in der Reihenfolge<br />

der Angabe in der Basisklassenliste. Eine Abweichung von dieser Reihenfolgeregel gibt<br />

es bei sog. virtueller Vererbung; hier wird zunächst die virtuelle Basisklasse initialisiert.<br />

• Nun geschieht die Initialisierung der Elemente der abgeleiteten Klasse, und zwar der Elemente,<br />

die in der Initialisiererliste vorhanden sind, dazu, falls vorhanden, der Elemente,<br />

die selbst wiederum Objekte sind (Initialisierung mit jeweiligem Standardkonstruktor,<br />

falls für sie in der Initialisiererliste kein expliziter Konstruktoraufruf angegeben ist). Die<br />

Reihenfolge der Initialisierung richtet sich nur nach der Reihenfolge des Auftreten der<br />

Elemente in der Klassendefinition.<br />

• Danach wird der Funktionsrumpf des aktuellen Konstruktors ausgeführt.<br />

Bei Vererbung über mehrere Stufen werden diese Regeln rekursiv angewendet, so dass immer<br />

zuerst die Elemente der ersten (obersten) Klasse in einer Vererbungshierarchie initialisiert<br />

werden.<br />

Beim Zerstören eines Objekts wird in genau umgekehrter Reihenfolge vorgegangen: zuerst<br />

wird der Destruktor der eigenen Klasse aufgerufen, danach der Destruktor der Basisklasse<br />

usf.<br />

↑↑ Dass die Initialisierungsreihenfolge nur von der Reihenfolge der Elemente in der Klassendefinition<br />

abhängt, hängt mit der Regel zusammen, dass bei Zerstörung eines Objekts die zugehörigen<br />

Destruktoren immer in umgekehrter Reihenfolge wie die Konstruktoren aufgerufen<br />

werden. Eine Erlaubnis zur Reihenfolgeänderung hätte zur Folge, dass für jedes Objekt diese<br />

Reihenfolge zur Laufzeit dokumentiert werden müsste, damit die Destruktor-Reihenfolge<br />

konsistent dazu sein kann.<br />

(11.35) Die abgeleitete Klasse darf Elemente (Daten, Funktionen) durch Elemente gleichen Namens<br />

redefinieren. Das jeweils zugehörige Element der Basisklasse wird dadurch verdeckt. Sind in


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 128<br />

der Basisklasse überladene Funktionen gleichen Namens vorhanden, so werden sie alle verdeckt<br />

△! . Diese verdeckten Elemente sind noch vorhanden; auf sie kann – Zugriffserlaubnis<br />

vorausgesetzt – mit der Spezifizierung durch den Basisklassen-Namen zugegriffen werden<br />

(BasisKlassenName::ElementName).<br />

class Basis {<br />

public: // public hier nur zur Verdeutlichung (vereinfachter Zugriff)<br />

int a;<br />

void fkt(int i);<br />

void fkt(double d);<br />

void aktion();<br />

};<br />

class Ableitung : public Basis {<br />

float a; // überdeckt Basis::a<br />

int fkt(char *str); // überdeckt Basis::fkt(int) und Basis::fkt(double)<br />

void aktion(); // überdeckt Basis::aktion()<br />

};<br />

Innerhalb der Klasse Ableitung kann auf das a der Klasse Basis durch volle Spezifizierung<br />

Basis::a zugegriffen werden, auf die überdeckten Funktionen z. B. durch Basis::fkt(16),<br />

Basis::fkt(1.0) oder Basis::aktion().<br />

Anm1 Die Verdeckung und der Zugriff auf die Basisklasse durch Spezifizierung liefe genauso ab,<br />

wenn das Datenelement der abgeleiteten Klasse den gleichen Typ hätte: int a;<br />

Anm2 Die Verdeckung geschieht auch dann, wenn kein Zugriff möglich wäre, beispielsweise bei<br />

private-Zugriffsspezifizierer in der Basisklasse (Bsp.: private: int a; in Klasse Basis.)<br />

Diese Regel soll sicherstellen, daß die Bedeutung von Namen in abgeleiteten Klassen unabhängig<br />

von der Zugriffsspezifikation in einer Basisklasse ist.<br />

Anm3 Die Verdeckung geschieht nur mit dem Namen. In einer abgeleiteten Klasse kann man daher<br />

keine Elementfunktion der Basisklasse überladen △! . Wie schon oben erwähnt, werden<br />

alle Elementfunktionen gleichen Namens verdeckt, vgl. auch (EffCpp/Kap. 50). Dieses ist auch<br />

sinnvoll, da sich sonst bei einer großen Klassenhierarchie leicht Fehler einschleichen könnten.<br />

Anm4 Diese Verdeckung geschieht sinngemäß auch bei Operatoren. Beispiele: Ein Präfix-Inkrementoperator<br />

aus einer Basisklasse wird durch die Definition des Postfix-Inkrementoperators in<br />

einer abgeleiteten Klasse verdeckt (oder umgekehrt); ein unärer Operator * aus einer Basisklasse<br />

wird durch Definition des binären Operators * in einer abgeleiteten Klasse verdeckt<br />

(oder umgekehrt).<br />

(11.36) Operator-Elementfunktionen (11.24) werden normal vererbt mit Ausnahme des Zuweisungsoperators.<br />

Wird das Standardverhalten dieses Operators (flache Kopie, s. (11.24b)) nicht<br />

gewünscht, muss er für jede Klasse überladen werden.<br />

11.4 Polymorphie<br />

(11.40) Übb Die Polymorphie vervollständigt das Konzept der Vererbung. Die Möglichkeit, Methoden<br />

in untergeordneten Klassen zu redefinieren, kommt dem menschlichen Denken sehr<br />

entgegen. Wir sind es gewohnt, Aktionen, die im Detail sehr unterschiedlich sein können,<br />

den gleichen Namen zu geben, wenn das Ergebnis ähnlich oder vergleichbar ist, wie z. B. die<br />

Aktion Schreiben oder Malen trotz unterschiedlicher Schreib- oder Malwerkzeuge mit sehr<br />

unterschiedlichen physikalisch-chemischen Einzelheiten. Die korrekte Aktion wird anhand<br />

des Objekttyps bestimmt (im Beispiel: des Werkzeugtyps).<br />

Dieses Konzept wird insbesondere im Zusammenhang mit der späten Bindung sehr mächtig<br />

und für die Praxis außerst wichtig. Es wird möglich, ” generischen“ Programmcode zu schreiben,<br />

dessen eigentliche Ausführung zur Kompilationszeit noch gar nicht feststeht. Hierdurch<br />

kann zur Laufzeit in Abhängigkeit vom (manchmal erst dann feststehenden) tatsächlichen<br />

Objekttyp die zugehörige Aktion ausgewählt werden.<br />

Punkt (11.41) beschreibt die Polymorphie näher, auch in Zusammenhang mit später Bindung.<br />

In (11.42) wird gezeigt, wie die späte Bindung in C ++ eingeführt wird. Abschließend beschreibt


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 129<br />

Punkt (11.43), wie in C ++ abstrakte Klassen erzeugt werden (d. h. Klassen, die nur für die<br />

Klassenhierarchie von Bedeutung sind, da sie selbst keine Objekte haben können).<br />

(11.41) Die Polymorphie (dt. ” Vielgestaltigkeit“) oder der Polymorphismus ist in der Informatik<br />

ein sehr schillernder Begriff. Es wird je nach Autor Unterschiedliches darunter verstanden.<br />

Allgemein bedeutet die Polymorphie, dass verschiedenen Aktionen die gleichen Namen gegeben<br />

werden können; die Bindung des Namens an die tatsächlich auszuführende Aktion<br />

hängt vom Zusammenhang (Kontext) ab.<br />

(11.42)<br />

Ein sehr einfacher Fall der Polymorphie in C ++ wäre schon die Überladungsmöglichkeit<br />

von Funktionsnamen: je nach Kontext (Signatur des Aufrufs) wird die tatsächliche Aktion<br />

ausgewählt.<br />

Häufiger wird unter Polymorphie die Möglichkeit verstanden, dass Methoden verschiedener<br />

Klassen denselben Namen für ihre – unterschiedlichen – Aktionen vergeben dürfen (diese<br />

Aktionen sollten aber aus Übersichtlichkeitsgründen ähnliche Bedeutung haben). Innerhalb<br />

einer Ableitungshierarchie besteht die Möglichkeit, Funktionen der Basisklasse zu redefinieren,<br />

beispielsweise wenn eine bestimmte Aktion für Objekte der abgeleiteten Klassen<br />

(zumindest teilweise) anders durchzuführen ist.<br />

Insbesondere ist dieses Mittel in Zusammenhang mit der späten Bindung (oder dynamischen<br />

Bindung) 〈late binding, dynamic binding〉 sehr ausdrucksstark: hierbei findet die<br />

Bindung des Namens an die tatsächlich durchzuführende Aktion erst zur Laufzeit statt. Bei<br />

einer frühen Bindung (oder statischen Bindung) 〈early binding, static binding〉 wird<br />

die Bindung bereits durch den Compiler oder Linker festgelegt.<br />

Bei getypten Sprachen (z. B. C ++) ist aus Gründen der Fehlerminimierung Polymorphie mit<br />

später Bindung nur innerhalb einer Klassen-Ableitungshierarchie erlaubt; andere Sprachen<br />

erlauben sie beliebig (z. B. Smalltalk).<br />

Bsp Die Aktion schreibe(Buchstabe) ist je nach Schreibmittel sehr verschieden: beim Bleistift Abrieb von<br />

Graphit, beim Kugelschreiber Abrollen einer Kugel mit Farbe, beim Filzstift Wirkung von Kapillarkräften<br />

auf farbige Flüssigkeit usf. Die Aktion bleistift.schreibe(’B’) kann schon frühzeitig an das Graphitabreiben<br />

gebunden werden, der Befehl schreibstift.schreibe(’B’) kann jedoch erst dann an eine<br />

spezielle Aktion gebunden werden, wenn entschieden ist – ggf. erst zur Laufzeit –, welcher Art der Schreibstift<br />

ist.<br />

(a) Bei getypten Sprachen steht der Speicherplatzbedarf für das Erzeugen eines (einzelnen) Objekts<br />

schon zur Kompilationszeit fest; bei der Erzeugung ist immer der richtige Konstuktor<br />

bekannt. Werden dann die Aktionen (Elementfunktionen) mit dem zugehörigen Objektnamen<br />

aufgerufen, kann schon der Compiler entscheiden, welche Aktion stattfindet ( ” frühe<br />

Bindung“).<br />

(b) In C ++ ist es erlaubt, einem Zeiger einer Basisklasse die Adresse eines Objekts einer abgeleiteten<br />

Klasse zuzuweisen – entsprechend auch bei einer Referenz.<br />

Fortsetzung Beispiel aus (11.35):<br />

Basis *zeigBasis;<br />

Ableitung abl;<br />

zeigBasis=&abl; // erlaubt<br />

Basis &refBasis=abl; // ebenfalls erlaubt<br />

Hierbei ist unbedingt zu unterscheiden:<br />

• der statische Typ des Zeigers bzw. der Referenz<br />

(angegebenes Beispiel: zeigBasis/refBasis sind Zeiger/Referenz auf Basis)<br />

• und der dynamische Typ<br />

(angegebenes Beispiel: zeigBasis/refBasis sind Zeiger/Referenz auf Ableitung);<br />

dieser dynamische Typ steht letzlich erst zur Laufzeit fest.<br />

In diesem Fall ist es wichtig, wann die Bindung an eine Aktion geschieht:<br />

Welche Aktion wird beim Aufruf<br />

zeigBasis->aktion() bzw. refBasis.aktion()<br />

durchgeführt: Basis::aktion() oder Ableitung::aktion()?


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 130<br />

Aus Effektivitätsgründen hat sich der Erfinder von C ++ entschlossen, zunächst dem statischen<br />

Typ den Vorrang zu geben. Dadurch kann schon der Compiler bzw. der Linker die<br />

Bindung vornehmen (frühe Bindung). Es wird demnach Basis::aktion() aufgerufen.<br />

Um eine Aktion in Abhängigkeit vom dynamischen Typ durchzuführen, d. h. um die späte<br />

Bindung einzuführen, muss der Programmierer in C ++ bei der Definition einer Klasse diese<br />

Möglichkeit ausdrücklich angeben: eine Aktion, deren Elementfunktion in der Basisklasse<br />

” virtuell“ (15 FunktionsSpezifizierer virtual) genannt wird, obliegt der späten Bindung.<br />

△! Die späte Bindung wird jedoch nur bei gleicher Signatur auch in den abgeleiteten<br />

Klassen vorgenommen! Eine andere Signatur führt zu einer Verdeckung (11.35) ohne<br />

späte Bindung.<br />

Bsp.: Hätte die Definition in der Basisklasse gelautet:<br />

virtual void aktion();<br />

so würde bei den Aufrufen<br />

zeigBasis->aktion() bzw. refBasis.aktion()<br />

zur Laufzeit Ableitung::aktion() ausgeführt.<br />

↑↑1 Eine virtuelle Funktion muss in der Basisklasse auch definiert sein, selbst wenn sie nicht<br />

benutzt wird. Ausnahme: rein virtuelle Funktion (11.43).<br />

↑↑2 Auch Operatorfunktionen können virtuell sein.<br />

↑↑3 Die Elementfunktionen der Streams (darunter z. B. auch die Ein-/Ausgabeoperatoren >><br />

und


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 131<br />

}<br />

Die zugehörige Ausgabe an cerr ergibt beim obigen Programm:<br />

Konstr. Basis ** Destr. Basis **<br />

Marke1<br />

Konstr. Basis ** Konstr. Ableit ** Destr. Ableit ** Destr. Basis **<br />

Marke2<br />

Marke3<br />

Konstr. Basis ** Destr. Basis **<br />

Marke4<br />

Konstr. Basis ** Konstr. Ableit ** Destr. Basis **<br />

Dieses ist im Falle der Zerstörung des Objekts hinter Marke4 nicht richtig, da der Destruktor von<br />

Ableit nicht aufgerufen wird. Bei später Bindung (Destruktor in der Basisklasse virtuell) ergibt<br />

sich das korrekte Verhalten (hier nur Teil ab Marke4 angegeben):<br />

Marke4<br />

Konstr. Basis ** Konstr. Ableit ** Destr. Ableit ** Destr. Basis **<br />

(11.43) Um dem C ++-Compiler anzugeben, dass eine Klasse abstrakt (11.31c) sein soll, ist in der<br />

Deklaration mindestens einer der Elementfunktionen hinter dem Deklarator der Spezifizierer<br />

” = 0“ hinzuzufügen. Dieses soll sozusagen andeuten, dass diese Funktion keine (eigentliche,<br />

echte) Implementierung hat (die Adresse 0 ist nie eine gültige Codeadresse). Die zugehörige<br />

Elementfunktion nennt man rein virtuelle Funktion 〈pure virtual function〉.<br />

DeklarationReinVirtuelleFunktion 12 DeklSpezifizierer1..n 30 Deklarator = 0 ;<br />

Deklarator [NV] 32 DirekterDeklarator ( ParameterDeklarations-LISTEopt )<br />

Zu einer abstrakten Klasse kann es kein Objekt geben (11.31c); der Compiler verhindert das<br />

Erzeugen eines solchen Variablen. Auch die abgeleiteten Klassen sind abstrakt, solange (auf<br />

dem Ableitungsweg dorthin, ggf. über mehrere Ableitungsstufen) nicht alle rein virtuellen<br />

Funktionen redefiniert worden sind.<br />

Diese rein virtuellen Funktionen müssen auch virtuell sein, d. h. den FunktionsSpezifizierer<br />

virtual (11.42) haben. Dadurch können sie polymorph benutzt werden. Die Definition eines<br />

Zeigers oder einer Referenz auf eine abstrakte Klasse ist nämlich erlaubt; von dieser Möglichkeit<br />

wird in der Praxis häufig Gebrauch gemacht. Jedoch ist eine valide Belegung des Zeigers<br />

(außer mit dem Zeiger 0) bzw. eine Initialisierung der Referenz nur möglich mit einem Objekt<br />

einer abgeleiteten (und natürlich nur konkreten) Klasse. Damit eine solche Belegung<br />

sinnvoll ist, muss späte Bindung für die rein virtuelle Funktion(en) garantiert sein.<br />

↑↑ Zu einer rein virtuellen Funktion darf eine Implementierung, d. h. eine Definition, gehören,<br />

muss es aber nicht. Diese Definition wird aber für diese Klasse selbst nicht gebraucht. Auf<br />

sie kann aber in einer abgeleiteten Klasse – durch Angabe der vollständigen Spezifizierung<br />

BasisKlassenName::ElementFunktionsName – zugegriffen werden.<br />

11.5 Statische Klassenelemente<br />

(11.50) Übb Nach den bisherigen Erläuterungen in diesem Kurs sind Attribute (Datenelemente)<br />

einer Klasse in jedem Objekt individuell vorhanden, jedes Objekt hat seinen eigenen<br />

Datensatz. Diese zugehören Elemente werden ” objekteigen“ genannt, da sie in jedem Objekt<br />

vorhanden sind. Manchmal ist es sinnvoll, zusätzlich Attibute anlegen zu können, die<br />

pro Klasse genau einmal vorhanden sind, und zwar unabhängig von der Existenz von Objekten.<br />

Diese ” klasseneigenen“ Elemente werden in C ++ statische Elemente genannt. (11.51)<br />

beschreibt dieses allgemeine Konzept, (11.52) zeigt die Realisierung in C ++. Ein ausführliches<br />

Beispiel zeigt (11.53). Im letzten Punkt werden einige Besonderheiten diskutiert.<br />

(11.51) Sprachunabhängige Betrachtungen:<br />

(a) Manchmal ist es sinnvoll, die Existenz bestimmter Attribute nicht an die Objekte einer Klasse<br />

zu binden, sondern an die Klasse selbst, z. B. wenn der Wert dieses Attributes für alle<br />

Objekte der Klasse gleichermaßen gelten soll. Wir wollen diese Attribute sprachunabhängig


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 132<br />

klasseneigene Attribute nennen – im Gegensatz zu den bisher besprochenen objekteigenen<br />

Attributen.<br />

Wenn man klasseneneigene Attribute zulässt, sollten sie nicht von dem Vorhandensein von<br />

Objekten dieser Klasse abhängen. Sie existieren demnach, bevor irgendein Objekt dieser<br />

Klasse erzeugt wird oder auch nachdem alle Objekte zerstört sind.<br />

Bsp Bei einer Klasse SparBuch gibt es objekteigene Attribute wie kontoStand, kontoNummer. Wenn sich<br />

jedoch der Zinssatz ändert, so wird er sich für alle Sparbücher ändern. Daher ist es sinnvoll, zinsSatz als<br />

klasseneigenes Attribut einzurichten. Sonst hätte man für jedes Objekt SparBuch eine zinsSatz-Änderung<br />

durchzuführen.<br />

Darüber hinaus ist es durchaus sinnvoll, einen Zinssatz für Sparbücher festzulegen, selbst wenn noch kein<br />

einzige Sparkonto eröffnet wurde. Auch dieser Grund spricht für ein klasseneigenes Attribut.<br />

(b) Erweitert man die Gedanken aus (a), sollten Elementfunktionen, die sich nur auf klasseneigene<br />

Attribute beziehen (Wert setzend, lesend, ändernd), unabhängig von der Existenz von<br />

Objekten der Klasse aufgerufen werden können. Folglich muss man dann auch bei Funktionen<br />

die Unterscheidung machen zwischen klasseneigenen und objekteigenen Elementfunktionen.<br />

(11.52)<br />

Bsp Zu dem Beispiel in (a): Es ist sinnvoll, Elementfunktionen wie gibZinsSatz() oder setzZinsSatz(...)<br />

unabhängig von der Existenz von Sparkonten aufzurufen.<br />

(a) Das erwähnte Konzept von klasseneigenen Attributen wird von C ++ unterstützt, und zwar<br />

durch sog. statische Datenelemente. In der Klassendefinition werden sie durch den Speicherklassen-Spezifizierer<br />

static charakterisiert, vgl. statische Variablen (8.12).<br />

Da der zugehörige Speicherplatz unabhängig von der Existenz von Objekten reserviert werden<br />

muss, muss ein statisches Klassenelement außerhalb der Klasse genau einmal definiert<br />

werden (dieses jedoch ohne den static-Spezifizierer, der sonst die Bedeutung ” interne Bindung“<br />

geben würde (8.23a)). Dieses geschieht sinnvollerweise in der zur Klassendefinition<br />

gehörigen .CPP-Datei. Hierbei sollte es auch geeignet initialisiert werden, da sonst die Regeln<br />

über das implizite Initialisieren von statischen Variablen greifen (8.12). Ein ausführliches<br />

Beispiel s. (11.53).<br />

Jede Elementfunktion, auch ein Konstruktor oder Destruktor, dürfen auf statische Datenelemente<br />

zugreifen, unabhängig von deren Zugriffsspezifizierung (public, protected,<br />

private).<br />

(b) Natürlich unterstützt C ++ auch das Konzept der klasseneigenen Elementfunktionen, die<br />

statischen Elementfunktionen. Auch sie werden in der Klassendefinition durch den<br />

Speicherklassen-Spezifizierer static charakterisiert. Der Compiler wacht darüber, dass in<br />

der Definition dieser Funktion (dort ohne den Spezifizierer static) entweder keine Datenelemente<br />

der Klasse oder nur statische Datenelemente benutzt werden.<br />

Diese statischen Elementfunktionen können in der üblichen Syntax über ein beliebiges Objekt<br />

der Klasse aufgerufen werden. Dieses ist jedoch normalerweise nicht zu empfehlen, da<br />

ein solcher Aufruf für den menschlichen Leser implizieren kann, die Funktion habe mit dem<br />

aufgerufenen Objekt zu tun. Wesentlich klarer ist die Syntax, die für den Leser mehr die<br />

Klassenzugehörigkeit betont:<br />

KlassenName :: ElementFunktionsName(. . . )<br />

Für die Studierenden dieses Kurses ist diese Schreibweise mit dem Klassennamen zwingend.<br />

Ein ausführliches Beispiel wird in (11.53) gegeben.<br />

↑↑ Die Syntax des Elementfunktions-Aufrufs mit dem Bereichsauflösungs-Operator Op1b wurde<br />

in anderem Zusammenhang bereits in (9.12a), insbesondere dort (↑↑2) erwähnt.<br />

(c) Da die statischen Elementfunktionen nicht mit einem Objekt verknüpft werden (selbst wenn<br />

sie mit einem Objekt aufgerufen werden), haben sie keinen this-Zeiger, was schon in (11.13e)<br />

erwähnt wurde.<br />

Eine solche Funktion darf auf alle statischen Elemente der Klasse zugreifen, unabhängig von<br />

der Zugriffsspezifizierung (public, protected, private). Ferner kann sie auf alle klasseninternen<br />

Typdefinitionen und enum-Konstanten zugreifen.<br />

(11.53) Beispiel für statische Klassenelemente in C ++ (Daten und Funktionen):<br />

(a)


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 133<br />

// Beispieldatei D11-53A.H<br />

#ifndef D11_53A_H_<br />

#define D11_53A_H_<br />

class Test {<br />

static int statVar;<br />

static const int statKonst;<br />

int normalVar;<br />

public:<br />

Test();<br />

void ausgabe();<br />

void setzNormalVar(int wert);<br />

static void setzStatVar(int wert);<br />

static void ausgabeStat();<br />

};<br />

#endif<br />

(b) // Beispieldatei D11-53B.CPP<br />

#include <br />

using namespace std;<br />

#include "d11-53a.h"<br />

// NOTWENDIGE Definitionen; wenn fehlend, dann Linker-Fehlermeldung;<br />

// Hier KEIN "static" wiederholen!!<br />

// Folgende Zeile auch OHNE Inititalisierer "=90" erlaubt<br />

// (dann Initialisierung mit 0 - sollte nicht so gemacht werden):<br />

int Test::statVar=90;<br />

// Folgende Zeile OHNE Inititalisierer "=-10" nicht erlaubt:<br />

const int Test::statKonst=-10;<br />

Test::Test()<br />

: normalVar(2)<br />

{<br />

// beliebige Setzungen, auch z. B. statVar, NICHT jedoch statKonst!<br />

}<br />

// Hier KEIN "static" wiederholen!!!<br />

void Test::setzStatVar(int wert)<br />

{<br />

// Bsp.: Übernahme nur, wenn positiv<br />

if (wert>0) statVar=wert;<br />

}<br />

void Test::setzNormalVar(int wert)<br />

{<br />

normalVar=wert;<br />

}<br />

void Test::ausgabe()<br />

{<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 134<br />

(c)<br />

}<br />

// NICHT möglich: Ausgabe von Datenelement normalVar<br />

// Beispieldatei D11-53C.CPP<br />

#include <br />

using namespace std;<br />

#include "d11-53a.h"<br />

int main()<br />

{<br />

// VOR Erzeugung eines Objekts:<br />

Test::ausgabeStat();<br />

Test::setzStatVar(99);<br />

Test::ausgabeStat();<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 135<br />

keine Gedanken zu machen. Zusätzlich ist garantiert, dass diese Konstante keinen (Daten-)<br />

Speicherplatz belegt. Diese sehr einfache Möglichkeit wird jedoch von manchen Autoren als<br />

” Enum-Hack“ betrachtet (12.21Anm) (Doch warum?).<br />

Bsp Hier werden eine Konstante maxAufzaehl und eine Konstante maxStatisch deklariert, in der Klasse<br />

initialisiert und als Arrayindexgrenzen benutzt – nicht mit MS VC++ 6.0 kompilierbar!.<br />

// Headerdatei<br />

class Test {<br />

public:<br />

enum { maxAufzaehl=20 };<br />

static const int maxStatisch=20;<br />

private:<br />

char arr1[maxAufzaehl],<br />

arr2[maxStatisch];<br />

};<br />

// CPP-Datei<br />

const int Test::maxStatisch; // Definition oh. Initialisierung,<br />

// Def. darf nicht vergessen werden<br />

// (sonst Linker-Fehler!)<br />

// KEINE Extra-Definition für Test::maxAufzaehl!!<br />

// (Diese ist bereits in der enum-Definition enthalten!)


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 136<br />

12 Operatoren, Typen, Ergänzungen zu Zeiger, Binärdateien<br />

12.0 Überblick<br />

In diesem Kapitel werden zunächst noch einmal die Operatoren vorgestellt (Unterkapitel 1).<br />

Das Folgekapitel führt den Aufzählungtyp ein und erläutert Einzelheiten der impliziten und<br />

expliziten Typumwandlungen.<br />

Unterkapitel 3 zeigt, dass es eine günstige Zeigerarithmetik gibt, die für effektive (Low-<br />

Level-)Programmierung unerlässlich ist. Da Arraybezeichnungen meist in Zeiger umgewandelt<br />

werden, gilt dieses auch für Arrays. Das Folgekapitel befasst sich damit, wie Zeiger als<br />

Funktionsparameter zu handhaben sind. Dieses ist für C sehr wichtig, aber auch in C ++,<br />

insbesondere in Zusammenhang mit Arraybezeichnungen (als Zeiger).<br />

Unterkapitel 5 zeigt einige Beispiele von Low-Level-Programmierung mit Zeigern und Arrays.<br />

Beim Umgang mit Binärdateien sind einige wichtige Besonderheiten zu beachten, die in<br />

Unterkapitel 6 ausführlich beschrieben sind. Binärdateien werden häufig dazu benutzt, um<br />

Objekte zu speichern und zu lesen. Ferner werden das Positionieren in Dateien und die<br />

Portierbarkeit von Binär- und Textdateien betrachtet.<br />

Kompliziertere Arraystrukturen sowie die zugehörigen Zeiger werden in Unterkapitel 7 vorgestellt.<br />

Dazu gehört auch die Behandlung von Kommandozeilenparametern.<br />

Das letzte Unterkapitel führt kurz in typlose Zeiger ein (in C häufig verwendet), dazu in die<br />

wichtigsten Bibliotheksfunktionen von C, die teilweise auch in C ++ benutzt werden.<br />

12.1 Operatoren<br />

(12.10) Übb In diesem Unterkapitel werden alle C ++-Operatoren angesprochen, teilweise auch zum<br />

erstenmal erläutert (12.11); die zugehörige Tabelle s. (5.11) oder (Cpp/Kap. 2). In (12.12) werden<br />

einige wichtige Eigenschaften von Ausdrücken zusammengefasst.<br />

(12.11) ↙ NEU Operatoren:<br />

s. (Cpp/Kap. 2) und die Tabelle in (5.11)<br />

– Die Nummern/Buchstaben geben die Hierarchiestufen/Unterscheidungskennung an –<br />

1 a :: C++ global: unär präfix (7.52)<br />

b :: C++ Bereichsauflösung: binär infix z. B. (9.12a)<br />

2 a [ ] Index: Übergang von Array zu Arraykomponente (5.41, 5.42)<br />

b ( ) Funktionsaufruf: Übergang von Funktionsname zum Funktionsaufruf-Ausdruck<br />

(7.12)<br />

c ( ) C++ △! explizite Typumwandlung 〈type cast〉 in Funktionsschreibweise;<br />

diese Art der Typumwandlung sollte nicht mehr benutzt<br />

werden, s. a. (5.64)<br />

Syntax: TypName( Ausdruck )<br />

Bedeutung: Ausdruck wird explizit in den Typ TypName umgewandelt.<br />

Im Gegensatz zu Op3i muss hier ein Typname vorliegen.<br />

– Zur Problematik der Typumwandlungen s. (Kap. 12.2) –<br />

d . Zugriff auf Klassenelement, s. (9.13)<br />

e -> Zugriff auf Klassenelement über Zeiger, s. (10.29)<br />

fg ++ -- Inkrement, Dekrement postfix (Stellung präfix s. Op3ab):<br />

Seiteneffekt unabhängig von Stellung präfix oder postfix<br />

Haupteffekt bei postfix: alter Wert, s. (3.32e)<br />

h-k ... cast C++ neue Operatoren zur sichereren Typumwandlung, s. (5.64), zu<br />

reinterpret cast s. a. (12.63a, 12.64a).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 137<br />

3 ab ++ -- Inkrement, Dekrement präfix (Stellung postfix s. Op2fg):<br />

Seiteneffekt unabhängig von Stellung präfix oder postfix,<br />

Haupteffekt bei präfix: neuer Wert, s. (3.32e)<br />

c ∼<br />

NEU Bitinversion des Ganzzahl-Operanden<br />

d ! logische Negation, s. (4.24)<br />

ef + - Identität und Vorzeichenumkehr<br />

gh & * Adresse und Inhalt/Verweis, s. (10.21b)<br />

i ( ) △! explizite Typumwandlung 〈type cast〉; diese Art der Typumwandlung<br />

sollte nicht mehr benutzt werden, s. a. (5.64)<br />

Syntax: ( Typ ) Ausdruck<br />

Bedeutung: wie Op2c; hier braucht Typ nicht unbedingt ein Name<br />

zu sein<br />

– Zur Problematik der Typumwandlungen s. (Kap. 12.2) –<br />

j sizeof Speicher-Größenangabe des Operanden in Anzahl Bytes (Typ des<br />

ErgebnisAusdrucks vorzeichenlos), s. (5.14).<br />

k<br />

l<br />

new<br />

delete<br />

C++ Freispeicherreservierung und -freigabe, s. (Kap. 10.3)<br />

5 ab * / Multiplikation und Division;<br />

wenn beide Operanden Ganzzahl-Ausdrücke, ist Ergebnis ein<br />

Ganzzahl-Typ, sonst ein Gleitkommatyp<br />

c % Teilerrest – nur Ganzzahl-Operanden<br />

6 ab + - Addition und Subtraktion<br />

7 a NEU dto. Bitverschiebung nach rechts:<br />

• bei vorzeichenlosem GanzzahlAusdruck1 werden 0-Bits nachgeschoben,<br />

– entspricht Division durch 2 Ausdruck2<br />

< >=<br />

• △! bei vorzeichenbehaftetem GanzzahlAusdruck1 werden<br />

bei positivem Wert 0-Bits nachgeschoben (Division w. o.),<br />

bei negativem Wert 0- oder 1-Bits je nach Implementation<br />

(bei Zweierkomplementdarstellung wird bei negativem Wert<br />

meist 1 nachgeschoben, dann ebenfalls Division w. o.)<br />

Vergleich (4.22)<br />

9 ab == != Test auf Gleichheit bzw. Ungleichheit (4.22), und zwar in geringerer<br />

Hierarchiestufe als die Vergleichsoperatoren Op8<br />

Anm △! VORSICHT bei der Hierarchie bei Ausdrücken mit den<br />

Bit-Operatoren Op10,11,12: die Ausdrücke müssen bei Test auf<br />

Gleichheit bzw. Ungleichheit geklammert werden (Hierarchie Op9<br />

ungünstig!).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 138<br />

10 & NEU Bitweiser Und-Operator<br />

Syntax (z. B.): GanzzahlAusdruck & Maske<br />

Anwendung:<br />

• Wert des gesamten Ausdrucks ist GanzzahlAusdruck mit einzelnen<br />

gelöschten Bits, und zwar den Bits, zu denen positionsgleich<br />

0-Bits in Maske vorhanden sind,<br />

• Abfrage, ob bestimmte Bits gesetzt sind, und zwar an der<br />

Position der 1-Bits in Maske: Test auf<br />

(GanzzahlAusdruck & Maske)!=0<br />

– △! Hierarchie, s. Anm bei Op9 –<br />

11 ∧ NEU bitweiser Exklusiv-Oder-Operator,<br />

Anwendung: Invertieren einzelner Bits, und zwar an der Position<br />

der 1-Bits in Maske, vgl. Op10<br />

– △! Hierarchie, s. Anm bei Op9 –<br />

12 | NEU bitweiser Inklusiv-Oder-Operator,<br />

Anwendung: Setzen einzelner Bits, und zwar an der Position der<br />

1-Bits in Maske, vgl. Op10<br />

– △! Hierarchie, s. Anm bei Op9 –<br />

13 && logischer Und-Operator, Kurzschlussverfahren ist garantiert, s.<br />

(4.24)<br />

14 || logischer (Inklusiv-)Oder-Operator, Kurzschlussverfahren ist garantiert,<br />

s. (4.24)<br />

15 ? : Bedingungsoperator, ternär, s. (5.12)<br />

Syntax: Ausdruck1 ? Ausdruck2 : Ausdruck3<br />

Bedeutung textuell:<br />

WENN Ausdruck1 DANN Ausdruck2 SONST Ausdruck3<br />

Durchführung:<br />

• zuerst Bewertung Ausdruck1 einschließlich Ausführung aller<br />

Seiteneffekte (garantiert),<br />

• dann Bewertung<br />

◦ entweder Ausdruck2, wenn Ausdruck1 im Booleschen<br />

Sinne wahr, s. (Kap. 4.2),<br />

◦ oder Ausdruck3, wenn Ausdruck1 falsch;<br />

• Wert des gesamten Ausdrucks (Haupteffekt): Wert von Ausdruck2<br />

oder Ausdruck3, je nachdem, welcher Ausdruck bewertet<br />

wurde.<br />

16 a = Zuweisung<br />

b-k op= zusammengesetzter Zuweisungsoperator (3.27)<br />

a op= b ist äquivalent zu a = a op ( b )<br />

– mit dem Unterschied, dass a nur einmal bewertet wird<br />

17 , Kommaoperator, s. (5.13)<br />

Syntax: Ausdruck1 , Ausdruck2<br />

Bedeutung:<br />

• zunächst Bewertung von Ausdruck1 einschließlich Ausführung<br />

aller Seiteneffekte (garantiert),<br />

• Haupteffekt von Ausdruck1 wird verworfen (darf fehlen),<br />

• dann Bewertung von Ausdruck2 einschließlich Ausführung aller<br />

Seiteneffekte,<br />

• Wert des gesamten Ausdrucks: Haupteffekt von Ausdruck2<br />

Anwendung: mehrere Ausdrücke an Stellen, an denen syntaktisch<br />

nur einer stehen darf, z. B. bei Initialisierung und Reinitialisierung<br />

der for-Anweisung, s. (4.43)


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 139<br />

(12.12) Zus zu Ausdrücken:<br />

(a) Der Haupteffekt von Ausdrücken ist unwichtig (wird verworfen) bei:<br />

• 51 AusdruckAnweisung, s. (3.32a)<br />

• erster Operand beim Kommaoperator Op17, s. (12.11) und (5.13)<br />

• Initialisierung und Reinitialisierung einer 72 for-Anweisung, s. (4.43)<br />

Bei unwichtigem Haupteffekt ist z. B. Funktionsaufruf ohne Rückgabewert (void) möglich.<br />

(b) In einem Ausdruck ist i. a. nicht festgelegt, in welcher Reihenfolge die Teilausdrücke bewertet<br />

werden und ggf. Seiteneffekte ausgeführt werden. Diese Regel der freien Bewertungsreihenfolge<br />

widerspricht nicht den Operatoreigenschaften Hierarchiestufe und Assoziativität (dieses:<br />

Reihenfolge beim Zusammenfassen von Teilausdrücken).<br />

Bsp liesZahl() lese eine Zahl von der Tastatur ein. Die Programmzeile<br />

cout > Variable,<br />

• Ausgabe auf dem Bildschirm, z. B. cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 140<br />

Ein direktes Einlesen von Aufzählungswerten ist nicht möglich; ggf. muss ein Ganzzahlwert<br />

eingelesen und nach Bereichsprüfung explizit in den Aufzählungstyp umgewandelt werden.<br />

Sinn des Aufzählungstyps (z. B.):<br />

• C/C++ Definition selbstdokumentierender Namen für Konstanten einer zusammengehörigen<br />

Menge.<br />

• C++ Konstantendefinition innerhalb von Klassen mit Gültigkeitsbereich Klasse ohne<br />

Anlegung von Speicherplatz.<br />

Anm Es gibt Autoren, die die Meinung vertreten, dass Aufzählungskonstanten nur für eine zusammenhängende<br />

Konstantenmenge – d. h. für ” Aufzählungen“ – genommen werden sollten.<br />

Die Definition beliebiger (später in der Programmentwicklung änderbarer) Konstanten, insbesondere<br />

ohne einen Typnamen (dann ja keine Variablen dieses Typs bildbar), bezeichnen<br />

sie als ” enum-Hack“. Man kann darüber sicher verschieden philosophieren. In der Benutzung<br />

jedoch sind Aufzählungstypen einfacher – und zwar insbesondere innerhalb Klassen, hier<br />

sind andere (statische) Konstanten wesentlich umständlicher zu handhaben, vgl. (11.54c)! Sie<br />

belegen auch garantiert keinen Extra-Speicherplatz. Selbst in (Str3/Kap. 4.8) werden sie ohne<br />

Wertung auch ohne Typnamen vorgestellt, in (Str3/Kap. 5.4) wird auf sie verwiesen.<br />

Bsp Definition eines Aufzählungstyps und einer Variablen:<br />

enum WTag {mon,die,mit,don,fre,sam,son};<br />

WTag wochenTag; // wochenTag Variable des Typs WTag<br />

Dieses ist gleichbedeutend mit:<br />

enum WTag {mon,die,mit,don,fre,sam,son} wochenTag;<br />

Auch ohne Typname, wenn er nicht benötigt wird:<br />

enum {mon,die,mit,don,fre,sam,son} wochenTag;<br />

Oder ohne Variablendefinition, wenn nur Konstantenwerte wichtig sind mit impliziter Umwandlung in<br />

Ganzzahltyp (was einige Autoren ” enum-Hack“ nennen, s. o. Anm):<br />

enum {mon,die,mit,don,fre,sam,son};<br />

// Werte der Konstanten: mon 0, die 1,..., son 6<br />

Besser für bürgerliche Zählung in Deutschland (mon 1,. . . ,son 7):<br />

enum WTag {mon=1,die,mit,don,fre,sam,son};<br />

Auch möglich, wenn auch wohl unsinnig:<br />

enum WTag {mon,die=0,mit,don=6,fre,sam=5,son};<br />

// Werte: mon 0, die 0, mit 1, don 6, fre 7, sam 5, son 6<br />

↑↑ zu C :<br />

↑↑1 Der optionale Name ist ein sog. Etikett 〈tag〉; nur die Kombination<br />

enum Name<br />

benennt einen Typ. Diese Syntax ist auch in C ++ möglich, aber nicht notwendig.<br />

Ausnahme davon (d. h. auch in C ++ diese Syntax nötig) s. (Cpp/Kap. 5.1 Anm.)<br />

↑↑2 Man definiert daher in C häufig einen Typnamen:<br />

typedef enum Nameopt { . . . } TypName ;<br />

In C ++ ist dieses nicht nötig, da der optionale Name bereits ein Typname ist.<br />

↑↑3 In C ist eine Aufzählungskonstante vom Typ int.<br />

↑↑4 Die Typumwandlung GanzahlTyp → AufzählungsTyp geschieht in C, wenn nötig, auch implizit<br />

– im Gegensatz zu C ++.<br />

(12.22) In C galt vor der ANSI-Normierung innerhalb von Ausdrücken:<br />

• Umwandlung aller schmaleren Ganzzahltypen in Typ int<br />

• Gleitkommaarithmetik mindestens mit Genauigkeit double<br />

• Umwandlung vorzeichenloser Ganzzahlen in vorzeichenbehaftete Ganzzahlen:<br />

möglichst lange Beibehaltung des unsigned-Charakters;<br />

jetzt (C/C ++): möglichst Beibehaltung des Werts<br />

(12.23) Die in (Cpp/Kap. 7.1-4) beschriebenen Umwandlungen werden in folgenden Fällen implizit angewendet<br />

(jeweils nur, wenn nötig). Diese impliziten Umwandlungen laufen so ab, als ob eine<br />

passende explizite Typumwandlung (5.64) gefordert wäre; auch diese läuft nach den gleichen<br />

Regeln (Cpp/Kap. 7.1-4) ab.<br />

• Operand eines binären Operators, wenn nicht passend:<br />

Regeln der ” arithmetischen Umwandlungen“ (Cpp/Kap. 7.1)<br />

• Umwandlung rechter Operand in Typ des linken Operanden beim Zuweisungsausdruck<br />

• Umwandlung aktueller Funktionsparameter in Typ des formalen Parameters<br />

• Umwandlung Rückgabewert einer Funktion (Ausdruck in return-Anweisung (7.11b)) in<br />

Rückgabetyp der Funktion


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 141<br />

• ↑↑ Typumwandlung bei Parametern einer Funktion:<br />

◦ C/C++ aktuelle Parameter bei beliebig vielen zusätzlichen Parametern entsprechend<br />

” ...“, da kein Typ für formale Parameter vorhanden<br />

◦ C Funktionsdeklaration ” alten Stils“ (explizit oder implizit)<br />

◦ C formaler Funktionsparameter bei Funktionsdefinition ” alten Stils“<br />

In diesen Fällen Umwandlung der aktuellen Funktionsparameter in folgender Weise:<br />

◦ float wird in double umgewandelt<br />

◦ jeder Ganzzahltyp unterliegt der ” ganzzahligen Typangleichung“ (Cpp/Kap. 7.2)<br />

△! Merken: Gefährlich ist die Umwandlung von vorzeichenlosen und vorzeichenbehafteten<br />

Ganzzahltypen ineinander. Beachten: sizeof (5.14) und strlen() (5.53c) haben vorzeichenlose<br />

Typen!<br />

12.3 Zeigerarithmetik<br />

(12.30) Übb Um mit Zeigern effektiv arbeiten zu können, ist das Verständnig für die Arithmetik<br />

mit Zeigern wichtig. Hiermit lässt sich sehr effektiver (Low-Level-)Programmcode schreiben.<br />

Da Array-Bezeichungen (fast) immer in Zeiger umgewandelt werden, hat dieses auch<br />

Auswirkungen auf dem Umgang mit Arrays. Insbesondere die Konsequenzen auf Arrays als<br />

Funktionsparameter (Unterkapitel 4) sind für den Programmierer wichtig.<br />

(12.31) Adressarithmetik bei Zeigern:<br />

(12.32)<br />

Z sei Zeigerausdruck,<br />

T sei zugehöriger Typ (d. h. Z Zeiger auf T),<br />

I sei eine beliebiger Ganzzahlausdruck.<br />

Z+I ist die um I Objektbreiten des Typs T vergrößerte Adresse; dieser Zeiger zeigt auf<br />

ein T, welches um (I*sizeof(T)) zu Z verschoben ist.<br />

Z-I wie oben, nur subtrahiert<br />

Z1-Z2 Anzahl Objekte (als Ganzzahl) des Typs T zwischen den Adressen Z1 und Z2;<br />

notwendig: Z1, Z2 müssen Zeiger vom selben Typ sein.<br />

Voraussetzung für die Richtigkeit der Adressarithmetik: Alle Adressen müssen auf Elemente<br />

desselben Arrays zeigen, und zwar bestehend aus Elementen des Typs T. Sonst ist das<br />

Verhalten undefiniert.<br />

Anm1 Die Array-Komponenten sind adress-aufwärtssteigend gespeichert.<br />

Anm2 Erlaubt ist die Zeigerbildung zur fiktiven Folgekomponente des Arrays (dadurch manchmal<br />

Schleifenbedingungen leichter formulierbar); dieser Zeiger darf jedoch nicht dereferenziert<br />

werden.<br />

(a) Ein Ausdruck der Form A[I]<br />

wird immer automatisch umgewandelt in den Ausdruck *(A+I).<br />

Hierbei ist A ein Array oder ein Zeiger, I ein Ganzzahlausdruck.<br />

Daher gelten folgende Äquivalenzen<br />

(links: in Praxis sehr häufig benutzte Schreibweise, rechts: zunächst wohl einsichtigere Schreibweise):<br />

arr sei ein Array (Array-Name/-Bezeichnung).<br />

Der Ausdruck: ist äquivalent zu:<br />

arr &arr[0]<br />

arr+1 &arr[1]<br />

arr+n &arr[n]<br />

*arr arr[0]<br />

*(arr+1) arr[1]<br />

*(arr+n) arr[n]<br />

(b) Ein Arrayname ist synonym zu einem konstanten Zeiger auf Element Nummer 0, vgl.<br />

(10.26). Für diesem Arraynamen ist daher keine Wertzuweisung erlaubt,


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 142<br />

z. B. falsch: arr++, arr=....<br />

Dagegen zu Funktionsparametern s. (12.44c).<br />

12.4 Zeiger als Funktionsparameter<br />

(12.40) Übb In diesem Unterkapitel wird gezeigt, wie man Zeiger als Funktionsparameter einsetzen<br />

kann. Dieses ist in C die einzige Möglichkeit, so etwas wie eine Referenzübergabe<br />

programmtechnisch zu imitieren. Für C ++-Programmierer ist dieser Aspekt nicht mehr ganz<br />

so wichtig, da es den Referenztyp gibt. Jedoch auch in C ++ sollte man Folgerungen kennen,<br />

zumal ja Arraybezeichnungen als Parameter nichts anderes als Zeiger sind.<br />

(12.41) Parameterübergabearten bei Funktionen, vgl. (Kap. 7.4):<br />

• CALL BY VALUE C/C++ : Der Wert des aktuellen Parameters wird auf den formalen<br />

Parameter kopiert.<br />

• CALL BY REFERENCE C++ : Der formale Parameter ist während des Funktionsdurchlaufs<br />

ein Synonym des aktuellen Parameters.<br />

(12.42) Zeiger als Funktionsparameter:<br />

C/C++ Auch mit Hilfe des CALL BY VALUE ist die Nachahmung eines CALL BY REFE-<br />

RENCE möglich, nämlich durch explizite Übergabe von Zeigern.<br />

Der aktuelle Parameter (Adresse einer Variablen) wird kopiert auf den formalen Parameter<br />

(CALL BY VALUE); diese kopierte Adresse zeigt jedoch auf denselben Speicherplatz wie die<br />

Originaladresse, d. h. die Funktion kann über diesen Zeiger Variable des rufenden Programms<br />

verändern.<br />

Bsp vgl. (7.45), Vertauschen hier mit Zeigern (tausch1) verwirklicht, zusätzlich zu Referenzen (tausch2) wie<br />

dort:<br />

void tausch1(int *wert1, int *wert2)<br />

{ int merk;<br />

merk=*wert1;<br />

*wert1=*wert2;<br />

*wert2=merk;<br />

}<br />

void tausch2(int &wert1, int &wert2)<br />

{<br />

int merk;<br />

merk=wert1;<br />

wert1=wert2;<br />

wert2=merk;<br />

}<br />

// Aufrufe (innerhalb einer anderen Funktion):<br />

int i=14,j=2;<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 143<br />

tausch(i, j); // Aufruf tausch(int&, int&)<br />

(12.43) Die Syntax eines Funktionsaufrufs lässt leider nicht erkennen, ob eine Wert- oder Referenzübergabe<br />

geschieht; das ist nur bei der Funktionsdeklaration oder -definition ersichtlich<br />

– vgl. auch die Ausführungen in (7.44).<br />

Daher ist die Übersichtlichkeit und Nachvollziehbarkeit am Ort des Funktionsaufrufs nicht<br />

gegeben, wenn man nicht weiß, ob die aktuellen Parameter durch den Funktionsaufruf<br />

geändert werden können – es sei denn, der aktuelle Parameter ist nicht oder nicht nur eine<br />

Variable (dann keine änderbare Referenz möglich). Es gibt daher Autoren, die folgendes<br />

empfehlen (mit Anmerkung von Bartning zu Punkt (2)):<br />

(1) Wenn keine Wertänderung durch den Funktionsaufruf geschieht: normale Wertübergabe,<br />

keine Zeiger. Da der Parameter eine lokale Kopie des aktuellen Parameters ist, kann<br />

auch keine Wertänderung am Aufrufort geschehen.<br />

Anm Diese Wertübergabe ist bei eingebauten Typen die normale Übergabeart; bei Objekten<br />

dagegen nimmt man normalerweise konstante Referenz, s. (3).<br />

(2) Wenn Wertänderung am Aufrufort benötigt wird: Übergabe von Zeigern (diese per<br />

Wert). Durch die Syntax (Zeiger) ist deutlich, dass die zugehörige deferenzierte Variable<br />

geändert werden kann.<br />

DAGEGEN (Anm. von Bartning): Diese Empfehlung ist für Programmierer mit C-<br />

Erfahrung sicher sehr gewichtig; für Programmierer nur mit C ++-Kenntnissen ist sie<br />

nicht ganz so wichtig, da sie sich wohl an die Möglichkeit nichtkonstanter Referenzübergabe<br />

gewöhnt haben und daher aufpassen. Daher für Studierende dieses Kurses: auch<br />

bei Wertänderung ist eine Referenzübergabe sinnvoll.<br />

↑↑ Pascalprogrammierer sind ebenfalls an dieselbe Problematik gewöhnt, dass am Aufrufort<br />

keine Unterscheidung zwischen Wert- und Referenzübergabe möglich ist.<br />

(3) C++ Wenn bei Variablen mit großer Speicherbelegung aus Effizienzgründen (Speicherplatz,<br />

Rechenzeit) keine Wertübergabe geschehen soll, aber trotzdem die Nichtänderung<br />

garantiert sein soll, ist eine konstante Referenz sinnvoll. Der Compiler garantiert die<br />

Nichtänderung, Näheres s. (7.44b). Bei Objekten (Variablen eines Klassentyps) ist dieses<br />

die normale Übergabeart, wenn Konstantheit möglich ist (eine Wertübergabe würde<br />

Aufrufe von Kopierkonstruktor und Destruktor bedeuten).<br />

Anm Dieses ist auch – nicht nur in C – bei Zeigerübergabe möglich durch ein const für die<br />

dereferenzierte Variable, vgl. (10.28):<br />

14/25 const Typ *Zeiger<br />

Dagegen konstanter Zeiger, d. h. Nichtänderbarkeit des Zeigers selbst:<br />

Typ * 31/25 const Zeiger<br />

Beides konstant:<br />

14/25 const Typ * 31/25 const Zeiger<br />

(12.44) Folgerungen aus (10.26) für formale Funktionsparameter, vgl. Bemerkungen (7.46):<br />

(a) Ist ein formaler Funktionsparameter vom Typ ” Array aus T“, wird auch er nach (10.26) durch<br />

den Compiler automatisch in ” Zeiger auf T“ umgewandelt. Eine Dimensionierung des Arrays<br />

wird demnach völlig ignoriert; daher ist hierbei eine Arraykennzeichnung ohne Dimension<br />

erlaubt, d. h. nur mit leerem Klammernpaar [ ]. Üblich ist jedoch statt dessen die direkte<br />

(explizite) Zeigerschreibweise.<br />

Drei äquivalente Deklarationen eines formalen Parameters (üblich: letzte Form):<br />

Typ Name[Anzahl]<br />

Typ Name[ ]<br />

Typ *Name<br />

Wegen (12.32a) ist innerhalb der Funktion – unabhängig vom Deklarator als Array oder Zeiger<br />

– die Array- und die Zeigerschreibweise erlaubt.<br />

(b) Ein Array-Parameter scheint daher die Übergabeart CALL BY REFERENCE zu haben; in<br />

Wirklichkeit liegt jedoch ein CALL BY VALUE eines Zeigers vor. Aus diesem Grund können<br />

Funktionen mit Array-Parametern die Originalarrays direkt verändern (Beispiele: strcpy,<br />

strcat).


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 144<br />

Ein echtes CALL BY VALUE eines Arrays (mit Erzeugung einer lokalen Kopie) gibt es in<br />

C ++/C nicht; eine indirekte Möglichkeit besteht durch die Einbindung des Arrays in eine<br />

Klasse/Struktur.<br />

(c) Ein formaler Parameter mit Arraytyp (eigentlich: Zeigertyp) ist – wegen des CALL BY<br />

VALUE des Zeigers – im Gegensatz zu (12.32b) kein konstanter Zeiger, er kann in der Funktion<br />

verändert werden, z. B. zum Hochzählen innerhalb des Arrays.<br />

(d) Da ein formaler Array-Parameter in Wirklichkeit ein Zeiger ist, wird durch den Ausdruck<br />

sizeof(ParameterArrayName)<br />

bei allen drei Deklarationsformen aus (a) immer nur<br />

sizeof(Typ*)<br />

berechnet (Speichergröße des Zeigers) – und nicht der Speicherbedarf des Arrays. Innerhalb<br />

von Funktionen muss daher bei formalem Array-Parameter<br />

statt sizeof(ArrayName) immer sizeof(ArrayTyp)<br />

(Typ in Arrayschreibweise mit Dimensionierung!) genommen werden.<br />

Dieses ist auch der tiefere Grund, weswegen es in C ++/C bei Arrays innerhalb von Funktionen<br />

völlig unmöglich ist, die Entdeckung einer Bereichsüberschreitung zu programmieren. Nur<br />

mit einem zusätzlichen Größenparameter kann dieses geschehen.<br />

12.5 Anwendungen<br />

(12.50) Übb Die Anwendungen (kurze Programmfragmente) sollen die Möglichkeiten aufzeigen,<br />

die die Sprachen C ++/C bieten, wenn man im Low-Level-Bereich mit Arrays und Zeigern<br />

programmiert: von manchen Programmierern gehasst, von anderen wiederum wegen der eleganten<br />

Möglichkeiten geliebt (als Gipfel: Stringkopieren (12.52 Version (d)). Es wird an Beispielen<br />

gezeigt, wie man mit Arrays, insbesondere mit Strings, umgehen kann. (12.53) zeigt außerdem,<br />

wie das System Stringkonstanten handhabt. Vorsicht bei der Rückgabe von (scheinbaren)<br />

Arrays aus Funktionen (12.54)!<br />

(12.51) Bsp Berechnung der Stringlänge<br />

Mögliche äquivalente Funktionsköpfe (in der Praxis meist zweite Version):<br />

int strlen_neu(char s[]); // Parameter in Array-Schreibweise<br />

int strlen_neu(char *s); // Parameter als Zeiger<br />

Mögliche Implementationen für die Funktionsblöcke, zunächst mit Benutzung der Array-<br />

Schreibweise:<br />

// Version (a)<br />

{<br />

int i=0;<br />

while (s[i]!=’\0’) ++i;<br />

return i;<br />

}<br />

// Version (b)<br />

{<br />

int i=0;<br />

while (s[i]) ++i;<br />

return i;<br />

}<br />

// Version (c)<br />

{<br />

int i=0;<br />

while (s[i++]);<br />

return i-1;<br />

}<br />

Implementationen unter Benutzung der Zeigerschreibweise:


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 145<br />

// Version (d)<br />

{<br />

int i=0;<br />

while (*s!=’\0’) {<br />

++s; ++i;<br />

}<br />

return i;<br />

}<br />

// Version (e)<br />

{<br />

int i=0;<br />

while (*s++) ++i;<br />

return i;<br />

}<br />

// Version (f)<br />

{<br />

char *merk=s;<br />

while (*s++);<br />

return s-merk-1;<br />

}<br />

Anm Wie schon in (12.44a) erwähnt, sind alle Versionen (a) bis (f) untereinander austauschbar –<br />

völlig unabhängig von der Art der Parameterdeklaration (Array oder Zeiger).<br />

(12.52) Bsp String-Kopierfunktionen<br />

(a) Mögliche Funktionsköpfe:<br />

void strcpy_neu(char ziel[],char quell[]); // Array-Schreibweise<br />

void strcpy_neu(char *ziel,char *quell); // Zeiger (meistens so)<br />

Mögliche Implementationen für die Funktionsblöcke, zunächst mit Benutzung der Array-<br />

Schreibweise:<br />

// Version (a)<br />

{<br />

int i=0;<br />

while (quell[i]!=’\0’) {<br />

ziel[i]=quell[i];<br />

++i;<br />

}<br />

ziel[i]=’\0’;<br />

}<br />

Implementationen unter Benutzung der Zeigerschreibweise:<br />

// Version (b)<br />

{<br />

while (*quell!=’\0’) {<br />

*ziel=*quell;<br />

++ziel;<br />

++quell;<br />

}<br />

*ziel=’\0’;<br />

}<br />

// Version (c)<br />

{<br />

while (*quell)<br />

*ziel++=*quell++;<br />

*ziel=’\0’;


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 146<br />

}<br />

// Version (d) - C-typisch!!<br />

{<br />

while (*ziel++=*quell++);<br />

}<br />

(b) Üblich ist es, solchen Funktionen einen Rückgabewert zu geben (d. h. nicht void), und zwar<br />

den (ursprünglichen) Wert des Zeigers ziel; dadurch ist eine Schachtelung von solchen<br />

Funktionsaufrufen möglich.<br />

char *strcpy_NEU(char *ziel,char *quell);<br />

// Funktionsblock zu Version (a)<br />

{<br />

int i=0;<br />

while (quell[i]!=’\0’) {<br />

ziel[i]=quell[i];<br />

++i;<br />

}<br />

ziel[i]=’\0’;<br />

return ziel; // wie: return &ziel[0];<br />

}<br />

// Funktionsblock zu Version (d)<br />

{<br />

char *merk=ziel;<br />

while (*ziel++=*quell++);<br />

return merk;<br />

}<br />

Auch die String-Bibliotheksfunktionen haben als Rückgabewert den Zeiger ziel, außerdem<br />

kann mit dem Quellzeiger das Quellarray nicht verändert werden, vgl. (10.28):<br />

char *strcpy(char *ziel, const char *quell);<br />

char *strcat(char *ziel, const char *quell);<br />

Bsp für die Ausnutzung der Rückgabewerte:<br />

char str1[]="String", str2[]="fortsetzung",<br />

puffer[30];<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 147<br />

str1[0]=’P’; // (eigentlich) nicht erlaubt<br />

str2[0]=’P’; // erlaubt: Ptring2<br />

*str3=’P’; // erlaubt: PTRing3<br />

str1=str2; // erlaubt; jetzt STRING1 jedoch nicht mehr zugänglich!<br />

str2=str3; // nicht erlaubt (12.32b)<br />

(12.54) Ein ” beliebter“ Fehler im Zusammenhang mit der Rückgabe von Strings aus Funktionen ist<br />

die Rückgabe der Adresse von automatischem Speicher.<br />

// SEHR schwerwiegender Fehler:<br />

char *wotag(int ord)<br />

{<br />

char str[20];<br />

switch(ord) {<br />

case 1: strcpy(str,"Montag"); break;<br />

// ...<br />

case 7: strcpy(str,"Sonntag"); break;<br />

}<br />

return str; // FALSCH!<br />

}<br />

Es bieten sich mehrere Lösungsmöglichkeiten für die Implementation des Funktionsblocks<br />

an:<br />

// Version (a) - jedoch NACHTEIL??<br />

{<br />

static char str[20];<br />

switch(ord) {<br />

// ... (wie oben) ...<br />

}<br />

return str;<br />

// NICHT abgefangen: ord außerhalb des erlaubten Bereichs<br />

}<br />

// Version (b), besser:<br />

{<br />

char *str;<br />

switch(ord) {<br />

case 1: str="Montag"; break;<br />

// ...<br />

case 7: str="Sonntag"; break;<br />

default: str="TagUnbekannt";<br />

}<br />

return str;<br />

}<br />

// Version (c), ebenfalls besser:<br />

{<br />

switch(ord) {<br />

case 1: return "Montag";<br />

// ...<br />

case 7: return "Sonntag";<br />

}<br />

// Statt "default" ggf. besser hier (zur Befriedigung des Compilers):<br />

return "TagUnbekannt";<br />

}


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 148<br />

12.6 Umgang mit Binärdateien<br />

(12.60) Übb Als Erweiterung zu (Kap. 5.3), wo der Umgang mit Textdateien dargestellt wurde, werden<br />

diese Kenntnisse auf Binärdateien erweitert. Hier sind einige wichtige Besonderheiten<br />

zu beachten, insbesondere muss man die beiden Elementfunktionen write und read anwenden<br />

können (12.63, 12.64). Zum Überladen des Ausgabe- und Eingabeoperators wird die<br />

Ableitungshierachie dargestellt (12.62). Punkt (12.65) erläutert das Positionieren in Dateien<br />

(auch in Textdateien). Ein ausführliches Beispiel (12.66) rundet das Unterkapitel ab. Wenn<br />

man Dateien auf andere Plattformen portieren möchte, ist zudem es wichtig, diesbezügliche<br />

Unterschiede zwischen Text- und Binärdateien zu kennen (12.67).<br />

(12.61) Binärdateien<br />

Beim Umgang mit Dateien muss man zwei Arten Dateien unterscheiden: Textdateien und<br />

Binärdateien. Der Unterschied ist bereits in (5.31) erläutert worden.<br />

Der Umgang mit Textdateien ist in (Kap. 5.3) beschrieben. Hier in diesem Unterkapitel werden<br />

i. w. nur die Besonderheiten für Binärdateien betrachtet.<br />

Wie bei Textdateien geschieht der Umgang in drei Schritten:<br />

• Öffnen der Datei,<br />

• Schreiben und/oder Lesen,<br />

• Schließen der Datei.<br />

(a) Das Öffnen (d. h. Eröffnen des Zugangs zur Datei implizit über das Betriebssystem) geschieht<br />

genau so wie bei Textdateien (5.33), jedoch muss hierbei unbedingt der Modus Binärzugang<br />

(ios::binary) angegeben werden. Da durch die Angabe des Modus die Standardvorgaben<br />

gelöscht werden, sollte die Art des Dateizugriffs (ios::out schreibend, ios::in<br />

lesend) zusätzlich angegeben werden. Die Verknüpfung geschieht über den Operator ” |“<br />

Op12.<br />

Öffnen zum Schreiben (die Streamobjektnamen sind nach C ++-Konvention für Variablennamen<br />

wählbar):<br />

ofstream SchreibStreamName;<br />

SchreibStreamName.open(DateiName,ios::binary|ios::out);<br />

oder (beides in einer Anweisung):<br />

ofstream SchreibStreamName(DateiName,ios::binary|ios::out);<br />

Öffnen zum Lesen:<br />

ifstream LeseStreamName;<br />

LeseStreamName.open(DateiName,ios::binary|ios::in);<br />

oder (beides in einer Anweisung):<br />

ifstream LeseStreamName(DateiName,ios::binary|ios::in);<br />

Weitere Modi und sonstige Informationen, z. B. Lesen und Schreiben gemischt, siehe in<br />

(5.33b).<br />

(b) Zum Schreiben oder Lesen können nicht die üblichen Elementfunktionen genommen werden.<br />

Der Ausgabeoperator > können nur dann benutzt werden,<br />

wenn sie passend überladen sind (11.23c). Dazu ist die Kenntnis der Ableitungshierarchie der<br />

Streams sinnvoll; sie ist im Folgepunkt (12.62) kurz beschrieben.<br />

Am sinnvollsten ist die Benutzung der Elementfunktionen write und read. Näheres ist in<br />

den beiden Punkten (12.63) und (12.64) beschrieben.<br />

(c) Das Schließen (Beenden des Zugriffs mit impliziter Signalisierung an das Betriebssystem,<br />

dass Abschlussarbeiten möglich sind) geschieht wie bei Textdateien mit der Elementfunktion<br />

close (5.34). Implizit wird ein Stream auch durch seinen Destruktoraufruf geschlossen. Sie<br />

sollten sich jedoch angewöhnen, einen Stream explizit zu schließen, sobald der Dateizugriff<br />

nicht mehr benötigt wird.<br />

(12.62) Im folgenden ist die Vererbungshierarchie der Streams abgebildet. Gemäß UML (2.14b ↑↑)<br />

zeigt das hohle Dreieck jeweils auf die Oberklasse(n). Die Hierarchie ist in Wirklichkeit –<br />

insbesondere in C++(neu) – wesentlich komplizierter. Daher ist hier nur ein kleiner Auszug<br />

beschrieben, der für das Selbstschreiben von Funktionen (z. B. Ausgabeoperator- und<br />

Eingabeoperator-Funktionen) hilfreich ist.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 149<br />

❅<br />

ios {abstrakt}<br />

❅ ❅<br />

ostream istream<br />

❅ ❅<br />

iostream<br />

❅<br />

❅<br />

ofstream fstream ifstream<br />

Die Klassen, zu denen die Standard-Streamobjekte cout, cerr, cin gehören (ferner auch<br />

clog, dieses nur in C++(neu)) sind je nach C++(neu) und C++(alt) verschieden:<br />

• C++(neu)<br />

cout, cerr, clog sind vom Typ ostream,<br />

cin ist vom Typ istream.<br />

• C++(alt)<br />

cout, cerr sind vom Typ ostream withassign<br />

(ostream withassign ist von ostream abgeleitet, ähnlich wie ofstream),<br />

cin ist vom Typ istream withassign<br />

(istream withassign ist von istream abgeleitet, ähnlich wie ifstream).<br />

(12.63) Funktionen zum Schreiben<br />

(a) Sinnvoll ist die Benutzung der Elementfunktionen write. Diese ist folgendermaßen durch<br />

die Bibliothek vorgegeben:<br />

ostream& ostream::write(const char* ZeigerAufPuffer, int AnzBytes);<br />

Wirkungsweise:<br />

• Die Funktion liest genau AnzBytes Bytes ( ” Zeichen“) aus dem angegebenen Puffer ab<br />

der Adresse ZeigerAufPuffer und schreibt sie in die Ausgabedatei (Typ ostream oder<br />

abgeleiteter Typ, z. B. ofstream). Hierbei wird jedes Byte gelesen und geschrieben.<br />

Ein eventuell vorhandenes Nullbyte erhält keine Sonderbehandlung, es gilt als normales<br />

Byte.<br />

Für AnzBytes sollten nie eine direkte Zahl angegeben werden, sondern die Größe sollte<br />

immer durch den Compiler mit dem sizeof-Operator Op3j (5.14) berechnet werden.<br />

• Es ist nötig, die Anfangsadresse dessen, was geschrieben werden soll (z. B. Anfangsadresse<br />

eines Objekts, d. h. Zeiger auf Klassentyp), nach const char* zu konvertieren:<br />

Parameter ZeigerAufPuffer. Das ist nicht mit einem static cast (5.64a) möglich, sondern<br />

nur mit dem Operator reinterpret cast, s. Op2k/(10.24Anm4). Ersatzweise kann<br />

auch die ” missbilligte“ Typkonvertierung (char*) bzw. (const char *) genommen<br />

werden, s. Op3i/(5.64b).<br />

• Der Rückgabewert ist der Stream im Zustand nach dem Schreiben. Im Fehlerfall wird<br />

meist badbit gesetzt.<br />

Bsp s. (12.66).<br />

↑↑ Angegeben ist für AnzBytes der Typ aus C++(alt) (int); in C++(neu) ist der Typ streamsize.<br />

(b) Manchmal kann die Funktion<br />

ostream& ostream::flush()<br />

nützlich sein; sie leert den Ausgabepuffer, so dass der Inhalt tatsächlich geschrieben wird.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 150<br />

Sonst wird er Ausgabepuffer automatisch geleert, wenn er voll ist oder wenn der Stream<br />

geschlossen wird. Problematisch kann ein noch teilweise gefüllter Puffer sein, wenn das Programm<br />

wegen eines Fehlers außerplanmäßig abbricht.<br />

(12.64) Funktionen zum Lesen<br />

(a) Die Deklaration der read-Funktion, dem Äquivalent zur write-Funktion:<br />

istream& istream::read(char* ZeigerAufPuffer, int AnzBytes);<br />

Wirkungsweise:<br />

• Die Funktion liest genau AnzBytes Bytes ( Zeichen“) aus der Eingabedatei (Typ<br />

”<br />

istream oder abgeleiteter Typ, z. B. ifstream) und schreibt sie in den angegebenen<br />

Puffer ab der Adresse ZeigerAufPuffer. Hierbei wird jedes Byte gelesen und geschrieben.<br />

Ein eventuell vorhandenes Nullbyte erhält keine Sonderbehandlung, es gilt als normales<br />

Byte.<br />

• Wie schon ähnlich bei der write-Funktion beschrieben, ist es nötig, die Anfangsadresse<br />

dessen, wohin gelesen werden soll (z. B. Anfangsadresse eines Objekts, d. h. Zeiger auf<br />

Klassentyp) nach char* zu konvertieren: Parameter ZeigerAufPuffer. Das ist nur mit<br />

dem Operator reinterpret cast, s. Op2k (10.24Anm4) möglich. Ersatzweise kann auch die<br />

” missbilligte“ Typkonvertierung (char*) genommen werden, s. Op3i/(5.64b).<br />

• Der Rückgabewert ist der Stream im Zustand nach dem Lesen. Im Fehlerfall (Erreichen<br />

des Dateiendes) werden eofbit und failbit gesetzt. Wenn EOF Probleme verursachen<br />

könnte: s. (b).<br />

Bsp s. (12.66).<br />

↑↑ Angegeben ist für AnzBytes der Typ aus C++(alt) (int); in C++(neu) ist der Typ streamsize.<br />

(b) C++(neu) Falls das Erreichen des Dateiendes Probleme verursacht, kann statt der read-<br />

Funktion die Funktion readsome genommen werden:<br />

streamsize istream::readsome(char* ZeigerAufPuffer, streamsize AnzBytes);<br />

Hierbei werden ggf. weniger Zeichen gelesen, falls vorher EOF erreicht wird. Es wird kein<br />

Fehlerbit infolge Erreichen von EOF gesetzt. Der Rückgabewert der Funktion gibt die Anzahl<br />

tatsächlich gelesener Bytes an.<br />

(12.65) Positionieren bei Dateien (Binär- und Textdateien)<br />

Jeder Stream hat jeweils einen Schreib- bzw. Lesezeiger. Streams, die beschrieben und gelesen<br />

werden sollen, haben beides, die Positionen sind jedoch i. a. gleich. Bei Schreibdateien<br />

kann man i. a. nur mit dem Schreibzeiger, bei Lesedateien nur mit dem Lesezeiger arbeiten.<br />

Dieser Zeiger zeigt die nächste zu beschreibende/lesende Dateiposition an. Bei fortlaufendem<br />

Schreiben oder bei fortlaufendem Lesen benötigt man keinen direkten Zugriff auf diese<br />

Zeiger, da sie sich automatisch jeweils um die geschriebenen/gelesenen Bytes vorwärts bewegen.<br />

In manchen Fällen benötigt man jedoch gezielten Zugriff auf eine bestimmte Dateiposition:<br />

(a) Das Abfragen der aktuellen Zeigerposition geschieht mit einer tellx-Elementfunktion<br />

(mit x=p 〈put〉 beim Schreibzeiger, x=g 〈get〉 beim Lesezeiger).<br />

streampos tellp();<br />

streampos tellg();<br />

↑↑ streampos ist ein vorzeichenbehafteter Ganzzahltyp aus C++(alt); in C++(neu) lautet der Typ<br />

ios::pos type. Jedoch ist auch in C++(neu) der Typ streampos i. a. bekannt.<br />

(b) Das Setzen des Zeigers geschieht mit einer der seekx-Elementfunktionen (ebenfalls mit<br />

x=p 〈put〉 beim Schreibzeiger, x=g 〈get〉 beim Lesezeiger).<br />

ostream& seekp(streamoff Offset, ios::seek dir Bezug);<br />

ostream& seekp(streampos Position);<br />

istream& seekg(streamoff Offset, ios::seek dir Bezug);<br />

istream& seekg(streampos Position);<br />

↑↑ Angegeben sind die Typnamen aus C++(alt): streamoff, streampos, ios::seek dir; die<br />

beiden ersten sind vorzeichenbehaftete Ganzzahltypen. In C++(neu) lauten die Typnamen:<br />

ios::off type, ios::pos type, ios::seekdir (letzter: ohne Unterstrich). Jedoch sind<br />

auch in C++(neu) die Typen aus C++(alt) i. a. bekannt.


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 151<br />

(12.66) Beispiel:<br />

Das Positionieren (Setzen des Dateizeigers) kann relativ oder absolut geschehen:<br />

• Funktion mit zwei Parametern: relative Positionierung, und zwar bezogen entweder<br />

auf den Dateianfang, auf die aktuelle Position oder auf das Dateiende, anzugeben durch<br />

den zweiten Parameter in Form von einer der drei Aufzählungskonstanten:<br />

ios::beg 〈begin〉: relativ zum Dateianfang,<br />

ios::cur 〈current〉: relativ zur aktuellen Position,<br />

ios::end 〈end〉: relativ zum Dateiende.<br />

Der erste Parameter der seekx-Funktion gibt den Offset an. Grundsätzlich ist es nicht<br />

erlaubt, vor den Dateianfang oder hinter das Dateiende zu positionieren, daher gilt<br />

Offset 0 bei Bezug auf Dateianfang, Offset 0 bei Dateiende.<br />

• Funktion mit einem Parameter: absolute Positionierung, d. h. das Positionieren<br />

bezogen auf den Dateianfang (wirkt wie ios::beg als zweiter Parameter).<br />

Der Zahlenwert von Position oder Offset bedeutet bei Binärdateien Anzahl Bytes. Bei Textdateien<br />

ist die Interpretation schwieriger, da die Betriebssystem-Darstellung und die C ++-<br />

Darstellung beispielsweise des Zeilenendes unterschiedlich sein kann, siehe (5.31). Bei Textdateien<br />

sollte daher i. a. nur ein vorher gemerkter tellx-Wert als Parameter für absolute<br />

Positionierung genommen werden (Wiederauffinden einer vorher gemerkten Position). Bei<br />

Binärdateien ist das Positionieren problemloser; am besten setzt man die gesuchte Stelle<br />

unter Zuhilfenahme des sizeof-Operators Op3j (5.14), vgl. Beispiel (12.66); dadurch benötigt<br />

man keine Kenntnisse über die interne Repräsentation der Daten.<br />

Bsp Zurück zum Dateianfang bei Lesedatei:<br />

LeseStreamName.seekg(0);<br />

Zum Dateiende bei Schreibdatei:<br />

SchreibStreamName.seekp(0,ios::end);<br />

Positionieren auf letztes Objekt des Typs T beim Lesen (die Typkonversion in einen vorzeichenbehafteten<br />

Ganzzahltyp – hier long – ist notwendig, da sizeof vorzeichenlos ist):<br />

LeseStreamName.seekg(-long(sizeof(T)),ios::end);<br />

Ignorieren des folgenden Objekts des Typs T beim Lesen:<br />

LeseStreamName.seekg(sizeof(T),ios::cur);<br />

// Beispieldatei D12-66.CPP<br />

// (eigentlich wie angedeutet in drei Dateien aufzuteilen)<br />

#include <br />

#include <br />

using namespace std;<br />

// ***** Headerdatei für Klasse Datum *****<br />

class Datum {<br />

int tag, monat, jahr;<br />

public:<br />

Datum(int t=1, int monat=1, int jahr=2000);<br />

void setzDatum(int t, int m, int j);<br />

int gibTag() const {return tag; }<br />

int gibMonat() const {return monat; }<br />

int gibJahr() const {return jahr; }<br />

};<br />

// Überladung des Eingabeoperators für Lesen eines Datums aus<br />

// Textstrom wie z. B. Standardeingabe (Format: 3 Ganzzahlen)<br />

istream& operator>> (istream &ein, Datum &d);<br />

// Überladung des Ausgabeoperators für Schreiben eines Datums in<br />

// Textstrom wie z. B. Standardausgabe<br />

ostream& operator> (ifstream &ein, Datum &d);


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 152<br />

// Überladung des Ausgabeoperators für binäres Schreiben<br />

// eines Datums in eine Datei<br />

ofstream& operator> (istream &ein, Datum &d)<br />

{<br />

int t, m , j;<br />

ein >> t >> m >> j;<br />

d.setzDatum(t,m,j);<br />

return ein;<br />

}<br />

ostream& operator


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 153<br />

}<br />

Datum d;<br />

cout > d;<br />

aus dAnf;<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 154<br />

zessorabhängige Byte-Reihenfolge low/high oder high/low). Daher sind Binärdateien nicht<br />

immer zwischen verschiedenen Plattformen portabel. Legt man größeren Wert auf Portabilität,<br />

empfiehlt sich die Abspeicherung von Zahlen als Text (als Ziffernfolgen), da Textdateien<br />

portabel sind (nach ggf. Umformen von unterschiedlichen Zeilenende-Repräsentationen<br />

beispielsweise zwischen DOS/Windows und Unix). Zu beachten ist hier jedoch unbedingt,<br />

dass zwischen zwei Zahlen mindestens ein Zwischenraumzeichen (<strong>Leer</strong>zeichen, Tab-Zeichen<br />

oder Zeilenendezeichen) gesetzt wird, damit die Zahlen beim Lesen getrennt wahrgenommen<br />

werden.<br />

Das obige Beispiel würde als Textdatei nur ein anderes Überladen des Ausgabeoperators<br />

benötigen. Der für cin überladene Eingabeoperator könnte auch für die Textdatei genommen<br />

werden; jedoch kann ein Extra-Überladen aus Performanzgründen sinnvoll sein (s. Kommentar<br />

im Beispiel). Im folgenden sind die zum Verständnis wichtigsten Teile abgedruckt,<br />

der vollständige Programmtext ist im Internet verfügbar. Die für Dateien überladenen Operatoren<br />

sind hier als Freunde von Datum deklariert, damit sie direkten Zugriff auf die Datenelemente<br />

haben.<br />

// Beispieldatei D12-67.CPP<br />

// (eigentlich wie angedeutet in drei Dateien aufzuteilen)<br />

// ACHTUNG, andere Headerdateien als üblich, Erläuterung s. Skript (11.23d)<br />

#include <br />

#include <br />

// ***** Headerdatei für Klasse Datum *****<br />

class Datum {<br />

// ...<br />

// Überladung des Eingabeoperators für Lesen eines Datums<br />

// aus einer Textdatei<br />

friend ifstream& operator>> (ifstream &ein, Datum &d);<br />

// Überladung des Ausgabeoperators für Schreiben eines Datums<br />

// in eine Textdatei<br />

friend ofstream& operator> (ifstream &ein, Datum &d)<br />

{<br />

// Gültigkeitsprüfung über setzDatum() nicht nötig,<br />

// da gültiges Datum beim Schreiben<br />

ein >> d.tag >> d.monat >> d.jahr;<br />

return ein;<br />

}<br />

ofstream& operator


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 155<br />

}<br />

return aus;<br />

// ***** Programmdatei für Hauptprogramm *****<br />

int main()<br />

{<br />

ofstream aus("DATEN.TXT");<br />

}<br />

// ... (weiter wie D12-66.CPP)<br />

ifstream ein("DATEN.TXT");<br />

// ... (weiter wie D12-66.CPP)<br />

// Suchen und Ausgeben des letzten Datums:<br />

// nicht ohne weiteres möglich, da Textdatei<br />

// ...<br />

In diesem Fall ist die Datei DATEN.TXT mit dem Editor klartextlesbar; sie lautet nach dem<br />

Programmlauf (mit der Eingabezeile ” 2 8 1999“):<br />

5 5 2005<br />

1 1 2000<br />

2 8 1999<br />

12.7 Mehrdimensionale Arrays und zugehörige Zeiger,<br />

Kommandozeilenparameter<br />

(12.70) Übb Die Kenntnisse aus den vorangegangenen Unterkapiteln werden hier auf kompliziertere<br />

Arraystrukturen angewendet. Die Typeigenschaften einschließlich der zum Array kompatiblen<br />

Zeiger scheinen zunächst sehr verwirrend zu sein. Es gelten aber auch hier die schon<br />

vorher eingeführten Typ-Regeln, sie müssen nur konsequent angewendet werden. (12.73) zeigt<br />

als Anwendung daraus, wie Kommandozeilenparameter verarbeitet werden können.<br />

(12.71) Die Typinterpretatationsregeln (10.25) gelten auch bei komplizierteren Typen, z. B. bei mehrdimensionalen<br />

Arrays oder bei Arrays aus Zeigern.<br />

(a) Auch bei komplizierteren Arraytypen werden die Arraybezeichnungen nach (10.26) in Zeiger<br />

auf ihren Komponententyp umgewandelt. Diese Arrays sind demnach zu Variablen, die den<br />

Typ eines Zeigers auf den Komponententyp haben, zuweisungskompatibel.<br />

int a[15][20], *b[10], (*c)[10];<br />

// kompatible Zeigertypen:<br />

int (*aa)[20], **bb; // c ist bereits Zeiger<br />

// Zuweisung und Inkrementierung:<br />

aa=a; ++aa;<br />

bb=b; ++bb;<br />

// c sei geeignet initialisiert, Inkrementierung:<br />

++c;<br />

Erläuterungen:<br />

– Annahme für Speicherplatzberechnungen: sizeof(int) 2 Byte, sizeof(Zeiger) 4 Byte –


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 156<br />

Namen a und aa:<br />

a[...][...] ist int<br />

a[...] ist Array aus 20 int<br />

a ist Array, bestehend aus 15 Arrays, jedes bestehend aus 20 int<br />

– Name wird meist umgewandelt in Zeiger auf Array, bestehend aus 20 int<br />

SpPl: 15 · 20 · 2 Byte<br />

(*aa)[...] ist int<br />

*aa ist Array aus 20 int<br />

aa ist Zeiger auf Array, bestehend aus 20 int<br />

SpPl: 4 Byte<br />

Nach obiger Zuweisung, vor der Inkrementierung:<br />

aa zeigt auf das erste 20-int-Array, d. h. auf a[0]<br />

Nach der Inkrementierung:<br />

aa zeigt auf das nächste 20-int-Array, d. h. auf a[1]<br />

Namen b und bb:<br />

*b[...] ist int<br />

b[...] ist Zeiger auf int<br />

b ist Array, bestehend aus 10 Zeigern auf int<br />

– Name wird meist umgewandelt in Zeiger auf Zeiger auf int<br />

SpPl: 10 · 4 Byte<br />

**bb ist int<br />

*bb ist Zeiger auf int<br />

bb ist Zeiger auf Zeiger auf int<br />

SpPl: 4 Byte<br />

Nach obiger Zuweisung, vor der Inkrementierung:<br />

bb zeigt auf den ersten int-Zeiger, d. h. auf b[0]<br />

Nach der Inkrementierung:<br />

bb zeigt auf den nächsten (zweiten) int-Zeiger, d. h. auf b[1]<br />

Name c:<br />

(*c)[...] ist int<br />

*c ist Array aus 10 int<br />

c ist Zeiger auf Array, bestehend aus 10 int<br />

SpPl: 4 Byte<br />

Nach obiger Inkrementierung (zuvor passende Zuweisung):<br />

c zeigt auf ein nächstes (ggf. fiktives) 10-int-Array<br />

(b) Umwandlungsregel Arraytyp in äquivalenten Zeigertyp:<br />

Aus name[dim] wird *name – wenn aus Vorrangregeln nötig (nämlich bei mindestens einer<br />

weiteren zusätzlichen Arraydimensionierung), dann unbedingt zusätzlich geklammert, d. h.<br />

(*name).<br />

Bsp name[dim] → *name<br />

name[dim][mehrDim] → (*name)[mehrDim]<br />

*name[dim] → **name<br />

*name[dim][mehrDim] → *(*name)[mehrDim]<br />

(c) Die (innerste) Dimension dim kann in folgenden Fällen weggelassen werden, d. h. es genügt<br />

statt dessen ein leeres Klammernpaar [ ]:<br />

• bei einem formalem Parameter, da der Ausdruck dim sowieso ignoriert wird,<br />

• bei einer Array-Deklaration, die keine Definition ist,<br />

• bei einer Array-Definition mit Initialisierern, da der Compiler anhand der Anzahl der<br />

Initialisierer die Dimension selbst setzt.<br />

Nicht erlaubt ist es, evtl. noch weitere vorhandenen Dimensionen (in (b) mehrDim genannt)<br />

wegzulassen; diese werden z. B. für die Zeigerarithmetik benötigt.<br />

Passen bei einer Array-Definition mit Initialisierer die (innerste) Dimension nicht mit der<br />

Anzahl der Initialisierer überein, so gilt folgendes:<br />

• Ist die Dimensionsangabe zu groß, werden fehlende Komponenten mit zugehörigen Nullwerten/Standardkonstruktoraufrufen<br />

aufgefüllt.<br />

• Ist sie zu klein, erzeugt der Compiler einen Fehler.<br />

Beispiel:<br />

char *monatAZ[] = {"Ungültig","Januar","Februar","März",


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 157<br />

"April","Mai","Juni","Juli","August",<br />

"September","Oktober","November","Dezember"<br />

},<br />

monatAA[][10] = {"Ungültig","Januar","Februar","März",<br />

"April","Mai","Juni","Juli","August",<br />

"September","Oktober","November","Dezember"<br />

};<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 158<br />

// Version (c), nur Funktionsblock:<br />

{<br />

while (argc-->0)<br />

cout


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 159<br />

}<br />

double *const *const matrixReservierung(int anzZeil, int anzSpalt)<br />

{<br />

double **matrix = new double *[anzZeil];<br />

for (int zeil=0; zeil


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 160<br />

(12.82) ↑↑<br />

• Auch in C ++: ” low-level“-Programmierung △!<br />

Die wichtigsten Bibliotheksfunktionen in C<br />

– Auswahl sehr eng und willkürlich (i. w. aus K&R2/, und zwar ANSI-C) –<br />

(a) Ein- und Ausgabe <br />

Anm In C ++ gibt es bessere Möglichkeiten, und zwar mit Hilfe der Streams.<br />

FILE ist ein Strukturtyp, in dem die intern benötigten Informationen über die<br />

Dateihandhabung abgelegt werden.<br />

Kennzeichnung, welche Datei gemeint ist: mit Hilfe eines Zeigers vom Typ<br />

FILE* (richtig belegt durch fopen()).<br />

Name Standardeingabeeinheit: stdin (Typ FILE*) - entspricht cin in C++,<br />

Name Standardausgabeeinheit: stdout (Typ FILE*) - entspricht cout in C++.<br />

FILE *fopen(const char *filename, const char *mode); <br />

Öffnen Datei filename (in betriebssystemspezifischer Schreibweise) im Modus:<br />

mode = "r" Lesen (Fehler, wenn nicht vorhanden)<br />

"w" Schreiben (vorher Löschen, wenn vorhanden, sonst Erzeugen)<br />

"a" Anhängen, Schreiben (ggf. Erzeugen)<br />

"r+" Lesen und Schreiben (Fehler, wenn nicht vorhanden)<br />

"w+" Lesen und Schreiben (vorher Löschen, falls vorhanden,<br />

sonst Erzeugen)<br />

"a+" Anhängen, Lesen und Schreiben (ggf. Erzeugen)<br />

Zusätzlich "b" (nicht als 1. Zeichen) möglich ("binär", ohne Übersetzung<br />

CR/LF (DOS) ’\n’ (C), unter UNIX i.a. unnötig), sonst "Textmodus"<br />

(dann ASCII 26 als EOF!)<br />

Rückgabewert: != NULL -> Zeiger auf Struktur, unter der Datei anzusprechen ist,<br />

== NULL -> Fehler<br />

int fclose(FILE *stream);<br />

Schließen, Rückgabewert 0 -> in Ordnung, EOF -> Fehler<br />

int printf(const char *format, ...); <br />

int fprintf(FILE *stream, const char *format, ...); <br />

int sprintf(char *s, const char *format, ...); <br />

Formatierte Ausgabe entsprechend format,<br />

Rückgabewert: >= 0: Anzahl ausgegebene Zeichen (bei sprintf() ohne Nullbyte),<br />

< 0: Fehler<br />

Ziel: stdout (printf), Datei stream (fprintf),<br />

String s mit Nullabschluss (sprintf).<br />

int scanf(const char *format, ...); <br />

int fscanf(FILE *stream, const char *format, ...);<br />

int sscanf(char *s, const char *format, ...);<br />

Formatierte Eingabe entsprechend format,<br />

Rückgabewert: >= 0: Anzahl gelesene/umgewandelte Parameter,<br />

EOF bei Dateiende (zu Anfang) oder Fehler<br />

Quelle: stdin (scanf), Datei stream (fscanf), String s (sscanf).<br />

VORSICHT bei scanf()!<br />

int getchar(void); Quelle: stdin, ggf. als Makro<br />

int getc(FILE *stream); Quelle: Datei stream, ggf. als Makro<br />

int fgetc(FILE *stream); Quelle: Datei stream, als Funktion<br />

Einlesen eines Zeichens,<br />

Rückgabe: (int)(unsigned char)Zeichen oder EOF (bei Dateiende/Fehler)<br />

char *gets(char *s); <br />

Einlesen aus stdin bis einschl. ’\n’, Ablegen in s (’\n’ -> ’\0’)<br />

char *fgets(char *s, int n, FILE *stream); <br />

Einlesen aus stream, Anzahl Zeichen: MIN(n-1, Anz. Zeichen bis einschl. ’\n’),<br />

’\n’ bleibt, falls vorhanden, zusätzl. Nullabschluss<br />

Beide Funkionen: Rückgabewert s -> in Ordnung, NULL -> Dateiende oder Fehler


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 161<br />

int putchar(int c); Ziel: stdout, ggf. als Makro<br />

int putc(int c, FILE *stream);Ziel: Datei stream, ggf. als Makro<br />

int fputc(int c, FILE *stream);Ziel: Datei stream, als Funktion<br />

Ausgabe c als (unsigned char)c<br />

Rückgabewert (int)(unsigned char)c -> in Ordnung, EOF -> Fehler.<br />

int puts(const char *s); Ziel: stdout <br />

int fputs(const char *s, FILE *stream); Ziel: Datei stream<br />

Ausgabe String ohne Nullbyte (bei puts(): zusätzl. ’\n’-Abschluss),<br />

Rückgabewert >= 0 -> in Ordnung, EOF -> Fehler.<br />

size_t fwrite(const void *p,size_t size,size_t nobj,FILE *stream);<br />

size_t fread ( void *p,size_t size,size_t nobj,FILE *stream);<br />

schreibt aus p in stream/liest aus stream in p,<br />

und zwar max. nobj Objekte der Größe size.<br />

Rückgabe: Anzahl der gelesenen/geschriebenen Objekte (ggf. < nobj).<br />

Bei Lesen meist Untersuchung von stream mit feof() bzw. ferror() nötig.<br />

long ftell(FILE *stream);<br />

Rückgabewert: Position Dateizeiger von stream, -1L bei Fehler<br />

int fseek(FILE *stream, long offset, int origin);<br />

Dateizeiger von stream setzen.<br />

Binärdatei: auf offset Zeichen , beginnend ab origin; hierbei:<br />

origin = SEEK_SET Dateianfang, SEEK_CUR augenbl. Stand, SEEK_END Dateiende<br />

Textdatei: erlaubt nur offset==0L (dann obige Konstanten für origin)<br />

oder offset==ftell-Wert (dann origin == SEEK_SET)<br />

Rückgabewert != 0 -> Fehler.<br />

void rewind(FILE *stream);<br />

rewind(fp) ist äquivalent zu fseek(fp,0L,SEEK_SET); clearerr(fp)<br />

int feof(FILE *stream); != 0 -> Dateiende-Indikator ist gesetzt<br />

int ferror(FILE *stream); != 0 -> Fehler-Indikator ist gesetzt<br />

void clearerr(FILE *stream);Löschen Fehler- und EOF-Indikator für stream<br />

int-Variable errno (in deklariert)<br />

kann mehr Information über Fehlerart liefern.<br />

(b) String-Funktionen <br />

Anm Diese Funktionen können manchmal auch in C ++ nützlich sein.<br />

Stringfunktionen mit Beachtung Nullbyte: <br />

Hier Abkürzung für Parameter:<br />

s Typ char *; cs, ct Typ const char *; n Typ size_t; c Typ int (-> char);<br />

bei Vergleichsfunktionen: Argumente behandelt als Arrays von unsigned char<br />

char *strcpy(s,ct); Kopieren<br />

char *strncpy(s,ct,n); dto., max. n Zeich., ggf. Auffüllen mit<br />

’\0’ oder auch kein Nullabschluss (!)<br />

char *strcat(s,ct); Anhängen<br />

char *strncat(s,ct,n); dto., max. n Zeichen, Nullabschluss<br />

int strcmp(cs,ct); Vergleich: 0 -> gleich, < 0 -> cs 0 -> cs>ct<br />

int strncmp(cs,ct,n); dto., max. n Zeichen<br />

size_t strlen(cs); Stringlänge ohne Nullbyte<br />

Ohne Beachtung eines Nullbytes: <br />

Hier Abkürzung für Parameter:<br />

s Typ void *; cs, ct Typ const void *; n Typ size_t; c Typ int (-> char);<br />

bei Vergleichsfunktionen: Argumente behandelt als Arrays von unsigned char<br />

void *memcpy(s,ct,n); Kopieren n Zeichen<br />

void *memmove(s,ct,n); dto., aber richtig auch bei Überlappung der Bereiche<br />

int memcmp(cs,ct,n); Vergleich n Zeichen, Ergebnis wie strcmp()<br />

void *memset(s,c,n); n-mal Zeichen c ab Adresse s setzen<br />

(c) Verschiedene Hilfsfunktionen


c○ Prof. Dr. B. Bartning, HS <strong>Emden</strong>/<strong>Leer</strong> Rumpfskript ” Informatik I/II“ (WS/SS 2010/11) 162<br />

Umwandeln ASCII-Zeichenfolge in Zahl: atof(), atoi(), atol() u.v.a.<br />

Anm In C ++ gibt es wesentlich bessere Funktionen zur Speicherverwaltung mit den Operatoren<br />

new und delete. Auf keinen Fall mit den hier vorgestellten Funktionen mischen!<br />

void *malloc(size_t size); <br />

Allokierung von (mind.) size_t Bytes, Rückgabe Zeiger zu Speicherplatz,<br />

wenn == NULL, dann kein Platz vorhanden.<br />

void *calloc(size_t nobj, size_t size); <br />

Allokierung Platz für nobj Objekte der Größe size, reservierter Platz ist<br />

mit Nullbytes gelöscht, Rückgabe wie malloc()<br />

void *realloc(void *p, size_t size);<br />

Vergrößert oder verkleinert Speicherplatz, zu p gehörig (bereits allokiert),<br />

so dass neue Größe size Bytes; behält bish. Inhalt bei, soweit size nicht<br />

größer, sonst Speicherbereich uninitialisiert, Rückgabe ähnlich malloc()<br />

void free(void *p);<br />

gibt Speicherplatz frei, der vorher auf p mit m/c/realloc() allokiert wurde<br />

char *getenv(const char *name); <br />

Rückgabe: Inhalt Umgebungsvariable name, NULL, wenn nicht vorhanden<br />

void qsort(void *base, size_t n, size_t size,<br />

int (*cmp)(const void *arg1,const void *arg2));<br />

Quicksort-Algorithmus, sortiert das Array base, und zwar die Komponenten<br />

base[0]...base[n-1] (Komponentengröße size) in aufsteigender Reihenfolge.<br />

Sortierkriterium ist die Funktion cmp (muss negativen Wert liefern bei<br />

arg1arg2); diese Funktion<br />

wird intern auf die base-Komponenten angewendet.<br />

void exit(int status); ordentliches Programmende<br />

Funktion ist kaum sinnvoll bei allgemein zu verwendenden Funktionen;<br />

besser: Rückgabe eines Fehlerwertes, wenn z. B. Abbruch einer Operation nötig<br />

(d) Implementationsdefinierte Grenzen , <br />

Grenzen als Konstanten, z.B. max./min. int-, long-, char-, float, double-Wert u.a.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!