Computerpraktikum SS2009 I Einführung in Linux und der

Werbung
Computerpraktikum SS2009
I Einführung in Linux und
der Programmierung in C++
Ein die Veranstaltung ”Computerpraktikum” ergänzendes Skript von
Matthias Knauer, Jörg Benke und Hartmut Jürgens
Bremen, 5. April 2009
ii
iii
Inhaltsverzeichnis
Inhaltsverzeichnis
Inhaltsverzeichnis
iii
Vorwort bzw. Disclaimer
1 Einführung in UNIX / Linux
1.1 Betriebssysteme . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.1 Aufbau eines Computers . . . . . . . . . . . . . . . . . .
1.1.2 UNIX / Linux . . . . . . . . . . . . . . . . . . . . . . .
1.2 Die Etikette im Rechnerpool des Fachbereichsnetzes . . . . . .
1.3 An- und Abmelden beim System . . . . . . . . . . . . . . . . .
1.4 Aufbau von UNIX-Kommandos . . . . . . . . . . . . . . . . . .
1.5 Editoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.6 Drucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.7 Verzeichnisse und Dateien . . . . . . . . . . . . . . . . . . . . .
1.7.1 Grundgerüst des Verzeichnisbaumes . . . . . . . . . . .
1.7.2 Dateien und Pfade . . . . . . . . . . . . . . . . . . . . .
1.7.3 Navigation im Verzeichnisbaum . . . . . . . . . . . . . .
1.7.4 Ausgabe von Inhaltsverzeichnissen . . . . . . . . . . . .
1.7.5 Zugriffsrechte für Dateien und Verzeichnisse . . . . . . .
1.7.6 Numerische und symbolische Form der Rechteänderung
1.8 UNIX-Shells . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.8.1 Sonderzeichen . . . . . . . . . . . . . . . . . . . . . . . .
1.8.2 Shell-Variablen . . . . . . . . . . . . . . . . . . . . . . .
1.8.3 Kommandos . . . . . . . . . . . . . . . . . . . . . . . . .
1.8.4 Platzhalter in Dateinamen . . . . . . . . . . . . . . . . .
1.8.5 Umleitung . . . . . . . . . . . . . . . . . . . . . . . . . .
1.8.6 Vorverarbeitung der Kommandos . . . . . . . . . . . . .
1.9 Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.10 Arbeiten auf entfernten Rechnern . . . . . . . . . . . . . . . . .
1.11 Weitere nützliche Befehle . . . . . . . . . . . . . . . . . . . . .
1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
2
2
5
7
8
10
11
14
15
15
16
17
19
20
21
23
23
24
25
26
27
28
29
31
31
2 Algorithmen
35
2.1 Begriff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.2 Sequenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.3 Auswahl (Selektion) . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.4 Schleifen (Wiederholung, Iteration) . . . . . . . . . . . . . . . . . 38
2.4.1 Maximumsbestimmung (Bsp. zur 2. bzw. 1. Schleifenart)
40
2.4.2 Berechnung von Potenzen xn (Bsp. für Zählschleife) . . . 40
2.5 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.6 Vom Algorithmus zum Programm . . . . . . . . . . . . . . . . . . 42
iv
Inhaltsverzeichnis
3 Einführung in C/C++
3.1 Überblick . . . . . . . . . . . . . . . . . . . . . .
3.1.1 Geschichte . . . . . . . . . . . . . . . . . .
3.1.2 Compiler und Tools . . . . . . . . . . . .
3.1.3 Das nullte und kürzeste C++-Programm
3.2 Hello, world! . . . . . . . . . . . . . . . . . . . .
3.3 Fundamentale Datentypen . . . . . . . . . . . . .
3.3.1 Deklaration . . . . . . . . . . . . . . . . .
3.3.2 Ganzzahltypen . . . . . . . . . . . . . . .
3.3.3 Grundrechenarten . . . . . . . . . . . . .
3.3.4 Bereichsüberschreitung . . . . . . . . . . .
3.3.5 Zuweisungsoperatoren . . . . . . . . . . .
3.3.6 Inkrement- und Dekrementoperatoren . .
3.3.7 Literale . . . . . . . . . . . . . . . . . . .
3.3.8 Bezeichner . . . . . . . . . . . . . . . . .
3.3.9 Fliesskommatypen . . . . . . . . . . . . .
3.3.10 Typumwandlungen . . . . . . . . . . . . .
3.3.11 Konstanten . . . . . . . . . . . . . . . . .
3.4 Ein- und Ausgabe . . . . . . . . . . . . . . . . .
3.4.1 Standardausgabe . . . . . . . . . . . . . .
3.4.2 Standardeingabe . . . . . . . . . . . . . .
3.4.3 Standardfehlerausgabe . . . . . . . . . . .
3.5 Anweisungen zur Auswahl . . . . . . . . . . . . .
3.5.1 Boolesche Werte . . . . . . . . . . . . . .
3.5.2 Logische Operatoren . . . . . . . . . . . .
3.5.3 Relationale Operatoren . . . . . . . . . .
3.5.4 if-Anweisungen . . . . . . . . . . . . . .
3.5.5 Bedingte Ausdrücke mit ?: . . . . . . . .
3.5.6 switch-Anweisungen . . . . . . . . . . . .
3.6 Funktionen . . . . . . . . . . . . . . . . . . . . .
3.6.1 Definition . . . . . . . . . . . . . . . . . .
3.6.2 Rückgabetyp . . . . . . . . . . . . . . . .
3.6.3 Funktionsparameter . . . . . . . . . . . .
3.6.4 Call by value . . . . . . . . . . . . . . . .
3.6.5 Call by reference . . . . . . . . . . . . . .
3.6.6 Gültigkeitsbereich von Variablen . . . . .
3.6.7 Rekursive Funktionen . . . . . . . . . . .
3.6.8 Mathematische Funktionen . . . . . . . .
3.7 Schleifen . . . . . . . . . . . . . . . . . . . . . . .
3.7.1 while-Schleifen . . . . . . . . . . . . . . .
3.7.2 for-Schleifen . . . . . . . . . . . . . . . .
3.7.3 Sprungbefehle break und continue . . .
3.7.4 Zusammenfassung Schleifen . . . . . . . .
3.7.5 Der Sprungbefehl goto . . . . . . . . . . .
3.8 Felder . . . . . . . . . . . . . . . . . . . . . . . .
3.8.1 Felder als Funktionsparameter . . . . . .
3.9 Präprozessoranweisungen . . . . . . . . . . . . .
3.9.1 #define . . . . . . . . . . . . . . . . . . .
3.9.2 #include . . . . . . . . . . . . . . . . . .
3.10 Formatierte Ausgabe . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
45
45
47
49
51
53
54
55
56
57
57
58
58
58
59
60
61
62
62
63
64
64
64
65
65
67
69
69
70
70
72
73
73
74
75
77
78
79
80
81
83
84
85
86
87
89
89
90
90
Programmverzeichnis
v
Bit-Operatoren . . . . . . . . . . . . . . . . . . . . . . . .
Formatierung über Elementfunktionen . . . . . . . . . . .
Manipulatoren . . . . . . . . . . . . . . . . . . . . . . . .
Beispiel: Berechnung von π nach Leibniz . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Zeigervariablen . . . . . . . . . . . . . . . . . . . . . . . .
Speicher reservieren mit new . . . . . . . . . . . . . . . .
Speicher freigeben mit delete . . . . . . . . . . . . . . .
Zugriff auf Zeigerwerte . . . . . . . . . . . . . . . . . . . .
Benutzung des Referenzierungs- und Dereferenzierungsoperators . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.11.6 Zeiger als Funktionsparameter . . . . . . . . . . . . . . .
3.11.7 Weitere Zeiger-Typen . . . . . . . . . . . . . . . . . . . .
3.11.8 Zeigerarithmetik . . . . . . . . . . . . . . . . . . . . . . .
3.11.9 Sichere Allozierung . . . . . . . . . . . . . . . . . . . . . .
3.12 Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.12.1 Der Datentyp char . . . . . . . . . . . . . . . . . . . . . .
3.12.2 Die Funktionen von ctype.h . . . . . . . . . . . . . . . .
3.12.3 Steuerzeichen . . . . . . . . . . . . . . . . . . . . . . . . .
3.12.4 Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . .
3.12.5 Ein- und Ausgabe von Zeichenketten . . . . . . . . . . . .
3.12.6 Die Funktionen von string.h . . . . . . . . . . . . . . .
3.12.7 Argumente für die Funktion main . . . . . . . . . . . . . .
3.13 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.13.1 Konstruktoren für Strings . . . . . . . . . . . . . . . . . .
3.13.2 Verändern von Strings . . . . . . . . . . . . . . . . . . . .
3.13.3 Informationen über Strings . . . . . . . . . . . . . . . . .
3.13.4 Vergleiche und Tests . . . . . . . . . . . . . . . . . . . . .
3.13.5 Umwandeln in Zeichen . . . . . . . . . . . . . . . . . . . .
3.13.6 Suchen in Strings . . . . . . . . . . . . . . . . . . . . . . .
3.13.7 Sonstiges . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.14 Umgang mit Dateien . . . . . . . . . . . . . . . . . . . . . . . . .
90
91
94
95
96
96
97
98
99
3.10.1
3.10.2
3.10.3
3.10.4
3.11 Zeiger
3.11.1
3.11.2
3.11.3
3.11.4
3.11.5
100
100
102
105
106
107
107
108
109
109
111
111
112
113
114
114
116
116
117
118
118
119
A Header
122
A.1 Die Funktionen von ctype.h . . . . . . . . . . . . . . . . . . . . 122
A.2 Die Funktionen von string.h . . . . . . . . . . . . . . . . . . . . 123
A.3 Mathematische Funktionen . . . . . . . . . . . . . . . . . . . . . 124
B Kompatibilitäten zu C
B.1 Ein-/Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . .
B.1.1 Formatierungsanweisungen für Ganzzahlen . . . .
B.1.2 Formatierungsanweisungen für Fließkommazahlen .
B.1.3 Eingabe mit scanf . . . . . . . . . . . . . . . . . .
B.1.4 Ein-/Ausgabe in C-Strings . . . . . . . . . . . . .
B.1.5 Einzelne Zeichen . . . . . . . . . . . . . . . . . . .
B.2 Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . .
Programmverzeichnis
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
126
126
127
129
131
132
132
132
134
vi
Programmverzeichnis
1
Vorwort bzw. Disclaimer
Dieses Skript wurde in seiner erster Fassung von Matthias Knauer erstellt. Es
begleitete den ersten Teil einer zweisemestriegen Blockveranstaltung Rechnerpraktikum I und II. Seit 2006 wurde die Veranstaltung schrittweise überarbeitet
und weiter entwickelt. 2009 wird sie erstmals als eine im laufenden Semester gehaltene Vorlesung unter dem Titel ”Computerpraktikum” gehalten.
Die Vorlesung orientiert sich in diesem Semester an einer ca. 350 seitigen Präsentation. Diese Unterlagen werden den Studierenden im Laufe der Veranstaltung als aktuelles Referenzmaterial sukzessive zur Verfügung gestellt. Das Skript
kann als zusätzliche Information herangezogen werden. Es entspricht aber nicht
immer dem Verlauf der Vorlesung, die den neuen Gegebenheiten angepasst werden musste.
Obwohl die Authoren bei der Erstellung grosse Sorgfalt haben walten lassen,
können gerade aufgrund der sukzessiven Anpassung Fehler im Skript verblieben
sein. Für diese und eventuell daraus entstehende Folgen können wir Autoren
weder eine juristische noch irgendeine Haftung übernehmen.
Bremen, 5. April 2009
2
Kapitel 1: Einführung in UNIX / Linux
1 Einführung
Linux
in
UNIX
/
1.1 Betriebssysteme
1.1.1
Aufbau eines Computers
Hardwarekomponenten eines Computer-Systems (ganz grob)
Hardware ist der Oberbegriff für die maschinentechnische Ausrüstung eines
Computersystems. Dazu gehören Prozessor und Arbeitsspeicher (inkl. Cache)
und seine (internen und externen) Peripheriegeräte (z.B. Festplatte (intern oder
extern), Grafikkarte (intern) oder Drucker (extern), Monitor (extern)). Vereinfacht gesagt gehört alles, was angefasst werden kann, zur Hardware.
• Ein Computer besteht grundsätzlich aus Prozessor (CPU), Arbeitsspeicher
(RAM) und Peripherie, welche über ein Bus-System (Leitungssytem) miteinander verbunden sind, um Daten und Steuersignale zu übertragen.
• Wird ein Programm gestartet, so wird dieses z.B. von der Festplatte in
den Arbeitsspeicher geladen und durch die CPU ausgeführt (ergibt Prozess, d.h. ein Programm in Ausführung). Ebenfalls werden dort die Daten
abgespeichert, die die Programme erzeugen (z.B. Texte).
• Ein Programm besteht aus einer endlichen Folge von Anweisungen (Befehlen) und strukturierten Daten, welche im Arbeitsspeicher abgelegt werden.
Der Prozessor holt sich die Anweisungen des Programms nacheinander
aus dem Speicher, verarbeitet sie, und legt die evtl. erzeugten Ergebnisse
des Anweisungsschrittes wieder im Speicher ab.
1.1 Betriebssysteme
3
• Der Inhalt des Arbeitsspeichers ist flüchtig, d.h. nach dem Abschalten des
Computers geht der Inhalt des Arbeitsspeichers komplett verloren. Somit
wichtig: Speicherung von weiterhin benötigten Daten und Programmen auf
externe Speichermedien (Festplatte, CD/DVD, ...).
• Der interne Speicher (d.h. der Arbeitsspeicher) ist in Speicherzellen organisiert, die man sich nacheinander (sequentiell) aneinandergereiht vorstellen kann (Numerierung nacheinander von 0 aufwärts). Der Zugriff auf
die Speicherzellen erfolgt über diese eindeutige Nummer (Adresse).
• Die Peripherie bzw. die Peripheriegeräte sind alle Komponenten bzw. Geräte, die sich ausserhalb der Zentraleinheit und des Arbeitsspeicher eines
Computers befinden, z.B. Ein- und Ausgabegeräte, Karten, etc.
Zusammenfassung wichtiger Hardwarekomponenten eines Computers:
• Prozessor (engl. CPU = Central Processing Unit)
• Interne Speicher: RAM, Cache, . . .
• Externe Speicher: Festplatte, CD/DVD, Diskette, Speicher-Stick, . . .
• Eingabegeräte: Tastatur, Maus, Touchpad, Scanner, . . .
• Ausgabegeräte: Bildschirm, Drucker, . . .
• Karten (Grafik, Sound, Netzwerk, . . . )
• Gehäuse, Stromversorgung, Mainboard
• ...
Weitergehende Literatur zu diesem Thema im Netz:
• http://www.wikipedia.de (Stichwort Computer, . . . )
• http://quantenleser.de/computerbuch/ (Einführende Behandlung, ca.
35 S.! Gut, um einen Überblick zu bekommen.)
• http://www.thgweb.de/lexikon/Hauptseite (Texte teilweise aus Wikipedia übernommen)
Software eines Computer-Systems
Der Begriff Software bezeichnet alle nichtphysischen Funktionsbestandteile
eines Computers bzw. eines jeden technischen Gegenstandes, der mindestens
einen Mikroprozessor enthält. Dies umfasst vor allem Computerprogramme
sowie die zur Verwendung mit Computerprogrammen bestimmten Daten (. . . )
(Wikipedia)
Beispiele:
• Betriebssysteme: Unix / Linux, Windows XP, . . .
• Büroprogramme: MS Office (Word, . . . ), StarOffice (Writer, . . . )
4
Kapitel 1: Einführung in UNIX / Linux
• Webbrowser: Internet Explorer, Firefox, . . .
• ...
Unterscheidungsformen:
• Systemsoftware (Computerprogramme, die für den Betrieb des Computers
nützliche Funktionen ausführen, d.h. das Betriebssystem, systemnahe Software (z.B. Datenbankverwaltungswerkzeuge) und Übersetzungsprogramme
(siehe unten)
• Anwendungsprogramme (Computerprogramme, die eine für den Anwender
nützliche Funktion ausführen, z.B. Büro-Programme, Datenbanken, CADProgramme, Buchhaltungsprogramme)
• Compiler, Interpreter (Computerprogramme, die ein in einer Programmiersprache geschriebenes Programm (z.B. C), in eine für den Computer
verständliche „Sprache“ übersetzen, d.h. Übersetzungsprogramme)
All dies ist für uns nur sinnvoll nutzbar, weil es ein Betriebssystem gibt, das
uns viel Arbeit abnimmt, z.B.
• Tastatureingabe erkennen und weiterleiten,
• Programme starten, deren ordnungsgemäßen Ablauf kontrollieren (z.B.
dass sich 2 „parallel“ laufende Programme ihre Speicherinhalte nicht gegenseitig überschreiben) und das Programm beenden,
• Dateien speichern und verwalten,
• Verteilung der Prozessorzeit an die laufenden Prozesse beim Multitasking
(Scheduling),
• Synchronisation von Prozessen, um Zugriffskonflikte und Prozessorwartezeiten zu vermeiden.
Somit bildet das Betriebssystem die Grundlage dafür, dass ein Benutzer den
Rechner überhaupt sinnvoll benutzen kann, indem es Dienste zur Verfügung
stellt und bestimmte Verwaltungsaufgaben übernimmt. Es verwaltet Betriebsmittel wie Speicher, Ein- und Ausgabegeräte und steuert die Ausführung von
Programmen (Prozessverwaltung).
Ein Betriebssystem besteht grundsätzlich aus 3 bzw. 4 Komponenten:
• Kern (engl. kernel), d.h. der Teil des Betriebssystems, der mit der Hardware kommuniziert und grundlegende Dienste bereit stellt, z.B. Dateien
öffnen oder schließen, Dateien schreiben oder lesen, Speicher- und Prozessverwaltung. Der Kern des Betriebssystems dient somit hauptsächlich der
Verwaltung von Programmen, Dateien, dem Speicher, Festplatten, Netzwerken, etc.
• Dienstprogramme (engl. utilities), wie z.B. UNIX-Kommandos zum Anzeigen von Verzeichnissen oder Dateiinhalten.
1.1 Betriebssysteme
5
• Shell, d.h. eine Benutzerschnittstelle, die Befehle bzw. Kommandos entgegennimmt (über Tastatur), diese mittels eines Kommandointerpreters ausführt und eine evtl. vorhandene Ausgabe anzeigt. Es gibt mehrere Shells,
wobei die bekanntesten die Bourne-Again-shell ( bash) oder die Korn-Shell
sind.
• Manche Autoren zählen auch noch die grafische Benutzeroberfläche zum
Betriebssystem (später).
Beispiele für Betriebssysteme
• UNIX und seine Derivate (BSD, Linux, FreeBSD, Solaris, Darwin / Mac
OS X, . . . )
• MS-DOS, Windows XP, Windows 2000, . . .
• MacOS (eigenständiges System bis Version 9), AmigaOS, TOS (Atari),
...
1.1.2
UNIX / Linux
Merkmale
• Programme können (scheinbar) gleichzeitig ablaufen (MultitaskingSystem)
• Mehrbenutzerbetrieb, netzwerkfähig (Multiuser-System)
• Rechnerunabhängigkeit, Portabilität (d.h. Unix kann leicht auf andere
Rechner angepasst werden, da es modular aufgebaut und größtenteils in
C geschrieben ist)
• hohe Stabilität und Sicherheit
• leistungsfähige Benutzerschnittstellen (Shells)
Historische Entwicklung
∼1970 erste UNIX-Version, BellLabs (Entwicklungszentrum von AT&T) u.a
Thompson, Ritchie
1973 Neuimplentierung des Unix-Kerns in C
∼1975 Weiterentwicklungen von Unix, u.a. University of Berkeley (eigene UnixVersion: Berkeley Software Distribution-Unix bzw. BSD-Unix)
1983 UNIX V5 (AT&T-Unix-Version), kommerzielle Version
ab 1988 POSIX-Standardisierung (Portable Operating System Interface), d.h.
Festlegung der Mindestanforderungen an ein UNIX-System (weitgehend
abgeschlossen in 2001)
6
Kapitel 1: Einführung in UNIX / Linux
ab1991 Entwicklung von LINUX als UNIX-Derivat für Intel386-Prozessor, Linus
Torvalds u.v.a. im Rahmen der GNU/GPL, d.h. Open Source.
∼1993 FreeBSD (alternative Open Souce Entwicklung).
∼1999 Darwin (Apple Open Source Release of Unix u.a. FreeBSD basiert)
Das Skript betrachtet ursprünglich primär Linux:
• im Rahmen der Fedora-Distribution,
• mit der grafischen Benutzeroberfläche KDE,
wobei die einzelnen Distributionen (z.B. Fedora/Redhat, SUSE/Novell, Ubuntu/Canonical, Debian) alle Linux enthalten, und entsprechende zusätzliche
(meist freie) Software, abhängig davon, für welchen Zweck sie zusammengestellt
wurden.
In 2009 wird das Praktikum auf Apple iMac durchgefuehrt (siehe nachfolgend).
Grafische Benutzeroberfläche
Früher kommunizierte der Benutzer über Kommandozeile mit dem System,
z.B. MS-DOS, C64. Heute existieren eine Reihe grafischer Benutzeroberflächen
(GUIs) für unterschiedlichste Systeme. Speziell für Linux sind dies z.B. KDE
oder Gnome, welche prinzipiell ähnlich zu Windows aufgebaut sind bzw. ein
ähnliches Verhalten aufweisen.
Grafische Benutzeroberflächen sollen dem Benutzer einen intuitiven Umgang mit dem Computer ermöglichen. Kernbestandteil dieser grafischen
Systeme ist das Fenster, d.h. ein rechteckiger Bereich, in dem z.B. Ausgaben
dargestellt werden. Insgesamt wird der Bildschirm als Schreibtisch interpretiert,
auf dem die Fenster dann übereinander gelegt werden, in Analogie zu einem
Stapel Papier, auf dem Blätter übereinander liegen. Mit Hilfe von Maus und
Tastatur können dann z.B. Eingaben gemacht oder Fenster in ihrer Größe
verändert, verschoben oder geschlossen werden. Icons (Piktogramme) symbolisieren bestimmte Aktionen, die der Benutzer bei Auswahl durchführen kann,
wie z.B. Start eines Programms oder Aufklappen eines Menüs. Jede grafische
Benutzeroberfläche besitzt ein eigenes „Look & Feel “, d.h. sie bestimmt das
Aussehen und das Verhalten der Elemente.
Grundlegende Elemente bzw. Eigenschaften einer grafischen Oberfläche
sind:
• Navigation mit der Maus, (!!)
• Icons,
• verschiedene Fenster (für Editor, E-Mail, WWW, Terminal, . . . ),
• Fenster verschieben, vergrößern, schließen, . . .
• Pulldown-Menüs.
7
1.2 Die Etikette im Rechnerpool des Fachbereichsnetzes
Mac OS X
Das Betriebssystem Mac OS X ist im Kern ein Unix Betriebssystem. Der Kern
liegt als OpenSource Variante unter dem Namen Darwin vor und wurde von
FreeBSD abgeleitet. Dem Benutzer tritt Mac OS X aufgrund seiner ausgeprägten Graphischen Benutzeroberfläche nicht unmittelbar als Unix Betriebssystem
gegenüber. Offenbar wird dieses z.B. erst, wenn man sich mit Hilfe des ”Terminals” oder der ”Console” auf die Unix Komandozeilen Ebene begibt. Deutlich
wird dieses auch bei der Softwareentwicklung oder bei Verwendung von Programmen, die das X11 Window Management verwenden.
Mac OS X ist heute die am weitesten verbreitete Unix Variante (Betriebssystem
Marktanteile Anfang 2009: Windows ca. 88%, OS X ca. 9%, Linux knapp 1%,
akuelle Quelle http : //marketshare.hitslink.com )
1.2 Die Etikette im Rechnerpool
Fachbereichsnetzes
des
• Transponderkarte beim Betreten und Verlassen der Ebene 0 benutzen (!).
• Niemals einen Computer selbst ausschalten oder neustarten (!!).
• Fehler oder Probleme an das Technik-Team melden. Dringende Fehler sofort (persönlich), alles andere per E-Mail an
[email protected].
• Sorgsam mit Hardware, Software und Netzlast umgehen.
• Keine personenbezogenen Daten verarbeiten oder ausspionieren.
• Pfiffige Passwörter wählen (z.B. die Anfangsbuchstaben von einem Gedicht, ...) und geheimhalten.
• Den Bildschirm nicht länger als ca. 10 Minuten blockieren.
• Auf Druckaufträge achten und lange Druckaufträge außerhalb der Stoßzeiten starten.
• Nur einen Arbeitsplatzrechner belegen.
• Ruhe bewahren, die Nachbarn möchten sich auch auf ihre eigenen Aufgaben
konzentrieren.
• Abmelden nicht vergessen (!!).
Weitere Informationen:
http://www.informatik.uni-bremen.de/t/info/
8
Kapitel 1: Einführung in UNIX / Linux
1.3 An- und Abmelden beim System
Zu Beginn jeder Sitzung muss man sich mit seiner Benutzerkennung (account,
login name) und seinem Passwort anmelden. Falls man noch keine Benutzerkennung besitzt, so holen !!
Folgendes gilt für die Linux System:
Nach der Anmeldung erscheint dann ein Hinweis auf dem Bildschirm, wie die
graphische Oberfläche gestartet werden kann:
Commands for starting the desktops:
X.kde
X.fvwm
X.gnome
$
Folgende GUIs verbergen sich dahinter:
X.kde
K Desktop Environment
X.fvwm
pure X11
X.gnome
Gnome Desktop
Für unsere Zwecke lautet die Eingabe dann
$ X.kde
(Bem: Das Dollarzeichen bezeichnet das Prompt, welches anzeigt, dass die Shell
auf die Eingabe eines Kommandos wartet. Dieses Prompt kann unterschiedlich aussehen !)
Zum Beenden verlässt man zunächst KDE (mit rechter Maustaste auf Desktop
klicken und Kontextmenüpunkt „Logout "Benutzername"“ auswählen oder „KMenü->Logout. . . “ auswählen. Das Icon des K-Menüs findet sich unten-links
auf dem Bildschirm). Damit ist man aber noch nicht endgültig abgemeldet
(man hat nur die grafische Oberfläche beendet). Man muß anschließend noch
die UNIX-Sitzung verlassen:
$ exit
Am besten ist es, KDE gleich so zu starten:
$ X.kde ; exit
Dann wird nach dem Beenden von KDE auch gleich die ganze Sitzung beendet.
Abmelden ist sehr wichtig, da eine Nichtabmeldung zur Folge hat, dass andere
Benutzer z.B. auf deine Daten zugreifen oder deine Mails lesen können!
Rechner in den Praktikumsräumen nach dem ausloggen aber niemals
ausschalten !
1.3 An- und Abmelden beim System
9
Terminal - Eingabe von Kommandozeilen
Nachdem eine graphische Benutzerschnittstelle gestartet wurde, ist die Eingabe
von Unixbefehle nur noch in speziellen Fenstern möglich (in der sogenannten
"Console" oder dem "Terminal"). Im K-Menü (unten links auf dem Bildschirm
können Sie in dem Untermenü " System" das Programm "Terminal" auswählen.
Das Fenster, das hierauf geöffnet wird, erlaubt die Eingabe von Unix Kommandos.
Auf dem Mac finden Sie das Programm in den Ordner Hilfsprogramme (Utilities), der unter Programme (Applications) zu finden ist.
Bildschirm sperren
Wenn man den Rechner nur kurzzeitig (!) verlässt (d.h. nicht länger als 10 - 15
Minuten), sollte man ihn zur Sicherheit sperren: Mausklick rechts auf Desktop
und Menüpunkt „Lock Screen“ im Kontextmenü auswählen oder „K-Menü->Lock
Screen“ bzw. „K-Menü->Lock Session“.
Passwörter (Achtung: nicht mehr aktuell)
Passwörter dienen der eigenen Sicherheit und der des Rechnernetzes, deshalb
• nicht vergessen,
• nicht aufschreiben,
• nicht weitergeben (besonders nicht per E-Mail).
Die Sicherheit wird regelmäßig überprüft und Passwörter ggf. gesperrt, falls
diese zu leicht zu knacken sind (z.B. Namen oder Wörter aus dem Lexikon).
Zum Entsperren an die Technischen Mitarbeiter wenden.
Zum Ändern seines Passworts gibt es einen UNIX-Befehl:
$ yppasswd
Changing NIS account information for [...] on bob.
Please enter old password:
Changing NIS password for [...] on bob.
Please enter new password:
Please retype new password:
The NIS password has been changed on bob.
$
Zunächst wird das alte Passwort eingegeben, dann zweimal das neue.
Ein paar Tipps zur Wahl des Passworts:
• Mindestens 8 Zeichen, davon mindestens 2 Buchstaben und 2 Nichtbuchstaben
• Keine Namen, Geburtsdaten, Telefonnummern u.ä.
• Z.B. Anfangsbuchstaben eines Gedichts/Liedes plus ein Sonderzeichen
10
Kapitel 1: Einführung in UNIX / Linux
1.4 Aufbau von UNIX-Kommandos
Unter UNIX kommuniziert der Benutzer mit dem Computer über Kommandos,
die in einem Terminal-Fenster (genauer in einer Shell, siehe Abschnitt 1.8)
eingegeben werden. Ein Terminal öffnet man über das KDE-Menü bzw. über
ein Icon im Kicker, man schließt es durch Eingabe von exit .
Kommandos besitzen unterschiedliche Parameter und diverse Optionen.
In der Regel folgen Kommandoaufrufe diesem Schema (wobei dieses nicht
immer einheitlich ist):
Kommando [-Optionen] [Argumente]
Jedes Kommando wird durch Drücken der <RETURN> oder <ENTER>-Taste
abgschlossen. Leerzeichen und Groß- und Kleinschreibung müssen beachtet werden! (Kein Windows!) Somit sollte man auch bei der Passworteingabe bzw. -änderung aufpassen, dass nicht ungewollt die Caps Lock-Taste
(Feststell-Taste) aktiviert ist, so dass das Passwort nur aus Großbuchstaben besteht.
CTRL + c beendet die laufende Kommandoausführung, CTRL + d beendet laufende Prozesse (auf einer deutschen Tastatur steht anstatt CTRL ein Strg (Abk.
für Steuerung)).
Manual Pages
Wie ein Kommando aufgerufen wird, ist in den Manual Pages bzw. Manpages
ausführlich dokumentiert:
man Kommandoname
wobei man das Kommando bezeichnet und Kommandoname das Argument.
Zum Blättern in der Dokumentation benutzt man RETURN (1 Zeile vorwärts) ,
SPACE (1 Seite vorwärts) und b (zurück zum Anfang); mit q verlässt man
sie.
apropos Stichwort
Kommandos auflisten, die zum Stichwort
passen
Die Manual Pages der verschieden Kommandos finden sich auch im Internet
unter http://man.linuxquestions.org, und können so mit dem Browser angesehen werden. Allerdings kann sich die dort zu findende Beschreibung in einigen Fällen von der unterscheiden, die auf dem System zu finden ist. Massgebend
ist die Beschreibung, die auf dem System vorhanden ist, also mit dem Befehl
"man" abgefragt werden kann.
Info System
Viele Befehle sind auch ausführlich im „Info-Format“ dokumentiert. Mit
info Kommandoname öffnet sich ein einfacher Textbrowser, mit dem man auf
die jeweilige Hypertext-Hilfe zugreifen kann. Mit den Cursortasten (Zeile vor /
1.5 Editoren
11
zurück), SPACE (eine Seite vorwärts), PageUp , PageDown (Seite vor / zurück) navigiert man auf der Seite
Links zu verwandten Themen sind mit * Linkname:: markiert. Diese lassen
sich per Cursortasten ansteuern und dann per <RETURN> auswählen.
RETURN
h
u
n
p
Link folgen
Hilfe zu info
Up: Ein Level nach oben.
Next: Nächste Seite (auf dieser Ebene)
Previous: Vorherige Seite (auf dieser Ebene)
q
Ende
Andere Benutzer
who
Wer arbeitet am gleichen Rechner wie ich?
w
ebenso, etwas ausführlicher
finger name
Suche nach Login- und Benutzernamen
last
Wer war zuletzt an diesem Rechner?
1.5 Editoren
Um eine Text-Datei (z.B. den Quelltext eines Programms) zu erzeugen, zu bearbeiten und zu speichern, benötigt man einen Editor. Dabei geht es um einfache
Dateien, keine Texte mit unterschiedlichen Schriftgrößen und -arten, Seitennumerierung, Kopf/Fuß-Zeile, eingebundenen Bildern u.ä. – dafür benutzt man
LATEX, Word oder andere Programme.
Alle Texteditoren verfügen über viele komfortable Funktionen: Suchen und Ersetzen, Ausschneiden und Einfügen, Undo, Syntaxhighlighting (abhängig von der
benutzten Sprache), . . . Diese Funktionen lassen sich über die Menüleiste oder
mittels Tastaturkürzeln (sog. Short-Cuts) ansprechen.
Unter UNIX stehen für jeden Geschmack verschiedene Editoren zur Verfügung,
z.B.
12
Kapitel 1: Einführung in UNIX / Linux
nedit
Der nedit ist ein vielseitiger Text-Editor, der gut für die Eingabe von Computer Programmen geeignet ist. Hier eine kurze Übersicht über seine Bedienung(selemente):
Start
Ende
Neue Datei erstellen
Datei öffnen
Speichern
Undo
Cut
Copy
Paste
Suchen
Suchen fortsetzen
Suchen + Ersetzen
K-Menü->Debian->Apps->Editors->NEdit
(oder nedit & aus der Shell bzw. Konsole
heraus)
Menü „File/Exit“
(oder CTRL + q , d.h. beide Tasten gleichzeitig
gedrückt halten)
Menü „File/New “ (oder CTRL + n )
Menü „File/Open...“ (oder CTRL + o ) und
Datei im Minibuffer (untere Zeile) eingeben
Menü „File/Save“ (oder CTRL + s )
Menü „Edit/Undo“ (oder CTRL + z )
Menü „Edit/Cut“ (oder CTRL + x )
Menü „Edit/Copy“ (oder CTRL + c )
Menü „Edit/Paste“ (oder CTRL + v )
Menü „Search/Find...“ (oder CTRL + F )
Menü „Search/Find again “ (oder CTRL + G )
Menü „ Replace ...“ (oder CTRL + r )
Eine ausführlichere Einleitung findet sich unter dem Menüpunkt Help.
Hinweis: nedit ist unter Mac OS X nicht installiert. Es könnte zwar installiert
werden, aber der Code ist offenbar gut 5 Jahre alt und enthaelt (zumindest in
der OS X Version einge ärgerliche Fehler. Alternativ können Sie ”TextEdit” oder
den ”Xcode” Editor (speziell für Programm Code) verwenden.
kwrite
Der kwrite ist ein einfacher Text-Editor, der ebenfalls recht gut für die Eingabe von Computer Programmen geeignet ist. Es ist gut in die KDE-Umgebung
1.5 Editoren
13
integriert und wird z.B. gestartet, wenn man eine C++ Datei in der KDE-GUI
doppelklickt. Das Menü ist ähnlich wie bei nedit aufgebaut.
emacs
emacs ist ein sehr mächtiger Editor, ausgestattet mit extrem vielen Befehlen
und einer eingebauten Programmiersprache (Emacs Lisp), mit der eigene Erweiterungen definiert werden können.
Wichtige Befehle finden sich in der folgenden Auflistung, wobei in der EmacsHilfe das C für Ctrl und M für Alt steht.
Start
Ende
Datei öffnen
Neue Datei erstellen
Speichern
Speichern als ...
Undo
Cut
Copy
Paste
einmaliges Suchen
nochmalige Suche
Suchen und Ersetzen
Zeile ab Cursor bis
Zeilenende löschen
K-Menü->Debian->Apps->Editors->Emacs
(oder emacs & aus der Shell bzw. Konsole heraus)
Menü „File/Exit Emacs“
(oder CTRL + q CTRL + c ,
d.h. beide Tasten gleichzeitig nacheinander
gedrückt halten)
Menü „File/Open...“ (oder CTRL + o ),
dann Datei in der Liste auswählen
Siehe Datei öffnen.
Menü „File/Save (current buffer)“
(oder CTRL + x CTRL + s )
Menü „File/Save Buffer As“
(oder CTRL + x CTRL + w )
Menü „Edit/Undo“ (oder CTRL + _ )
Menü „Edit/Cut“ (oder CTRL + w )
Menü „Edit/Copy“ (oder M + w )
Menü „Edit/Paste“ (oder CTRL + y )
Menü „Edit/Search/Search...“
Menü „Edit/Search/Repeat Search...“
Menü „Edit/Search/Replace “ (oder M+% )
CTRL+k )
14
Kapitel 1: Einführung in UNIX / Linux
Eine extrem ausführliche Einleitung findet sich wiederum unter dem Menüpunkt
Help.
Hinweis: Auf den Macs sollte die Emacs Version Aquamacs installiert sein.
Diese Version bietet eine volle Intergration in die Graphische Benutzeroberfläche
und ist gerade für gelegentliche Nutzer leichter zu nutzen.
1.6 Drucken
Zum Ausdrucken von Dateien steht unter UNIX der Befehl lpr zur Verfügung.
lpr dateiname
Pro Semester hat jeder Student nur eine bestimmte Anzahl von freien Druckseiten, zurzeit 300. Um sein Druckkontingent ( Quota) nicht zu stark zu belasten,
kann man mehrere Seiten einer Postskriptdatei verkleinert zusammenlegen, wobei zahl die Anzahl der Seiten ist, die auf einer Seite zusammengelegt werden
sollen.
psnup -n zahl datei.ps > datei_neu.ps
In Ebene 0 stehen die Drucker lw0, lw2, lw3 (farbig) zur Verfügung. Standardmäßig wird lw0 benutzt, einen anderen Drucker kann man mit der -P-Option
auswählen,z.B.
lpr -Plw2 dateiname
Wichtig! Druckt keine PDF-Dateien mit lpr aus, da ihr sonst einen unleserlichen Seitensalat bekommt, den ihr nicht gebrauchen könnt! Ausdruck von Dateien, die kein PS-Format besitzen, aus einem entsprechenden Programm heraus,
z.B. Acrobat-Reader für PDF-Dateien.
Das Kommando lpq gibt eine Liste aller Druckaufträge aus, etwa:
Rank
active
1st
2nd
owner (class)
benner
jschmidt
benner
Job
79
83
84
Files
titel.ps
daten
zv.text
Total size
132 438
488
6 212
(Time)
bytes
bytes
bytes
Die Zahl in der Spalte Job gibt eine Identifikationsnummer (ID) an, mit der
man den Druckauftrag löschen kann. Dies geschieht mit
lprm jobid
1.7 Verzeichnisse und Dateien
pacc
15
Information über aktuelles Druckkontingent
Weitere Informationen finden sich unter
http://www.informatik.uni-bremen.de/t/info/drucken.html
1.7 Verzeichnisse und Dateien
Alle Daten, Texte, Programme usw. sind unter UNIX in einer baumartigen
Struktur angeordnet:
• Eine Datei (file) ist ein „Behälter“, der Daten, Texte, Programme enthält
(UNIX-Kommandos sind auch Dateien).
• Ein Verzeichnis (auch Ordner oder Schublade, directory) ist eine Zusammenfassung mehrerer Dateien und Ordner. Durch Verzeichnisse kann man
zusammengehörige Dateien bündeln und Daten strukturieren. Verzeichnisse, die in Verzeichnissen enthalten sind, nennt man auch Unterverzeichnisse.
• Sämtliche Dateien und Verzeichnisse in einem UNIX-Dateisystem befinden sich in Unterverzeichnissen eines großen Hauptverzeichnisses, dem
sogenannten Wurzelverzeichnis (bzw. Wurzel). Dieses wird mit / bezeichnet, ansonsten dient der Schrägstrich zum Trennen von Verzeichnisnamen
in einem Pfad.
• Ein Verweis (link) verweist auf eine Datei oder ein Verzeichnis und verhält
sich dann wie eine Datei bzw. ein Verzeichnis, nur dass der Speicherplatz
nur einmal benötigt wird. Wir werden davon in dieser Einführung keinen
Gebrauch machen.
1.7.1
Grundgerüst des Verzeichnisbaumes
Der Verzeichnisbaum unter UNIX hat prinzipiell die Struktur, wie sie Abb. 1.1
zeigt.
Benutzer legen ihre privaten Daten in ihrem Heimverzeichnis bzw. HomeVerzeichnis ab. Die Heimverzeichnisse aller Nutzer befinden sich im Verzeichnis
/home. Das Heimverzeichnis des Benutzers mit dem Nutzerkürzel benke ist somit /home/benke. In der Shell-Variablen (Abschnitt 1.8) $HOME befindet sich
der Pfad seines Heimverzeichnisses.
Nach dem Einloggen befindet man sich im eigenen Heimverzeichnis.
16
Kapitel 1: Einführung in UNIX / Linux
XW
ffffjfj / RRWXRWXRWXRWXRWXWXWXWXXX
fffjfjfjjjj
f
RRR WWWXWXWXXXX
f
f
f
j
RRR WWWWXWXXXX
ffff jjjjj
f
f
f
X
RRR
f
j
j
ffff
RR WWWWWXWXWXWXXXXXXX
f
j
f
f
j
f
j
f
f
...
usr
etc
lib
bin
home RR
RRR
jjjj
j
R
j
j
R
RRR
jj
RRR
jjjj
j
RR
j
j
j
...
...
benke R
k
R
RRR
kk
k
k
R
k
RRR
kkk
RRR
kkkk
R
k
k
k
k
...
SKRIPTEPhysik
AWI
SKRIPTEMathe
R
RRR
kkk
k
R
k
R
RRR
kk
kkk
RRR
kkk
RRR
k
k
kk
...
Analysis
...
AnalysisRannacher/
AnalysisTretter.pdf
analysis.txt
...
Abbildung 1.1: Verzeichnisbaum
1.7.2
Dateien und Pfade
Oft muss man einem Kommando den Namen einer Datei oder eines Verzeichnisses übergeben, mit dem das Kommando irgendetwas anstellen soll.
Dieses soll anhand des Befehls more erläutet werden, der das seitenweise
Anzeigen von Textdateien ermöglicht, wobei das zeilenweise Vorblättern mit
<RETURN> und das seitenweise Vorblättern mit <SPACE> vonstatten
geht. Ein komfortableres Anzeigeprogramm wäre less, welches mittels <PAGE
DOWN> und <PAGE UP> ein seitenweises Vor- und Zurückblättern im Text
ermöglicht.
Die einfachste Möglichkeit ist, allein den Dateinamen anzugeben. Das funktioniert, wenn sich die Datei im aktuellen Verzeichnis (Abschnitt 1.7.3) befindet,
d.h. in dem Verzeichnis, in dem man sich gerade befindet (Wichtig! HomeVerzeichnis und aktuelles Verzeichnis sind i. A. verschieden). Das aktuelle
Verzeichnis ist in unserem Fall /home/benke/SKRIPTEMathe/Analysis/.
more analysis.txt
Angenommen die Datei befindet sich im direkt dem aktuellen Verzeichnis übergeordneten Verzeichnis (d.h. wir befinden uns bspw. in
/home/benke/SKRIPTEMathe/Analysis/AnalysisRannacher/), dann kann
man mit dem Verweis .. darauf zugreifen:
more ../analysis.txt
Das lässt sich auch kombinieren:
17
1.7 Verzeichnisse und Dateien
more ../../editor.txt
zeigt die Datei editor.txt an, welche
/home/benke/SKRIPTEMathe/ befinden muss.
sich
im
Unterverzeichnis
Der Punkt . bezeichnet ein Verzeichnis selbst,
more ./analysis.txt
bedeutet das gleiche wie
more analysis.txt
Trotzdem ist der Punkt nicht überflüssig, denn wenn ein Kommando ein Verzeichnis als Argument erwartet, dann kann man mit einem einfachen Punkt das
aktuelle Verzeichnis übergeben.
Die bisher aufgezählten Pfade waren relative Pfade, denn sie gingen immer vom
aktuellen Verzeichnis aus. Im Gegensatz dazu gibt es absolute Pfade. Diese beginnen mit einem Schrägstrich /, dem Wurzelverzeichnis. Die Aneinanderreihung von Verzeichnissen heißt Pfad, weil sie gewissermassen den Weg zu einer
Datei beschreiben.
more /home/benke/SKRIPTEMathe/Analysis/analysis.txt
Dies bezeichnet einen absoluten Pfad.
1.7.3
Navigation im Verzeichnisbaum
Um sich im Verzeichnis- bzw. Dateibaum bewegen zu können oder um Dateien und Verzeichnisse zu erzeugen oder zu löschen, existieren eine Reihe von
Befehlen:
Verzeichnisse wechseln
pwd
print working directory
zeigt das aktuelle Verzeichnis an
cd
change directory
cd ohne Argumente springt ins HomeVerzeichnis
cd ..
Wechsel ins übergeordnete Verzeichnis des
aktuellen Verzeichnis (dafür steht das ..)
18
Kapitel 1: Einführung in UNIX / Linux
cd verzeichnis
Wechsel in ein Unterverzeichnis des aktuellen Verzeichnis (relativer Pfad)
cd /home/benke/AWI/
Wechsel ins Unterverzeichnis von benke (absoluter Pfad)
cd ∼/AWI
dito, d.h. ∼ ist eine Abkürzung für /home/benke
Dateien kopieren, verschieben, löschen
cp datei1 datei2
copy
Kopieren von Dateien – Vorsicht! Falls datei2 schon existiert, so wird ihr Inhalt mit
dem Inhalt von datei1 überschrieben
cp datei1 verzeichnis
Kopiere Datei ins Unterverzeichnis – Vorsicht! Falls datei1 im Unterverzeichnis
schon existiert, so wird ihr Inhalt mit dem
Inhalt von datei1 überschrieben
cp -i datei1 verzeichnis Kopieren mit Sicherheitsabfrage (nur falls
identische Datei im Verzeichnis bereits existiert)
cp verzeichnis/* .
Kopiere alle Dateien aus Verzeichnis ins aktuelle Verzeichnis (Anwendung von ‚.‘)
mv datei1 datei2
move
Verschieben bzw. Umbenennen von Dateien
– Vorsicht! Falls datei2 schon existiert, so
wird ihr Inhalt überschrieben
mv datei1 verzeichnis
Verschiebe Datei ins Unterverzeichnis –
Vorsicht (siehe oben)!
mv -i datei1 verzeichnis Verschieben mit Sicherheitsabfrage
rm datei
remove
Löschen einer Datei – Vorsicht! Keine vorherige Sicherheitsabfrage wie bei WindowsPapierkorb!
rm -i datei
Löschen mit Sicherheitsabfrage
19
1.7 Verzeichnisse und Dateien
rm -r verzeichnis
Rekursives Löschen von Verzeichnissen inkl.
deren Inhalt, d.h. alle Dateien und alle Unterverzeichnisse in diesem Ordner – VORSICHT!! Damit könnt ihr im ungünstigsten Fall euer gesamtes Home-Verzeichnis
löschen
rm -ri datei
Rekursives Löschen von Verzeichnissen mit
Sicherheitsabfrage (hier findet man eine
Kombination zweier Argumente vor)
rm -rf verzeichnis
Rekursives Löschen von Verzeichnissen ohne Sicherheitsabfrage und Fehlermeldungen
– EXTREME VORSICHT!
Verzeichnisse erstellen, löschen
1.7.4
mkdir verzeichnis
make directory
Anlegen von neuem Unterverzeichnis im aktuellen Verzeichnis
rmdir verzeichnis
remove directory
Löschen von leerem Verzeichnis im aktuellen Verzeichnis
Ausgabe von Inhaltsverzeichnissen
Den Inhalt eines Verzeichnisses kann man sich mit ls ( list) oder ls -l bzw.
ll ( list long) anschauen (Sortiert nach Namen; Großschreibung zuerst). Mit
ls -a sieht man auch die „versteckten“ Dateien (Systemdateien, Voreinstellungen u.ä., d.h. Dateien, die mit ‚. ‘beginnen).
total 1836
drwxr-xr-x
-rw-r--r-drwx-----drwx-----drwxr-xr-x
-rw-r--r-drwxrwxrwx
-rw-r--r-drwx--x--x
drwxrwxrwx
drwxr-xr-x
5
1
18
4
11
1
16
1
20
3
17
Man sieht
• die Zugriffsrechte,
stoever
stoever
stoever
stoever
stoever
stoever
stoever
stoever
stoever
stoever
stoever
wimi
wimi
wimi
wimi
wimi
wimi
wimi
wimi
wimi
wimi
wimi
512
35328
1024
512
1536
18874
512
1296336
2048
1536
2560
Oct
Aug
Feb
Aug
Dec
Dec
Jan
Feb
Feb
Jan
Feb
17
28
5
15
10
20
17
4
4
28
3
11:10
15:18
14:21
08:30
12:52
13:18
14:41
08:44
12:26
17:21
15:41
KUTTA
Lab.doc
Mail
fortran
images
go.txt
mfiles
te1.tgz
tfiles
tmp
word
20
Kapitel 1: Einführung in UNIX / Linux
• einen Referenzzähler für die Verweise auf die Datei (i.d.R 1) bzw. das
Verzeichnis (Anzahl ≥ 2, denn bei Verzeichnissen sind immer mind. 2
Links vorhanden, nämlich von ‚.‘und dem Verzeichnisnamen selbst),
• den Besitzer der Datei oder des Verzeichnisses,
• die Benutzergruppe, zu welcher die Datei gehört,
• die Größe der Datei (in Bytes) bzw die Anzahl der Bytes, die zur Verwaltung der Verzeichniseinträge notwendig sind (Vielfaches von 512 Bytes).
• das Datum der letzten Änderung und
• den Namen der Datei oder des Verzeichnisses.
Datei- und Verzeichnisnamen bestehen aus einer Folge von Buchstaben und
Zahlen (bis zu 256 Zeichen) und dürfen auch Sonderzeichen enthalten, wobei
es aber üblich ist, aus diesen hauptsächlich den Unter- und Bindestrich zu
nehmen. Leerzeichen sind ebenfalls erlaubt, aber problematisch. Die Groß- und
Kleinschreibung wird unterschieden (!!). Man sollte selbsterklärende Namen
verwenden, damit man selbst (und vielleicht auch ein anderer) vermuten kann,
was die Datei enthält. Häufig (und oft auch sinnvollerweise) sind Dateinamen
als name.endung aufgebaut, wobei die Endung über den Typ der Datei Auskunft
gibt, bzw. geben sollte, z.B.
name.c
C-Programmdatei (Sourcecode),
name.o
Objektdatei; wird vom Compiler aus einer .c-Datei erzeugt,
name.tex
TEX-Datei,
name.ps
Postscript-Datei,
name.doc
Word-Dokument,
name.html Datei im HTML-Format,
.name
versteckte Datei: Systemdatei, Einstellungen, oder ähnliches
Weitere wichtige Varianten von ls sind z.B. ls -la, ls -lt (Ausführliche Anzeige
plus Sortierung der Dateien und Verzeichnisse nach Datum und Zeit).
1.7.5
Zugriffsrechte für Dateien und Verzeichnisse
Die Zugriffsrechte legen fest, welche Nutzer wie auf Dateien und Verzeichnisse
zugreifen dürfen. Dabei werden Dateien und Verzeichnisse im Wesentlichen
gleich behandelt. In der Ausgabe von ls -l erkennt man ein Verzeichnis
daran, dass an der ersten Stelle ein d (für directory) steht.
Es gibt drei Klassen von Benutzern für die entsprechende Datei- bzw. Verzeichniszugehörigkeiten formuliert werden können:
user
group
others
Eigentümer der Datei
Benutzergruppe
andere (Rest der Welt)
1.7 Verzeichnisse und Dateien
21
Und drei Arten von Zugriffsrechten:
r
w
x
Read
Write
eXecute
Leserechte
Schreibrechte
Ausführbarkeit bei Dateien, Zutritt zu Verzeichnissen
Die Zugriffsberechtigung für Dateien ergibt sich aus der Kombination der Benutzerklassen und der Zugriffsarten, z.B.
r-- r-- r-rwx --- --rw- r-- r--
Alle können Datei lesen, aber keiner darf auf sie
(über-)schreibend zugreifen.
Der Eigentümer darf alles, alle anderen nichts.
Alle dürfen lesen, aber nur der Eigentümer darf
(über-)schreiben.
Bei Ordnern würde sich dann folgende Lesart ergeben
r-- r-- r--
rwx --- ---
rw- r-- r--
Alle können den Inhalt des Verzeichnisses mit ls
auflisten, aber keiner darf in das Verzeichnis
schreiben bzw. wechseln.
Der Eigentümer darf den Inhalt des Verzeichnisses
auslesen, in dieses wechseln oder Dateien anlegen
oder verändern. Alle anderen schauen in die Röhre!
Alle dürfen Inhalt auflisten, aber nur der Eigentümer
darf schreiben (aber nicht ins Verzeichnis wechseln,
d.h. kein cd, cp, ll,...).
Der Besitzer einer Datei oder eines Verzeichnisses kann die Zugriffsrechte
ändern. Die Zugriffsrechte kann man in numerischer oder symbolischer Form
angeben.
chmod rechte dateiname
1.7.6
change mode
Numerische und symbolische Form der Rechteänderung
22
Kapitel 1: Einführung in UNIX / Linux
Numerische Form
400
200
100
40
20
10
4
2
1
z.B.
read
write
execute
read
write
execute
read
write
execute
Leseberechtigung des Eigentümers
Schreibberechtigung des Eigentümers
Ausführungsberechtigung des Eigentümers
Leseberechtigung für Gruppe
Schreibberechtigung für Gruppe
Ausführungsberechtigung für Gruppe
Leseberechtigung für Rest der Welt
Schreibberechtigung für Rest der Welt
Ausführungsberechtigung für Rest der Welt
rw- r-- r--
ergibt 400 + 200 + 40 + 4 = 644, also setzt
chmod 644 datei1
die Rechte für diese Datei entsprechend, d.h. der Eigentümer der Datei darf sie
auslesen und verändern bzw. überschreiben, während die Mitglieder der Gruppe
und der Rest der Welt nur lesend darauf zugreifen können, bzw. bei Ordnern
seinen Inhalt nur mittels ls auslesen können.
Symbolische Form
Die symbolische Form der Rechteänderungen besteht aus Benutzerklasse, Operation und Zugriffsrecht:
[who] op permission
Dabei bedeuten die einzelnen Ausdrücke das Folgende:
who:
op:
permission:
z.B.: Ändern von
u
g
o
a
+
−
=
r
w
x
Eigentümer
Gruppe
andere
Alle (Voreinstellung, falls „who“ weggelassen)
Rechte hinzufügen
Rechte wegnehmen
nur diese Rechte einräumen und alle anderen widerrufen
Leserechte
Schreibrechte
Ausführungsrechte
rwx rw- r-rw- r-- r--
$ chmod u-x,g-w dateiname
in
geschieht durch
1.8 UNIX-Shells
23
1.8 UNIX-Shells
Öffnet man ein Terminal-Fenster, dann läuft im Hintergrund eine Shell, die die
eingegebenen Kommandos interpretiert und dann ausführt oder eine Fehlermeldung herausgibt. Die Shell bildet die Schnittstelle zwischen dem Benutzer und
dem UNIX-Kern und legt sich somit wie eine Schale um den UNIX-Kern. Sie
bildet somit eine Art Vermittler, wobei alle Eingaben (bzw. Kommandos) des
Benutzers durch die Shell interpretiert werden ( Kommandointerpreter), d.h.
sie „übersetzt“ die Kommandos in Aufrufe von Systemfunktionen an den UnixKernel, welcher diese dann ausführt und als Ergebnis Statusmeldungen zurückliefert, die dann die Shell auswertet. Ebenso besitzt die Shell eine eigene Sprache, in der man Programme schreiben (sog. Shell-Skripte) und sie in der Shell
ablaufen lassen kann.
Von den verschiedenen Shells wird hier standardmäßig die bash ( BourneAgain-Shell) benutzt.
Ein paar Merkmale der bash:
• Editieren in der Kommandozeile (mittels Cursortasten, Backspace)
• Zurückholen bereits eingegebener Befehle mit den Cursor-Tasten (History)
• Zurückholen bereits eingegebener Befehle mit passendem Präfix mit den
Tasten “vorige Seite” und “nächste Seite” (History)
• Vervollständigung von Dateinamen mit der TAB -Taste
(wenn die Vervollständigung eindeutig möglich ist)
• Definition eigener Namen für häufig benutzte Kommandos mit dem aliasMechanismus
1.8.1
Sonderzeichen
Eine Reihe von Zeichen werden von der Shell besonders behandelt.
24
Kapitel 1: Einführung in UNIX / Linux
Solche Sonderzeichen sind z.B.
;
&&
||
$
<
>
|
&
‘cmd‘
t
Zeilenende
Kommandos hintereinander ausführen
Kommandos hintereinander ausführen bis Fehler
auftritt; alle folgenden Kommandos bleiben
unberücksichtigt
Folgendes Kommandos wird nur abgearbeitet, falls
voriges Kommando mit Fehler endete
Shell-Variable abrufen, Abschnitt 1.8.2
Eingabeumleitung aus Datei, siehe Abschnitt 1.8.5
Ausgabeumleitung in Datei
Ausgabeweiterleitung zu anderem Programm
Programm vom Terminal abkoppeln (!), siehe Abschnitt 1.9
Ausgabe von Kommando cmd als Argument benutzen
Leerzeichen
Kommando starten.
Mit \ (backslash) wird die Bedeutung von Sonderzeichen aufgehoben.
Setzt man einen Befehl oder einen Teil davon in einfache Hochkommata, wird
dieser Text genauso, also ohne Ersetzungen übernommen.
$ echo \$PATH ’$PATH’ $PATH
$PATH $PATH /usr/bin:...
1.8.2
Shell-Variablen
Die Shell verwaltet eine Reihe von Shell- und Environment-Variablen. ShellVariablen haben nur Auswirkungen auf das Verhalten der Shell (z.B. Variable PS1, welche für das Aussehen des Prompt- bzw. Eingabeaufforderungszeichens verantwortlich ist), während Environment-Variablen das Verhalten der
Programme beeinflussen, welche aus der Shell heraus gestartet werden (z.B. Variable LANG, welche Programme abfragen, um abzufragen, in welcher Sprache
die Programmausgabe erfolgen soll). Sie beeinflussen das Verhalten der Shell
nicht. Manche Variablen werden sowohl von der Shell, als auch von den aus
der Shell gestarteten Programmen benutzt (z.B. HOME). Der Zweck von ShellVariablen ist, dass man mit ihnen das Verhalten der Shell leicht konfigurieren
kann, was die Arbeit sehr erleichtert.
Eine Variable hat einen Namen und einen Wert. Der Wert einer Variablen kann
abgefragt werden, indem man dem Namen der Variablen ein $ voranstellt, etwa
$HOME. Existiert die Variable nicht, gibt es leider keine Fehlermeldung, sondern
als Wert wird der leere Text zurück gegeben. Schreibfehler können dadurch nur
schwer aufgedeckt werden.
Mit
1.8 UNIX-Shells
25
$ BLA=fasel
$ echo $BLA
fasel
wird eine Shell-interne Variable BLA gesetzt und mittels echo wieder ausgegeben. Mit printenv bekommt man eine Liste aller definierten Variablen mit
ihrem aktuellen Wert.
Mit export setzt man den Wert einer (auch selbstdefinierten) Variablen und
macht sie dann zu einer Environment- bzw. Umgebungs-Variablen, mit echo
kann man ihn dann ausgeben.
$ export BLA=fasel
$ echo $BLA
fasel
Einige interessante Shell-Variablen:
HOME
HOST
SHELL
DISPLAY
PATH
PS1
PWD
USER
Heimverzeichnis
Rechnername
Name der benutzten Shell
Name des X-Window-Displays, bzw. Bildschirms, auf dem die
Ausgabe erfolgen soll.
Liste aller Verzeichnisse, in der die Shell die Programme finden
kann.
Format der Eingabeaufforderung (Prompt), z.B. \u@\h:\w>
aktuelles Verzeichnis
Benutzername
Konvention: Shell-Variablen werden komplett groß geschrieben!
1.8.3
Kommandos
Zu jedem UNIX-Kommando existiert eine entsprechende ausführbare Datei. Es
gibt aber auch Shell-interne Kommandos, wie export. Eine ausführbare Datei
ist in der Regel ein Maschinenprogramm, also kein Text, den man sich anschauen kann. Die meisten dieser Dateien liegen in Verzeichnissen wie /bin,
/usr/bin.
Damit die Shell den Befehl ausführen kann, muss sie wissen, in welchem Verzeichnis die dazugehörige ausführbare Datei liegt. Dafür hat sie einen Suchpfad,
der sich aus ein paar festverdrahteten Orten wie /bin, /usr/bin und den in
der Shellvariablen PATH aufgezählten Verzeichnissen zusammensetzt. Man kann
sich diesen Pfad anzeigen lassen mit
$ echo $PATH
/usr/local/cm3/bin:/usr/lib/qt3/bin:/opt/kde3/bin
26
Kapitel 1: Einführung in UNIX / Linux
Die verschiedenen Verzeichnisse werden mit Doppelpunkten getrennt.
Ein weiteres Verzeichnis kann man dem Pfad z.B. mit
$ export PATH=$PATH:/home/name/bin
hinzufügen, was zu einem neuen Wert der Variablen PATH führt.
$ echo $PATH
/usr/local/cm3/bin:/usr/lib/qt3/bin:/opt/kde3/bin:/home/name/bin
Nun schaut die Shell auch in /home/name/bin nach, ob dort eine ausführbare
Datei des eingegebenen Namens existiert.
Erfahrene UNIX-Nutzer schreiben und benutzen sogenannte Shell-Skripte. ShellSkripte bestehen aus einer Abfolge mehrerer UNIX-Kommandos. Sie werden (im
Gegensatz zu C-Programmen) für die Ausführung nicht in Maschinenprogramme
übersetzt.
1.8.4
Platzhalter in Dateinamen
Durch Benutzung spezieller Sonderzeichen, der Platzhalter, auch Jokerzeichen
oder Wildcards genannt, kann man Dateinamen abkürzen oder mehrere Dateien
gleichzeitig ansprechen. Die Shell analysiert das verwendete Suchmuster, ersetzt
es durch alle dazu passenden Dateinamen und übergibt das Ergebnis an das
angegebene Kommando.
?
*
[ ]
steht für genau ein Zeichen (?? für zwei, ??? für drei, etc.).
steht für beliebig viele Zeichen (keins und eins eingeschlossen).
steht für ein Zeichen aus einem begrenzten Zeichenvorrat.
Beispiele:
ls prog*.c
listet prog.c, prog1.c, prog2.c, progx.c,
. . . , soweit im Verzeichnis vorhanden
ls prog?.c
listet nicht prog.c, aber prog1.c, prog2.c,
progx.c, . . . , soweit im Verzeichnis vorhanden
ls prog[0-9].c
listet nicht prog.c, aber progn.c, wobei
n ∈ {0, 1, . . . , 9}
ls prog[a-z].c
listet progn.c, wobei n ∈ {a, b, . . . , z}
rm *.ps
löscht alle Postscript-Dateien (in diesem
Verzeichnis)!
1.8 UNIX-Shells
rm *
1.8.5
27
löscht alle Dateien (in diesem Verzeichnis)!
Also vorsichtig mit * in manchen Situationen!
Umleitung
Bis jetzt haben wir Kommandos immer über die Tastatur ein- und das Ergebnis
immer auf dem Bildschirm ausgegeben (genannt Standardeingabe (stdin) und
-ausgabe (stdout)). Man kann aber sowohl Ein- wie Ausgabe umleiten.
ll > datei
Das Inhaltsverzeichnis wird in eine Datei
geschrieben.
cat datei1 datei2 > datei3
concatenate
Verbinde datei1 und datei2, schreibe das
Ergebnis in datei3 (Überschreiben)
cat datei4 >> datei3
Hänge datei4 noch an datei3 an.
mail nutzer < datei
Schicke die Datei an den Nutzer.
Eine besondere Form der Umleitung ist die Pipe (von Pipeline). Sie schaltet
mehrere Kommandos hintereinander, wobei die Ausgabe des einen Kommandos
als Eingabe des nächsten dient.
ll | sort -r -k8
Das Inhaltsverzeichnis wird in alphabetischer Reihenfolge sortiert.
Der Befehl sort gibt Textzeilen nach einem vom Benutzer gewählten Sortierungskriterium aus, wobei -r -k8 bedeutet, dass die Ausgabe umgekehrt wird
(d.h. es wird vom Größten zum Kleinsten sortiert) und es wird nach der 8.
Spalte der Ausgabe (d.h. den Dateinamen) sortiert.
Man kann alle diese Möglichkeiten auch miteinander kombinieren, z.B.
ll | sort -r -k8 | lpr
Inhaltsverzeichnis sortieren und dann ausdrucken.
(Bem: ‚|‘ ist links-assoziativ)
ll | sort -r -k8 > datei Inhaltsverzeichnis sortieren und dann in eine Datei schreiben. Falls sie existiert, so
wird sie überschrieben
28
1.8.6
Kapitel 1: Einführung in UNIX / Linux
Vorverarbeitung der Kommandos
Noch bevor das eigentliche Kommando (Programm) gestartet wird, erledigt
die Shell einige Aufgaben. Es ist wichtig zu wissen, welche Aufgaben die Shell
übernimmt und welche die Kommandos.
Nachdem man eine Kommandozeile mit
siert in etwa folgendes:
RETURN
abgeschlossen hat, pas-
• Die Shell liest bis zum ersten Kommandotrenner (z.B. &, <, >).
Weiterhin stellt die Shell fest, ob Variablenzuweisungen oder Ein/Ausgabeumlenkungen erfolgen sollen.
• Die Shell zerlegt die Kommandozeile in einzelne Argumente (an der Stelle
wo i.d.R. Leerzeichen oder Tabs stehen).
• Shell-Variablen, denen ein $ vorangestellt wurde, werden durch ihren Inhalt ersetzt. echo $PWD könnte zu echo /home/benke werden.
• Komandos, die in Apostrophen (“) stehen, werden ausgeführt und durch
ihre Ausgabe ersetzt.
• Standardeingabe und -ausgabe sowie die Standardfehlerausgabe werden auf
ihre Ziele umgelenekt und die entsprechenden Dateien geöffnet. Im Falle
der Ausgabe wird die entsprechende Datei geleert.
• Texte, die Platzhalter enthalten, werden entsprechend der vorhandenen
Dateien im aktuellen Verzeichnis durch eine durch Leerzeichen unterteilte Liste ersetzt. Gibt es keine Datei, die zu dem Muster passt, wird das
Muster beibehalten.
ls *.tex *.bla könnte zu ls skript.tex anhang.tex *.bla werden.
Vorsicht: Die Shell weiß nichts über die Bedeutung der Argumente für ein
Kommando, sie ersetzt stur alles was wie ein Dateinamenmuster aussieht,
auch wenn es z.B. ein Suchmuster für grep ist.
• Das eigentliche Kommando wird mit den umgewandelten Argumenten aufgerufen. Wie das Kommando letztlich aufgerufen wird, bekommt man zu
sehen, wenn die bash mit der Option −x gestartet wird.
Die Bequemlichkeiten, die eine Shell durch ihre Automatiken erlaubt, können
sich auch schnell rächen. Beispiele:
1. Wir suchen alle Zeilen eines Textes in denen ein > vorkommt und schreiben:
$ grep > text.txt
Die Argumente > und text.txt erreichen das Programm grep allerdings
nie, denn die Shell hat bereits die Datei text.txt zum Zwecke der Ausgabeumleitung gelöscht und ruft grep ohne jegliche Argumente auf.
1.9 Prozesse
29
2. Wir wollen eine Reihe von Dateien in ein anderes Verzeichnis verschieben,
vergessen aber, das Zielverzeichnis anzugeben.
$ mv *.tex
Möglicherweise gibt es nur die Dateien datei1.tex und datei2.tex, die
zu diesem Muster passen. Dann wird der Befehl
mv datei1.tex datei2.tex
ausgeführt, mit dem Ergebnis, dass datei2.tex unwiederbringlich durch
datei1.tex ersetzt wird.
1.9 Prozesse
UNIX ist nicht nur ein Mehrbenutzer- (Multi-User), sondern auch ein
Multitasking-Betriebssystem, d.h. jeder kann (scheinbar) parallel mehrere Prozesse (Kommandos, Programme, . . . ) laufen lassen. Tatsächlich laufen ständig
diverse, vom System ausgelöste Prozesse, ohne dass man es merkt.
Um ein Terminal nicht zu blockieren, kann man Programme bzw. Prozesse auch
so starten, dass sie im Hintergrund laufen, z.B.
nedit &
Es öffnet sich das nedit -Fenster und im Terminal kann weitergearbeitet werden.
Startet man ein Programm ohne &, so ist das Terminal blockiert. Aber man kann
das Programm mit CTRL + z anhalten und dann mit dem Kommando bg (und
der Prozess- bzw Jobnummer) unabhängig laufen lassen („in den Hintergrund
verlagern“ = background). Mit fg wird es wieder in den Vordergrund geholt
und blockiert die Shell.
Eine Übersicht über die laufenden Prozesse liefert der ps -Befehl. Durch
ps -U nutzer
bekommt man eine Liste seiner eigenen, aktiven Prozesse, z.B.
30
Kapitel 1: Einführung in UNIX / Linux
PID
23629
23671
23686
23775
23804
24229
24233
26045
26516
26520
26852
TTY
pts/1
?
?
pts/1
?
?
pts/4
pts/1
?
pts/5
pts/5
TIME
00:00:00
00:02:02
00:00:00
00:00:06
00:00:01
00:00:01
00:00:00
00:00:03
00:00:00
00:00:00
00:00:00
CMD
bash
mozilla-bin
gconfd-2
nedit
kdeinit
kdeinit
bash
acroread
kdeinit
bash
ps
Dabei ist für jeden Prozess eine eindeutige Kennzahl PID (Process Identification)
angegeben, die man zum außerplanmäßigen Beenden eines Prozesses benötigt
(wenn CTRL + c nicht möglich ist):
$ kill PID
$ kill -9 PID
Das kill -Kommando versucht den Prozess so ordentlich wie möglich zu beenden, d.h. der Prozess hat nach Erhalt des Termination-Signals noch die Möglichkeit, alle Dateien zu schließen, auf denen er gearbeitet hat und evtl. vorhandene
temporäre Dateien zu löschen. Die zweite Variante (d.h. kill -9 PID) sollte nur
im Notfall verwendet werden, weil dann der Prozess möglicherweise Resourcen
nicht wieder frei gibt.
Eine ausführlichere Übersicht über seine eigenen Benutzerprozesse erhält man
mittels
ps -ef | grep nutzer
ps -ef gibt Informationen über alle auf dem Rechner laufenden Prozesse aus,
z.B. auch root- Prozesse (Systemprogramme). Mittels grep werden dann die
eigenen Benutzerprozesse „herausgefiltert“.
Eine weitere Möglichkeit, Prozesse anzeigen zu lassen, wäre:
aktuelle Programme interaktiv anzeigen lassen
top
Die durch top erhaltene Prozessliste wird in regelmäßigen Abständen aktualisiert.
Bem: Mittels
ps
werden alle aktiven Benutzerprozesse angezeigt, wäh-
rend jobs nur alle in der aktuellen Shell laufenden Prozesse bzw. von ihr
gestarteten Prozesse anzeigt.
$ jobs
1.10 Arbeiten auf entfernten Rechnern
[1]
[2]
[3][6]+
[7]
Running
Stopped
Stopped
Stopped
Running
31
nedit weiterebefehle.tex \&
make
make
make
acroread skript.pdf \&
Mittels kill %JOBID kann man den Job bzw. das Programm abbrechen.
1.10 Arbeiten auf entfernten Rechnern
Unter UNIX ist es problemlos möglich, Rechner so zu vernetzen, dass man auf
einem anderen Rechner arbeiten kann, als dem, vor dem man sitzt. Auf einen
fremden Rechner begibt man sich mit
ssh nutzer@rechner
Eventuell muss man noch sein Passwort eingeben. Ist man auf dem Rechner
angelangt, kann man bereits einfache Kommandozeilenprogramme starten. Neben ls , cp , rm usw. wären das z.B. der Editor vi oder das Textsatzsystem
latex .
Es ist auch möglich, Programme mit grafischer Benutzeroberfläche auf dem fremden Rechner zu starten, aber die Ausgabe dann auf dem eigenen Rechner anzeigen zu lassen. Da alle Grafikdaten von dem entfernten Rechner zum eigenen
übertragen werden müssen, benötigt das ganze viel Netzbandbreite. Um Grafikdaten zu übertragen, ruft man ssh zusätzlich mit der Option -X auf.
1.11 Weitere nützliche Befehle
Ohne Anspruch auf Vollständigkeit seien noch ein paar wichtige UNIX-Befehle
aufgelistet, wobei hier häufig nur wichtige Spezialformen behandelt werden. Bei
weitergehendem Interesse kann man z.B. in den manpages oder einem UNIXBuch nachsehen.
Konfigurieren
source datei
Einlesen einer Konfigurationsdatei
alias la=’ls -la’
Neuen Befehl la definieren
32
Kapitel 1: Einführung in UNIX / Linux
.bashrc
Konfigurationsdatei für Shell, die beim Start
jeder neuen Shell automatisch ausgeführt
wird. Hier können Dinge gespeichert werden, die dauerhaft beim Start jeder Shell berücksichtigt werden sollen.
.private_functions
Ergänzung der Datei .bashrc für eigene Erweiterungen (fachbereichsinterne Regelung).
Diese wird automatisch eingebunden.
Suche
find . -name ’*.c’
Suche nach Dateinamen
grep suchmuster datei
Suche nach Text in Datei bzw. Dateien, z.B.
grep nedit editor.txt
which kommando
Suche nach dem Pfad von Kommandos, z.B.
which ssh
Speicherplatz
quota
Zeigt an, wieviel Plattenplatz man beansprucht und ob die Grenze überschritten
worden ist. Diese liegt momentan bei 200
MByte für Studenten ( Quota). Bei Überschreitung der vorgegebenen Grenze kann
man sein Benutzerkonto praktisch nicht
mehr benutzen. Auch ist der Start z.B. von
KDE nicht mehr möglich. Somit ... Löschen
von Dateien ist angesagt!
du [optionen] [dateien] Berechnet bzw. schätzt den Speicherplatz,
der von den angegebenen Dateien bzw. Verzeichnissen (inklusive Unterverzeichnisse)
belegt wird. Z.B. berechnet du -sk . den
von aktuellen Verzeichnis belegten Plattenplatz und gibt ihn als Summe in KByte aus
(ohne -s würde der Platzbedarf für jedes Unterverzeichnisse einzeln mit ausgegeben.)
Komprimieren und Archivieren
gzip datei
Komprimiert datei (eine oder mehrere),
Ausgabe jeweils datei.gz
gunzip datei.gz
Dekomprimiert gz-Dateien
bzip2 datei
analog gzip
bunzip2 datei.bz2
analog gunzip
1.11 Weitere nützliche Befehle
33
tar -cvf archiv.tar ordner
Legt Datei-Archiv von ordner an, wobei c
für create (d.h. erzeuge Archiv), v für verbose (d.h. gibt die Namen der Dateien aus)
und f archiv.tar den Dateinamen für das Archiv bezeichnet
tar -tvf archiv.tar
Listet den Inhalt des tar-Archivs, wobei t für
table of contents steht
tar -xvf archiv.tar
Entpackt Archiv
tar -cvzf archiv.tgz ordner
Legt Datei-Archiv von ordner an und komprimiert es danach mit gzip; nur bei GNUtar
tar -xvzf archiv.tgz
Entpackt komprimiertes Archiv
Die Funktion von ”gzip” und ”tar” sind in den Kommandos ”zip / unzip” quasi
vereint:
zip zipdatei name(n)
Komprimiert die Datei(en) name(n) (eine
oder mehrere Dateien oder auch Ordner)
und speichert das Resultat unter zipdatei
unzip zipdatei
Dekomprimiert die Datei(en) und Ordner
die in zipdatei gespeichert sind
Zugriff auf andere Rechner
scp nutzer@rechner:datei .
Datei zwischen Nutzern oder Rechnern austauschen
ssh nutzer@rechner
Anmelden auf einem anderen Rechner
Gruppen
grp -setup boerse
Anlegen der Gruppe boerse
grp -invite boerse nutzer
Einladen von Teilnehmern
grp -show boerse
Anzeigen der Daten der Gruppe
grp -show
In welchen Gruppen bin ich ?
chgrp boerse BOERSE
Ändern der Gruppenzugehörigkeit vom Verzeichnis BOERSE. Darf nur einer aus der
Gruppe machen.
34
Kapitel 1: Einführung in UNIX / Linux
chmod 770 BOERSE
Nur Mitglieder der Gruppe dürfen in das
Verzeichnis
Disketten und USB-Sticks
mount /floppy
Diskettenlaufwerk
Root)
einbinden
(evtl.
nur
cp /floppy/hallo.txt .
Kopiere Datei von Diskette ins aktuelle Verzeichnis
umount /floppy
Laufwerkseinbindung lösen (evtl. nur Root)
cat /etc/fstab
Welche Laufwerke gibt es?
Z.B. /media/usbdisk
Dokumententypkonvertierungsprogramme
pdf2ps datei.pdf
Umwandlung von PDF- nach PS-Datei
ps2pdf datei.ps
Umwandlung von PS- nach PDF-Datei
...
Anzeigeprogramme
gv name.ps
Anzeigen einer Postscript-Datei (Ghostview)
acroread name.pdf
Anzeigen einer PDF-Datei (Acrobat Reader)
xdvi name.pdf
Anzeigen einer DVI-Datei
35
2 Algorithmen
2.1 Begriff
(Versuch einer) Definition:
Ein Algorithmus ist eine endliche Vorschrift, die genau angibt, wie ein bestimmtes Problem in einzelnen Schritten zu lösen ist, die von dieser Vorschrift
exakt vorgegeben sind. (Taschenbuch der Wirtschaftsinformatik, S.168)
Beispiele für Algorithmen: Kochrezepte, Montageanleitungen, . . .
Die Durchführung eines Algorithmus liefert einen Prozess. Ein Prozessor
ist eine Einheit, die einen Algorithmus durchführt, z.B. ein Mensch oder die
CPU des Computers. Der Algorithmus selbst ist unabhängig von seiner
Umsetzung in einem Prozess und seiner Abarbeitung durch einen Prozessor
(z.B. unabhängig von der Implementierung in einer Programmiersprache)
Die Entwicklung eines Algorithmus ist um so schwieriger, je komplexer die
zu lösende Aufgabe ist. Deshalb zerlegt man die Aufgabe in Teilprobleme,
zu deren Lösung man dann Algorithmen entwickelt. Sofern nötig kann man
die Teilaufgabe wiederum in Teilaufgaben zerlegen, usw. Diese Methode heißt
schrittweise Verfeinerung.
Ein Algorithmus besteht aus einer Folge von einzelnen Schritten bzw.
Anweisungen, so dass
– zu einem bestimmten Zeitpunkt nur ein Schritt ausgeführt wird (serieller/sequentieller Algorithmus im Unterschied zum parallelem Algorithmus),
– jeder Schritt genau einmal ausgeführt wird,
– die Reihenfolge der Ausführung der Schritte die gleiche Folge ist, in der
sie niedergeschrieben sind,
– mit Beendigung des letzten Schrittes der Algorithmus endet.
Wichtige prinzipielle Fragen beim Algorithmenentwurf sind
36
Kapitel 2: Algorithmen
• Berechenbarkeit: Existiert ein Algorithmus, der das gegebene Problem löst?
• Korrektheit: Löst der Algorithmus die gegebene Aufgabe korrekt?
• Komplexität: Vergleich von Algorithmen nach Zeit- und Speicherbedarf
Zu einem Algorithmus gehören in der Regel Eingabe- und Ausgabedaten. Der
Ausführungsteil besteht aus einer endlichen Folge von Anweisungen, die aus einer Reihe von typischen Bausteinen bestehen, welche man wiederum ineinander
verschachteln kann.
• Aneinanderreihung (Sequenz)
• Auswahl (Selektion)
• Schleife (Wiederholung, Iteration)
• Funktion
Diese werden im Folgenden beschrieben.
2.2 Sequenzen
Eine Sequenz ist eine Abfolge bzw. Aneinanderreihung einzelner Anweisungen,
wobei die Sequenz selbst wiederum als Anweisung anzusehen ist.
Beispiel: Zubereitung einer Tasse Pulverkaffee
1. Koche Wasser
2. Gib Kaffeepulver in die Tasse
3. Fülle Wasser in die Tasse
In dieser Sequenz findet sich sich eine Aneinanderreihung von 3 Anweisungen,
wobei alle 3 zusammen wiederum eine Anweisung ergeben.
2.3 Auswahl (Selektion)
37
Verfeinerung des Algorithmus
1.1
1.2
1.3
1.4
2.1
2.2
2.3
2.4
3.
Fülle Wasserkocher mit Wasser
Schalte Wasserkocher an
Warte bis es kocht
Schalte Wasserkocher bei Bedarf aus
Öffne Kaffeeglas
Entnimm einen Löffel Kaffee
Kippe Löffel in die Tasse
Schließe Kaffeeglas
Fülle Wasser in die Tasse
Algorithmen, die nur aus einer Sequenz von Anweisungen bestehen, sind unßexibel und in ihrer Ausführung starr, da sie keine Alternativen enthalten, wenn
z.B. ein Schritt nicht durchführbar ist. In unserem Beispiel tritt dieser Fall dann
ein, wenn das Kaffeeglas leer ist.
2.3 Auswahl (Selektion)
Man benötigt ein Konstrukt, das eine Auswahl unter 2 möglichen Teilalgorithmen vorsieht.
FALLS
DANN
SONST
Bedingung
Anweisung1
Anweisung2
Falls die Bedingung erfüllt ist, wird Anweisung1 durchgeführt, während
Anweisung2 vernachlässigt wird, d.h. nach Ausführung von Anweisung1 wird
mit dem Algorithmus direkt nach Anweisung2 fortgefahren. Falls die Bedingung
nicht erfüllt wird, so wird Anweisung2 ausgeführt und Anweisung1 nicht
berücksichtigt. Die Anweisungen können wiederum aus Sequenzen, Auswahl,
Wiederholung oder Funktionsaufrufen bestehen.
Beispiel: Kaffeekochen mit Entscheidung
Verfeinerung von Schritt 2.1 durch Auswahl
38
Kapitel 2: Algorithmen
2.1.1 Nimm Kaffeeglas aus dem Schrank
2.1.2 FALLS Kaffeeglas leer ist
DANN Nimm neues Kaffeeglas
2.1.3 Öffne Kaffeeglas
Der SONST-Teil kann hier entfallen, weil keine Alternativ-Aktion nötig ist.
Auswahl-Aktionen können auch ineinander geschachtelt werden („nesting“):
2.1.1 Nimm Kaffeeglas aus dem Schrank
2.1.2 FALLS Kaffeeglas leer ist
DANN
FALLS Weiteres Kaffeeglas vorhanden
DANN Nimm neues Kaffeeglas
SONST Brich Kaffeekochen ab
2.1.3 Öffne Kaffeeglas
Problem: Möglicherweise ist nicht von vornherein klar, wieviele Fälle unterschieden werden müssen!
2.4 Schleifen (Wiederholung, Iteration)
Wir benötigen ein Konstrukt, in dem die Anweisungen solange wiederholt
werden, bis die Teilaufgabe gelöst ist – Schleifen.
Folgende drei Arten stehen in der Regel zur Verfügung:
• SOLANGE Bedingung erfüllt FÜHRE AUS Anweisungsfolge
While-Schleife, d.h. Schleife mit Vorabtest.
Ist die Bedingung in dieser Schleife nicht mehr erfüllt, so wird der
Schleifendurchlauf abgebrochen und mit der nächsten Anweisung fortgefahren, die auf das Schleifenkonsrukt folgt. Ist die Bedingung bereits beim
ersten Durchlauf nicht erfüllt, so wird die Anweisungsfolge innerhalb der
Schleife niemals ausgeführt, da die Prüfung der Bedingung jeweils vor dem
Schleifendurchlauf stattfindet (Abweisende Schleife). Die Wiederholung
ist selbst wiederum eine Anweisung.
• WIEDERHOLE
Folge von Anweisungen
SOLANGE Bedingung
2.4 Schleifen (Wiederholung, Iteration)
39
erfüllt
Do-While-Schleife, d.h. Schleife mit (weiterführendem) Nachtest.
Der Schleifendurchlauf wird wiederum fortgeführt, solange die Bedingung erfüllt ist. Ist die Bedingung bereits beim ersten Durchlauf nicht
erfüllt, so wird die Anweisungsfolge innerhalb der Schleife trotzdem
einmal ausgeführt, da die Prüfung der Bedingung jeweils nach dem
Schleifendurchlauf stattfindet (Aufnehmende Schleife).
• WIEDERHOLE Folge von Anweisungen BIS Bedingung erfüllt
Do-Until-Schleife, d.h. Schleife mit (beendendem) Nachtest.
Der Schleifendurchlauf wird wiederholt, bis die Bedingung erfüllt ist
(dann wird die Schleife abgebrochen.
• FÜR Zähler VON Anfang BIS Ende FÜHRE AUS Anweisungsfolge
For-Schleife, d.h. eine Zählschleife.
Bei dieser Schleifenform wird eine (evtl. erst zur Laufzeit) vorgegebene Anzahl von Schleifendurchläufen absolviert.
Achtung: Für alle Schleifenformen gilt, dass man die Abbruchbedingung so formulieren muss, dass die Schleife in jedem Fall nach endlich vielen Durchläufen
beendet wird. Dies kann auch im Schleifenrumpf geschehen, sofern eine Endlosschleife realisiert wurde. Notfalls muss man eine maximale Anzahl an Schritten
vorgeben. Ebenso ist zu vermeiden, dass die Schleife weiter durchlaufen wird,
obwohl die Schritte in der Schleife nicht mehr ausführbar sind.
Beispiel: Kaffeekochen mit Wiederholung
Schritt 2.1 mit Wiederholungsschleife
2.1.1 WIEDERHOLE
Nimm Kaffeeglas aus dem Fach
BIS Kaffeeglas gefüllt oder keine Gläser mehr vorhanden
2.1.2 FALLS Weiteres gefülltes Kaffeeglas vorhanden
DANN Öffne Kaffeeglas
SONST Brich Kaffeekochen ab
40
2.4.1
Kapitel 2: Algorithmen
Maximumsbestimmung (Bsp. zur 2. bzw. 1. Schleifenart)
Eingabe: Liste von Zahlen
Ausgabe: Größte Zahl in der Liste
Setze erste Zahl der Liste als bislang größte Zahl
WIEDERHOLE
Lies nächste Zahl in der Liste
FALLS Zahl größer als bisher größte Zahl
DANN Setze diese Zahl als bislang größte Zahl
BIS Liste erschöpft
Schreibe die insgesamt größte Zahl raus
Problem: Wenn die Liste nur aus einer Zahl besteht, dann kann die erste
Anweisung des Schleifenrumpfs nicht ausgeführt werden (da dies eine aufnehmende Schleife ist). Ein Prozessor, der diesen Algorithmus ausführen soll,
befindet sich so in einem undefinierten Zustand.
Lösung: Schleife mit Vorabtest (und Auswahl)
Setze erste Zahl der Liste als bislang größte Zahl
SOLANGE weitere Zahl in Liste FÜHRE AUS
Lies nächste Zahl in der Liste
FALLS Zahl größer als bisher größte Zahl
DANN Setze diese Zahl als bislang größte Zahl
Schreibe die insgesamt größte Zahl raus
2.4.2
Berechnung von Potenzen xn (Bsp. für Zählschleife)
Eingabe: Reelle Zahl x, natürliche Zahl n
Ausgabe: xn
Übernimm die Werte von x und n
Setze Produkt auf 1
FÜR k VON 1 BIS n
Multipliziere Produkt mit x
Schreibe Produkt raus
2.5 Funktionen
41
2.5 Funktionen
Funktionen erleichtern die schrittweise Verfeinerung eines Algorithmus und erlauben die wiederholte Verwendung von Algorithmenteilen, ohne diese Teile erneut eingeben zu müssen. Dies ist extrem vorteilhaft, da es Zeit und Platz spart
und den Code sehr viel übersichtlicher macht.
Beispiel: Zeichengerät/Plotter zeichnet zwei konzentrische Quadrate
B
A
ZeichneQuadrat(10, A)
ZeichneQuadrat(15, B)
Verfügbare Funktionen
Funktionalität
forward(x)
Bewege Stift um x Einheiten
vorwärts (in die aktuelle Richtung)
Drehe Zeichenrichtung um α Einheiten nach rechts
Senke Stift auf den Punkt Y und
setze die aktuelle Zeichenrichtung (0 = nach rechts)
Hebe Stift vom Papier ab
right(α)
down(Y ,α)
up
ZeichneQuadrat(x, Y )
down(Y ,0)
FÜR k VON 1 BIS 4 {
forward(x)
right(π/2) }
up
Man sieht hier, dass die Funktion ZeichneQuadrat zwei Argumente erhält. Einen
Rückgabewert hat diese Funktion nicht.
Die hier beschreibene Methode des Zeichnens heisst "Turtle Graphics" (man
stelle sich eine laufende Schildkröte vor, die hinter sich einen in Tinte getränkten
Schwanz herzieht).
42
Kapitel 2: Algorithmen
2.6 Vom Algorithmus zum Programm
Um den Algorithmus durch einen Computer ausführen zu lassen, muss man
ihn in einer Programmiersprache formulieren und diese dann in eine für den
Computer verständliche Sprache, der Maschinensprache, übersetzen.
Computer haben eine eigene, vom Typ der CPU abhängige Maschinensprache,
d.h. eine Menge von Instruktionen und Daten in einem für den Menschen
kaum lesbaren Binärcode (d.h. einer Folge von Bits). Die Assemblersprache
wiederum formuliert die Prozessorbefehle des Maschinencodes als sog. Mnemonics in einer einfachen und leichter zu lesenden Syntax. Die Steuerbefehle
sind durch Zeichen bzw. Worte statt durch pure Bits verschlüsselt, d.h. jede
zulässige Bitfolge wird durch eine oder mehreren Befehlen (sog. Mnemonics)
und evtl. zusätzlichen Zahlen dargestellt. Der Quelltext (welcher die Mnemonics
enthält) wird dann von einem Assembler (d.h. dem Übersetzungsprogramm)
nach der Programmerstellung nahezu eins zu eins in die Maschinensprache
übersetzt. Ein Programm, dass in der Assemblersprache geschrieben ist, ist sehr
schnell. Insbesondere „besitzt“ jeder Prozessortyp seinen eigenen Assemblercode.
Größere Algorithmen sollten trotz des Geschwindigkeitsvorteils nur in Ausnahmefällen (geschwindigkeitskritische Teile des Codes) in Assembler geschreiben
werden, da es zu aufwändig und zu fehleranfällig ist. Deshalb wurden (und
werden) höhere Programmiersprachen entwickelt, die u.a. deutlich einfacher
zu handhaben sind und sehr viel mächtigere Paradigmen besitzen. Zu diesen
gehören z.B.
• imperative bzw. prozedurale Sprachen
C, Pascal, Modula-2, FORTRAN, Basic, Algol, . . .
• imperative objektorientierte Sprachen
Modula-3, Ada, C++, Java, . . .
• funktionale Programmiersprachen
Haskell, ML, OCaml, LISP, . . .
• Programmiersprachen für spezielle Anwendungen
SQL (Datenbanken), HPC Fortran (Parallelrechnen), . . .
Höhere Programmiersprachen sind in der Regel rechnerunabhängig, ihre Beherrschung erfordert die Kenntnis folgender Elemente der entsprechenden Sprache:
• Lexikalischen Elemente, d.h. dem Symbolvorrat der Sprache (Schlüsselwörter und Interpunktionszeichen, Operatoren, Bezeichner, Trenner, Literalkonstanten). Aus dieser Menge bilden sich dann die (korrekt gebildeten)
Programme der Sprache -> analog Wörter, Satzzeichen und Trenner in
menschlicher Sprache.
• Syntax, d.h. welche Folgen von lexikalischen Elementen bilden zulässige
Sätze in der betreffenden Programmiersprache (d.h. Programme, die der
Compiler verstehen und übersetzen kann). Dieses wird durch die Synatx
bzw. Grammatik geregelt -> analog zur menschlichen Sprache.
2.6 Vom Algorithmus zum Programm
43
• Semantik, d.h. Bedeutung und Sinnhaftigkeit formal korrekter Sätze. Sie
beschreibt also, welche Aktion das Programm ausführt oder wie eingegebene
Daten verarbeitet werden.
Algorithmus
?
Programmierung
?
Programm in
höherer
Programmiersprache
?
Übersetzung
?
Programm in
Maschinensprache
?
Interpretation durch CPU
Programm wird ausgeführt
Damit ein Algorithmus auf einem Computer ausgeführt werden kann, muss dieser in einer Programmiersprache implementiert werden. Im nächsten Schritt
muss das entsprechende Programm dann in die Maschinensprache des Prozessors übersetzt werden, d.h. es wird eine Folge von Maschinensprachebefehlen
erzeugt, die der Prozessor versteht und ausführen kann. Dieses geschieht mittels folgenden Übersetzungswerkzeugen:
• Compiler: Der gesamte Quelltext wird übersetzt und ein ablauffähiges Programm wird inkl. Link-Vorgang erzeugt. Beispiel: Übersetzte Rede als Manuskript.
• Interpreter: Es wird je ein Befehl übersetzt und abgearbeitet (Ausführung
ist i.d.R langsamer als compilierter Code). Beispiel: Simultanübersetzung.
44
Kapitel 2: Algorithmen
• Mischformen: Just-In-Time-Compiler, Byte-Code-Interpreter
Merke: Ein Algorithmus ist unabhängig von der Programmiersprache, d.h.
der Algorithmus bleibt gleich, aber seine Implementierung ist i.d.R. von
Programmiersprache zu Programmiersprache verschieden.
Abschließend: Wie formuliere ich ein gegebenes Problem als Algorithmus ?
Wichtig ... Nicht gleich Losprogrammieren, sondern erstmal zurücklehnen und
sich das Problem klar machen (Problemanalyse). Bei größeren Problemen Problem in Teilprobleme zerlegen. Dann einen Grobentwurf des Algorithmus (eine
Strategie) zur Lösung des Problems machen, d.h. grobe Skizze der Vorgehensweise (erstmal umgangssprachlich, aber schon strukturiert). Dann erzeuge feineren
Entwurf (Verfeinerung), der wiederum umgangssprachlich formuliert oder auch
mithilfe grafischer (veranschaulichender) Objekte oder Pseudo-Code erzeugt werden kann. Abschliessend wird dann das Program erstellt, welches dann getestet
wird. Genau genomen heißt das, das man ein ausführbares Programm erstellt
und dieses testet, und dabei evtl. weitere Fehler entdeckt, die man korrigieren
muß. Danach wird dann wieder getestet. Dieser Zyklus kann theoretisch endlos sein, aber bei einem bestimmten (zufriedenstellenden) Qualitätsstand wird
abgebrochen.
45
3 Einführung in C/C++
3.1 Überblick
3.1.1
Geschichte
C++ ist eine der verbreitetsten und industriell wichtigsten Programmiersprachen. Sie ist als Mehrzwecksprache konzipiert und unterstützt insbesondere effiziente und maschinennahe Programmierung, Datenabstraktion sowie prozedurale
bzw. objektorientierte und generische Programmierung.
C++ basiert auf der Programmiersprache C. Zusätzlich zu den in C vorhandenen Möglichkeiten (Schleifen, Funktionen, Zeiger usw.) bietet C++ weitere
Datentypen, Klassen mit Vererbung, Ausnahmebehandlung, Templates (Schablonen), Namensräume, Überladen von Operatoren und Funktionsnamen, und
Operatoren zur Freispeicherverwaltung.
Mit der C++-Standardbibliothek steht zusätzlich eine erweiterte Bibliothek
für verschiedene generische Container, Funktionen zu deren Manipulierung,
Strings, Dateizugriffe, etc. zur Verfügung.
1972 In den Bell Labs entwickeln Ken Thompson und Dennis Ritchie C als
Sprache für Systemprogrammierung unter Unix.
1973 Implementierung von UNIX in C
1978 Ritchie und Kernighan schaffen mit dem Buch „The C Programming Language“ einen Quasi-Standard für C (K&R-Standard).
1989 Der Standard ANSI1 -C vereinheitlicht die verschiedenen C-Dialekte (genannt C89). Dadurch sind C-Programme besonders portabel. Dieser Stan1 American
National Standards Institute
46
Kapitel 3: Einführung in C/C++
dard wird 1990 von der ISO
übernommen ( C90).
2
(bis auf kleinere Änderungen) vollständig
1980 Bei AT&T erweitert Bjarne Stroustrup C zu „C mit Klassen“.
1983 C mit Klassen wird in C++ umbenannt und die Sprache für Anwendungsprogrammierung weiterentwickelt.
1993 Nach einer Vorlage von Hewlett-Packard wird mit der Entwicklung einer
C++-Standardbibliothek3 begonnen.
1998 Die endgültige Fassung der Sprache C++ wird von der ISO4 genormt.
2005 Das C++ standardization committee verabschiedet einen Technical Report
mit Erweiterungen zur C++-Standardbibliothek. Eine weitere Liste ist in
Arbeit.
Keine Panik
C++ ist zwar eine Weiterentwicklung von C, aber C ist keine echte Teilmenge
von C++. In diesem Kurs werden wir sozusagen mit dem C-Teil von C++
beginnen.
Man muss nicht alle Sprachelemente kennen, um zu programmieren. Diese lernt
man nach und nach.
Objektorientiertes Programmieren (Klassen, Vererbung, . . . ) erlaubt strukturiertes Programmieren.
Die STL ist eine mächtige Ergänzung zu C++ und vereinfacht häufige Aufgaben.
......................................................................................................................................................................................
..
..
...
....
...
...
.
...
.................................................................................................................
...
...
...
..
...
.
.
.
.
..
...
.
.
...
.
.
...
.
.
...
.
.
...
.
.
.
....
....
...
....
...
.
...
....
.
....
...
...
....
....
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
..
...
...
...
...
...
...
...
...
...
..
...
...
...
...
...
...
...
...
...
...
...
...
....
...
...
...
...
...
...
...
...
...............................................................................................................
...
...
...
...
..
...
..
...
.....................................................................................................................................................................................
C++
STL
C
Abbildung 3.1: C, C++ und die STL
2 International
Standardization Organization
Standard Template Library
4 International Organization for Standardization
3 STL,
47
3.1 Überblick
3.1.2
Compiler und Tools
Nachdem der Quelltext (engl. source code) mit einem Editor geschrieben wurde,
wird der Quelltext (*.cpp oder *.cc) vom Compiler unter Verwendung eventueller
Headerdateien (*.hh) in eine oder mehrere Zwischendateien (*.o, Objekt-Datei)
übersetzt. Der Linker bindet die Zwischendateien mit weiteren Bibliotheken zu
einem lauffähigen Programm, wobei Bibliotheken eine Sammlung bereits übersetzter Funktionen (z.B. sin aus der Standardmathebibliothek) sind. Falls sich im
Quelltext ein Fehler befindet, so bricht der Compiler den Übersetzungsvorgang
ab und gibt einen Fehler aus, welchen man dann wieder im Quelltext bereinigen
muß. Dieser Editier-Übersetzungszyklus erfolgt so lange, bis der Compiler den
Übersetzungsprozess einwandfrei beendet hat. Das auf den Übersetzungsprozess
folgende Linken erzeugt das ausführbare Programm (sofern er alle Dateien und
Bibliotheken gefunden hat). Übersetzung und Linken werden in dem von uns benutzten Compiler (scheinbar) in einem Schritt durchgeführt. Kleine Programme,
die nur aus einer Datei bestehen, können in einem Schritt verarbeitet werden.
Wie man größere Projekte handhabt, werden wir im SoSe 2007 sehen.
Compiler
.....
........................................................................................................
.....
......
............
..... ..
... ... ...
...
...
...
...
...
...
...
.....................................
Linker
.....
.................................................................................................
.....
....
............
.......
... .... ...
...
....
..
...
...
...
...
...
...
...
...
...
......................................
ausführbares
Programm
Bibliotheken
Abbildung 3.2: Compiler und Linker
Beliebte Compiler
GNU-g++
Intel C++
Microsoft Visual C++
IDE
Kommandozeilencompiler; sehr beliebt und mächtig!
(kostenlos)
Dieser Kommandozeilencompiler erzeugt hocheffizienten Code für Intel-Prozessoren (Windows, Linux)
Verbreitetster Compiler unter Windows. Einfache
Version auch kostenlos erhältlich.
48
Kapitel 3: Einführung in C/C++
Größere Programme werden in der Regel nicht mehr mit einfachen Texteditoren
erstellt, sondern mit Integrierten Entwicklungsumgebungen (IDE),
• die grundsätzlich aus den Programmierwerkzeugen Editor, Compiler, Linker und Debugger bestehen,
• die mehrere Quelltexte bzw. große Projekte verwalten können,
• die Quelltexte übersetzen und Programme erstellen,
• die Sprachelemente farblich markieren,
• Quelltexte einheitlich formatieren,
• die grafische Benutzeroberflächen leicht erstellen lassen,
• die die Fehlersuche vereinfachen (komfortable Debugger).
Verbreitete IDEs sind KDevelop für Linux und Microsoft Visual C++ und
MinGW-DEveloper-Studio für Windows (Entwicklung von MinGW liegt im
Moment auf Eis).
Hinweis für die Übungen SS2009:
Die Entwicklungsumgebung unter Mac OS X heisst Xcode. Der Editor von
Xcode wird standardmäßig aufgerufen, wenn ein C / C++ Datei mit einem
Doppelklick geöffnet wird. Der Editor ist recht intuitiv zu verwenden und führt
automatisch Formatierungen und eine farbliche Hervorhebung verschiedener
Syntaxelemente durch. Das Compilieren kann man mit g++ von der Komandozeile aus erledigen. Für unsere Zwecke ist dieses im Prinzip der einfachste
Weg. In der Vorlesung wird kurz besprochen, wie Xcode auch zum Compilieren
der Übungsprogramme verwendet werden kann.
Literaturauswahl und Links
• Bjarne Stroustrup: „Die C++-Programmiersprache“, Addison-Wesley,
2000. (Klassiker von Mr. C++ persönlich)
• Martin Schader, Stefan Kuhlins: „Programmieren in C++“, SpringerVerlag, 1998. (Sehr gut für Anfänger und Fortgeschrittene, u.a. da viele
Aufgaben mit Lösungen)
• Kyle Loudon: „C++ kurz & gut“, O’Reilly, 2003. (Nachschlagewerk)
• Ray Lischner: „STL kurz & gut“, O’Reilly, 2004. (Nachschlagewerk)
• Kernighan, Brian; Ritchie, Dennis: „Programmieren in C“, HanserVerlag.
3.1 Überblick
49
• Krüger, Guido: GoTo C-Programmierung, Addison-Wesley, 1998.
• C Von A bis Z (http://www.pronix.de/pronix-4.html)
(siehe auch http://www.galileocomputing.de und dort Menüpunkt
„openbooks“.)
Online-Resourcen
• Tutorial C++: http://www.mathematik.uni-marburg.de/∼cpp
• Tutorial C++: http://www.volkard.de/Cpp/index.html
• Tutorial C++: http://de.wikibooks.org/wiki/C++-Programmierung
• Referenz C++: http://www.cppreference.com
• Referenz C++: http://www.cplusplus.com
• Alles mögliche über C/C++: http://www.c-plusplus.de/cms/
• STL: http://www.sgi.com/tech/stl
• Tutorial C: http://wwwuser.gwdg.de/∼kboehm/ebook/inhalt.html
• Tutorials C: http://www.c-plusplus.de/cms/ (siehe unter Tutorials/C
z.B. „Einführung in die Sprache C“ (!!) oder „Softwareentwicklung in C“)
• Linkliste: http://www.rz.uni-freiburg.de/bera/c-kurs/scripten.html
• Xcode: http://developer.apple.com/documentation/DeveloperTools/
Conceptual/XcodeQuickTour/qt_intro/qt_intro.html
3.1.3
Das nullte und kürzeste C++-Programm
Ein minimales C++-Programm sieht so aus:
zero.cpp: Das nullte und kürzeste C++-Programm
1
2
3
// zero . cpp : Das nullte C ++ - Programm
int main () {}
Das Programm schreiben wir in einem Texteditor, speichern es unter obigem
Namen und übersetzen es dann mit dem Compiler g++:
$ g++ zero.cpp
50
Kapitel 3: Einführung in C/C++
Dies erzeugt das Programm a.out, das man mit
$ a.out
bzw.
$ ./a.out
ausführen kann. Probiere zuerst die erste Version, dann die 2. (falls die 1. nicht
funktioniert). Wir sehen: Beim Programmaufruf passiert (natürlich) nichts.
Prinzipiell besteht der C++-Quelltext (C++-source code) aus Kommentaren,
Präprozessorbefehlen und Anweisungen, welche wiederum aus Schlüselwörtern
und Interpunktionszeichen, Operatoren, Literalen (Konstanten), Trennern und
Bezeichnern (gehören alle zu den lexikalischen Elementen) bestehen.
An diesem kleinen Programm erkennt man schon einige der oben genannten
Bausteine der C++-Syntax:
• Kommentare sollen das Verstehen und Nachvollziehen des Programms
vereinfachen. Der Compiler ignoriert Programmteile von // bis zum
Zeilenende. Alternativ können auch Kommentare durch /*
*/ umschlossen werden, die dann auch über mehrere Zeilen gehen können
( Blockkommentare).
Benutzt Kommentare reichhaltig, denn unkommentierte Programme sind
später häufig nur noch schwer bzw. gar nicht zu verstehen!
• Schlüselwörter sind in C++ vordefinierte bzw. reservierte Wörter mit
bestimmten Bedeutungen. Diese dürfen vom Programmierer nicht als Bezeichner für z.B. Variablen benutzt werden. Wichtige Beispiele für C++Schlüsselwörter sind
– if-else, switch-case-default,
– while, do-while, for,
– bool, char, short, int, long, float, double, void, signed, unsigned,
– const, sizeof
– struct, class, union, enum, typedef
– private, public, protected,
– return, continue, break, goto, try-catch
– new, delete, true, false.
In unserem Programm findet sich mit int ein Schlüsselwort.
• Wichtige Interpunktionszeichen sind
– ‘,‘ , ‘;‘ , ‘:‘ ,
– ‘(‘ , ‘)‘ , ‘‘ , ‘‘ , ‘<‘ , ‘>‘.
3.2 Hello, world!
51
mit denen z.B. Anweisungen abgeschlossen oder in Blöcken zusammengefaßt werden. In diesem Programm sind die (geschweiften) Klammern zu
finden.
• Operatoren führen verschiedene Operationen auf ihren Argumenten (Operanden) aus. C++ besitzt u.a. folgende Operatoren:
– ‘+‘ , ‘-‘ , ‘*‘ , ‘/‘ , ‘%‘ ,
– ‘=‘ , ‘ !=‘ , ‘>‘ , ‘<‘ , ‘<=‘ , ‘>=‘ , ‘«‘, ‘»‘,
– ‘&&‘ , ‘||‘ , ! (Negation).
• Trenner sind z.B. Leerzeichen, Tabulatoren, Zeilenendezeichen, Seitenvorschübe und Kommentare. Sie dienen dazu, die übrigen lexikalischen Elemente voneinander zu trennen, d.h. auf aufeinanderfolgenden Bezeichnern
oder Schlüsselwörtern muss immer ein Trenner folgen.
• Bezeichner sind Namen, die man an Variablen, Konstanten, Funktionen
oder Strukturen (bzw. Klassen) im Programm vergibt. Sie müssen mit einem Buchstaben oder Unterstrich beginnen, während sich daran eine Folge
von Groß- und Kleinbuchstaben (die unterschieden werden) sowie Ziffern
von 0 bis 9 und Unterstriche anschließen können. Bezeichner können beliebig lang sein.
• C++-Programme bestehen u.a. aus Funktionen (hier int main()), die
sich gegenseitig aufrufen können. Jede Funktion löst eine bestimmte Aufgabe. Funktionen sind entweder selbst erstellt oder bereits geschrieben und
Teil der Standard-C++-Bibliothek oder einer anderen Bibliothek. Funktionen liefern i.A. Rückgabewerte (hier vom Typ int; Ausnahme void) und
ihnen können Argumente übergeben werden (hier nicht).
• In jedem C++-Programm gibt es die Funktion main, bei der das Programm
beginnt. Diese Funktion bildet das steuernde Element des Programms. Sie
wird automatisch vom System aufgerufen, wenn das Programm startet.
Ein Programm kann nur genau eine main-Funktion besitzen.
• Zusammengehörige Programmblöcke werden durch {
fasst.
} zusammenge-
• Das Programm endet, wenn man aus der Funktion main zurückkehrt bzw.
wenn die Funktion main beendet ist. Der Rückgabewert von main (int),
der an das Betriebssystem zurückgegeben wird, ist 0 („success“, d.h. fehlerfreier Programmlauf ), wenn nichts anderes angegeben ist.
3.2 Hello, world!
Als Erstes sollen die Worte „Hello, world!“ auf dem Bildschirm erzeugt werden.
52
Kapitel 3: Einführung in C/C++
hello.cpp: Hello world in C++, erster Versuch
1
2
3
4
5
# include < iostream >
int main () {
std :: cout << Hello , world !;
}
• Für die Textausgabe auf der Standard-Ausgabe ( standard output, d.h.
dem Bildschirm), gibt es den Standardausgabestream -bzw. strom
std::cout, der von C++ zusammen mit anderen Standard- Funktionen
zur Ein-/Ausgabe in dem Modul iostream zur Verfügung gestellt.
• Mit der Präprozessoranweisung5 #include <iostream> wird das Modul in den Programmtext textuell vom Compiler bzw. Präprozessor eingefügt.
• Der Operator („nach“) schreibt das, was rechts steht, in das was links
steht, d.h. in unserem Fall wird der Text nach std::cout geschrieben.
• (Fast) Jeder C++-Befehl bzw. jede C++-Funktion muss durch ein Semikolon abgeschlossen werden.
• C++ unterscheidet zwischen Groß- und Kleinschreibung. Alle Befehle werden prinzipiell klein geschrieben.
Wir versuchen das Programm zu übersetzen:
$ g++ hello.cpp
hello.cpp: In function ‘int main()‘:
hello.cpp:4: error: ‘Hello‘ was not declared in this scope
hello.cpp:4: error: ‘world‘ was not declared in this scope
hello.cpp:4: error: expected ‘;‘ before ‘!‘ token
Wo liegt der Fehler? Wir wollen als Argument von std::cout eine StringKonstante übergeben, die aber durch Anführungszeichen gekennzeichnet sein
muss.
hello1.cpp: Hello world in C++, zweiter Anlauf
1
2
3
4
5
# include < iostream >
int main () {
std :: cout << " Hello , ␣ world ! " ;
}
$ g++ hello1.cpp
$ a.out
Hello, world!$
5 Präprozessoranweisungne beginnen immer mit Doppelkreuz und stehen am Zeilenanfang.
Ohne Semikolon. Siehe 3.9.
3.3 Fundamentale Datentypen
53
Warum erscheint das Prompt $ direkt hinter dem ausgegebenen Text? In dem
String, der std::cout übergeben wird, muss noch ein Befehl zum Zeilenumbruch
eingebaut werden. Dies geschieht mit dem Steuerbefehl std::endl. (Bem: Steuerbefehle dienen der Steuerung von Ausgabegeräten, wie z.B. Bildschirm oder
Drucker.)
hello2.cpp: Ein fast perfektes Hello world
1
2
3
4
5
# include < iostream >
int main () {
std :: cout << " Hello , ␣ world ! " << std :: endl ;
}
$ g++ hello2.cpp
$ a.out
Hello, world!
$
3.3 Fundamentale Datentypen
Wie schon im ersten Kapitel erläutert wurde, ist der Arbeitsspeicher in Form
von Speicherzellen organisiert und sequentiell durchnumeriert (jede Speicherzelle repräsentiert 1 Bit). Da es aber nicht sehr komfortabel ist, die Speicherplätze
selber zu verwalten und über ihre eindeutige Nummer anzusprechen, werden
ihnen vom Compiler „Namen gegeben“ (etwas vereinfacht und blumig gesprochen). Diese benannten Speicherplätze werden als Variablen bezeichnet (wobei
diese immer ein Vielfaches von 8 Bit (=1 Byte) belegen).
Falls eine neue Variable definiert wird, so vergibt der Compiler automatisch den nächsten freien Platz (abhängig von der Größe bzw. Datentyp der
Variablen). Der Typ einer Variablen gibt an, was in ihr gespeichert wird. Damit
ist auch festgelegt, wieviel Speicherplatz für die Variable reserviert wird (in
Byte = 8 Bit) und welche Operationen sie erlaubt. C++ ist streng typisiert,
d.h. alle Variablen und Funktionen haben einen vom Programmierer genau
festgelegten Typ, der bestimmt, welchen Wert die Variable annehmen darf und
welche Operationen auf ihnen erlaubt sind.
Die fundamentalen Datentypen von C++ lauten:
• Ganzzahlige bzw. Integer-Typen
• Fließkomma- bzw. Float-Typen
• void-Datentyp (können Variablen nicht annehmen)
54
Kapitel 3: Einführung in C/C++
Integer- und Float-Typen können die unendlichen Mengen Z und R nicht vollständig darstellen.
In ?? werden wir aus diesen fundamentalen bzw. elementaren Typen komplexere
Datentypen aufbauen.
3.3.1
Deklaration
Variablen müssen vor ihrer Benutzung deklariert werden. Dadurch verbindet
der Compiler den verwendeten Namen mit dem zugewiesenen Datentyp. Die
Deklaration einer Variablen muss erfolgen, bevor sie zum ersten Mal benutzt
wird (in der Regel wird dieses am Anfang einer Funktion oder eines Blocks
erfolgen - muss aber nicht). Eine Deklaration hat die Form:
Typbezeichnung Variablenname ;
Hier wird dem Compiler weiterhin gleichzeitig angewiesen, dass für diese
Variable auch Speicherplatz reserviert werden soll und dieser Speicherplatz soll
an die Variable geknüpft sein (Definition der Variablen).
Bei der Deklaration kann die Variable auch initialisiert werden. Der Wert, der
ihr dabei zugewiesen wird, wird mit dem Zuweisungsoperator = angegeben:
Typbezeichnung Variablenname = Wert ;
Solange Variablen nicht initialisiert wurden, haben sie einen undefinierten und
unvorhersagbaren Wert. (Vorsicht! Werden uninitialisierte Variablen benutzt, so
kann das zu merkwürdigem Programmverhalten führen !)
Erfordert der Ausdruck auf der rechten Seite des Zuweisungsoperators eine Berechnung, so wird dieser zunächst ausgewertet und erst dann der Variablen zugewiesen.
Vor dem Zuweisungsoperator darf nur ein Variablenname stehen.
declare.cpp: Deklaration von Variablen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# include < iostream >
int main () {
int a ;
int b ;
int c ;
a = 12;
b = 88;
c = a + b;
std :: cout << " Die ␣ Zahlen ␣ lauten ␣ " << a << " ,␣ "
<< b << " ␣ und ␣ " << c << " . " << std :: endl ;
}
3.3 Fundamentale Datentypen
55
$ g++ declare.cpp
$ a.out
Die Zahlen lauten 12, 88 und 100.
$
declare2.cpp: kürzere Deklaration
1
2
3
4
5
6
7
8
3.3.2
# include < iostream >
int main () {
int a =12 , b =88 , c = a + b ;
std :: cout << " Die ␣ Zahlen ␣ lauten ␣ " << a << " ,␣ "
<< b << " ␣ und ␣ " << c << " . " << std :: endl ;
}
Ganzzahltypen
In C++ gibt es mehrere Datentypen, die verschiedene endliche Teilmengen von
Z repräsentieren. Der Speicherplatz für Variablen wird in Blöcken von 8 Bits (1
Byte) reserviert. Mit 1 Byte können genau 28 = 256 Zustände dargestellt werden, z.B. die Zahlen {0, 1, . . . , 255}. Standardmäßig verwendet C++ jedoch die
vorzeichenbehafteten Zahlen {−128, −127, . . . , 127}. Die folgenden Typen stehen
in C++ zur Verfügung:
Typ
char
unsigned
(signed)
unsigned
(signed)
unsigned
(signed)
unsigned
char
short
short
int
int
long
long
Bytes
1
1
2
2
4
4
4
4
Zahlenbereich
−128 . . . 127
0 . . . 255
−32768 . . . 32767
0 . . . 65535
−2147483648 . . . 2147483647
0 . . . 4294967295
−2147483648 . . . 2147483647
0 . . . 4294967295
Bei char ist nicht festgelegt, ob er mit oder ohne Vorzeichen implementiert ist,
d.h. dies ist vom Compiler abhängig.
Durch Angabe von unsigned und dem optionalen Schlüsselwort signed wird
zwischen vorzeichenlosen und vorzeichenbehafteten Zahlen gewählt.
Der Speicherbedarf der Typen ist nicht durch einen Standard festgelegt,
sondern abhängig vom Compiler. Meist entspricht er aber dieser Tabelle. Es
muss aber folgende Beziehung immer gelten:
1 = size(char) ≤ size(short int) ≤ size(int) ≤ size(long int),
56
Kapitel 3: Einführung in C/C++
wobei size die Größe des Datentyps in Vielfachem vom Typ char angibt.
3.3.3
Grundrechenarten
Die Symbole +,-,*,/ werden für die Grundrechenarten benutzt, wobei * und
/ stärker binden als + und - (Punkt vor Strich-Regel). Durch Gruppierung mit
runden Klammern (
) lässt sich die Reihenfolge der Auswertung explizit festlegen.
Bei Integer-Variablen entspricht / der Ganzzahldivision, also 3/2=1 oder
-9/2=-4.
rechnen.cpp: Rechnen mit ganzen Zahlen
1
2
# include < iostream >
3
4
5
6
7
8
int main () {
int a ,b ,c , d ;
9
10
11
12
13
14
a
b
c
d
a
=
=
=
=
=
3*2;
c = a * 2;
c * 2;
c / (a -1);
b * d + c;
std :: cout << " Das ␣ Ergebnis ␣ ist ␣ " << a
<< " . " << std :: endl ;
}
Wie kommt das Ergebnis zustande?
Anweisung
a = 3*2;
b = c = a * 2;
c = c * 2;
d = c / (a-1);
a = b * d + c;
a
6
6
6
6
72
b
c
d
12
12
12
12
12
24
24
24
4
4
Den Rest der Ganzzahldivision erhält man mit dem Modulo-Operator %, z.B.
int a =13 , b =4;
int quot = a / b ;
int mod = a % b ;
// quot = 3
// mod = 1
An jeder Programmstelle, an der eine Variable stehen kann, darf auch ein berechenbarer Ausdruck oder eine Konstante stehen, ausgenommen links von einer
Zuweisung (d.h. kein a+b=c). Die Zeilen
3.3 Fundamentale Datentypen
57
c = a + b;
std :: cout << a <<" ␣ plus ␣ " << b
<< " ␣ gleich ␣ " << c ;
lassen sich auch kürzer schreiben, falls c nicht weiter benötigt wird:
std :: cout << a <<" ␣ plus ␣ " << b
<< " ␣ gleich ␣ " << a + b ;
3.3.4
Bereichsüberschreitung
Was passiert bei Überschreitung des Zahlbereichs bei ganzzahligen Datentypen
(overflow)?
Dies ist im Standard nicht festgelegt, in der Regel wird modulo weitergerechnet.
Sind z.B. a und b vom Typ unsigned short, d.h. 0 ≤ a,b < 65536 = 216 , dann
unsigned short a = 60000;
unsigned short b = 2* a ;
// b =120000 mod 2^16 = 54464
Manchmal wird aber auch eine Fehlermeldung ausgegeben. Aber am besten beim
jeweiligen Compiler ausprobieren!
3.3.5
Zuweisungsoperatoren
Die Zuweisungsoperatoren =, +=, -=, *=, /=, %= dienen der verkürzten
Schreibweise. Beispielsweise wird bei
i += 5;
der Ausdruck rechts von += ausgewertet und zu i hinzuaddiert. Dabei muss die
Variable i aber nur einmal ausgewertet werden. Das Endergebnis ist das gleiche
wie bei
i = i + 5;
Weitere Beispiele:
w *= 3 + v ;
z += w / v ;
z %= w - v ;
// w = w * (3 + v );
// z = z + ( w / v );
// z = z % ( w - v );
58
3.3.6
Kapitel 3: Einführung in C/C++
Inkrement- und Dekrementoperatoren
Eine weitere Kurzschreibweise bieten die Operatoren zum Inkrement (erhöhe
Wert um Eins) und Dekrement (erniedrige Wert um Eins):
z ++;
z - -;
++ z ;
--z ;
//
//
//
//
z
z
z
z
=
=
=
=
z
z
z
z
+
+
-
1;
1;
1;
1;
Die Operatoren in der Schreibweise z++, z-- sind Postfix-Operatoren, d.h. die
Variable wird erst benutzt, dann in- bzw. dekrementiert. Operatoren in ++z, --z
sind dagegen Präfix-Operatoren: die Variable wird erst in- bzw. dekrementiert,
dann benutzt. Ihre Verwendung liefert unterschiedliche Ergebnisse, z.B.
int x =42 , y =42;
std :: cout << " x ++ ␣ " << x ++ << std :: endl ;
std :: cout << " ++ y ␣ " << ++ y << std :: endl ;
x++ = 42
++y = 43
Diese Operatoren sollten nur für Zähler eingesetzt werden (z.B. für die In- bzw.
Dekrementierung be der for-Schleife), nicht für Addition oder Subtraktion innerhalb einer Berechnung. Das erhöht die Lesbarkeit und hilft, Fehler zu vermeiden.
3.3.7
Literale
Integerzahlen können auch direkt angegeben werden (4711). Der Compiler stellt
dafür einen Speicherplatz zur Verfügung, der zur Größe des ganzzahligen Literals
(bzw. ganzzahliger Konstante) passt. Durch die Endung l oder L können Zahlen
als long Zahlen definiert werden. Durch u oder U als unsigned int oder unsigned
long int, je nach Größe des Literals. 4711L wird also bereits als long angelegt.
Hexadezimalzahlen muss man nicht erst in Dezimalzalhen umrechnen, um sie im
Programm zu verwenden. Steht 0x vor einer Zahl, wird sie automatisch im 16ersystem interpretiert, z.B. 0x2a (4210 ). Entsprechend werden mit 0 Oktalzahlen
eingeleitet, z.B. 060 (4810 ).
3.3.8
Bezeichner
In C++ müssen Variablennamen (bzw. allgemein alle Bezeichner) folgenden
Regeln genügen:
3.3 Fundamentale Datentypen
59
• Sie müssen aus Buchstaben (a-z, A-Z), Ziffern (0-9) und Unterstrichen
(_) bestehen.
• Sie dürfen nicht mit einer Ziffer beginnen.
• Sie dürfen sich nicht mit Schlüsselwörtern decken.
• Groß- und Kleinschreibung wird unterschieden.
• Verboten sind Bezeichner, die mit einem Unterstrich anfangen und dann
von einem Großbuchstaben gefolgt werden.
Achtung: Viele Bezeichner mit Unterstrichen am Anfang werden von Compilern bereits genutzt. Z.B. bezeichnet __LINE__ die aktuelle Programmzeile im
Quelltext. Verboten sind auch Bezeichner, die mit einem Unterstrich anfangen
und dann von einem Großbuchstaben gefolgt werden, da solche Bezeichner für
andere Dinge genutzt werden.
Zur besseren Lesbarkeit werden Bezeichner aus mehreren Wörtern in der Regel
mit Unterstrich getrennt oder die Anfangsbuchstaben der Wörter groß geschrieben:
int die_erste_Zahl ;
int dieErsteZahl ;
3.3.9
Fliesskommatypen
Die interne Darstellung reeller Zahlen erfolgt auf dem Computer in der Form
±0.z1 z2 . . . zt · B e
mit der Basis B = 2, Ziffern zi ∈ {0, . . . , B − 1} mit z1 6= 0 und einem beschränkten Exponenten e ∈ Z.
Wieviele Bits zur Verfügung gestellt werden, um das Vorzeichen, die Ziffern
und den Exponenten abzuspeichern, ist implementationsabhängig. Der Standard
schreibt vor, dass die Bereiche
[−1038 , −10−38 ] ∪ {0} ∪ [10−38 , 1038 ]
abgedeckt sind. Man beachte aber, dass immer nur endliche Teilmengen von
R dargestellt werden können, d.h. die Mengen der Fließkommazahlen auf dem
Computer sind nach oben und unten beschränkt und außerdem von endlicher
Genauigkeit. Reelle Zahlen können deshalb (praktisch) nie exakt als Fließkommazahlen auf dem Computer dargestellt werden, sondern es sind Rundungen
erforderlich. Bei Berechnungen mit Fließkommazahlen hat man mit den entstehenden Rundungsfehlern zu kämpfen.
genauigkeit.cpp: Rundungsfehler
1
# include < iostream >
60
2
3
4
5
6
7
8
9
10
11
12
Kapitel 3: Einführung in C/C++
int main (){
float a = 1000012000 , c =2.0 e -06;
double b = 1000012000 , d =2.0 e -06;
std :: cout . precision (10);
std :: cout << a << " ␣ " << b << " ␣ "
std :: cout << c << " ␣ " << d
<< std :: endl ;
}
1000012032 1000012000
1.999999995e-06 2e-07
In C++ gibt es drei Datentypen für Fließkommavariablen, wobei die folgenden
Werte abhängig vom Compiler sein können (hier wurde der g++ benutzt):
Typ
float
double
long double
Bytes
4
8
12
Zahlenbereich
±3.40282 · 10±38
±1.79769 · 10±308
±1.18973 · 10±4932
Anzahl der Stellen
6
15
18
Die Anzahl der Stellen bedeutet hier, dass z.B. die ersten 6 Stellen der Zahl
signifikant bzw. exakt sind, nicht aber, dass die ersten 6 Nachkommastellen
exakt sind.
Fließkommaliterale werden durch einen Dezimalpunkt gekennzeichnet, z.B.
3.1415, .120, -12.. Für Zahlen anderer Größenordnungen kann nach einem
e die Zehnerpotenz angegeben werden (wissenschaftliche Notation):
double alter_universum = 13.77 e9 ;
double radius_atomkern = 1.0 e -14;
// 13.77*10^9
// 10^ -14
Standardmäßig wird der Speicherplatz für ein double reserviert. Durch das Suffix f oder F wird ein float angelegt, durch l bzw. L ein long double.
Die Grundrechenarten erfolgen in natürlicher Art und Weise durch die Operatoren +,-,*,/. Die Zuweisungsoperatoren +=,-=,*=,/= können auch benutzt
werden.
3.3.10 Typumwandlungen
Bei den Grundrechenarten können auch Variablen unterschiedlicher Datentypen
verknüpft werden, das Ergebnis ist dann vom „höherwertigen“ Typ. Auch bei
Zuweisungen können Typen vermischt werden. Dies ist möglich, weil implizit
eine automatische Typumwandlung vorgenommen wird (wobei der Compiler evtl.
3.3 Fundamentale Datentypen
61
eine Warnung liefert, die aber ignoriert werden kann (im Gegensatz zur richtigen
Fehlermeldung), wenn diese Typumwandlung gewollt ist).
int pi = 3.1415;
double a = 1/100.;
// pi = 3
// a = 0.01
Die implizite Typumwandlung (engl.: implicit cast)birgt aber auch Gefahren:
double a , b ;
b = 1/2* a ;
Unabhängig vom Wert von a gilt wegen der Ganzzahldivision 1/2=0 immer b=0.
double a , b ;
b = 1./2* a ;
// jetzt ist b = a /2.
Nun ist der Typ vom Divisionsausdruck auch double.
Zur expliziten Typumwandlung kann (und sollte) man die cast-Operatoren
benutzen. Der neue Ausdruck erhält den angegebenen Datentyp, aber die
ursprüngliche Variable behält ihren Typ.
int a =3 , b =2;
double x = ( double ) a / ( double ) b ; // 3.0/2.0=1.5
double y = ( double )( a / b );
// 3/2=1 , d . h . 1.0
Im obigen Beispiel:
double a , b ;
b = ( double )1/2* a ;
// b = a /2.
3.3.11 Konstanten
Variablen kann man jederzeit im Programm neue Werte zuweisen. Ist bekannt,
dass sich der Wert einer Variable im Verlauf des Programms nicht ändern wird,
deklariert man sie als Konstante.
Dann darf dieser Variablen nur in der Definition ein Wert zugewiesen werden.
Wird dieses im Laufe des Programms wiederum versucht, so bricht der Compiler
den Übersetzungsprozeß mit einer Fehlermeldung ab.
const int MAX_ZEILEN = 256;
const double EulerZahl = 2.718281828459;
const double PI = 3 . 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 6 ;
π ist übrigens bereits als M_PI in <cmath> definiert.
62
Kapitel 3: Einführung in C/C++
3.4 Ein- und Ausgabe
C++ selbst verfügt über keine direkte Möglichkeit der Ein- und Ausgabe. Stattdessen bietet die C++-Standardbibliothek einfachen Zugriff auf Kommunikation
über die Konsole oder über Dateien.
Dabei wird die Ein- und Ausgabe als Datenstrom (Stream) interpretiert: Die
Zeichenfolgen fließen also auf die Konsole oder aus einer Datei.
Die Standardbibliothek legt alle ihre Elemente im Namensraum std ab. Um darauf zuzugreifen, wird der Sichtbarkeitsoperator :: verwendet: std::cout heißt,
dass die Standardausgabe cout im Namensraum std, also in der Standardbibliothek liegt.
Alternativ kann mit
using namespace Namensraum ;
auch angegeben werden, dass Ausdrücke automatisch in einem bestimmten Namensraum gesucht werden.
3.4.1
Standardausgabe
Mit std::cout werden Daten auf der Standardausgabe ausgegeben. Normalerweise ist das die Konsole bzw. der Bildschirm. Durch die Linux-Operatoren >
lässt sich die Ausgabe auch direkt in eine Datei umlenken.
$ a.out > hello.txt
$ cat hello.txt
Hello, world!
$
Im Programm hello.cpp deutet der Ausgabeoperator << dabei die Richtung an,
in der die Informationen fließen: von rechts nach links in das std::cout (wobei
cout console output bedeutet).
Auf diese Weise können alle fundamentalen Typen ausgegeben werden.
Auch mehrere Ausgaben lassen sich aneinanderreihen.
cout << " Das ␣ Ergebnis ␣ von ␣ " << k
<< " ␣ plus ␣ " << d
<< " ␣ lautet ␣ " << k + d ;
Zusätzlich stehen diverse Formatierungsanweisungen zur Verfügung: Eine neue
Zeile wird mit std::endl begonnen. Dabei wird gleichzeitig der Ausgabepuffer
geleert, und die Daten werden auch wirklich abgeschickt.
Nur den Puffer leert man mit std::flush.
In 3.10 stehen weitere Formatierungsanweisungen.
3.4 Ein- und Ausgabe
3.4.2
63
Standardeingabe
Die Standardeingabe std::cin wird wie std::cout im Modul <iostream> erklärt. Sie nimmt Eingaben von der Tastatur entgegen und speichert sie in den
entsprechenden Variablen ab.
eingabe.cpp: Eingabe mit std::cin
1
2
3
4
5
6
7
8
9
10
11
12
# include < iostream >
using namespace std ;
// Erspart uns das std ::
int main () {
int year ;
double size ;
char letter ;
const double ft = 3.2808;
cout << " Bitte ␣ gib ␣ dein ␣ Geburtsjahr ␣ ein : ␣ " ;
cin >> year ;
13
14
15
16
17
18
19
20
21
22
cout << " Bitte ␣ gib ␣ deine ␣ Körpergröße ␣ in ␣ Metern "
<< " ␣ und ␣ den ␣ A nfangsbu chstaben ␣ deines "
<< " ␣ Namens ␣ ein : ␣ " ;
cin >> size >> letter ;
cout << " Hallo , ␣ " << letter << " ! " << endl
<< " Du ␣ bist ␣ " << 2007 - year << " ␣ Jahre ␣ alt ␣ und ␣ "
<< size * ft << " ft ␣ groß . " << endl ;
}
Der Operator >> deutet wieder die Richtung des Datenstroms an, nämlich von
cin in die Variablen.
Bitte gib dein Geburtsjahr ein: 1777
Bitte gib deine Körpergrö"se [...] ein: 1.87 M
Hallo, J!
Du bist 229 Jahre alt und 6.1679ft gro"s.
Falsche Eingaben (Text, wo eine Zahl erwartet wird) führen zu unterwarteten
Ergebnissen! Der Programmierer muss Eingaben nach Korrektheit prüfen (siehe
if-Anweisungen).
Unter Linux kann man den Eingabestrom auch mit < von einer Datei holen:
$ cat person.txt
1956
1.60
A
$ eingabe < person.txt
Bitte gib dein Geburtsjahr ein: Bitte gib [...] ein: Hallo, A!
Du bist 50 Jahre alt und 5.24928ft gro"s.
64
Kapitel 3: Einführung in C/C++
Genauso lässt sich jede andere Ausgabe mit | in das Programm weiterleiten:
$ cat person.txt | eingabe
Bitte gib dein Geburtsjahr ein: Bitte gib [...] ein: Hallo, A!
Du bist 50 Jahre alt und 5.24928ft gro"s.
3.4.3
Standardfehlerausgabe
Ein weiterer Stream steht für die Ausgabe von Fehlern zur Verfügung. Wenn die
normale Ausgabe in eine Datei umgelenkt wird, erscheinen die Fehler immer
noch auf dem Bildschirm (oder umgekehrt; hier nicht demonstriert).
fehler.cpp: Fehlerausgabe mit std::cerr
1
2
3
# include < iostream >
using namespace std ;
4
5
6
7
int main () {
cout << " Hello , ␣ world ! " << endl ;
cerr << " No ␣ response ! " << endl ;
}
$ fehler
Hello, world!
No response!
$ fehler > out.txt
No response!
%$ fehler 2> out.txt
%Hello, world!
# Hello, world! steht in out.txt
# No response! steht in out.txt
3.5 Anweisungen zur Auswahl
3.5.1
Boolesche Werte
Mit Booleschen Werten steht in C++ ein weiterer Datentyp für logische Aussagen zur Verfügung. Sie haben den Typ bool und können nur zwei Werte annehmen: true und false.
3.5 Anweisungen zur Auswahl
65
Dabei ist false als 0 definiert. Alle anderen Werte (also insbesondere 1) gelten als true. Die explizite Benutzung von true und false ist jedoch besser zu
verstehen.
bool a = false ; // direkt angeben
bool b = 42;
// 42 wird umgewandelt in true ,
// da nicht null .
3.5.2
Logische Operatoren
Boolesche Werte (und nach einem cast auch alle anderen Typen) können durch
die logischen Verknüpfungen Konjunktion (AND) und Disjunktion (OR) miteinander kombiniert werden. Die Negation kehrt den Wahrheitswert um.
Die Operatoren stehen jeweils auch als ausgeschriebnes Schlüsselwort zur Verfügung.
Operator
a && b
Schlüsselwort
a and b
a || b
a or b
!a
not a
Ergebnis
Wert ist genau dann true, wenn beide
Operanden true sind.
Wert ist genau dann true, wenn mindestens ein Operand true ist.
Wert ist genau dann true, wenn der
Operand false ist.
Die Wertetabelle listet die Ergebnisse der möglichen Kombinationen auf:
a
false
false
true
true
3.5.3
b
false
true
false
true
a && b
false
false
false
true
a || b
false
true
true
true
!a
true
false
Relationale Operatoren
Relationale Operatoren vergleichen zwei Operanden. Das Ergebnis ist 1, falls der
66
Kapitel 3: Einführung in C/C++
Vergleich wahr ist, sonst 0.
Operator
a < b
a <= b
a > b
a >= b
a == b
a != b
wahr,
wahr,
wahr,
wahr,
wahr,
wahr,
wenn
wenn
wenn
wenn
wenn
wenn
Ergebnis
a kleiner b
a kleiner oder gleich b
a grösser b
a grösser oder gleich b
a gleich b
a nicht gleich b
Beispiel: Ist eine Zahl in einem Intervall enthalten?
x ∈ [20, 40]
⇐⇒
(20 <= x) && (x <= 40)
x 6∈ [20, 40]
⇐⇒
(x < 20) || (40 < x)
logik.cpp: Intervalltest
1
2
3
4
5
6
7
8
9
int main () {
int x = 25;
bool a = (20 <= x && x <=40);
bool b = (x <20 || 40 < x );
cout << x << " ␣ ist ␣ in ␣ [20 ,40]? ␣ " << a << endl ;
cout << x << " ␣ ist ␣ in ␣ ].. ,20[ ␣ U ␣ ]40 ,..[? ␣ "
<< b << endl ;
}
25 ist in [20,40]? 1
25 ist in ]..,20[ U ]40,..[? 0
Logische Operatoren werden von links nach rechts abgearbeitet. Die Auswertung
endet, wenn Wert des Ausdrucks feststeht.
Z.B. für x = 17: Im letzten Beispiel wird dann nur (x<20) ausgewertet, da der
Gesamtausdruck wahr ist und es auch bleibt.
Achtung!!
Der Vergleichsoperator == kann leicht mit dem Zuweisungsoperator = verwechselt
werden. Letzterer ist fast immer „wahr“, da eine Zuweisung den zugewiesenen
Wert zurückgibt (d.h. eine Zuweisung ist dann „falsch“, wenn Null zugewiesen
wurde). Falls man also nur ein statt zweier Gleichheitszeichen verwendet, so
wird zum einen der Ausdruck nicht richtig ausgewertet, zum anderen können
unerwünschte Nebenwirkungen auftreten.
3.5 Anweisungen zur Auswahl
3.5.4
67
if-Anweisungen
Die in Kapitel 2 eingeführte Anweisung zur Auswahl kann direkt in C++-Syntax
übersetzt werden:
if ( Bedingung )
{
...
}
else
{
...
}
// FALLS Bedingung
// DANN ...
// SONST ...
Zur Formulierung von Bedingungen in den Auswahlanweisungen kann man logische und relationale Operatoren verwenden.
maximum.cpp: Maximums-Bestimmung
1
2
3
4
5
6
int main () {
int x ,y ,z , m ;
cout << " Bitte ␣ 3 ␣ Zahlen ␣ eingeben ! ␣ " ;
cin >> x >> y >> z ;
if (x > y ) {
if (x > z ) {
m = x;
}
else {
m = z;
}
}
else {
if (y > z ) {
m=y;
}
else {
m=z;
}
}
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cout << " max ( " << x << " ,␣ " << y << " ,␣ "
<< z << " ) ␣ = ␣ " << m << endl ;
}
Bei if-Anweisungen kann der else-Zweig auch weggelassen werden, sofern keine Alternative verlangt wird. Ebenso gilt dieses für die geschweiften Klammern,
wenn nur eine einzelne Anweisung folgt. Dies kann aber zu Zweideutigkeiten
führen, z.B.
68
Kapitel 3: Einführung in C/C++
if ( a > b )
if ( a > 2 *
cout <<
<<
else
cout <<
b)
" a ␣ ist ␣ mehr ␣ als ␣ doppelt ␣ "
" so ␣ gross ␣ wie ␣ b . " << endl ;
" a ␣ ist ␣ groesser ␣ als ␣ b . " << endl ;
Zu welchem if gehört else? In C++ zum letzten if.
Deshalb sollte man immer geschweifte Klammern benutzen, außer in völlig eindeutigen Situationen wie
if ( a > b ) cout << " a ␣ ist ␣ groesser ␣ als ␣ b . " << endl ;
Weil Wahrheitswerte wie ganze Zahlen behandelt werden, kann man die Abfragen
x 6= 0 und x = 0 auch kurz schreiben als
if ( x ) { ... }
if (! x ) { ... }
// x !=0?
// x ==0?
Besser zu verstehen ist jedoch die explizite Angabe, so wie sie die Kommentare
zeigen.
Wir können uns nun auch ansehen, wie die Standardeingabe std::cin (d.h. die
Eingabe von Daten über die Tastatur) geprüft werden kann. "std::cin >> a"
(also die Anweisung Daten in die Variable a einzulesen) nimmt den Wahrheitswert true an, wenn die Anweisung erfolgreich war. Sie nimmt den Wert false
an, wenn es ein Problem gab. Mit der Anweisung "std::cin.clear()" kann
man in diesem Fall den auftretenden Fehlerzustand zurücksetzen und das Einlesen in anderer Form noch einmal versuchen.
eingabe–test.cpp: Test der Eingabe
1
2
3
4
int main () {
double d_num ;
char letter ;
cout << " Bitte ␣ gib ␣ Zahl ␣ ein : ␣ " ;
if ( cin >> d_num ) {
// Eine Zahl konnte gelesen werden
cout << " Das ␣ ist ␣ die ␣ Zahl : ␣ " << d_num << endl ;
}
else {
// Es gab ein Prolem .
cin . clear ();
// Wir versuchen die Eingabe noch einmal
// ( als Zeichen ) zu lesen .
cin >> letter ;
cout << " Das ␣ war ␣ ein : ␣ " << letter << endl ;
}
5
6
7
8
9
10
11
12
13
14
15
16
17
18
}
3.5 Anweisungen zur Auswahl
3.5.5
69
Bedingte Ausdrücke mit ?:
Die Schreibweise von if-else kann in einigen Fällen mit dem bedingten Ausdrucksoperator ?: verkürzt werden. Dieser Operator mit drei Operanden wird
benutzt, um eine Entscheidung innerhalb eines berechneten Ausdrucks zu fällen.
Im Gegensatz zur if-Anweisung ist dies ein Ausdruck, der einen Wert zurück
liefert, während if ein Befehl ist.
var = ( Bedingung ) ? IF - Ausdruck : ELSE - Ausdruck
Häufig benutzt man ihn in einfachen mathematischen Ausdrücken:
max = a >= b ? a : b ;
betrag = a <0 ? -a : a ;
3.5.6
switch-Anweisungen
Mit geschachtelten if-Anweisungen lassen sich grundsätzlich alle Entscheidungsfolgen realisieren. Die Programme werden dadurch aber sehr unübersichtlich. Statt dessen nutzt man häufig folgendes Konstrukt:
switch ( Ausdruck ) {
case Vergleichswert :
...
break ;
case Vergleichswert :
...
break ;
default :
...
break ;
}
Die Anweisung break ist nötig, weil sonst alle Anweisungen nach dem zugeordneten case nicht übersprungen sondern zusätzlich ausgeführt werden (unabhängig davon, ob die Bedingung für diese case-Teile zutrifft; sog. Durchfallen).
Man sollte immer eine default-Anweisung einbauen, die ausgeführt wird, wenn
keiner der anderen Fälle eingetreten ist.
switch.cpp: Switch
1
2
# include < iostream >
using namespace std ;
3
4
5
int main () {
int z ;
70
6
7
8
9
10
cout << " Bitte ␣ geben ␣ Sie ␣ eine ␣ Zahl ␣ ein : " ;
cin >> z ;
cout << endl ;
switch ( z ) {
case 0: cout <<" Die ␣ Zahl ␣ ist ␣ 0. " << endl ;
break ;
case 1: case 2: case 3:
cout << " Die ␣ Zahl ␣ ist ␣ " << z
<< " . " << endl ;
break ;
default :
cout << " Zahl ␣ ist ␣ negativ ␣ oder ␣ groesser ␣ 3. "
<< endl ;
break ;
}
11
12
13
14
15
16
17
18
19
20
21
22
23
Kapitel 3: Einführung in C/C++
}
Der Auswahlausdruck (hier z) muss von ganzzahligem Typ sein. Außerdem müssen für die Verzweigungen konstante (und ganzzahlige) Ausdrücke verwendet
werden. Die Reihenfolge der case-Anweisungen ist dagegen egal.
3.6 Funktionen
Funktionen dienen der strukturierten Programmierung. Wiederholt genutzte
Programmteile werden aus dem Hauptprogramm ausgelagert und in sinnvoll benannte Unterprogramme aufgeteilt. Funktionen können Parameter entgegennehmen, diese unter Umständen verändern und auch einen Wert zurückgeben.
3.6.1
Definition
Eine Funktion wird folgendermaßen definiert:
Rückgabetyp Funktionsname ( Typ Parametername , ...) {
...
[ return Wert ;]
}
Gültige Namen für Funktionen werden wie Variablennamen aus den Zeichen
a-z, A-Z, 0-9 und _ gebildet. Auch sie dürfen nicht mit einer Ziffer beginnen.
3.6 Funktionen
71
Vor dem ersten Aufruf einer Funktion, muss diese bereits dem Compiler bekannt
sein (Deklaration). Dies geschieht entweder durch ihre vollständige Definition
oder vorläufig durch den Prototyp. Es genügt, im Prototyp die Datentypen der
Parameter anzugeben:
// Prototyp für Funktionsname
Rückgabetyp Funktionsname ( Typ [ Paremtername ] , ...);
quadrat.cpp: Ein einfacher Funktionsaufruf
1
2
3
4
5
6
7
8
9
10
11
int quadrat ( int a ) {
int b ;
b = a*a;
return b ;
}
int main () {
int z = 11;
int zz = quadrat ( z );
cout << z << " ^2 ␣ = ␣ " << zz << endl ;
}
quadrat1.cpp: Falsch - Die Funktion ist unbekannt
1
2
3
4
5
6
7
8
9
int main () {
int z = 11;
int zz = quadrat ( z ); // quadrat unbekannt !
cout << z << " ^2 ␣ = ␣ " << zz << endl ;
}
int quadrat ( int a ) {
return a * a ;
}
quadrat2.cpp: Richtig - Prototyp deklariert die Funktion
1
2
3
4
5
6
7
8
9
10
11
int quadrat ( int a );
// Prototyp für quadrat
int main () {
int z = 11;
int zz = quadrat ( z );
cout << z << " ^2 ␣ = ␣ " << zz << endl ;
}
int quadrat ( int a ) {
return a * a ;
}
// Kompakter
Folgt der Funktionsdeklaration (im Funktionskopf ) die Funktionsdefinition (im
Funktionsrumpf ), also ein Anweisungsblock, so wird sie nicht mit Semikolon
72
Kapitel 3: Einführung in C/C++
beendet. Steht die Funktionsdeklaration allein als Prototyp, dann wird sie mit
Semikolon beendet.
Der Funktionsaufruf selbst wird mit einem Semikolon abgeschlossen.
3.6.2
Rückgabetyp
Eine Funktion kann einen, aber nur höchstens einen Wert mit dem returnBefehl zurückgeben. Will man mehrere Werte zurückgeben, muss man die Adresse eines Speicherbereichs zurückgeben, oder die Eingabeparameter modifizieren.
Der Typ des Rückgabewerts wird als Typ der Funktion angegeben und steht vor
dem Funktionsnamen. Will man keinen Wert zurückgeben, verwendet man den
Datentyp void und den return-Befehl ohne Wert. Ein solches return am Ende
einer Funktion kann auch entfallen.
Dem return-Befehl kann auch ein berechenbarer Ausdruck übergeben werden.
In einem sauberen Programmierstil verwendet man nur einen return-Befehl am
Ende einer Funktion. Prinzipiell können weitere return-Befehle aber auch zum
vorzeitigen Verlassen der Funktion benutzt werden. Auch mehrfache returnKommandos dürfen in einer Funktion benutzt werden.
return.cpp: Rückgabetypen
1
2
3
4
5
6
7
8
9
10
int test ( int a ) {
if (a >10) return a /10;
if (a >0) return a ;
}
int main () {
cout << test (1) << " ␣ "
<< test (20) << " ␣ "
<< test ( -2) << endl ;
}
Das Programm lässt sich kompilieren, führt aber zu unschönen Ergebnissen:
$ ./return
1 2 1076098592
Erst die Kompilierung mit -Wall gibt eine Warnung aus:
$ g++ -Wall return.cpp -o return
return.cpp: In function ‘int test(int)’:
return.cpp:7: warning: control reaches end of non-void function
Diese Fehlermeldung bedeutet, dass der Programmfluß das Ende der Funktion
erreicht, aber „nicht alle Fälle durch return abgedeckt sind“.
3.6 Funktionen
3.6.3
73
Funktionsparameter
Man kann einer Funktion einen oder mehrere Parameter als Argument übergeben. Die Parameter werden in der Funktionsdefinition festgelegt und stehen
innerhalb der Funktion als Variable zur Verfügung.
Ein Funktionsaufruf wird immer durch Klammern gekennzeichnet. In diesen
können Variablen (oder berechenbare Ausdrücke) als Argumente übergeben werden.
Für einzelne Parameter können in der Funktionsdeklaration auch mit einem =
Default-Argumente angegeben werden. Parameter mit Default-Werten müssen
als letzte in der Parameterliste stehen und werden von links nach rechts aufgefüllt.
default.cpp: Parabel mit Default-Argumenten
1
2
3
4
5
6
7
8
9
10
11
double parabel ( double x , double a =1 , double b =0 ,
double c =0) {
return a * x * x + b * x + c ;
}
int main () {
cout << parabel (1) << endl ;
cout << parabel (1 ,2) << endl ;
cout << parabel (1 ,2 ,3) << endl ;
cout << parabel (1 ,2 ,3 ,4) << endl ;
}
Falls im Funktionsaufruf an der Stelle, an denen in der Funktionsdeklaration
Default-Werte stehen, Argumente sind, dann wird der Funktionsaufruf mit diesen Werten durchgeführt.
3.6.4
Call by value
Beim Aufruf der Funktion werden die aktuellen Werte dieser Variablen übergeben, wobei die Parameter erst im Speicher erzeugt und dann mit den Werten der
Argumente belegt bzw. diese in die entsprechenden Speicherplätze kopiert werden. C++ übergibt Funktionsargumente grundsätzlich (bis auf 2 Ausnahmen)
als Wert.
Dies hat zur Folge, dass Änderungen an den Variablen, die in der Funktion
vorgenommen werden, bei der Rückkehr zum Hauptprogramm (bzw. zur aufrufenden Funktion) nicht mehr wirksam sind. Die Variablen haben dort ihre alten
Werte.
callvalue.cpp: call by value
1
2
void vertauschen ( int a , int b ) {
int help = a ;
74
3
4
5
6
7
8
9
10
11
12
13
Kapitel 3: Einführung in C/C++
a = b;
b = help ;
cout << " a ␣ = ␣ " << a << " ␣ b ␣ = ␣ " << b << endl ;
}
int main () {
int a =47 , b =11;
vertauschen (a , b );
cout << " a ␣ = ␣ " << a << " ␣ b ␣ = ␣ " << b << endl ;
}
a = 11 b = 47
a = 47 b = 11
3.6.5
Call by reference
Eine Referenz kann man sich als alternativen Namen (ein Alias) für eine Variable, Konstante oder Funktion vorstellen. Die Referenz hat die gleiche Speicheradresse wie das Original (und daher auch immer den gleichen Wert). Referenzen werden mit dem Kaufmanns-Und (&) gekennzeichnet und folgendermaßen
deklariert:
Typbezeichnung & Referenzname = Originalname ;
Die “Initialisierung“ mit dem Originalnamen ist immer erforderlich. Hier einige
Beispiele:
int i =10;
int & m = i ;
i =42;
m =2006;
int & j ;
int & k =12;
int & n = i + i
// Richtige Deklaration der Referenz ,
// Addresse von m und i ist gleich ,
// m ist jetzt 10.
// i ist 42 , m aber auch !
// m ist 2006 , i aber auch !
// Falsch : Referenz muss initialisiert
// werden .
// Falsch : Referenz darf nicht mit
// konstantem Wert initialisiert werden .
// Falsche Initialisierung mit
// temporärem Wert i + i
Referenzen auf Variablen sind eine Möglichkeit um Parameter nicht nur mit
ihrem Wert zu übergeben. Sind Funktionsparameter als Referenzen deklariert,
so wird beim Aufruf dieser Funktion direkt dieser Speicherplatz übergeben und
3.6 Funktionen
75
es werden keine Kopien der Variablen angelegt. Werden die Parameter in der
Unterfunktion geändert, so ändern sie sich auch in der aufrufenden Umgebung.
callref.cpp: call by reference
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void vertauschen ( int &a , int & b ) {
int help = a ;
a = b;
b = help ;
cout << " a ␣ = ␣ " << a
<< " ␣ b ␣ = ␣ " << b
<< endl ;
}
int main () {
int a =47 , b =11;
vertauschen (a , b );
cout << " a ␣ = ␣ "
<< a << " ␣ b ␣ = ␣ "
<< b << endl ;
}
a = 11 b = 47
a = 11 b = 47
3.6.6
Gültigkeitsbereich von Variablen
Eine Variable oder Funktion ist nicht nur durch ihren Namen bestimmt, sondern
auch dadurch, wo sie definiert wurde. Man unterscheidet allgemein zwischen
lokalen und globalen Variablen.
Lokale Variablen werden in Funktionen und Blöcken definiert und sind auch
nur dort sichtbar. Beim Verlassen der Funktion bzw. des Blocks wird ihr Wert
gelöscht.
lokal.cpp: Lokale Variablen
1
2
3
4
5
6
7
8
9
void aendern () {
int x =10;
cout << " x ␣ = ␣ " << x << endl ;
}
int main () {
int x =4;
cout << " x ␣ = ␣ " << x << endl ;
aendern ();
76
10
11
Kapitel 3: Einführung in C/C++
cout << " x ␣ = ␣ " << x << endl ;
}
x = 4
x = 10
x = 4
Die Änderung des Wertes von x in der Funktion aendern() ist nur dort wirksam. Wird die Funktion verlassen, dann wird ihr Wert gelöscht.
Genauer: Die Variable x in aendern() ist eine andere als die Variable x im
Hauptprogramm. Trotzdem kann der gleiche Name benutzt werden. In diesem
Fall wird über ihn immer die lokalste der Variablen angesprochen, der Inhalt
anderer Variablen gleichen Namens ist dann in der Funktion nicht zugänglich.
Es ist also wichtig, den Gültigkeitsbereich bzw. die Lebensdauer jeder Variablen
zu kennen.
Variablen können aber nicht nur bzgl. einer Funktion lokal sein, sondern in
jedem Anweisungsblock kann eine neue Variable mit schon verwendetem Namen
definiert werden. Der Bezugsrahmen ist immer der Anweisungsblock, in dem
eine Variable definiert wurde.
Globale Variablen werden außerhalb aller Funktionen definiert. Sie können in
jeder Funktion benutzt und verändert werden.
global.cpp: Globale Variablen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int a , b ;
void aendern ( void ) {
int a ;
a = b =0;
if ( a ==0) {
int a = 20;
b = 20;
cout < < " a ␣ = ␣ " <<a < < " ␣ und ␣ " <<" b ␣ = ␣ " <<b < < endl ;
}
cout < < " a ␣ = ␣ " <<a < < " ␣ und ␣ " <<" b ␣ = ␣ " <<b < < endl ;
}
int main () {
a = b =10;
cout < < " a ␣ = ␣ " <<a < < " ␣ und ␣ " <<" b ␣ = ␣ " <<b < < endl ;
aendern ();
cout < < " a ␣ = ␣ " <<a < < " ␣ und ␣ " <<" b ␣ = ␣ " <<b < < endl ;
}
a
a
a
a
=
=
=
=
10 und b = 10
20 und b = 20
0 und b = 20
10 und b = 20
3.6 Funktionen
77
Je enger eine Information begrenzt ist, d.h. je kleiner der Gültigkeitsbereich
einer Variablen ist, desto weniger Fehler kann man z.B. durch unbeabsichtigte
Änderungen an der Variablen machen.
Eine Variable, die nur innerhalb einer Funktion benutzt wird, sollte immer lokale
Variable der Funktion sein (z.B. Zählervariablen für for-Schleifen).
Kurz gesagt: So lokal wie möglich, so global wie nötig.
Wenn der Inhalt von in Funktionen lokal definierten Varibalen doch behalten
werden soll, so kann man statische Variablen benutzen.
statisch.cpp: Statische Variablen
1
2
3
4
5
6
7
8
9
10
11
void count () {
static int a = 1;
cout << " a ␣ = ␣ " << a << endl ;
a ++;
}
int main () {
count ();
count ();
count ();
}
a = 1
a = 2
a = 3
Wenn a nicht statisch wäre, würde der angegebene Wert immer 1 sein.
Wird a als statische Variable definiert, dann behält sie ihren aktuellen Wert beim
Verlassen der Funktion count(). Trotzdem ist a hier eine lokale Variable, z.B.
könnte man im Hauptprogramm nicht auf sie zugreifen. Die Lebensdauer einer
statischen Variable erstreckt sich (unabhängig, ob sie global oder lokal ist), vom
Zeitpunkt ihrer Definition, d.h. wenn der Programmfluß ihre Definiton erreicht
hat, bis zum Programmende.
Beachte: Statische Variablen (dazu gehören auch globale Variablen) können bei
ihrer Definition initialisiert werden. Falls dies nicht geschieht, wird dieses automatisch zur Laufzeit gemacht (mit dem Nullwert des entsprechenden Datentyps).
Dies geschieht im Unterschied zu automatischen bzw. lokalen Variablen.
3.6.7
Rekursive Funktionen
Funktionen dürfen sich auch selbst wieder aufrufen. Ebenso können sich auch
mehrere Funktionen wechselseitig aufrufen.
Die rekursive Lösung ist in der Regel kürzer und eleganter formuliert ist, aber
ein iterativer Algorithmus löst die Aufgabe oft deutlich schneller (sofern es denn
einen äquivalenten iterativen Algorithmus gibt).
78
Kapitel 3: Einführung in C/C++
Die Fakultät einer Zahl n ist definiert als n! = 1 · 2 · 3 · 4 · · · n
rekursiv.cpp: Fakultät als rekursive Funktion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int fakultaet ( int n ) {
if ( n ==1) {
return 1;
}
else {
return n * fakultaet (n -1);
}
}
int main () {
cout << fakultaet (4) << endl ;
cout << fakultaet (6) << endl ;
cout << fakultaet (12) << endl ;
}
24
720
479001600
rekursiv2.cpp: Fakultät als kurze rekursive Funktion
1
2
3
int fakultaet ( int n ) {
return ( n ==1)? 1 : n * fakultaet (n -1);
}
Endloses rekursives Aufrufen muss vermieden werden. Dies kann man dadurch
erreichen, dass die fortschreitenden rekursiven Aufrufe „einfacher“ werden (oben
n -> n-1 -> n-2 -> ...) und ein begrenzender Fall existiert (hier n=1), bei
dem kein rekursiver Aufruf zur Berechnung des Funktionswertes mehr nötig ist.
Aber was passiert bei dem Aufruf von fakultaet(-1)? Ausprobieren ! Wie kann
man das entstehende Problem lösen ?
3.6.8
Mathematische Funktionen
Von C++ werden in dem Modul <cmath> die Prototypen für eine Reihe mathematischer Standardfunktionen zur Verfügung gestellt.
Eine Übersicht über die Funktionen ist im Anhang A.3 zu finden.
wurzel.cpp: Die Mathe-Bibliothek
1
2
3
# include < iostream >
# include < cmath >
using namespace std ;
3.7 Schleifen
4
5
6
7
8
9
10
11
79
int main () {
double x ;
cout << " Zahl ␣ eingeben : ␣ " ;
cin >> x ;
cout << " Wurzel ␣ von ␣ " << x << " ␣ = ␣ " << sqrt ( x )
<< endl ;
}
Die Winkelfunktionen erwarten Argumente im Bogenmaß. Für die Umrechnung
zwischen Grad- und Bogenmaß kann man folgende Funktionen benutzen.
degrad.cpp: Bogen- und Gradmaß
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# include < iostream >
# include < cmath >
using namespace std ;
double radtodeg ( double a ) {
return a * 180. / M_PI ;
}
double degtorad ( double a ) {
return a / 180. * M_PI ;
}
int main () {
double x ;
cout << " Zahl ␣ im ␣ Bogenmass ␣ eingeben : ␣ " ;
cin >> x ;
cout << x << " ␣ im ␣ Bogenmass ␣ = ␣ " << radtodeg ( x )
<< " ␣ im ␣ Gradmass " << endl ;
cout << " Zahl ␣ im ␣ Gradmass ␣ eingeben : ␣ " ;
cin >> x ;
cout << x << " ␣ im ␣ Gradmass ␣ = ␣ " << degtorad ( x )
<< " ␣ im ␣ Bogenmass ␣ " << endl ;
}
3.7 Schleifen
80
3.7.1
Kapitel 3: Einführung in C/C++
while-Schleifen
Auch die in Kapitel 2 eingeführten Algorithmen-Bausteine zur Wiederholung
von Anweisungen werden direkt in die C++-Syntax übersetzt:
while ( Bedingung )
{
...
}
// SOLANGE Bedingung ERFÜLLT IST
// FÜHRE AUS
// ...
do
{
// WIEDERHOLE
// ...
...
}
while ( Bedingung );
// SOLANGE Bedingung ERFÜLLT IST .
Man beachte, dass hier die -while-Anweisung mit Semikolon abgeschlossen wird.
while.cpp: Eingabe mit Überprüfung als while-Schleife
1
2
3
4
5
6
7
8
9
10
11
12
int main () {
int year , a =2007;
cout << " Bitte ␣ gib ␣ dein ␣ Geburtsjahr ␣ ein : ␣ " ;
cin >> year ;
while ( year > a || year <1900) {
cin >> year ;
}
cout << " Du ␣ bist ␣ " << a - year << " ␣ Jahre ␣ alt . "
<< endl ;
}
dowhile.cpp: Eingabe mit Überprüfung als do-while-Schleife
1
2
3
4
int main () {
int year , a =2007;
cout << " Bitte ␣ gib ␣ dein ␣ Geburtsjahr ␣ ein : ␣ " ;
5
6
7
8
9
10
11
12
do {
cin >> year ;
} while ( year > a || year <1900);
cout << " Du ␣ bist ␣ " << a - year << " ␣ Jahre ␣ alt . "
<< endl ;
}
3.7 Schleifen
81
In diesem Beispiel ist die do-Schleife angebrachter als die while-Schleife, weil
die Eingabeaufforderung in jedem Fall mindestens einmal erfolgen soll. Prinzipiell sollte man aber while-Schleifen, wenn immer möglich, vorziehen. Denn
durch Vorabprüfung der Schleifenbedingung kann sichergestellt werden, dass der
Schleifenrumpf sinnvoll bearbeitet werden kann.
while2.cpp: while ohne Anweisungsblock
1
2
3
4
5
6
7
8
9
10
11
bool geburtsjahr () {
int year ;
cout << " Bitte ␣ gib ␣ dein ␣ Geburtsjahr ␣ ein : ␣ " ;
cin >> year ;
return ( year >2006 || year <1900);
}
int main () {
while ( geburtsjahr ())
;
}
Die Abfrage wird erst beendet, wenn ein gültiges Geburtsjahr eingegeben wurde.
Mit einer while-Schleife kann man z.B. auch eine iterative Version der Fakultätsberechunung angeben. Man beachte, dass hierbei die Variable i als Zähler
verwendet wird, der die Werte 1 bis n druchläuft.
iterativ2.cpp: Iterative Version von Fakultät mit while
1
2
3
4
5
6
7
8
9
3.7.2
int fakultaet ( int n ) {
int produkt = 1;
int i =1;
while (i <= n ) {
produkt *= i ;
i ++;
}
return produkt ;
}
for-Schleifen
Für Schleifen, die wie in dem letzen Beispiel im wesentlichen durch einen “Zähler“ kontrolliert werden, sollte man eine for-Schleife benutzen. Diese hebt den
“Zähler“ deutlich hervor.
Allgemein haben for-Schleifen folgende Syntax:
for ( Initialisierung ; Bedingung ; Reinitia lisierun g )
{
...
82
Kapitel 3: Einführung in C/C++
}
Eine solche Schleife wird folgendermaßen abgearbeitet:
@
@
-
Initialisierung
@ falsch
Bedingung @
@
@
@
@ wahr
6
?
Reinitialisierung
Anweisungsblock
In allen 3 Bereichen des Schleifenkopfs muss ein Ausdruck stehen, wobei der
Ausdruck bei der Bedingung einen Bool-Wert zurückliefern muss.
Zum Beispiel bei der Berechnung von n!:
iterativ.cpp: Iterative Version von Fakultät mit for
1
2
3
4
5
6
7
int fakultaet ( int n ) {
int produkt = 1;
for ( int i =1; i <= n ; i ++) {
produkt *= i ;
}
return produkt ;
}
Anmerkung: In dem Beispiel findet sich in der Initialisierung eine Deklaration.
In diesem Fall wird i als lokale Integer Variable erklärt (quasi just-in-time).
Eine Deklaration vor der for-Anweisung ist nicht erforderlich. Die Variable ist
aber nur für die for-Anweisung gültig. Sie kann nach dem Anweisung nicht
mehr verwendet werden. In den weiteren Beispielen werden wir sehen, dass eine
Initialisierung die Deklaration nicht enthalten muss. Die verwendete Variable
muss dann aber schon vorher deklariert sein.
Alle drei Ausdrücke bzw. Bedingungen im Schleifenkopf der for-Anweisung
können auch weggelassen werden. Meist sind aber mindestens die Bedingung
(sonst entsteht eine Endlosschleife) und Reinitialisierung vorhanden.
Der nachfolgende Programmausschnitt ist z.B. nur sinnvoll, wenn j im vorangegangenen Teil des Programms auf einen positiven Wert gesetzt wurde:
for (; j >0; j - -) {
cout << " j ␣ = ␣ " << j << endl ;
}
Die for-Anweisung kann auch berechenbare Ausdrücke oder zusätzliche Anweisungen enthalten, die durch Komma getrennt werden.
3.7 Schleifen
83
for ( int x =1 , y =100; x <= y ; x ++ , y - -) {
cout << " x ␣ = ␣ " << x << " ␣ y ␣ = ␣ " << y << endl ;
}
3.7.3
Sprungbefehle break und continue
Mit dem break-Kommando (vgl. switch-Anweisung) kann die Abarbeitung der
kompletten Schleife vorzeitig abgebrochen werden: Die Programmausführung
wird hinter dem aktuellen Anweisungsblock fortgesetzt.
Im Unterschied dazu beendet der Befehl continue nur die Ausführung des aktuellen Schleifendurchlaufs:
Bei einer do-Schleife wird zum Ende der Schleife gesprungen, die Bedingung erneut überprüft und gegebenefalls ein neuer Durchlauf gestartet.
Bei einer while-Schleife wird erneut die Bedingung überprüft und gegebenenfalls ein neuer Durchlauf gestartet.
Bei einer for-Schleife wird zum Schleifenkopf zurück gesprungen und zunächst die Reinitialisierung ausgeführt, dann wird erneut die Bedingung überprüft und gegebenenfalls ein neuer Durchlauf gestartet.
Beide Befehle beziehen sich jeweils auf die innerste Schleife. Befinden sie sich im
Inneren geschachtelter Schleifen, dann wird nur die innerste verlassen, während
die umgebenden Schleifen weiter abgearbeitet werden.
continue.cpp: Die Schleife läuft mit continue weiter
1
2
3
4
5
6
7
8
9
10
int main () {
int i ;
for ( i =1; i <10; i ++) {
if (i >5) {
continue ;
}
cout << " Aktueller ␣ Wert : ␣ " << i << endl ;
}
cout << " Schleifenende : ␣ i ␣ = ␣ " << i << endl ;
}
Aktueller Wert: 1
Aktueller Wert: 2
Aktueller Wert: 3
Aktueller Wert: 4
Aktueller Wert: 5
Schleifenende: i = 10
84
Kapitel 3: Einführung in C/C++
break.cpp: Die Schleife endet mit break sofort
1
2
3
4
5
6
7
8
9
10
int main () {
int i ;
for ( i =1; i <10; i ++) {
if (i >5) {
break ;
}
cout << " Aktueller ␣ Wert : ␣ " << i << endl ;
}
cout << " Schleifenende : ␣ i ␣ = ␣ " << i << endl ;
}
Aktueller Wert: 1
Aktueller Wert: 2
Aktueller Wert: 3
Aktueller Wert: 4
Aktueller Wert: 5
Schleifenende: i = 6
3.7.4
Zusammenfassung Schleifen
Das folgende Beispiel verdeutlicht nicht nur nochmals die Benutzung der in
diesem Abschnitt vorgestellten Befehle, sondern insbesondere die Wirkung von
break bei Benutzung innerhalb geschachtelter Schleifen.
Das Programm fordert zur Eingabe einer Zahl auf und bestimmt die größte Primzahl, die kleiner oder gleich dieser Eingabe ist. Dabei ist int isprim(int) eine
Funktion, die Eins zurückgibt, wenn der eingegebene Wert eine Primzahl ist,
und Null sonst.
prim.cpp: Primzahltest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool isprim ( int a );
int main () {
int p ;
do {
int z ;
cout << " Bitte ␣ ganze ␣ Zahl ␣ in ␣ [2 , ␣ 1000] ␣ "
<< " eingeben : ␣ " ;
cin >> z ;
if (( z < 2) || ( z > 1000)) continue ;
for ( p = z ; p >1; p - -) {
if ( isprim ( p )) {
cout << " Die ␣ naechste ␣ Primzahl ␣ zu ␣ "
3.7 Schleifen
<< z << " ␣ ist ␣ " << p << endl ;
break ;
16
17
18
19
20
21
22
23
24
25
26
85
}
}
cout << " Nochmal ? ␣ ( ja ␣ = ␣ 1 , ␣ nein ␣ = ␣ 0) ␣ " ;
cin >> p ;
}
while ( p );
return 0;
}
$ ./prim
Bitte ganze Zahl in [2, 1000] eingeben: 100
Die naechste Primzahl zu 100 ist 97.
Nochmal? (ja = 1, nein = 0)
0
Die drei Schleifenkonstrukte sind äquivalent, d.h. man kann jede for-Schleife
durch eine while-Schleife (oder genauso durch eine do-Schleife) ersetzen und
umgekehrt. Und jede while-Schleife kann man durch eine do-Schleife ersetzen
und umgekehrt.
for ( Init ; Bedingung ;
Reinit ) {
Anweisungen
}
while ( Bedingung ) {
Anweisungen
}
⇐⇒
Init ;
while ( Bedingung ) {
Anweisungen
Reinit
}
⇐⇒
for ( ; Bedingung ; ) {
Anweisungen
}
Sofern die Schleifen eine “continue“ Anweisung enthalten, muss dieses dieses
ggf. durch eine geeignete “goto“ Anweisung ersetzt werden (siehe folgend). Man
§berlege, warum dieses notwendig ist!
3.7.5
Der Sprungbefehl goto
Der goto-Befehl erlaubt direkt zu einer mit einer Sprungmarke (Label) versehenen Anweisung zu springen. Weil das aber zu unstrukturiertem Code führen
kann, ist dieser Sprungbefehl verpönt. Label sind Bezeichner, die einer Anweisung vorangestellt sind und von dieser durch einen Doppelpunkt abgesetzt sind.
Siehe folgendes Beispiel:
goto.cpp: goto höchstens zur Fehlerbehandlung
86
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Kapitel 3: Einführung in C/C++
# include < iostream >
# include < cmath >
using namespace std ;
int main () {
double x ;
cout << " Positive ␣ Zahl ␣ eingeben ? ␣ " ;
cin >> x ;
if (x <=0) goto ERROR_NUMBER ;
cout << " Die ␣ Wurzel ␣ von ␣ " << x << " ␣ ist ␣ "
<< sqrt ( x ) << endl ;
return 0;
ERROR_NUMBER :
cout << " Keine ␣ gültige ␣ Zahl : ␣ " << x << endl ;
return 1;
}
$ ./goto
Positive Zahl eingeben? 14
Die Wurzel von 14 ist 3.74166
$ ./goto
Positive Zahl eingeben? -5
Keine gültige Zahl: -5
goto sollte man höchstens in der Form des obigen Beispiels verwenden oder bei
Sprüngen aus einer tiefgeschachtelten Schleife. Die Anweisung break beendet
stets nur die Schleife, in der sie sich unmittelbar befindet. Mit einem geeigneten
goto kann man mit einem “Satz“ an das Ende der äußeren Schleife springen.
Dort muss sich ein entsprechendes Label befinden.
3.8 Felder
Felder oder Arrays sind die geeignete Methode, um Variablen oder Daten des
gleichen Typs zusammenzufassen und somit eine zusammengehörige Speicherung und effiziente Zugriffe (über Schleifen o.ä.) zu ermöglichen. Vektoren oder
Matrizen sind die bekanntesten Beispiele für Felder. Man bestimmt die Anzahl
der zu speichernden Objekte und ihren Typ und kann dann über einen Index auf
jedes einzelne Objekt zugreifen.
Datentyp Name [ Dimension ];
Datentyp Name [ Dimension ] = { Initialierung };
3.8 Felder
87
Zum Beispiel
int x [4];
int y [4] = {4 , 7 , 1 , 1};
Felder dürfen sich auch über mehrere Dimensionen erstrecken:
Datentyp Name [ Dim1 ][ Dim2 ];
int matrix [3][3];
int z [2][3] = {{1 ,2 ,3} , {4 ,5 ,6}};
Auf die Einträge wird über den Index (bzw. die Indizes) zugegriffen, z.B. x[2]
oder z[0][0]. Dabei muss man beachten, dass der Index zwischen 0(!) und
Dimension-1 läuft.
Intern werden mehrdimensionale Felder zeilenweise als eindimensionale Felder
abgespeichert, z.B. z={1,2,3,4,5,6}. Bei mehrdimensionalen Feldern erfolgt
auch der interne Zugriff über die Berechnung des Index in der Art
index = index1*Dim2+index2 ∈ { 0, ... , Dim1*Dim2-1 }.
Beispiel: z[0][1]=2 ist der zweite, z[1][2]=6 ist der sechste Eintrag.
Mit Ausnahme der Initialisierung sind aber Operationen mit Feldern (Zuweisung, Vergleich, Addition, ...) nur elementweise möglich, z.B.
double x [3] , y [3] , z [3];
... Initialisierung von x , y ...
for ( int j =0; j <3; j ++) {
z [ j ] = x [ j ] + y [ j ];
}
3.8.1
Felder als Funktionsparameter
Der Name eines Feldes (ohne eckige Klammern) ist eine Speicheradresse, nämlich die Adresse unter der der erste Feldeintrag abgespeichert ist. Davon ausgehend werden die Adressen der anderen Einträge berechnet.
Wird ein Feld als Parameter an eine Funktion übergeben, dann ist dieser Funktion die Speicheradresse bekannt und folglich sind Änderungen im Feld, die in
der Funktion vorgenommen werden, auch außerhalb der Funktion gültig.
feld.cpp: Felder sind keine lokalen Variablen
1
2
3
int sum ( int v [] , int n ) {
/* Berechnung der Summe der Eintraege eines Feldes */
/* Eingabe : v [] = Feld , n = Feldlaenge
*/
88
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Kapitel 3: Einführung in C/C++
/* Ausgabe : Summe
int s =0;
for ( int i =0; i < n ; i ++) {
s += v [ i ];
}
v [0] = 42;
n = 42;
return s ;
}
*/
int main () {
int v [10]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10};
int n =10;
cout << " Summe ␣ ist ␣ " << sum (v , n ) << endl ;
cout << " Das ␣ Feld ␣ hat ␣ " << n << " ␣ Einträge . "
<< endl ;
cout << " Der ␣ erste ␣ Feldeintrag ␣ ist ␣ " << v [0]
<< " . " << endl ;
}
Summe ist 55
Das Feld hat 10 Einträge.
Der erste Feldeintrag ist 42.
Der Parameter n wird per call by value übergeben, er ist eine lokale Variable
der Funktion sum. Also hat die Änderung n=42 keinen Einfluss auf den Wert im
Hauptprogramm.
Die Änderung v[0]=42 des ersten Eintrags des Feldes v ist dagegen auch im
Hauptprogramm gültig, weil v – genauer die Speicheradresse von v – an sum
übergeben wurde (call by reference).
Man beachte, dass in dem Beispiel in der Funktionsdefinition die Feldübergabe mit “v[]“ erklärt wurde. Bei eindimensionalen Feldern ist in der Klammer
keine Angabe notwendig. Bei mehrdimensionalen Feldern darf aber nur in der
ersten Klammer die entsprechende Angabe fehlen. Die weiteren Klammern müssen genau die Größe enthalten, die der Deklaration des beim Aufruf verwendeten
Feldes entspricht. Variable Angaben sind nicht zulässig. Zumindest wird dieses
von vielen C++ Compilern verweigert. Die Verwendung von sogenannten “Variable Length Arrays“ ist im C-Standard seit 1999 enthalten, aber noch nicht
in C++. Will man aber Funktionen für Standardoperationen mit Feldern definieren, so sollten die Dimensionen sinnvollerweise variabel sein. Die direkte
Verwendung von mehrdimensionale Felder als Parameter von Funktionen ist
hierbei zu unflexibel. Wir werden später erklären, wie man dieses Problem lösen
kann.
Beispiel: Eine Funktion zur Skalarmultiplikation von Matrizen (A → s·A) könnte man unter Verwendung mehrdimensionaler Felder nur für feste Dimensionen
von A erklären, z.B.
smult(double A[10][10], double s);
3.9 Präprozessoranweisungen
89
3.9 Präprozessoranweisungen
Vor der Kompilierung wird der C++-Quelltext vom sogenannten Präprozessor
verarbeitet. Dieser nimmt temporäre Änderungen am Programmtext vor, die für
die Kompilierung benötigt werden. Jede Präprozessoranweisung beginnt mit einem Doppelkreuz (#) als erstes Zeichen einer Zeile.
3.9.1
#define
Mit dem Kommando
# define NAME Ersatztext
werden sämtliche Vorkommen von NAME durch den Ersatztext ersetzt.
# define BUFFER_SIZE 256
char buf [ BUFFER_SIZE ];
// also buf [256]
Man kann auch deutlich komplexere Ersetzungen vornehmen lassen, da #define
auch Argumente verarbeitet:
# define HUNDRED ( a ) a *100
int z = 5;
int z1 = HUNDRED ( z );
// z1 = 5*100 = 500
Leider liefert dieses #define nicht immer das Erwartete:
int z2 = HUNDRED ( z +1);
int z3 = ++ HUNDRED ( z );
// z2 = z +1*100 = 105
// z3 = ++ z *100 = 6*100
Nach der Textersetzung ist die Reihenfolge, in der die Operationen durchgeführt
werden, anders, als es die scheinbare Funktion HUNDRED() suggeriert, denn der
Präprozessor führt eine reine Textersetzung durch, wobei mathematische Vorrangregeln nicht berücksichtigt werden. Sicherer ist es, #define nur unter großzügiger Verwendung von Klammern zu verwenden:
# define HUNDRED ( a ) (( a )*100)
Meist sind Funktionen aber die bessere Alternative.
90
3.9.2
Kapitel 3: Einführung in C/C++
#include
Trifft der Präprozessor auf den #include Befehl, liest er die entsprechende
Header-Datei ein. Alle in ihr definierten Funktionen und Variablen sind für
den Rest des Quelltext bekannt.
# include < syscall .h >
# include < cmath >
# include " matrix . h "
// Systemaufrufe
// sin , cos , ...
// Eigene Matrixfunktionen
Wird der Dateiname in spitze Klammern gesetzt, sucht der Präprozessor zuerst
im include-Pfad des Systems (z.B. /usr/include/). Dateien in Anführungszeichen werden zuerst im aktuellen Verzeichnis gesucht (lokal).
Headerdateien enden auf .h. Bei den Headerdateien der C++Standardbibliothek lässt man diese Endung weg, um sie von den alten
C-Headern zu unterscheiden.
3.10 Formatierte Ausgabe
Mit std::cout haben wir Zahlen und Texte bisher nur unformatiert ausgegeben. Oft wünscht man jedoch eine Struktur in der Ausgabe, etwa bei Tabellen
oder Matrizen. Die Formatierung der Ausgabe wird über Formatflags verwaltet. Auf diese kann direkt über Elementfunktionen zugegriffen werden oder über
Manipulatoren.
Die Formatflags sind Integer-Zahlen, bei denen einzelne Bits durch die folgenden
Bit-Operatoren gesetzt und gelöscht werden können.
3.10.1 Bit-Operatoren
Mit den Operatoren &, |, ^ und ~ lassen sich Zahlen bitweise verändern. Dabei
wird die Operation auf die einzelnen Stellen der Zahl in der Binärdarstellung
angewendet. Die Operatoren << und >> verschieben die Stellen der Zahl nach
links oder rechts.
3.10 Formatierte Ausgabe
Operator
&
|
^
~
<<
>>
Erläuterung
Beispiel
Bitweises UND
Bitweises ODER
Bitweises XOR
Bitweises NOT
Schieben nach links
Schieben nach rechts
char a = 14;
char b = 19;
a & b
a | b
a ^ b
~a
a << 2
a >> 2
91
Ergebnis
binär
dezimal
00001110
14
00010011
19
00000010
2
00011111
31
00011101
29
11110001
-15
00111000
56
00000011
3
3.10.2 Formatierung über Elementfunktionen
Die Formatierung kann mit Hilfe sogenannter Elementfunktionen gesteuert werden. Elementfunktionen werden wir im Rahmen der Diskussion von Klassen und
Strukturen genauer kennenlernen.
Mit der Elementfunktionen des Ausgabeoperators
cout . width ( int n );
lässt sich eine minimale Feldbreite für die unmittelbar nächste Ausgabe definieren. Dieser Wert wird danach wieder auf 0 zurückgesetzt. Die Ausgabe von
Buchstaben bzw. Zeichen ist davon nicht betroffen.
Die Anzahl der gültigen Nachkommastellen bei der Ausgabe einer Fließkommazahl kann mit
cout . precision ( int n );
geändert werden (in Festpunkt- und wissenschaftlicher Darstellungsform). In
der normalen Form bezieht sich dieser Wert auf die Anzahl der Vor- und Nachkommastellen. Im Ist das Flag ios::fixed oder ios::scientific gesetzt, gibt
setprecision() die Anzahl der Nachkommastellen an. Mit
cout . fill ( char c )
lässt sich das Standard-Füllzeichen ’ ’ durch ein beliebiges anderes Zeichen ersetzen. Die Formatflags werden mit
cout . setf ( long flag );
cout . unsetf ( long flag );
92
Kapitel 3: Einführung in C/C++
gesetzt oder gelöscht. Die möglichen Flags stehen dabei als Formatierungsanweisungen zur Verfügung:
Bitfeld
ios::adjustfield
(Ausrichtung)
ios::basefield
(Zahlensystem)
ios::floatfield
(Gleitkomma)
(Sonstiges)
Flags
ios::left
ios::right
ios::oct
ios::dec
ios::hex
ios::showbase
ios::fixed
ios::scientific
ios::showpoint
ios::uppercase
ios::showpos
Bedeutung
linksbündig
rechtsbündig
oktal
dezimal (standard)
hexadezimal
Zahlenbasis anzeigen
Dezimaldarstellung (dddd.dd)
Exponentialdarstellung
(.dddddd Edd)
führende Nullen ausgeben
Großbuchstaben verwenden,
d.h. ’E’,’X’ statt ’e’,’x’
explizite Ausgabe eines positiven Vorzeichens
Mehrere Flags können dabei durch die Bit-Operation | kombiniert werden:
cout . setf ( ios :: fixed | ios :: right );
Leider löscht cout.setf(ios::left) nicht automatisch das ios:right-Flag.
Deshalb sollte man die Funktion
cout . setf ( long flag , long gruppe );
nutzen, die zuerst die Bitfelder von gruppe löscht und anschließen gemäß flag
neu setzt.
Beispiel: Alle Bits der Gruppe ios::adjustfield löschen und danach
ios::fixed und ios::right setzen:
cout . setf ( ios :: fixed | ios :: right , ios :: adjustfield );
formatint.cpp: Formatierung von Ganzzahlen
1
2
3
4
5
6
7
8
9
10
11
12
void writenumbers ( int d [] , int m , int width ) {
for ( int i =0; i < m ; i ++) {
cout << " ( " ;
cout . width ( width );
cout << d [ i ];
cout << " ) " << endl ;
}
cout << endl ;
}
int main () {
int a []={24 , 28203 , -120 ,1000000};
3.10 Formatierte Ausgabe
cout . fill ( ’_ ’ );
13
14
15
16
17
18
19
20
21
93
writenumbers (a , 4 , 0);
writenumbers (a , 4 , 10);
cout . setf ( ios :: left );
writenumbers (a , 4 , 10);
}
(24)
(28203)
(-120)
(1000000)
(________24)
(_____28203)
(______-120)
(___1000000)
(24________)
(28203_____)
(-120______)
(1000000___)
formatflt.cpp: Formatierung von Fließkommazahlen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void writenumbers ( double d [] , int m , int width , int p ) {
cout . precision ( p );
for ( int i =0; i < m ; i ++) {
cout << " ( " ;
cout . width ( width );
cout << d [ i ] << " ) " << endl ;
}
cout << endl ;
}
int main () {
double b [] = {123. , 3.141529 , 123456789. , 0.000012};
cout << " normale ␣ Ausgabe " << endl ;
writenumbers (b , 4 , 10 , 4);
writenumbers (b , 4 , 10 , 6);
cout << " feste ␣ Ausgabe " << endl ;
cout . setf ( ios :: fixed , ios :: floatfield );
writenumbers (b , 4 , 10 , 3);
writenumbers (b , 4 , 10 , 6);
cout << " wissensc haftlich e ␣ Ausgabe " << endl ;
cout . setf ( ios :: scientific , ios :: floatfield );
writenumbers (b , 4 , 10 , 3);
writenumbers (b , 4 , 10 , 6);
}
94
Kapitel 3: Einführung in C/C++
normale Ausgabe
(
123)
(
3.142)
( 1.235e+08)
(
1.2e-05)
(
123)
(
3.14153)
(1.23457e+08)
(
1.2e-05)
feste Ausgabe
(
123.000)
(
3.142)
(123456789.000)
(
0.000)
(123.000000)
( 3.141529)
(123456789.000000)
( 0.000012)
wissenschaftliche Ausgabe
( 1.230e+02)
( 3.142e+00)
( 1.235e+08)
( 1.200e-05)
(1.230000e+02)
(3.141529e+00)
(1.234568e+08)
(1.200000e-05)
3.10.3 Manipulatoren
Die Verwendung von Manipulatoren zur Formatierung der Ausgabe kann durch
die header-Datei <iomanip> eingebunden werden.
Manipulatoren werden dem Ausgabestream einfach mit dem Operator <<
übergeben.
3.10 Formatierte Ausgabe
Manipulator
cout << setw(8)
cout << setfill(’#’)
cout << setprecision(3)
cout << right
cout << left
cout << fixed
cout << scientific
cout << oct
cout << dec
cout << hex
95
entsprechende Elementfunktion
cout.width(8)
cout.fill(’#’)
cout.precision(3)
cout.setf(ios::right, ios::adjustfield)
cout.setf(ios::left, ios::adjustfield)
cout.setf(ios::fixed, ios::floatfield)
cout.setf(ios::scientific, ios::floatfield)
cout.setf(ios::oct, ios::basefield)
cout.setf(ios::dec, ios::basefield)
cout.setf(ios::hex, ios::basefield)
Der Vorteil der Verwendung von Manipulatoren ist, dass Manipulatoren einfacher zu handhaben sind und für mehr Übersicht sorgen (bei gleicher Funktionalität im Vergleich zu den Elementfunktionen).
3.10.4 Beispiel: Berechnung von π nach Leibniz
Von Leibniz stammt die Reihenentwicklung
∞
X
1 1 1 1
(−1)n
π
= 1 − + − + ∓ ... =
,
4
3 5 7 9
2n + 1
n=0
die wir zur approximativen Berechnung von π benutzen wollen (wobei es dazu
deutlich bessere Algorithmen gibt):
leibniz.cpp: Approximation von π mit Leibniz-Verfahren
1
2
3
4
5
6
7
int main () {
long max , n ;
double pi = 0;
cout << " Bis ␣ zu ␣ welchem ␣ n ␣ soll ␣ gerechnet ␣ werden ? ␣ " ;
cin >> max ;
8
9
10
11
12
13
for ( n =0; n <= max ; n ++) {
if ( n %2)
pi -= 1./(2* n +1);
else
pi += 1./(2* n +1);
}
14
15
16
pi *=4;
cout << " n ␣ = ␣ " << left << setw (10) << --n ;
96
17
18
19
20
Kapitel 3: Einführung in C/C++
cout . setf ( ios :: fixed );
cout << " ␣ ␣ pi ␣ = ␣ " << right << setw (14)
<< setprecision (10) << pi << endl ;
}
$ leibniz
Bis zu welchem n soll gerechnet werden? 200
n = 200
pi =
3.1465677472
$ leibniz
Bis zu welchem n soll gerechnet werden? 20000
n = 20000
pi =
3.1416426511
$ leibniz
Bis zu welchem n soll gerechnet werden? 2000000
n = 2000000
pi =
3.1415931536
3.11 Zeiger
Felder haben einen großen Nachteil: Man muss bereits zur Übersetzungszeit wissen, wieviel Speicher man für sie reservieren muss.
Häufig will man mit komplexen Daten umgehen, die in unterschiedlichen Ausprägungen auftreten können, z.B. Vektoren v ∈ Rn und Matrizen A ∈ Rm×n für
verschiedene m, n ∈ N.
Eine Möglichkeit ist die Definition über Felder, die so groß dimensioniert sind,
dass alle möglichen Ausprägungen dort Platz finden:
double v [1000] , A [1000][1000];
Dabei werden aber enorme Mengen Speicher verschwendet, wenn man den bereitgestellten Platz mit Vektoren und Matrizen viel kleinerer Dimension belegt.
3.11.1 Zeigervariablen
C++ bietet die Möglichkeit, Speicherbereiche zur Laufzeit dynamisch zu allozieren (bzw. reservieren) und wieder freizugeben. Die Adressen von Speicherbereichen werden in C++ in Zeigern (engl.: Pointern) verwaltet. Während z.B.
eine Integer-Variable eine ganze Zahl als Wert (bzw. Inhalt) hat, hat eine Zeigervariable eine Speicheradresse als Inhalt, und diese Adresse ist dann sogar
veränderbar.
Datentyp * name ;
Beispiel:
3.11 Zeiger
97
double * v ;
Durch diese Definition wird eine Variable name vom Typ Zeiger auf Datentyp
vereinbart. Durch diese Definition sind Zeiger und der entsprechende Datentyp
untrennbar miteinander verbunden. Bei dieser Definiton wird kein Speicherplatz für ein Objekt vom entsprechenden Datentyp reserviert, sondern ein
Speicherbereich, der groß genug ist, um die Adresse zu speichern (i.d.R. auf
heutigen Systemen 4 Bytes). Analog zur normalen Variablendefinition, ist auch
der Specherbereich nach der Erzeugung der Variablen undefiniert, und dieser
erhält erst dann einen sinnvoll interpretierbaren Inhalt, falls er initialisiert wird.
Aber damit sind Zugriffe der Art v[0]=0.0, v[1]=1.0, . . . noch nicht
sinnvoll, denn der Compiler weist v erstmal eine beliebige Adresse zu.
Sauberer ist es, Zeiger mit 0 (oder auch NULL) zu initialisieren. 0 ist eine spezielle Adresse, und bedeutet, dass der Zeiger auf nichts zeigt.
double * v = 0;
Jetzt kann geprüft werden, ob v bereits auf reservierten Speicher zeigt:
if ( v != 0)
... // nein , noch nicht
if ( v != NULL )
... // das gleiche
if ( v )
... // und nochmal das gleiche
...
3.11.2 Speicher reservieren mit new
Um nun tatsächlich den Speicher zu reservieren (bzw. allozieren) auf den ein
Zeiger weisen soll, verwendet man die Operatoren
new Datentyp ;
new Datentyp ( Initialisierung );
Beispiel:
double * v = 0;
v = new double ;
* v = 2.718;
Kompakter lässt sich das auch schreiben als
98
Kapitel 3: Einführung in C/C++
double * v = new double (2.718);
Was passiert hier ? new erzeugt im Speicher eine Variable vom Typ double,
initialisiert sie mit dem Wert 2.718 und liefert als Wert des Ausdrucks deren
Speicheradresse zurück, die dann dem Zeiger v zugewiesen wird. Der Zeiger
v speichert also nun die Adresse der neu erzeugten Variable. Mit *v wird
auf diesen Wert zugegriffen. Der direkte Zugriff über einen Namen ist nicht
möglich, da mittels new erzeugte Variablen „namenlos“ sind.
Auf den Wert der neuen Variablen kann übrigens auch mit Hilfe des Ausdrucks
v[0] zugegriffen werden, also quasi wie auf ein Feld mit einem Element. Für
den Umgang mit Vektoren und Matrizen benötigt man jedoch nicht nur einen
Speicherplatz, sondern große Speicherbereiche in Form eines entsprechend
dimensionierten Feldes.
new Datentyp [ Groesse ];
Auf die einzelnen allozierten Elemente des dynamischen Arrays kann man dann
z.B. mit dem Operator [ ] wie im folgenden Beispiel zugreifen:
kehrwert.cpp: Beliebige viele Kehrwerte
1
2
3
4
5
int main () {
int m ;
cout << " Wieviele ␣ Kehrwerte ? ␣ " ;
cin >> m ;
// double v [ m ]; -- nicht erlaubt .
double * v = new double [ m ];
6
7
8
9
10
11
12
13
14
15
16
for ( int i =0; i < m ; i ++) {
v [ i ] = 1./( i +1);
}
for ( int i =0; i < m ; i ++) {
cout << v [ i ] << endl ;
}
}
Wichtig! Bei der bisherigen Form der Deklaration eines Arrays, muss der die
Größe bestimmende Ausdruck konstant sein, während das in diesem Fall nicht
sein muss.
3.11.3 Speicher freigeben mit delete
Mit new allozierter Speicher wird mit dem Programmende wieder freigegeben.
Dennoch sollte man nicht mehr benötigten Speicher in seinem Programm selber
3.11 Zeiger
99
freigeben, da C++ dieses nicht automatisch macht (auch wenn gar keine Zeiger
mehr existieren, die auf diesen Bereich „zeigen“):
delete Name ;
delete [] Name ;
// ein Wert mit new alloziert .
// ein Array mit new [] alloziert .
Danach zeigt Name weiterhin auf die alte Speicheradresse, auf die sie aber nicht
mehr sinnvoll zugreifen kann, da das Objekt, auf das dieser Zeiger zeigt, zerstört worden ist. Dem Zeiger kann nun mit einem neuen new wieder Speicher
zugewiesen werden. Eine saubere Lösung, um den Zeiger noch zu „deaktivieren“
ist es, ihn auf 0 zu setzen.
3.11.4 Zugriff auf Zeigerwerte
In Zeigern werden Speicheradressen gespeichert. new gibt dafür eine neue Speicheradresse zurück. Aber ein Zeiger kann auch auf bereits bestehende Variablen
zeigen, wenn dem Zeiger die Adresse der Variablen mit dem Referenzierungsoder Adressoperator & übergeben wird. Um auch dieser Adresse einen konkreten
Inhalt zuzuweisen, benutzt man den Dereferenzierungsoperator *:
pointer.cpp: Zeiger auf Variablen
1
2
3
4
5
6
7
8
int main () {
int x , y =42 , * z ;
z = &x;
*z = y;
cout << " Auf ␣ welche ␣ Adresse ␣ zeigt ␣ z ? ␣ " << z << endl ;
cout << " Was ␣ ist ␣ dort ␣ gespeichert ? ␣ " << * z << endl ;
cout << " Was ␣ ist ␣ dort ␣ gespeichert ? ␣ " << x << endl ;
}
Auf welche Adresse zeigt z? 0xbfffd844
Was ist dort gespeichert? 42
Was ist dort gespeichert? 42
Man beachte, dass * verschiedene Bedeutungen hat: Bei der Variablendeklaration
zeigt er an, dass eine Zeigervariable definiert wird. Sonst wird damit gekennzeichnet, dass auf den Wert zugegriffen wird, der unter der Adresse gespeichert
ist, auf die der Zeiger zeigt. Wie schon erwähnt, kann man auch mit dem Operator [ ] dereferenziern, d.h. *z und z[0] ist das Gleiche.
100
Kapitel 3: Einführung in C/C++
3.11.5 Benutzung des Referenzierungs- und Dereferenzierungsoperators
reference.cpp: (De-)Referenzierung von Zeigern
1
2
3
4
5
6
int main () {
int x , * z ;
x = 10;
cout << " Der ␣ Wert ␣ von ␣ x ␣ ist ␣ x ␣ = ␣ " << x << endl ;
cout << " Die ␣ Adresse ␣ von ␣ x ␣ ist ␣ " << & x << endl ;
7
8
9
10
11
12
13
14
15
16
17
18
z = &x;
cout << " Der ␣ Wert ␣ von ␣ z ␣ ist ␣ die ␣ Adresse ␣ von ␣ x : ␣ "
<< " z ␣ = ␣ " << z << endl ;
* z = 20 ;
cout << " x ␣ = ␣ " << x << endl ;
x = 30;
cout << " x ␣ = ␣ " << x << " ,␣ dereferenziertes ␣ z ␣ = ␣ "
<< * z << endl ;
}
Der
Die
Der
x =
x =
Wert von x ist x = 10
Adresse von x ist 0xbfffe640
Wert von z ist die Adresse von x: z = 0xbfffe640
20
30, dereferenziertes z = 30
Dem Zeiger z wird die Adresse der Variablen x zugewiesen, wodurch die beiden
miteinander verbunden sind (“z zeigt auf x”). Dementsprechend wirkt sich die
Anweisung *z=20 auf den Wert von x aus, und ebenso die Wertänderung von x
auf das derefenzierte *z.
3.11.6 Zeiger als Funktionsparameter
In dem Beispiel werden zwei Vektoren x, y und ein Skalar α übergeben, zurückgegeben werden z = x + αy und die euklidische Norm kzk2 .
pointerret.cpp: Rückgabe mehrerer Werte
1
2
3
# define n 3
101
3.11 Zeiger
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
double vektornorm ( double * , double * , double * , double );
int main () {
double x [3] = {4.0 ,2.0 ,2.0};
double y [3] = { -0.5 ,0.5 ,2.0} , z [3];
double alpha = 4.0 , norm ;
int i ;
norm = vektornorm (x ,y ,z , alpha );
cout << " z ␣ = ␣ " << endl ;
for ( i =0; i < n ; i ++) {
cout << z [ i ] << endl ;
}
cout << " || ␣ z ␣ || ␣ = ␣ " << norm << endl ;
}
double vektornorm ( double *X , double *Y , double *Z ,
double s ) {
int i ;
double sum = 0.0;
for ( i =0; i < n ; i ++) {
Z [ i ] = X [ i ] + s * Y [ i ];
sum += Z [ i ]* Z [ i ];
}
return sqrt ( sum );
}
z =
2
4
10
|| z || = 10.9545
An diesem Beispiel erkennt man:
• Von der Notation her ist ein Feld identisch mit einem Zeiger auf eine Variable (siehe Prototyp von vektornorm). Bei Feldern zeigt der Zeiger auf
den ersten Feldeintrag bzw. der Name des Arrays wird in C++ per Standardumwandlung (d.h. automatisch) in einen Zeiger auf die erste Feldkomponente umgewandelt.
• Die Definition der Funktion bzw. des Prototyps ist mit x[] oder *x möglich, dies macht keinen Unterschied in der Funktionsweise.
Man beachte, dass man in beiden Fällen x in der Funktion eine neue Adresse zuweisen kann. Dieses hat natürlich gravierende Auswirkungen, die in
der Regel nicht beabsichtigt sind.
Ein großer Unterschied besteht aber zur Verwendung von x[N] in der
Funktionsdefinition (wobei N eine Konstante ist). In diesem Fall kann die
Funktion nur mit einem Feld genau dieser Größe aufgerufen werden.
• Der Funktionsaufruf erfolgt nicht mit &x,&y,...
x,y,..., da der Feldname für die Adresse steht.
sondern mit
102
Kapitel 3: Einführung in C/C++
• Bei Benutzung des Feldindexes (vgl. vektornorm) wird der
Dereferenzierungsoperator nicht benötigt, da sich bei Zugriffen auf
Felder Feldzeiger und Feldname nicht unterscheiden (beide beinhalten die
Adresse des Feldes).
3.11.7 Weitere Zeiger-Typen
1. Zeiger auf Zeiger
Datentyp ** Name ;
Matrizen von dynamischer Größe kann man durch Zeiger auf Zeiger realisieren. Dabei muss der Speicherbereich in zwei Schritten alloziert werden:
Zuerst wird ein Feld von Zeigern auf Zeiger auf den Datentyp alloziert,
anschließend wird für jeden Zeiger des Feldes ein Feld vom Datentyp allociert und dem Zeiger zugeweisen.
Im Vergleich zur direkten Deklaration einer Matrix mit N mal M Elementen, wird also nicht nur der Speicherplatz für die N mal M Elemente
angelegt, sondern zusätzlich ein Feld von N Elementen, das die Zeiger auf
jeweils M Elemente (die Zeilen der Matrix) enthält.
Das Freigeben das Speichers erfolgt ebenfalls in zwei Schritten, aber in
anderer Reihenfolge.
matrix.cpp: Matrixoperationen
1
2
3
4
5
6
7
8
9
10
11
void initMatrix ( double ** m , int size );
void writeMatrix ( double ** m , int size );
int main () {
double ** v =0;
int size = 10;
v = new double *[ size ];
for ( int i =0; i < size ; i ++) {
v [ i ] = new double [ size ];
}
initMatrix (v , size );
writeMatrix (v , size );
12
13
14
15
16
17
18
19
20
for ( int i =0; i < size ; i ++) {
delete [] v [ i ];
}
delete [] v ;
}
3.11 Zeiger
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
103
void initMatrix ( double ** m , int size ) {
for ( int i =0; i < size ; i ++) {
for ( int j =0; j < size ; j ++) {
m [ i ][ j ] = i == j ?1:0;
}
}
}
void writeMatrix ( double ** m , int size ) {
for ( int i =0; i < size ; i ++) {
for ( int j =0; j < size ; j ++) {
cout << m [ i ][ j ] << " ␣ " ;
}
cout << endl ;
}
}
2. Felder von Zeigern
Datentyp * Name [ n ];
int a =10 , b =20 , * c [10];
c [0] = & a ;
c [1] = & b ;
* c [0] = 30;
c [2] = c [1];
* c [2] = 40;
3. Zeiger als Funktionswerte
Rückgabetyp * Name ( ptyp1 , ptyp2 , ...)
Der Rückgabewert einer Funktion darf auch ein Zeiger sein. In der Funktion allozierter Speicher bleibt auch außerhalb bestehen.
matrix2.cpp: Speicher für Matrix reservieren
1
2
3
4
5
6
7
8
9
10
11
12
double ** newMatrix ( int size );
void initMatrix ( double ** m , int size );
void writeMatrix ( double ** m , int size );
void deleteMatrix ( double ** m , int size );
int main () {
double ** v =0;
int size = 10;
v = newMatrix ( size );
initMatrix (v , size );
writeMatrix (v , size );
104
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Kapitel 3: Einführung in C/C++
deleteMatrix (v , size );
}
double ** newMatrix ( int size ) {
double ** m = new double *[ size ];
for ( int i =0; i < size ; i ++) {
m [ i ] = new double [ size ];
}
return m ;
}
void deleteMatrix ( double ** m , int size ) {
for ( int i =0; i < size ; i ++) {
delete [] m [ i ];
}
delete [] m ;
}
4. Zeiger auf Funktionen
Rückgabetyp (* Name )( ptyp1 , ptyp2 , ...)
(
Beispiel: Zusammengesetzte Funktion f (x) =
f1 (x) ;
f2 (x) ;
x>0
x ≤ 0,
zeigerfkt.cpp: Zeiger auf Funktionen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double f1 ( double x ) { return x * x ;}
double f2 ( double x ) { return 0;}
int main () {
double x ;
double (* fkt )( double ); // wie f1 und f2 !
cout << " Funktionswert ␣ x ␣ eingeben : ␣ " ;
cin >> x ;
if (x >0)
fkt = f1 ;
else
fkt = f2 ;
cout << " f ( x ) ␣ = ␣ " << ( fkt )( x ) << endl ;
}
Bei der Definition muss *name eingeklammert sein (vgl. 3), beim Aufruf
könnten diese Klammern weggelassen werden. Man sollte trotzdem Klammern setzen, um die Benutzung eines Funktionszeigers zu symbolisieren.
Der Name eines Funktionszeigers ohne alle Klammern (auch ohne Funktionsaufrufklammern) repräsentiert dagegen die Adresse und wird an den
entsprechenden Stellen benutzt (analog zu Feldern).
3.11 Zeiger
105
Beachte: Ein Funktionszeiger kann nur Adressen von Funktionen aufnehmen, die gleichen Rückgabetyp und gleiche Parameterliste besitzen.
3.11.8 Zeigerarithmetik
Mit Zeigern kann man auch „rechnen“. Die Standardoperationen sind dafür sinnvoll neubelegt: Beispielsweise in- bzw. dekrementieren die Operatoren ++ und -einen Zeiger um die Größe des Variablentyps, auf den der Zeiger zeigt.
pointerar.cpp: Zeigerarithmetik
1
2
3
4
5
6
7
8
9
int main () {
int x =10 , * y ;
y = &x;
cout << " ␣ y ␣ = ␣ "
cout << " * y ␣ = ␣ "
y ++;
cout << " ␣ y ␣ = ␣ "
cout << " * y ␣ = ␣ "
}
y
*y
y
*y
=
=
=
=
<< y << endl ;
<< * y << endl ;
<< y << endl ;
<< * y << endl ;
0xbfffebc0
10
0xbfffebc4
1076098592
Aber jetzt zeigt y nicht mehr auf x und das Resultat von *y ist undefiniert.
Allgemein kann man ganze Zahlen mit Zeigern addieren bzw. subtrahieren (dann
wird der Zeiger um entsprechend viele Adressen verschoben), Zeiger miteinander
vergleichen oder Differenzen von Zeigern berechnen. Insbesonder ist *(z+n) das
Gleiche wie z[n].
pointerar2.cpp: Vergleich von Zeigern
1
2
3
4
5
6
7
8
9
10
int main () {
int v [10]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10};
int n =10 , * z ;
int sum = 0;
for ( z = v ; z < v + n ; z ++) {
sum += * z ;
}
cout << " Summe ␣ ist ␣ " << sum << endl ;
}
Ein weiteres Beispiel soll anhand des Beispielprogramms pointerret.cpp diese
äquivalente Formulierung weiter verdeutlichen:
106
Kapitel 3: Einführung in C/C++
pointer3.cpp: Vergleich von Zeigern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# define n 3
double vektornorm ( double * , double * , double * , double );
int main () {
double x [3] = {4.0 ,2.0 ,2.0};
double y [3] = { -0.5 ,0.5 ,2.0} , z [3];
double alpha = 4.0 , norm ;
int i ;
norm = vektornorm (x ,y ,z , alpha );
cout << " z ␣ = ␣ " << endl ;
for ( i =0; i < n ; i ++) {
cout << z [ i ] << endl ;
}
cout << " || ␣ z ␣ || ␣ = ␣ " << norm << endl ;
}
double vektornorm ( double *X , double *Y , double *Z ,
double s ) {
int i ;
double sum = 0.0;
for ( i =0; i < n ; i ++) {
*( Z + i ) = *( X + i ) + s *(*( Y + i ));
sum += *( Z + i )*(*( Z + i ));
}
return sqrt ( sum );
}
3.11.9 Sichere Allozierung
Was passiert, wenn new den gewünschten Speicherbereich nicht allozieren kann?
In C++ wird ein spezieller Fehlerbehandlungsmechanismus eingeleitet: Dazu
muss der Fehler aber in einem überwachten try-Block ausgelöst werden, um
im anschließenden catch-Block wieder aufgefangen und behandelt zu werden.
Der Fehler (besser Ausnahme oder Exception) für Speicherplatzprobleme heißt
bad_alloc:
memory2.cpp: Sichere Allozierung mit Exceptions
1
2
3
4
int main () {
double * v ;
try {
for ( int i =0;; i ++) {
3.12 Zeichenketten
v = new double [100000];
cout << i << " ␣ " << v << endl ;
5
6
7
8
9
10
11
12
107
}
}
catch ( bad_alloc ) {
cerr << " Kein ␣ Speicher ␣ mehr ␣ frei ! " << endl ;
}
}
Eine weitere Lösung wäre, den Compiler mit dem Befehl new(nothrow) anzuweisen, den Nullzeiger zurückzugeben, wenn nicht mehr ausreichend Speicher
reserviert werden konnte.
memory.cpp: Sichere Allozierung mit Nullzeigern
1
2
3
4
5
6
7
8
9
10
11
int main () {
double * v ;
for ( int i =0;; i ++) {
v = new ( nothrow ) double [100000];
cout << i << " ␣ " << v << endl ;
if (! v ) {
cerr << " Kein ␣ Speicher ␣ mehr ␣ frei ! " << endl ;
break ;
}
}
}
3.12 Zeichenketten
3.12.1 Der Datentyp char
Zwar können in char auch Zahlen abgespeichert werden, aber eigentlich wird dieser Datentyp als Zeichen (Ziffern, Buchstaben, Sonderzeichen, Steuerzeichen)
interpretiert.
Zeichenliterale werden durch einfache Anführungszeichen gekennzeichnet:
char zeichen = ’d ’;
zeichen += ’A ’ - ’a ’;
// Nun ist zeichen ein ’D ’.
Der Datentyp char spielt eine Doppelrolle: Ein Variable des Typs belegt
1 Byte = 8 Bits Speicherplatz, entsprechend ist jeder mögliche char-Wert c
108
Kapitel 3: Einführung in C/C++
eindeutig durch eine Zahl −128 ≤ c ≤ 127 beschrieben. Der Compiler unterscheidet nicht zwischen Zeichen und Zahl, man kann deshalb mit Zeichen auch
rechnen:
zeichen = ’a ’ + 5;
zeichen += ’A ’ - ’a ’;
// zeichen ist ’f ’.
// Nun ist zeichen ein ’F ’.
ascii.cpp: Erzeugen einer (lokalen) ASCII-Tabelle
1
2
3
4
5
int main () {
for ( char c =32; c <=126; c ++)
cout << " Zeichen ␣ " << c << " ␣ hat ␣ ASCII ␣ Code ␣ "
<< ( int ) c << endl ;
}
Die Doppelrolle kann man auch ausnutzen um char-Variablen für Fallunterscheidungen bei switch-case-Konstrukten zu benutzen:
switch ( c ) {
case ’a ’:
case ’! ’:
}
...
...
3.12.2 Die Funktionen von ctype.h
Nützliche Funktionen zur Behandlung von char-Variablen und -Konstanten findet man in dem Modul <ctype.h> (siehe Anhang A.1).
ctype.cpp: Beispielprogramm zu ctype.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# include < iostream >
# include < ctype .h >
using namespace std ;
int main () {
char z , dummy ;
do {
cout << " Bitte ␣ ein ␣ Zeichen ␣ eingeben : ␣ " ;
cin . get ( z );
cin . get ( dummy );
cout << " Der ␣ ASCII - Code ␣ des ␣ Zeichens ␣ ist ␣ "
<< ( int ) z << endl ;
cout << " Zeichen ␣ als ␣ Grossbuchstabe : ␣ ’"
<< ( char ) toupper ( z ) << " ’" << endl ;
cout << " ... ␣ und ␣ als ␣ Kleinbuchstabe : ␣ ’"
3.12 Zeichenketten
17
18
19
20
109
<< ( char ) tolower ( z ) << " ’" << endl ;
}
while ( isgraph ( z ));
}
Bitte ein Zeichen eingeben:
Der ASCII-Code des Zeichens
Zeichen als Grossbuchstabe:
... und als Kleinbuchstabe:
Bitte ein Zeichen eingeben:
Der ASCII-Code des Zeichens
Zeichen als Grossbuchstabe:
... und als Kleinbuchstabe:
Bitte ein Zeichen eingeben:
Der ASCII-Code des Zeichens
Zeichen als Grossbuchstabe:
... und als Kleinbuchstabe:
a
ist 97
’A’
’a’
!
ist 33
’!’
’!’
ist 32
’ ’
’ ’
3.12.3 Steuerzeichen
Einige Zeichen sind für Steuerzeichen reserviert.
Steuerzeichen
\a
\b
\f
\n
\r
\t
\v
\"
\’
\?
\\
Name
BEL (bell)
BS (backspace)
FF (formfeed)
NL (newline)
CR (carriage return)
HT (horizontal tab)
VT (vertical tab)
Ausgabe
akustisches Warnsignal
Rückschritt
Seitenvorschub
Zeilenwechsel
Wagenrücklauf
horizontaler Tabulator
vertikaler Tabulator
doppelte Anführungszeichen "
einfache Anführungszeichen ’
Fragezeichen ?
Backslash \
3.12.4 Zeichenketten
Durch Zusammenfassung von char-Konstanten oder -Variablen bildet man Zeichenketten. Zumindest ist dies die Methode, die C anbietet. In C++ hat man
110
Kapitel 3: Einführung in C/C++
die Möglichkeit die Zeichenketten (Strings) der Standardbibliothek zu verwenden. Die damit verbundenen Vereinfachungen undVerbesserungen werden wir
im nachfolgenden Kapitel ”Strings” betrachten.
Literale Zeichenketten werden durch doppelte Anführungszeichen gekennzeichnet.
char * s = " xyz " ;
char s [4] = { ’x ’ , ’y ’ , ’z ’ };
Mit Ausnahme der Initialisierung muss die Verarbeitung von Zeichenketten ansonsten elementweise erfolgen (vgl. Felder), d.h. Zuweisungen der Art
str = "abcd" sind nicht erlaubt.
Jede Zeichenkette besitzt eine Endekennung (intern als Wert 0 gespeichert), um
den Umgang mit flexiblen Textlängen zu erlauben. Damit ist die
Textlänge
=
Anzahl Zeichen
+
1
≤
Feldlänge .
Bei der Ausgabe einer Zeichenkette werden dann nur die Zeichen bis zur Endekennung ausgegeben, auch wenn die Zeichenkette als größeres Feld definiert
ist.
zeichen.cpp: Zeichen einzeln ausgegeben
1
2
3
4
5
6
7
8
9
int main () {
char s [20] = " Sommer ␣ 2006 " ;
int i = 0;
while ( s [ i ]) {
cout << s [ i ];
i ++;
}
cout << endl ;
}
Der Text s wird zeichenweise ausgegeben, bei Erreichen der Endekennung ist
s[i]==0 und die while-Schleife bricht ab.
Beim Umgang mit Zeichenketten muss man darauf achten, dass genügend Speicherplatz zur Verfügung steht: Bei durch char s[n] definierten Zeichenketten
dürfen nur (inkl. Endekennung) n Zeichen benutzt werden, bei der Definition
char *s muss man sogar noch hinreichend viel Speicherplatz beschaffen. Ansonsten werden undefinierte Speicheradressen benutzt.
Felder von Zeichenketten werden durch char name[n1][n2] oder
char *name[n1] definiert.
Beispiel: Speicherung der Wochentage
a) char tage[7][11] = {"Montag", ..., "Sonntag"}
Die Zeilenlänge ist durch den längsten Wochentag ("Donnerstag") festgelegt.
b) char *tage[7] = {"Montag", ... }
Ein Feld von Zeigern, jeder Eintrag zeigt auf eine Zeichenkette von variabler Länge. Diese Variante verbraucht weniger Speicherplatz.
3.12 Zeichenketten
111
3.12.5 Ein- und Ausgabe von Zeichenketten
Für die Ausgabe von Zeichenketten benutzt man std::cout, die Eingabe ist mit
std::cin möglich:
cin >> s ;
std::cin kann mehr als eine Eingabe gleichzeitig verarbeiten, bei der Eingabe
über die Tastatur müssen die Werte dann durch Leerzeichen getrennt werden:
int i ,j , k ;
cout << " 2 ␣ Zahlen ␣ eingeben : ␣ " ;
cin >> j >> k ;
Was passiert bei cin >> s, wenn z.B. die Zeichenkette "Hello World" eingelesen werden soll?
Es wird nur "Hello" eingelesen, weil danach das Leerzeichen als StringEnde interpretiert und das Einlesen beendet wird. Abhilfe schafft die Funktion getline(), die eine Zeile bis zum Zeilenendezeichen (oder einem anderen
Zeichen) einliest:
cin . getline ( char * str , int n , char ende = ’\ n ’ );
zeichen2.cpp: Einlesen einer ganzen Zeile
1
2
3
4
5
6
int main () {
char s [20];
cout << " String ␣ eingeben : ␣ " ;
cin . getline (s ,20);
cout << s << endl ;
}
String eingeben: hello world
hello world
3.12.6 Die Funktionen von string.h
Weitere nützliche Funktionen zum Umgang mit Strings liefert <string.h> (Anhang A.2).
Einige Beispiele zur Anwendung:
• strlen ermittelt die Länge eines Strings ohne Endekennung.
Syntax: size_t strlen(const char *s)
Dabei ist size_t ein spezieller Datentyp, der alle möglichen Speicherbereichsgrößen enthält, und const ist ein Attribut zur Kennzeichnung von
112
Kapitel 3: Einführung in C/C++
Konstanten, mit dem man klarstellt, dass der Wert von s in der Funktion
nicht verändert werden kann.
• strcpy dient zum Kopieren von Strings.
Syntax: char *strcpy(char *ziel, const char *quelle)
Weil die Zuweisung t = "Mathematik" so nicht möglich ist, benutzt man
diese Funktion, z.B.
zeichen3.cpp: Zeichenlänge
1
2
3
4
5
6
7
8
int main () {
char s [20] = " Mathematik " ;
int n = strlen ( s );
cout << " n = ␣ " << n << endl ;
char t [20];
strcpy (t , s );
}
• strcmp benutzt man zum Vergleichen von Strings.
Syntax: int strcmp(const char *s1, const char *s2)
Die Funktion liefert genau dann “0”, wenn die Strings identisch sind. Ist
das erste unterschiedliche Zeichen bei s1 größer (im ASCII Code) als bei
s2, dann wird ein positiver, sonst ein negativer Wert ausgegeben.
Weitere nützliche Funktionen zur Umwandlung von Strings in Integer- bzw.
Double-Werte finden sich im Modul stdlib.h:
int
double
atoi(const char *s)
atof(const char *s)
convert.cpp: Umwandlung von Zeichenkette in Zahl
1
2
3
4
5
6
7
int main () {
char * s = " 3.1415 " ;
int j = atoi ( s );
cout << " j = ␣ " << j << endl ;
double x = atof ( s );
cout << " x = ␣ " << x << endl ;
}
3.12.7 Argumente für die Funktion main
Linux-Programme können mit Argumenten aufgerufen werden, z.B.
ls -l *.cpp . Um solche Argumentlisten C++-Programmen zu übergeben, muss die Funktion main folgendermaßen deklariert werden:
3.13 Strings
113
int main ( int argc , char * argv []) {
/* ... */
}
Dabei gibt argc an, wieviele Argumente bereitstehen. Die einzelnen Argumente
stehen als C-String in argv.
arg.cpp: Argumente für das Programm
1
2
3
4
5
6
7
int main ( int argc , char * argv []) {
for ( int i =0; i < argc ; i ++) {
cout << setw (4) << i << " : ␣ "
<< ’" ’ << argv [ i ] << ’" ’ << endl ;
}
}
$ arg -s 120 datei.dat
0: "arg"
1: "-s"
2: "120"
3: "datei.dat"
$ ./arg *.cpp
0: "./arg"
1: "arg.cpp"
2: "ascii.cpp"
3: "bitweise.cpp"
4: "break.cpp"
5: "callref.cpp"
...
In argv[0] steht dabei immer der Programmname, mit dem das Programm aufgerufen wurde. Danach folgen die Argumente, so wie die Shell sie übergibt (also
z.B. mit Wildcard-Ersetzung).
3.13 Strings
Das Arbeiten mit Zeichenketten der Form char s[] ist sehr mühsam und fehleranfällig. Im Modul <string> wird eine Klasse std::string zum eleganten
Umgang mit Zeichenketten definiert.
114
Kapitel 3: Einführung in C/C++
3.13.1 Konstruktoren für Strings
Erzeugen eines leeren Strings.
string ();
Erstellen einer Kopie eines anderen Strings oder Erstellen eines Teilstrings der
Länge n ab Position pos.
string ( const string & str );
string ( const string & str , size_type pos ,
size_type n = npos );
size_type steht in diesem Kapitel immer für eine Position in einem String,
also eine Integerzahl. Die Zahl 0 markiert immer den Anfang, und npos das
Ende des Strings.
Erstellen eines Strings aus einer Zeichenkette (mit der maximalen Länge n).
string ( const char * s );
string ( const char *s , size_type n );
Erstellen eines Strings in dem n mal das Zeichen ch vorkommt.
string ( size_type n , const char & ch );
str_konstr.cpp: Erstellen von Strings
1
2
3
4
5
6
7
8
9
10
11
int main ()
string
string
string
string
cout
cout
cout
cout
<<
<<
<<
<<
{
s1 ( " Vorgang " ); //
s2 ( s1 , 1 , 5); //
s3 ( s1 , 3);
//
s4 (5 , ’1 ’ );
//
s1
s2
s3
s4
<<
<<
<<
<<
endl ;
endl ;
endl ;
endl ;
//
//
//
//
String aus Zeichenkette
Teilstring , 5 Zeichen lang
Teilstring bis zum Ende
fünfmal das Zeichen ’1 ’
Vorgang
organ
gang
11111
}
3.13.2 Verändern von Strings
Strings können den Methoden in diesem Kapitel als Parameter in diesen Formen
übergeben werden:
• const string &str
115
3.13 Strings
• const string &str, size_type pos, size_type n
• const char* s
• const char* s, size_type n
• size_type n, char ch
Die Bedeutung der Parameter entspricht der in den jeweiligen Konstruktoren.
Die Methoden werden hier aber nur für die erste Variante vorgestellt.
Anfügen eines Strings.
string & append ( const string & str );
Außerdem fügt der Operator + strings mit strings, char* oder char
zusammen.
Fügt str an der Stelle index ein.
string & insert ( size_type index , const string & str );
Ersetzt m Zeichen ab index durch str.
string & replace ( size_type index , size_type m ,
const string & str );
Entfernt n Zeichen ab index.
string & erase ( size_type index = 0 , size_type n = npos );
Entfernt alle Zeichen.
void clear ();
Zuweisen an einen String.
string & assign ( const string & str );
Der Operator = weist ebenfalls strings, char* oder char an einen String zu.
str_manip.cpp: Verändern von Strings
1
2
3
4
5
6
7
8
9
10
11
12
13
int main () {
string s1 , s2 ;
s1 = string ( " Hallo " );
s2 = " Welt " ;
cout << s1 + ’␣ ’+ s2 << endl ; // " Hallo Welt "
s1 . append ( s2 );
s1 . insert (5 , " ␣ liebe ␣ " );
s1 . replace (12 ,4 , " Sonne " );
s1 . erase (5);
cout << s1 << endl ;
}
//
//
//
//
" HalloWelt "
" Hallo liebe Welt "
" Hallo liebe Sonne "
" Hallo "
116
Kapitel 3: Einführung in C/C++
3.13.3 Informationen über Strings
Aktuelle Länge des Strings.
size_type length () const ;
size_type size () const ;
Derzeitige Größe des Strings im Speicher.
size_type capacity () const ;
Maximale Größe eine Strings.
size_type max_size () const ;
str_info.cpp: Informationen über String-Längen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void info ( const string & s ) {
cout << setw (12) << s
<< setw (12) << s . length ()
<< setw (12) << s . size ()
<< setw (12) << s . capacity ()
<< setw (12) << s . max_size () << endl ;
}
int main () {
string s1 ( " Hallo ␣ Welt " );
info ( s1 );
s1 . erase (2 ,6);
info ( s1 );
}
Hallo Welt
Halt
10
4
10
4
10
10
1073741820
1073741820
3.13.4 Vergleiche und Tests
Vergleich mit einem anderen String.
int compare ( const string & str );
int compare ( const char * str );
Vergleich von n Zeichen ab Position index mit einem anderen String, oder
einem Teil davon.
3.13 Strings
117
int compare ( size_type index , size_type n ,
const string & str );
int compare ( size_type index , size_type n ,
const string & str , size_type index2 , size_type n2 );
Der Rückgabewert von compare() bestimmt sich nach dieser Tabelle:
Fall
Rückgabewert
this<str
negativer Wert
this==str 0
this>str
positiver Wert
Die Operatoren == != < > <= >= sind ebenfalls gültig.
Prüft, ob der String leer ist.
bool empty () const ;
str_cmp.cpp: Vergleichen von Strings
1
2
3
int main () {
string s1 ( " Hallo ␣ Welt " );
string s2 ( " Welt " );
4
5
6
7
8
9
cout << s1 . compare ( s2 ) << endl ;
// " H " < " W " : -1
cout << s1 . compare (6 ,4 , s2 ) << endl ;
// " Welt "== " Welt " : 0
10
11
12
13
14
15
16
cout << s1 . compare (6 ,4 , s2 ,0 ,3) << endl ;
// " Welt " > " Wel " : 1
cout << ( s1 == s2 ) << endl ;
// 0
}
3.13.5 Umwandeln in Zeichen
Umwandeln in eine Zeichenkette (die mit 0 beendet wird).
const char * c_str ();
Rückgabe des Zeichens an Position n.
char at ( size_type n );
118
Kapitel 3: Einführung in C/C++
Der Operator [] liefert ebenfalls ein Zeichen zurück.
str_char.cpp: Zeichenumwandlung von Strings
1
2
3
4
5
6
7
8
9
10
int main () {
string s ( __FILE__ );
ifstream file ( s . c_str ());
string t ;
file >> t ;
cout << t << endl ;
cout << t . at (3) << endl ;
cout << t . c_str () << endl ;
}
3.13.6 Suchen in Strings
Sucht nach dem ersten Vorkommen von str ab der Position index und gibt die
Position zurück, oder npos, falls nichts gefunden wurde.
size_type find ( const string & str , size_type index );
Sucht rückwärts nach dem Suchstring.
size_type rfind ( const string & str , size_type index );
Sucht nach erstem Vorkommen eines Zeichens aus str.
size_type find_first_of ( const string & str ,
size_type index = 0);
Analog:
• find_first_not_of sucht nach erstem Nicht-Vorkommen eines Zeichens.
• find_last_of sucht nach letztem Vorkommen eines Zeichens.
• find_last_not_of sucht nach letztem Nicht-Vorkommen eines Zeichens.
3.13.7 Sonstiges
Rückgabe eines Teilstrings.
string substr ( size_type index , size_type n = npos );
3.14 Umgang mit Dateien
119
Anfügen eines Zeichens ans Ende.
void push_back ( const char val );
3.14 Umgang mit Dateien
Sind komplexe Daten zu verarbeiten, dann ist die Eingabe über Tastatur mühsam
und langwierig. Effizienter ist das Einlesen aus einer Datei. Umgekehrt ist es
bei umfangreicheren Ergebnissen angebrachter, sie in eine Datei zu schreiben als
auf dem Bildschirm auszugeben.
Dafür unterscheidet man zwei Arten von Dateien:
– Textdateien: editierbarer „Klartext“, geordnete Zeichenfolgen in ASCII,
zeilenweise strukturiert.
– Binärdateien: Byteweise Speicherung der Daten (so, wie sie im Computer
intern abgelegt werden), geordnete, aber unstrukturierte Zeichenfolge.
Wir wollen hier nur Textdateien bearbeiten.
Die Standardbibliothek stellt in fstream Stream-Klassen6 für den Dateizugriff
zur Verfügung: std::ofstream für die Dateiausgabe und std::ifstream für
das Lesen einer Datei.
Mit den Elementfunktionen open() und close() kann die Datei geöffnet und
geschlossen werden. Dazwischen kann mit dem Operator << bzw. >> in die Datei
geschrieben werden, bzw. aus ihr gelesen werden.
file.cpp: Datei zum Schreiben öffnen
1
2
3
4
5
6
7
8
9
# include < fstream >
using namespace std ;
int main () {
ofstream output ;
output . open ( " test . dat " );
output << " Hallo ␣ Welt ! " << endl ;
output . close ();
}
Wenn ein Objekt einer Klasse erzeugt wird, können automatisch bestimmte Aktionen ausgeführt werden. Erzeugt man ein ofstream-Objekt, kann beispielsweise gleich eine Datei angegeben werden, die geöffnet werden soll. Wird der
Gültigkeitsbereich des Objektes verlassen, können ebenfalls automatisch Aktionen durchgeführt werden. Ein ofstream-Objekt schließt dann selbständig die
Datei7 .
6 später...
7 Konstruktor,
Destruktor
120
Kapitel 3: Einführung in C/C++
file2.cpp: Schnell eine Datei öffnen
1
2
3
4
5
6
7
8
9
10
11
int main () {
char c ;
cout << " Ausgabe ? ␣ " ;
cin >> c ;
if ( c == ’j ’) {
ofstream output ( " test . dat " );
// test . dat wurde schon geöffnet !
output << " Hallo ␣ Welt ! " << endl ;
}
// Ende Gü lt igk ei ts ber ei ch output , also close ()
}
Genauso leicht lassen sich Dateien zum Lesen öffnen. Hier ist es noch sinnvoll
zu überprüfen, ob die Datei auch geöffnet werden konnte. Die Funktion good()
prüft, ob die Datei zum Lesen oder Schreiben bereitsteht.
filecheck.cpp: Datei lesen, wenn sie existiert
1
2
int main () {
ifstream input ( " test . dat " );
3
4
5
6
7
8
9
10
11
12
13
if ( input . good ()) {
char buf [30];
input >> buf ;
cout << buf << endl ;
}
else {
cerr << " Fehler ! " << endl ;
// Öffnen ist gescheitert = > Fehlerbehandlung
}
}
Das Programm liest aber nur das erste Wort. Um eine ganze Datei auszulesen,
lesen wir in einer Schleife solange, bis wir das Dateiende erreicht haben. Auch
das lässt sich mit good() überprüfen. Alternativ kann auch der !-Operator verwendet werden, der das Stream-Objekt bewertet und true zurückgibt, wenn ein
Fehler auftrat.
filecheck2.cpp: Datei komplett auslesen
1
2
3
4
5
6
7
8
9
10
int main () {
ifstream input ( " test . dat " );
if (! input ) {
cerr << " File ␣ not ␣ found ! " << endl ;
return 1;
}
while ( true ) {
char buf [30];
input >> buf ;
3.14 Umgang mit Dateien
11
12
13
14
if (! input ) break ;
cout << buf << endl ;
}
}
121
122
Anhang A: Header
A Header
A.1 Die Funktionen von ctype.h
Nützliche Funktionen zur Behandlung von char-Variablen und -Konstanten
findet man in dem Modul <ctype.h>.
Prototyp
Funktionsweise
int isalnum(int)
wahr für einen Buchstaben oder eine Zahl, also a–z,
A–Z, 0–9 oder umgebungsspezifische Buchstaben (in
Deutschland ä, Ä, ö, Ö, ü, Ü, oder ß).
int isalpha(int)
wahr für einen Buchstaben, also a–z, A–Z, oder umgebungsspezifische Buchstaben.
int iscntrl(int)
wahr für Steuerzeichen FF, NL, CR, HT, VT, BEL, BS.
int isdigit(int)
wahr für Dezimalziffern 0–9.
int isgraph(int)
wahr für darstellbare Zeichen außer Leerzeichen.
int islower(int)
wahr für Kleinbuchstaben.
int isupper(int)
wahr für Großbuchstaben.
int isprint(int)
wahr für darstellbare Zeichen inklusive Leerzeichen.
int isxdigit(int)
wahr für hexadezimale Ziffern, also 0–9,a–f,A–F.
int tolower(int)
Umwandlung in Kleinbuchstaben, x = tolower(a)
wandelt a in den entsprechenden Kleinbuchstaben,
ansonsten wird a zurückgegeben.
int toupper(int)
Umwandlung in Großbuchstaben.
A.2 Die Funktionen von string.h
123
A.2 Die Funktionen von string.h
Weitere nützliche Funktionen zum Umgang mit C-Strings liefert <string.h>.
Prototyp
Funktionsweise
memchr
Zeichen im Speicherblock suchen
memcmp
Speicherblöcke vergleichen
memcpy
Speicherblöcke kopieren
memmove
Sicheres Kopieren von Speicherblöcken
memset
Speicherblock initalisieren
strcat
String an einen anderen hängen
strchr
Zeichen im String suchen
strcmp
Zwei Strings vergleichen
strcoll
Zwei Strings umgebungsabhängig vergleichen
strcpy
String kopieren
strerror
Umwandlung eines Fehlers in verbale Form
strlen
Länge des Strings ermitteln
strncat
Teil eines Strings an einen anderen hängen
strncmp
Teile von zwei Strings vergleichen
strncpy
Teile eines Strings kopieren
strrchr
Zeichen vom Stringende aus suchen
strstr
prüft, ob String Teil eines anderen ist
124
Anhang A: Header
A.3 Mathematische Funktionen
Von C++ werden in dem Modul <cmath> die Prototypen für eine Reihe
mathematischer Standardfunktionen zur Verfügung gestellt, die man mit dem
#include-Kommando in das Programm einfügen kann.
Prototyp
Funktionsweise
double acos(double)
Arcus Kosinus: Umkehrfunktion des Kosinus im
Bogenmaß im Intervall [0, π].
w=acos(a); ⇐⇒ w = arccos(a)
double asin(double)
Arcus Sinus: Umkehrfunktion des Sinus im Bogenmaß im Intervall [−π/2, π/2].
w=asin(a); ⇐⇒ w = arcsin(a)
double atan(double)
Arcus Tangens: Umkehrfunktion des Tangens im
Bogenmaß.
w=atan(a); ⇔ w = arctan(a), w ∈ [−π/2, π/2]
double atan2(double,
double)
Umkehrfunktion des Tangens im Bogenmaß.
w=atan2(a,b); ⇔ w = arctan( ab ), w ∈ [−π, π]
double ceil(double)
Obere Gaußklammer: Nächstgrößere Ganzzahl
w=ceil(a); ⇐⇒ w = dae
double cos(double)
Kosinus: Berechnung des Kosinus mit Argument
im Bogenmaß
w=cos(a); ⇐⇒ w = cos(a)
double cosh(double)
Kosinus Hyperbolicus: Berechnung des Kosinus
Hyperbolicus mit Argument im Bogenmaß
w=cosh(a); ⇐⇒ w = cosh(a)
double exp(double)
Exponentialfunktion:
w=exp(a); ⇐⇒ w = ea
double fabs(double)
Absolutwert:
w=fabs(a); ⇐⇒ w = |a|
double floor(double)
Untere Gaußklammer: Nächstkleinere Ganzzahl
w=floor(a); ⇐⇒ w = bac
double fmod(double,
double)
Modulofunktion: analog zu a%b
double frexp(double,
int *)
Aufspaltung von a in a = f · 2i , f ∈ [0.5, 1) wird
zurückgegeben, i in b gespeichert
f=frexp(a,&b); ⇐⇒ a = f · 2b
w=fmod(a); ⇐⇒ w = a mod b
A.3 Mathematische Funktionen
125
Prototyp
Funktionsweise
double ldexp(double,
int)
Umkehrfunktion zu frexp.
w=ldexp(a,b); ⇐⇒ w = a · 2b
double log(double)
Logarithmus Naturalis: Logarithmus zur Basis e
w=log(a); ⇐⇒ w = ln(a) = loge (a)
double log10(double)
Dekadischer Logarithmus: Logarithmus zur Basis
10
w=log10(a); ⇐⇒ w = log10 (a)
double modf(double,
double *)
Aufspaltung einer Zahl a in Ganzahlanteil i und
Rest. f = a − i, f ∈ [0, 1) wird zurückgegeben,
i ∈ Z in b gespeichert.
f=modf(a,&b); ⇐⇒ a = b + f
double pow(double,
double)
Potenzfunktion:
double sin(double)
Sinus: Berechnung des Sinus mit Argument im
Bogenmaß
w=pow(a,b); ⇐⇒ w = ab
w=sin(a); ⇐⇒ w = sin(a)
double sinh(double)
Sinus Hyperbolicus: Berechnung des Sinus Hyperbolicus mit Argument im Bogenmaß
w=sinh(a); ⇐⇒ w = sinh(a)
double sqrt(double)
Quadratwurzel:
w=sqrt(a); ⇐⇒ w =
double tan(double)
√
a
Tangens: Berechnung des Tangens mit Argument
im Bogenmaß
w=tan(a); ⇐⇒ w = tan(a)
double tanh(double)
Tangens Hyperbolicus: Berechnung des Tangens
Hyperbolicus mit Argument im Bogenmaß
w=tanh(a); ⇐⇒ w = tanh(a)
126
Anhang B: Kompatibilitäten zu C
B Kompatibilitäten zu C
B.1 Ein-/Ausgabe
Unter C wird die Kommunikation mit der Konsole im Modul <stdio.h> erklärt. Die Funktion printf() dient zur formatierten Ausgabe und scanf() zur
formatierten Eingabe.
Ein Formatierungsstring gibt jeweils an, wie die Ein- oder Ausgabe erfolgen soll:
int printf ( const char * format , Typ Name , ...);
int scanf ( const char * format , Typ & Name , ...);
# include < stdio .h >
int main () {
int i ;
printf ( " Bitte ␣ Zahl ␣ eingeben : ␣ " );
scanf ( " % d " , & i );
double k = 1./ i ;
printf ( " Der ␣ Kehrwert ␣ von ␣ % d ␣ ist ␣ % f \ n " , i , k );
}
Der Formatierungsstring kann neben normalem Text Formatierungsanweisungen enthalten, die mit % eingeleitet werden. Für jede dieser Formatierungsanweisungen wird eines der weiteren Argumente vom Aufruf von printf bzw. scanf
verarbeitet.
Eine Formatierungsanweisung besteht aus
%[ Flags ][ Feldbreite ][. Genauigkeit ] Typspezifizierer
B.1 Ein-/Ausgabe
Flag
Bedeutung
0
Auffüllen mit Nullen
-
Linksbündig
+
Vorzeichen (+ oder -) immer mit ausgeben
’ ’
Platzhalter für Vorzeichen
Typspezifizierer
erlaubter Typ
Ausgabe
d, i
int
Ganzzahlen
u
unsigned int
Ganzzahlen
f
double
Fließkommazahlen in normaler
Darstellung
e
double
Fließkommazahlen in wiss. Darstellung
c
unsigned char
Zeichen
s
const char *
C-String
o
int
Oktalzahlen
x, X
int
Hexadezimalzahlen
127
B.1.1 Formatierungsanweisungen für Ganzzahlen
Rechtsbündige Ausgabe mit 6 (Leer-)Stellen
printf ( " Wert ␣ 1 ␣ = ␣ %6 i \ n " , 42);
printf ( " Wert ␣ 2 ␣ = ␣ %6 i \ n " , 42424);
Wert 1 =
Wert 2 =
42
42424
Benötigt die Variable mehr Platz als im Format angegeben ist, werden trotzdem
alle Ziffern angezeigt, wobei aber das Format nicht mehr berücksichtigt wird.
printf ( " Wert ␣ 2 ␣ = ␣ %1 i \ n " , 42424);
Wert 2 = 42424
128
Anhang B: Kompatibilitäten zu C
Linksbündige Ausgabe mit 6 (Leer-)Stellen
printf ( " Wert ␣ 1 ␣ = ␣ % -6 i \ n " , 42);
printf ( " Wert ␣ 2 ␣ = ␣ % -6 i \ n " , 42424);
Wert 1 = 42
Wert 2 = 42424
Rechtsbündige Ausgabe mit 6 Stellen, Auffüllen mit Nullen
printf ( " Wert ␣ 1 ␣ = ␣ %06 i \ n " , 42);
printf ( " Wert ␣ 2 ␣ = ␣ %06 i \ n " , 42424);
Wert 1 = 000042
Wert 2 = 042424
Immer das Vorzeichen mit ausgeben
printf ( " Wert ␣ 1 ␣ = ␣ %+ i \ n " , 42);
printf ( " Wert ␣ 1 ␣ = ␣ %+ i \ n " , -42);
Wert 1 = +42
Wert 2 = -42
Ausgabe von „%“
printf ( " Wert ␣ 1 ␣ = ␣ % i %%\ n " , 42);
Wert 1 = 42%
Ausgabe von Zahlen im Oktal- oder Hexadezimalformat
printf ( " Wert ␣ dezimal ␣ = ␣ % i \ n " , 2748);
printf ( " Wert ␣ oktal ␣ = ␣ % o \ n " , 2748);
printf ( " Wert ␣ hexadezimal ␣ = ␣ % X \ n " , 2748);
Wert dezimal = 2748
Wert oktal = 5274
Wert hexadezimal = ABC
B.1 Ein-/Ausgabe
129
B.1.2 Formatierungsanweisungen für Fließkommazahlen
Die Ausgabe von Fließkommazahlen kann in Fixpunktnotation (z.B. x=123.456)
oder in Exponentialschreibweise (z.B. x=0.123456e-2) erfolgen, wobei angegeben werden kann, wieviele Ziffern vor bzw. nach dem Dezimalpunkt verwendet
werden sollen.
printf.cpp: Fließkommazahlen mit printf
1
# include < stdio .h >
2
3
4
5
6
7
char format [][10] = { " % f " ," %15.2 f " , " %7.2 f " , " %9.4 f " ,
" %6.4 f " , " %.3 f " , " % -15.2 f " , " % -+15.2 f " , " %+15.2 f " ,
" ␣ " ," % e " ," %7.3 e " ," %9.3 e " ," %17.3 e " ," %17.8 e " ,
" -%9.3 e " ," -%9.4 e " ," % -+15.2 e " ," %+15.2 e " , 0};
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int main () {
float x =2367.12864;
printf ( " ␣ ␣ Format ␣ ␣ | ␣ ␣ Ausgabe \ n " );
printf ( " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\ n " );
for ( int i =0; format [ i ][0]; i ++) {
printf ( " ␣ % -8 s ␣ | ␣ " , format [ i ]);
printf ( format [ i ] , x );
printf ( " \ n " );
//
// Das gleiche , nur ausf " uhrlicher ...
//
printf ( " ␣ % -8 s ␣ | ␣ " ," % f " );
printf ( " % f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %15.2 f " );
printf ( " %15.2 f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %7.2 f " );
printf ( " %7.2 f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %9.4 f " );
printf ( " %9.4 f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %6.4 f " );
printf ( " %6.4 f " ,x );
printf ( " \ n " );
130
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
Anhang B: Kompatibilitäten zu C
printf ( " ␣ % -8 s ␣ | ␣ " ," %.3 f " );
printf ( " %.3 f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," % -15.2 f " );
printf ( " % -15.2 f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," % -+15.2 f " );
printf ( " % -+15.2 f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %+15.2 f " );
printf ( " %+15.2 f " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," ␣ " );
printf ( " ␣ " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," % e " );
printf ( " % e " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %7.3 e " );
printf ( " %7.3 e " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %9.3 e " );
printf ( " %9.3 e " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %17.3 e " );
printf ( " %17.3 e " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," %17.8 e " );
printf ( " %17.8 e " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," -%9.3 e " );
printf ( " -%9.3 e " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," -%9.4 e " );
printf ( " -%9.4 e " ,x );
printf ( " \ n " );
printf ( " ␣ % -8 s ␣ | ␣ " ," % -+15.2 e " );
printf ( " % -+15.2 e " ,x );
B.1 Ein-/Ausgabe
printf ( " \ n " );
91
92
93
94
95
96
97
98
131
printf ( " ␣ % -8 s ␣ | ␣ " ," %15.2 e " );
printf ( " %+15.2 e " ,x );
printf ( " \ n " );
}
Format | Ausgabe
-----------------------------%f
| 2367.128662
%15.2f
|
2367.13
%7.2f
| 2367.13
%9.4f
| 2367.1287
%6.4f
| 2367.1287
%.3f
| 2367.129
%-15.2f | 2367.13
%-+15.2f | +2367.13
%+15.2f |
+2367.13
|
%e
| 2.367129e+03
%7.3e
| 2.367e+03
%9.3e
| 2.367e+03
%17.3e
|
2.367e+03
%17.8e
|
2.36712866e+03
-%9.3e
| -2.367e+03
-%9.4e
| -2.3671e+03
%-+15.2e | +2.37e+03
%+15.2e |
+2.37e+03
B.1.3 Eingabe mit scanf
Mit der Funktion scanf können Werte von der Tastatur eingelesen werden. Die
Funktion erzeugt aber keine Ausgabe und muss zum sinnvollen Einlesen mit
printf kombiniert werden.
Während printf der Wert einer Variablen übergeben wird, der mit dem Formatierungsstring ausgegeben werden soll, verarbeitet scanf die Adresse der Variablen, also die Stelle im Speicher, wo die Variable abgelegt ist. Deshalb muss
beim Argument der Adress-Operator & benutzt werden.
Falsche Eingaben (z.B. falscher Typ oder overflow) erzeugen keinen Programmabbruch, sondern es wird mit einem fehlinterpretierten also falschen Wert weitergerechnet.
132
Anhang B: Kompatibilitäten zu C
B.1.4 Ein-/Ausgabe in C-Strings
Mit sprintf und sscanf kann man in C-String schreiben oder aus C-Strings
lesen (statt auf den Bildschirm bzw. von der Tastatur):
char s [20];
sprintf (s , " % i ␣ % f " , 42 , 3.1415); // s == "42 3.1415"
sscanf (s , " % i ␣ % f " , &j ,& p );
// j == 42 , p == 3.1415
B.1.5 Einzelne Zeichen
Auch einzelne Zeichen können verarbeitet werden:
int getchar ()
// Ein Zeichen einlesen
int putchar ( int c ) // Ein Zeichen ausgeben
B.2 Speicherverwaltung
Die Operatoren new und delete sind nur unter C++ verfügbar. Echte CProgramme müssen deshalb auf Funktionen der <stdlib.h> zugreifen, um dynamisch Speicher zu allozieren oder freizugeben.
void
void
void
void
* malloc ( size_t groesse );
* calloc ( size_t anzahl , size_t groesse );
free ( void * ptr );
* realloc ( void * ptr , size_t groesse );
Die Funktion calloc() stellt einen Zeiger auf den Anfang eines Feldes mit
anzahl Komponenten der Größe groesse bereit und initialisiert alle Feldkomponenten mit binären Nullen; malloc stellt einen Zeiger auf einen Speicherbereich
der angegebenen Größe bereit (ohne Initialisierung). Beide liefern den Nullzeiger
zurück, wenn der angeforderte Speicherplatz nicht zur Verfügung steht.
Sinnvollerweise sollte man nicht benötigten Speicher mit der Funktion free()
wieder freigeben. Der angegebene Zeiger muss von einem vorherigen Aufruf der
drei anderen Funktionen geliefert und noch nicht wieder freigegeben worden sein.
realloc() kann man sich als Kombination von free() und malloc() vorstellen: Wenn ptr der Nullzeiger ist, wirkt die Funktion wie malloc(). Wenn ptr
nicht der Nullzeiger, dafür aber groesse=0 ist, wirkt realloc() wie free().
B.2 Speicherverwaltung
133
Andernfalls wird der durch ptr angesprochene Speicherbereich in Abhängigkeit
von groesse verkleinert oder vergrößert, wobei in nicht freigegebenen Bereichen
der Inhalt unverändert bleibt. Zu realloc() gibt es in C++ übrigens keine direkte Entsprechung. Mit new angelegter Speicherplatz kann in der Größe nicht
verändert werden.
int * feld ;
feld = ( int *) ( malloc ( n * sizeof ( int ) ));
feld = ( int *) ( calloc ( n , sizeof ( int ) ));
feld = ( int *) ( realloc ( feld , 2* n * sizeof ( int ) ));
free ( feld );
Der in den Prototypen angegebene, typenlose Zeiger auf void muss mithilfe eines
cast-Operators in den Typ umgewandelt werden, für den Speicher belegt werden
soll.
134
Anhang B: Programmverzeichnis
Programmverzeichnis
zero.cpp
Das nullte und kürzeste C++-Programm . . . .
hello.cpp
Hello world in C++, erster Versuch . . . . . .
hello1.cpp
Hello world in C++, zweiter Anlauf . . . . . .
hello2.cpp
Ein fast perfektes Hello world . . . . . . . . . .
declare.cpp
Deklaration von Variablen . . . . . . . . . . .
declare2.cpp
kürzere Deklaration . . . . . . . . . . . . . . .
rechnen.cpp
Rechnen mit ganzen Zahlen . . . . . . . . . . .
genauigkeit.cpp Rundungsfehler . . . . . . . . . . . . . . . . .
eingabe.cpp
Eingabe mit std::cin . . . . . . . . . . . . . .
fehler.cpp
Fehlerausgabe mit std::cerr . . . . . . . . . .
logik.cpp
Intervalltest . . . . . . . . . . . . . . . . . . .
maximum.cpp
Maximums-Bestimmung . . . . . . . . . . . . .
eingabe–test.cppTest der Eingabe . . . . . . . . . . . . . . . . .
switch.cpp
Switch . . . . . . . . . . . . . . . . . . . . . .
quadrat.cpp
Ein einfacher Funktionsaufruf . . . . . . . . .
quadrat1.cpp
Falsch - Die Funktion ist unbekannt . . . . . .
quadrat2.cpp
Richtig - Prototyp deklariert die Funktion . . .
return.cpp
Rückgabetypen . . . . . . . . . . . . . . . . . .
default.cpp
Parabel mit Default-Argumenten . . . . . . . .
callvalue.cpp call by value . . . . . . . . . . . . . . . . . . .
callref.cpp
call by reference . . . . . . . . . . . . . . . . .
lokal.cpp
Lokale Variablen . . . . . . . . . . . . . . . . .
global.cpp
Globale Variablen . . . . . . . . . . . . . . . .
statisch.cpp
Statische Variablen . . . . . . . . . . . . . . .
rekursiv.cpp
Fakultät als rekursive Funktion . . . . . . . . .
rekursiv2.cpp Fakultät als kurze rekursive Funktion . . . . . .
wurzel.cpp
Die Mathe-Bibliothek . . . . . . . . . . . . . .
degrad.cpp
Bogen- und Gradmaß . . . . . . . . . . . . . .
while.cpp
Eingabe mit Überprüfung als while-Schleife . .
dowhile.cpp
Eingabe mit Überprüfung als do-while-Schleife
while2.cpp
while ohne Anweisungsblock . . . . . . . . . . .
iterativ2.cpp Iterative Version von Fakultät mit while . . .
iterativ.cpp
Iterative Version von Fakultät mit for . . . . .
continue.cpp
Die Schleife läuft mit continue weiter . . . . .
break.cpp
Die Schleife endet mit break sofort . . . . . .
prim.cpp
Primzahltest . . . . . . . . . . . . . . . . . . .
goto.cpp
goto höchstens zur Fehlerbehandlung . . . . . .
feld.cpp
Felder sind keine lokalen Variablen . . . . . . .
formatint.cpp Formatierung von Ganzzahlen . . . . . . . . .
formatflt.cpp Formatierung von Fließkommazahlen . . . . . .
leibniz.cpp
Approximation von π mit Leibniz-Verfahren . .
kehrwert.cpp
Beliebige viele Kehrwerte . . . . . . . . . . . .
pointer.cpp
Zeiger auf Variablen . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
49
52
52
53
54
55
56
59
63
64
66
67
68
69
71
71
71
72
73
73
75
75
76
77
78
78
78
79
80
80
81
81
82
83
84
84
85
87
92
93
95
98
99
135
reference.cpp
pointerret.cpp
matrix.cpp
matrix2.cpp
zeigerfkt.cpp
pointerar.cpp
pointerar2.cpp
pointer3.cpp
memory2.cpp
memory.cpp
ascii.cpp
ctype.cpp
zeichen.cpp
zeichen2.cpp
zeichen3.cpp
convert.cpp
arg.cpp
str_konstr.cpp
str_manip.cpp
str_info.cpp
str_cmp.cpp
str_char.cpp
file.cpp
file2.cpp
filecheck.cpp
filecheck2.cpp
printf.cpp
(De-)Referenzierung von Zeigern . . . .
Rückgabe mehrerer Werte . . . . . . . .
Matrixoperationen . . . . . . . . . . . .
Speicher für Matrix reservieren . . . . .
Zeiger auf Funktionen . . . . . . . . . .
Zeigerarithmetik . . . . . . . . . . . . .
Vergleich von Zeigern . . . . . . . . . .
Vergleich von Zeigern . . . . . . . . . .
Sichere Allozierung mit Exceptions . . .
Sichere Allozierung mit Nullzeigern . .
Erzeugen einer (lokalen) ASCII-Tabelle
Beispielprogramm zu ctype.h . . . . .
Zeichen einzeln ausgegeben . . . . . . .
Einlesen einer ganzen Zeile . . . . . . .
Zeichenlänge . . . . . . . . . . . . . . .
Umwandlung von Zeichenkette in Zahl .
Argumente für das Programm . . . . . .
Erstellen von Strings . . . . . . . . . .
Verändern von Strings . . . . . . . . .
Informationen über String-Längen . . .
Vergleichen von Strings . . . . . . . . .
Zeichenumwandlung von Strings . . . .
Datei zum Schreiben öffnen . . . . . . .
Schnell eine Datei öffnen . . . . . . . .
Datei lesen, wenn sie existiert . . . . .
Datei komplett auslesen . . . . . . . . .
Fließkommazahlen mit printf . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
100
100
102
103
104
105
105
106
106
107
108
108
110
111
112
112
113
114
115
116
117
118
119
120
120
120
129
Zugehörige Unterlagen
Herunterladen