Skript 1 - Hochschule Ravensburg

Werbung
STUDIENGANG
ANGEWANDTE INFORMATIK
STUDIENGANG
INFORMATIONS - UND
KOMMUNIKATIONSTECHNIK
VORLESUNGSUNTERLAGEN ZU PROGRAMMIEREN 1
Grundlagen der Programmierung und Einführung in die Sprache C
Prof. Dr.-Ing. Silvia Keller
Ausgabe: 24.09.00
Seitenzahl:
108
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 2 v o n 108
INHALTSVERZEICHNIS
1
GRUNDLAGEN DER PROGRAMMIERUNG
4
1.1
Vom Problem zu Programm, der Algorithmus
1.1.1 Entwurf eines Algorithmus für ein Suchproblem
1.1.2 Programmablaufplan und Struktogramm
4
6
8
1.2
Arbeitsweise einer programmierbaren Rechenmaschine
15
1.3
1.3.1
1.3.2
1.3.3
Compiler, Interpreter, Binder, Lader
Compiler
Binder
Interpreter
17
17
18
18
1.4
1.4.1
1.4.2
1.4.3
1.4.4
Programmiersprachen
Syntaxdiagramme
Datentypen und Variablen
Operatoren, Ausdrücke, Anweisungen
Unterprogramme und Funktionen
19
19
21
24
25
2
EINFÜHRUNG IN DIE SPRACHE C
27
2.1
Aufbau und Struktur eines C-Programms
2.1.1 Funktionen in C
29
32
2.2
2.2.1
2.2.2
2.2.3
38
44
45
48
Elementare Datentypen, Variablen und Operatoren
Definition von Variablen
Gültigkeitsbereiche von Variablen
Die wichtigsten Operatoren
2.3
Kontrollstrukturen
2.3.1 Fallunterscheidungen
2.3.2 Schleifen
55
55
60
2.4
Ein-/Ausgaben zur Kommunikation mit dem Anwender
2.4.1. Formatierte Bildschirmausgabe
2.7.2. Eingabe von Tastatur
62
62
64
3
3.1
ELEMENTARE ALGORITHMEN
Suchen von Werten
66
66
3.2
Sortieren von Listen
3.2.1 Sortieren mit dem Verfahren InsertienSort
68
68
3.3
70
4
Sortieren und Suchen von strings
UNTERPROGRAMMTECHNIK
73
4.1
Prozeduren und Funktionen
74
4.2
Parameter
75
4.3
Definition von Funktionen in C
76
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 3 v o n 108
4.4
Makros
4.4.1 Vergleich Unterprogrammtechnik mit Makros
79
82
E
84
84
86
E.1
E.2
Ergänzungen zu Operatoren und Ausdrücken
Besondere Operatoren
Implizite Typwandlung in Ausdrücken
5
TEST VON PROGRAMMEN
88
6
REKURSIVE ALGORITHMEN
91
6.1
Rekursion versus Iteration am Beispiel der Fakultät
91
6.2
Die Türme von Hanoi
95
5.3
7
Rekursives Sortieren
SORTIEREN UND SUCHEN NACH DEM HASH-VERFAHREN
99
102
7.1
Sortieren mit Hash-Funktionen
102
7.2
Suchen
103
7.3
Kollissionen
103
7.4
Löschen
104
7.5
Eigenschaften einer Hash-Funktion
7.5.1 Beispiel für übliche Hash-Funktionen
7.5.2 Beispiele für Kollissions-Funktionen
105
105
105
LITERATURVERZEICHNIS
106
A ANHANG
A1
A2
A3
Rangfolge von Operatoren
Syntaxdiagramme
ANSI-Funktionen
107
107
107
108
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
1
Grundlagen der Programmierung
1.1
VOM P
ROBLEM ZU
PROGRAMM,
DER
A u s g a b e : 24.09.00
Seite 4 v o n 108
ALGORITHMUS
Algorithmen - Vom Problem zum Programm
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
echnische nformatik
Problemlösung
Programm
22.09.1997
Problem
Übersetzer
( Compiler )
Entwerfen
Algorithmus
in grafischer Form
Hardwareorientierte
Maschinensprache
problemorientierte
Programmiersprache
Ablaufpläne
Struktogramme
textuelle Beschreibung
nach formalen Regeln
Natürliche Sprache
- nicht formal
Algorithmus: Eine Beschreibung , durch welche Operationen und Daten die Lösung
eines Problems realisiert werden kann. Der Lösungsprozeß kommt nach
endlich vielen Operationen zum Stillstand.
Zur Beschreibung eines Algorithmus benötigt man:
8 Vorrat von Operationen
8 Daten bestimmten Typs wie Zahlen, Buchstaben, logische Werte ...
8 Ausdrucksmittel zur Steuerung der Reihenfolge, in der die
ð
Operationen auszuführen sind
Kontrollstrukturen
Die Beschreibung kann erfolgen in:
8
grafischer Form:
8
textlicher Form:
ð
ð
Ablaufpläne, Struktogramme
Pseudocode, Programmiersprache
Beispiel:
EIN KOCHREZEPT IST EIN ALGORITHMUS. DAS KOCHREZEPT BESCHREIBT EINEN PROZEß , WIE AUS DEN
ZUTATEN EINE SPEISE ZUBEREITET WIRD. DATEN SIND DIE ZUTATEN/MENGEN. OPERATIONEN SIND MISCHEN,
RÜHREN, SCHLAGEN, .....
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 5 v o n 108
DIE E NTWURFSSCHRITTE
PROBLEM
ò
Feststellen und Festhalten von Randbedingungen
•
•
•
Wie sehen die Eingangsdaten aus ?
Welche Resultate sind zu erzielen und in welcher Form ?
Welche Sonderfälle können auftreten und wie soll auf diese reagiert
werden ?
ò
Analyse der Problemstellung
•
•
•
Einordnen in eine Problemklasse
Zerlegen in Teilprobleme und finden von Teillösungen
Zusammensetzen der Lösung aus den Teillösungen
ò
Entwurf eines Algorithmus ( i.a. in grafischer Form )
ò
Formulieren in einer Programmiersprache
ò
PROGRAMM
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 6 v o n 108
1.1.1
Entwurf eines Algorithmus für ein Suchproblem
Gegeben ist eine Liste von ganzen Zahlen und eine ganze Zahl N. Man soll feststellen,
ob die Zahl N in der Liste enthalten ist.
E N T W U R F S S C H R I T T E:
A)
Feststellen der Randbedingungen
Es sind folgende Fragen noch zu klären:
8 Wie groß ist die Liste der Zahlen ? Ist die Länge fest oder variabel ? Wie erhält man die Länge
der Liste, im Falle einer variablen Größe ?
Antwort:
Die Länge der Liste ist variabel. Das Ende der Liste wird durch die Zahl
-1 gekennzeichnet. Die Liste kann maximal 255 Werte enthalten.
8 In welchem Zahlenbereich liegen die Werte in der Liste ?
Antwort:
Die Liste enthält nur Zahlen >= 0. Der größte Wert wird durch die im
Rechner maximal darstellbare Zahl beschränkt.
8 Ist die Liste sortiert oder unsortiert ? Wenn sortiert, aufsteigend oder absteigend sortiert ?
Antwort:
Die Liste ist unsortiert
8 Wie soll das Ergebnis der Suche aussehen im falle daß die Zahl in der Liste vorkommt und im
Falle, das die Zahl nicht in der Liste enthalten ist ?
Antwort:
Wird die Zahl N in der Liste gefunden soll die Antwort lauten:
Die Zahl N kommt an der Position i in der Liste vor.
Wird die Zahl N in der Liste nicht gefunden soll die Antwort lauten:
Die Zahl N kommt in der Liste nicht vor
8 Wie soll das Ergebnis lauten, wenn die Zahl N mehrfach in der Liste vorkommt ?
Antwort:
B)
Es ist nur das erste Vorkommen der Zahl N in der Liste relevant.
Problemanalyse
Dieses Beispiel ist der erste Algorithmus, der in der Vorlesung behandelt wird. Es sind daher noch keine
Problemklassen bekannt. Das Problem führt zur ersten elementaren Problemklasse „SUCHPROBLEM „.
Eine Zerlegung in Teilprobleme ist nicht nötig, da es sich um ein elementare Problemklasse handelt.
C)
Entwurf
Erster Ansatz formuliert in natürlicher Sprache:
Durchlaufe die Liste vom Anfang bis zum Ende und vergleiche jedes Listenelement mit der Zahl N
solange bis entweder die Zahl N in der Liste gefunden wurde oder das Listenende erreicht ist
Zweiter Ansatz formuliert in einer vereinfachten formalen Schreibweise (Pseudosprache ):
Algorithmus:
Suche
Benötigte Daten:
Liste
Liste von maximal 256 ganzen Zahlen. Das Ende der
Liste wird durch den Wert -1 angezeigt.
1
2
1. Zahl
Liste[1]
N
Index
3
4
5
6
7
8
Letzte
Zahl
Liste[8]
Die gesuchte Zahl
Die aktuelle Position eines Wertes in der Liste
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 7 v o n 108
Aktionen:
Index := 1; ( Beginne mit dem ersten Wert in der Liste )
Wiederhole die folgende Zuweisung solange bis Liste[Index] = N oder Liste[Index] = -1
Index:=Index+1 ( Nächste Position in der Liste )
Falls Liste[Index] den Wert N hatte so antworte
Die Zahl N steht an Position Index in der Liste
sonst antworte
Die Zahl N kommt in der Liste nicht vor.
Index wird im Laufe des Algorithmus verändert und ist damit eine Variable. Werte, die sich nicht verändern,
sind Konstanten .
Der Algorithmus enthält folgende Kontrollstrukturen:
8 eine Wiederholung von Aktionen solange bis eine Bedingung erfüllt ist, die bedingte Schleife
8 In Abhängigkeit davon, ob eine Aussage wahr oder falsch ist sollen zwei unterschiedliche
Aktionen erfolgen, die Fallunterscheidung
Dritte Variante grafisch formuliert als Programmablaufplan:
Index:=1
SchleifenBedingung
falsch
Schleifen-Bedingung:
Liste[Index] ungleich N
und
Liste[Index] ungleich -1
Schleife
wahr
Index:=Index + 1
wahr
Liste[Index]
=N
falsch
Fallunterscheidung
Die Zahl kommt an der
Position Index in der Liste vor
D)
Die Zahl kommt in der
Liste nicht vor
Formuliert in der Programmiersprache C
void suche( int Liste[], unsigned int N )
{
unsigned short Index;
Index=0;
while ( Liste[Index] != N && Liste[Index] != -1 ) Index=Index+1;
if ( Liste[Index]== N )
printf(“Die Zahl %u steht an Position %hu in der Liste\n“, N, Index );
else printf(“Die Zahl %u kommt in der Liste nicht vor\n“,N );
}
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 8 v o n 108
In C wird das erste Element einer Liste immer mit dem Index 0 angesprochen.
In einer Liste mit 32 Werten wird der erste Wert mit dem Index 0 und der letzte Wert mit dem Index 31
angesprochen.
Beispiel:
int Liste[32];
/*
Definition einer Liste mit 32 Werten */
Liste[0]=1;
/*
Erster Wert ist die Zahl 1
*/
Liste[31]=32;
/*
Letzter Wert ist die Zahl 32
*/
Im obigen Beispiel wurden folgende C Elemente verwendet:
Kommentarzeichen
Operatoren
/* */
=
==
!=
&&
Zuweisung eines Wertes an eine Variable
Vergleich zweier Werte auf Gleichheit
Vergleich zweier Werte auf Ungleich
Logisches UND
Eine Ausgabe auf dem Bildschirm erfolgt über die Anweisung printf. Alle Stellen in printf, die mit % beginnen,
werden durch Variablenwerte ersetzt.
1.1.2
Programmablaufplan und Struktogramm
Strukturtheorem:
( Böehm, Jacopini, 1966 )
Um den Kontrollfluß eines Programms zu beschreiben reichen drei
Arten von Strukturblöcken aus ( Kontrollstrukturen eines Programms )
ð
Sequenz
Fallunterscheidung ( Selektion )
Schleife ( Iteration )
REGELN DER STRUKTURIERTEN PROGRAMMIERUNG:
( Dijkstra, 1968 )
1.
Ein Strukturblock hat genau einen Eingang und genau einen Ausgang
2.
Aneinanderreihen und Ineinanderschachteln von Strukturblöcken ergibt
wieder einen Strukturblock
3.
Jeder Strukturblock muß mindestens einmal erreicht und verlassen
werden
4.
Elementare Strukturblöcke sind die Zuweisung ( a := b ) oder
Unterprogrammaufrufe
( Makroblöcke )
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
1.1.2.1
PROGRAMMABLAUFPLÄNE ZUR B ESCHREIBUNG EINES
ALGORITHMUS
Seite 9 v o n 108
Programmablaufpläne beschreiben die Abarbeitungsreihenfolge von Programmanweisungen.
Grundelement ist ein Strukturblock, in dem eine elementare Anweisung abgearbeitet wird.
Anweisung
Dieses Grundelement kann in den 3 Arten von Strukturblöcken - Sequenz, Fallunterscheidung und
Schleife - vorkommen.
Der Vorteil von Programmablaufplänen ist, daß sie sehr einfach und schnell gezeichnet
werden können. Nachteil ist jedoch, daß Strukturblöcke entstehen können, die den
Regeln der strukturierten Programmierung widersprechen
A)
Die Sequenz
entsteht durch Hintereinanderreihung von Strukturblöcken. Die Strukturblöcke werden in Pfeilrichtung
nacheinander abgearbeitet.
Anweisung1
Neuer Strukturblock SEQUENZ
Anweisung2
B)
Fallunterscheidung
Nach Auswertung von Aussagen wird entweder der Strukturblock Anweisung1 ausgeführt, wenn die
Aussage wahr ergibt, oder es wird der Strukturblock Anweisung2 ausgeführt, wenn die Aussage falsch
ergibt.
wahr
Anweisung1
Beding.
falsch
Anweisung2
Beispiele:
•
Wenn es regnet gehe ich Turnen sonst gehe ich Fußball spielen
( Natürliche Sprache )
Programmieren 1
•
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 10 v o n 108
Pseudocode :
if Wetter=Regen
then Turnen
else Fußball spielen
endif
wahr
Wetter =
Regen
Turnen
falsch
Fußballsp.
Es gibt den Sonderfall, daß im Falle von falsch gar kein Strukturblock ausgeführt wird.
wahr
Beding.
falsch
Anweisung1
Beispiel:
•
•
Wenn ich im Lotto gewinne kaufe ich mir eine Villa ( sonst nicht )
if Lottogewinn=true then Villa kaufen endif
Es gibt auch Fallunterscheidungen, bei der nach Auswerten einer Bedingung mehrerer Fälle zu
unterscheiden sind.
Beding.
Fall1
Fall2
Fallk
Anweisung1 Anweisung2
Falln
Anweisungk
Anweisungn
Beispiel:
• Falls es regnet gehe ich turnen, falls es schneit gehe ich Ski fahren, falls es windet gehe ich surfen,
falls die Sonne scheint gehe ich schwimmen sonst lese ich.
•
In Pseudocode:
Case Wetter of
Regen : Turnen;
Schnee: Skifahren;
Wind: Surfen;
Sonne: Schwimmen;
else Lesen.;
endcase
Wetter
Regen
Turnen
Schnee
Ski fahren
Wind
Surfen
Sonne
sonst
Schwimmen Lesen
Programmieren 1
C)
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 11 v o n 108
Schleifen
Eine Schleife ist eine Wiederholung von Anweisungen in Abhängigkeit von einer Bedingung.
Man unterscheidet zwei Varianten von bedingten Schleifen.
In der ersten Variante wird zuerst eine Bedingung geprüft. Ergibt die Auswertung der Bedingung wahr, so
werden die Anweisungen in der Schleife wiederholt, bis die Bedingung falsch wird. Bei dieser Art von
Schleife kann schon beim ersten Auswerten der Bedingung falsch ergeben, so daß die Anweisungen der
Schleife nicht ausgeführt werden, d.h. die Schleife wird 0-mal durchlaufen.
Beispiel:
•
•
Solange es regnet arbeite ich am Schreibtisch
while Wetter=Regen do
Arbeit am Schreibtisch
endwhile
Bedingung
falsch
wahr
A
In der zweiten Variante werden zuerst die Anweisungen in der Schleife ausgeführt und dann erst die
Bedingung geprüft. Ergibt die Auswertung der Bedingung wahr, so werden die Anweisungen in der Schleife
wiederholt, bis die Bedingung falsch wird. Bei dieser Art von Schleife werden damit die Anweisungen
mindestes einmal ausgeführt.
Beispiel:
•
•
A
Ich warte solange das Telefon nicht klingelt.
do warten while Telefon <> klingeln
Bedingung
falsch
wahr
Es gibt den Sonderfall einer Schleife, bei der von vornherein feststeht wie oft die Schleife
durchlaufen wird. Solche Schleifen nennt man Zählschleifen.
Im Unterschied zu den Zählschleifen ist bei bedingten Schleifen, die Anzahl von Durchläufen nicht von
vornherein bekannt ist, der Schleifenabbruch wird erst bei Ausführen der Schleifenanweisungen berechnet .
Es kann gezeigt werden, daß jeder Algorithmus auf die Variante 1 von bedingten Schleifen
zurückgeführt werden kann d.h. jede bedingte Schleife der Variante 2 und jede Zählschleife
kann durch eine bedingte Schleife der Variante 1 realisiert werden.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Beispiel strukturierter und unstrukturierter Pläne
Strukturierter Plan:
Die Strukturblöcke werden entweder aneinandergereiht oder ineinander geschachtelt
( Hierarchische Verfeinerung )
A
B
wahr
Beding.
falsch
C
D
E
Unstrukturierter Plan:
Merkmale sind:
8 der unkontrollierte Gebrauch von Sprüngen
8 es können keine Strukturblöcke gefunden werden
8 die Programmzustände sind damit nur schwer erkennbar
A
Beding.
falsch
Sprung in eine Schleife
B
wahr
Beding.
falsch
Dieser Strukturblock
C
•
Weitere Beispiele siehe Übungsblatt 1
hat 2 Ausgänge
Seite 12 v o n 108
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 13 v o n 108
Resümee
8 Man kann auch in einer nicht strukturierten Programmiersprache wie Assembler oder C gut
strukturierte Programme erstellen
8 Die Verwendung einer strukturierten Sprache wie PASCAL bedeutet nicht, daß nur gute
Programme entstehen
8 Die Verwendung sinnvoller Bezeichner gehört ebenso zu einer guten Programmiertechnik wie
eine saubere Strukturierung
1.1.2.2
STRUKTOGRAMME (
NA S S I- S C H N E I D E R M A N N - D I A G R A M M E )
Vorteil:
Strukturblöcke können nur aneinandergereiht oder ineinander verschachtelt werden.
Es können daher nur Strukturblöcke verwendet werden, die den Regeln der Strukturierten
Programmierung folgen.
Nachteil:
Komplexe und aufwendige Grafiken
Struktogramm zu einem
Algorithmus/Programm
Name des Algorithmus
Kurzbeschreibung
Datenobjekte
M-NAME
b
Makro-Block;
wird in einem gesonderten Struktogramm verfeinert
M-NAME : Bezeichner für Teilalgorithmus
Elementarblock;
b: logische Beschreibung der Aktionen
Programmieren 1
Kontrollstruktur
1
Sequenz
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
Struktogramm
Anweisung1
Anweisung2
2
Fallunterscheidung
a
A1
Sonderfall:
vollständige
Alternative
Bedingung
c
b
A2
A3
wahr
falsch
A
2
Bedingung
wahr
A1
3
Schleifen
Bedingte
Schleife
A4
Bedingung
A
1
Sonderfall:
einfache
Alternative
sonst
solange
falsch
A u s g a b e : 24.09.00
Seite 14 v o n 108
Pseudocode
C-Syntax
begin
Anweisung1
Anweisung2
end
{
case Bedingung
of
a: A1;
b: A2;
c: A3;
sonst: A4;
endcase
switch ( Bed )
{
case a: A1;
case b: A2;
case c: A3;
default: A4;
}
if Bedingung
then
A1
else A2
endif
if ( Bed ) A1;
else
A2;
if Bedingung
then
A1
endif
if ( Bed ) A1;
while Bedingung
do
Anweisung1
endwhile
while ( Bed )
A1;
do
do
Anweisung1
while ( Bed )
Ausdruck1;
Ausdruck2;
}
---
Bedingung
A1
Anweisung1
while Bedingung
A1
solange
Bedingung
Zählschleife
von i=Anfang bis Ende
Schrittweite = k
A1
Jede Zählschleife kann durch eine while-Schleife
realisiert werden
for
Schleifenzähler :=
Anfangswert
to Endwert
with
Schrittweite := k
do
Anweisung1
endfor
for
(Sz=Aw;Sz<=Ew; Sz++)
A1;
for
(Sz=Aw;Sz>=Ew;Sz--)
A1;
Programmieren 1
1.2
Prof. Dr.-Ing. Silvia Keller
ARBEITSWEISE
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
EINER PROGRAMMIERBAREN
R
Seite 15 v o n 108
ECHENMASCHINE
Von Neumann Rechnerarchitektur
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Programme abarbeiten
Datenwerte berechnen
echnische nformatik
Maschinenprogramm
Speicher
veränderbare Variablen
( Daten ) des Programms
CPU
Tastatur
Daten eingeben
8
Maschinenprogramm :
Basiszyklus
der
Bildschirm
Daten ausgeben
Sequenz von Grundoperationen der
Maschine
Rechenmaschine:
1. Maschinenbefehl aus dem Speicher holen
⇓
2. Programmzeiger auf das nächste Speicherwort setzen
⇓
3. Befehl interpretieren
⇓
4. Befehl ausführen
⇓
5. weiter bei 1
22.09.1997
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Arbeitsspeicher
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
echnische nformatik
03.10.1997
Speicherzelle
8 Bit = 1 Byte
0
1
2
3
4
00110110
11111111
00001100
11001100
10101010
Die Speicherzellen sind fortlaufend nummeriert
Speicherzellen enthalten entweder
- Maschinenbefehle oder
- Daten
Speicheradresse
Sequentielle Abarbeitung der Maschinenbefehle
Programmzeiger
Bei der Abarbeitung der Befehle
wird der Programmzeiger
automatisch immer auf den
nächsten Maschinenbefehl
verschoben
Befehl
Befehl
Befehl
Befehl
1
2
3
4
Befehl 99
Befehl 100
Variable 1
Variable 2
Beim Sprungbefehl
wird der Programmzeiger auf einen
bestimmten Befehl versetzt
Seite 16 v o n 108
Programmieren 1
1.3
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
C OMPILER, IN T E R P R E T E R, B I N D E R, L
A u s g a b e : 24.09.00
Seite 17 v o n 108
ADER
Programmierumgebung
Prof. Dr. - Ing. S.Keller
Quelltext
FH Ravensburg-Weingarten
Übersetzter
Compiler
Datei
mit dem Progrann
formuliert in einer
Programmiersprache
8
Programmobjekt
Binder
Linker
echnische nformatik
lauffähigesMaschinenprogramm
Lader
Loader
Datei
Datei
enthält ein
mit einem
unfertiges Programm
ausführbaren
in Maschinensprache
Maschinenprogramm
( Executable )
Vorgefertigte
Programmobjekte
22.09.1997
Maschinenprogramm
Im Arbeitsspeicher
Übersetzer, Binder und Lader sind Systemprogramme,
die im Arbeitsspeicher liegen und
dem Anwender ermöglichen den eigene Programme zu schreiben.
1.3.1
Compiler
Programme werden als Text formuliert ( Quelltext ) und durch einen Compiler in eine, der Rechenmaschine
verständliche Sprache übersetzt. Der Compiler ist selbst ein Maschinenprogramm, das auf dem Rechner
ausgeführt wird und zum Betriebssystem des Rechners gehört. Der Compiler speichert das übersetzte
Programm auf der Festplatte in einer Datei ab.
Die Aufgaben des Compilers sind:
8 Zeichenweises einlesen des als Text formulierten Programms ( Quelltext )
8 Erkennung von Grundsymbolen. Dies sind:
•
Operatoren wie z.B. = , + , - , /, & .....
•
Trennzeichen wie z.B. ( ) { } , : ; [ ] ......
•
Schlüsselworte wie if, then, else, while, .....
•
Konstanten wie 1, -5, +1.5, - 3e10 .....
•
Bezeichner, das sind Namen für Programmobjekte, die der Programmierer selbst
vergeben kann wie z.B. Variablennamen
8 Prüfen auf syntaktische Korrektheit
Jede Sprache wird aufgebaut aus Grundsymbolen z.B. besteht unsere natürliche
Sprache aus Worten, die nur Zeichen aus unserem Alphabet enthalten. Diese
Grundsymbole können nur nach bestimmten Regeln, die die Grammatik der Sprache
definieren, zu Sätzen zusammengebaut werden.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 18 v o n 108
Beispiel für eine Grammatikregel der deutschen
S p r a c h e:
Ein Satz setzt sich zusammen aus Subjekt, Prädikat und Objekt.
Die Menge aller Grundsymbole und die grammatikalischen Regeln wird Syntax einer
Sprache genannt.
Zur Syntax einer Programmiersprache gehören daher Regeln, die angeben wie die
Grundsymbole der Programmiersprache miteinander kombiniert werden dürfen., so daß
ein korrektes Programm entsteht.
Diese Regeln können entweder in textueller Form ( Backus Nauer Form : BNF ) oder
aber in grafischer Form, den Syntaxdiagrammen, angegeben werden.
8 Übersetzen des Textes in die Maschinensprache
Ein Maschinenbefehl ist ein Speicherwort bestehend aus einer bestimmten Folge von
0 und 1 Werten.
1.3.2
Binder
Ein Programm benutzt i.a. vorgefertigte Programmobjekte, die nachträglich zu dem übersetzten Programm
hinzugefügt werden. Solche vorgefertigten Programmobjekte sind Systemfunktionen wie z.B.
Programmstücke mit denen man Daten einlesen und ausgeben kann
( in der Programmiersprache C : printf zur Ausgabe auf dem Bildschirm ).
Der Programmierer kann sein Programm jedoch in einzelne Teile ( Programmodule ) zerlegen, die er
getrennt übersetzt, in Objektdateien speichert und dann später erst beim Binden alle diese vorübersetzten
Module miteinander zu einem lauffähigen Programm zusammenfügt ( Modulares Programmieren ). Das
beim binden entstandenen lauffähige Programm, das in einer Datei auf Festplatte gespeichert ist, nennt
man executable.
Erst durch den Lader wird das in Maschinensprache übersetzte Programm von Datei in den Arbeitsspeicher
geladen und dort von der CPU ausgeführt.
1.3.3
Interpreter
Ein Compiler übersetzt das Programm, das nur als komplett lauffähigen Programm in den Arbeitsspeicher
geladen werden kann. Erst nach dem Laden des vollständigen Programms kann das Programm
abgearbeitet werden. Fehler im Programm werden daher erst noch vollständiger Übersetzung entdeckt.
Interpreter übersetzen das Programm zeilenweise. Jede Zeile wird sofort in Maschinensprache übersetzt
und auch sofort ausgeführt. Fehler im Programmfluß können sofort während der Übersetzung erkannt
werden. Nachteil: Die Programmabarbeitung wird deutlich langsamer, da nach jeder Ausführung von
Maschinenanweisungen erst wieder der Interpreter nie nächste Zeile bearbeitet.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 19 v o n 108
1.4 PROGRAMMIERSPRACHEN
1.4.1
Syntaxdiagramme
Syntaxdiagramme beschreiben die Aufbauregeln einer Programmiersprache. Die folgende Tabelle zeigt alle
grafischen Symbole, die als Ausdrucksmittel in Syntaxdiagrammen vorkommen.
Regelform
Grundsymbol
Diese Symbole sind die Grundsymbole der
Programmiersprache und damit nicht weiter
zerlegbar
Syntaxdiagramm
A
Nichtterminales Symbol
Dies sind Symbole, die zur Beschreibung der
Regeln notwendig sind und ersetzt werden durch
neue Symbolfolgen. Diese Ersetzung endet immer
dann, wenn nur noch Grundsymbole vorkommen.
a
Symbolfolge
Ersetzungsregel ( Produktion )
a
b
A
a
Alternative
a
A
A
B
C
Option
a
Wiederholung
Wiederholung als Option
a
C
a
a
a
C
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Beispiele aus der Programmiersprache C
8 Bezeichner ( identifier )
Zeichen
Zeichen
Ziffer
8 Ziffer ( digit )
0
1
2
3
4
5
Z
a
6
7
8
9
8 Zeichen
_
...
A
...
z
Gültige Bezeichner sind damit:
Index, index, INDEX, Zahl1, N10t2
Ungültige Bezeichner sind:
1DM, $wert, n%, suche-wert
8 Sequenz ( compound statement )
{
}
declaration
statement
8 While-Schleife ( while statement )
while
(
expression
)
statement
Seite 20 v o n 108
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
1.4.2
Datentypen und Variablen
A u s g a b e : 24.09.00
Seite 21 v o n 108
Datentypen und Variablen
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
29.09.1997
Arbeitsspeicher
Elementar
Variable
Datentyp
Strukturiert
8
8
Datenwerte, die eine Anzahl von
Speicherzellen im
Arbeitsspeicher belegen
Die Datenwerte können vom
Programm verändert werden
Beschreibt den Wertebereich
einer Variable
Angaben für den Compiler wie
viele Speicherzellen für die
Darstellung der Werte benötigt
werden und wie die Werte intern
dargestellt sind
belegen keinen Speicherplatz
8
8
8
Variablen sind Speicherzellen, deren Werte während der Programmabarbeitung verändert werden
Bei der Definition erhält die Variable einen Namen. Über diesen Namen kann die Variable im Programm
angesprochen werden. Eine Variable muß daher am Programmanfang vor ihrer Verwendung definiert
werden.
Konstanten sind Werte, die nicht veränderbar sind. Konstanten sind entweder
8 direkte Wertangaben wie z.B. 1, -1.5, ‘z’
oder
8 Namen, die einen konstanten Wert benennen wie z.B. der Konstantenname PI
Beispiel für eine Variablendefinitionen in C
int zahl;
/*
int wert = 1;
/*
Die
ist
Die
mit
Variable zahl wird definiert. Der Datentyp von Zahl
int */
Variable wert wird definiert Die Speicherzelle wird
der Konstanten 1 vorbelegt */
Beispiel für Konstanten in C
#define FALSE 0
const double pi=3.1415926535;
/* Die Konstante FALSE wird mit 0 festgelegt */
/* Die Konstante pi wird definiert und mit
einem Wert vorbelegt */
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
1.4.2.1
ELEMENTARE DATENTYPEN
A u s g a b e : 24.09.00
Seite 22 v o n 108
Elementare Datentypen werden durch die Programmiersprache vorgegeben. Ein Wert wird in einem
Speicherwort abgelegt. Wobei ein Speicherwort 1, 2 oder auch mehrere Byte lang sein kann.
Elementare Datentypen sind:
8 Ganze Zahlen ( integer )
Eine Untermenge der ganzen Zahlen bilden die positiven ganzen Zahlen mit Werten >= 0 und
die natürlichen Zahlen mit Werten > 0
8 Reelle Zahlen ( real )
8 Zeichen ( character )
Zeichen sind Buchstaben, Ziffern, Trennzeichen, Sonderzeichen und spezielle
Steuerzeichen, die zur Ansteuerung von Ausgabegeräten benötigt werden.
Im genormten ASCII-Zeichensatz werden 256 Zeichen in einem Byte codiert.
8 Logische Werte ( boolean )
Es gibt nur zwei mögliche Werte wahr (true) oder falsch (false ). Zur Darstellung der
Werte würde daher 1 Bit genügen. Da der Arbeitsspeicher jedoch byteweise organisiert
ist, wird ein logischer Wert immer in einem Byte abgelegt.
8 Zeiger ( pointer )
Zeiger sind Variablen, deren Wert als Referenz ( Zeiger ) auf ein Datenobjekt interpretiert
wird.
Zeigervariable
Datenobjekt
4711
Der Wert des Datenobjektes kann entweder direkt über den Namen der Variablen oder
indirekt über den Zeiger angesprochen werden.
Beispiel:
Direkte Ansprache über den Namen: „Herr Meier, würden Sie bitte wiederholen „
Indirekte Ansprache über Zeigen mit dem Zeigefinger: „ Würde Sie bitte wiederholen „
1.4.2.2
STRUKTURIERTE DATENTYPEN
Strukturierte Datentypen setzen sich aus mehreren Datenelementen zusammen und werden vom
Programmierer festgelegt. Die Programmiersprache stellt hierfür spezielle Konstruktionsregeln bereit.
8 Aufzählungstypen
Der Programmierer definiert eine geordnete Menge von möglichen Werten, die unter
einem Typnamen zusammengefaßt werden. Die Ordnung wird durch
Aufzählungsreihenfolge festgelegt.
Beispiel aus C:
enum Tage { januar, februar, maerz, april, mai };
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 23 v o n 108
Die Werte werden durch den C Compiler hintereinander im Speicher abgelegt. Wobei
jedem Wert eine Nummer beginnend mit 0 zugeordnet wird.
8 Zeichenketten ( string )
Zeichenketten sind eine lineare Folge von Zeichen. Strings werden benötigt um Worte
und Texte zu speichern.
8 Felder ( array )
Felder sind Datenstrukturen, in denen alle Datenelemente vom gleichen Typ sind und
nach Zeilen/Spalten-Regeln zusammengebaut werden.
Eindimensionale Felder sind Listen von gleichartigen Datenwerten. Die einzelnen Werte
werden hintereinander lückenlos im Arbeitsspeicher abgelegt. Auf einzelne Datenwerte
kann im Programm über Angabe der Position ( Index ) zugegriffen werden.
Beispiel:
( 1, 4, 7, 101, 66, 4711, 75, 35 )
Beispiel aus C:
int
liste[32];
liste [5]= 4711;
/*
Definition einer Liste mit 32 ganzen
Zahlen. */
/* In C wird das erste Element einer Liste
immer mit dem Index 0 angesprochen. In
diesem Fall erhält daher das 6. Element in
der Liste den Wert 4711 */
Mehrdimensionale Felder sind entsprechend der mathematischen Definition von
mehrdimensionalen Räumen zu verstehen z.B.
 4711 2 3 


