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.