ist  11
25 47  eine zweidimensionale Matrize.
 5
1 0 

Beispiel aus C:
int
matrix[10][30]; /*
liste [0][0]= 4711;
Definition einer Matrix mit 10
Zeilen und 30 Spalten */
/* Das Datenelement in der 1. Zeile und
1. Spalte erhält den Wert 4711 */
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 24 v o n 108
8 Datensätze ( record )
Zusammenfassung von Datenelementen unterschiedlichen Typs zu komplex
strukturierten Datenobjekten. So können Daten zu einer Person wie Vorname,
Nachnahme, Alter, Größe, Gewicht und Geburtsdatum in einer Variablen gespeichert
werden.
Vorname
Alter
Geburtsdatum
Groesse
1.4.3
Nachname
Gewicht
Operatoren, Ausdrücke, Anweisungen
Jede Programmiersprache stellt eine Menge von Operatoren für die Berechnung von komplexen Ausdrücken
zu Verfügung.
Zu den Grundoperationen gehört:
8 der Zuweisungsoperator . Er wird benötigt um Variablen Werte zuweisen zu können
Beispiel aus C:
zahl = 5;
wert = zahl + 1;
8 arithmetische Operatoren wie die MULTIPLIKATION, DIVISION, S UBTRAKTION, A DDITION
8 logische Operatoren zur Bestimmung von logischen werten von Ausdrücken wie UND, ODER,
NICHT
8 Vergleichsoperatoren zum Vergleichen zweier Werten wie KLEINER, GRÖßER, KLEINER-GLEICH,
GRÖßER-GLEICH, UNGLEICH, GLEICH
Die Operatoren werden zusammen mit Variablen und Konstanten zu komplexen Ausdrücken verknüpft.
Jeder Ausdruck hat einen Wert als Resultat. Dieser Wert hat einen zugeordneten Datentyp. Will man den
berechneten Wert aufheben, so muß man das Resultat in einer Variablen mit den korrekten Datentyp
speichern ( Zuweisungsoperator )
Beispiel aus der Sprache C
int zahl = 2, wert = 10;
zahl * ( 5 + wert ) / ( zahl + 8 );
erg = zahl * ( 5 + wert ) / zahl + 10;
/* Ausdruck mit Resultat 3 */
/* Resultat wird in erg gespeichert */
Neben den Operatoren zu Bildung von Ausdrücken muß jede Programmiersprache Anweisungen zu
Verfügung stellen, mit Hilfe derer man Daten einlesen und Ausgeben kann und die den Kontrollfluß des
Programms steuern.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
1.4.4
Unterprogramme und Funktionen
A u s g a b e : 24.09.00
Seite 25 v o n 108
Ein Unterprogramm ist eine Zusammenfassung eines Programmteils zu einem Programmobjekt, das einen
Namen erhält und immer dann, wenn der Unterprogrammname im Programm auftaucht abgearbeitet wird.
Gründe für die Erstellung von Unterprogrammen:
8 Einsparen von Schreibarbeit, wenn Programmteile öfter an verschiedenen Stellen vorkommen
8 Wiederverwendung von schon vorhandenen Programmteilen z. B. die Verwendung von
Unterprogrammen, mit denen man Daten einlesen kann ( in C printf ) oder Datenwerte in eine
Datei schreiben kann.
8 Klare Strukturierung von Programmen.
Beim Entwurf eines komplexen Algorithmus wird das vorgegebene Problem in Teilprobleme
zerlegt. Jedes Teilproblem wird separat bearbeitet. Diese Teile werden dann als Unterprogramme
realisiert.
Man unterscheidet zwei Arten von Unterprogrammen
8 die Prozedur ist eine Zusammenfassung von Aktionen.
8 die Funktion hat die Aufgabe aus Eingangswerten einen Ergebniswert zu berechnen.
Parameter von Unterprogrammen
In ein Unterprogramm können auch Werte aus dem aufrufenden Programm übergeben werden.
Programm
Diese Werte werden als Parameter bezeichnet. Das
Unterprogramm kann dann die Werte seiner Parameter in
Anweisungen verwenden.
Parameter
a
b
Unterprogramm
verwendet a und b
Betrachten wir als Beispiel die Definition einer Funktion. In der Mathematik wird eine Funktion z.B. in der
Form y = sin ( x ) dargestellt.
Die Funktion heißt sin und berechnet den Sinus einer gegeben Zahl x. Eine Funktion erhält also eine
eindeutigen Namen. x ist Platzhalter für den Eingangswert, y ist Platzhalter für den Ergebniswert der
Funktion. Setzt man für x einen bestimmten Wert ein, so erhält man genau einen Ergebniswert. Wird in
obiger Funktion für x der Wert 90 Grad eingesetzt erhält y den Wert 1.
Grafische Darstellung einer Funktion als Black Box
Eingabewert
x
sin
Algorithmus zur
Berechnung des
Sinus
Ergebniswert
y
Den Platzhalter für den Eingangswert bezeichnet man in
Programmiersprachen als formalen Parameter eines
Unterprogramms. Formale Parameter werden bei der Definition
eines Unterprogramms benötigt. Beim Aufrufen eines
Unterprogramms wird dann der formale Parameter x durch einen
konkreten Wert ersetzt. Dieser Wert wird aktueller Parameter
genannt.
Im Falle einer Funktion erhält man nach Abarbeitung des Unterprogramms einen Ergebniswert, den man in
einer Variablen ( hier y ) speichern muß. Prozeduren als Zusammenfassung von Aktionen können zwar
Werte aus dem übergeordneten Programmteil erhalten, liefern jedoch keinen Wert zurück.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 26 v o n 108
Kontrollfluß beim Unterprogrammaufruf
Unterprogrammtechnik
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
04.10.1997
Arbeitsspeicher
Hauptprogramm
8
UP-Name
mehrfache Verwendung
des Unterprogramms
im Hauptprogramm
Hauptprogramm
UP-Name
Rücksprung
ins
Hauptprogramm
UP-Name
Definition
eines
Unterprogramms
UP-Name
Aufrufhierarchie
8
von
UP-Name
Unterprogramm
wird genau einmal
in den Arbeitsspeicher geladen
Anspringen
des
Unterprogramms
Unterprogrammen
Die Verwendung von Unterprogrammen dient hauptsächlich der hierarchischen Strukturierung von
Programmen. Ein Hauptprogramm verwendet z.B. die Unterprogramme BERECHNE und TUE . Das
Unterprogramm BERECHNE ruft die Unterprogramme POTENZ und WURZEL auf.. Das Unterprogramm
TUE ruft das Unterprogramm AUSGABEZEILE aus. Das Unterprogramm AUSGABEZEILE verwendet ein
weiters Unterprogramm AUSGABEZEICHEN. Somit entsteht eine Aufrufhierarchie, die man grafisch
darstellen kann.
Haupprog
BERECHN
POTENZ
TUE
WURZE
AUSGABE
ZEILE
AUSGABE
ZEICHEN
Programmieren 1
108
2
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 27 v o n
Einführung in die Sprache C
Geschichte von C
•
•
•
1965
1969
1970
•
•
1971
1973
•
1977
– >>
•
1989
Prof. Dr. S. Keller
MULTICS
UNIX
Sprache B
1. Multiuser/ Multitasking Betriebssystem
Ken Thompson, 1. Version auf PDP-7, Assembler
Ken Thompson, 2. Version UNIX auf PDP-7,
Grund Portierbarkeit
Sprache C
Dennis M. Rithie ( AT&T),
UNIX
Dennis Ritchie, Ken Thompson, 3. Version UNIX
auf PDP-11 ( 1000 Zeilen Assembler, Rest C )
Veröffentlichung Buch Rithie, Brian Kernighan:
" The C Programming Language "
verschiedene UNIX Derivate und Portierungen
ANSI-Standard
( American National Standard Institute )
FH Ravensburg-Weingarten/Technische Informatik
Programmieren 3
C Compiler
Prof. Dr. - Ing. S.Keller
Quelltext
mit
Steueranweisungen
für den
Präprozessor
FH Ravensburg-Weingarten
Präprozessor
einzufügende
Textdateien
8
Übersetzbarer
C-Quelltext
8
echnische nformatik
eigentlicher
Übersetzer
22.09.1997
Programmobjekt
Der Präprozessor ist ein reines
Textverarbeitungsprogramm,
kein Übersetzer
Funktionalität des Präprozessors:
8 Einfügen zusätzlicher Textdateien
8 Definition von Makros
8 Bedingte Übersetzung
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 28 v o n 108
PRÄPROZESSORANWEISUNGEN
Diese Anweisungen beginnen mit einem
eigentlichen Übersetzung :
# und steuern die Aktionen des Präprozessors vor der
Syntax:
# <Anweisung>
Die beiden häufigsten Anweisungen, die in einem C-Programm vorkommen sind
8 die include-Anweisung
Sie bewirkt, daß an der aktuellen Position der Inhalt einer Textdatei einkopiert wird.
Beispiele
#include <stdio.h>
#include “meintext.h“
/* Einfügen einer Textdatei mit dem Namen
stdio.h. Diese Datei liegt in einem
Systemverzeichnis */
/* Einfügen der Textdatei meintext.h.
Diese Datei liegt im aktuellen
Arbeitsverzeichnis des Programmierers
*/
8 die define -Anweisung
Diese Anweisung dient der Benennung von Textstücken, die häufig verwendet werden
( Definition eines Textmakros ). Jedes Auftauchen des Makronamen ( Name des
Textstückes ) bewirkt, daß an Stelle des Namen das durch den Makronamen benannte
Textstück eingefügt wird ( Makroexpansion )
Beispiele
#define N
a = a + N ;
16
/* Festlegung, dass jedes Auftauchen von
N im Quelltext, durch 16 zu ersetzen
ist */
/* N wird ersetzt durch 16 */
Die Define-Anweisung wird häufig benutzt um Konstanten im Programm zu benennen. Die
Konstanten werden in einer Headerdatei definiert und immer bei Gebrauch über eine includeAnweisung eingebunden. Bei Änderung des Wertes so definierter Konstanten muß der Wert
nur an einer Stelle ( in der Headerdatei ) geändert werden. Das C-Programm selbst bleibt
unverändert.
Programmieren 1
2.1
Prof. Dr.-Ing. Silvia Keller
AUFBAU
UND
Studiengang Angewandte Informatik
STRUKTUR
EINES
A u s g a b e : 24.09.00
Seite 29 v o n 108
C-PROGRAMMS
Ein C-Programm besteht aus einem oder mehreren Unterprogrammen. Jedes Unterprogramm in C ist
eine Funktion. Prozeduren kennt die Programmiersprache C nicht.
Genau eine dieser Funktionen muß den Namen main haben. Diese Funktion main enthält das eigentliche
Hauptprogramm. Jede weitere Funktion ist ein Unterprogramm des Hauptprogramms.
Die Gesamtheit aller in einer Datei vorhandenen Funktionen bilden ein Modul.
Programm
Modul
Das einfachste C Programm besteht aus genau einem Modul. In diesem
Modul existiert genau eine Funktion mit den Namen main.
main()
Beispiel für ein einfaches C-Programm
/*********************************************************************/
/* Modul Erstprog.c enthält als einzigste Funktion die Funktion main */
/*********************************************************************/
#include <stdio.h>
/* stdio wird benötigt, da die ANSI-Funktion printf zur Ausgabe auf dem
Bildschirm verwendet wird */
/* Definition des Hauptprogramms */
int main(void)
{ char c;
/* Definition einer Variablen vom Typ char ( Zeichen )*/
/* Ausgabe eines Textes auf dem Bildschirm */
printf(" \n------ ASCII-Tabelle --------------------------------------\n");
/* Zaehlschleife von Startwert char(0) bis Endwert char(z) */
/* Die Schleifenanweisung gibt ein Zeichen und eine ganze Zahl am
Bildschirm aus */
for ( c='0'; c<='z'; c=c+1)
printf("%c -> %i \t",c,c);
/* Ausgabe eines Textes auf dem Bildschirm */
printf(" \n-----------------------------------------------------------\n");
/* Beenden des Programms mit dem Rueckgabewert OK an das Betriebssystem */
return 0;
}
Das Hauptprogramm ist eine Funktion ohne Parameter, die eine ganze Zahl berechnet.
Das eine Funktion keine Parameter besitzt, wird durch das Schlüsselwort void angezeigt.
Will man in einem Programm Bildschirmausgaben erzeugen, so muß man vordefinierte Funktionen, im
obigen Beispiel die Funktion printf verwenden. Die Funktion printf ist ein vorübersetztes
Programmobjekt, das in Maschinensprache vorliegt, und zusammen mit ähnlichen vorübersetzten
Funktionen in einer Funktionsbibliothek liegt Zu jeder Bibliothek gibt es eine Textdatei mit gleichem
Namen, die man zusätzlich zu dem vorübersetzten Programmobjekt benötigt. Diese Texdatei wird als
Headerdatei bezeichnet, da sie am Modulanfang ( Kopf des Moduls ) mit der #include-Anweisung
einkopiert werden muß. Die Funktion printf liegt in der Bibliothek stdio, also muß als erste Anweisung im
Modul, vor der Funktion main, die Headerdatei stdio.h einkopiert werden.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 30 v o n 108
Die Anweisungen eines Programms bzw. der Funktion main stehen zwischen { und }.
{ kennzeichnet den Beginn der Programmanweisungen,
} kennzeichnet das Ende der Programmanweisungen.
Jedes Programm benötigt Variablen zur Speicherung von Datenwerten. Eine Variable kann in
Programmanweisungen erst dann verwendet werden, wenn Name und Typ bekannt sind. Die Variablen
müssen daher immer am Anfang einer Funktion definiert werden, damit man sie in Anweisungen
verwenden kann.
Eine Funktion wird durch die Anweisung return beendet. Der Wert hinter der return-Anweisung ist der
Ergebniswert der Funktion, der an das aufrufende Programm übergeben wird. Im Falle der Funktion main
ist das aufrufende Programm das Betriebssystem des Rechners. Das Ergebnis der Funktion main ist 0,
falls das Programm korrekt abgearbeitet wurde. Im Fehlerfall kann die Funktion main eine Fehlernummer
übergeben.
Im obigen Beispiel wird jedoch immer der Wert 0 als Ergebnis geliefert.
Programm
Modul
main()
berechne()
tue()
potenz()
wurzel()
AusgabeZeile()
AusgabeZeichen()
Wird ein Programm größer und komplexer, so zerlegt man das
Programm in kleinere Untereinheiten (Unterprogramme). Das C
Programm besteht damit aus der Funktion main, die das
Hauptprogramm enthält und einer Menge von weiteren Funktionen,
die als Unterprogramme vom Hauptprogramm verwendet werden.
Das Beispiel aus Kapitel 1.4.4 kann z.B. wie im Bild nebenan
realisiert sein. Das Programm besteht aus einem Modul, in dem
neben der Funktion main sechs weitere Funktionen vorkommen. Die
Aufrufhierarchie zu dem Programm ist in Kapitel 1.4.4 beschrieben
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 31 v o n 108
Modulares Programm
Im obigen Beispiel kann man einige Funktionen zu einer Gruppe zusammenfassen.
Programm
Modul 1
Modul 2
berechne()
main()
tue()
Modul 3
potenz()
wurzel()
AusgabeZeile()
AusgabeZeichen()
Die Funktionen potenz, wurzel,
AusgabeZeile und AusgabeZeichen sind
z.B. Programmteile, die so
allgemeingültig sind, daß sie nicht nur in
diesem Programm, sondern auch in
anderen Programmen Verwendung
finden können, wenn man sie als
eigenständige Programmobjekte in
einem eigenen Modul definiert.
Man kann die sieben Funktionen aus
obigem Programm also auf mehrere
Module verteilen. Jedes Modul wird
unabhängig von den anderen Modulen
übersetzt. Jede Funktion des Moduls
wird damit zu einem eigenständigen
Programmobjekt, das auch in anderen
Programmen benutzt werden kann.
Durch diese Modularisierung entstehen
Programmbausteine, die alleine für sich
kein lauffähiges Programm ergeben.
Durch den Binder können die Programmbausteine zu einem lauffähigen Programm zusammengebaut
werden. Die vier Funktionen in Modul 3 können so auch in anderen Programmen verwendet werden.
Ein modulares Programm hat den Vorteil,
8 es ist gut strukturiert,
8 einfach zu warten und
8 enthält wiederverwendbare Programmbausteine.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
2.1.1
Funktionen in C
A u s g a b e : 24.09.00
Seite 32 v o n 108
Funktionen in C
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
22.10.1997
Ankündigung der Verwendung
einer noch nicht
definierten Funktion
Definition
Prototyp
Verwendung der Funktion
ohne vorherige Defintion
im Modul
Verwendung einer Funktion
nach der Definition im Modul
Funktionsaufruf
2.1.1.1
DEFINITION EINER F UNKTIONEN
Eine Funktion in C besteht aus:
8 dem Funktionskopf
Dieser besteht aus dem Funktionsnamen, den formalen Parametern und dem Ergebnistyp
8 dem Funktionsrumpf
Der Funktionsrumpf enthält die Anweisungen der Funktion.
Jede Funktion muß über eine Anweisung return einen Ergebniswert
Programm übergeben.
an das aufrufende
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 33 v o n 108
Vereinfachtes Syntaxdiagramm - Funktionsdefinition
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
type specifier
identifier
echnische nformatik
(
)
22.10.1997
compound
statement
parameter
type list
parameter
type list
type specifier
identifier
,
compound
statement
{
}
declaration
statement
Beispiele:
Es soll eine Funktion zum addieren zweier ganzen Zahlen programmiert werden. Der Name
der Funktion soll add sein. Die Funktion benötigt zwei formale Parameter a und b. Beide sind
ganze Zahlen. Das Ergebnis der Funktion ist eine ganze Zahl.
int add(int a, int b)
{ int erg;
erg = a+b;
return erg;
/* Funktionskopf */
/* der Rumpf wird durch die {} zu einem Block
zusammengefasst */
/* Addition beider Parameter */
/* Beenden der Funktion und Übergabe des Ergebnisses
an das Hauptprogramm */
}
Es soll eine Funktion programmiert werden, die den Sinus einer reellen Zahl berechnet. Der
Name der Funktion soll sin sein. Die Funktion benötigt einen Parameter x. x ist eine reelle
Zahl. Das Ergebnis der Funktion ist eine reelle Zahl.
double sin( double x )
{
double y;
/* Variable zur Speicherung des Ergebniswertes */
/* --------------------------------------------------------------- */
/* Hier ist ein Algorithmus zur Berechnung des Sinus implementiert */
/* --------------------------------------------------------------- */
return y;
}
/* beenden der Funktion und Übergabe des Ergebnisses */
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
2.1.1.2
AUFRUFEN VON F UNKTIONEN
A u s g a b e : 24.09.00
Seite 34 v o n 108
Im vorliegenden Beispiel verwendet das Hauptprogramm die beiden oben definierten Funktionen add
und sin.
Beim Aufrufen einer Funktion muß für jeden formalen Parameter ein aktueller Wert angegeben werden.
Das Ergebnis wird einer Variablen zugewiesen. Aktueller Parameter der Funktion sin ist 90 Grad.
Aktueller Parameter der Funktion add ist die Zahl 15 und das Ergebnis der Funktion add. Der aktuelle
Parameter einer Funktion kann also wieder über eine Funktion berechnet werden. Ein Aufruf der gleichen
Funktion als Parameter ist möglich.
int main()
{ int erg;
double y;
erg=add(15,add( 5, 4 ));
y=sin(90.0);
return 0;
}
/* Hauptprogramm */
/* Definition der Variablen erg als ganze Zahl
*/
/* Definition der Variablen y als reelle Zahl */
/* Aufruf d. Funktion add mit aktuellen
Parametern */
/* Aufruf der Funktion sin */
/* Beenden der Funktion. Rückgabewert ist o.k.
*/
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
2.1.1.3
PROTOTYPEN VON FUNKTIONEN
A u s g a b e : 24.09.00
Seite 35 v o n 108
Jeder Bezeichner kann in einem Programm nur dann verwendet werden, wenn der Bezeichner vor
seiner ersten Verwendung dem Compiler bekannt gemacht wurde ( Definition von Programmobjekten ).
Programm
Die Funktion main im obigen Beispiel kann daher die beiden
Funktionen add und sin nur dann verwenden, wenn im Modul alle
drei Funktionen vorhanden sind und die beiden Funktionen add und
sin vor der Funktion main definiert wurden.
Modul HAUPT
add()
sin()
main()
Beispiel
/***************************************************************************/
/* Modul HAUPT.c
*/
/* Enthaelt die Funktionen add, sin, main
*/
/* add und sin muessen vor main definiert sein
*/
/***************************************************************************/
int add(int a, int b)
{ int erg;
erg = a+b;
return erg;
Ergebnisses an
}
/* Funktionskopf */
/* der Rumpf wird durch die {} zu einem Block
zusammengefasst */
/* Addition beider Parameter */
/* Beenden der Funktion und Übergabe des
das Hauptprogramm */
double sin( double x )
{
double y;
/* Variable zur Speicherung des Ergebniswertes */
/* --------------------------------------------------------------- */
/* Hier ist ein Algorithmus zur Berechnung des Sinus implementiert */
/* --------------------------------------------------------------- */
return y;
/* beenden der Funktion und Übergabe des Ergebnisses */
}
int main()
{ int erg;
double y;
/* Hauptprogramm */
/* Definition der Variablen erg als ganze Zahl */
/* Definition der Variablen y als reelle Zahl */
erg=add(15,add( 5, 4 ));
y=sin(90);
return 0;
}
/* Aufruf d. Funktion add mit aktuellen
Parametern */
/* Aufruf der Funktion sin */
/* Beenden der Funktion. Rückgabewert ist o.k. */
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 36 v o n 108
Programm
Erstellt man ein modulares Programm
kann diese Forderung nicht mehr
Modul HAUPT
Modul MATH
eingehalten werden.
add()
Wird z.B. main in einem Modul HAUPT
und die Funktionen add und sin in einem
main()
Modul MATH erstellt, so sind bei der
sin ()
Übersetzung des Moduls HAUPT die
beiden Funktionsnamen noch nicht
definiert. Damit das Modul main
trotzdem übersetzt werden kann, muß
der Programmierer vor der Funktion main
die Verwendung beider externer Funktionen durch den Prototyp ankündigen. Der Prototyp macht dem
Compiler den Funktionsnamen, Anzahl und Typ der Parameter sowie den Ergebnistyp bekannt.
Beispiel
/******************************************************************/
/* Modul HAUPT enthält nur die Funktion main
*/
/******************************************************************/
extern int add( int, int );
/* Prototyp von add */
extern double sin ( double );
/* Prototyp von sin */
/* Jetzt kann main die beiden extern definierten Funktionen verwenden */
int main()
{ int erg;
double y;
/* Hauptprogramm */
/* Definition der Variablen erg als ganze Zahl */
/* Definition der Variablen y als reelle Zahl */
erg=add(15,add( 5, 4 ));
y=sin(90);
return 0;
/* Aufruf d. Funktion add mit aktuellen
Parametern */
/* Aufruf der Funktion sin */
/* Beenden der Funktion. Rückgabewert ist o.k. */
}
Die Prototypen werden auch dann benötigt, wenn alle drei Funktionen in einem Modul liegen, die
Funktion main jedoch vor den beiden Funktionen add und sin definiert wird. In diesem Fall entfällt jedoch
das Schlüsselwort extern bei den Prototypen, da die Funktionen intern definiert werden.
Programm
Modul HAUPT
main()
add()
sin()
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 37 v o n 108
Beispiel
/**********************************************************************/
/* Modul HAUPT enthält alle drei Funktionen
*/
/* Main wird jedoch vor add und sin definiert
*/
/**********************************************************************/
int add( int, int );
/* Prototyp von add */
double sin ( double );
/* Prototyp von sin */
/* Jetzt kann main die beiden Funktionen verwenden */
int main()
{ int erg;
double y;
/* Hauptprogramm */
/* Definition der Variablen erg als ganze Zahl */
/* Definition der Variablen y als reelle Zahl */
erg=add(15,add( 5, 4 ));
/* Aufruf d. Funktion add mit aktuellen Parametern
*/
/* Aufruf der Funktion sin */
/* Beenden der Funktion. Rückgabewert ist o.k. */
y=sin(90);
return 0;
}
int add(int a, int b)
{ int erg;
erg = a+b;
return erg;
/* Funktionskopf */
/* der Rumpf wird durch die {} zu einem Block
zusammengefasst */
/* Addition beider Parameter */
/* Beenden der Funktion und Übergabe des Ergebnisses
an das Hauptprogramm */
}
double sin( double x )
{
double y;
/* Variable zur Speicherung des Ergebniswertes */
/* --------------------------------------------------------------- */
/* Hier ist ein Algorithmus zur Berechnung des Sinus implementiert */
/* --------------------------------------------------------------- */
return y;
}
/* beenden der Funktion und Übergabe des Ergebnisses */
Programmieren 1
2.2
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
E LEMENTARE D A T E N T Y P E N, VA R I A B L E N
UND
Seite 38 v o n 108
OPERATOREN
Datentypen in C
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
Programmieren 3
10/28/97
Datentyp
Skalare Typen
arithmetische
Typen
Ganze Zahlen
Reelle Zahlen
Zeiger
Strukturierte Typen
Aufzählungstypen
array
void
Strukturen
Zeichen
Die Wertebereiche von skalaren Typen sind geordnet.
Der Datentyp void ist ein spezieller Datentyp, der anzeigt, daß keine Typangabe möglich ist. Beispiele für
die Verwendung von void werden im Verlauf der Vorlesung eingeführt.
Ganze Zahlen
Der Wertebereich einer ganzen Zahl ist abhängig von der Anzahl von Binärziffern, die der Rechner zur
Darstellung der Zahl verwendet.
Bei vorzeichenbehafteten Zahlen, die n Bit dargestellt liegt der Wertebereich der Zahl bei:
n-1
-(2
) <= Zahl <= ( 2
n-1
) -1
Beispiel:
Eine ganze Zahl wird auf dem PC in 16 Bit dargestellt.
15
Der größte positive Wert ist in diesem Fall
2 – 1 = 32767,
der kleinste negative Wert beträgt
-32768.
Addiert man auf den größten positiven Wert eine Konstante z.B. den Wert 1, so erhält man einen
Zahlenüberlauf. Der dargestellte Wert ist fehlerhaft. Das gleiche passiert, wenn man von der kleinsten
negativen Zahl eine Konstante subtrahiert.
Das folgende C-Programm demonstriert diesen Effekt.
/**************************************************************************/
/* Programm zur Demonstration eines Zahlenüberlaufes
*/
/**************************************************************************/
#include <stdio.h>
/******
/* Enthält den Prototyp von printf() */
Festlegung von Namen für Konstanten
******************/
#define MAXINT 32767
/* Groesste positive ganze Zahl
*/
#define MININT -32768
/* Kleinste negative Zahl
*/
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
/**************
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 39 v o n 108
Hauptptogramm **************************************/
int main(void)
{
int zahl1=MININT, zahl2=MAXINT;
printf(" Zahl2 enthält die größte positive Zahl: %i\n",zahl2);
/* Addiert man auf zahl2 eine positive Konstante so erhält man einen
Zahlenüberlauf */
zahl2 = zahl2 + 1;
printf(" Zahl2 + 1 ergibt den Wert: %i\n\n",zahl2);
printf(" Zahl1 enthält die kleinste negative Zahl: %i\n",zahl1);
/* Subtrahiert man auf zahl1 eine positive Konstante so erhält man einen
Zahlenüberlauf */
zahl1 = zahl1 - 1;
printf(" Zahl1-1 ergibt den Wert: %i\n",zahl1);
return 0;
}
Im Falle von positiven ganzen Zahlen, die mit n Bit dargestellt werden, ist der Wertebereich:
0 <= Zahl <= 2
n
-1
Beispiel:
Eine ganze Zahl wird auf dem PC in 16 Bit dargestellt.
16
Der größte positive Wert ist in diesem Fall
2 - 1 = 65535.
Addiert man auf diesen Wert die Konstante 1 so erhält man einen Zahlenüberlauf. Der dargestellte Wert
ist fehlerhaft.
/**************************************************************************/
/* Programm zur Demonstration eines Zahlenüberlaufes
*/
/**************************************************************************/
#include <stdio.h>
#define MAXINT 65535u
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 40 v o n 108
int main(void)
{
unsigned int zahl1=MAXINT;
printf(" Zahl1 enthält die groesste positive Zahl: %u\n",zahl1);
/* Addiert man auf zahl1 eine positive Konstante so erhält man einen
Zahlenüberlauf */
zahl1 = zahl1 + 1;
printf(" Zahl1+1 ergibt den Wert: %u\n",zahl1);
return 0;
}
In C gibt es mehrere Standardtypen:
8 int, long int , short int
sind vorzeichenbehaftete ganze Zahlen
8 Mit dem Zusatz unsigned
definiert man positive ganze Zahlen also:
unsigned int, unsigned long int, unsigned short int
INT KONSTANTEN
Konstante Werte in einem Programm sind automatisch vom Typ int, falls keine weitere Angabe gemacht
wird.
Ein Wert kann als Dezimalzahl, als Oktalzahl ( Basis 8 ) oder als Hexadezimalzahl ( Basis 16 )
angegeben werden.
Beispiele für Konstanten:
10
4711
4711u
/* Dezimalkonstante vom Typ int */
/* Dezimalkonstante vom Typ int */
/* Dezimalkonstante vom Typ unsigned int */
-1
-1L
/* Dezimalkonstante vom Typ int */
/* Dezimalkonstante vom Typ long int */
0
0xff
/* Oktalkonstante vom Typ int */
/* Hexadezimalkonstante vom Typ int */
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 41 v o n 108
integer-Konstanten
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
Programmieren 3
long
L
Dezimalkonstante
Oktalkonstante
Hexadezimalkonstante
//
unsigned
l
U
u
U
L
u
l
Dezimalkonstante
Nichtnull-Ziffer
Ziffer
1 ... 9
0 ... 9
Oktalkonstante
Hexadezimalkonstante
0
0x
Ziffer
Ziffer
0X
Reelle Zahlen
Auch bei reellen Zahlen kennt C mehrere Typen, die sich in der Genauigkeit der Zahlendarstellung
unterscheiden:
8 float,
8 double
8 long double
Bei der Verwendung reeller Zahlen sollte man beachten, daß eine reelle Zahl in einem Rechner als
Dualzahl i.a. nicht exakt dargestellt werden kann. Ein Vergleich auf Gleichheit zweier reellen Zahlen ist
daher sehr gefährlich. Auch ein Vergleich auf den exakten Wert 0 sollte vermieden werden.
DOUBLE KONSTANTEN
Konstante Werte in einem Programm sind automatisch vom Typ double, falls keine weitere Angabe
gemacht wird.
Beispiele für Konstanten:
10.0
4.711f
1.0e3
-1.5643L
-1e3
.0
1e-1
/*
/*
/*
/*
/*
/*
/*
Festpunkt-Konstante vom Typ Double */
Festpunkt-Konstante vom Typ float */
Gleitkomma-Konstante vom Typ double */
Festpunktkonstante vom Typ long double */
Gleitkommakonstante vom Typ double */
Festpunktkonstante vom Typ double */
Gleitkommakonstante vom Typ double */
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 42 v o n 108
double-Konstanten
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
Dezimalkonstante
Programmieren 3
02.11.1997
Exponent
L
Festpunktkonstante
l
F
f
Festpunktkonstante
Ziffer
.
Ziffer
.
Exponent
Ziffer
E
+
e
-
Zeichen
Mit char definiert man ein Zeichen in 7 Bit ASCII-Code dargestellt ( 128 unterschiedliche Zeichen ).
Tabelle des 7-Bit ASCII-Codes
Binär
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
Dezimal
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
000
0
NUL
SOH
STX
ETX
EOT
ENQ
ACK
BEL
BS
HT
LF
VT
FF
CR
SO
SI
001
1
DLE
DC1
XON
DC3
XOF
NAK
SYN
ETB
CAN
EM
SUB
ESC
FS
GS
RS
US
010
2
011
3
Leerzeich. 0
!
1
"
2
#
3
$
4
%
5
&
6
'
7
(
8
)
9
*
:
+
;
,
<
=
.
>
/
?
100
4
@
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
101
5
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
^
_
110
6
`
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
111
7
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
DEL
Im 8-Bit ASCII-Zeichensatz können neben den normalen alfanumerischen Zeichen auch nationale
Zeichen wie ä, ö, ü ,ß codiert werden.
Ein 8-Bit Zeichen wird mit dem Datentyp unsigned char definiert.
In C gibt es die Besonderheit, das der Datentyp char ( Zeichen ) doppel belegt ist. Der Typ
char definiert neben einem Zeichen gleichzeitig auch eine ganze Zahl, die in 8 Bit gespeichert
ist. Dieser Datentyp wird z.B. für Variablen genutzt, die als Laufindex für ein array verwendet
wird, um Speicherplatz zu sparen. Unsigned char definiert damit eine positive ganze Zahl mit 8
Bit Speicherlänge. Welche Interpretation bei der Verwendung einer Variablen vom Typ char
gewählt wird, bestimmt der Programmierer durch sein Programm.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 43 v o n 108
Beispiel:
Im Programmbeispiel „ Einfaches C Programm „ auf Seite 27 wird die Variable c bei der
Bildschirmausgabe einmal als Zeichen (%c) und ein weiteres mal als ganze Zahl (%i) interpretiert.
ZEICHEN K ONSTANTEN
Konstante Werte werden in ‘ ‘ angegeben. Steuerzeichen und bestimmte Sonderzeichen werden durch \
gefolgt von einem Kennzeichen angegeben.
Beispiele für Konstanten:
‘A’
‘1’
‘\n’
‘\t’
‘\a’
‘\f’
‘\r’
‘\\’
‘\’’
‘\“’
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
der Buchstabe A */
Die Ziffer 1 als zeichen, nicht die ganze Zahl 1 */
Das Steuerzeichen Newline */
Das Steuerzeichen Tabulator */
Signalton */
Das Steuerzeichen Form feed ( Neue Seite ) */
Das Steuerzeichen carriag return ( Wagenrücklauf ) */
Das Zeichen \ */
Das Zeichen ‘ */
Das Zeichen “ */
Aufzählungstypen
Aufzählungstypen erhalten einen durch den Programmierer selbstvereinbarten Typnamen und werden
durch das Schlüsselwort enum definiert.
Beispiel:
enum Tag = { Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag, Sonntag } ;
Tag ist hier der Typname. Die Bezeichner in {} sind die Werte des selbstvereinbarten Typs.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 44 v o n 108
Speichergröße und Wertebereich elementarer Datentypen
Die folgende Tabelle gibt die Speichergröße und Wertebereiche dieser Datentypen in C an.
Die Speichergröße von int, unsigned int, long double und enum sind systemabhängig und daher nicht
ohne weiteres fehlerfrei portierbar.
Datentyp
Speichergröße
Wertebereich
(signed) char
8 Bit
unsigned char
8 Bit
-128 ... 127
7-Bit ASCII Darstellung
0 ... 255
8-Bit ASCII Darstellung
int
unsigned short
unsigned long
float
Systemabhängig
32 Bit ( Windows NT, SUN )
16 Bit
32 Bit
Systemabhängig
32 Bit ( Windows NT, Sun )
16 Bit
32 Bit
32 Bit
double
64 Bit
long double
Systemabhängig
64 Bit
Systemabhängig
32 Bit
short
long
unsigned int
enum
2.2.1
- 2 147 438 648 ... 2 147 438 647
-32 768 ... 32 767
-2 147 483 648 ... 2 147 438 647
0 ... 4 294 967 259
0 ... 65 535
0 ... 4 294 967 295
3.4e-38 ... 3.4e+38
( 7 Stellen Genauigkeit)
( 23-Bit Mantisse, 8 Bit
Exponent)
1.7e-308 ... 1.7 e+308
(15 Stellen Genauigkeit)
( 52 Bit Mantisse, 11 Bit
Exponent)
dto
0 ... 4 294 967 295
Definition von Variablen
Bei der Definition von Variablen muß der Datentyp und ein Bezeichner angegeben werden.
,
Datentyp
Bezeichner
Initalisierung
Der Compiler reserviert für die Variable Speicherplatz, der Wert der Variablen wird jedoch nicht
automatisch mit dem Wert 0 vorbelegt. Die Variable hat den Wert, der zufällig im Speicher steht. Man
kann bei der Definition einer Variablen einen Wert vorgeben. In diesem Fall wird der Compiler die
Variable mit dem angegebenen Wert vorbelegen ( Initialisierung ):
Regeln für Bezeichner ( auch Funktionsnamen )
8 Erlaubt sind Buchstaben, Ziffern und "_". Erstes Zeichen des Namens darf keine Ziffer sein.
8 Ein Name darf maximal 32 Zeichen lang sein.
8 Groß-/Kleinschreibung wird unterschieden. Bsp: var , VAR und Var sind drei
unterschiedliche Bezeichner.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 45 v o n 108
Beispiele
int zahl;
/* Definition einer ganzen Zahl ( integer ). Der Wert
von zahl ist undefiniert */
float fwert;
/* Definition einer reellen Zahl (float). Der Wert von
fwert ist undefiniert */
int zahl = 0;
/* Definition einer ganzen Zahl ( integer ). zahl wird
mit dem Wert 0 vorbelegt */
double dwert=1.41;
/* Definition einer reellen Zahl (double). dwert wird
mit
der double-Konstanten 1.41 vorbelegt */
int liste[32];
/* Definition einer Liste mit 32 ganzen Zahlen */
int liste[10]={2,5,7,3,10,11,23,26,13,47}; /* Definition einer Liste mit 10
ganzen Zahlen. Die Liste wird mit
Werten vorbelegt */
int liste[32]={1,2,3};
/* Definition einer Liste mit 32 Zahlen. Die Liste wird
mit den Werten 1,2,3 vorbelegt. Alle noch fehlenden
Komponenten erhalten den Wert 0 */
int liste[]={1,2,3,4,5,6,7,8,9};
2.2.2
/* Definition einer Liste mit 9 Werten. Die
Anzahl der Werte kann sich der Compiler
aus der Initialisierung selbst berechnen */
Gültigkeitsbereiche von Variablen
Gültigkeitsbereiche von Variablen
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
Modul 1
Modul 2
m
l Lokal
Funktion 1
10/23/97
Modulglobal
l Lokal
Funktion 2
g
l
Funktion 3
Global
l
Funktion 4
g ist eine globale Variable und kann daher von allen Funktionen
verwendet werden
8 m ist eine modulglobale Variable und kann nur von Funktionen
innerhalb des Modul 1 verwendet werden
8 l sind lokale Variablen, die nur innerhalb einer Funktion gültig
sind.
8
In C unterscheidet man 3 Gültigkeitsbereiche:
8 Lokale Variablen
Der Name einer Variablen ist nur lokal in dem Block gültig, in dem die Variable
definiert wurde.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 46 v o n 108
Ein Block ist eine Folge von Anweisungen, die in { } steht.
Blöcke können geschachtelt werden. Enthält ein Block weitere Unterblöcke, so ist die
Variable auch in den darunterliegenden Blöcken sichtbar.
Wird eine Variable am Anfang einer Funktion definiert, so ist diese Variable gültig in
allen Blöcken der gesamten Funktion. Wird eine Variable am Anfang eines Blockes
innerhalb der Funktion definiert, so ist die Variable nur innerhalb des Blockes gültig,
außerhalb des Blocks ist die Variable nicht sichtbar.
Wird eine Variable in mehreren geschachtelten Blöcken definiert, so überdeckt die
Variable eines inneren Block die Variable mit gleichem Namen im übergeordneten
Block.
Beispiel:
int main(void)
{
int i,zahl=-1;
/* zahl und i sind lokal gültig in
allen Blöcken der Funktion main */
while ( zahl <= 0 )
{
zahl=zahl+10;
printf("zahl in while %i\n",zahl);
for ( i=1; i <= 10; i=i+1)
{ int zahl=i+1;
/* zahl wird lokal für
diesen Block definiert
und überdeckt zahl aus
main*/
printf("zahl in for %i\n",zahl);
} /* endfor */
printf("zahl in while %i\n",zahl);
} /* endwhile */
return 0;
} /* Ende Programm */
8 Globale Variablen
Eine Variable, die am Modulanfang außerhalb einer Funktion definiert wird ist global
gültig. Eine globale Variable ist in jeder internen und externen Funktion, d.h. auch
außerhalb des Modul, sichtbar.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 47 v o n 108
Gültigkeitbereiche von Variablen
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Modul HAUPT
Technische Informatik
8
int GlobaleZahl;
int main(void)
int LokaleZahl;
int a,b;
8
8
int a,c;
Block B
Block A
8
29.10.1997
GlobaleZahl ist global gültig in allen
Funktionen und allen Blöcken innerhalb
und außerhalb des Moduls
LokaleZahl ist gültig innerhalb der
Funktion und in den Blöcken A und B
a in Block a ist gültig in Block a
b in Block A ist gültig in Block A und
BlockB
a in Block B ist gültig in Block B
c ist gültig in Block B
Beispiel:
/*************************************************************/
/*
Programm:
Globale und Lokale Variablen
*/
/*************************************************************/
int GlobaleZahl = 100;
/* Die Variable GlobaleZahl ist
sichtbar in allen internen und
allen externen
Funktionen */
int main(void)
{ int LokaleZahl;
/* Lokale Variable ist nur sichtbar
innerhalb main*/
LokaleZahl=GlobaleZahl;
GlobaleZahl=GlobaleZahl+1; /* Die Globale Variable wird
veraendert */
return 0;
}
8 Modulglobale Variablen
Eine globale Variable kann durch Angabe eines zusätzlichen Schlüsselwortes static
im Gültigkeitsbereich eingeschränkt werden. Die Variable ist zwar innerhalb des
Moduls global, kann also von jeder Funktion verwendet werden. Außerhalb des
Moduls ist die Variable jedoch nicht mehr sichtbar.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
2.2.3
Die wichtigsten Operatoren
A u s g a b e : 24.09.00
Seite 48 v o n 108
An dieser Stelle werden nur elementaren Grundoperationen von C eingeführt. Neben diesen Operatoren
kennt C weitere Operatoren, die vor allem zur Programmoptimierung, zur Schreiberleichterung oder in
Verbindung mit Zeigern verwendet werden. Diese Operatoren werden später an geeigneter Stelle
eingeführt.
ARTIHMETISCHE O PERATOREN
Operator
*
/
Argumenttyp
int
float
(unsigned) int
Ergebnistyp
int
float
int
Bedeutung
Multiplikation
Ganzzahlige Division
Sind beide Operanden ganze Zahlen ist das
Ergebnis immer eine ganze Zahl.
Nachkommastellen werden abgeschnitten
Undefiniert bei neg. Zahlen.
Compiler kann aufrunden oder auch abrunden
float
float
%
(unsigned) int
int
+
int
float
int
float
int
float
int
float
-
Reelle Division
Ist mindestens ein Operand eine reelle Zahl
ist das Ergebnis ebensfalls reell.
Rest bei ganzzahliger Division ( Modulo )
Undefiniert auf negative Zahlen.
Compilerabhängig ist Ergebnis entweder positiv oder
negativ
Addition
Subtraktion
BEISPIELE:
Ausdruck
Ergebnis
5 / 2
2
5 / -2
x / 0
-2 oder -3 ; Bei negativen Operanden ist die Division nicht definiert.
Der kann Compiler kann auf- oder abrunden
undefiniert ; Division durch 0 ist nicht erlaubt
1 / 3
0
1.0 / 3.0
0.3333
5 % 2
1
-5 % 2
-1 oder 1 ; Modulo ist auf negative Operanden nicht definiert
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 49 v o n 108
VERGLEICHENDE OPERATOREN
Operator
<
Argumenttyp
int, float
<=
>
>=
==
int,
int,
int,
int,
!=
int, float
float
float
float
float
Ergebnistyp
Boolean
FALSCH : 0
WAHR:
1
Boolean
Boolean
Boolean
Boolean
Boolean
Bedeutung
Vergleich auf Kleiner
Vergleich auf Kleiner oder Gleich
Vergleich auf Größer
Vergleich auf Größer oder Gleich
Vergleich auf Gleichheit
Vorsicht bei reellen Zahlen !
Vergleich auf Ungleichheit
Ein Datentyp Boolean gibt es in C nicht.
Logische Werte werden in C auf ganze Zahlen abgebildet.
Der logische Wert falsch entspricht dem Wert 0. Der logische Wert wahr ist ein Wert ungleich 0.
Wird als Ergebnis eines Vergleiches ein logischer Wert berechnet so wird der Ergebniswert wahr durch
die Zahl 1 repräsentiert.
BEISPIELE:
Ausdruck
Ergebnis
5 < 2
0 ( FALSCH )
5 > 2
1 ( WAHR )
5 == 2
0
5 != 2
1
1.5 <= 3.0
1
(1.0/3.0 + 1.0/3.0 + 1.0/3.0) == 1.0 0
1.0 / 3.0 ergibt 0.33333333......
Diese Zahl ist nicht exakt im Rechner darstellbar. Es
werden Stellen abgeschnitten. Die Addition dieser drei
unexakten Zahlen ergibt nicht den exakten Wert 1.0.
Daher Vorsicht bei Vergleichen mit reellen Zahlen
LOGISCHE OPERATOREN
Operator
!
&&
||
Argumenttyp
Boolean
Boolean
Boolean
Ergebnistyp
Boolean
Boolean
Boolean
Bedeutung
Verneinung
Logisches UND
Logisches ODER
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 50 v o n 108
BEISPIELE:
Ausdruck
Ergebnis
0 && 1
0
1 && 1
1
5 && 2
1
5 || 0
1
!5
0
0 || 1
1
(5 < 2) && (3 < 10)
0
BIT OPERATOREN
Operator
&
^
|
Argumenttyp
int
int
int
int
Ergebnistyp
int
int
int
int
Bedeutung
Komplement
bitweise UND
bitweise exklusiv oder ( antivalenz )
bitweise ODER
Bei den Bitoperatoren wird eine ganze Zahl als Bitmuster interpretiert. Die Operatoren verrechnen dann
die einzelnen Bitpositionen miteinander.
Beispiel:
unsigned short muster1, muster2;
muster1=16
muster2=15
erzeugt folgendes Bitmuster im Speicher: 0000 0000 0001 0000
erzeugt folgendes Bitmuster im Speicher: 0000 0000 0000 1111
Verrechnet man diese beiden Variablen mit dem Bitoperator ODER ( | ),
so entsteht folgendes Bitmuster: 0000 0000 0001 1111
Dieses entspricht dem Dezimalwert 31.
BEISPIELE:
Ausdruck
Ergebnis
16 & 32
0
15 & 7
7
16 | 32
48
15 | 7
15
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
2.2.4.
Ausdrücke
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 51 v o n 108
Ein Ausdruck dient der Berechnung eines Wertes. Ein Ausdruck hat damit immer ein Resultat mit einem
vorbestimmten Datentyp.
Ausdrücke
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Ausdruck
8
Technische Informatik
Resultat
05.11.1997
Datentyp
Ausdruck enthält Variabeln,
Konstanten, Operatoren und
Funktionsaufrufe
Ein Ausdruck setzt sich zusammen aus Variablen, Konstanten, Operatoren und Funktionsaufrufen.
Der häufigste Ausdruck ist ein Zuweisungsausdruck. In einer Ergebnisvariablen wird durch den
Zuweisungsoperator = der berechnete Wert eines Ausdruckes gespeichert. Der Zuweisungsausdruck ist selbst
wieder ein Ausdruck, der ein Resultat abliefert. In diesem Fall ist das Resultat der Wert, der in der
Ergebnisvariablen gespeichert wurde.
Zuweisungsausdruck
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
05.11.1997
Zuweisungsausdruck
Ergebnisvariable
=
Resultat
Ausdruck
8
8
Linke Seite und rechte Seite vom
Zuweisungsoperator
müssen vom gleichen Datentyp sein
Datentyp
Wert, der in Ergebnisvariabel
gespeichert wurde
der in Ausdruck
berechnete Wert wird
in Ergebnisvariable
gespeichert
BEISPIELE FÜR EINFACHE A USDRÜCKE
int zahl1=1,zahl2=10,zahl3=5;
char symbol=‘s’, zeichen=‘a’
double mitte=0.0,laenge=10.5;
Ausdruck
Resultat
zahl2 - zahl1
Typ des
Ergebniswertes
int
zahl2 / zahl3
int
2
laenge / 2.0
double
5.25
zahl3 = zahl2 % zahl3
symbol + 1
int
char
0
‘t’
9
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 52 v o n 108
Tauchen in einem Ausdruck mehrere Operanden auf, so werden die Operationen entsprechend der
Rangordnung der Operatoren ausgeführt.
Die Rangordnung kann durch Klammern verändert werden.
Im Ausdruck 1 + 2 * 5 hat der Operator * den höheren Rang also wird zuerst 2 * 5 berechnet und danach
auf den Ergebniswert 10 der Wert 1 addiert ( -> 11 )
Der Ausdruck 1 + 2 * 5 entspricht dem Ausdruck 1 + ( 2 * 5 ).
Man kann die Berechnung durch Klammern verändern (1 + 2 ) * 5 ergibt als Resultat den Wert 15.
Im folgenden ist die Rangordnung der eingeführten Operatoren angegeben:
*
!
%
<
+
<=
==
&
^
|
&&
||
=
>
!=
>=
----------------------------------------------------------------------------------------------------------->
Rangordnung
/
-
2.2.4.1
W ANDLUNG VON DATENTYPEN
Gegeben ist folgende Aufgabe:
Gegeben ist eine Liste mit 10 ganzen Zahlen. Sie sollen den Mittelwert dieser
Zahlenreihe berechnen.
Sie entwerfen dazu folgenden Algorithmus:
Algorithmus:
Daten:
Mittelwert
summe Summe aller 10 Zahlen
mittelwert
Mittelwert
index
Position einer Zahl in der Liste
N
Anzahl der Zahlen in der Liste
Aktionen:
N hat den Wert 10
bilde die Summe der N Zahlen durch addieren aller N Zahlen in der Liste in einer
Zählschleife
mittelwert = summe dividiert durch N.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 53 v o n 108
Programmablaufplan
summe:=0
Index:=1
index <= N
falsch
wahr
Zählschleife
summe:=summe + liste[index]
Index:=Index + 1
mittelwert:=summe / N
Den Algorithmus realisieren Sie durch folgendes C Programm.
#include <stdio.h>
#define N
10
main()
{
int index,summe=0,liste[N]={2,4,6,4,4,10,8,3,9,7};
float mittelwert=0.0;
for (index=0; index<N; index=index+1)
{ summe=summe+liste[index]; }
mittelwert = summe/N;
printf("der Mittelwert = %f\n",mittelwert);
return 0;
}
Als Ausgabe erhalten Sie den Wert 5. 000000. Dieser Wert ist fehlerhaft. Korrekt wäre der Wert 5.700000
Der Fehler liegt daran, daß bei der Berechnung des Mittelwertes eine ganzzahlige Division erfolgt.
Summe / N ergibt eine ganze Zahl.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Lösung:
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 54 v o n 108
Umwandlung des Datentyps int in den Datentyp float.
In C exisitiert ein Operator, Cast-Operator genannt, mit dem man eine Typwandlung erzwingen kann.
( type casting )
Cast-Operator :
( zu erzeugender Datentyp )
Der cast-Operator wirkt sich nur auf den aktuell folgenden Wert in einer Anweisung aus. Dar Datentyp
von Variablen und Konstanten wird also nur kurzfristig für die momentane Berechnung geändert.
Lösungsversuch #1:
Mit dem cast-Operator ändern Sie die Mittelwertberechnung wie folgt ab:
mittelwert = (float) ( summe/N );
Auch diese Lösung liefert den Wert 5.000 als Mittelwert.
Erklärung:
Es wird zuerst der Wert von ( summe / N ) berechnet. Ergebnis ist eine ganze Zahl. Diese ganze Zahl
wird in eine reelle Zahl gewandelt( 5 ---> 5.0000 ).
Lösungsversuch #2:
Sie ändern die Mittelwertberechnung wie folgt:
mittelwert = (float) summe / (float) N ;
Der int Wert summe und die int Konstante N werden vor der Division in reelle Zahlen vom Typ float
gewandelt. Erst danach wird die Division durchgeführt. Jetzt erfolgt eine reelle Division mit dem Ergebnis
5.7. Dieser float-Wert wird dann in mittelwert gespeichert.
Programmieren 1
2.3
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 55 v o n 108
KONTROLLSTRUKTUREN
2.3.1
Fallunterscheidungen
Einfache Alternative
if ( <Ausdruck> ) <Anweisung> ;
Wenn die Berechnung von <Ausdruck> den Wert wahr ( also ein Wert <> 0 ) ergibt, so führe die
folgende Anweisung aus.
Jede Anweisung kann auch durch einen Anweisungsblock ( compound statement ) ersetzt werden. Ein
Anweisungsblock ist eine Folge von Anweisungen, die eingeschlossen ist in { }.
Beispiel:
In einem Programm soll die Division durch 0 verhindert werden. Hat der Divisor den Wert 0, so soll der
Divisor auf den Wert 1 gesetzt werden. Die Division kann damit fehlerfrei ausgeführt werden.
if ( divisor == 0 ) divisor = 1;
ergebnis = zahl / divisor ;
/* diese Zeile wird auf jeden
Fall ausgeführt */
Da eine ganze Zahl in C auch als Boolean verwendet werden kann, sieht man in C Programmen auch
häufig folgende Programmzeilen.
if ( ! divisor ) divisor = 1;
/* Falls not divisor */
ergebnis = zahl / divisor ;
Hat der Divisor den Wert 0, so kann dieser Wert auch als logischer Wert FALSCH interpretiert werden.
Da die fallunterscheidung aber für den Fall WAHR den Divisor auf 1 setzen soll muß der Logische Wert
des Divisor negiert werden.
Häufige Fehlersituationen:
Fall 1:
if ( x == 1 ) ;
/*
{
/* Dieser Block wird in jedem Fall
ausgeführt da das if */
printf( " ..... ");
x = 0;
;
bedeuted leere Anweisung
*/
/* durch die leere Anweisung ; abgeschlossen
wurde
*/
}
Ein ; nach der Bedingung in der Fallunterscheidung bedeutet in C leere Anweisung also tue nichts. Die
Fallunterscheidung hat demnach keine Wirkung, da nichts passiert. Der darauf folgende Block wird auf
jeden Fall ausgeführt, da er nicht mehr zur Fallunterscheidung gehört.
Korrektur:
if ( x == 1 )
{
printf(...);
x=0;
}
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 56 v o n 108
Fall 2:
if ( x=1 ) printf(" Der Wert von x ist %i \n ",x);
x wird im Bedingungsausdruck nicht mit dem Vergleichsoperator == mit dem Wert 1 verglichen sondern
mit dem Zuweisungsoperator = auf den Wert 1 gesetzt. Der Ausdruck x=1 ergibt immer den Wert 1, also
wahr, so daß in jedem Fall eine Ausgabe erzeugt wird.
Korrektur:
if ( x == 1) printf(....);
Vollständige Alternative
if ( <Ausdruck> ) <Anweisung_wahr> ;
else
<Anweisung_falsch> ;
Wenn die Berechnung des Ausdrucks den Wert wahr ( <> 0 ) ergibt, so wird Anweisung_wahr
ausgeführt, sonst wird Anweisung_falsch ausgeführt.
Beispiel
Der Modulo-Operator angewendet auf negativen Zahlen ist in C nicht definiert. Sie wollen diese Situation
in Ihrem Programm vermeiden. Die Lösung sieht dann so aus:
if ( i<0 )
{
i = -i;
rest = i % 8;
}
else
rest = i % 8;
Beispiel:
Im Falle einer Division durch 0 soll eine Fehlermeldung erscheinen. Der Sonderfall 0 dividiert durch 0 soll
erkannt werden und liefert den Wert 1.
if ( nenner == 0 )
if ( zaehler == 0 ) division = 1;
else
else
printf("Division ist undefiniert \n ");
division = zaehler / nenner ;
Im obigen Programmausschnitt wurde nenner und zaehler in einer geschachtelten Fallunterscheidung
ausgewertet.
Geschachtelte Fallunterscheidungen sind oft unübersichtlich. In diesem Fall bietet sich eine elegantere
Lösung mit logischen Operatoren an.
if ( ( nenner == 0 ) && ( zaehler == 0 ) ) division = 1;
else if ( i
else
!= 0 ) division = zaehler / nenner ;
printf(" Division ist undefiniert \n");
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 57 v o n 108
Häufige Fehlersituationen:
Der ; vor else wird vergessen
Fallunterscheidung mit mehreren Alternativen
switch ( <Ausdruck> )
{ case <Konstantenausdruck_1> : <Anweisung_1> ;
case <Konstantenausdruck_2> : <Anweisung_2> ;
....
default: <Anweisung_sonst> ;
}
Wenn Ausdruck den Wert Konstantenausdruck_1 hat , so führe Anweisung_1 und alle folgenden
Anweisungen aus,
wenn Ausdruck den Wert Konstantenausdruck_2 hat, so führe Anwweisung_2 und alle folgenden
Anweisungen aus .....
Hat Ausdruck einen Wert, der nicht in den case-Fällen genannt wird, führe die Anweisung_sonst und alle
folgenden Anweisungen aus.
Beispiel:
Sie haben in einer Variablen ErrorNummer vom Typ unsigned short eine Fehlernummer gespeichert. Für
jede mögliche Fehlernummer soll ein Fehlertext am Bildschirm ausgegeben werden. Sie realisieren ihr
Programmstück wie folgt:
switch ( error_nummer )
{
case 1:
printf("Falsche Eingabe");
case 2:
printf("Falscher Parameter");
case 3:
printf("unbekanntes Kommando");
default:
printf("Unbekannte Fehlernummer");
}
Dieses Programmstück arbeitet fehlerhaft. Die Ursache liegt in der Logik der switch-Anweisung.
Hat die Variable ErrorNummer den Wert 1, so wird der erste case-Fall ( case 1: ) ausgeführt. Es erfolgt
die Bildschirmausgabe Falsche Eingabe. Danach werden jedoch noch alle folgenden Anweisungen in
der switch-Anweisung ausgeführt. Es erscheinen am Bildschirm daher nacheinander die Ausgaben
Falscher Parameter , unbekanntes Kommando Unbekannte Fehlernummer.
Damit im Falle case 1 nur die Anweisungen ausgeführt werden, die zu dem case Fall gehören, muß die
switch-Anweisung explizit mit einer break-Anweisung verlassen werden.
Das korrekte Programmstück sieht dann wie folgt aus:
switch ( error_nummer )
{
case 1:
printf("Falsche Eingabe");
break;
case 2:
printf("Falscher Parameter");
break;
case 3:
printf("unbekanntes Kommando");
break;
default:
printf("Unbekannte Fehlernummer");
break;
}
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 58 v o n 108
Jede Fallunterscheidung mit mehreren Alternativen kann durch Schachtelung von if / else -Anweisungen
realisiert werden. So kann z.B. die folgende switch-Anweisung
unsigned char zeichen;
switch ( zeichen)
{
case 'A':
erg =
a + b ;
break;
case 'Z':
erg = zeichen;
break;
default:
erg = 0;
}
return erg;
kann somit über folgendes Programmstück realisiert werden :
if ( zeichen == ‘A’ ) erg = a + b ;
else if ( zeichen == ‘Z’ ) erg = zeichen;
else erg = 0;
return erg;
Beispiel
Soll in mehreren unterschiedlichen Fällen gleiche Aktionen passieren kann man diese Fälle zu einer
Gruppe zusammenfassen und die switch-Anweisung kürzer gestalten. Diese Situation zeigt das folgende
Beispiel. Auf die Zeichen ‘.’, ‘,’, ‘;’ und ‘:’ soll die gleiche Ausgabe erfolgen. Dies kann man explizit in
jedem case-Fall ausführen lassen und dann die switch-Anweisung mit einem break verlassen.
switch ( zeichen )
{
case '.':
printf("Das Zeichen ist ein Trennzeichen");
break;
case ',':
printf("Das Zeichen ist ein Trennzeichen");
break;
case ';':
printf("Das Zeichen ist ein Trennzeichen");
break;
case ':':
printf("Das Zeichen ist ein Trennzeichen");
break;
case '+':
printf("Das Zeichen ist eine Addition");
break;
default:
printf(" Das zeichen ist weder Trennzeichen noch Addition");
}
Man kann jedoch alle gleichen Fälle hintereinander zu einer Gruppe zusammenfassen und alle caseFälle mit einer leeren Anweisung füllen. Erst im letzen Fall der Gruppe läßt man die Fehlermeldung
ausgeben. Da in den Fällen ., , und ; nur die leere Anweisung ausgeführt wird und kein break das
Verlassen der switch-Anweisung veranlaßt wird in allen Fällen die Aktionen des case-Falles ‘:’
ausgeführt. danach wird mit break die switch-Anweisung verlassen.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 59 v o n 108
switch ( zeichen )
{
case '.':
case ',':
case ';':
case ':':
printf("Das Zeichen ist ein Trennzeichen");
break;
case '+':
printf("Das Zeichen ist eine Addition");
break;
default:
printf(" Das zeichen ist weder Trennzeichen noch Addition");
}
Häufige Fehlersituationen:
Ein case-Fall wird nicht explizit durch break verlassen
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
2.3.2
Schleifen
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 60 v o n 108
Bedingte Schleifen
while ( <Ausdruck> ) <Anweisung> ;
Vor Eintritt in die Schleife wird <Ausdruck> berechnet. Solange der errechne Wert wahr ist ( <> 0 ) wird
Anweisung ausgeführt. Wird der Wert falsch ( 0 ) berechnet, wird die Schleife verlassen, d.h. die
Anweisung wird nicht mehr ausgeführt. Die while-Schleife wurde in der Vorlesung am Beispiel des SuchAlgorithmus eingeführt und erläutert.
do <Anweisung> while ( <Ausdruck> ) ;
Die Anweisung wird ausgeführt und danach der Ausdruck berechnet. Solange der errechne Wert wahr ist
( <> 0 ) wird Anweisung ausgeführt. Ist der <Ausdruck> falsch ( 0 ) wird die Schleife verlassen, d.h. die
Anweisung wird nicht mehr ausgeführt. In der do/while-Schleife wird die Anweisung mindestens einmal
ausgeführt, während in einer while-Schleife die Anweisung auch niemals ausgeführt werden muß.
Das Beispiel des Suchalgorithmus aus der Vorlesung kann auch mit einer do /while Schleife realisiert
werden. Man muß jedoch beachten, das die Anweisung ausgeführt wird bevor die Bedingung geprüft
wird. Beginnt man daher mit Index=0 als Startwert, so wird in der Schleife Index zuerst um 1 erhöht und
steht damit auf dem Wert 1. In der nachfolgenden Bedingung muß jedoch das erste Listenelement ( Index
hat den Wert 0 ), in diesem Fall also Liste[Index-1] verglichen werden. Eine while-Schleife kann also nicht
ohne Änderungen in eine do/while-Schleife umgewandelt werden.
Beispiel: Suchalgorithmus mit do/while-Schleife
void suche( int Liste[], unsigned int N )
{
unsigned short Index;
Index = 0;
do Index = Index + 1 while ( Liste[Index-1] != N && Liste[Index-1] != -1);
if ( Liste[Index]== N )
printf(“Die Zahl %i steht an Position %hu in der Liste\n“, N, Index-1 );
else printf(“Die Zahl %i kommt in der Liste nicht vor\n“,N );
}
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 61 v o n 108
Zählschleife
Die Zählschleife wird immer dann genommen, wenn die Anzahl der Schleifendurchläufe vorgegeben ist.
Typischerweise also, wenn alle Werte einer Liste mit fester Anzahl von Werten abgearbeitet werden soll.
for ( Initalialausdruck ; Bedinungsausdruck ; Ausführungsausdruck ) <Anweisung> ;
Bedeutung:
Initialausdruck:
Bedingungsausdruck:
Ausführungsausdruck:
Dieser Ausdruck wird genau einmal vor dem Eintritt in die Schleife
ausgeführt. Dies entspricht dem Anfangswert der Schleife.
Dieser Ausdruck entspricht der Bedingung einer while-Schleife. Vor der
Ausführung der Schleifenanweisung wird dieser Ausdruck ausgewertet.
Ergibt der Ausdruck wahr wird die Schleife ausgeführt. Gibt der Ausdruck
falsch wird die Schleife verlassen.
Wurde <Anweisung> ( also die Schleife ) ausgeführt, so wird danach der
Ausführungsausdruck ausgeführt. Durch diese Anweisung kann der
Schleifenzähler um Schrittweite k erhöht oder erniedrigt werden.
Beispiel:
Es soll eine Liste mit 10 Werten am Bildschirm ausgegeben werden. Man beginnt mit dem 1. Wert in der
Liste. Dieser Wert wird mit dem Index 0 angesprochen. In der Zählschleife wird der Index fortlaufend um
1 erhöht bis alle 10 Werte ausgegeben sind.
int main(void)
{ int Liste[10]={2,4,6,8,10,12,14,16,18,20};
unsigned short index;
for ( index=0; index < 10; index = index +1 ) printf(“%i“,liste[index]);
return 0;
}
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
2.4
E I N - / AU S G A B E N
AN W E N D E R
ZUR
A u s g a b e : 24.09.00
KOMMUNIKATION
Seite 62 v o n 108
MIT DEM
Zur Ausgabe von Datenwerten auf dem Bildschirm und zum Einlesen von Datenwerten von der Tastatur
stellt ANSI-C vordefinierte Funktionen zu Verfügung.
2.4.1.
Formatierte Bildschirmausgabe
Zur Ausgabe von Datenwerten auf dem Bildschirm exisiert in ANSI-C die vordefinierte Funktion printf().
Benötigte Headerdatei:
stdio.h
Prototyp:
int printf(<formatstring>, Werteliste );
Der <formatstring> ist ein Text, der neben auszugebenden Zeichen Formatangaben enthält. Eine
Formatangabe wird durch das Zeichen % eingeleitet. printf gibt den Text in <formatsring> zeichenweise
am Bildschirm aus. An der Stelle im Text, an dem das Zeichen % auftritt wird der Wert einer Variablen
eingefügt. Dazu nimmt printf den nächsten Wert aus einer Liste von Werten, die dem <formatstring>
folgen.
Ein auszugebende Wert muß in printf() an zwei Stellen bekannt gegeben werden:
1.
2.
Die Position im Ausgabestrom und der Datentyp des Wertes wir über eine Formatangabe ( %...)
im <formatstring> angegeben.
Der auszugebende Wert wird in der Werteliste angegeben. Als Wert kann eine Variable, eine
Konstante, ein Ausdruck oder ein Funktionsaufruf angegeben werden. Wird in printf mehr als ein
Wert ausgegeben sind die Werte durch , zu trennen. Für jede Formatangabe im <formatstring>
muß auch ein Wert in der Werteliste vorhanden sein.
Beispiel:
int main(void)
{ int zahl=111;
unsigned short index=10;
/* Ausgabe eines Textes am Bildschirm ohne neue Zeile am Ende */
printf(“Dieser Text erscheint
Textende“);
am
Bildschirm.
Der
Ausgabezeiger
steht
am
/* Positionierung auf eine neue Zeile */
printf(“\n“);
/* Es werden zwei Textzeilen ausgegeben. */
/* In der 1. Zeile wird der Wert von zahl im Format int ( %i ) eingefuegt. */
/* In der 2. Zeile wird der Wert von index im Format unsigned short ( %hu )
eingefügt */
printf(“ Der Wert von zahl : %i \n Der Wert von index : %hu \n“,zahl,index);
return 0;
}
Dieses Programm produziert folgende Ausgabe:
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 63 v o n 108
Dieser Text erscheint am Bildschirm. Der Ausgabezeiger steht am Textende
Der Wert von zahl : 111
Der Wert von index : 10
Die Formatangabe im Formatstring hat folgende Form:
%<Modus-Flag><Länge><.Stellen><Typ>Kennung
<Modus-flag>, <Länge>,<,.Stellen> und <Typ> können weggelassen werden, die Angaben sind also
optional. Das %Zeichen und die Kennung müssen angegeben werden.
Modus-Flag
+
Leerzeichen
Linksbündige Ausgabe ( normal ist Rechtsbündig )
Ausgabe von + falls Zahl positiv
Ausgabe eines Leerzeichens für positive Zahlen, - für
negative
#
einfügen von 0x
Dezimalpunktes in allen Fällen
Einfügen einer 0 als erstes Zeichen einer Oktalzahl,
bei Hexadezimalzahlen, Ausgabe eines
Länge
Minimale Anzahl von Zeichen in der Ausgabe. Falls * angegeben wird, so wird für Länge
der Wert des nächsten Wertes in der Werteliste genommen
Stellen
Ganze Zahl (i,d,u,o,x): Mindestzahl der Ziffern
Reelle Zahl (f,e,g):
Stellen hinter dem Komma
String:
Maximalzahl der Zeichen
Falls * angegeben wird, so wird für Stellen der Wert des nächsten Wertes in der
genommen
Werteliste
Typ
Zusätzliche Angabe zu Kennung
h
Wert ist vom Typ short int ( zusammen mit i,d,u,o,x)
l
Wert ist vom Typ long int ( zusammen mit i,d,u,o,x)
L
Wert ist vom typ long double ( zusammen mit f,e,g)
Kennung:
Kennung
i,d
Datentyp
signed int
u
o,x,X
f
e,E
g,G
unsigned int
unsigned int
double
( float-Werte werden in double
gewandelt )
double
double
c
s
char
char[]
Weitere Beispiele:
int zahl = 5;
char zeichen=‘A’;
float ergebnis=0.5;
Darstellung
Dezimalzahl mit neg. Vorzeichen oder ohne
Vorzeichen falls positiv
Dezimalzahl ohne Vorzeichen
Oktal oder Hexidezimal
Darstellung mit Dezimalpunkt
Darstellung mit Exponent
Dezimalpunkt oder Exponent abhängig vom
Wert
ein ASCII-Zeichen
Zeichenfolge ( string )
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 64 v o n 108
printf(“Zahl : %i zeichen : %x Quadrat : %i\n“, zahl, zeichen, zahl*zahl);
Ausgabe -->
Zahl : 5 zeichen : 41 Quadrat : 25
printf(“Zahl hat den Wert %6d\n“ zeichen ist %c“, zahl,zeichen);
Zahl hat den Wert
Zeichen ist A
5
printf(“Der Fehler betraegt %f %%\n“,ergebnis);
Der Fehler betraegt 0.500000%
printf(“Der Fehler betraegt %3.1f %%\n“,ergebnis);
Der Fehler betraegt 0.5%
2.7.2.
Eingabe von Tastatur
Zum Einlesen von Datenwerten, die der Anwender auf seiner Tastatur eingibt, existiert in ANSI-C die
vordefinierte Funktion scanf().
Benötigte Headerdatei:
Prototyp:
stdio.h
int scanf(<formatstring>, Liste von Variablenadressen );
Der Formatstring hat eine ähnliche Form wie in printf().
Alle Zeichen im Formatstring, die keine Formatangabe bedeuten, werden als Eingabezeichen
erwartet, müssen also auch vom Anwender eingegeben werden.
Dies macht normalerweise keinen Sinn. Der Formatstring enthält daher normalerweise nur
Formatangaben, die mit % beginnen.
Die Formatangabe im Formatstring hat folgende Form:
%<*><Länge><Typ>Kennung
*
Eingegebener Wert wird zwar interpretiert aber keiner Variablen zugewiesen d.h. Wert
wird ignoriert
Länge
Maximalzahl der zu interpretierenden Zeichen im Eingabestrom
Typ
Zusätzliche Angabe zu Kennung
h
l
L
Wert ist short int ( zusammen mit i,d,u,o,x)
Wert ist long int ( zusammen mit i,d,u,o,x)
Wert ist double ( zsammen mit f,e,g)
Wert ist long double ( zusammen mit f,e,g)
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 65 v o n 108
Kennung:
Kennung
I,d
Datentyp der Eingabe
signed int
u
o,x,X
unsigned int
unsigned int
f,e,E,g,G
float
c
s
char
char[]
Erwartete Zeichenfolge
Dezimalzahl ( ganzzahlig ) mit oder ohne
Vorzeichen
Dezimalzahl ( ganzzahlig ) ohne Vorzeichen
Oktalzahl oder Hexidezimalzahl ( ohne Präfix
0x )
Reelle Zahl mit oder ohne Vorzeichen, mit
oder ohne Dezinmalpunkt, mit oder ohne
Exponent
ein ASCII-Zeichen
Zeichenfolge ( string )
Es wird immer nur ein Wort eingelesen.
Trennzeichen für Wörter sind Leerzeichen,
Zeilenende oder Tabulator.
Beispiele
int i;
scanf( “ Value : %d“,&i);
Dieser Funktionsaufruf erwartet folgende Eingabe an der Tastatur:
8 Überlese alle Leerzeichen. Dann lese die Zeichenfolge Value :
8 Einlesen einer Dezimalzahl und speichern in der Variablen i.
scanf(“%d“,&i);
Dieser Funktionsaufruf erwartet als Eingabe eine Dezimalzahl.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
3
Elementare Algorithmen
3.1
SUCHEN
VON
A u s g a b e : 24.09.00
Seite 66 v o n 108
WERTEN
In Kapitel 1.1.1, Seite 6-7 wurde ein einfacher Suchalgorithmus „Lineares Suchen“ entwickelt, der in einer
ungeordneten Liste von Zahlen nach einer gegebenen Zahl sucht. Bei diesem Algorithmus muß im
ungünstigsten Fall die gesamte Liste durchsucht werden.
Enthält die zu durchsuchende Liste die Zahlen in einer aufsteigend sortierten Reihenfolge, so kann die
Suche wesentlich effizienter erfolgen. Die Idee dabei ist die, man durchsucht nur den teil der Liste, in der
die Zahl vorkommen kann. Sucht man z.B. die Zahl 5 in einer Liste von positiven ganzen Zahlen so muß
nur der linke Teil der Liste durchsucht werden, in dem die Zahlen 0 bis 5 vorkommen können.
Der Algorithmus kann wie folgt beschrieben werden:
Gegeben ist eine Liste mit positiven ganzen Zahlen. Die Zahlen in der Liste sind aufsteigend sortiert. Die
Liste enthält eine feste Anzahl von Werten.
Beispiel:
Gegeben ist eine Liste mit 10 Zahlen. Gesucht wird die Zahl 5
Index ( Position )
1
2
5
9
10
12
13
20
28
40
1
2
3
4
5
6
7
8
9
10
Man vergleicht das mittlere Element ( hier den Wert an der Position 5 ) mit der gesuchten Zahl
Liste[Mitte] =Liste[5]=10 GLEICH 5 ?
Entweder ist die Zahl gefunden oder man kann den Suchbereich einschränken auf den rechten Teil der
Liste, wenn die gesuchte Zahl größer ist als der Wert an der mittleren Position oder auf den linken Teil
der Liste, wenn die gesuchte Zahl kleiner ist als der Wert an der mittleren Position. In unserem Beispiel
ist 5 kleiner Liste[5], , also wird der Suchbereich auf die linke Hälfte also die Positionen 0 .. . Mitte-1
eingegrenzt.
Man wiederholt nun die Suchstrategie mit dem mittleren Element der linken Listenhälfte, hier neue Mitte =
Position 2. Ein Vergleich Liste[2] GLEICH 5 ? ergibt FALSCH. Jetzt wird die Liste wieder halbiert. 5 ist
größer als 2 also wird die rechte Listenhälfte von Position Mitte+1 ... 4 genommen. Diese Liste enthält nur
noch zwei Elemente. Bestimmt man die Mitte so erhält man die Position 3. Ein Vergleich
Liste[3] GLEICH 5 ? ergibt einen positiven Treffer. Die gesuchte Zahl ist gefunden.
Hätte man anstelle der Zahl 5 die Zahl 7 gesucht, so wäre auch hier das Vergleichsergebnis negativ
ausgefallen. Da 7 größer ist als Liste[Mitte] müßte die Suche auf die rechte Hälfte ausgedehnt werden.
Die rechte Hälfte enthält jedoch nur noch 1 Element. Den Wert 9 an der Position 4. Ein Vergleich ergibt
Liste[4] ist ungleich 5. Ein erneutes teilen der Liste ist jetzt nicht mehr möglich. Die Sucher endete daher
mit dem Ergebnis Die Zahl 7 kommt in der Liste nicht vor.
Dieser Algorithmus ist unter dem Namen BINÄRES S UCHEN bekannt.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 67 v o n 108
Verfahren zur Berechnung der mittleren Position:
Mitte
MinPos
1
2
3
4
5
6
7
MaxPos
8
9
10
MinPos
11
MaxPos
Beim Start kann das mittlere Element bestimmt werden durch
MaxPos - MinPos
2
11- 1
+ MinPos --->
2
+1 = 5 + 1 = 6
Wird die Suche auf die rechte Hälfte reduziert so berechnet sich die Mitte wie folgt:
MinPos=7; MaxPos=11;
MaxPos - MinPos
2
+ MinPos =
11- 7
2
+7 = 2 + 7 = 9
Diese Berechnung kann wie folgt vereinfacht werden:
MaxPos - MinPos
2
+ MinPos =
MaxPos - MinPos +2 MinPos
2
Formale textuelle Darstellung
Algorithmus
Binäres Suchen
Objekte:
sortiert.
Liste
MaxPos + MinPos
=
2
in Pseudocode:
Liste mit n positiven ganzen Zahlen. Die Liste ist aufsteigend
Zahl
Die gesuchte Zahl
MinPos Linke Position in der zu durchsuchenden Liste
MaxPos
Rechte Position in der zu durchsuchenden Liste
Mitte
Mittlere Position in der zu durchsuchenden Liste
Aktionen:
Initialisierung
MinPos=1
MaxPos=n;
Mitte = (MinPos + MaxPos) dividiert durch 2
solange Wert an der mittleren Position ungleich Zahl und die Liste noch Elemente enthält tue
wenn liste[mitte] < Zahl dann
MinPos = Mitte +1
sonst
MaxPos = Mitte - 1
wenn liste[mitte] = Zahl dann
sonst
Ausgabe „ Die Zahl kommt an Position mitte in der Liste vor „
Ausgabe „ Die Zahl kommt in der Liste nicht vor „.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
3.2 SORTIEREN
VON
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 68 v o n 108
LISTEN
Sortieren ist eine häufig vorkommende Problemstellung in der Informationstechnik. Aus diesem Grund
wurden mehrere unterschiedliche Algorithmen entwickelt, von denen jeder seine Vorteile und Nachteile
hat. Es gibt einfache Verfahren, die jedoch bei großen Listen viel Laufzeit benötigen. Andere sind zwar
wesentlich schneller benötigen aber vielleicht mehr Speicherplatz oder sind komplexer. Man
unterscheidet folgende Sortierverfahren.
8 Sortieren durch Einfügen
Bekannte Verfahren sind: InsertienSort, Shellsort
8 Sortieren durch Auswählen
SelectionSort ( wurde in Übung 3 eingeführt )
8 Sortieren durch Austauschen
Bubblesort ( wird in Übung 7 eingeführt ) , Shakersort
8 Sortieren durch Teilen und Mischen
Quicksort und Mergesort werden in Programmieren 2 behandelt
Eine detaillierte Beschreibung aller in diesem Skript nicht beschriebenen Sortierverfahren kann
nachgelesen werden in:
8 Niklaus Wirth: Algorithmen und Datenstrukturen,
8 Ralf Güting:
Datenstrukturen und Algorithmen,
8 R. Sedgewick: Algorithmen,
8 K. Mehlhorn:
Datenstrukturen und effiziente Algorithmen, Sortieren und Suchen Band 1,
8 Knuth:
The Art of Computer Programming, Vol 3, Sorting and Searching
Im folgenden Abschnitt sei InsertienSort als Sortierverfahren durch Einfügen beschrieben.
3.2.1
Sortieren mit dem Verfahren InsertienSort
Dieses Sortierverfahren wurde von einem alten Kartenspiel abgeleitet. Man hat eine Reihe von
ungeordneten Karten und will diese Karten dem Wert entsprechend sortieren.
44
Beispiel:
12
55
3
Man beginnt mit der 2. Karte. Man nimmt diese Karte aus der Reihe. Es bleibt eine linke Seite und eine
rechte Seite übrig. Ziel ist nun, die aktuell genommene Karte x in der linken Seite an die richtige Position
einzufügen. Die rechte Seite bleibt unverändert.
44
Linke
Seite
55
12
3
Rechte
Seite
Im Beispiel nimmt man die Karte mit dem Wert 12.
44
55
3
Um diese Karte an die richtige Position zu bringen, schiebt man alle Karten,
deren Wert größer ist als 12, um eine Position nach rechts.
Hat man alle größeren Karten verschoben bleibt eine Lücke, in die man die
genommene Karte x einfügen kann.
12
12
44
55
3
Die Karte mit dem Wert 12 steht in unserem Beispiel danach an der 1. Position
Dieses Vorgehen wiederholt man für alle Karten, die auf der rechten Kartenseite liegen. Die nächste
gewählte Karte ist also die Karte an der Position 2 +1 = 3. Diese Karte hat den Wert 55.
Programmieren 1
12
Prof. Dr.-Ing. Silvia Keller
44
3
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 69 v o n 108
Man nimmt die Karte aus der Reihe. Alle Karten auf der linken Seite haben
einen kleineren Wert, werden daher nicht verschoben.
55
Alle Karten links
haben einen
kleineren Wert
12
44
55
3
Die Karte wird wieder an der selben Position eingefügt.
Die nächste Karte hat den Wert 3.
12
44
Man nimmt diese Karte aus der Reihe. Alle Karten links davon haben einen
größeren Wert und werden daher um eine Position nach rechts geschoben.
55
3
Als Lücke bleibt die 1. Position. An diese Stelle wird die genommene Karte
eingefügt.
12
44
55
3
3
12
44
55
Dies war die letzte Karte in der Reihe. Die Reihe ist damit sortiert.
Der beschriebene Algorithmus kann durch folgendes C-Programm realisiert werden:
void InsertionSort(int liste [], int laenge )
{ int KarteX;
/* Aktuell genommene Karte */
int i,links;
/* Positionen in der Liste */
/*----------------------------------------*/
/* Starte mit der zweiten Karte von links */
/* und wiederhole für alle rechten Karten */
/*----------------------------------------*/
for(i = 1; i<laenge; i=i+1 )
{
KarteX=liste[i];
links=i-1;
/*-----------------------------------------------------------------------*/
/* Verschiebe alle linken Karten, die groesser sind als KarteX nach rechts
*/
/*-----------------------------------------------------------------------*/
while ( (liste[links] > KarteX) && (links >= 0) )
{ liste[links+1]=liste[links];
links=links - 1;
}
/*---------------------------------------------------*/
/* Fuege die KarteX an die verbleibende Position ein */
/*---------------------------------------------------*/
liste[links+1]=KarteX;
}
}
Programmieren 1
3.3
Prof. Dr.-Ing. Silvia Keller
SORTIEREN
UND
Studiengang Angewandte Informatik
SUCHEN
A u s g a b e : 24.09.00
Seite 70 v o n 108
VON STRINGS
Sortier- und Suchalgorithmen werden häufig benutzt um Namen also Zeichenketten zu sortieren und zu
suchen.
Deshalb soll an dieser Stelle die Behandlung von strings in der Programmiersprache C eingeführt
werden.
Eine Zeichenkette ( string ) ist eine Folge von Zeichen. In der Programmiersprache C werden diese
Zeichen in einem array ( Liste ) abgelegt. Ein string ist in C also ein array, in dem Zeichen gespeichert
sind , und ist daher wie folgt zu definieren:
char string[256];
string ist eine Variable, in der 256 Werte abgelegt werden. Da eine Zeichenkette im Normalfall eine
variable Anzahl von Zeichen enthält, wird in dem array das Ende der gültigen Zeichenfolge durch ein
Sonderzeichen, das ASCII-Zeichen NUL ( Dezimalwert 0, Zeichenkonstante ‘\0’ ) gekennzeichnet. Die
Variable string enthält somit maximal 255 gültige Zeichen eines string und das Endezeichen ‘\0’.
Beispiel:
char name[12];
0
K
11
e
l
l
e
r
0
-
-
-
-
-
Durch Definition von name wird eine Array mit 12 Komponenten angelegt. Dieses Array enthält nach der
Definition ungültige Werte. Das Array kann mit dem Namen „Keller“ gefüllt werden durch eine
komponentenweise Zuweisung der einzelnen Zeichen der Zeichenkette an die array-Komponenten:
name[0]=‘K’; name[1]=‘e’; name[2]=‘l’; name[3]=‘l’; name[4]=‘e’; name[5]=‘r’;
name[6]=‘\0’;
Man kann das Array auch direkt bei der Definition analog einem array, das Zahlenwerten enthält,
initialisieren:
char name[12]={‘K’,‘e’,’l’,’l’,’e’,’r’};
Der Rest des array wird automatisch mit 0 aufgefüllt. Damit wird das Endezeichen ‘\0’ automatisch ans
Ende der Zeichenkette geschrieben.
Im Falle von strings ermöglicht C auch eine vereinfachte Schreibweise bei der Initialisierung:
char name[12]=“Keller“;
Die rechte Seite “Keller“ ist eine string-Konstante.
Möchte man die Größe des Array an die Länge des Namens anpassen um möglichst wenig Speicherplatz
zu belegen, so ist folgende Initialisierung möglich:
char name[]=“Keller“;
Wird bei der Definition keine Anzahl von array-Komponenten angegeben, so berechnet sich der Compiler
die Anzahl selbst aus der Anzahl der angegebenen Initialwerte. In diesem Fall wird ein Array mit genau 7
Komponenten ( 6 Zeichen für Keller + ‘\0’ ) erzeugt.
Will man im Programm den Inhalt des Array ändern, im vorliegenden Beispiel also einen anderen Namen
speichern, so kann dies nur komponentenweise geschehen.
Beim Einlesen und bei der Ausgabe kann jedoch ein Wort oder eine Zeile komplett eingelesen bzw.
ausgegeben werden:
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 71 v o n 108
Mit scanf kann ein array mit einem eingelesenen Wort beschrieben werden. Hier ist zu beachten, daß
beim Einlesen von strings der &-Operator entfällt. Ein Wort in der Eingabe ist getrennt durch Leerzeichen,
Tabulator oder Neue Zeile.
scanf(„%s“, name);
/* Liest ein Wort von der Tastatur ein */
Zum Einlesen einer kompletten Zeile inklusive Leerzeichen und Tabulatoren kann die Funktion
gets(name) aus stdio benutzt werden.
Zur Ausgabe einer beliebigen Zeichenkette kann die Funktion printf() oder puts() verwendet werden.
printf(„%s“, name );
Während printf() an die Zeichenkette keine neue Zeile anfügt wird durch puts automatisch eine neue Zeile
am Ende der Zeichenkette ausgegeben.
Da string kein eigener Datentyp ist, sondern als array von Zeichen realisiert wird, sind folgende
Ausdrücke in C fehlerhaft:
string=„ Ein neuer text“;
string=name;
/* fehlerhafte Zuweisungen */
Die Zuweisung einer Stringkonstanten an eine Variable ist nur bei der Initialisierung möglich. Eine
Änderung von string während der Programmlaufzeit, kann nur komponentenweise erfolgen.
string[0]=‘E’; string[1]=‘i’ ; ........ string[ ]=‘t’; string[
]=‘\0’;
Eine char array kann auch nicht als Ganzes zugewiesen werden.
Will man die Zeichenfolge im array name dem array string zuweisen, so kann auch dies nur
komponentenweise geschehen.
Da Zeichenketten in einem Programm häufig benötigt werden, stellt C viele Standardfunktionen
( Bibliothek: string , Headerdatei string.h ) zur Bearbeitung von Zeichenketten zu Verfügung. Die
wichtigsten davon werden an dieser Stelle aufgeführt. Weitere Funktionen können den Handbüchern zu
ANSI C entnommen werden.
Bestimmung der Länge einer Zeichenkette
Prototyp:
size_t strlen( string );
Beispiel:
strlen(„Keller“) liefert den Wert 6.
Kopieren einer Zeichenkette in ein char array
Prototyp:
strcpy(ziel,string);
ziel muß ein char array sein.
String kann entweder ein char array oder eine string-Konstante sein.
Beispiel:
strcpy(string,name);
strcpy(string,“Hulin“);
Anfügen einer Zeichekette string2 an eine Zeichenkette string1
Prototyp:
strcat( string1, string2)
string1 muß ein char array sein. string2 kann entweder ein char array oder eine string-Konstante sein.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 72 v o n 108
Vergleichen zweier Zeichenketten
Prototyp:
int strcmp( string1, string2 )
Der Vergleich der Strings beginnt mit dem ersten Zeichen in jedem String und wird zeichenweise
fortgesetzt, solange sich die Zeichen unterscheiden, oder das Ende des Strings erreicht wird.
Rückgabewert:
3.3.1
Zahl kleiner 0 wenn
0 wenn
Zahl größer 0 wenn
string1 kleiner string2
string1 gleich string2
string1 größer string2
Sortieren einer Liste mit Namen
Sie sollen mit einem bekannten Sortieralgorithmus eine Liste mit 10 Namen sortieren. Jeder
Name kann maximal 16 Zeichen lang sein.
Ein Name ist eine Zeichenkette und wird daher in einem Array mit 16 + 1 Element gespeichert.
K
e
l
l
e
r
‘\0’
0
0
0
0
0
0
0
0
0
0
0
15
16
Eine Liste von 10 Namen ist daher ein Array, dessen Komponenten selbst wieder ein array ( in diesem
Fall ein string ) sind.
#define ANZAHLNAMEN 10
#define ANZAHLZEICHEN 16
char liste[ ANZAHLNAMEN ][ ANZAHLZEICHEN + 1 ]
Beispiel für eine Liste mit 10 Namen
Zeile 0
Zeile 1
Zeile 2
Zeile 3
Zeile 4
Zeile 5
Zeile 6
Zeile 7
Zeile 8
Zeile 9
0
1
2
3
4
5
6
K
H
G
U
B
E
K
S
A
K
e
u
a
s
r
r
o
c
d
r
l
l
m
a
u
t
c
h
e
a
l
i
p
d
e
e
h
i
r
g
e
N
p
e
m
l
r
‘\0’ 0
Mit der Anweisung
7
0
‘\0’ 0
0
‘\0’ 0
‘\0’ 0
l
8
0
9
0
10
0
11
0
12
0
13
0
14
0
15
0
16
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
e
r
‘\0’
0
‘\0’ 0
0
‘\0’ 0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
g
‘\0’ 0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
l
m
l
m
l
a
e
i
n
r
n
n
‘\0’
0
‘\0’ 0
liste[0] --1. Name
liste[1] -- 2. Name
liste[9]
printf(“%i-ter Name = %s\n“, i, liste[i]);
kann ein Name aus der Liste am Bildschirm ausgedruckt werden.
Möchte man einen Namen verändern, so kann man hierzu die ANSI-Funktion strcpy aus string.h
verwenden.
strcpy(liste[0], “Maier“);
/* 1. Name in der Liste wird Maier */
Programmieren 1
4
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 73 v o n 108
Unterprogrammtechnik
Unterprogramme dienen hauptsächlich dazu, größere Programme in kleinere Untereinheiten zu zerlegen.
So kann z.B. das Programm aus Übung 6 in wie folgt strukturiert werden:
Hauptprogramm zu Übung 6
Beginn
Einlesen einer Liste von 20 Zahlen.
Sortieren der Liste.
Einlesen einer Zahl, die in der Liste gesucht werden soll.
Binäre Suche.
Ende
Einlesen, Sortieren und BinäreSuche sind Untereinheiten, die als Unterprogramme realisiert werden
können.
Das Struktogramm des Programms enthält 4 Makroblöcke. 3 Makroblöcke sind selbst definierte
Unterprogramme. Jedes Unterprogramm wird durch ein eigenes Struktogramm beschrieben.
Der Makroblock EINLESEN ZAHL wird durch ein in C vordefiniertes Unterprogramm ( ANSI-Funktion
scanf() ) realisiert. Dieser Makroblock wird daher nicht durch ein Struktogramm erklärt. Er wird als
gegeben hingenommen.
Hauptprogramm
EinlesenListe
Sortieren
Einlesen Zahl
BinaereSuche
Beschreibung des Makroblockes Sortieren
Sortieren
Index:=1
solange Liste[Index] ungleich N und Liste[Index] ungleich -1
Index:=Index + 1
wenn Liste[Index] = N
wahr
Die Zahl kommt an der
Position Index in der Liste vor
falsch
Die Zahl kommt in der
Liste nicht vor
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 74 v o n 108
Ein Unterprogramm wird genau einmal definiert und liegt auch nur einmal im Speicher. Bei der Definition
eines Unterprogramms wird nur der Algorithmus beschrieben und im Speicher abgelegt. Das
Unterprogramm wird jedoch nicht ausgeführt. Ein Unterprogramm wird erst ausgeführt, wenn im
Hauptprogramm das Unterprogramm verwendet wird ( Unterprogrammaufruf ). Durch den
Unterprogrammaufruf werden alle Programmanweisungen des Unterprogramms ausgeführt und , falls
das Unterprogramm eine Funktion ist, der Ergebniswert berechnet und an das Hauptprogramm
übergeben.
Ein Unterprogramm kann mehrfach im Hauptprogramm verwendet werden. Jedes Unterprogramm kann
selbst wieder Unterprogramme aufrufen. Ein Unterprogramm kann sowohl vom Hauptprogramm im
gleichen Modul als auch von Unterprogrammen im gleichen oder in externen Modulen verwendet werden
( Modulares Programmieren ). Beim Unterprogrammaufruf wird das Hauptprogramm verlassen und in
das Unterprogramm verzweigt. Nach Ausführung des Unterprogramms wird wieder ins Hauptprogramm
zurückgekehrt ( siehe Bild Kapitel 1, Seite... ).
4.1
PROZEDUREN
UND
FUNKTIONEN
Die Programmiersprache C unterscheidet nicht in Prozeduren und Funktionen. In C sind alle
Unterprogramme Funktionen. Eine Funktion liefert genau einen Wert als Ergebnis. Beispiel für eine
Funktion ist die Berechnung des Sinus. Definitionsbereich und Wertebereich dieser mathematischen
Funktion sind die rellen Zahlen. Die Funktion wird in C folgendermaßen definiert:
Prototyp der C Funktion:
Eingabewert
x
Funktion
sinus
Ergebniswert
double sinus( double x );
y
x ist Eingabewert in die Funktion.
Durch Aufruf der Funktion wird der Ergebniswert also sinus(x) berechnet und kann durch eine Zuweisung
an eine Programmvariable im Programm verfügbar gemacht werden.
double y ;
y = sinus(90);
Fehlt die Zuweisung, wird zwar der Wert in der Funktion berechnet, im aufrufenden Programm jedoch
nicht gespeichert und damit verworfen.
Eine Prozedur kann in C dadurch realisiert werden, daß man als Ergebnis einer Funktion den Datentyp
void angibt. void besagt in diesem Fall, daß die Funktion keinen Ergbnistyp besitzt, also eine Prozedur
ist.
Beispiel:
Prozedur
AusgabeStern
Das Unterprogramm AusgabeStern produziert
folgende Ausgabe auf dem Bildschirm:
*
*
*
* *
*
*
*
*
Das Unterprogramm ist eine reine Zusammenfassung von Programmanweisungen, ohne das ein Wert
berechnet wird. Das Unterprogramm ist damit eine Prozedur. Das Unterprogramm wird in C wie folgt
definiert:
Prototyp der Funktion:
void AusgabeStern(void);
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Info rmatik
A u s g a b e : 24.09.00
Seite 75 v o n 108
Der Unterprogrammaufruf kann nicht in einer Zuweisung verwendet werden, da kein Wert berechnet wird.
Folgende Programmanweisung ist daher fehlerhaft:
4.2
erg = AusgabeStern();
/* fehlerhafter Unterprogrammaufruf */
AusgabeStern();
/* Korrekter Unterprogrammaufruf
*/
PARAMETER
Um Werte aus dem Hauptprogramm ans Unterprogramm übergeben zu können, kann man bei der
Definition einer Funktion eine beliebige Anzahl von formalen Parametern festlegen. Ein formaler
Parameter verlangt die Angabe eines Parameternamens und eines Datentyps. Dar Name wird im
Unterprogrammrumpf benötigt , um die einzelnen Eingabewerte benutzen zu können.
Formale Parameter
a
Beim Aufruf der Funktion werden Werte vom Hauptprogramm
ans Unterprogramm übergeben ( aktuellen Parameter ).
b
Die
in
û
û
û
Unterprogramm
verwendet a und b
Parameter eines Unterprogramms kann man klassifizieren
Import-Parameter,
Export-Parameter und
Import/Export-Parameter.
Unterprogrammtechnik - Klassifikation der Parameter
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Hauptprogramm
Variable
UP
Hauptprogramm
Hauptprogramm
Variable
Variable
UP
UP
Export-Parameter
call by value
8
30.11.1997
Kopie
Import-Parameter
8
Technische Informatik
Bei der Parameterübergabe wird vom
Parameter eine Kopie erzeugt. Das
Unterprogramm erhält nur die Kopie des
aktuellen Wertes einer Variablen.
Wird im Unterprogramm der Parameter
verändert, so wird nur die Kopie im
Unterprogramm verändert. Nach
verlassen des Unterprogramms ist der
Originalwert im Hauptprogramm
unverändert vorhanden.
Import-/Export-Parameter
call by
reference
8
8
8
Diese Parameter werden als Variablen an das
Unterprogramm übergeben. Damit hat das
Unterprogramm Zugriff auf das Original im
Hauptprogramm und kann dieses auch verändern.
Bei export-Parametern wird die Variable durch das
Unterprogramm verändert ohne den Wert vorher zu
lesen.
Beim Import-/Exportparameter wird die Variable
sowohl gelesen als auch geschrieben.
Programmieren 1
4.3
Prof. Dr.-Ing. Silvia Keller
DEFINITION
VON
Studiengang Angewandte Info rmatik
FUNKTIONEN
IN
Seite 76 v o n 108
C
<Ergebnistyp>
<Funktionsname> ( <Formale Parameter> )
{
/* Hier beginnt der Rumpf der Funktion */
/*
A u s g a b e : 24.09.00
Funktionskopf
Funktionsrumpf
Hier sollten alle lokale Variablen definiert werden */
/* Im Rumpf der Funktion muß mindestens eine return - Anweisung vorkommen */
/* Mit return wird das Ergebnis ans Hauptprogramm übergeben und das Unterprogramm
verlassen */
}
/* Hier endet der Rumpf der Funktion */
Parameter werden in C grundsätzlich nach der Methode call by value übergeben, d. h. C kennt nur
Import-Parameter.
BEISPIEL:
Im Übungsblatt 2 wurde zur Umrechnung einer Dezimalzahl in eine Dualzahl eine Operation benötigt, die
zwei Ergebnisse liefert. Eine ganzzahlige Division von zwei positiven ganzen Zahlen mit Berechnung des
Rest.
Hierfür kann man ein Funktion schreiben, die beide Operation ( ganzzahlige Division und Berechnung
des Rests ) zu einer Anweisung DivisionMitRest zusammenfaßt.
zaehler
nenner
ImportParameter
ExportParameter
Funktion
DivisionMitRest
zaehler
nenner
Rest
Quotient
Rest
In C kann eine Funktion nur einen Wert als Ergebnis
liefern, also entweder den Quotienten oder den Rest.
Der zweite Wert muß daher als Export-Parameter
realisiert werden.
C kennt jedoch nur Import-Parameter.
Funktion
DivisionMitRest
Quotient
Wie kann dieses Problem gelöst werden ?
Lösungsvorschlag:
8 Verwendung globaler Parameter
Globale Variablen sind gültig in allen Programmobjekten und damit auch dem Unterprogramm
zugänglich. Ein Unterprogramm kann damit Globale variablen verändern, ohne das diese als Parameter
erscheinen. Das Unterprogramm verändert also Variabeln, die außerhalb des Unterprogramms liegen.
Dies nennt man Seiteneffekt. Seiteneffekt des Unterprogramms ist die Veränderung von Variablen, die in
aus der Umgebung dem Unterprogramm zugänglich sind.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
zaehler
ImportParameter nenner
Studiengang Angewandte Info rmatik
Funktion
DivisionMitRest
A u s g a b e : 24.09.00
Seite 77 v o n 108
Quotient
Das Unterprogramm kann damit folgendermaßen definiert werden:
long quotient=0, rest=0;
/* Definition globaler Variabeln */
int main(void)
{ quotient = DivisionMitRest(4,3);
printf(„ %li dividurt durch %li ergibt %li mit Rest li\n“,zaehler,nenner,quotient,rest);
}
long DivisionMitRest(long zaehler, long nenner)
{
rest = zaehler % nenner ;
return zaehler / nenner ;
}
Sonderfall :
Ü b e r g a b e v o n e i n d i m e n si o n a l e n L i s t e a n e i n e
C-Funktion
Gegeben ist folgendes Problem:
Es ist ein Programm zu schreiben, welches eine Liste mit 2000 ganzen Zahlen definiert. Es soll eine
positive ganze Zahl eingelesen werden. Diese Zahl soll in der Liste gesucht werden. Zum Suchen soll ein
Unterprogramm verwendet werden. Dieses Unterprogramm benötigt zwei Import-Parameter, die Liste mit
2000 Zahlen und die zu suchende Zahl.
In diesem Fall müßten alle 2000 Werte der Liste einzeln als Kopie an das Unterprogramm übergeben
werden. Dies ist nicht sinnvoll. Deshalb übergibt C im Falle einer Liste den Variablennamen an das
Unterprogramm. Dies entspricht jedoch der Übergabemethode call by reference. Im Falle von
eindimensionalen Listen macht C hier eine Ausnahme mit folgenden Nachteilen:
8 Es wird der Variablennname an das Unterprogramm übergeben. Die größer der Liste wird
nicht übergeben. Im Unterprogramm ist damit die Listengröße nicht mehr bekannt.
8 Alle Listenwerte stehen dem Unterprogramm im Original zu Verfügung. Eine Änderung von
Listenwerten im Unterprogramm verändert damit die Originalliste.
Ein Schutz vor
Überschreibung der Variable ist damit nicht mehr gegeben.
Beispiel:
void suche( unsigned int n );
/* Prototyp der Funktion */
/*****************/
/* Hauptprogramm */
/*****************/
int main(void)
{ int Liste[N]={ 2,4,5,8,9,55,21,0,7,20,12,45,11,13,78, 45,23,78,90,189, -1 );
unsigned int zahl;
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Info rmatik
A u s g a b e : 24.09.00
Seite 78 v o n 108
printf(„Bitte gib die zu suchende Zahl ein: „);
scanf(„%u“,&zahl);
suche(Liste,N); /* Aufrufen der Funktion suche mit aktuellen Parametern */
return 0;
}
/*********************************************/
/* Definition einer Prozedur
suchen()
*/
/* Formale Parameter: Eine Liste Zahlen
*/
/*
eine zu suchende Zahl n
*/
/*********************************************/
void suche( int Liste[], unsigned int n )
{
unsigned short Index;
Index=0;
while ( Liste[Index] != n && Liste[Index] != -1 ) Index=Index+1;
if ( Liste[Index]== n )
printf(“Die Zahl %u steht an Position %hu in der Liste\n“, n, Index );
else printf(“Die Zahl %u kommt in der Liste nicht vor\n“,n );
}
Programmieren 1
4.4
M
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Info rmatik
A u s g a b e : 24.09.00
Seite 79 v o n 108
AKROS
In Kapitel 4.1 wurde die Unterprogrammtechnik als Mittel zur Strukturierung und Vereinfachung von
Programmen eingeführt. Eine Alternative zu Unterprogrammen, die aus nur wenigen Programmzeilen
bestehen, sind Makros.
Ein Makro ist ähnlich einem Unterprogramm ein Programmteil, der unter einem Namen, dem
Makronamen, zusammengefaßt wird, und über den Namen im Programm verwendet werden kann. Wie
ein Unterprogramm, kann ein Makro auch formale Parameter erhalten, die bei der Verwendung durch
aktuelle Werte ersetzt werden.
Bei der Verwendung eines Programmteils als Makro oder als Unterprogramm gibt es jedoch
Unterschiede, die im einen oder anderen Fall als Vorteil oder als Nachteil zu sehen sind.
Makros werden vor der Übersetzung eines C Programms definiert und ersetzt.
Der C-Compiler besteht aus zwei Phasen ( siehe Kapitel 2, Seite 25 ).
In der ersten Phase wird vom Präprozessor ein Quelltext, der C-Sprachelemente und spezielle
Anweisungen an den Präprozessor enthält, in einen Quelltext überführt, in dem nur noch gültige CSprachelemente vorkommen.
In der zweiten Phase wird dann der C-Quelltext vom Compiler in ein Programmobjekt übersetzt.
Der Präprozessor ist ein reines Textverarbeitungsprogramm, welches C-Sprachelemente unverändert
verändert kopiert, aber Textelemente, die mit dem Sonderzeichen # beginnen, als Anweisungen
interpretiert, um Texte einzufügen oder zu verändern.
Eine Anweisung des Präprozessors ist die Anweisung #include, mit der man Textdateien an die
angegebene Position einfügen kann. Eine weitere Anweisung ist #define. Mit define kann man ein Makro
definieren.
Ein Makro besteht aus einem Makronamen und einem Programmtext. Nach der Definition wird der
Makroname als Platzhalter für den Programmtext verwendet d.h. taucht der Makroname in einem
Programm auf, wird der Makroname durch den Programmtext ersetzt. Diese Textersetzung übernimmt
der Präprozessor und wird Makroexpansion genannt.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Info rmatik
A u s g a b e : 24.09.00
Seite 80 v o n 108
Makros
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
Makrodefinition
04.03.1998
MakroExpansion
Quelltext
vor Präprozessor
Makrorumpf
Makroname
Quelltext
nach Präprozessor
a=a*b;
Programmtext
• Die Definition eines Makros muß vor
der ersten Expansion liegen
• Die Definition eines Makros geschieht
durch die
Präprozessoranweisung
#define
a=a*b;
Makroname
b=a++ *c;
b++;
Makroname
return b;
Ersetze
Makroname
durch
Programmtext
Programmtext
b=a++ *c;
b++;
Programmtext
return b;
Beispiel:
/* Definition eines Makros ohne Parameter */
#define
NEWLINE
printf(„\n“);
/* Makroexpansion */
printf(“%i“,i);
NEWLINE
/* Der Makroname NEWLINE wird an dieser Stelle durch
den Programmtext printf(„\n“); ersetzt */
b++;
Weitere Beispiele für Makros:
Makrodefinition
Makroverwendung
Programmtext nach der
Makroexpansion
#define BUF_LEN 512
int puffer[BUF_LEN];
int puffer[512]
#define UEBERSCHRIFT printf(„ -- Tab ---“);
\
printf(„ ----------“);
\
NEWLINE
UEBERSCHRIFT
printf(„ -- Tab ---“);
printf(„ ----------“);
printf(„\n“);
#define ADD( a,b)
erg=ADD(4*5,3);
erg=( (4*5) + (3) );
( (a) + (b) )
Die Vereinbarung von Konstanten mit #define ist also eine Makrodefinition. Der Makroname ist in
diesem Fall der symbolische Name für einen konstanten Wert. Der Konstantenname wird im Programm
durch den vereinbarten Wert ersetzt. Muß ein konstanter Wert, der mehrmals im Programm benötigt wird
geändert werden, kann dies an genau einer Stelle erfolgen. Alle Vorkommen werden dann automatisch
durch den Präprozessor geändert.
Ein Makro kann auch mehr als eine Programmzeile umfassen. Eine Folgezeile des Makrorumpfes wird
durch \ angezeigt. Das Makro UEBERSCHRIFT besteht im obigen Beispiel aus drei Programmzeilen.
Bei der Makroexpansion wird der \ weggelassen.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Info rmatik
A u s g a b e : 24.09.00
Seite 81 v o n 108
Makros können geschachtelt werden. Im Makro UEBERSCHRIFT wurde das vorher definierte Makro
NEWLINE verwendet.
Ein Makro kann auch mit Parametern definiert werden.
Das Makro ADD im obigen Beispiel hat z.B. die beiden Parameter a und b. a und b wird im Makrorumpf
durch die entsprechenden aktuellen Parameter bei der Makroverwendung ersetzt.
Aber Vorsicht bei der Definition von Makros mit Parametern. Die Parameter eines Makros werden in ( )
angegeben. Die öffnende Klammer ( muß direkt dem Makroname folgen. Steht ein Leerzeichen zwischen
Makroname und ( wird das Makro als parameterlos angenommen und die Parameterliste einschließlich
der () wird als Programmtext des Makrorumpfes interpretiert.
Beispiel:
#define ADD (a,b)
erg = ADD(2,4);
a + b
/* wird expandiert zu
erg=(a,b) a + b(2,4);
*/
Die Klammern im Programmteil des Makro ADD werden benötigt, damit keine ungewollten Fehler
passieren. Hätte man das Makro folgendermaßen definiert
#define ADD(a,b) a+b , so würde die folgende Makroexpansion zu einem Fehler führen:
erg=5*ADD(3,4);
wird ersetzt durch
erg=5*3+4;
Dies ist ungleich
erg=5*(3+4);
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Info rmatik
A u s g a b e : 24.09.00
Seite 82 v o n 108
4.4.1
Vergleich Unterprogrammtechnik mit Makros
Makros
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
( (a) + (b) )
ADD(5,2);
ADD( 4, 6);
ADD ( 2, 7);
( (5)+(2) );
( (4)+(6) );
( (2)+(7) );
04.03.1998
Unterprogramm
Makro
ADD(a,b)
Technische Informatik
int add(int a, int b)
{ return a+b; }
add(5,2);
add( 4, 6);
add ( 2, 7);
wird ersetzt
durch
Hole Parameter
a
Hole Parameter
b
a+b;
Unterprogramm add
Was geschieht bei einem Unterprogramm ?
Ein Unterprogramm wird zu einem eigenständigen Programmobjekt übersetzt. Dieses Programmobjekt
wird nur einmal in den Speicher geladen, auch wenn das Unterprogramm mehrfach verwendet wird.
Bei einem Unterprogrammaufruf werden die aktuellen Parameterwerte an eine bestimmte Stelle in den
Speicher kopiert. Dann wird das Hauptprogramm verlassen und in das Unterprogramm verzweigt. Das
Unterprogramm holt sich die Parameterwerte aus dem Speicher ( der Ort ist vorgegeben ), arbeitet die
Programmanweisungen ab, bis das Ende des Unterprogramms erreicht ist. Letzte Anweisung eines
Unterprogramm ist ein Rücksprung zum Hauptprogramm, an die Programmanweisung, die dem
Unterprogrammaufruf folgt.
Das Kopieren der Parameterwerte und das Verzweigen ins und vom Unterprogramm benötigt zusätzliche
Rechenzeit.
Was geschieht bei einem Makro ?
Das Makro wird nicht als eigenständiges Programmobjekt übersetzt. Ein Makro entspricht nur einer
Textersetzung. Bei jedem Makroaufruf wird der Programmtext in das Programm eingefügt und erst
danach wird das Programm übersetzt.
Zusammenfassung:
Ein Makro ist schneller, da keine Parameterübergabe und keine Programmverzweigung erfolgt. Bei
mehrfacher Verwendung eines Makros wird jedoch mehr Speicherplatz benötigt, da der Makrorumpf
mehrmals im Speicher steht.
Ein weiterer Unterschied besteht darin, daß bei einem Unterprogramm der Compiler die Anzahl und den
Datentyp der Parameter prüft. Bei einem Makro wird nur die Anzahl überprüft, eine Typprüfung entfällt.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Info rmatik
A u s g a b e : 24.09.00
Seite 83 v o n 108
Beispiel:
Das Makro ADD kann sowohl mit ganzen Zahlen als auch mit reellen Zahlen korrekt verwendet werden.
erg=ADD(2.5,1.5);
wird ersetzt durch
erg=( (2.5) + (1.5 ) );
Bei einem Unterprogramm muß Typ des Ergebnisses und Typ der Parameter angegeben werden.
int add( int a, int b)
{ return a+b; }
Der Aufruf des Unterprogramms führt bei reellen Zahlen zu einem Fehler:
erg=add(2.5, 1.5 );
ergibt als Ergebnis nicht 4.0 sondern die ganze Zahl 3.
Umdefinieren eines vorhanden Makros
Ein einmal definiertes Makro kann mit der Präprozessoranweisung
#undef <Makroname>
als ungültig gekennzeichnet und danach neu vereinbart werden.
Beispiel
#define NEWLINE printf(„\n“);
NEWLINE
/* Erzeugt eine Leerzeile */
#undef NEWLINE
#define NEWLINE printf(„\n\n\n“);
NEWLINE
/* Erzeugt drei Leerzeilen */
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
E
ERGÄNZUNGEN
E.1
Besondere Operatoren
ZU
OPERATOREN
UND
A u s g a b e : 24.09.00
Seite 84 v o n 108
AUSDRÜCKEN
Neben den eingeführten arithmetischen Operatoren stellt C spezielle Operatoren zu Verfügung, die
einerseits Schreibarbeit einsparen andererseit aber auch zur Optimierung des Programms dienen. Im
folgenden werden diese Operatoren beschrieben:
K OMBINATION VON ARITHM. O PERATOREN MIT DER ZUWEISUNG
Abkürzende
Schreibweise
a += b
a -= b
a *= b
a /= b
a %= b
Normale
Schreibweise
a=a+b
a=a-b
a=a*b
a=a/b
a=a%b
Mit der abkürzenden Schreibweise sollte jedoch möglichst Vorsichtig umgegangen werden, da sehr
häufig Fehler passieren.
So bedeutet die abkürzende Schreibweise
in normaler Schreibweise
und nicht
links *= 3 + 4;
links = links * ( 3 + 4 );
links = links * 3 + 4;
ADDITION UND SUBTRAKTION VON 1
Der Sonderfall a = a + 1 wird in der Hardware durch spezielle schnelle Schaltnetze, das
Inkrement ( a++ ) realisiert.
Ebenso wird der Sonderfall a = a - 1 durch das Dekrement ( a - - ) realisiert.
Inkrement und Dekrement sind schneller in der Programmausführung, als die herkömmliche Addition
mit der Konstanten 1.
In C wird Inkrement und Dekrement in zwei unterschiedlichen Versionen zu Verfügung gestellt:
1. In Postfix schreibweise : a++ oder a - -.
In dieser Version wird in einem Ausdruck der Wert von a in eine unsichtbare Variable kopiert.
Der Wert dieser Variablen wird im Ausdruck verwendet und erst später wird die Variable a um
1 erhöht.
2. In Präfix schreibweise : ++a ode - - a
In dieser Version wird a zuerst um 1 erniedrigt, und dann wird der geänderte Wert von a im
Ausdruck verwendet.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 85 v o n 108
Beispiel
int i=1, j=0;
int x;
x= i++ - --j;
Die Berechnung des rechten Ausdrucks ergibt:
i++ :
--j:
der Wert von i, also 1 wird zur Werteberechnung verwendet,
j wird um 1 erniedrigt und der veränderte Wert also -1 zur Wertberechnung des Ausdrucks
verwendet.
Der Wert des rechten Ausdrucks ist damit 1 - -1 also 2. x wird damit zu 2.
Seiteneffekt des Ausdrucks ist die gleichzeitige Änderung der Variablen i und j. Neben der Zuweisung
2 -> x hat nach der Berechnung des Ausdrucks i den Wert 2 und j den Wert -1.
Bei der Verwendung von Inkrement und Dekrement kann durch den Seiteneffekt des Operators in
Ausdrücken Fehler passieren.
Beispiel:
int a, b=5;
a=b * b++;
Je nach Compiler kann der Ausdruck folgendes Ergebnis liefern:
1.
Fall:
Zur Berechnung der rechten Seite wird zuerst der linke Operand und dann erst der rechte Operand
ausgewertet. Also wird das linke b ersetzt mit 5, das rechte b ebenfalls mit 5.
a= 5 * 5 ergibt 25.
a erhält also den wert 25.
Seiteneffekt des Ausdrucks:
b erhält den Wert 6.
2. Fall:
Zur Berechnung der rechten Seite wird zuerst der rechte Operand und dann erst der linke Operand
ausgewertet. Also wird das rechte b ersetzt mit 5, danach wird das Inkrement ausgeführt. Also wird b
um 1 erhöht und erhält den Wert 6. Danach wird das linke b ersetzt mit dem Wert 6.
a = 6 * 5 ergibt 30.
a erhält also den wert 30.
Seiteneffekt des Ausdrucks:
b hat den Wert 6.
Um dieses Problem zu vermeiden sollte man folgende Regel einhalten:
Falls in einem Ausdruck ein Operand mit Seiteneffekt ( Inkrement oder Dekrement )
auftritt, so darf die betroffene Variable nirgendwo sonst im Ausdruck vorkommen.
Mit dieser Regel kann a im obigen Beispiel wir folgt berechnet werden:
a = b * b ;
b++;
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 86 v o n 108
BITOPERATOREN
Ein häufig benutzter Bitoperator ist das Schieben nach rechts oder links.
Mit dem Schiebeoperator kann eine Multiplikation oder Division um eine Zweierpotenz wesentlich
schneller ausgeführt werden.
x
Eine Multiplikation mit 2 entspricht einem schieben um x Positionen nach links ( Operator << ).
Beispiel:
16 * 2
ergibt
32.
Durch schieben der Dualzahl
00010000 um eine Stelle nach links ergibt sich die Zahl
00100000.
Beim schieben nach links wird immer eine 0 an die fehlende Stelle geschrieben.
16 * 2 kann also ersetzt werden durch 16 << 1.
Eine Division durch 2
Beispiel:
x
entspricht einem schieben um x Positionen nach rechts ( Operator >> ).
32 dividiert durch 4 ergibt 8.
Schieben der Dualzahl 00100000
um 2 Stellen nach rechts ergibt
32 / 4 kann damit ersetzt werden durch 32 >> 2.
E.2
00001000 .
Implizite Typwandlung in Ausdrücken
In einer Zuweisung oder bei der Berechnung von Werten in Ausdrücken müssen die Operanden eines
Operators vom gleichen Typs sein.
Will man z.B. in einer Variabel vom Typ int einen Wert speichern, so muß dieser Wert den Typ int
haben.
Addiert man zwei Zahlen so müssen die beiden Zahlen vom gleichen Typ sein. In der
Programmiersprache PASCAL muß diese Regel auch strikt eingehalten werden.
Die Programmiersprache C erlaubt jedoch dem Programmierer die Typen zu mischen, um diese Regel
jedoch dennoch einzuhalten wandelt der Compiler von sich aus die Typen. Diese implizite
Typwandlung erfolgt nach bestimmten Regeln.
1. Regel: Wandlung beim Zuweisungsoperator
Variable = Ausdruck
In einer Zuweisung hat immer die Variable auf der linken Seite den bestimmenden Typ. Der Wert des
Ausdrucks auf der rechten Seite wird daher immer in den Typ der linken Seite gewandelt.
Beispiel:
long L;
int i;
char c;
L = i;
c = i ;
/* implizite Wandlung des Wertes von i in den Typ long */
/* Der wert von i wird automatisch in den Typ char
gewandelt. Ist der Wert von i > +127 entsteht ein Fehler
*/
Programmieren 1
2.
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 87 v o n 108
Regel: In Ausdrücken
In einem Ausdruck müssen die Operanden eines zweistelligen Operators gleichen Typ besitzen. Der
Operand, dessen Typ einen kleineren Wertebeeich besitzt wird immer in den Typ mit dem größeren
Werteber gewandelt.
char, unsigned char, short und unsigned short besitzen einen kleineren Wertebereich als int. int besitzt
einen kleineren Wertebereich als long. Ganze Zahlen besitzen einen kleineren Wertebereich als reelle
Zahlen also werden diese nach float oder double gewandelt.
Beispiele:
double f;
f=10;
/* 10 ist eine Konstante vom Typ int. 10 wird daher in
double gewandelt */
Die Zuweisung ist eigentlich nicht korrekt, da links eine Variable vom Typ double steht, rechts jedoch
ein Wert vom Typ int. C läßt diese Zuweisung jedoch zu und wandelt automatisch den int-Wert 10 in
eine double-Konstante 10.0.
Man sollte solche Konstruktionen jedoch vermeiden.
float f,g;
f + g + 2.5;
int i;
float a;
i=a;
/* 2.5 ist eine Konstante vom Typ double. f und
sind vom Typ float. double hat den höheren
Werteberecih also wird der Wert von f und g nach
double gewandelt */
/* Der Wert von a wird nach int gewandelt. Dies
geschieht durch abschneiden des rationalen Anteils.
Die Variable i enthält nur noch den ganzzahligen
Anteil */
unsigned long int i=2147483615UL;
float a;
a=i;
/* Die ganze Zahl i wird in eine reelle zahl vom
Typ float gewandelt.
Falls der Wert von i größer
ist als die maximale Genauigkeit einer float-Zahl (
7 Stellen Genauigkeit ) entsteht ein Fehler */
float Zahlen sind nur auf 7 Stellen genau.
Druckt man den Wert von a am Bildschirm aus erhält man 2147483648.0. Die Zahl ist daher falsch.
Programmieren 1
5
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 88 v o n 108
Test von Programmen
Bei der Entwicklung von Programmen können folgende Arten von Fehlern auftreten:
1.
Logische Fehler ( Denkfehler )
Hier arbeitet der entworfenen Algorithmus fehlerhaft. Das Programm produziert dadurch
falsche Ergebnisse oder läuft in eine Endlosschleife.
Diese Fehler werden erst zur Laufzeit des Programmes entdeckt.
2.
Syntaktische Fehler
Es werden unzulässige C Sprachelement verwendet z.B. fehlerhafte Schlüsselworte WHILE
anstatt while, Trennzeichen ; am Ende einer Anweisung wird vergessen, nach einer
öffnenden Klammer wird die schließende Klammer vergessen. Verwendung von
Programmobjekten, die nicht deklariert wurden uva.
Diese Fehler werden vom Compiler entdeckt und angezeigt. Das Programm wird nicht
übersetzt.
3.
Laufzeitfehler
Diese Fehler treten nach dem Programmstart zur Laufzeit auf.
Dazu gehören Fehler, die eine Programmunterbrechung bewirken wie z.B. Division durch 0,
Zugriff auf geschützte Speicherbereiche und Programmierfehler wie z.B., Überschreiten von
Zahlenbereichen ( Zahlenüberlauf ), Überschreiten oder Unterschreiten von array-Grenzen
z.B. Zugriff auf das 11. Array-Element in einem array definiert mit 10 Werten.
Programmunterbrechungen werden vom Betriebssystem ausgelöst und bewirken eine
Fehlermeldung am Bildschirm mit sofortigem Programmabbruch.
Programmierfehler führen analog den logischen Fehlern zu unvorhersehbaren
Programmergebnissen wie fehlerhafte Werte, Endlosschleifen, oder sonstigen nicht gewollten
Reaktionen des Programms.
Zur Sicherstellung korrekter Programme muß der Entwickler seine Programme auf Korrektheit prüfen.
Dazu sind für das Programm Testmuster anzugeben, die festlegen welche Ergebnisse das Programm
für bestimmte Eingaben produzieren muß.
Hierzu Testmuster für folgende Fälle anzugeben:
1.
2.
3.
Für korrekte Eingabewerte müssen richtige Ergebniswerte produziert werden
Auf fehlerhafte Eingabewerte darf das Programm nicht oder nur mit entsprechender
Fehlermeldung reagieren
Alle Sonderfälle müssen korrekt abgearbeitet werden.
Beispiel: Umrechnung einer Dezimalzahl in eine 16-Bit-Dualzahl
Es können folgende Testmuster angegeben werden:
û
16
Korrekte Eingabewerte sind alle ganzen Zahlen zwischen 0 und 2 -1.
Als Testmuster wird gewählt:
eine gerade Zahl
Eine ungerade Zahl:
Eine Zweierpotenz:
Eine sonstige Zahl:
6
7
1024
150
Programmergebnis:
0000 0000 0000 0110
0000 0000 0000 0111
0000 0010 0000 0000
0000 0000 1001 0110
Programmieren 1
û
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
Seite 89 v o n 108
Fehlerhafte Eingabewerte sind:
Negative Zahlen
-1
16
Zahlen mit Wert größer 2 -1
68500
Reelle Zahlen
1.25
Sonstige Eingabezeichen,
die keine ganzen Zahlen darstellen a$23
û
A u s g a b e : 24.09.00
Fehlermeldung: Zahl ist negativ
Fehlermeldung: Zahl zu groß
Fehlermeldung: keine ganze Zahl
Fehlermeldung: keine ganze Zahl
16
Sonderfälle, die beiden Grenzwerte, also die Zahl 0 und die Zahl 2 -1=65535
0
65535
0000 0000 0000 0000
1111 1111 1111 1111
Reagiert das Programm nicht wie erwartet auf die Testmuster, so ist das Programm fehlerhaft. Durch
eingrenzen des Fehlerortes muß dann im Programm der oder die Fehler lokalisiert werden.
Dazu hat der Programmierer zwei grundsätzliche Möglichkeiten:
1.
Fehlereingrenzung durch die klassische Methode
In das Programm werden Ausgabe-Anweisungen eingebaut, mit deren Hilfe man den Kontrollfluß
und die Variablenwerte beobachten kann.
2.
Fehlerlokalisierung mit einem Testhilfeprogramm ( Quellcode-Debugger )
Das Programm wird unter Kontrolle eines Testhilfeprogramms gestartet. Diese Systemprogramm
überwacht den Ablauf und die Variablenwerte des Programms.
Beispiel:
Gegeben ist folgendes fehlerhafte Programm. Durch einen Programmfehler wird die do..while-Schleife
niemals beendet, es entsteht eine Endlosschleife.
int main()
{
int i, summe;
summe=0;
i=1;
do
{
summe=summe + i;
i=i+2;
}
while ( i != 20 );
printf("die summe ist: %i\n", summe);
return 0;
}
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 90 v o n 108
Programmtest nach der klassischen Methode
In das Programm wird eine Ausgabeanweisung eingebaut, so daß die Anzahl der Schleifendurchläufe
und die Schleifenabbruch-Variable i beobachtet werde kann.
int main()
{
int i, summe;
summe=0;
i=1;
do
{
summe=summe + i;
i=i+2;
printf(„ i = %i\n“,i);
/*
Die
Werte
von
i
werden
in
jedem
Schleifendurchlauf am Bildschirm angezeigt.
So kann festgestellt werden daß i immer einen
Wert ungleich 20 besitzt und damit die Schleife
niemals endet. */
}
while ( i != 20 );
printf("die summe ist: %i\n", summe);
return 0;
}
Testen des Programms mit einem Testhilfeprogramm
( Debugger )
Der Debugger ist ein Systemprogramm, der die Syntax der Programmiersprache kennt und unter
dessen Kontrolle ein Programm abgearbeitet wird. Gestartet wird das Programm durch den Debugger,
nicht direkt durch das Betriebssystem. Der Debugger überwacht den Programmablauf und die
Variablenwerte und erlaubt folgende Programmabläufe:
1.
2.
3.
4.
Starten des Programms und abarbeiten bis zu einem gesetzten Haltepunkt ( Breakpoint )
Schrittweises abarbeiten jeweils einer Programmzeile angestoßen durch einen Tastendruck
Anzeigen und Verändern von Variablenwerten ( im angehaltenen Zustand ).
Weiterbearbeitung des Programms nach einem Haltepunkt.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 91 v o n 108
6 Rekursive Algorithmen
Folgendes Bild veranschaulicht den Begriff Rekursion.
Rekursion im Bild
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
04.03.1998
Ein rekursiver Algorithmus ist ein Algorithmus, der sich selbst verwendet.
Rekursion findet man auch in der Mathematik bei der Definition von Funktionen und Folgen. Die
einfachste rekursive Funktion ist die Fakultät. Ein andere etwas komplexere Rekursion ist die Folge der
Fibonacci-Zahlen.
Man kann beweisen, daß jede Rekursion durch Iteration realisierbar ist. Ein rekursiver Algorithmus kann
also immer mit Hilfe von Schleifen ( Iteration ) formuliert werden. Häufig ist jedoch der rekursive Ansatz
die einfachere oder auch bessere Lösung.
Algorithmen zum Sortieren und Suchen beruhten i.a. auf dem Prinzip „man durchlaufe eine Liste bis man
die Liste vollständig bearbeitet oder das gesuchte Element gefunden hat“. Dieses Prinzip verwendet als
Kontrollstruktur die Schleife, auch Iteration genannt. Sortieren und Suchen kann jedoch auch rekursiv
erfolgen. So gibt es den Sortieralgorithmus Quicksort, der auf der Rekursion basiert, und als schnellster
Algorithmus zum sortieren von linearen Listen gilt. Suchalgorithmen, die auf rekursiven Datenstrukturen,
den Bäumen basieren, sind ebenfalls die effektivsten.
6.1
REKURSION
FAKULTÄT
VERSUS
ITERATION
AM
BEISPIEL
DER
Mit der Unterprogrammtechnik ergibt sich die Möglichkeit Algorithmen nicht mit Hilfe einer Schleife, also
als Iteration zu realisieren, sondern durch die rekursive Verwendung eines Unterprogramms. Rekursion
heißt in diesem Fall ein Unterprogramm ruft zur Lösung des Problems ich selbst wieder auf.
Die Funktion Fakultät ist auf folgende zwei Arten definiert:
Iterative Definition
n ! = 1 * 2 * 3 * .....* n
Rekursive Definition
n ! = ( n-1) !
0 ! = 1
* n
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 92 v o n 108
Zur iterativen Berechnung der Fakultät in einem C Programm benutzt man eine Zählschleife. Die CFunktion sieht dann so aus:
/* ---- Definition der iterativen Funktion Fakultät(n) ----- */
unsigned int
{
fac( unsigned int n )
unsigned int i, fakultaet=1;
if ( n == 0 ) fakultaet=1;
else
for ( i=1; i<=n; i++ ) fakultaet=fakultaet*i;
return fakultaet;
}
Bei der rekursiven Berechnung der Fakultät wird eine Funktion fac() definiert, die sich selbst aufruft.
/* ---- Deklaration der rekursiven Funktion Fakultät(n) ----- */
unsigned int
{
fac(unsigned int n )
unsigned int fakultaet;
if (n==0) fakultaet=1;
else
fakultaet=n*fac(n-1);
return fakultaet;
}
Duch die Verwendung der Funktion fac bei der Definition von fac könnte man meinen, eine endlose Folge
von Funktionsaufrufen zu produzieren. Dies ist jedoch nicht der Fall. Jeder Funktionsaufruf führt zu einer
Vereinfachung ( von n nach n-1 bis zum einfachsten Wert 0 ). Die Rekursion endet beim einfachsten Fall,
dies ist 0 ! . Zur Berechnung von 0 ! wird kein weiterer Funktionsaufruf mehr benötigt, da der Wert per
Definition bekannt ist. Im Programm ist das Ende der Rekursion erreicht, wenn man in die
Fallunterscheidung mit n==0 eintritt. Dieser Programmteil verwendet nicht mehr die Funktion fac().
Wie funktioniert die Rekursion ?
Es soll z.B. der Wert von 4! berechnet werden.
Das Hauptprogramm ruft daher die Funktion fac(4) auf.
fac(4) = 4 * fac(3)
Also muß fac(3) aufgerufen werden bevor man fac(4) berechnen kann.
fac(3) = 3 * fac(2 )
fac(2) = 2 * fac(1)
fac(1) = 1 * fac(0) Beim Aufruf von fac(0) tritt man in die
Fallunterscheidung ein.
fac(0) erhält damit den Wert 1.
Damit kann man jetzt auch fac(1) berechnen.
fac(1) = 1 * 1 = 1
fac(2) = 2 * 1 = 2
fac(3) = 3 * 2 = 6
fac(4) = 4 * 6 = 24.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 93 v o n 108
Wird ein Unterprogramm aufgerufen, dann werden die Parameter im Speicher in einer bestimmten Art
und Weise dem Unterprogramm zu Verfügung gestellt. Da zur Ermöglichung der Rekursion die Aufrufe
geschachtelt werden müssen und die Anzahl der geschachtelten Aufrufe erst zur Laufzeit bekannt sind,
werden alle Parameter und lokale Variablen eines Unterprogramms auf einer dynamisch zu Laufzeit
veränderbaren Datenstruktur abgelegt. Diese Datenstruktur ist der STACK.
Ein Stack ist ein Stapel von Werten. Die Höhe des Stapels kann sich zur Laufzeit dynamisch erhöhen
oder erniedrigen.
Push
legt ein
Element oben
auf den Stapel
Pop
holt oberstes
Element vom
Stapel herunter
Boden
Der Stapel hat einen Boden. Auf diesen Boden werden die Werte nacheinander immer von oben auf den
Stack gelegt. Jeder neue Wert wird daher auf den vorherigen Wert gelegt. Diesen Vorgang nennt man
PUSH. Der letzte Wert liegt dann oben auf dem Stapel. Holt man die Werte wieder herunter, wird der
Wert an oberster Stelle genommen. Diesen Vorgang nennt man pop. Wird der letzte Wert geholt ist der
Stackboden erreicht. Der Stack ist dann leer. Ein Beispiel für einen Stack findet man z.B. im China
Restaurant. Dort gibt es ein Gerät „Tellerwärmer“. Saubere Teller werden nacheinander oben in das
Gerät gelegt. Der Boden sinkt damit nach unten ab und der letzte Teller liegt oben auf dem Stapel.
Benötigt man für einen Gast einen Teller wird der oberste Teller herausgenommen. Eine solche
Datenstruktur bezeichnet man auch als LIFO ( Last In First Out ). Der Wert der zuletzt auf den Stapel
gelegt wurde, wird als erster wieder herunter genommen.
Die Parameter eines Unterprogramms werden vom Hauptprogramm nacheinander auf den Stack gelegt (
der Stack wird aufgebaut ). In C wird die Parameterliste von rechts nach links bearbeitet. So liegt der
erste Parameter oben auf dem Stack.. Das Unterprogramm verwendet die Parameter auf dem Stack. Vor
dem Verlassen des Unterprogramms werden die Parameter wieder vom Stack genommen ( der Stack
wird abgebaut ). Auch lokale Variablen, die im Unterprogramm definiert sind, werden vom
Unterprogramm auf den Stack gelegt und vor Verlassen des Unterprogramms wieder
heruntergenommen. Lokale Variablen eines Unterprogramms sind damit nur für die Zeit auf dem Stack
vorhanden, in der das Unterprogramm abgearbeitet wird. Liefert das Unterprogramm im falle einer
Funktion einen Wert an das Hauptprogramm zurück wird auch dieser Wert über den Stack übergeben.
Nachdem die lokalen Variablen und die Unterprogrammparameter vom Stack genommen sind, wird der
errechnete Ergebniswert auf den Stack gelegt. Das Hauptprogramm nimmt diesen Wert dann wieder vom
Stack herunter.
Bei einer Rekursion wird daher der Stack langsam aufgebaut bis das Rekursionsende erreicht ist und
danach wird der Stack zur Werteberechnung wieder abgebaut. Im Falle der Fakultät sieht der Stack dann
folgendermaßen aus:
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 94 v o n 108
Aufbau und Abbau des Stack bei Rekursion
Prof. Dr. - Ing. S.Keller
FH Ravensburg-Weingarten
Technische Informatik
05.03.1998
Rekursionsende ist erreicht
Stackabbau
Stackaufbau
4
Aufruf
von fac(4)
3
4
Aufruf
fac(3)
2
3
4
Aufruf
fac(2)
1
2
3
4
Aufruf
fac(1)
0
1
2
3
4
0!=1
1
2
3
4
1*1=1
2
3
4
2*1=2
3
4
3*2=6
4
4*6=24
Aufruf Ergebnis Ergebnis Ergebnis Ergebnis
fac(0)
von
von
von
von
fac(0)
fac(1)
fac(2)
fac(3)
Ergebnis
von
fac(4)
Wann verwendet man die Rekursion ?
8 Falls eine rekursive Definitionen einer Funktion vorliegt, dann ist die Umsetzung in ein
Programm trivial.
8 Es gibt rekursiv definierte Datenstrukturen, die Bäume, die sich über rekursive Algorithmen
einfach realisieren lassen
8 Es gibt Probleme, deren Lösung sich einfacher rekursiv anbieten. Ein Beispiel dafür ist das
Problem der Türme von Hanoi
Programmieren 1
6.2
Prof. Dr.-Ing. Silvia Keller
DIE T
ÜRME VON
H
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 95 v o n 108
ANOI
Es gibt eine Legende, die besagt, daß Mönche in Hanoi die Aufgabe hatten 64 Scheiben
unterschiedlicher Größe von einem Stab A auf einen Stab C unter Verwendung eines dritten Stabes C zu
legen.
Versetzte Turm mit n Scheiben
von A nach C
Stab A
Stab B
Stab
Bei der Lösung des Problems sollten folgende Randbedingungen eingehalten werden:
8 Es darf immer nur eine Scheibe versetzt werden
8 Es darf niemals eine größere Scheibe auf einer kleineren Scheibe liegen
Lösungsansatz:
8 Probiere eine Lösung zu finden mit n = 1
8 Löse das Problem für den Fall n = 2
8 Löse das Problem für n Scheiben, indem man die Lösung zurückführt auf die Lösung mit
kleinerem n. Der einfachste Fall ist der Fall mit n = 1
Dieses Vorgehen nennt man vollständige Induktion, und wird in der Mathematik zum beweisen von
Sätzen benutzt.
Lösung für n=1
Versetzte von
A nach C
Stab A
Stab B
Stab C
Die Lösung ist trivial. Man versetzt die Scheibe von A nach C ohne Verwendung von B.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
Lösung für n=2
Stab A
Stab B
Stab C
Versetzte
oberste Scheibe
von A nac B
Stab A
Stab B
Stab
Versetzte
unterste Scheibe
von A nach C
Stab A
Stab B
Stab C
Versetzte
Scheibe von
B nach C
Stab A
Stab B
Stab C
A u s g a b e : 24.09.00
Seite 96 v o n 108
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 97 v o n 108
Lösung für n=3
Versetzte n-1
Scheiben
von A nach B
Versetzte n-1
Scheiben
von B nach C
N-te Scheibe
Stab A
Stab B
Stab C
versetzte n-1 Scheibe von A nach B
( n-1 ) = 2 Scheiben Start ist A und Ziel ist B
versetzte Scheibe von A nach C, versetzte Scheibe von A nach B, versetzte Scheibe von C nach
B
versetzte n-te Scheibe von A nach C
versetzte n-1 Scheiben von B nach C
( n-1 ) = 2 Scheiben , Start ist B und Ziel ist C
versetzte Scheibe von B nach A, versetzte Scheibe von B nach C,
versetzte Scheibe von A
nach C
Struktogramm zum Algorithmus Türme von Hanoi
Algorithmus
Türme von Hanoi
Versetzt n Scheiben von Stab Start nach Stab Ziel über den Stab Lager
Objekte:
n Anzahl der Scheiben
N gleich 1 ?
Ja
Versetzte Scheibe von Start nach Ziel
Nein
Versetzte n-1 Scheiben von Start nach
Lager. Benutze Ziel als Zwischenlager
Versetzte Scheibe von Start nach Ziel
Versetze n-1 Scheiben von Lager nach
Ziel. Benutze Start als Zwischenlager
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 98 v o n 108
Realisierung des Algorithmus als C Programm
#include <stdio.h>
void TuermeVonHanoi(void);
void VersetzeScheibe( int n ,char start,char ziel,char lager);
int main()
{ TuermeVonHanoi();
return 0;
}
void TuermeVonHanoi(void)
{
int
n;
char
start,ziel,lager;
start='A';
ziel= 'C';
lager='B';
printf(„Bitte geben Sie eine natürliche Zahl ein: „);
scanf(„%i“,&n);
printf(„Das Programm versetzt %i Scheiben von %c nac %c\n“,n,start,ziel);
VersetzeScheibe(n,start,ziel,lager);
}
void VersetzeScheibe( int n ,char start,char ziel,char lager)
{
if (n==1)
printf(„Versetze Scheibe von %c nach %c\n“,start,ziel );
else
{
VersetzeScheibe((n-1),start,lager,ziel);
printf(„Versetze Scheibe von %c nac %c\n“,start,ziel );
VersetzeScheibe((n-1),lager,ziel,start);
}
}
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 99 v o n 108
Aufwandsabschätzung
Anzahl der
Scheiben
1
2
3
4
5
n
Anzahl der Versetzungen
1
1 vgl. 2 =2
2
3 vgl. 2 = 4
3
2*3 + 1 = 7 vgl. 2 = 8
4
2 * ( ( 2*3) +1 ) + 1 = 15 vgl. 2 = 16
5
2 * ( (2*7) + 1) +1 = 31 vgl. 2 = 32
n
ca. 2
64
Bei 64 Scheiben müssten die Mönche daher 2 Scheiben versetzen. Wenn das versetzen einer Scheibe
1 Sekunde braucht, dann sind das 590 Milliarden Jahre. Für die Mönche also eine unlösbare Aufgabe.
5.3
REKURSIVES SORTIEREN
Das schnellste Verfahren zum sortieren einer Liste von Zahlen hat C.A.R. Hoare erfunden. Der
Algorithmus beruht auf dem Prinzip Zerlegen und Mischen und heißt Quicksort.
Die Idee
Gegeben ist eine Liste von unsortierten Zahlen.
44
-------->
55
12
Durchsuchen
von links
42
94
Elemen
tx
6
18
67
<----Durchsuchen
von
rechts
Es wird ein Element x in der Mitte der Liste als Vergleichselement gewählt. Dieses Vergleichselement
trennt die Liste in zwei Teile. Eine Liste rechts davon und eine Liste links davon. Man durchsucht die linke
Hälfte von links nach rechts und die rechte Hälfte von rechts nach links. Falls eine Zahl L in der linken
Teilhälfte größer dem Vergleichselement x und eine Zahl R in der rechten Teilhälfte kleiner dem
Vergleichselement x ist, so werden die beiden Zahlen L und R miteinander vertauscht. Linke und rechte
Teilhälfte werden solange durchsucht und gefundene Zahlen miteinander vertauscht bis sich linker Index
und rechter Index überholen, d.h. beide Teilhälften durchsucht worden sind. Danach ist die ursprüngliche
Liste in zwei Hälften zerlegt. Die linke Liste enthält Zahlen kleiner dem Vergleichselement, die rechte
Liste Zahlen größer dem Vergleichselement.
Das Verfahren an obigem Beispiel gezeigt:
Linke Position
Links =1
Rechte Position
Rechts = 8
Vergleichselement ist Liste[4] = 42
Durchsuche linken Teil von links
Liste[Links] = Liste[1] = 44.
44 ist größer als 42. Also merke Position Links zum tauschen
Durchsuche rechten Teil von rechts
Liste[Rechts] = Liste[8] = 67. 67 ist größer als 42. Also ist die nächste Position Rechts=Rechts - 1 = 7
Liste[Rechts] = Liste[7] =18. 18 ist kleiner als 42. Also merke diese Position zum tauschen
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 100 v o n 108
Falls links <= rechts ( die beiden Positionen haben sich noch nicht überholt ),
dann tausche die beiden Zahlen und rücke Links und Rechts um eine Position weiter.
Hier wird also 44 mit 18 vertauscht. Links = Links +1 = 2 und Rechts = Rechts -1 = 6
18
55
12
42
94
6
44
67
Durchsuche von links
Liste[Links]=Liste[2]=55. 55 ist größer 42. Also merke diese Position zum tauschen
Durchsuche von rechts
Liste[rechts]=Liste[6]=6. 6 ist kleiner als 42. Also merke diese Position zum tauschen.
Falls links <= rechts, dann tausche die beiden Zahlen und rücke links und rechts um eine Position
weiter.
Hier wird also 55 mit 6 vertauscht. Links = Links +1 = 3 und Rechts = Rechts -1 = 5
18 6
12 42 94 55 44 67
Durchsuche von links
Liste[Links]=Liste[3]=12. 12 ist kleiner als 42. Links = Links +1 = 4
Liste[Links]=Liste[4]=42. 42 ist nicht größer als 42. Merke diese Position zum tauschen
Durchsuche von rechts
Liste[rechts]=Liste[5]=94. 94 ist größer als 42. Rechts=Rechts -1 = 4.
Liste[rechts]=Liste[4]=42. 42 ist nicht kleiner als 42. Merke diese Position zum tauschen
Falls links <= rechts, dann tausche die beiden Zahlen und rücke links und rechts um eine Position
weiter.
Hier wird also 42 mit 42 vertauscht. Links = Links +1 = 5 und rechts = rechts -1 =3
Die beiden Positionen haben sich jetzt überholt, also endet das durchsuchen und tauschen.
18 6
12 42 94 55 44 67
| wende hier
|
| wende Quicksort
| Quicksort an |
|
an
|
|
Die Liste ist jetzt in zwei Teile zerlegt. Der linke Teil enthält Zahlen kleiner 42. Der rechte Teil Zahlen
größer als 42. Die Liste ist jedoch noch nicht sortiert.
Möchte man die gesamte Liste sortieren, dann braucht man das Verfahren nur noch auf die linke
Teilhälfte und die rechte Teilhälfte anzuwenden. Dadurch erhält man eine rekursive Lösung. Die
Rekursion endet, wenn die zu sortierende Liste nur noch ein Element enthält.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 101 v o n 108
C-Programm für Quicksort
#include <stdio.h>
void quicksort(int anfang,int ende, int liste[] )
{
int startwert, links, rechts, hilf ;
links
= anfang;
rechts = ende;
startwert = liste[(anfang+ende) / 2];
do
{
/* Durchlaufen von
while ( liste[links] < startwert )
*/
/* links nach rechts */
links = links + 1;
while ( liste[rechts] > startwert )
rechts = rechts - 1 ;
/* Durchlaufen von
*/
/* rechts nach links
*/
if ( links <= rechts )
/* Tauschen falls noch nicht */
{
/* ueberholt
*/
hilf = liste[links];
liste[links]= liste[rechts];
liste[rechts]=hilf;
links = links + 1;
/* Links und rechts um eine
*/
rechts = rechts - 1;
/* Position vorschalten
*/
}
}
while ( links <= rechts) ;
/* Abbruch falls links und
*/
/* rechts •berholt
*/
if (rechts > anfang) quicksort(anfang,rechts,liste);
/* Qsort linker Bereich */
if (links < ende)
quicksort(links,ende,liste);
/* Qsort rechter Bereich */
}
int main ()
{
int i,liste[8]={44,55,12,42,94,6,18,67 };
for ( i=0; i < 8; i++)
/* Liste von Zahlen */
printf(" %i ",liste[i]);
printf("\n");
quicksort(0,7,liste);
for ( i=0; i < 8; i++)
return 0;
}
printf(" %i ",liste[i]);
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 102 v o n 108
7 Sortieren und Suchen nach dem Hash-Verfahren
DAS PROBLEM
Gegeben ist eine Liste von Namen. Diese Namen sind mit einem Verfahren so zu
sortieren, dass ein Name k mit dem gleichen Verfahren gesucht werden kann
D IE I D E E:
7.1
SORTIEREN
MIT
HA
S H-
F
UNKTIONEN
Man speichert die Namen in einer Liste mit M Elementen, also ein array, in dem M Werte Platz finden.
Liste mit M Werten
0
Name k
HashFunktion
Position i
M-1
Die Position eines zu sortierenden Namen k in der Liste berechnet man über eine Funktion, HashFunktion genannt.
Der in der Liste einzutragenden Name k ist Eingabeparameter der Hashfunktion H. Als Ergebnis liefert
die Hash-Funktion die Position i im array, in der der Name zu speichern ist. Die gleiche Funktion H wird
benutzt, um die Position eines gesuchten Namen f in der Liste zu bestimmen. Der gesuchte Name f ist
dann ebenfalls Eingabeparameter der Hash-Funktion H. Ergebnis von H ist die Position in der Liste, an
der der gesuchte Name f steht.
Beispiel:
Die Liste enthält 10 Datenelemente, also ist
M=10.
Zu sortieren sind die Namen:
Keller, Hulin, Ertel, Gampp
Hash-Funktion H(k)
ist die Ordnungsnummer des ersten Buchstabens
des Namen k modulo M
è
Die Hash-Funktion muss immer einen Wert mit
dem Modulo-Operator berechnen, damit die
berechnete Position i immer innerhalb des array
liegt.
Zum Einfügen der Namen in die richtige Position wird die Hash-Funktion H(k) wie folgt auf die sortierende
Namen angewendet:
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 103 v o n 108
Liste mit 10 Werten
0
Gampp
H(Keller)= 75 modulo 10 = 5
H(Hulin) = 72 modulo 10 = 2
Hulin
H(Ertel) = 69 modulo 10 = 9
H(Gampp) = 71 modulo 10 = 1
Keller
9
7.2
Ertel
SUCHEN
Bei der Suche nach einem Namen wird ebenfalls die Hash-Funktion verwendet. Ein Suche nach dem
Namen Gampp ergibt:
H(Gampp) = 71 modulo 10 = 1
An dieser Position steht tatsächlich auch der Name Gampp.
Eine Suche nach dem Namen „Brümmer“ ergibt:
H(Brümmer) = 66 modulo 10 = 6
An dieser Position ist kein Name gespeichert
Ein Vergleich des gesuchten Namens „Brümmer“ mit dem Eintrag in der Liste ergibt jedoch – die Position
ist leer. Der Name „Brümmer“ wurde nicht in die Liste eingetragen.
7.3
KOLLISSIONEN
Es soll ein weiterer Name „Koch“ in die Liste eingetragen werden.
Die Berechnung der Hash-Funktion ergibt:
H(Koch) = 75 modulo 10 = 5
An Position 5 ist jedoch schon der Name „Keller“ eingetragen. Die Berechnung des Namens „Koch“ führte
zu einer Kollission.
Wie kann dieses Problem gelöst werden ?
Idee
Zur Lösung der Kollission wird versucht für den Namen „Koch“ eine Ausweichposition zu bestimmen.
Wenn also Position 5 schon mit dem Namen „Keller“ belegt ist, dann versuchen wir den Namen an die
folgende Position, also 6, zu schreiben. In unserem Fall ist die Position 6 frei und damit eine Lösung des
Problems. „Koch“ wird also an die Position H(Koch ) + 1 eingetragen.
Wäre diese Position jedoch auch belegt, versucht man die nächste folgende Position also H(Koch ) + 2
usw.
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 104 v o n 108
Diese Strategie eine Ausweichposition zu finden wird solange angewendet bis entweder ein freier Platz
zur Speicherung des Namens gefunden ist oder aber die Liste vollständig ohne Resultat nach freien
Positionen durchsucht wurde.
Zur Lösung der Kollission wird also eine weitere Funktion angewendet, Kollossions-Funktion genannt.
Die oben beschriebene Strategie für die Kollssionsbehandlung wird lineares Sondieren genannt und
versucht ausgehend von der berechneten Hash-Position H(Koch ) alle folgenden Positionen in der Liste:
(H(k) + i ) modulo M
1 <= i < M
Bei der Suche nach dem Namen „Koch“ liefert die Hash-Funktion die Position 5. Da bei jeder durch die
Hash-Funktion errechneten Position eine Kollission möglich war, muss ein Vergleich des gesuchten
Namens mit dem Namen an der errechneten Position erfolgen. Steht der gesuchte Namen nicht an der
errechneten Position, wurde der gesuchte Name, wenn dieser überhaupt in der Liste vorkommt, über
eine Kollissionsbehandlung an eine Ausweichposition geschrieben. Also muss auch beim Suchen alle
Ausweichpositionen nach dem Namen durchsucht werden. Die Suche endet dann, wenn entweder der
gesuchte Name, hier „Koch“, in der Liste gefunden werden konnte, oder aber die errechnete Position frei
ist. Im letzten Fall kommt der gesuchte Name nicht in der Liste vor.
Im konkreten Beispiel wird zuerst an der errechneten Hash-Position 5 nach Koch gesucht, dann die
Ausweichposition 6 berechnet. Hier endte die Suche, da der Eintrag Koch gefunden wurde.
7.4 LÖSCHEN
Im obigen Beispiel soll nach Einfügen von „Koch“ der Name „Keller“ aus der Liste gelöscht werden. Die
Liste würde nach einem Löschvorgang folgendes Aussehen haben:
Liste mit 10 Werten
0
Gampp
Hulin
Koch
9
Die Position 5 ist nach dem Löschen frei. Wollen wir jetzt nach dem
Namen „Koch“ suchen berechnet die Hash-Funktion die Position 5. An der
Position 5 steht kein Name mehr, also endet hier die Suche mit dem
Ergebnis: Der Name „Koch“ kommt in der Liste nicht vor. Dies ist
aber nicht richtig, da Koch an der Position 6 steht.
Ertel
Das Problem
besteht darin, dass Koch vorher über eine Kollission in die Liste eingetragen wurde. Nach dem Löschen
von Keller tritt keine Kollssion mehr auf.
Wie kann man dieses Problem lösen ?
In der Liste wird eine gelöschte Position durch den Eintrag „Hier wurde was gelöscht“ gekennzeichnet.
Nach der Berechnung der Hash-Funktion
H(Koch) = 5
findet man den Eintrag „ Hier wurde was gelöscht“, d.h. „Koch“ könnte durch eine Kollission an einer
anderen Position stehen. Also muss die Kollissionsbehandlung angewendet werden. Entweder man
findet über die Kollissionsbehandlung den Namen „Koch“ ( in diesem Beispiel an der Position 6 ) oder
aber man trifft auf eine freie Position. Im ersten Fall ist „Koch“ gefunden im zweiten Fall kommt der Name
„Koch“ in der Liste tatsächlich nicht vor.
Programmieren 1
7.5
Prof. Dr.-Ing. Silvia Keller
EIGENSCHAFTEN
Studiengang Angewandte Informatik
EINER
HA
S H-
A u s g a b e : 24.09.00
Seite 105 v o n 108
FUNKTION
Das Hashverfahren arbeitet sehr schnell , wenn
1. Die Hash-Funktion sehr einfach zu berechnen ist
2. als Ergebnis der Hash-Funktion alle möglichen Positionen von 0 bis M-1 in der Liste möglich
sind, d.h. die Hash-Funktion ist surjektiv
3. die Hash-Funktion die Dateneelemente in der Liste gut streut
4. möglichst wenig Kollssionen auftreten
7.5.1
Beispiel für übliche Hash-Funktionen
Die zu sortierenden Werte werden im Folgenden als Schlüssel bezeichnet. Ein Schlüssel kann entweder
eine Zahl oder eine Zeichenfolge sein.
û
Sortiert werden soll eine Zeichenkette mit Anzahl k Zeichen z.B. es sollen Namen sortiert werden
k
H ( Schlüssel ) = ∑ Ordnungsza hl ( Zeicheni ) modulo M
i =1
û
Es sollen natürliche Zahlen sortiert werden ( Divisionsmethode )
H(Schlüssel) = Zahl modulo M
û
Es sollen natürliche Zahlen sortiert werden. Die Zahlen werden als Folge von Ziffern znz n-1...z 1
interpretiert ( Mittel-Quadrat-Methode )
H ( Schlüssel ) berechnet sich wie folgt:
•
•
7.5.2
Bilde das Quadrat der Zahl. Der berechnete Wert ist neue Ziffernfolge mit einer Anzahl von
Ziffern grösser oder gleich n
Entnehme den mitteleren Block von n Ziffern. Diese Ziffern ergeben die Position in der Liste.
Beispiele für Kollissions-Funktionen
Die zu sortierenden Werte werden im Folgenden als Schlüssel bezeichnet. Ein Schlüssel kann entweder
eine Zahl oder eine Zeichenfolge sein.
Lineares Sondieren
Ausweichpos i( Schlüssel ) = ( H(Schlüssel) + i ) modulo M,
1 <= i <= M-1
Quadratisches Sondieren
2
Ausweichpos i(Schlüssel ) = ( H(Schlüssel ) +(-) i ) modulo M,
1 <= i <= M-1
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 106 v o n 108
Literaturverzeichnis
Darnell/Margolis:
C – A software Engineering Approach (ANSI C), Springer
DIN 66 261:
Sinnbilder für Struktogramme nach Nassi-Shneidermann, BeuthVerlag, Berlin, 1985
Goll/Güner/Wiese:
C als erste Programmiersprache, ISO-Standard, Teubner, 1999
Güting R. H.:
Datenstrukturen und Algorithmen, Teubner, 1992
Hoare, C.A.R.:
Quicksort, Computer Journal No. 1, 1962
Kernighan B.W./Ritchie D.M.:
Programmieren in C, 2. Auflage, Hanser, München, 1990
Knuth, D.E.
The Art of Computer Programming Vol.1 – Vol.3, Addison
Wesley
Kurt Mehlhorn
Datenstrukturen und effiziente Algorithmen
Lowes/Paulik :
Programmieren mit C – ANSI Standard -, Teubner, 1992
Regionales Rechenzentrum
Niedersachsen / Uni Hannover
(RRZN) *
T. Pratt, M. Zelkowitz
C
Wirth, N::
Algorithmen und Datenstrukturen, Teubner, 1983
Programmiersprachen, Design und Implementierung, Prentice
Hall, 1997
*: Dieses Nachschlageheft kann beim ASTA / Lehrmittelreferat für Preise unter 10.- DM erworben
werden. Weitere Themenhefte: C++, JAVA, UNIX, Windows NT ......
Programmieren 1
Prof. Dr.-Ing. Silvia Keller
A
Anhang
A1
RANGFOLGE
VON
Studiengang Angewandte Informatik
Operatoren
()
[]
->
.
2
4
5
6
!
~
++
-sizeof
+
(Typname)
*
&
* /
%
+ << >>
> >= <
7
==
8
9
10
11
12
13
14
15
&
^
|
&&
||
?:
=
+= -= *= /=
%= &= ^= |=
<<= >>=
,
16
A2
Seite 107 v o n 108
OPERATOREN
Priorität
1
3
A u s g a b e : 24.09.00
Assoziativität
Funktionsaufruf
Links -> Rechts
Arrayzugriff
Zugriff auf Komponenten
einer Struktur
Logisch NICHT
Rechts -> Links
Komplement
Inkrement, Dekrement
<=
!=
Vorzeichen
Explizite Typwandlung
Zeiger Dereferenzierung
Adressbildung
Multiplikation, Division
Modulo
Addition, Subtraktion
Bitweises Schieben
Vergleiche
grösser, grösser-gleich,
kleiner, kleiner-gleich
Vergleiche
Gleich, Ungleich
Bitweises UND
Bitweise Exklusiv-ODER
Bitweise ODER
Logisches UND
Logisches ODER
Bedingte Auswertung
Zuweisung
Kombinierte Zuweisung
Komma-Operator
Links -> Rechts
Links -> Rechts
Links -> Rechts
Links -> Rechts
Links -> Rechts
Links -> Rechts
Links -> Rechts
Links -> Rechts
Links -> Rechts
Links -> Rechts
Rechts -> Links
Rechts -> Links
Rechts -> Links
Links -> Rechts
Syntaxdiagramme
Syntaxdiagramme sind zu finden in :
Darnell/Margolis: C – A software Engineering Approach (ANSI C), Springer
Lowes/Paulik : Programmieren mit C – ANSI Standard -, Teubner, 1992
Programmieren 1
A3
Prof. Dr.-Ing. Silvia Keller
Studiengang Angewandte Informatik
A u s g a b e : 24.09.00
Seite 108 v o n 108
ANSI-Funktionen
Alle vorgegebenen Bibliotheksfunktionen sind in Klassen ( Bibliotheken ) eingruppiert.
Zu jeder Klasse existiert eine Headerdatei xxx.h , in der die Prototypen der Funktionen sowie allgemeine
Datentypen und Konstanten definiert sind. Nach dem Standard sind folgende Klassen definiert:
Name der benötigten Headerdatei
Inhalt der Funktionsbibliothek
assert.h
ctype.h
locale.h
Funktionen zur Fehlersuche
Funktionen zur Konvertierung von Zeichen
Funktionen zur Einstellung länderspezifischer
Darstellungen
Mathematische Funktionen
Funktionen zur Signalbehandlung
Funktionen zur Behandlung einer variablen
Parameterliste
Ein-/Ausgabe-Funktionen
Stringkonvertierung, Speicherverwaltung,
Zufallszahlen
Funktionen zu Bearbeitung von Zeichenketten
( strings )
Datum und Uhrzeit
math.h
signal.h
stdarg.h
stdio.h
stdlib.h
string.h
time.h
Die fettgedruckten Bibliotheken werden in den Übungen zu Programmieren 1 / 2 benötigt. Eine
ausführliche Beschreibung der Funktionen sind aus der Online-Hilfe zu den Compilern zu entnehmen.
Herunterladen