HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR– Inhalt – 01 – TH/KR – 11 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Semester) H. Kristl R. Thomas hier : Version von R. Thomas Stand : WS 2015/16 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR– Inhalt – 02 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Teil 1: Prozedurales Programmieren in C, Fortsetzung Teil 2: Einführung in die objektorientierte Programmierung mit Java Voraussetzungen : Inhalte der LV Algorithmen und Datenstrukturen (sowie teilweise der LV Digitaltechnik) im wesentlichen sind das : Aufbau und Arbeitsweise eines Digitalrechners Datendarstellung im Rechner Entwurf und Darstellung von Algorithmen Schritte der Softwareentwicklung Umsetzung von Algorithmen in C-Programme Einfache Datentypen in C Anweisungen und Kontrollstrukturen in C Der C-Preprozessor Erzeugung eines C-Programms Ausdrücke und Operatoren in C Unterprogramme (Funktionen) in C Arrays in C Strings in C Vorhang (vorangestellter Anhang) : Einige Programmierregeln HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR – Regeln – 01 – TH – 03 ---------------------------------------------------------------------------------------------------------------------------------------------------- Einige Programmier-Regeln Vorbemerkung : Im Einzelfall kann ein Verstoß gegen die folgenden Regeln zulässig (und manchmal auch sinnvoll) sein. Jeder Verstoß muss aber gut überlegt und wohl begründet sein. Grundregeln : ∙ Sinnvolle Zerlegung des vom Programm zu realisierenden Gesamt-Algorithmus in Teil-Algorithmen, gegebenenfalls hierarchische Zerlegungsstruktur ( Top-Down-Design) Implementierung der einzelnen Teil-Algorithmen durch Funktionen (Unterprogramme, Prozeduren) ∙ Strukturiert programmieren Aufbau eines (Teil-)Algorithmus (und des implementierenden ProgrammCodes) aus klaren Struktur-Elementen, die jeweils über genau einen Eingang und einen Ausgang verfügen. Jeder (Teil-)Algorithmus muss durch ein Struktogramm formulierbar sein. Zu verwendende Struktur-Elemente : lineare Aktion (Anweisung), einfache Verzweigung ( z.B. if ... else), Mehrfach-Verzweigung (z.B. switch ... case), abweisende Schleife (z.B. while ... bzw. for ...), nichtabweisende Schleife (z.B. do ... while) keine Sprünge (außer Unterprogramm-Aufrufe) ∙ Modular programmieren Aufteilung der Funktionalität eines Programms auf mehrere gut voneinander abgrenzbare Module (Quelldateien, Übersetzungseinheiten) ∙ Änderungsfreundliche, übersichtliche und selbstdokumentierende Formulierung des Quellcodes Abgeleitete Regeln : ∙ Im Regelfall keine globalen Variablen Ausnahmen müssen wohlüberlegt und begründet sein (z.B. können modul-globale Variable sinnvoll sein, wenn sie nach außen verborgen bleiben sollen und die Schnittstelle der – insbesondere - von außen aufrufbaren Funktionen dadurch sinnvoll vereinfacht werden kann) ∙ Klare Funktionsschnittstellen : Alle nicht lokal definierten Variablen einer Funktion müssen über Parameter bereitgestellt werden, keine Verwendung globaler Variabler (Ausnahmen : s. oben) ∙ Funktionen dürfen nur am Funktionsende verlassen werden keine "bedingten" Rücksprünge, sondern nur nur eine einzige "unbedingte" Rücksprunganweisung Bei Funktionen, die einen Wert zurückgeben : Gegebenenfalls Definition einer Rückgabe-Variablen, der innerhalb der Funktion an unterschiedlichen Stellen ein Rückgabewert zugewiesen wird. ∙ Keine Verwendung von Sprunganweisungen (z.B. kein goto, kein continue in Schleifen) Ausnahme : Abschluss der einzelnen Verzweigungsfälle (case-Fälle) in einer switch-Anweisung mit break. ∙ Kein Verlassen von Schleifen unter Umgehung der Schleifensteuerung, d.h. kein Verlassen von Schleifen mit break oder goto ∙ Keine Verwendung von Konstanten direkt mit ihrem Wert sondern : Definition und Verwendung von symbolischen Konstanten Dies gilt insbesondere auch für die Größenfestlegung von Arrays in C. üblich und empfohlen : Konstantennamen bestehen nur aus Großbuchstaben ∙ Definition von symbolischen Konstanten nur an einer einzigen zentralen Stelle (z.B. bei C-Programmen am Modulanfang oder in einer Headerdatei, falls Konstante in mehreren Modulen verwendet wird) ∙ Für gleiche Werte unterschiedlicher Bedeutung Definition verschiedener symbolische Konstante ∙ Konsequente Formatierung des Quelltextes (Block sinnvolles Einrücken, empfohlen : 2 Stellen/Ebene) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR-C– Inhalt –TH/KR – 05 ---------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Teil 1: Prozedurales Programmieren in C, Fortsetzung 5. Datentypen in C, Teil 2 5.1. Felder (Arrays) Inhalt der LV "Algorithmen und Datenstrukturen" 5.2 Typumwandlung 5.2.1. Implizite Typumwandlung 5.2.2. Explizite Typumwandlung 5.3 Verfügbarkeit und Lebensdauer – Speicherklassen 5.4 Zeiger (Pointer) 5.4.1. Eigenschaften und Verwendung 5.4.2. Zeiger und Arrays 5.4.3. Programm-Parameter in C 5.4.4. Zeiger und Funktionen 5.5 Strukturen (strukturierter Datentyp – struct) 5.6 Definition von Datentypnamen mittels typedef 5.7 Aufzählungstypen und Aufzählungskonstante 5.8 Der sizeof-Operator 6. Dateibearbeitung in C 6.1 6.2 6.3 6.4 6.5. Dateibearbeitungskonzept von ANSI-C Grundlegende Funktionen zur Dateibearbeitung Funktionen zum Datentransfer (Schreiben und Lesen) Funktionen zur Dateiverwaltung Beispiel zu Binärdateien 7. Ergänzungen zu Datenstrukturen in C 7.1 7.2 7.3 7.4 Dynamische Speicherallokation Dynamische Datenstrukturen (Listen und Bäume) Unions Vorwärtsdeklaration von strukturierten Datentypen 8. Die C-Standardbibliothek 8.1 8.2 8.3 8.4 8.5 8.6 8.7 ANSI/ISO-C-Standardbibliothek Überblick über die I/O-Funktionen Funktionen zum Zeichenklassentest Funktionen zur Stringbearbeitung Utility-Funktionen Mathematische Funktionen Datums- und Zeitfunktionen 9. Ausgewählte Algorithmen 9.1. 9.2. 9.3 9.4 Sieb des Eratosthenes Rekursion Bestimmung von Nullstellen durch Iteration Sortieren HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – Inhalt – TH – 07 ------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Teil 2: Einführung in die OOP mit Java 10. Grundkonzepte der Objektorientierten Programmierung 10.0. 10.1. 10.2. 10.3. 10.4. 10.5 Einführung : Grundgedanke der OOP Objekte und Klassen Kapselung Vererbung Polymorphie Klassenbeziehungen 11. Grundlegende Eigenschaften von Java 11.1 11.2. 11.3. 11.4. 11.5. 11.6. Java und C Klassen und Kapselung Programmstruktur und Startklasse Erzeugung und Start von Java-Programmen Packages in Java Java-Standardbibliothek (Java API) 12. Daten und Objekte in Java 12.1. 12.2. 12.3. 12.4. 12.5. 12.6. 12.7. 12.8. Einfache Datentypen Referenztypen und Objekterzeugung Konstante in Java Wrapper-Klassen für die einfachen Datentypen Strings Arrays Die Klasse Object Beispiel : Klasse Ratio 13. Elementare Programmfunktionalitäten in Java 13.1. 13.2. 13.3. 13.4. Zugriff zu Programmparametern Standard-Ein- und Ausgabe Exceptions Dateibearbeitung 14. Vererbung und Polymorphie in Java 14.1. 14.2. 14.3. 14.4. 14.5. Umfassende Klassendefinition Klassenableitung Polymorphie Interfaces und ihre Implementierung Programmbeispiel : Simpson-Integration HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/00/0 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 5. Datentypen in C, Teil 2 5.1. Arrays Inhalt der LV "Algorithmen und Datenstrukturen" 5.2. Typumwandlung 5.2.1. Implizite Typumwandlung 5.2.2. Explizite Typumwandlung 5.3. Verfügbarkeit und Lebensdauer – Speicherklassen 5.4. Zeiger (Pointer) 5.4.1. 5.4.2. 5.4.3. 5.4.4. Eigenschaften und Verwendung Zeiger und Arrays Programmparameter in C Zeiger und Funktionen 5.5. Strukturen (strukturierter Datentyp – struct) 5.6. Definition von Datentypnamen mittels typedef 5.7. Aufzählungstypen und Aufzählungskonstante 5.8. Der sizeof-Operator HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/01/1 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Wiederholungs-Beispiel zu Arrays ● Beispiel zu eindimensionalen Arrays : Ermittlung von Primzahlen mittels des Siebs des Eratosthenes. s. Kap. 9, Abschnitt 1 (V-PR-C-09/01/1-TH/KR-02) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/02/1/1 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Implizite Typumwandlung Bei Verknüpfungen in Ausdrücken können Operanden unterschiedlicher Datentypen auftreten. In C wird vor der Durchführung der Operation eine Angleichung der verschiedenen Datentypen der Operanden vorgenommen: ⇒ implizite Typumwandlung (Typkonvertierung, type conversion) Die implizite Typkonvertierung findet in folgenden Situationen statt : • Bei Zuweisungen wird der Wert der rechten Seite in den Typ der linken Seite umgewandelt: aVar = bVar int int ← float ← double Weglassen des gebrochenen Anteils int char char ← long ← int ← short Weglassen der höherwertigen Bits float ← double Runden oder Abschneiden (implementierungsabhängig) float ← long, int, short, char double ← long, int, short, char durchgeführte Aktion Wenn keine exakte Darstellung möglich Runden oder Abschneiden (implementierungsabhängig) • Bei Parameterübergabe an Funktionen: Umwandlung des Typs des aktuellen Parameters in den Typ des formalen Parameters nach den für die Zuweisung gültigen Regeln. • Automatische Umwandlung in arithmetischen Ausdrücken: (integral promotion) Vorzeichenbehaftet: char, short → int Vorzeichenlos: unsigned char, unsigned short → int (falls damit darstellbar) unsigned int (sonst) • Bei 2-stelligen arithmetischen Operationen wird bei unterschiedlichem Datentyp der beiden Operanden der Wert des "niedrigeren" Typs in den "höheren" Typ umgewandelt. Das Ergebnis der Operation ist vom Typ des "höheren" Operanden-Typs. Dabei gilt die folgende Typ-Hierarchie: long double ("höchster" Typ) double float unsigned long int long int unsigned int int ("niedrigster" Typ) Anmerkung : Zuvor wird für short und char prinzipiell "integral promotion" durchgeführt (s.oben). HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/02/1/2 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Regeln für die implizite Typumwandlung Ist ein Operand "long double" ? ja nein Zweiter Operand und Ergebnis sind "long double" Ist ein Operand "double" ? ja nein Zweiter Operand und Ergebnis sind "double" Ist ein Operand "float" ? nein ja Zweiter Operand und Ergebnis sind "float" integral promotion beider Operanden: (unsigned)char,short → (unsigned)int Ist ein Operand "unsigned long" ? ja nein Zweiter Operand und Ergebnis sind "unsigned long" Ist ein Operand "unsigned" und der zweite Operand "long" ? ja nein Kann "long" alle "unsigned"Werte darstellen? ja Ist ein Operand "long" ? ja nein "unsigned"-Operand und Beide Operanden und Ergebnis "unsigned long" Ergebnis sind "long" nein Zweiter Operand und Ergebnis sind"long" ja Zweiter Operand und Ergebnis "unsigned int" Ist ein Operand "unsigned" ? nein Ergebnis ist"int" HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/02/2/1 – TH/KR – 07 --------------------------------------------------------------------------------------------------------------------------------------------------------- Explizite Typumwandlung • In manchen Fällen ist es erforderlich die Typumwandlung gezielt vorzunehmen. Beispiel: int j=2, k=3; float f; ..... f = k/j; .... /* in f steht 1.0 */ beide Operanden sind vom Typ "int" also ist auch das Ergebnis der Division vom Typ "int"; die Zuweisung erfolgt erst hinterher und führt erst dann zur impliziten Typkonversion "int"→"float"! Soll das "echte" Ergebnis gewünscht werden, so wird dies durch eine explizite Typumwandlung ermöglicht. • Die explizite Typumwandlung (type cast) erfolgt mittels des Cast-Operators, der unmittelbar vor den Ausdruck zu setzen ist,dessen Typ konvertiert werden soll: Cast-Operator Ausdruck • Cast - Operator: unärer Operator : in Klammern eingeschlossene Typangabe (Typangabe) Wirkung: Der Wert des Ausdrucks wird in den durch den Cast-Operator festgelegten Datentyp umgewandelt. Achtung: Der Datentyp der am Ausdruck beteiligten Variablen selbst bleibt unverändert. Präzedenz: Gleiche Priorität wie die anderen unären Operatoren. • Anwendung : Überall dort, wo ein ganz bestimmter Datentyp erforderlich ist, der aber durch die implizite Typumwandlung nicht erzielt wird! Beispiel: (vgl. oben ) int j=2, k=3; float f; ..... f = (float)k/j; ..... /* in f steht 1.5 */ Der Wert der Variablen k wird typkonvertiert ("gecastet"). Dies führt dazu, dass vor der Ausführung der Division j implizit typkonvertiert wird und die Division ein "float"Ergebnis liefert. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/03/1 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Verfügbarkeit und Lebensdauer - Speicherklassen ● Attribut Speicherklasse Neben der Typangabe gibt es für Objekte (Funktionen und Variable) in C als weiteres Attribut die Speicherklasse (storage class). Sie beeinflusst folgende Merkmale von Objekten: - Verfügbarkeit Lebensdauer Weiterhin wirkt sie sich auf den Speicherort des Objekts aus ● Verfügbarkeit (Scope): Bereiche eines Programmes, in denen zu einem Objekt zugegriffen werden kann. C unterscheidet vier Verfügbarkeitsbereiche: (1) gesamtes Programm (2) Modul (File) globale Objekte: Definition außerhalb jeder Funktion; Funktionen sind immer global! (3) Funktion (4) Verbundanweisung lokale Objekte: Definition innerhalb eines Blockes (innerhalb einer Funktion oder Verbundanweisung) Lokale Objekte können nur Variablen sein! ● Lebensdauer (Duration): Dauer der Zuordnung von Speicherplatz zu einem Objekt. Man unterscheidet: ◇ Gesamte Programmlaufzeit (static duration): Der Speicherbereich wird dem Objekt zu Programmbeginn zugeordnet. Die Zuordnung wird erst mit dem Programmende wieder aufgehoben. ◇ Ausführungszeit des Blockes, in dem die Definition des Objektes erfolgte (automatic duration). Dem Objekt wird mit Eintritt des Programmes in den Block automatisch Speicher zugewiesen. Mit Verlassen des Blockes wird diese Speicherzuweisung wieder aufgehoben. Merke: Globale Objekte - also auch alle Funktionen - "leben" grundsätzlich während der gesamten Programmlaufzeit. Lokale Objekte (Variable) besitzen eine Lebensdauer, die auf die Blockausführungszeit begrenzt ist, sie können aber auch für die gesamte Programmlaufzeit vereinbart werden. ● Speicherklassen (storage classes): Zur Kennzeichnung der Speicherklasse eines Objekts, bei dessen Definition bzw. Deklaration, gibt es in der Programmiersprache C vier Speicherklassen- Spezifizierer (storage class specifier), die auch die Speicherklassennamen festlegen. Bei Vereinbarungen müssen sie gegebenenfalls vor dem Objekttyp angegeben werden. Erfolgt keine derartige Angabe, so gilt eine Default-Speicherklasse. Die vier Speicherklassen(-Spezifizierer) sind: extern - static - auto - register HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/03/2 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Speicherklassen (1) • Speicherklasse auto (nur Variable) ◇ Verfügbarkeitsbereich : Block, der die Definition enthält Lebensdauer : Block-Ausführungszeit Speicherort : Stack ◇ Vorgabespeicherklasse für lokale Verfügbarkeit. Speicherklasse für ▻ alle Variablen, die innerhalb eines Blockes definiert sind und nicht explizit mit "static" oder "register" vereinbart sind; ▻ alle formalen Funktionsparameter, für die nicht explizit "register" angegeben wird. ◇ Default-Initialisierung: undefiniert explizite Initialisierung: bei Blockeintritt einfache Datentypen und Zeiger durch beliebige Ausdrücke; zusammengesetzte Datentypen (arrays, structures und unions) durch konstante Ausdrücke. • Speicherklasse register (nur Variable) Spezialfall der lokalen Verfügbarkeit : wie Speicherklasse auto - mit der Besonderheit, dass die Variable in einem lokalen CPU-Speicher (Register) angelegt werden soll (Zugriffszeit!). • Speicherklasse extern (Variable und Funktionen) ◇ Verfügbarkeitsbereich : Gesamtes Programm Lebensdauer : Programmlaufzeit Speicherort : Datensegment (Variable) bzw Codesegment (Funktionen) ◇ Vorgabespeicherklasse für globale Verfügbarkeit. Speicherklasse für ▻ alle Funktionen, ▻ alle außerhalb jeder Funktion definierten Variablen, für die nicht explizit "static" angegeben wird. ◇ Um Objekte verwenden zu können, die in einem anderen Modul oder im gleichen Modul später definiert sind, sind extern -Deklarationen erforderlich. ◇ Default-Initialisierung: mit 0 explizite Initialisierung: durch konstanten Ausdruck (ausgewertet durch Compiler). HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/03/3 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Speicherklassen (2) • Speicherklasse static (Variable und Funktionen) ◇ Es existieren zwei Formen: ▻ static local : Anwendung auf lokales Objekt ▻ static global : Anwendung auf globales Objekt ◇ Verfügbarkeitsbereich: Lebensdauer: Speicherort : statisch-lokal (nur Variable) statisch-global (Variable und Funktionen) Block, der die Definition enthält - falls static local Modul, das die Definition enthält - falls static global Programmlaufzeit Datensegment (Variable) bzw Codesegment (Funktionen) ◇ Statisch-lokale Variable behalten ihre Werte zwischen zwei Ausführungen des Blockes bei, im Gegensatz zu Variablen der Speicherklasse "auto". Dieses Verhalten ist speziell dann interessant, wenn zwischen zwei Aufrufen der gleichen Funktion die Variable den zuletzt ermittelten Wert beibehalten soll. Eine eventuelle Initialisierung erfolgt nur beim ersten Eintritt in diesen Block. ◇ Statisch-globale Objekte (Variable und Funktionen) ermöglichen es, die Globalität auf das betrachtete Modul zu beschränken. Sie können in anderen Modulen desselben Programmes nicht "gesehen" werden. Sollen also einzelne Funktionen und globale Variable nur im eigenen Quellmodul sichtbar sein, so sind sie mit der Speicherklasse "static" zu definieren. ◇ Default-Initialisierung: mit 0 explizite Initialisierung: durch konstanten Ausdruck (ausgewertet durch Compiler). • Zusammenfassung Eigenschaften der Speicherklassen Speicherklasse auto *) falls möglich, sonst Stack register static local Speicherort Definitionsort Stack CPU-Register *) normaler Arbeitsspeicher innerhalb eines Blocks (default : auto) außerhalb jeder Funktion (default : extern) Verfügbarkeitsbereich Block (Funktion bzw Verbundanweisung) Lebensdauer Ausführungszeit des Blocks, in dem das Objekt definiert ist Modul Programm gesamte Programmlaufzeit **) nur mit Initialisierung (*) darf auch fehlen • Überblick Verwendung der Speicherklassen-Spezifizierer auto extern global register static extern Variablendefinition innerhalb einer Funktion (∗) ∗ ∗ --- Variablendefinition außerhalb jeder Funktion --- --- ∗ (∗) **) Variablendeklaration --- --- --- ∗ Funktionsdefinition --- --- ∗ (∗) Funktionsdeklaration --- --- ∗ (∗) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/03/4 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsbeispiel zu Speicherklassen /*-----------------------------------------------------/* Modul 1: stclass_m.c /*-----------------------------------------------------/* Verfuegbarkeit, Lebensdauer und Speicherklassen /*------------------------------------------------------ */ */ */ */ */ #include <stdio.h> extern void exprint(int); static void locprint(void); int iZ ; Hinweis: Funktion ist in einem anderen Modul definiert Hinweis: Funktion ist später in diesem Modul definiert und außen nicht verfügbar lokale auto-Variable mit und ohne Initialisierung int main(void) { int iZ=1,i; printf("\nZahl(1): %d\n",iZ); { extern int iZ; Hinweis auf verwendete globale Variable printf("Zahl(2): %d\n",iZ++); for(i=0;i<2;i++) "sub"-lokale static-Variable, wird nur beim ersten Block{ static int iZ = 22; Eintritt initialisiert und behält ihren Wert bei Wiedereintritt exprint(iZ++); locprint(); } } return 0; } Definition der globalen Variablen, macht obige Vereinbarung zur Deklaration int iZ = 33; static void locprint(void) { int iZ = 49; printf("\nZahl(loc1): %d\n",iZ++); } lokale auto-Variable mit Initialisierung /*-----------------------------------------------------/* Modul 2: stcl1.c /*-----------------------------------------------------/* Verfuegbarkeit und Lebensdauer und Speicherklassen /*-----------------------------------------------------#include <stdio.h> extern int iZ; static void locprint(void); void exprint(register int ilZ) { printf("\nZahl(ex): %d %d\n",iZ, ilZ); locprint(); } static void locprint(void) { extern int iZ; printf("\nZahl(loc2): %d\n",iZ++); } Funktion ist nur in diesem Modul verfügbar */ */ */ */ */ Verweis auf globale Variable, die in einem anderen Modul definiert ist Hinweis auf ein zweites "locprint()", das in diesem Modul (2. Modul des gleichen Programms) später definiert ist Hinweis auf verwendete globale Variable Funktion ist nur in diesem Modul verfügbar Probelauf : Zahl(1): 1 Zahl(2): 33 Zahl(ex): 34 22 Zahl(loc2): 34 Zahl(loc1): 49 Zahl(ex): 35 23 Zahl(loc2): 35 Zahl(loc1): 49 /* /* /* /* /* /* /* /* lokales iZ in main */ globales iZ in main verwendet */ globales iZ in exprint verwendet, "sub"-lokales iZ von main an exprint übergeben */ globales iZ verwendet in locprint aus Modul 2, das von exprint aufgerufen wird */ lokales iZ in locprint aus Modul 1 */ globales iZ in exprint verwendet, "sub"-lokales iZ von main an exprint übergeben */ globales iZ verwendet in locprint aus Modul 2, das von exprint aufgerufen wird */ lokales iZ in locprint aus Modul 1 */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/1/1 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger in C (1) ● Wesen und Eigenschaften von Zeigern ◇ Ein Zeiger (Pointer) ist eine Variable (Zeigervariable) oder eine Konstante (Zeigerkonstante) Eine Zeigervariable benötigt wie andere Variable Speicherplatz. ◇ Der Wert einer Zeigervariablen (bzw eine Zeigerkonstante) ist eine Speicheradresse. Diese Speicheradresse ist ▻ entweder die (Anfangs-) Adresse von einer – anderen – Variablen ▻ oder die (Start-) Adresse einer Funktion ⇒ Ein Zeiger z e i g t auf eine andere Variable oder auf eine Funktion Beispiel : Die Zeigervariable zeiger_auf_x zeigt auf die Variable x zeiger_auf _x : x : ◇ Ein Zeiger kann auch wiederum auf eine Zeigervariable zeigen ( Zeiger auf Zeiger) ◇ Mittels Zeiger können Variable indirekt über ihre Adresse (⇒ Referenz) angesprochen werden. ◇ Zeiger sind i.a. an bestimmte Objekt-Typen gebunden. Z.B. kann ein Zeiger auf int-Werte nur Adressen von int-Variablen als Werte annehmen. Oder : ein Zeiger auf int-Zeiger kann nur Adressen von int-Zeiger-Variablen annehmen Ausnahme: generic pointer: void * kann auf beliebige Objekt-Typen zeigen. ● Zeigerbezogene Operatoren Im Zusammenhang mit Zeigern existieren zwei – zueinander inverse – unäre Operatoren: ▻ & Adressoperator z.B. liefert die Adresse eines Objektes &x ⇒ Adresse der Variablen x Adressen können Zeigervariablen zugewiesen werden: z.B. px sei Zeigervariable (auf int) x sei int-Variable ⇒ px = &x; /* die Zeigervariable px zeigt jetzt auf die Variable x */ ▻ * Objektoperator z.B. liefert das Objekt, auf das ein Zeiger zeigt ⇒ Dereferenzierung *px ⇒ Objekt, auf das der Zeiger px zeigt, bzw. Variable deren Adresse in px steht. Zeigerobjekte können wie unter Namen referenzierte Variable verwendet werden: z.B. px sei Zeigervariable (auf int) y sei int-Variable ⇒ *px = 3; /* Zuweisung von 3 an das Objekt von px (int-Var.), Verwendung von px als lvalue */ y = *px; /* Zuweisung des Werts des Objekts von px an y, Verwendung von px als rvalue */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/1/2 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger in C (2) ● Vereinbarung von Zeigervariablen erfolgt mittels Verwendung des Objektoperators *. z.B. int *px; /* px wird als Zeigervariable vereinbart, die auf int-Variable zeigt /* Interpretation : *px ist vom Typ int, ⇒ px ist ein Zeiger auf int double **ppd; /* ppd ist eine Zeigervariable, die auf double-Zeiger-Variable zeigt /* Interpretation : **ppd ist vom Typ double ⇒ *ppd ist ein double-Zeiger ⇒ ppd ist ein Zeiger auf double-Zeiger */ */ */ */ ● Ergänzungen zur Verwendung von Zeigern ◇ Zeigerwerte (Adressen) können als Text ausgegeben werden (i.a. als Sedezimalzahl), z.B. in die Standardausgabe mittels printf(). Umwandlungsspezifikation %p im Steuerstring Beispiel : int x; int *px = &x; printf("Adresse von x : %p\n", &x); printf("Wert von px : %p\n", px); ◇ Größtes Problem bei Zeigern : Verwendung von Zeigervariablen, denen kein Wert zugewiesen wurde ( nicht-initialisierte Zeiger !). Lokale nicht-initialisierte auto-Variable besitzen einen zufälligen Wert. Laufzeitfehler wird häufig nicht erkannt. ◇ Zeigervariablen, die keinen definierten Wert enthalten, sollte der Wert NULL (== 0, NULL-Pointer) zugewiesen werden. Ihre Verwendung führt dann zu einem erkennbaren Laufzeitfehler ( Null-Pointer-Exception) Die Konstante NULL ist definiert in <stddef.h>, oft auch noch in weiteren Headerdateien (z.B. <stdio.h> und/oder <stdlib.h>). ● Demonstrationsbeispiel zu Zeigern Programm poindem1 siehe nächste Seite Entwicklung der Variablenwerte des Demonstrationsbeispiels: Variable x: y: px: py: ppz: Wert Wert Wert Wert Wert Wert Wert HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/1/3 – TH/KR – 07 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger in C (3) /*----------------------------------------------------------------------- ----------*/ /* C-Quelldatei poindem1_m.c */ /* Modul mit main()-Funktion fuer das Programm poindem1 – Einfache Demo zu Zeigern */ /*-------------------------------------------------------------------------------- */ #include <stdio.h> int main(void) { int x=1, y; int *px, *py=NULL; /* Zeiger auf int-Variable */ int **ppz; /* Zeiger auf int-Zeiger-Variable */ printf("\nAdresse printf("\nAdresse printf("\nAdresse printf("\nAdresse printf("\nAdresse von x von y von px von py von ppz : : : : : %p", &x); %p", &y); %p", &px); %p", &py); %p\n", &ppz); printf("\nWert printf("\nWert px=&x; *px=5; (*px)++; y=*px+1; printf("\nWert printf("\nWert von px (nicht-initialisiert) : %p", px); von py (initialisiert mit NULL) : %p", py); /* Wertzuweisung an Zeigervariable mittels Adressoperator */ /* Modifikation des Zeigerobjekts */ /* Klammern notwendig fuer Modifikation des Zeigerobjekts */ /* Zeigerobjekt als Operand */ von x : %d",x); von y : %d\n",y); py=px; *py += 3; printf("\nWert printf("\nWert printf("\nWert printf("\nWert /* Zuweisung des Werts eines anderen Zeigers */ /* Modifikation des Zeigerobjekts */ von px : : %p", px); des Zeigerobjekts von px : %d",*px); von py : : %p", py); des Zeigerobjekts von py : %d\n",*py); py = &y; ppz = &py; /* Zuweisung der Adresse einer Zeigervariablen */ **ppz = 12; /* Modifikation des Objekts des Zeigerobjekts */ printf("\nWert von ppz : %p", ppz); printf("\nWert des Zeigerobjekts von ppz : %p", *ppz); printf("\nWert des Zeigerobjekts von *ppz : %d\n", **ppz); printf("\nWert von x : %d", x); printf("\nWert von y : %d", y); return 0; } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/1/4 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger in C (4) ● Operationen mit Zeigern Auf Zeiger in C lassen sich die folgenden Operationen anwenden : ◇ Zuweisung einer Adresse, z.B. des Wertes einer anderen Zeigervariablen (desselben Typs). siehe oben ◇ Ermittlung des Zeigerobjekts ◇ Vergleichsoperationen (==, !=, >, <, >=, <=) ▻ zwischen Zeigern gleichen Typs (sinnvoll nur für Zeiger, die auf Elemente des gleichen Arrays zeigen) ▻ sowie zwischen einem Zeiger und dem Wert 0. ◇ Folgende Arithmetische Operationen Zeigerarithmetik ∗ Addition und Subtraktion eines ganzzahligen Wertes (einschließlich Inkrement und Dekrement). Addition (bzw Subtraktion) von n zu einem Zeiger liefert als neuen Zeigerwert die Adresse, die um den Speicherplatz für n Objekte seines Objekttyps größer (bzw kleiner) als sein ursprünglicher Wert ist. Compiler nimmt Skalierung von n entsprechend der Größe des Objekttyps vor Portabiltät Beispiele : double dAr[8], *pdX; pdX = &dAr[1]; /* Zeigervariable hat den Wert der Adresse des Arrayelements mit dem Index 1 */ pdX += 4; /* Erhöhung von pdX um 4 Speicherplätze von double */ pdX++; /* Erhöhung um den Umfang eines double-Speicherplatzes */ pdX = pdX – 2; /* Erniedrigung um den Umfang zweier double-Speicherplätze */ ∗ Subtraktion von zwei Zeigern, die auf Objekte des gleichen Typs zeigen. Die Operation liefert die Anzahl der Objekte, um die die Werte der beiden Zeiger voneinander entfernt sind. Sinnvoll anwendbar auf Zeiger, die auf Elemente des gleichen Arrays zeigen. Anzahl der zwischen den Zeigerobjekten liegenden Elemente +1 Beispiel : Mögliche Formulierung von "strlen()" int strLen (char sKette[]) { char *pcStart = &sKette[0]; char *pcEnde; pcEnde = pcStart; while (*pcEnde != '\0') pcEnde++; return (pcEnde – pcStart); } /* /* /* /* Def. und Init. einer Zeigervariablen Def. einer Zeigervariablen Anfangswert vorbelegen Ermitteln der Adresse des Stringendes */ */ */ */ /* Differenz zwischen Start- und Endezeiger gibt die Anzahl der dazwischen liegenden Arrayelemente +1 wieder, das ist die gesuchte String-Laenge */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/2/1 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger und Arrays in C ● Verwandschaft zwischen Zeigern und Arrays ◇ Vergleich der Verwendung von Arrays uns Zeigern : /* Definition eines int-Arrays mit 10 Elementen */ int a[10]; a[0] a[1] a[i] 1. Element von a 2. Element von a Element, das i Positionen vom Anfang entfernt ist. ⇒ ⇒ ⇒ /* Definition eines Zeigers auf int-Variable */ int *pa; pa = &a[0]; pa pa + 1 pa + i ⇒ zeigt auf das 1. Element von a zeigt auf das 2. Element von a zeigt auf das Element, das i Positionen von dem Element entfernt ist, auf das pa zeigt. ⇒ ⇒ ⇒ *pa *(pa+1) *(pa+i) ist gleich mit ist gleich mit ist gleich mit a[0] a[1] a[i] ◇ Array-Namen werden durch den Compiler wie Zeiger auf das erste Arrayelement behandelt: ist äquivalent mit ⇒ a ⇒ statt pa=&a[0] kann man auch statt a[i] kann man auch &a[0] pa=a formulieren *(a+i) formulieren ◇ Umgekehrt lassen sich Zeigervariable wie Arrays verwenden (d.h. sie lassen sich indizieren !) : ⇒ ◇ statt *(pa+i) kann man auch pa[i] formulieren Jeder Array-Index-Ausdruck kann auch als PointerOffset-Ausdruck formuliert werden und umgekehrt. ◇ Eine Pointer-Variable kann auch mit negativem Index verwendet werden : pa[-i] (i>0) bezeichnet das Element, das i Positionen (Objekte !) vor dem Element liegt, auf das pa zeigt. pa[-i] ist äquivalent mit *(pa-i) ◇ Es besteht jedoch ein gravierender Unterschied zwischen Array-Namen und Zeiger-Variablen : ▻ Eine Zeiger-Variable ist eine Variable, deren Wert jederzeit geändert werden darf. z.B. int x,*px; px = &x; /* Veränderung ist jederzeit zulässig */ ▻ Ein Array-Name ist eine Zeiger-Konstante, ihr Wert ist nicht veränderbar! z.B. int z, y[5]; y = &z; /* U n z u l ä s s i g */ ◇ Als Funktionsparameter sind Pointer und Arrays vollkommen äquivalent (beliebig austauschbar) an Parameterposition sind char s[] und char *s identische Formulierungen HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/2/2 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zur Verwandschaft zwischen Zeigern und Arrays /*--------------------------------------------------------------------------- */ /* Äquivalente Formulierungen der Funktion strcopy() */ /*--------------------------------------------------------------------------- */ void strcopy(char strl[] ,char str2[]) { int i=0; while((str1[i]=str2[i]) != '\0') i++; } /*--------------------------------------------------------------------------- */ void strcopy(char *strl ,char *str2) { int i=0; while((str1[i]=str2[i] ) != '\0') i++; } /*--------------------------------------------------------------------------- */ void strcopy(char *strl ,char *str2) { int i=0; while((*(str1+i)= *(str2+i) ) != '\0') i++; } /*--------------------------------------------------------------------------- */ void strcopy(char *strl ,char *str2) { while((*str1 = *str2 ) != '\0') { str1++; str2++; } } /*--------------------------------------------------------------------------- */ void strcopy(char *strl ,char *str2) { while (*str1++=*str2++); } /*--------------------------------------------------------------------------- */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/2/3 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Mehrdimensionale Arrays und Zeiger ● Der Name eines mehrdimensionalen Arrays kann als "Zeiger auf Arrays" aufgefasst werden. Beispiel: char caF [3][4]; caF /* Definition und damit Speicherplatzreservierung eines Arrays von 3*4 char-Werten. /* caF ist ein Zeiger auf ein Array (== Zeiger auf das erste Element des Arrays) mit 3 Elementen, die ihrerseits char-Arrays der Länge 4 sind. *caF */ */ [0][0] [0][1] [0][2] [0][3] (caF+1) *(caF+1) [1][0] [1][1] [1][2] [1][3] (caF+2) *(caF+2) [2][0] *(caF+2)+1 [2][1] [2][2] [2][3] ● Daraus ergeben sich folgende Zusammenhänge : caF + i ist ein Zeiger auf das (i+1)-te Zeilenarray (und zeigt auf das erste Element dieser Zeile) *(caF + i) = caF[i] ist das (i+1)-te Zeilenarray, genauer ein Zeiger auf das erste Element dieses Zeilenarrays ⇒ (caF+i) und *(caF+i) liefern immer die gleiche Adresse *(caF+i)+j = caF[i] + j ist ein Zeiger auf das (j+1)-te Element des (i+1)-ten Zeilenarrays *(*(caF+i)+j) = *(caF[i] + j) = caF[i][j] = (*(caF+i))[j] ist das (j+1)-te Element des (i+1)-ten Zeilenarrays ● Für die Parameterdeklarationen mehrdimensionaler Felder besteht folgende Äquivalenz : char (*caMat)[4] und char caMat [][4] HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/2/4 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger - Arrays (1) ● Zeiger - Arrays sind Arrays deren Komponenten Zeiger sind. Die Komponenten können auch Adressen von Arrays sein, damit ergibt sich eine Ähnlichkeit zu mehrdimensionalen Arrays. Beispiel: char *paZ[3]; /* Definition und damit Speicherplatzreservierung für ein Array mit 3 Elementen, die jeweils Zeiger auf char-Typen sind. */ /* paZ ist ein Zeiger auf ein Array ( == auf das erste Element des Arrays) mit 3 char-Zeigern */ char caF1[4], caF2[4], caF3[4]; /* Definition und damit Speicherplatzreservierung für 3 /* char-Arrays mit jeweils der Länge 4. */ */ paZ[0] = caF1; paZ[1] = caF2; paZ[2] = caF3; /* Wertzuweisung an die char-Zeiger */ paZ caF1 paZ+1 caF2 paZ+2 caF3 caF1[] caF2[] caF3[] *(paZ+2)+1 paZ[2][1] = caF3[1] ● Man sieht folgende Analogie zwischen Zeiger-Arrays und mehrdimensionalen Arrays: paZ+i ist ein Zeiger auf das (i+1)-te Element des Zeiger-Arrays paZ *(paZ+i) = paZ[i] ist das Array, auf das das (i+1)-te Element des Zeiger-Arrays zeigt, also ein Zeiger auf das erste Element dieses Arrays. ((paZ+i) und *(paZ+i) liefern nicht die gleiche Adresse) *(paZ+i)+j = paZ[i]+j ist ein Zeiger auf das (j+1)-te Element des Arrays, auf das das (i+1)te Element des Zeiger-Arrays paZ zeigt. *(*(paZ+i)+j) = *(paZ[i]+j) = paZ [i][j] = (*(paZ+i))[j] ist das (j+1)-te Element des Arrays, auf das das (i+1)-te Element des Zeiger-Arrays paZ zeigt. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/2/5 – TH/KR – 07 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger - Arrays (2) ● Programmbeispiel : Das obige Beispiel geht davon aus, dass die Elemente des Zeiger-Arrays alle auf Arrays gleicher Größe verweisen. Dies bedingt die große Ähnlichkeit zu mehrdimensionalen Arrays. Bei häufigen Anwendungen von Zeiger-Arrays besitzen die referenzierten Arrays aber unterschiedliche Längen, wie z.B. verschieden lange Strings. /*------------------------------------------------------------------------*/ /* Programm: zeigarr (Modul zeigarr_m.c) */ /*----------------------------------------------------------------------- */ /* Aehnlichkeit und Unterschied zwischen Zeiger-Arrays u. mehrdim. Arrays */ /* Elemente des Zeiger-Arrays zeigen auf Arrays unterschiedlicher Laenge */ /*------------------------------------------------------------------------*/ #include <stdio.h> char caTagfeld[][11] = { "Montag", /* "Dienstag", /* "Mittw", /* "Donnerstag", /* "Freitag", /* "Samstag", "Sonntag" }; Dies ist ein 2-dimensionales Array Die zweite Dimension ist fest 11, die erste wird durch die Initialis. festgelegt mit 7. Fuer jeden String werden genau 11 Zeichen reserviert */ */ */ */ */ char *paTagzeig[] = Dies ist ein eindimensionales Zeigerarray mit genau 7 Zeigern auf Strings als Elemente. Die Strings besitzen unterschiedliche Laengen */ */ */ */ */ { "Montag", /* "Dienstag", /* "Mittw", /* "Donnerstag", /* "Freitag", /* "Samstag", "Sonntag" }; int main (void) { int i,j; char cZa, cZb; while ( ((printf("\nZeile ? "), fflush(stdout), scanf("%d",&i)) >0) && (i>=0) && ((printf("Spalte ? "), fflush(stdout), scanf("%d",&j)) >0) && (j>=0) ) { cZa = caTagfeld[i][j]; /* Zugriff auf das 2-dim. Array cZb = paTagzeig[i][j]; /* Zugriff ueber das Zeiger-Array printf("caTagfeld [%2d][%2d] = %c (= %2x hex)\n",i,j,cZa,cZa); printf("paTagzeig [%2d][%2d] = %c (= %2x hex)\n",i,j,cZb,cZb); } return 0; */ */ } /* --------------------------------------------------------------------------- */ Ergebnis eines Probelaufs : Zeile ? 0 Spalte ? 4 caTagfeld [ 0][ 4] = a (= 61 hex) paTagzeig [ 0][ 4] = a (= 61 hex) Zeile ? 2 Spalte ? 10 caTagfeld [ 2][10] = (= 0 hex) paTagzeig [ 2][10] = e (= 65 hex) Zeile ? -1 /* Wie laesst sich dieser Unterschied /* erklaeren ? */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/3/1 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programm-Parameter in C ● C bietet die Möglichkeit, einem Programm beim Start Strings als Parameter zu übergeben. ( Programm-Parameter, Kommandozeilen-Parameter). Beim Aufruf des Programms aus der Kommandozeile sind die Programm-Parameter nach dem Programmnamen jeweils durch Blanks getrennt anzugeben. ● Den Zugriff zu den Programm-Parametern innerhalb der Funktion main() ermöglichen zwei Funktionsparameter, mit denen main() vom Startup-Code aufgerufen wird. Die korrespondierenden formalen Parameter in main() werden üblicherweise mit argc argv (= argument count) (= argument vector) und bezeichnet : alternative Formulierung : int main(int argc, char *argv[]) int main(int argc, char **argv) ● Im ersten Parameter argc wird die Anzahl der Programmparameter übergeben. Dabei zählt der Programmname selbst immer als erster Parameter. argc ist immer mindestens == 1. (Wert, wenn es keine Programm-Parameter gibt) ● Der zweite Parameter argv ist ein Pointer auf ein char-Pointer-Array, dessen Elemente auf die einzelnen Programm-Parameter-Strings verweisen. Dabei zeigt das erste Element immer auf den Programmnamen. Abgeschlossen ist das Array mit dem NULL-Pointer. argv[0] argv[1] argv[argc-1] argv[argc] ● Beispiel : ist ein Pointer auf den Programmnamen ist ein Pointer auf den ersten echten Programm-Parameter ist ein Pointer auf den letzten Programm-Parameter ist der NULL-Pointer ( argv[argc] == NULL) Demo-Programm echopar (zeilenweise Ausgabe der einzelnen Programmparameter) Programmaufruf : echopar Willkommen in der C-Welt argc = 5 argv argv[0] "echopar" argv[1] "Willkommen" argv[2] "in" argv[3] "der" argv[4] argv[5] "C-Welt" NULL HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/3/2 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiele zu Programm-Parametern /* /* /* /* /* ------------------------------------------------------------------------Programm echopar (Modul echopar_m.c) ------------------------------------------------------------------------Ausgabe der Programm-Parameter untereinander nach stdout ------------------------------------------------------------------------- */ */ */ */ */ #include <stdio.h> int main(int argc, char *argv[]) /* argc Anzahl der Programm-Parameter */ /* argv Pointer-Array auf Programm-Parameter */ { int i; putchar('\n'); for (i=1; i<argc; i++) printf("%s\n",argv[i]); return 0; } /* /* /* /* /* /* /* ------------------------------------------------------------------------Programm echopar2 (Modul echopar2_m.c) ------------------------------------------------------------------------Ausgabe der Programm-Parameter untereinander nach stdout Alternative Verwendung des Parameters argv : als Pointer auf char-Pointer (genauer : auf ein Array von char-Pointern) ------------------------------------------------------------------------- #include <stdio.h> int main(int argc, char **argv) /* argc Anzahl der Programm-Parameter /* argv Pointer auf char-Pointer-Array { putchar('\n'); while (--argc>0) printf("%s\n",*++argv); return 0; } */ */ */ */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/4/1 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger als Funktionsparameter und Funktionswert ● Zeiger als Funktionsparameter : Funktionsparameter in C sind grundsätzlich Wert-Parameter. Bei der Übergabe des aktuellen Parameters wird dessen Wert in den formalen Parameter kopiert. Für Zeiger als Parameter bedeutet dies, das die durch den Parameter übergebene Adresse als lokale Kopie in der Funktion zur Verfügung steht. Über sie kann zu der durch sie referierten – ausserhalb der Funktion existierenden – Variable zugegriffen und diese damit auch verändert werden. Parameter existieren – wie lokale auto-Variable – nur, solange die Funktion durchlaufen wird. Beispiel (s. Programm zeigerpar) : int mach(int iR, int *piL) { iR += 5; /* Verändert die lokale Variable iR, nicht den an dieser /* Position übergebenen aktuellen Paramter (hier iB) !!! *piL += 5; /* Verändert den Wert der Variablen auf die die lokale /* Variable piL zeigt - also iC in main() !! piL = &iR; /* piL zeigt nun auf die lokale Variable iR return *piL; /* gibt den Wert zurück, den iR besitzt } /* iR und piL hören auf zu existieren! */ */ */ */ */ */ */ int main(void) { int iA=0, iB=2, iC=3; ....... iA = mach (iB, &iC); /* Übergabe einer Kopie an die formalen Parameter iR */ /* und piL */ ...... /* iA = ; iB = ; iC = ?? */ } ● Konsequenzen : ◇ Als aktuelle Parameter übergebene Variable werden durch die in der Funktion ausgeführten Aktionen nicht verändert, da nur ihr jeweiliger Wert als Kopie übergeben wird ( Wertübergabe). ◇ Soll eine außerhalb der Funktion existierende Variable durch die Funktion modifiziert werden, so muss ein Zeiger auf diese Variable als Parameter übergeben werden. Die Übergabe eines Zeigers (== Adresse !) als Parameter wirkt wie eine Referenzübergabe für die Variable. ◇ Variable eines zusammengesetzten Datentyps, sollten im allgemeinen stets mittels eines Zeigers übergeben werden (schnellere Parameterübergabe, Einsparung von Speicherplatz !) Gegebenenfalls ist von Veränderungen einzelner Komponenten durch Anweisungen innerhalb der Funktion abzusehen. ◇ Arrays lassen sich nur per Zeiger übergeben (Array-Name == Zeiger-Konstante !). ● Zeiger als Funktionswert (Rückgabewert ) : Der Funktionswert kann ebenfalls von einem Zeigertyp sein: int *tuwas(int *, int *); /* Funktionswert ist ein Zeiger auf ein int-Objekt */ Es ist dabei besonders darauf zu achten, dass der Rückgabewert auf noch existierende Variable verweist. Innerhalb der Funktion verwendete lokale auto-Variable dürfen deshalb nicht referiert werden ! HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/4/2 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger als Funktionsparameter und Funktionswert – Beispiele 1 /*------------------------------------------------------------------------- */ /* Programm: zeigarr2 (Modul zeigarr2_m.c) */ /*--------------------------------------------------------------------------*/ /* Modifikation des Programms zeigarr : */ /* Aehnlichkeit und Unterschied zwischen Zeiger-Arrays u. mehrdim. Arrays */ /* Elemente des Zeiger-Arrays zeigen auf Arrays unterschiedlicher Laenge */ /* zusätzlich Beispiel für die Verwendung von Pointer als Funktionsparameter*/ /*--------------------------------------------------------------------------*/ #include <stdio.h> char caTagfeld[][11]= { "Montag", /* "Dienstag", /* "Mittw", /* "Donnerstag", /* "Freitag", /* "Samstag", "Sonntag" }; Dies ist ein 2-dimensionales Array Die zweite Dimension ist fest 11, die erste wird durch die Initialis. festgelegt mit 7. Für jeden String werden genau 11 Zeichen reserviert */ */ */ */ */ char *paTagzeig[] Dies ist ein eindimensionales Zeigerarray mit genau 7 Zeigern auf Strings als Elemente. Die Strings besitzen unterschiedliche Laengen */ */ */ */ */ = { "Montag", /* "Dienstag", /* "Mittw", /* "Donnerstag", /* "Freitag", /* "Samstag", "Sonntag" }; int iIndxLesen(int *iI, int *iJ) { int iRet = 1; printf("\nZeile ? "); fflush(stdout); if ((scanf("%d",iI)<=0) || (*iI < 0)) iRet = -1; else { printf("Spalte ? "); fflush(stdout); if ((scanf("%d",iJ)<=0) || (*iJ < 0)) iRet= -1; } return iRet; } int main (void) { int iRet = 1; int i,j; char cZa, cZb; while (iIndxLesen(&i, &j)>= 0) { cZa = caTagfeld[i][j]; /* cZb = paTagzeig[i][j]; /* printf("caTagfeld [%2d][%2d] = printf("paTagzeig [%2d][%2d] = } return 0; } Zugriff auf das 2-dimensionale char-Array */ Zugriff über das 1-dimensionale Zeiger-Array */ %c (= %2x hex)\n",i,j,cZa,cZa); %c (= %2x hex)\n",i,j,cZb,cZb); /*------------------------------------------------------------------------------*/ Ergebnis eines Probelaufs: Zeile ? 0 Spalte ? 4 caTagfeld [ 0][ 4] = a (= 61 hex) paTagzeig [ 0][ 4] = a (= 61 hex) Zeile ? 2 Spalte ? 10 caTagfeld [ 2][10] = (= 0 hex) paTagzeig [ 2][10] = e (= 65 hex) Zeile ? -1 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/4/3 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger als Funktionsparameter und Funktionswert – Beispiele 2 /*------------------------------------------------------------------------*/ /* Funktion: tausch */ /*------------------------------------------------------------------------*/ /* Zeiger als Parameter von Funktionen */ /* hier: Vertauschen zweier int-Werte */ /*------------------------------------------------------------------------*/ void tausch(int * piY, int *piZ) { int iZw = *piY; /* Wert 1 merken */ *piY = *piZ; /* Wert 1 mit Wert 2 überschreiben */ *piZ = iZw; /* Wert 2 ist gemerkter Wert 1 */ } /*------------------------------------------------------------------------*/ /* Programm: zeigerfunc (Modul zeigerfunc_m.c) */ /*------------------------------------------------------------------------*/ /* Zeiger als Funktionswert */ /* hier: Ermitteln des maximalen Zahlenwertes in einem double-Array */ /* Rückgabe: Zeiger auf das größte Element */ /*------------------------------------------------------------------------*/ #include <stdio.h> #define UMFANG 4 double *pdMaximum (double adF[],int iU) { double dMax = adF[0]; /* Merkvariable für den Maximalwert vorbelegen */ int iM=0; /* Index des größten Elementes */ int i; for(i=1;i<iU;i++) { if(dMax < adF[i]) { dMax = adF[i]; /* Aktuelles Maximum */ iM = i; /* und sein Index */ } } return &adF[iM]; /* Adresse des Maximalwertes */ } int main(void) { int iA; double adW[UMFANG] = {3.4,7.6,12.2,3.0}; printf("\nMaximalwert des Arrays: %10.3f", *pdMaximum(adW,UMFANG)); *pdMaximum(adW,4) = 0; /* So kann auch ein Funktionsaufruf auf der */ /* linken Seite einer Zuweisung sinnvoll sein! */ for (iA=0;iA<UMFANG;iA++) { printf("\n Element %d: %10.3f",iA,adW[iA]); } return 0; } /*------------------------------------------------------------------------*/ Probelauf von zeigerfunc : Maximalwert des Arrays: Element Element Element Element 0: 1: 2: 3: 3.400 7.600 0.000 3.000 12.200 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/4/4 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger auf Funktionen • Eine Zeigervariable kann als Wert auch die Anfangsadresse einer Funktion besitzen ⇒ Zeiger auf Funktionen Es ist möglich: - Zeigervariable, die auf Funktionen zeigen, zu vereinbaren - Funktionszeigervariablen Werte zuzuweisen - Arrays von Funktionszeigern zu definieren - Zeiger auf Funktionen als Parameter zu übergeben - Zeiger auf Funktionen als Funktionswert zurückzugeben. • Vereinbarung von Funktionszeigervariablen ◇ anzugeben sind : ▻ Typ des Funktionswerts der Objekt-Funktion ▻ Typ und Anzahl der Parameter der Objekt-Funktion ◇ Beispiel: double (*pfFunc)(int) /* pfFunc ist ein Zeiger auf eine Funktion, die einen int-Parameter erwartet und einen double-Wert zurückliefert */ ACHTUNG: Die runden Klammern (*pfFunc) sind zwingend erforderlich – vgl. Prioritäten! • Wertzuweisung an eine Funktionszeigervariable ◇ Wie bei Arrays, so wird auch bei Funktionen der Name allein als Zeiger auf die Funktion interpretiert (keinen &-Operator anwenden, Funktionsname ist Adresskonstante !). ◇ Beispiel: double dF1(int); /* Deklaration der Funktion dF1(..) double (*pfFunc)(int); /* Definition der Funktionszeigervariablen pfFunc pfFunc = dF1; /* Zuweisung der Adresse der Funktion dF1(..) /* als Wert an die Variable pfFunc */ */ */ */ • Aufruf einer Funktion über einen Funktionszeiger ◇ Der Funktionszeiger ist mittels * zu dereferenzieren und mit einer aktuellen Parameterliste auszustatten. ◇ Beispiel: dWert = (*pfFunc)(2*i); /* Aufruf der durch pfFunc referierten Funktion */ • Funktionszeiger als Funktionswert ◇ Beispiel: int (* pGift (char *))( int); /* extrem fortgeschrittene Programmierung!!! */ pGift ist eine Funktion, die einen char-Zeiger als Parameter erwartet und einen Zeiger auf eine Funktion zurückliefert, die ihrerseits einen int-Parameter erwartet und deren Rückgabewert den Typ int besitzt. • Anwendungen : ◇ Realisierung allgemeiner anwendbare Funktionen durch Übergabe von ▻ Funktionszeigern als Parameter ( z.B. Anwendung der Integration auf verschiedene Funktionen) ▻ Zeiger auf ein Array von Funktionszeigern (Realisierung von "Menüs") HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/4/5 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger auf Funktionen - Beispiel ∙ Beispiel :Simpson-Integration ◇ Ermittlung des Integrals einer Funktion f(x) zwischen x0 und x n ◇ Annäherung des Kurvenverlaufs von f(x) durch Parabeln, die jeweils durch 3 aufeinanderfolgende Stützstellen verlaufen. Stützstellen : x0 ... x n (n gerade!) Abstand der Stützstellen : h = ( x n − x0 ) / n (= Schrittweite) ◇ Der Flächeninhalt zwischen f(x) und der x-Achse (= Integral) ergibt sich näherungsweise zu: F = h / 3 * ( f ( x0 ) + 2 * n / 2 −1 ∑ k =1 n/2 f ( x2 k ) + 4 * ∑ f ( x2 k −1 ) + f ( xn ) ) k =1 ◇ Iterative Verbesserung der Genauigkeit dieses Näherungswertes durch Erhöhung der Anzahl n der Stützstellen. Abbruch der Iteration, wenn ein vorgegebenes Genauigkeitskriterium erreicht ist. ◇ Struktogramm : HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/4/6 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger auf Funktionen – Programmcode des Beispiels /* Headerdatei simpson.h - Vereinbarungen für das Programm simpint */ #ifndef SIMPSON_H_ #define SIMPSON_H_ #define #define #define #define EPSILON ANZFUNC Xu -1. Xo 1. 1.e-8 3 double dKreis (double); double dPoly (double); double d3Eck (double); /* /* /* /* Genauigkeit */ Anz. der zu integrierenden Funktionen */ Default fuer untere Integrationsgrenze */ Default fuer obere Integrationsgrenze */ /* zu integrierende Funktionen */ double dSimpson (double(*)(double), double, double); /* Integrationsfunktion */ #endif /* SIMPSON_H_ */ /* /* /* /* /* /* /* /* /* -----------------------------------------------------------------------Modul simpson.c Integration von Funktionen der Form y = f(x) mittels Simpsonregel -----------------------------------------------------------------------Ein: pdFunc - Zeiger auf Funktion der Form y = f(x) dX0 - Startwert (erste Stuetzstelle) dXn - Endwert (letzte Stuetzstelle) Aus: Flaeche ueber der x-Achse ------------------------------------------------------------------------ #include <math.h> #include "simpson.h" double dSimpson( double (*pdFunc)(double), double dX0, double { long int i; double dFl = 0.; /* vorheriger Integralwert double dFa = 0.; /* aktueller Integralwert double dH; /* Schrittweite double dX; /* X-Wert double dY; /* Funktionswert double dg; /* Gewicht double dErr; /* relativer Fehler long int n = 2; /* Intervallzahl dXn ) */ */ */ */ */ */ */ */ /* Schleife ueber jeweils verdoppelte Intervallanzahl */ /* Abbruch, wenn Genauigkeit EPSILON erreicht ist */ do { dH = (dXn –dX0)/n; /* Schrittweite bestimmen */ dFa = (*pdFunc)(dX0) + (*pdFunc)(dXn); for (i=1; i < n; i++) /* Flaeche der akt. Intervallzahl */ { dX = dX0 + dH*i; dY = (*pdFunc)(dX); if (i%2 == 1) dg = 4.; else dg = 2.; dFa += dg*dY; } dFa *= dH/3; dErr = fabs((dFa -dFl)/dFa); n*=2; /* Verdoppeln der Intervallzahl */ dFl = dFa; } while ( dErr > EPSILON); return dFa; } */ */ */ */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/04/4/7 – TH/KR – 07 --------------------------------------------------------------------------------------------------------------------------------------------------------- Zeiger auf Funktionen – Programmcode des Beispiels, Forts. /* Modul simpint_m.c */ /* Modul mit main()-Funktion und Beispielfunktionen für das Programm simpint*/ /* Programmbeispiel: Simpson-Integration von Funktionen der Form y = f(x) */ #include <stdio.h> #include <math.h> #include "simpson.h" /* ------------------------------------------------------------------------ */ /* oberer Halbkreis mit Radius 1 Flaeche ergibt Pi/2. */ double dKreis (double dX) { double dR = 1.; double dY = 0.0; if (dX <= 1. && dX >=-1 dY = sqrt (dR*dR - dX*dX); return dY; } /* ------------------------------------------------------------------------ */ /* Dreieck mit (-1,0) (0,1) (1,0) Flaeche ergibt 1.0 */ double d3Eck (double dX) { double dY = 0.0; if (dX <= 1. && dX >= -1.) if (dX >= 0) dY = (1 - dX); else dY = 1 + dX; return dY; } /* ------------------------------------------------------------------------ */ /* Polynom 2. Grades */ double dPoly(double dX) { return (dX*dX-6*dX+1); } /* ------------------------------------------------------------------------ */ int main(void) { double dXo = XO; double dXu = XU; double dF; /* hier : feste Integrationsgrenzen */ /*Funktionszeiger-Array*/ double (*pfaA[ANZFUNC])(double) = {dKreis, d3Eck, dPoly}; int i=1, bBed; do { printf(" Auswahl: "); scanf("%d",&i); bBed = i>0 && i <= ANZFUNC; /* Formulierung der Abbruchbedingung */ if(bBed) { dF = dSimpson (pfaA[i-1],dXu,dXo); printf (" Xu: %8.4f, Xo: %8.4f, F: %12.8f \n", dXu,dXo,dF); } } while(bBed); return 0; } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/05/1 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Strukturen in C (1) ● Aufbau von Strukturen ◇ Strukturen (structures) in C sind selbstdefinierte zusammengesetzte Datentypen. Sie bestehen aus einer festen Anzahl von Komponenten, die von unterschiedlichen Typen (auch wieder zusammengesetzte Typen) sein können. mehrere Daten der unterschiedlichsten Art lassen sich somit in einer einzigen Variablen zusammenfassen und damit unter einem einzigen Namen ansprechen. Aufbau von sehr effektiv handhabbaren problemangepassten Datenstrukturen. ◇ Beispiel : Zusammenfassung relevanter Personaldaten, wie - Name char name[20]; - Vorname char vname[20]; - Geburtsdatum int jahr; char monat[4]; short tag; - Familienstand char famstand; ◇ Die einzelnen Komponenten (Felder) einer Struktur werden nicht – wie bei Arrays – durch einen Index sondern durch einen Namen angesprochen. ● Vereinbarung von Strukturen ◇ Auflistung der einzelnen Komponenten (Felder) unter Angabe von Typ und Namen. ◇ Eine Strukturvereinbarung kann in drei Formen geschehen: - reine Strukturtypdefinition (ohne Angabe einer Variablenbezeichnung) Der dabei eingeführte Typname (structure tag) kann im weiteren für die Vereinbarung von Strukturvariablen verwendet werden. - reine Strukturvariablenvereinbarung (ohne Angabe eines Strukturnamens) Definition eines namenlosen Strukturtyps. - gleichzeitige Strukturtypdefinition und Strukturvariablenvereinbarung ◇ Syntax der Strukturvereinbarung : struct Strukturname { Komponentenangabe Variablenbezeichner , Komponentenangabe: Datentypangabe Komponentenbezeichner , : ; } ; HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/05/2 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Strukturen in C (2) ● Variablenvereinbarung mit zuvor definiertem Strukturnamen: struct Strukturname Variablenbezeichner ; , "struct Strukturname" ist die Datentypangabe ! ● Beispiele : /*Definition eines Structurtypnamens */ struct datum { int tag; char monat [4]; int jahr; }; /* Variablenvereinbarung unter Verwendung des obigen Strukturtyps */ struct datum geb_datum; /* Variablenvereinbarung eines "namenlosen" Struturtyps */ struct { int tag; char monat [4]; int jahr; } geb_datum; /* Definition eines Strukturtypnamens und Vereinbarung einer Variablen dieses Typs */ struct datum { int tag; char monat [4]; int jahr; } geb_datum; /* Strukturtyp mit einer Struktur als Komponente */ struct person { char name[20], vname[20]; struct { int tag, char monat[4]; int jahr; } geb_datum; char famstand; } student, diplomand; /* oder : Verwendung des Strukturtypnamens struct datum */ struct person { char name[20], vname[20]; struct datum geb_datum; char famstand; } student, diplomand; HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/05/3 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Operationen mit Strukturen • Anwendung des Adressoperators & Auf Strukturvariable lässt sich, ganz analog zu einfachen Variablen, der Adressoperator & anwenden. Die so gebildete Adresse kann einer Zeigervariablen zugewiesen werden, die als Zeigervariable dieses Strukturtyps vereinbart sein muss. Beispiel: struct person /* Definition des Strukturtyps */ { char name[20], vname[20]; struct datum geburtstag; /* Kompon. ist selbst strukt. Datentyp */ char famstand; } struct person sStudent, sDiplomand, *psPersZeig; ... psPersZeig = &sStudent; • Zugriff zu Strukturkomponenten ◇ Direkter Zugriff zur Komponente mittels Element-Operator "." Beispiel : ... scanf("%s", sStudent.name); sStudent.vorname[0] = 'F'; sStudent.geburtstag.jahr = 2001; ◇ Zugriff zur Komponente eines Struktur-Zeiger-Objektes mittels Objektelement-Operator "->". Es gilt folgende Äquivalenz : sptr->komp entspricht (*sptr).komp Beispiel : .... scanf("%s", psPersZeig->name); psPersZeig->vorname[0] = 'F'; psPersZeig->geburtstag.jahr = 2001; ◇ Die Operatoren "." und "->" haben höchste Priorität (gleiche Priorität wie "()" und "[]"), ihre Assoziativität ist von links nach rechts. ◇ Strukturkomponenten können genauso behandelt werden wie einfache Variable ihres Typs. • Wertzuweisung an Strukturvariable Zwischen Strukturvariablen ist eine geschlossene Zuweisung möglich: Beispiel : sDiplomand = sStudent; • Initialisierung von Strukturvariablen ist durch eine Liste von konstanten Ausdrücken – entsprechend den Komponenten – möglich. Beispiel: struct datum geburtstag = {23, "Mai", 1983 }; • Strukturen als Funktionsparameter und Rückgabewerte von Funktionen Strukturen sind als Funktionsparameter und Rückgabewerte von Funktionen zulässig. ACHTUNG : Bei größeren Strukturen ist es meist effizienter Zeiger auf Strukturen als Parameter zu verwenden, um umfangreiches Kopieren beim Funktionsaufruf zu vermeiden! HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/05/4 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel zu Strukturen /*-----------------------------------------------------------------------*/ /* Programm: complmult (Modul complmult_m.c) */ /*-----------------------------------------------------------------------*/ /* Darstellung komplexer Zahlen durch strukturierten Datentyp */ /* Komplexe Rechnung: Multiplikation */ /*-----------------------------------------------------------------------*/ #include <stdio.h> /* Typdefinition zur Darstellung komplexer Zahlen */ struct complex { double re; double im; }; struct complex liesComplex( void ) { struct complex sL; printf ("\n Realteil: "); scanf ("%lf",&sL.re); printf ( " Imaginaerteil: "); scanf ("%lf",&sL.im); return sL; } void ausComplex (char * text, struct complex sWert) /* Form: (a + j*b) */ { char cSign; if ( sWert.im >= 0 ) cSign = '+'; else { cSign = '-'; sWert.im *= -1; } printf("%s(%.3f %c j*%.3f)",text, sWert.re, cSign, sWert.im); } struct complex multComplex(struct complex sV1,struct complex sV2) { struct complex sErg; sErg.re = sV1.re*sV2.re - sV1.im*sV2.im; sErg.im = sV1.re*sV2.im + sV1.im*sV2.re; return sErg; } int main (void) { struct complex scWert1, scWert2, scWert3; char cZeich; do { printf("Erste Zahl:"); scWert1 = liesComplex(); printf("Zweite Zahl:"); scWert2 = liesComplex(); scWert3 = multComplex(scWert1,scWert2); ausComplex("\n",scWert1); ausComplex(" * ",scWert2); ausComplex(" = ",scWert3); printf("\n weiter? (j/n) "); do cZeich = getchar(); while (cZeich == '\n'); } while(cZeich == 'j' || cZeich == 'J'); return 0; } Probelauf : Erste Zahl: Realteil: Imaginaerteil: Zweite Zahl: Realteil: Imaginaerteil: 2.5 1.0 3.0 -1.5 (2.500 + j*1.000) * (3.000 - j*1.500) = (9.000 - j*0.750) weiter? (j/n) n HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/05/5 – TH/KR – 07 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel zu Strukturen als Arraykomponenten /* /* /* /* /* ------------------------------------------------------------------------------Programm telbuch : Suche Rufnummer aus einem Telefonbuch ------------------------------------------------------------------------------Beispiel zu Arrays deren Komponenten von einem strukturierten Datentyp sind ------------------------------------------------------------------------------- #include <stdio.h> #include <string.h> #define MAXANZ 1000 #define MAXNL 20 #define MAXAL 40 struct { char char char long }; eintrag name[MAXNL]; vorname[MAXNL]; anschrift[MAXAL]; nummer; */ */ */ */ */ /* maximale Anzahl von Eintraegen */ /* maximale Laenge von Namen u. Vornamen */ /* maximale Laenge einer Anschrift */ /* hier nur aus Platzgruenden keine symbol. Konst. */ /* fuer die Array-Groessen definiert */ int readBuch(struct eintrag * ,int ); /* Holt aktuelles Telefonbuch vom Massenspeicher - wird spaeter realisiert Rückgabe : Anzahl gelesener Eintraege */ long lSuchNummer(struct eintrag * telbuch, int iMax) { char suchname[MAXNL], suchvorn[MAXNL]; int i, gefunden; long lRet = -2L; printf("\"Nachname Vorname\" eingeben: "); if (scanf("%s%s",suchname,suchvorn)==2) { i=gefunden=0; while(i<iMax && !gefunden) { gefunden = (!strcmp(telbuch[i].name, suchname)) && (!strcmp(telbuch[i].vorname, suchvorn)); i++; } if (!gefunden) lRet = -1L; else lRet = telbuch[i-1].nummer; /* Anwendung des Element-Operators */ /* oder Anwendung des Objektelement-Operators: lRet = (telbuch+i-1)->nummer; */ } return lRet; } int main (void) { long lNummer; struct eintrag telefonbuch[MAXANZ]; int aktAnz = readBuch(telefonbuch, MAXANZ); /* Hole aktuelles Telefonbuch vom Massenspeicher */ while ((lNummer = lSuchNummer(telefonbuch,aktAnz)) != -2L) { if (lNummer >= 0) printf("Nummer: %ld\n",lNummer); else printf("Nummer nicht gefunden\n"); } return 0; } Hinweise zur Weiterarbeit: 1) Modifizieren Sie "lSuchNummer()" so, dass nicht die Nummer sondern der Index des Eintrags zurückgeliefert wird. 2) Gestalten Sie nun die Ausgabe so, dass Name,Vorname, Adresse und Rufnummer ausgegeben werden! 3) Erweitern Sie "eintrag" um die Komponente "beruf" - welche Konsequenzen hat diese Maßnahme für den Rest des Programmes? 4) Nehmen Sie den "beruf" in die Ausgabe von 2) auf! HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/06/1 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Definition von Datentypnamen mittels typedef ● "Typdefinition" mittels typedef ◇ C erlaubt die Einführung eigener Typnamen mittels des Schlüsselwortes "typedef". So eingeführte Bezeichner können dann in weiteren Vereinbarungen alleine als Typangabe (wie die Namen der Standardtypen) verwendet werden. ⇒ Typdefinition mittels typedef Anmerkung : Es wird kein neuer Typ, sondern nur ein neuer Name für einen existierenden Typ definiert. ◇ Syntax der Typdefinition typedef Typangabe neuer Typbezeichner ; , ◇ Eine Typdefinition mittels typedef darf überall dort stehen, wo eine Vereinbarung stehen darf also außerhalb jeder Funktion, im Vereinbarungsteil des Funktionsrumpfes und im Vereinbarungsteil eines Blocks. ◇ Die Typdefinition weist eine formale Ähnlichkeit mit der Variablenvereinbarung auf: ▻ Die neue Typbezeichnung steht dort, wo in der Variablenvereinbarung der Bezeichner für die Variable steht. ▻ Das Schlüsselwort typedef steht dort, wo in der Variablenvereinbarung die Speicherklassenangabe steht. ▻ Aus diesem Grund wird typedef syntaktisch auch als Speicherklassen-Spezifizierer bezeichnet (obwohl es eine ganz andere Bedeutung und Wirkung besitzt). ◇ Mittels typedef ist die Definition neuer Namen für beliebige existierende Typen, auch für Zeigertypen (inklusive Zeiger auf Funktionen) möglich. ● Anwendung ▻ Parametrisierung eines Programmes gegen Portabilitätsprobleme durch Definition von Typnamen für maschinenabhängige Datentypen. Diese werden meist in eigenen Header-Dateien aufgeführt. Beispielsweise sind in der Headerdatei <stdint.h> (ANSI-C 99) Ganzzahltypen mit exakter Größe definiert, u.a. (hier Visual-C) : typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; Bei der Portierung solcher Programme sind dann lediglich diese Header-Dateien entsprechend anzupassen. In mehreren Headerdateien der ANSI-C-Standardbibliothek findet sich z.B. für den "natürlichen unsigned-Typ" die folgende Definition : typedef unsigned int size_t; ▻ Erhöhung der Übersichtlichkeit und Lesbarkeit eines Quelltextes durch die Wahl von problemangepassten und treffenden Typnamen selbstdokumentierend! HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/06/2 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiele zur Definition von Datentypnamen mittels typedef ● Statt struct { int char int }; struct datum tag; monat[4]; jahr; /* Typdefinition */ datum geb_datum; /* Variablenvereinbarung */ kann man auch formulieren : typedef struct { int tag; char monat[4]; int jahr; } Datum; Datum geb_tag; /* Typdefinition */ /* Typname !! */ /* Variablenvereinbarung */ ● Die Anwendung von typedef ist nicht auf struct-Typen begrenzt, sondern ist für alle Datentypen zulässig Beispiele : typedef char Wort[30]; typedef float Real; /* Typdefinition */ /* Typdefinition */ Wort name; Real a; /* Variablenvereinbarung */ /* Variablenvereinbarung */ ● Auch die Definition von Zeigertypen mittels typedef ist möglich (einschließlich Zeiger auf Funktionen) Beispiele : typedef struct { double re; double im; } Complex; typedef Complex *ComplPtr; /* Definition des Typs "ComplPtr" als /* Zeiger auf "Complex" */ */ typedef double (*Pfd)(char*, char*); /* nur zur Demonstration: */ /* Definition des Typs "Pfd" als Zeiger auf*/ /* eine Funktion, die zwei char-Zeiger als */ /* Parameter besitzt und einen double-Wert */ /* zurückliefert */ Pfd myFktPtr; /* Vereinbarung einer Variablen dieses Typs*/ Aufgabe : Formulieren Sie den Datentyp Person mit einer Komponente des obigen Typs Datum so, dass folgende Variablenvereinbarung möglich ist: Person sStudent = { "Moor", "Moritz", "Muenchen", { 17,"Jun",1995 }, 'l' }; HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/07/1 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Aufzählungstypen und Aufzählungskonstante ● Aufzählungskonstante (enumeration constants) ◇ Aufzählungskonstante ermöglichen die Definition von symbolische Ganzzahl-Konstanten und bilden damit in vielen Fällen eine Alternative zur Festlegung symbolischer Konstanter mit Hilfe der PräprozessorDirektive #define. ◇ Aufzählungskonstante sind immer vom Typ int und werden immer gleichzeitig mit der Definition eines Aufzählungstyps festgelegt. ● Aufzählungstypen ◇ Syntax der Aufzählungstyp-Definition: enum Typbezeichner Aufzählungsliste { } ; Aufzählungsliste: Konstantenname = konstanter Ausdruck , ◇ Werden nur Konstantennamen in der Aufzählungsliste angegeben, so wird dem ersten Namen der Wert 0, dem zweiten Namen der Wert 1 usw. zugeordnet. ◇ Wird ein Konstantenname mittels "=" mit einem konstanten Ausdruck gleichgesetzt, so wird ihm der Wert dieses Ausdrucks zugeordnet. Die nachfolgenden Konstantennamen erhalten dann in der Reihenfolge ihrer Auflistung den jeweils nächsten int-Wert zugeordnet, wenn sie nicht selbst explizit mit einem "=" in ihrem Wert festgelegt werden. Aufzählungstypen bilden einen Teilbereich von int. ◇ Der Aufzählungstyp kann einen Typnamen erhalten. Damit lassen sich auch Variable dieses Typs definieren Aufzählungsvariable. Diese werden die in C wie Variable des Typs int behandelt. ◇ Wird kein Typname angegeben, so handelt es sich um die Definition eines namenlosen Typs. ⇒ nur Definition der Aufzählungskonstanten ! ● Beispiele: enum { MON, DIE, MIT, DON, FRE, SAM, SON }; enum Monat { JAN = 1, FEB, MAR, APR, MAI, JUN, JUL, AUG, SEP, OKT, NOV, DEZ }; enum Monat myMonat typedef enum Monat Month Month wonnemonat = MAI; enum Bool { FALSE, TRUE }; /* nur Aufzählungskonstante */ /* Typdefinit.: enum Monat */ /* Definition der Variablen myMonat */ /* Definition des neuen Typnamnes Month */ /* Definition und Initialisierung einer Variablen*/ /* Möglichkeit einen log. Datentyp einzuführen */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/07/2 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel : Auswertung arithmetischer Ausdrücke ● Beschreibung arithmetischer Ausdrücke ◇ Beispiel : x = 1.7+5*(4.5-3)/((2.6+1.3)*3)-2.1 ◇ Ein arithmetischer Ausdruck wie der obige kann allgemein beschrieben werden, entweder mit den schon bekannten Syntaxdiagrammen (graphische Notation) , oder mittels einer textuellen Notation, der sogenannten Backus-Naur-Form. : Ausdruck Term Faktor Zahl Ziffer ::= ::= ::= ::= ::= Term { + Term | - Term } Faktor { * Faktor | / Faktor } Zahl | ( Ausdruck ) [+|-]Ziffer {Ziffer}[.{Ziffer}] 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Dabei bedeutet: ::= "wird definiert als" {...} Was in geschweiften Klammern steht, darf beliebig oft wiederholt werden (bzw. auch fehlen). [...] Was in eckigen Klammern steht, darf auch fehlen. | Alternative Auswahl, z. B. kann "Faktor" entweder eine Zahl oder ein Ausdruck in runden Klammern sein. Die Ziffern 0...9 sowie die Zeichen '(', ')', '+', '-', '*', '/' sind sogenannte Terminalsymbole. ● Aufgabe: Auswertung arithmetischer Ausdrücke der obigen Form mittels eines Formelinterpreters. Es gilt – wie allgemein in der Arithmetik vorgeschrieben – auch hier "Punkt vor Strich". ● Zur Programmierung: ◇ Die obigen Definitionen – für Ausdruck, Term, Faktor und Zahl – werden nachfolgend in einer Art "Pseudocode" formuliert, der der Programmiersprache C nachempfunden ist.. Dabei wird immer nur das jeweils nächste Eingabezeichen betrachtet, das in einer global definierten Variablen c abgelegt wird. Ausdruck: { wert = Term; solange (c in ['+','-']) { falls (c == '+') wert = wert + Term; falls (c == '-') Wert = wert - Term; } Ausdruck = wert; } Term: { wert = Faktor; solange (c in ['*','/']) { falls (c == '*') wert = wert * Faktor; falls (c == '/') Wert = wert / Faktor; } Term = wert; } Faktor: { Lies ein Zeichen c von der Eingabe; wenn (c in ['0'..'9']) wert = Zahl; wenn (c == '(') { wert = Ausdruck; Lies schließende Klammer; } Faktor = Wert; } Zahl: { Lies eine Zahl mit Vorzeichen zeichenweise ein; } ◇ Das jeweils nächste – von Blank (' ') verschiedene – in Zeichen wird durch eine Funktion int lies() in die globale Variable c abgelegt. Je nach Definition dieser Funktion können die den zu interpretierenden Ausdruck bildenden Zeichen verschiedenen Eingabequellen entnommen werden (z.B. aus der StandardEingabe oder aus einem String) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/07/3 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel : Auswertung arithmetischer Ausdrücke (Forts. 1) ● Programm: // C-Headerdatei arithmausdr.h #ifndef ARITHMAUSDR_H_ #define ARITHMAUSDR_H_ enum Bool_t { FALSE, TRUE }; /* logischer Datentyp float float float float int /* Funktions-Deklarationen */ ausdruck(void); term(void); faktor(void); zahl(void); lies(void); */ #endif /* ARITHMAUSDR_H_ */ // C-Quelldatei arithmausdr_m.c // Modul mit main()-Funktion fuer das Programm arithmausdr // Programm zur Auswertung arithmetischer Ausdrücke (Formelinterpreter) #include <stdio.h> #include <stdlib.h> #include "arithmausdr.h" /* Definition globaler Variabler - Speicherklasse extern */ int c; /* eingelesenes Zeichen global! */ enum Bool_t fehler = FALSE; /* globale Fehlervariable */ int cfehl; /* fehlerhaftes Zeichen global ! */ int main(void) { float wert; /* Ergebniswert */ do { printf("Eingabe : "); fflush(stdout); /* Eingabeaufforderung */ wert = ausdruck(); /* Einlesen und Berechnen */ if((c != '\n') && (c!=EOF)) { fehler = TRUE; cfehl = c; while(((c=lies())!='\n') && (c!=EOF)); } if (c!=EOF) { if (fehler) { printf("Syntaxfehler : Char:%02X\n\n",cfehl); fflush(stdout); fehler = FALSE; } else printf("Ergebnis: %0.5f\n\n",wert); fflush(stdout); /* 5 Nachpunktstellen */ } } while (c != EOF); /* Ende mit Ctrl-Z bzw Ctrl-D */ return EXIT_SUCCESS; } int lies(void) /* Einlesen eines Zeichens, Leerzeichen uebergehen */ { int cA; do { cA = getchar(); } while(cA == ' '); return(cA); } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/07/4 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel :Auswertung arithmetischer Ausdrücke (Forts 2.) // C-Quelldatei arithmausdr_func.c // Modul mit Funktionen fuer das Programm arithmausdr #include "arithmausdr.h" /* Deklaration externer Variabler */ extern int c; /* eingelesenes Zeichen global! */ extern enum Bool_t fehler; /* globale Fehlervariable */ extern int cfehl; /* fehlerhaftes Zeichen global ! */ float ausdruck(void) /* Ausdruck ::= Term { +Term || -Term } { float wert; wert = term(); while (c == '+' || c == '-') { if (c == '+') wert = wert + term(); else wert = wert - term(); } return wert; } */ float term(void) /* Term ::= Faktor { *Faktor || /Faktor } */ { float wert; wert = faktor(); while (c == '*' || c == '/') { if (c == '*') wert = wert * faktor(); else wert = wert / faktor(); } return wert ; } float faktor(void) /* Faktor :== Zahl || ( Ausdruck ) { float wert=0.0; c = lies(); /* erst hier wird gelesen !!! if (('0' <= c && c <= '9') || (c == '-') || (c == '+')) wert = zahl(); else if (c == '(') /* rekursiver Aufruf !!! { wert = ausdruck(); if (c!=')') /* naechstes Zeichen muss ')' sein { fehler = TRUE; /* falls nicht --> Fehler cfehl = c; } else c = lies(); } else /* weder Zahl noch '(' --> Fehler { fehler = TRUE; cfehl = c; } return wert; } // Fortsetzung der Quelldatei arithmausdr_func.c siehe nächse Seite */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/07/5 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel :Auswertung arithmetischer Ausdrücke (Forts 3.) // Fortsetzung der C-Quelldatei arithmausdr_func.c float zahl(void) /* Zahl ::= [+|-] Ziffer {Ziffer} [.{Ziffer}] { enum Bool_t negativ = FALSE; /* Merker fuer neg. Vorzeichen enum Bool_t bruch = FALSE; /* Merker fuer Dezimalpunkt float wert=0.0, teiler= 1.0; if ((c == '+') || (c== '-')) /* Vorzeichen behandeln { if (c == '-') negativ = TRUE; c = lies(); } if ((c<'0' || c>'9') && (c!='.')) { fehler = TRUE; /* keine Zahl --> Fehler cfehl = c; } else { /* Zahl konvertieren */ while ((c >= '0' && c <= '9') || (!bruch && (c == '.'))) { if (c == '.') /* Dezimalpunkt bruch = TRUE; else { wert = wert * 10.0f + (c - '0'); /* weitere Stellen dazu if (bruch) teiler = teiler*10.0f; /* fuer Nachkommastellen } c = lies(); /* naechstes Zeichen } } if (negativ) wert = -wert; return(wert/teiler); /* teiler nur dann !=1.0, wenn bruch==TRUE } ● Beispiel eines Probelaufs: Eingabe: Ergebnis: 1.7+5*(4.5-3)/((2.6+1.3)*3)-2.1 0.24103 Eingabe : 2.3++3 Ergebnis: 5.30000 Eingabe : 2.3+++3 Syntaxfehler : Char:2B Eingabe : 4.7*(3.4-2.1 Syntaxfehler : Char:0A Eingabe : (4.5-2+3.1)*(7.9-(3+5.6*2.4)) Ergebnis: -47.82401 Eingabe : 8.2/5*(2-7-6.3)) Syntaxfehler : Char:29 */ */ */ */ */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 05/08/1 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Der sizeof-Operator in C ● unärer Operator sizeof ◇ Die Auswertung erfolgt zur Compilezeit! ◇ Der Operator dient zur Ermittlung der Größe des Speicherplatzes in Bytes, den ein Objekt des Typs seines Operanden belegt. • Operand kann sein: ▻ Ausdruck z.B. sizeof sizeof sizeof sizeof i 3 (3) (a+b) Der Ausdruck wird nicht numerisch ausgewertet, es wird nur sein Typ bestimmt. ▻ in Klammern gesetzte Typangabe z.B. sizeof (int) sizeof (double) sizeof (char *) ● Ergebnis der Operation ist vom Typ size_t size_t ist der implementierungsabhängige "natürliche" unsigned-Ganzzahl-Typ, er ist u.a. in den Header-Dateien <stddef.h> und <stdlib.h> definiert. ● Anwendung : ▻ Ermittlung des Platzbedarfs zusammengesetzter Datentypen, wie "structures" und "arrays" ▻ zur portablen Angabe des Platzbedarfs der Standard-Datentypen Die genaue Größe des von Objekten belegten Speicherplatzes wird z.B. für eine dynamische Speicherallokation oder zum Schreiben von Binärdateien benötigt. ● Beispiel : /* Programm typesize (Modul typesize_m.c) /* Demonstrationsprogramm zum sizeof-Operator /* Ausgabe der Speichergrößen der einfachen Datentypen und von Pointern */ */ */ #include <stdio.h> #define MAX 17 int main(void) { int i=6, aiM[MAX]; printf("\nchar printf("short printf("int printf("unsigned printf("long printf("float printf("double printf("long double printf("char-Pointer printf("int-Pointer printf("Arraygroesse printf("Arraygroesse return 0; } %2u Bytes\n", sizeof(char)); /* %2u Bytes\n", sizeof(short)); /* %2u Bytes\n", sizeof i); /* %2u Bytes\n", sizeof(unsigned)); /* %2u Bytes\n", sizeof(long)); /* %2u Bytes\n", sizeof(float)); /* %2u Bytes\n", sizeof(double)); /* %2u Bytes\n", sizeof(long double)); /* %2u Bytes\n", sizeof(char*)); /* %2u Bytes\n", sizeof(int*)); /* %2u Bytes\n", sizeof aiM /* %2u Elemente\n", sizeof aiM/sizeof aiM[0]); --> 1 */ --> 2 */ --> 4 */ --> 4 */ --> 4 */ --> 4 */ --> 8 */ --> 12 */ --> 4 */ --> 4 */ --> 68 */ /* 17 */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/00/0 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 6. Dateibearbeitung in C 6.1. Dateibearbeitungskonzept von ANSI-C 6.2. Grundlegende Funktionen zur Dateibearbeitung 6.3. Funktionen zum Datentransfer (Schreiben und Lesen) 6.4. Funktionen zur Dateiverwaltung 6.5. Beispiel zu Binärdateien HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/01/1 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitungskonzept von ANSI-C - Stream-I/O (1) • Eine Programm- Ein- und Ausgabe erfolgt üblicherweise aus/in Dateien oder von/zu Geräten (z.B. Konsole, Drucker). In C wird kein Unterschied zwischen diesen beiden Gruppen gemacht. Geräte werden als Dateien betrachtet und behandelt ( Dateibearbeitung). • Die Sprache C selbst enthält keine Elemente zur Dateibearbeitung. Jegliche Ein- und Ausgabe in C geschieht mittels Bibliotheksfunktionen. Ihre Deklarationen sind in der Headerdatei <stdio.h> enthalten. • Den in der ANSI-C-Standardbibliothek definierten Funktionen zur Dateibearbeitung ( Programm-Ein/Ausgabe) liegt das Stream-Model zu Grunde ( Stream-I/O): Jede zu bearbeitende Datei bzw. jedes Gerät wird logisch als Stream - als kontinuierliche geordnete Folge von Bytes - betrachtet. • Häufig wird der Begriff Stream mit den Begriffen Datei (file) – unter Einschluss von Geräten – gleichgesetzt. • Referiert wird ein Stream über einen Filepointer. Dies ist ein Zeiger auf eine programminterne - vom Startup-Code bereitgestellte - Variable eines StrukturTyps mit dem Typnamen FILE. In Variablen dieses Typs sind die von den Bibliotheksfunktionen für die Bearbeitung einer Datei bzw. für Ein-/Ausgabe von/zu Geräten benötigten Verwaltungsinformationen zusammengefasst. • Der genaue Aufbau des Datentyps FILE ist betriebssystem- und implementierungsabhängig. Er ist in <stdio.h> definiert. Der Aufbau braucht dem Benutzerprogramm aber nicht bekannt zu sein. Das Benutzerprogramm sollte keinesfalls direkt zu der über einen Filepointer referierten Variablen zugreifen, sondern nur Filepointer als aktuelle Parameter für die Bibliotheksfunktionen verwenden. Folgende Informationen enthält eine Variable vom Datentyp FILE zumindest: - Referenz zur Datei im Dateisystem des Betriebssystems - Zeiger auf den Puffer, über den der Dateizugriff erfolgt - Information über die aktuelle Bearbeitungsposition in der Datei - Information über die Zugriffsart - Kennungen für das Auftreten von Fehlern und das Erreichen des Dateiendes • Üblicherweise sind die in einem Programm zur Verfügung stehenden Variablen des Datentyps FILE in einem Array angeordnet ( FILE-Array). Die Anzahl der Komponenten dieses Arrays legt fest, wieviele Dateien in einem Programm gleichzeitig geöffnet sein können. Durch das Öffnen einer Datei wird eine Komponente des FILE-Arrays belegt und der entsprechenden Datei zugeordnet. Dabei werden bestimmte Initialisierungsinformationen eingetragen. Die Funktion zum Öffnen der Datei liefert einen Zeiger auf die belegte FILE-Array-Komponente zurück ⇒ Filepointer. Jeder weitere Zugriff zu dieser Datei erfolgt dann über den Filepointer. Durch das Schließen einer Datei wird die Zuordnung zwischen Datei und FILE-Array-Komponente wieder aufgehoben, die FILE-Array-Komponente wird freigegeben der Filepointer wird ungültig. • Die ersten drei Komponenten des FILE-Arrays werden generell beim Programmstart belegt und die damit assoziierten Dateien (tatsächlich Geräte) automatisch geöffnet (Standarddateien, Standardgeräte). In <stdio.h> sind für die entsprechenden Filepointer Namen ( Zeigerkonstante !) definiert: - stdin ⇒ Standardeingabegerät (i.a. Tastatur) - stdout ⇒ Standardausgabegerät (i.a. Bildschirm) - stderr ⇒ Standardfehlerausgabe (i.a. ebenfalls Konsolenbildschirm) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/01/2 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitungskonzept von ANSI-C - Stream-I/O (2) • ANSI-C unterscheidet zwei Dateiformate : ▻ Textdateien Sie bestehen aus einer Folge von Zeilen. Jede Zeile ist logisch mit einem Newline-Zeichen abgeschlossen. (In der realen Datei, auf der Betriebssystemebene, kann durchaus eine andere Art des Zeilenabschlusses vorliegen, gegebenenfalls finden beim Lesen und Schreiben Umsetzungen statt). ▻ Binärdateien Sie bestehen aus einer Folge von Bytes und besitzen keine weitere Struktur. Beim Lesen und Schreiben finden grundsätzlich keine Umsetzungen statt. Dateien können im Text- oder im Binär-Modus geöffnet werden. Die Standarddateien (Standardgeräte) sind alle im Textmodus geöffnet. • Der Dateizugriff mittels der ANSI-C- (Stream-) I/O-Funktionen findet grundsätzlich gepuffert statt. Dabei wird unterschieden zwischen - Zeilenpufferung (nur für Textdateien) und - Blockpufferung (für beliebige Dateien) Die Puffergröße bei Blockpufferung ist durch die in <stdio.h> definierte Konstante BUFSIZ festgelegt. Der Puffer wird defaultmäßig vom System zur Verfügung gestellt und ist dem Programm nicht direkt zugänglich. Es existieren aber Funktionen zum Setzen eines benutzerdefinierten Puffers. Mit diesen Funktionen kann eine Pufferung auch ganz ausgeschaltet werden ungepufferter Dateizugriff. • Zur Kenzeichnung eines Dateiendes wird innerhalb von C-Programmen die Konstante EOF verwendet. Diese Konstante ist nicht identisch mit dem im jeweiligen Betriebssystem gegebenenfalls verwendeten Dateiendezeichen. Vielmehr nehmen die jeweiligen Datei-Lesefunktionen eine entsprechende Umsetzung vor. • In der Standardbibliothek existieren (die Funktionsdeklarationen sind in <stdio.h> enthalten) ▻ Grundlegende Funktionen zur Dateibearbeitung , wie Öffnen und Schließen von Dateien, Positionieren in Dateien, Beeinflussung des Dateipuffers usw. ▻ Funktionen zum Schreiben und Lesen ▻ Funktionen zur Dateiverwaltung ▻ Funktionen zur Fehlerbehandlung • In der Header-Datei <stdio.h> sind neben - den Funktionsdeklarationen, der Definition des Datentyps FILE, der Deklaration des FILE-Arrays , der Definition der Standard-Filepointer und der Definition der Konstanten BUFSIZ insbesondere enthalten : - Definition der Konstanten EOF (end of file) Definition der Konstanten NULL (⇒ NULL-Pointer) Definition des Datentyps size_t (ein ganzzahliger Typ) Definitionen weiterer von den Funktionen verwendeter Konstanten HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/02/1 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Grundlegende Funktionen der C-Standard-Bibliothek zur Dateibearbeitung (1) fopen () - Öffnen einer Datei #include <stdio.h> FILE *fopen(const char *path, const char *mode); Aktion: "fopen()" öffnet die durch "path" bezeichnete Datei und liefert einen Pointer auf die dadurch belegte Komponente des FILE-Arrays (⇒ Filepointer) zurück. Über diesen Filepointer erfolgt jeder weitere Dateibezug. Übergabeparameter: path mode Pfadname der Datei Zugriffsmodus Gueltige Angaben fuer "mode" sind : "r" Lesen (Datei muß existieren) "w" Schreiben (evtl. exist. Datei wird gelöscht) "a" Anhaengen (append) (evtl. exist. Datei wird nicht gelöscht) "r+" Lesen und Schreiben (Datei muß existieren) *) "w+" Schreiben und Lesen (evtl. exist. Datei wird gelöscht) *) "a+" Lesen (an beliebiger Position) und Schreiben am Dateiende *) *) Zwischen Lesen und Schreiben muß jeweils ein explizites Herausschreiben der Dateipuffer erfolgen (Aufruf von fflush) oder die Dateibearbeitungsposition verändert werden (Aufruf von fpos oder rewind). Durch Anhängen der Zeichen 'b' oder 't' kann zusätzlich angegeben werden, ob es sich bei der zu bearbeitenden Datei um eine Binär- oder Textdatei handelt : "...t" Textdatei (Default in ANSI-C) "...b" Binärdatei Funktionswert : Filepointer bzw. NULL-Pointer im Fehlerfall. fclose() - Schließen einer Datei #include <stdio.h> int fclose(FILE *fp); Aktion: "fclose()" schreibt noch nicht herausgeschriebene Filebuffer in die durch "fp" referierte Datei, gibt die Buffer und die FILE-Array-Komponente, auf die fp zeigt, frei und schließt die Datei. Übergabeparameter: fp Funktionswert : Filepointer im Erfolgsfall der Wert "0" bzw. im Fehlerfall der Wert EOF. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/02/2 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Grundlegende Funktionen der C-Standard-Bibliothek zur Dateibearbeitung (2) fseek() - Verändern der Bearbeitungsposition #include <stdio.h> int fseek(FILE *fp, long offset, int origin); Aktion: "fseek()" verändert die aktuelle Bearbeitungsposition der durch "fp" referierten Datei um "offset" Bytes gegenüber der Bezugsposition "origin" ( ⇒ wahlfreier Dateizugriff ! ). Übergabeparameter: fp offset origin Funktionswert: Filepointer Anzahl Bytes relativ zur Bezugsposition Bezugsposition "0", wenn die Bearbeitungsposition wie angegeben verändert werden konnte bzw. ein Wert ungleich "0" im Fehlerfall bzw. undefiniert bei I/O-Geraeten. Die Bezugsposition wird durch einen der folgenden 3 Werte fuer "origin" gekennzeichnet : SEEK_SET (=0) Dateianfang SEEK_CUR (=1) augenblickliche Bearbeitungsposition SEEK_END (=2) Dateiende Achtung : Bei Textdateien muß offset == 0 oder ein von der Funktion "ftell()" zurück gegebener Wert sein, wobei dann origin == SEEK_SET (Dateianfang !) sein muß. rewind() - Zurücksetzen auf den Dateianfang # include <stdio.h> void rewind(FILE *fp); Aktion: "rewind()" setzt die durch "fp" referierte Datei auf den Anfang zurueck. Übergabeparameter: fp Filepointer Funktionswert: keiner es gilt : rewind(fp) == (void)fseek(fp, 0L, SEEK_SET) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/02/3 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Grundlegende Funktionen der C-Standard-Bibliothek zur Dateibearbeitung (3) ftell() - Ermitteln der aktuellen Bearbeitungsposition # include <stdio.h> long ftell(FILE *fp); Aktion: "ftell()" ermittelt die aktuelle Bearbeitungsposition der durch "fp" referierten Datei. Bei Binärdateien entspricht diese Bearbeitungsposition der Anzahl Bytes relativ zum Dateianfang. Bei Textdateien muß das nicht notwendigerweise der Fall sein. Der dafür ermittelte Wert für die Bearbeitungsposition dient aber als sinnvoller "offset" für den Aufruf von "fseek()". Er stellt eine Rückkehr zur Bearbeitungsposition zum Zeitpunkt des Aufrufs von "ftell()" sicher. Übergabeparameter: fp Funktionswert: Filepointer aktuelle Bearbeitungsposition bei Binärdateien : Anzahl Bytes relativ zum Dateianfang bei Textdateien : für "fseek()" verwertbare Information (s. oben) bzw. "-1L" im Fehlerfall bzw. undefiniert bei I/O-Geraeten feof() - Überprüfung auf Dateiende # include <stdio.h> int feof(FILE *fp); Aktion: "feof()" überprüft, ob für die durch "fp" referierte Datei das Dateiende-Flag gesetzt ist. Übergabeparameter: fp Funktionswert: Filepointer ein Wert !=0, wenn das Dateiende-Flag gesetzt ist. "0", wenn das Dateiende-Flag nicht gesetzt ist. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/02/4 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Grundlegende Funktionen der C-Standard-Bibliothek zur Dateibearbeitung (4) fflush() - Herausschreiben eines Dateipuffers #include <stdio.h> int fflush(FILE *fp); Aktion: Wenn "fp" eine Datei referiert, die zum Schreiben oder Update - bei zuletzt durchgeführter Schreiboperation - geöffnet wurde, veranlasst "fflush()", dass der zugehörige Dateipuffer geleert, d.h. an das Betriebssystem zum Herausschreiben in die Datei übergeben wird; die Datei bleibt geöffnet. Andernfalls ist das Verhalten undefiniert. Falls für "fp" der NULL-Pointer übergeben wird, werden die Dateipuffer aller Dateien, für die obige Bedingung erfüllt ist, herausgeschrieben. Übergabeparameter: fp Funktionswert : Filepointer "0", wenn der Puffer erfolgreich geleert werden konnte, bzw EOF, wenn beim Schreiben ein Fehler aufgetreten ist. Anmerkung : Ein Dateipuffer wird automatisch geleert, wenn er voll ist, wenn die Datei geschlossen wird oder wenn das Programm normal beendet wird. setbuf() - Bereitstellen eines benutzerdefinierten Dateipuffers #include <stdio.h> void setbuf(FILE *fp, char *buffer); Aktion: "setbuf()" stellt für die durch "fp" referierte Datei einen durch "buffer" referierten benutzerdefinierten Dateipuffer zur Verfügung. "buffer" muss auf den Anfang eines charArrays der Laenge BUFSIZ (definiert in <stdio.h>) zeigen. Ist "buffer" ==NULL, so erfolgt der Zugriff zur Datei ungepuffert. Übergabeparameter: fp Filepointer buffer Pointer auf benutzerdefinierten Dateipuffer Funktionswert : keiner HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/03/1 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der C-Standard-Bibliothek zum Datentransfer (1) fgetc() - Zeichenweises Lesen getc() - Zeichenweises Lesen #include <stdio.h> int fgetc(FILE *fp); bzw. int getc(FILE *fp); Aktion: "fgetc()" bzw "getc()" liest das nächste Zeichen aus der durch "fp" referierten Datei. Übergabeparameter: fp Filepointer Funktionswert : das gelesene Zeichen (als int-Wert !) bzw. EOF bei Dateiende oder im Fehlerfall es gilt : getc(stdin) == getchar() ! ungetc() - Rückgabe eines Zeichens in den Eingabepuffer #include <stdio.h> int ungetc(int c, FILE *fp); Aktion: "ungetc()" gibt das Zeichen "c" (umgewandelt in unsigned char) in den Eingabepuffer der durch "fp" referierten Datei zurück. Übergabeparameter: c fp Funktionswert : zurueckzugebendes Zeichen Filepointer das zurückgegebene Zeichen bzw EOF im Fehlerfall fputc() - Zeichenweises Schreiben putc() - Zeichenweises Schreiben #include <stdio.h> int fputc(int c, FILE *fp); bzw. int putc(int c, FILE *fp); Aktion: "fputc()" bzw "putc()" schreibt das Zeichen "c" (umgewandelt in unsigned char) in die durch "fp" referierte Datei. Übergabeparameter: c fp zu schreibendes Zeichen Filepointer Funktionswert : das geschriebene Zeichen (als int-Wert !) bzw. EOF im Fehlerfall es gilt: putc(c,stdout) == putchar(c) ! HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/03/2 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel zum zeichenweisen Lesen von Dateien /* /* /* /* /* /* ----------------------------------------------------------------------*/ Programm fcopy (Modul fcopy_m.c) */ ----------------------------------------------------------------------*/ Kopiert den Inhalt der Datei "test.txt" in einen auswählbaren Stream */ --> stdout, "aus.txt", oder Datei mit angebbarem Pfadnamen */ ----------------------------------------------------------------------*/ #include <stdio.h> #include <stdlib.h> void copy_from_to(FILE *, FILE *); int main(void) { char *quelle = "test.txt"; char *ziel = "aus.txt"; FILE *fpa, *fpb; char c, *pdt, dateiname[25]; int iRet = EXIT_SUCCESS; if ((fpa=fopen(quelle,"r")) == NULL) { printf("\nQuelldatei :%s: kann nicht geoeffnet werden\n\n",quelle); iRet = EXIT_FAILURE; } else { do { pdt = ziel; /* Ziel vorbelegen */ printf("Wohin soll kopiert werden?\n"); printf("(1) stdout (2) \"aus.txt\" (3) Neue Datei (E) Exit :"); do c=getchar(); while(c == '\n'); /* Überlesen von '\n' */ switch (c) { case '1': copy_from_to (fpa, stdout); break; case '3': printf("\nZieldatei ? "); scanf("%s",dateiname); pdt = dateiname; case '2': if ((fpb=fopen(pdt,"w")) == NULL) printf("\nAuf %s ist Schreiben nicht moeglich\n\n",pdt); else { copy_from_to(fpa,fpb); fclose(fpb); } break; } rewind(fpa); } while (c != 'e' && c != 'E'); fclose(fpa); } return EXIT_SUCCESS; } void copy_from_to(FILE *fp1, FILE *fp2) { int c; while ((c = fgetc(fp1)) != EOF) fputc(c,fp2); } /* Kopiert zeichenweise */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/03/3 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der C-Standard-Bibliothek zum Datentransfer (2) fscanf() - Formatiertes Lesen #include <stdio.h> int fscanf(FILE *fp, const char *ctrl , ...); Aktion: "fscanf()" liest die nächsten Zeichen aus der durch "fp" referierten (Text-)Datei, interpretiert sie entsprechend den im Steuerstring "ctrl" vorliegenden Typ- und Formatangaben und weist die demgemäß konvertierten Werte den durch ihre Pointer (weitere Parameter !) referierten Variablen zu. Übergabeparametert: fp ctrl ... Filepointer String zur Steuerung des Eingabeformats (vgl. scanf()) weitere Parameter : Pointer auf Variable, die die Eingabewerte aufnehmen sollen Aufbau und Bedeutung von "ctrl" exakt wie bei der Funktion scanf(). Funktionswert : die Anzahl der erfolgreich zugewiesenen Werte bzw EOF wenn – beim Lesen des ersten Werts – versucht wurde über das Dateiende hinaus zu lesen es gilt : fscanf(stdin,ctrl, ... ) == scanf(ctrl, ... ) fprintf() - Formatiertes Schreiben #include <stdio.h> int fprintf(FILE *fp, const char *ctrl, ...); Aktion: "fprintf()" gibt die als weitere Parameter übergebenen Werte entsprechend den Formatangaben in "ctrl" als Zeichenfolgen in die durch "fp" referierte (Text-) Datei aus. Übergabeparametetr: fp ctrl ... Filepointer String zur Festlegung des Ausgabeformats (vgl. printf()) weitere Parameter: Ausgabewerte, Anzahl und Typ entsprechend den Konvertierungsanweisungen in "ctrl". Aufbau und Bedeutung von "ctrl" exakt wie bei der Funktion printf(). Funktionswert : Anzahl der ausgegebenen Zeichen es gilt : fprintf(stdout,ctrl, ... ) == printf(ctrl, ... ) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/03/4 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der C-Standard-Bibliothek zum Datentransfer (3) fgets() - Zeilenweises Lesen #include <stdio.h> char *fgets(char *s, int n, FILE *fp); Aktion: "fgets()" liest die nächste Zeile (einschliesslich '\n') aus der durch "fp" referierten Datei, hoechstens jedoch (n-1) Zeichen. Die gelesenen Zeichen werden in dem durch "s" referierten String abgelegt (einschliesslich '\n' sofern es gelesen wurde), wobei '\0' angefügt wird. Übergabeparameter: s n fp Funktionswert : String zum Ablegen der gelesenen Zeile (genauer: Zeiger auf String) max Anz der zu lesenden Zeichen + 1 Filepointer String "s" (genauer: Pointer "s" auf String) bzw NULL-Pointer bei Erreichen des Dateiendes ohne vorheriges Lesen von Zeichen oder im Fehlerfall Hinweis : Anders als bei gets() wird ein eventuell gelesener '\n'-Character mit im String abgelegt ! fputs() - Stringweises Schreiben #include <stdio.h> int fputs(const char *s, FILE *fp); Aktion: "fputs()" schreibt den durch "s" referierten String (ohne abschließenden '\0'-Character) in die durch "fp" referierte Datei. Übergabeparameter: s fp Funktionswert : auszugebender String Filepointer ein nicht-negativer Wert bei Fehlerfreiheit bzw EOF (== -1) im Fehlerfall Hinweis : Anders als bei puts() wird an den ausgegebenen String kein '\n'-Character angefügt. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/03/5 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der C-Standard-Bibliothek zum Datentransfer (4) fread() - Datenobjektorientiertes Lesen #include <stdio.h> size_t fread(void *bptr, size_t size, size_t count, FILE *fp); Aktion: "fread()" liest maximal "count" Datenobjekte der Länge "size" Bytes aus der durch "fp" referierten Datei und legt sie in dem durch "bptr" bezeichneten Buffer ab. Übergabeparameter: bptr size count fp Pointer auf Buffer zur Ablage der gelesenen Datenobjekte Länge eines Datenobjekts in Bytes Anzahl der zu lesenden Datenobjekte Filepointer Funktionswert: Anzahl der tatsächlich in voller Länge gelesenen Datenobjekte (diese kann im Fehlerfall oder bei Erreichen des Dateiendes weniger als "count" sein). Anmerkung: Der Typ size_t ist der "natürliche" unsigned Typ (definiert in <stdio.h> und <stdlib.h>) fwrite() - Datenobjektorientiertes Schreiben #include <stdio.h> size_t fwrite(const void *bptr, size_t size, size_t count, FILE *fp); Aktion: "fwrite()" schreibt "count" Datenobjekte der Länge "size" Bytes in die durch "fp" referierte Datei. Die auszugebenden Datenobjekte werden dem Buffer entnommen, auf den "bptr" zeigt. Übergabeparameter: bptr size count fp Pointer auf Buffer mit den zu schreibenden Datenobjekten Länge eines Datenobjekts in Bytes Anzahl der zu schreibenden Datenobjekte Filepointer Funktionswert: Anzahl der geschriebenen Datenobjekte (diese kann im Fehlerfall weniger als "count" sein). Anmerkung: Der Typ size_t ist der "natürliche" unsigned Typ (definiert in <stdio.h> und <stdlib.h>) Anmerkung : Mittels der Funktionen fread()und fwrite() können typenbezogene Dateien, die Datenobjekte im internen Maschinenformat enthalten, bearbeitet werden. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/03/6 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm zu den Funktionen "fwrite" und "fread" /* /* /* /* /* /* /* /* /* /* ----------------------------------------------------------------------Programm floatdatei (C-Modul floatdatei_m.c) ----------------------------------------------------------------------Einlesen einer Reihe von float-Werten von stdin, Ausgabe derselben in ein "file of float", anschliessend Einlesen der in der Datei abgespeicherten Werte und Ausgabe nach stdout. Zum Vergleich Ausgabe der in der Datei gespeicherten Werte als sedezimale Bytefolge nach stdout ----------------------------------------------------------------------- */ */ */ */ */ */ */ */ */ */ #include <stdio.h> #define #define #define #define ANZ1 6 ANZ2 24 FLDATEI "daten.flt" WTRENN(i,a) putchar((++i%a)==0 ? '\n' : ' ') int main(void) { FILE *pDatei; float fWert; int i,iByte; pDatei=fopen(FLDATEI,"wb"); /* Oeffnen zum Schreiben in eine Binaerdatei */ printf("\nBitte geben Sie Zahlen ein :\n"); while (scanf("%f",&fWert) != EOF) fwrite(&fWert,sizeof(float),1,pDatei); fclose(pDatei); pDatei=fopen(FLDATEI,"rb"); /* Oeffnen zum Lesen aus einer Binaerdatei */ i=0; printf("\nAus der Datei wiedergelesene Zahlen :\n"); while (fread(&fWert,sizeof(float),1,pDatei) != 0) { printf("%9.4f ",fWert); WTRENN(i,ANZ1); } fclose(pDatei); // Alternative : pDatei=fopen(FLDATEI,"rb"); // rewind(pDatei); i=0; printf("\n\nDer Dateiinhalt als sedezimale Bytefolge :\n"); while ((iByte=fgetc(pDatei)) != EOF) { printf("%02x",iByte & 0xff); WTRENN(i,ANZ2); } fclose(pDatei); putchar('\n'); return 0; } Probelauf: Bitte geben Sie Zahlen ein : 1.0 0 -1.0 2.6 3.2e-3 ^Z Aus der Datei wiedergelesene Zahlen : 1.0000 0.0000 -1.0000 2.6000 0.0032 Der Dateiinhalt als sedezimale Bytefolge : 00 00 80 3f 00 00 00 00 00 00 80 bf 66 66 26 40 17 b7 51 3b HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/04/1 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der C-Standard-Bibliothek zur Dateiverwaltung remove() - Entfernen einer Datei aus dem Dateisystem #include <stdio.h> int remove(const char *path); Aktion: "remove()" entfernt die durch "path" bezeichnete Datei aus dem Dateisystem. Falls diese Datei zum Zeitpunkt des Aufrufs von "remove()" geöffnet ist, ist das Verhalten der Funktion implementierungsabhängig. Übergabeparameter: path Funktionswert: Pfadname der zu entfernenden Datei "0", falls erfolgreich bzw. ein Wert !="0" im Fehlerfall rename() - Umbenennen einer Datei im Dateisystem #include <stdio.h> int rename(const char *old, const char *new); Aktion: "rename()" benennt die durch "old" bezeichnete Datei um in "new". Der alte Pfadname "old" ist danach ungültig. Falls bereits eine Datei mit dem Pfadnamen "new" existiert, ist das Verhalten implementierungsabhängig. Übergabeparameter: old new alter Pfadname der Datei neuer Pfadname der Datei Funktionswert: "0", falls erfolgreich bzw. ein Wert !="0" im Fehlerfall, der alte Pfadname bleibt gültig Anmerkung : kann auch zum Verschieben von Dateien eingesetzt werden HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/05/1 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu Binärdateien Im Praktikum (2. Übung) wird die Binärdatei "lager.dat" zur Verwaltung eines Drogerie-Lagers eingesetzt. Die Datei enthält für jeden im Lager vorhandenen Artikel einen Datensatz (Record), der durch den Structure-Typ struct artikel_t beschrieben wird. Diese Datei ist Datenbasis für folgende Aktionen : int modifyLager(void); (1) Fragt die Nummer des zu modifizierenden Datensatzes von der Konsole ab. (2) Sucht den angegebenen Datensatz in der Datei und bietet diesen durch "modifyRec()" zur Modifikation an. (3) Ersetzt den alten durch den modifizierten Datensatz. (4) Wiederholt (1)-(3) solange bis eine negative Datensatznummer eingegeben wird oder ein Fehler beim Lesen bzw Schreiben der Datei auftritt (5) Nicht existierende Datensatznummern werden zurückgewiesen (Ausgabe Fehlerhinweis). Rückgabe: 1 o.k. -1 Fehler static int modifyRec (struct artikel_t *); (1) Zeigt die Daten des übergebenen Artikel an. (2) Bietet die Ist-Anzahl des Artikels (Structure-Element "bestand") zur Korrektur an. (3) Übernimmt die neue Ist-Anzahl von der Konsole. Übergabe: Zeiger auf zu modifizierenden Artikel. Rückgabe: 1 erfolgreicher Abschluss -1 Fehler int makeTextDatei(void); (1) Erzeugt aus der Binärdatei "lager.dat" die Texdatei "lager.txt". (2) Jeder Datensatz aus "lager.dat" entspricht einer geeignet formatierten "Zeile" in "lager.txt". Zwischen den Elementen des Artikels ist als Trennzeichen der Tabulator anzuwenden ('\t'). Rückgabe: 1 o.k. -1 Fehler #include <stdio.h> #define LAGERDATEINAME "lager.dat" #define ZIELTEXTDATEI "lager.txt" #define MAXCHAR 32 struct artikel_t { int artnr; char artname[MAXCHAR]; float preis; int bestand; int min; } ; /* Datentyp der Lagerartikel */ static int modifyRec (struct artikel_t *sM) { int iRet = 1 printf("Artikel: %03d %-16s %6.2f %4d:%4d\n", sM->artnr, sM->artname, sM->preis, sM->bestand, sM->min); printf ("Korrektur Bestand %2d nach: ",sM->bestand); if (scanf ("%d",&(sM->bestand)) != 1) iRet = -1; return iRet; } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 06/05/2 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu Binärdateien (Forts.) int modifyLager (void) { int iRet = 1; char *dateiName = LAGERDATEINAME; int rnr, rerg; long pos; FILE *f_zeig; struct artikel_t e_1; if((f_zeig = fopen(dateiName,"r+b")) == NULL) { printf("%s kann nicht geoeffnet werden!\n",dateiName); iRet -1; } else { do { printf("\nRekordnr ? "); scanf("%d",&rnr); if (rnr >= 0) { if (fseek(f_zeig,rnr*sizeof(e_1),SEEK_SET) == 0 ) { pos = ftell(f_zeig); rerg = fread(&e_1,sizeof(struct artikel_t),1,f_zeig); if (rerg != 1) { printf("Read-Error!!\n"); iRet = -1; } else if ( modifyRec(&e_1) == 1) { fseek(f_zeig,pos,SEEK_SET); if (fwrite(&e_1,sizeof(struct artikel_t),1,f_zeig) != 1) { printf("Write-Error!!\n"); iRet = -1; } } } } } while (rnr>= 0 && iRet!=-1); if (fclose(f_zeig)== EOF) iRet = -1; } return iRet; } int makeTextDatei(void) { int iRet = 1; char zForm[] = "%03d\t%-16s\t%6.2f\t%4d\t%4d\n"; /* Ausgabeformat */ char *quellName = LAGERDATEINAME; char *zielName = ZIELTEXTDATEI; FILE *fqZeig, *fzZeig; char erg1,erg2; struct artikel_t e1; if((fqZeig = fopen(quellName,"rb")) == NULL) { printf("%s kann nicht geoeffnet werden!\n",quellName); iRet = –1; } else if((fzZeig = fopen(zielName,"wt")) == NULL) { printf("%s kann nicht geoeffnet werden!\n",zielName); iRet = –1; } else { while (fread(&e1,sizeof(struct artikel_t),1,fqZeig) > 0) fprintf(fzZeig,zForm,e1.artnr,e1.artname,e1.preis,e1.bestand,e1.min); erg1 = fclose (fqZeig); erg2 = fclose (fzZeig); if (erg1 == EOF || erg2 == EOF) { printf("Fehler beim Datei-Schliessen ! \n"); iRet = -1; } else printf("Die Textdatei %s wurde erfolgreich generiert!\n",zielName); } return iRet; } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/00/0 – TH/KR – 02 ----------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 7. Ergänzungen zu Datenstrukturen in C 7.1. Dynamische Speicherallokation 7.2. Dynamische Datenstrukturen (Listen und Bäume) 7.3. Unions 7.4. Vorwärtsdeklaration von strukturierten Datentypen HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/01/1 – TH/KR – 02 ----------------------------------------------------------------------------------------------------------------------------------------------------- Dynamische Speicherallokation in C • Datenobjekte (Variable), die in einem Programm definiert werden, sind statische Objekte. Der Compiler sorgt für die Bereitstellung von Speicher für diese Objekte. Ihre Anzahl und Größe (Speicherbedarf !) muss daher bereits zum Zeitpunkt der Compilierung festliegen. • In vielen Fällen lässt sich aber die Größe eines Objektes (z.B. Arrays) bzw. die Anzahl der Objekte erst zur Programmlaufzeit angeben, wobei sich deren gesamte Struktur auch noch dynamisch während der Programmabarbeitung verändern kann. Eine statische Bereitstellung von Speicherplatz der zu erwartenden maximalen Größe stellt in solchen Fällen nur eine Notlösung dar. Denn erstens ist damit i.a. eine mehr oder weniger große Speicherplatzverschwendung verbunden und zweitens kann sich die dadurch getroffene Größenbegrenzung in der Zukunft als zu gering herausstellen. • Zur sinnvollen Lösung des genannten Problems bietet sich die dynamische Speicherallokation (Dynamic Storage Allocation) an: Der für ein Objekt benötigte Speicherplatz wird nicht statisch durch Vorkehrungen des Compilers, sondern erst bei Bedarf während der Programmabarbeitung bereitgestellt. Derartige - dynamisch allokierte - Objekte können nicht wie statische Objekte definiert werden. Sie werden auch nicht über einen Namen, sondern nur über ihre Adresse angesprochen. Diese Adresse kann ihrerseits einer statischen Pointer-Variablen zugewiesen werden, die wie üblich zu definieren ist. • Die dynamische Speicherbereitstellung erfolgt mittels spezieller Speicherallokationsfunktionen, die in der Standardbibliothek (Funktionsdeklarationen in <stdlib.h>) enthalten sind : malloc() - Allokation eines Speicherblocks bestimmter Größe. calloc() - Allokation eines Speicherblocks für eine bestimmte Anzahl von Objekten einer bestimmten Größe, Initialisierung aller Bytes des Speicherblocks mit 0. realloc() - Veränderung der Größe eines allokierten Speicherblocks. Diese Funktionen liefern die Anfangsadresse des allokierten Blocks als void-Pointer (void *). Es ist kein Type-Cast bei der Zuweisung an Pointer-Variable erforderlich. • • Der für die dynamische Speicherverwaltung zur Verfügung stehende Speicherbereich wird als Heap bezeichnet. Die Lage und Größe dieses Bereichs hängt von der Implementierung ab, folgende Varianten sind möglich: - Der Heap ist Teil des Datenbereichs zwischen statischen Daten und Stack. - Der Heap verwendet den gesamten restlichen Arbeitsspeicher. Die Lebensdauer dynamisch allokierten Speichers ist nicht an die Ausführungszeit eines Blocks gebunden. Nicht mehr benötigter dynamisch allokierter Speicher ist explizit freizugeben. Bibliotheksfunktion hierfür : free() - Gibt dynamisch allokierten Speicher frei. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/01/2 – TH/KR – 04 ----------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der ANSI-C-Std-Bibliothek zur dynamischen Speicherverwaltung - malloc() Allokation eines Speicherblocks #include <stdlib.h> void *malloc(size_t size); Aktion: "malloc()" allokiert einen Speicherblock von wenigstens der Länge "size" Bytes (der tatsächlich allokierte Speicherblock kann infolge evtl. notwendiger Speicherplatzausrichtung größer sein). Übergabeparameter : size Anzahl der Bytes des zu allokierenden Blockes Funktionswert : Pointer auf den allokierten Speicherblock; NULL-Pointer, falls der Speicherplatz nicht ausreicht. Anmerkung : Der Typ size_t ist der "natürliche" unsigned Typ (u.a. in <stdlib.h> definiert). - calloc() Allokation und Initialisierung eines Speicherblocks #include <stdlib.h> void *calloc(size_t nobj, size_t size); Aktion: "calloc()" allokiert einen Speicherblock von wenigstens der Länge "nobj" * "size" Bytes (der tatsächlich allokierte Speicherblock kann infolge evtl. notwendiger Speicherplatzaus richtung größer sein) und initialisiert alle Bytes mit 0. Übergabeparameter: nobj size Funktionswert: free() Anzahl der Objekte, für die Speicherplatz allokiert werden soll; Länge eines Objektes in Bytes. Pointer auf den allokierten Speicherblock; NULL-Pointer, falls der Speicherplatz nicht ausreicht. - Freigabe von Speicherplatz #include <stdlib.h> void free(void *ptr); Aktion: "free()" gibt den - zuvor allokierten - Speicherblock, auf den "ptr" zeigt, wieder frei. Übergabeparameter: ptr Funktionswert : keiner Zeiger auf allokierten Speicherblock HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/01/3 – TH/KR – 03 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zur dynamischen Speicherallokation in C /* /* /* /* /* /* /* /* /* /* /* /* -------------------------------------------------------------------Programm maldem (C-Modul maldem_m.c) -------------------------------------------------------------------Demonstrationsprogramm zur Verwendung der Bibliotheksfunktionen zur dynamischen Speicherallokation. */ */ */ */ */ */ Einlesen, Sortieren und Ausgeben einer beliebigen Anzahl von */ double-Werten. */ */ Die Funktion zum Sortieren eines Feldes von double-Werten steht */ in einem getrennt zu übersetzenden Modul zur Verfügung. */ ---------------------------------------------------------------------*/ #include <stdio.h> #include <stdlib.h> extern void dfeld_sort(double *, unsigned); void dfeld_ein(double *, unsigned); void dfeld_aus(double *, unsigned); /* im Modul bubble.c def. */ int main(void) { int anz; double *dfeld; printf("\nAnzahl der double-Werte ? "); scanf("%d", &anz); if (anz>0) if ((dfeld = malloc(anz*sizeof(double))) == NULL) // oder : calloc(anz,sizeof(double)) printf("\nSpeicherplatz reicht nicht aus !\n"); else { dfeld_ein(dfeld, anz); dfeld_sort(dfeld, anz); dfeld_aus(dfeld, anz); free(dfeld); } else printf("\nAnzahl muss >0 sein !\n"); return 0; } void dfeld_ein(double * df, unsigned int iNbr) { unsigned int i; for (i=0;i<iNbr;i++) { printf("Wert %2d: ",i); scanf ("%lf", df++); } } void dfeld_aus ( double * df, unsigned int iNbr) { unsigned int i; printf("\nAusgabe:\n"); for (i=0;i<iNbr;i++) printf("Wert %2d: %7.3lf\n",i,*(df++)); } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/1 – TH/KR – 02 ----------------------------------------------------------------------------------------------------------------------------------------------------- Dynamische Datenstrukturen in C • Dynamische Datenstrukturen ändern ihre Struktur und den von ihnen belegten Speicherplatz während der Programmausführung. Sie sind aus einzelnen Elementen (Knoten) aufgebaut, zwischen denen üblicherweise eine bestimmte ("Nachbarschafts"-) Beziehung besteht. Die Dynamik liegt im Einfügen neuer Knoten, Entfernen vorhandener Knoten und Änderung der Nachbarschaftsbeziehung. Der Speicherplatz für einen Knoten wird erst bei Bedarf zur Programmlaufzeit mittels der Speicherallokationsfunktionen bereitgestellt → dynamische Speicherallokation. Da der Speicherort der einzelnen Knoten im voraus nicht bekannt ist und auch für "benachbarte" Knoten weit auseinander liegen kann, läßt sich eine ("Nachbarschafts"-) Beziehung nicht durch den Speicherort abbilden.Vielmehr muß die Beziehung der einzelnen Knoten untereinander über Zeiger, die jeweils auf den Speicherort des "Nachbarn" zeigen, hergestellt werden. Jeder Knoten wird daher neben den jeweils zu speichernden Nutzdaten Zeiger auf die jeweiligen "Nachbar"-Knoten - also auf Elemente des gleichen Typs wie der des Knotens selbst enthalten. Verkettete Datenstrukturen • Die wichtigsten dieser Datenstrukturen sind: Lineare Listen ▻ einfach verkette Listen ▻ doppelt verkette Listen ▻ Spezialformen (bezüglich der Anwendung) - Queue (Pufferspeicher, FIFO) - Stack (Kellerspeicher, LIFO) Bäume ▻ Binärbäume (zwei Nachfolger) ▻ Vielweg-Bäume (mehr als zwei Nachfolger) Allgemeine Graphen • Die wichtigsten Operationen mit dynamischen Datenstrukturen sind : • • • • • Erzeugen eines neuen Elements Einfügen eines Elements Entfernen eines Elements Suchen eines Elements In C lassen sich die einzelnen Elemente (Knoten) durch "structures" darstellen. Zur Realisierung verketteter Datenstrukturen müssen diese Strukturen Zeiger auf Strukturen ihres eigenen Typs enthalten. Derartige Strukturen nennt man rekursiv. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/2 – TH/KR – 02 ----------------------------------------------------------------------------------------------------------------------------------------------------- Aufbau verketteter Datenstrukturen mittels rekursiver Strukturen • Einfach verkettete Liste root Inhalt Inhalt Inhalt Inhalt NULL head tail struct listelement { int inhalt; struct listelement *next; }; • Doppelt verkettete Liste root inhalt inhalt inhalt inhalt NULL NULL head tail struct listelement { int inhalt; struct listelement *next; struct listelement *back; }; • Binärer Baum root inhalt inhalt inhalt inhalt inhalt NULL inhalt NULL NULL inhalt NULL NULL inhalt NULL NULL NULL NULL struct baumelement { int inhalt; struct baumelement *rechts; struct baumelement *links; }; HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/3 – TH/KR – 05 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu rekursiven Strukturen – Binärbaum (1) Sortierte Speicherung von double-Werten in einem Binär-Baum ● Aufgabe : Es soll eine beliebige Anzahl von double-Werten von der Tastatur eingelesen werden (Ende der Eingabe mit CTRL-Z). Anschließend sind die Werte in aufsteigender Reihenfolge auf dem Bildschirm auszugeben. Treten gleiche Werte mehrmals auf, so ist der nur einmal, zusammen mit der Anzahl seines Auftretens, auszugeben. ● Lösung : Bereits während des Einlesens werden die Werte sortiert in einem Binär-Baum gespeichert. Jeder Knoten des Baums enthält den jeweiligen Wert, die Anzahl der Auftritte dieses Wertes, sowie je einen Pointer auf einen rechten (für größeren Folgewert) und einen linken Folgeknoten (für kleineren Folgewert). ◇ Typdefinition des Knotens : struct dknoten { double wert; int anzahl; struct dknoten *rechts; struct dknoten *links; }; ◇ Die Speicherung erfolgt so, dass sich jeder Wert der kleiner als ein Knotenwert ist, im linken Unterbaum und jeder Wert der größer ist, im rechten Unterbaum dieses Knotens befindet. ◇ Die Ausgabe erfolgt mittels einer (rekursiven) Funktion, die beginnend mit dem Wurzelknoten zuerst den linken Unterbaum, dann den Wurzelknoten und dann den rechten Unterbaum ausgibt. ◇ Beispiel für Eingabe : 1.2 4.5 3.1 0.8 6.3 3.1 -5.8 4.0 4.5 2.8 1.2 root 1 4.5 2 0.8 1 NULL -5.8 1 6.3 3.1 1 2 NULL 4.0 NULL 2.8 1 NULL 1 NULL NULL NULL NULL NULL HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/4 – TH/KR – 04 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu rekursiven Strukturen – Binärbaum (2) ● Programm /* Programm baumsortd (C-Modul baumsortd_m.c) Sortierte Ausgabe (aufsteigende Wertefolge) einer beliebigen Anzahl in beliebiger Reihenfolge ueber stdin eingegebenen Folge von double-Werten nach stdout. Speicherung der Werte in einem Binär-Baum */ #include <stdio.h> #include <stdlib.h> typedef struct knoten knoten_t; struct knoten { double wert; int anzahl; knoten_t *rechts; knoten_t *links; }; /* Vorwärtsdeklaration von knoten_t /* Typ-Definition des Knotens knoten_t * psEinfBaum(knoten_t *, double); void vBaumAus(knoten_t *); /* Funktionsdeklarationen */ */ */ int main(void) { knoten_t *wurzel = NULL; double d; printf("\nBitte Zahlen eingeben !\n"); while (scanf("%lf",&d)!=EOF) wurzel = psEinfBaum(wurzel,d); printf("\nSortierte Folge der eingegeben Zahlen :\n\n"); vBaumAus(wurzel); return 0; } knoten_t * psEinfBaum(knoten_t *pk, double a) /* Einfügen Wert in Baum /* pk Zeiger auf akt. Knoten des Baums /* a neuer einzufuegender Wert { if (pk==NULL) /* neuer Wert ist einzufuegen { pk=malloc(sizeof(knoten_t)); pk->wert = a; pk->anzahl = 1; pk->links = pk->rechts = NULL; } else if (a==pk->wert) /* Wert bereits vorhanden pk->anzahl++; else if (a<pk->wert) /* Wert ist kleiner als Knotenwert pk->links = psEinfBaum(pk->links,a); else /* Wert ist groesser als Knotenwert pk->rechts = psEinfBaum(pk->rechts,a); return pk; } */ */ */ */ */ */ */ void vBaumAus(knoten_t *pk) /* Rekursive Ausgabe der Baumwerte */ { if (pk!=NULL) /* pk Zeiger auf akt. Knoten des Baums */ { vBaumAus(pk->links); printf("%13.6lf",pk->wert); if (pk->anzahl > 1) printf(" (%2d)",pk->anzahl); putchar('\n'); vBaumAus(pk->rechts); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/5 – TH/KR – 04 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu rekursiven Strukturen – Binärbaum (3) ● Probelauf Ergebnis eines Probelaufs des Programms baumsortd : HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/6 – TH/KR – 05 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu rekursiven Strukturen – Binärbaum (4) ● Iterative Lösung für das Einfügen eines Knotens /* /* /* /* /* /* ----------------------------------------------------------------------Einfügen eines Wertes in den Baum pk Zeiger auf akt. Knoten des Baums a neuer einzufuegender Wert Iterative Variante (enthalten im Modul baumsort_iter_m.c) ----------------------------------------------------------------------- knoten_t * psItEinfBaum(knoten_t *pk, double a) { knoten_t *pka, *pkn, *pkneu; int erfolg; if (pk==NULL) // 1. Wurzel untersuchen { pk = malloc(sizeof(knoten_t)); pk->wert = a; pk->anzahl = 1; pk->links = pk->rechts = NULL; } else { pka = pkn = pk; // 2. Knoten untersuchen erfolg = 0; do { if (pka->wert == a) { pka->anzahl++; erfolg = 1; } else if (pka->wert > a) { pkn = pka->links; if (pkn == NULL) { pkneu = malloc(sizeof(knoten_t)); pkneu->wert = a; pkneu->anzahl = 1; pkneu->links = pkneu->rechts = NULL; pka->links=pkneu; erfolg = 1; } } else { pkn = pka->rechts; if (pkn==NULL) { pkneu = malloc(sizeof(knoten_t)); pkneu->wert = a; pkneu->anzahl = 1; pkneu->links = pkneu->rechts = NULL; pka->rechts = pkneu; erfolg = 1; } } pka = pkn; } while (erfolg == 0); } return pk; } */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/7 – TH/KR – 06 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu dynamischen Datenstrukturen – Lineare Liste (1) Modifikation der im Praktikum gestellten Aufgabe zur Erstellung einer Bestellliste durch Auswertung einer Lagerbestandsdatei (Binärdatei "lager.dat") : Ablage der nachzubestelleden Artikel in einer linearen vorwärts verketteten Liste. Realisierung der folgenden Funktionen : struct artlst_t *makeOrderList(char *); (1) Öffnet die durch den Parameter referierte Lagerdatei. (2) Liest den nächsten Datensatz ein. (3) Übergibt den Artikel falls die aktuelle Stückzahl geringer ist als die Mindestmenge an "putInList()", gemeinsam mit der Wurzel der Liste. (4) Wiederholt (2)-(3) bis alle Datensätze gelesen und geprüft sind. Übergabe: Zugriffspfad der Lagerdatei Rückgabe: Pointer auf Wurzelknoten (bei erfolgreicher Listenerzeugung) NULL-Pointer, falls keine Liste erzeugt wurde (z.B. kein Öffnen der Lagerdatei) static struct artlst_t *putInList(struct artlst_t *, struct artikel_t); (1) Allokiert Speicherplatz für einen Listenknoten. (2) Kopiert den übergebenen Artikel in den Listenknoten und (3) hängt den neuen Knoten am Ende der Liste an. Übergabe: Zeiger auf ersten Listenknoten (Wurzelknoten). Artikel, der zu übernehmen ist. Rückgabe: Pointer auf Wurzelknoten (ggf. neu allokierter Knoten). bzw NULL-Pointer, falls ein Fehler aufgetreten ist. void prtOrderList(struct artlst_t *); (1) Gibt den Inhalt der Liste knotenweise in die Standardausgabe aus. Übergabe: Pointer auf Wurzelknoten der Liste. Rückgabe: void remOrderList(struct artlst_t *); (1) Gibt den allokierten Speicherplatz für jedes Listenelement wieder frei. Übergabe: Pointer auf Wurzelknoten der Liste. Rückgabe: // Inhalt der C-Headerdatei lagfunc3.h : #define MAXCHAR 40 #define ANZ 5 #define LAGERDATEINAME "lager.dat" struct artikel_t { int artnr; char artname[MAXCHAR]; float preis; int bestand; int min; }; /* Datentyp der Lagerartikel */ struct artlst_t { struct artikel_t sArtikel; struct artlst_t *next; }; /* Datentyp der Listenknoten */ /* Rekursive Datenstuktur!!! */ struct artlst_t *makeOrderList(char*); void prtOrderList(struct artlst_t *); void remOrderList(struct artlst_t *); /* Funktionsprototypen */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/8 – TH/KR – 06 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu dynamischen Datenstrukturen – Lineare Liste (2) // C-Modul lagfunc3.c #include <stdio.h> #include <stdlib.h> #include "lagfunc3.h" static struct artlst_t *putInList(struct artlst_t *, struct artikel_t); struct artlst_t *makeOrderList(char * quellPfad) { int speicherVorh = 1; FILE *fqZeig; struct artikel_t e1; struct artlst_t *root=NULL; struct artlst_t *erg; if((fqZeig = fopen(quellPfad,"rb")) == NULL) printf("%s kann nicht geoeffnet werden!\n",quellPfad); else { while (fread(&e1,sizeof(struct artikel_t),1,fqZeig) > 0 && speicherVorh) { if(e1.bestand < e1.min) { erg = putInList(root,e1); if (erg == NULL) { printf("Speicher nicht ausreichend!!!\n"); speicherVorh = 0; //Speicher fehlt } else root=erg; } } fclose (fqZeig); } return root; } static struct artlst_t *putInList(struct artlst_t *wrzl, struct artikel_t sG) { struct artlst_t *pRet = wrzl; struct artlst_t *pakt, *palt; pakt = malloc (sizeof(struct artlst_t)); if (pakt == NULL) pRet = NULL; else { pakt->sArtikel = sG; pakt->next = NULL; if(wrzl == NULL) // Liste war noch leer pRet = pakt; // neues Element --> erstes Listenelemnt else { palt = wrzl; while (palt->next != NULL) // Ende der Liste suchen palt = palt->next; palt->next = pakt; // neues Element an das Ende der Liste } } return pRet; } // Fortsetung s. nächste Seite HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/02/9 – TH/KR – 07 ----------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel zu dynamischen Datenstrukturen – Lineare Liste (3) // C-Modul lagfunc3.c, Fortsetzung void prtOrderList(struct artlst_t *wrzl) { const char *ueber = "Lfd.Nr. Art.Nr. Artikel BestellAnz Einzelpr."; const char *ueber2= " Gesamtpr.\n"; const char *line = "------------------------------------------------------"; const char *line2 = "-------------\n"; const char *form = " %3d %03d %-18s %3d %8.2f %9.2f\n"; const char *sform = " Summe: %14.2f\n\n"; int lfdnr = 0; float gesamtpreis = 0.0; float gesamtartikelpreis; int bestellmenge; struct artlst_t *palt; printf(ueber); printf(ueber2); printf(line); printf(line2); palt = wrzl; while (palt != NULL) { bestellmenge = ANZ*palt->sArtikel.min - palt->sArtikel.bestand; gesamtartikelpreis = bestellmenge * palt->sArtikel.preis; gesamtpreis += gesamtartikelpreis; printf(form,++lfdnr,palt->sArtikel.artnr,palt->sArtikel.artname, bestellmenge, palt->sArtikel.preis, gesamtartikelpreis); palt = palt->next; } printf(line); printf(line2); printf(" "); printf(sform,gesamtpreis); } void remOrderList(struct artlst_t * wrzl) { struct artlst_t *pmrk, *palt; palt = wrzl; while (palt != NULL) { pmrk = palt; palt = palt->next; free(pmrk); // Speicher wieder freigeben } } // C-Modul lager3_m.c (Programm lager3) // Modifikation der 2. Aufgabe des Prakt. Programmieren #include <stdio.h> #include <stdlib.h> #include "lagfunc3.h" int main(void) { struct artlst_t *root = NULL; char *ldatei = LAGERDATEINAME; if((root = makeOrderList(ldatei)) == NULL) printf("Fehler beim Oeffnen der Lagerdatei!!\n"); else { printf("Bestellliste :\n"); prtOrderList(root); remOrderList(root); } return 0; } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/03/1 – TH/KR – 04 ----------------------------------------------------------------------------------------------------------------------------------------------------- Unions (Variante Strukturen) in C ● Aufbau von Unions ◇ Variante Strukturen – in C Unions genannt – sind Strukturen, deren Komponenten alle an der gleichen Anfangsadresse im Arbeitspeicher beginnen. Die Komponenten einer Union liegen nicht wie bei einer normalen Struktur hintereinander – an verschiedenen Adressen – im Arbeisspeicher, sondern sie liegen "übereinander" im gleichen Speicherbereich, der am Unionanfang beginnt. d.h. der Inhalt des gleichen Speicherbereichs wird je nach union-Komponente unterschiedlich interpretiert. ◇ Beispiel : struct { int i; double f; char *pc; } svar; svar i union { int i; double f; char *pc; } uvar; uvar i f pc f pc ◇ Die einzelnen Komponenten einer Union können durchaus unterschiedlich lang sein. Der von der Union belegte Speicherplatz richtet sich nach der längsten Komponente. ● Vereinbarung von Unions Die Vereinbarung von Unions (Typdefinition und Variablenvereinbarung) erfolgt analog zur Vereinbarung von Strukturen. Lediglich das Schlüselwort struct wird durch das Schlüsselwort union ersetzt. ● Initialisierung von Unions nur für die 1. Komponente mit konstantem Ausdruck zulässig. ● Operationen mit unions Operationen mit Unions sind die gleichen, die mit Strukturen möglich sind: ▻ Anwendung des Adressoperators ▻ Zugriff zu Union-Komponenten - mittels des Element-Operators . und - und mittels des Objektelement-Operators -> ▻ Wertzuweisung: ganze Unions geschlossen ▻ Verwendung als Funktionsparameter und Funktionswerte: sowohl durch Union-Pointer als auch durch Unions direkt. ● Interpretation von Unions Eine Union-Variable kann als eine Variable aufgefasst werden, die Werte unterschiedlichen Typs annehmen kann. Die jeweils richtige Interpretation durch entsprechende Wahl der Komponente obliegt dem Programmierer. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/03/2 – TH/KR – 03 ----------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel zu Unions in C /*----------------------------------------------------------------------*/ /* Programm floatbyte (C-Modul floatbyte_m.c) */ /*----------------------------------------------------------------------*/ /* Ausgabe der internen Darstellung von float-Werten als sedezimale */ /* Bytefolge (MS-Byte zuerst) */ /*----------------------------------------------------------------------*/ #include <stdio.h> union floathex { float f; char b[sizeof(float)]; }; /* unabhaengig von interner Darstellung */ int main(void) { union floathex flvar; float r; int i; printf("\nInterne Darstellung von FLOAT-Werten als Bytefolge"); printf(" (MS-Byte zuerst).\n"); printf("Bitte geben Sie Zahlenwerte ein :\n\n"); while (scanf("%f",&r)!=EOF) { flvar.f=r; for (i=sizeof(float)-1; i>=0; i--) printf("%02x ",flvar.b[i]&0xff); printf("\n\n"); } return 0; } ● Probelauf: Interne Darstellung von FLOAT-Werten als Bytefolge (MS-Byte zuerst). Bitte geben Sie Zahlenwerte ein : 0 00 00 00 00 1.0 3f 80 00 00 -1.0 bf 80 00 00 2.6 40 26 66 66 2.6e-6 36 2e 7b a9 ^Z HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C– 07/04/1 – TH/KR – 04 ----------------------------------------------------------------------------------------------------------------------------------------------------- Vorwärtsdeklaration von Struktur-Typen ● In ANSI-C ist es möglich, einen struct- oder union-Typ vorwärts zu deklarieren. Diese Typ-Vorwärtsdeklaration besteht lediglich aus dem Wortsymbol struct bzw. union und dem Struktur- bzw. Union-Name. Die Auflistung der Komponenten fehlt. ● Syntax : struct Strukturname union Unionname ; ● Anwendung : Die Typ-Vorwärtsdeklaration ermöglicht die Definition von struct- bzw. union-Typen, die gegenseitig aufeinander Bezug nehmen wobei wenigstens einer der Typnamen bereits für einen anderen struct- bzw. union-Typ in einem übergeordneten Block vereinbart ist. ● Beispiel : // Programm vorwdekl (C-Modul vorwdekl_m.c) // Demonstrationsbeispiel zur Vorwaertsdeklaration #include <stdlib.h> #include <stdio.h> struct st2 { int ba; int lu; }; int main(void) { struct st2; struct st1 { struct st2* sp2; int a; }; struct st2 { struct st1 sp1; int b; }; /* Typ-Vorwaertsdeklaration, setzt globale */ /* Vereinbarung von struct st2 ausser Kraft */ /* Pointer auf lokal definierte sruct st2 */ /* lokale Definition von struct s2 */ struct st1 mys1 = { NULL, 7}; struct st2 mys2 = { mys1, 5 }; mys1.sp2 = &mys2; printf("%d\n", (mys1.sp2)->b); return 0; } /* ohne Typ-Vorwaertsdeklaration falsch */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/00/0 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 8. Die C-Standardbibliothek 8.1. ANSI/ISO-C-Standardbibliothek 8.2. Überblick über die IO-Funktionen 8.3. Funktionen zum Zeichenklassentest 8.4. Funktionen zur Stringbearbeitung 8.5. Utility-Funktionen 8.6. Mathematische Funktionen 8.7. Datums- und Zeitfunktionen HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/01/1 – TH/KR – 06 --------------------------------------------------------------------------------------------------------------------------------------------------------- ANSI/ISO-C-Standardbibliothek • Mit ANSI-ISO-C wurde nicht nur die Sprache C (die selbst keinerlei Funktionen enthält) sondern auch eine Standard-Bibliothek genormt. Eine ausschließliche Verwendung von Standard-Bibliotheks-Funktionen gewährleistet die Portabilität von C-Programmen. • Die Standard-Bibliothek umfasst im wesentlichen Ein-/Ausgabe-Funktionen Funktionen zum Zeichenklassentest Funktionen zur String- (und Zeichenarray-)bearbeitung Diverse Utility-Funktionen (z.B. zur Speicherallokation) Mathematische Funktionen Datums- und Zeitfunktionen Diagnose-Funktion Funktionen zum Zugriff zu einer variablen Parameterliste Funktionen zur Ermöglichung nicht-lokaler Sprünge Funktionen zur Signal-Behandlung Funktionen zur Auswahl länderspezifischer Darstellungen - • Die Anwendung einiger Funktionen setzt speziell definierte Konstanten und Typen voraus. • Einige der "Funktionen" sind tatsächlich nicht als Funktion sondern als Makro realisiert. • Mit der Standard-Bibliothek eng assoziiert ist eine Gruppe von Standard-Header-Dateien, diese enthalten: notwendige Extern-Deklarationen der Bibliotheksfunktionen (Function Prototypes) die Definitionen von Makros, die anstelle von Funktionen realisiert sind die Definitionen der speziellen Konstanten und Typen, die für die Anwendung der Funktionen benötigt werden die Definitionen weiterer implementierungsabhängiger Größen (z.B. Fehlernummern, numerische Konstante) die Externdeklarationen einiger Systemvariablen • Funktionell zusammengehörige Definitionen und Deklarationen sind jeweils in einer spezifischen HeaderDatei zusammengefaßt. Einige Definitionen und Deklarationen sind auch mehrfach, d.h. in mehreren Header-Dateien, enthalten. • Jede Bibliotheks-Funktion ist mit einer oder mehreren Header-Dateien assoziiert. Diese Header-Datei(en) ist (sind) bei Verwendung der Funktion mittels #include <datname> in das Quellprogramm einzubinden. • Die Header-Dateien sind so aufgebaut, daß sie ohne Probleme in jeder beliebiger Reihenfolge und auch mehrfach in ein C-Quellmodul eingebunden werden können. • Die ANSI/ISO-C-Norm C90 (einschließlich Amendment 1) schreibt die Namen und den Inhalt von 18 Header-Dateien vor ( Standard-Header-Dateien). Die neuere Norm C99 fügt 6 weitere Header-Dateien hinzu. • Reale ANSI-C-Implementierungen enthalten i.a. darüberhinaus weitere Header-Dateien entsprechend einer Erweiterung des Funktionsumfangs der Bibliothek. Außerdem können einzelne Standard-Header-Dateien auch weitere - in der Norm nicht vorgeschriebene Deklarationen und Definitionen enthalten. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/01/2 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Die Standard-Header-Dateien von ANSI/ISO-C (1) <assert.h> Deklaration einer Diagnose-Funktion bzw. Definition eines die Funktion ersetzenden Makros <ctype.h> Deklaration von Funktionen zum Zeichenklassentest bzw. Definition ersetzender Makros <errno.h> Deklaration der System-Fehlervariablen und Definition von System-Fehlercodes <float.h> Definition gleitkommaarithmetikbezogener Konstanten <iso646.h> Definition von Ersatzdarstellungen einiger Operatorsymbole <limits.h> Definition von Konstanten zur Beschreibung des Wertebereichs der Ganzzahl-Typen <locale.h> Definition von Konstanten und eines Typs sowie Deklaration von Funktionen zur Auswahl länderspezifischer Darstellungen <math.h> Deklaration mathematischer Funktionen <setjmp.h> Deklaration von Funktionen zur Realisierung nicht-lokaler Sprünge <signal.h> Definition von Konstanten und Deklaration von Funktionen zur Signal-Behandlung <stdarg.h> Definition von Typen und Makros zum Zugriff zu einer variablen Parameterliste <stddef.h> Definition einiger Typen und der Konstanten NULL <stdio.h> Definition von Konstanten und Typen, Deklaration von Funktionen zur Ein- und Ausgabe (I/O-Funktionen) <stdlib.h> Definition von Konstanten und Typen, sowie Deklaration diverser Utility-Funktionen (z.B. String-Konvertierungsfunktionen, Speicherverwaltungsfunktionen, Environment-Funktionen, Such- und Sortier-Funktionen, Integer-Arithmetik-Funktionen, Multibyte-Zeichen-Funktionen) <string.h> Deklaration von Funktionen zur Stringbearbeitung und Zeichenarraybearbeitung <time.h> Definition von Konstanten und Typen, Deklaration von Zeitund Datums-Funktionen <wchar.h> Definition von Konstanten und Makros sowie Deklaration von Funktionen zur Bearbeitung von "wide characters" (Ein/Ausgabe, Stringbearbeitung) <wctype.h> Deklaration von Funktionen zum Test und Klassifizieren von "wide characters" HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/01/3 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Die Standard-Header-Dateien von ANSI/ISO-C (2) Mit der neueren C-Norm C99 (1999 verabschiedet als ISO/IEC 9899:1999, ergänzt 2001 durch Technical Corrigendum 1 TC1, 2004 durch Technical Corrigendum 2 TC2 und 2007 durch Technical Corrigendum 3 TC3, in dieser Version bezeichnet als ISO/IEC 9899:TC3) wurden – neben einer Reihe von Änderungen und Ergänzungen der Sprache selbst – auch die Standardbibliothek erweitert und 6 weitere Header-Dateien hinzugefügt. <complex.h> Definition von Makros und Deklaration von Funktionen zur Unterstützung komplexer Arithmetik <fenv.h> Definition von Makros sowie Deklaration von Typen und Funktionen, die einen Zugriff zur implementierungsabhängigen Floating Point Umgebung ermöglichen <inttypes.h> Einbinden von <stdint.h> und Bereitstellung ergänzender Verwendunsgmöglichkeiten für die darin deklarierten Typen <stdbool.h> Definition von Makros zur einfacheren Verwendung des in der Sprache definierten logischen Datentyps _Bool <stdint.h> Deklaration von Ganzzahl-Datentypen mit besonderen Eigenschaften hinsichtlich ihrer Darstellungsbreite, Definition von Makros zur Bezeichnung des jeweiligen Minimal- und Maximalwertes dieser Ganzzahl-Datentypen <tgmath.h> Einbinden von <math.h> und <complex.h>, Definition typ-generischen Makros zur Anwendung der in diesen Header-Dateien deklarierten mathematischen Funktionen. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/02/1 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Überblick über die I/O-Funktionen der ANSI-C-Standardbibliothek (1) (Funktionsdeklarationen in <stdio.h>) • Funktionen zur Dateibearbeitung (ohne Schreiben und Lesen) int fclose (FILE *fp); Schließen einer Datei int fflush (FILE *fp); Herausschreiben eines Dateipuffers int fgetpos (FILE *fp, fpos_t *ptr); Ermitteln der aktuellen Bearbeitungsposition FILE *fopen (char *path, char *mode); Öffnen einer Datei FILE *freopen (char *path, char *mode, FILE *fp); Erneutes Öffnen einer offenen Datei int fseek (FILE *fp, long offs, int org); Verändern der Bearbeitungsposition int fsetpos (FILE *fp, fpos_t *ptr); Verändern der Berabeitungsposition long ftell (FILE *fp); Ermitteln der aktuellen Bearbeitungsposition void rewind (FILE *fp); Zurücksetzen auf den Dateianfang void setbuf (FILE *fp, char *buf); Bereitstellen eines benutzerdefinierten Dateipuffers int setvbuf (FILE *fp, char *buf, int mode, size_t size); Bereitstellen eines benutzerdefinierten Dateipuffers • Funktionen zur Dateiverwaltung int remove (char *path); Löschen einer Datei int rename (char *oldpath, char *newpath); Umbenennen einer Datei FILE *tmpfile (void); Erzeugen und Öffnen einer temporären Datei char *tmpnam (char s[L_tmpnam]); Erzeugen eines noch nicht vorhandenen Dateinamens • Funktionen zur Fehlerbehandlung void clearerr (FILE *fp); Rücksetzen des Fehler- und des EOFFlags int feof (FILE *fp); Überprüfung des EOF-Flags int ferror (FILE *fp); Überprüfung des Fehler-Flags void perror (char *s); Ausgabe von Fehlermeldungen nach stderr HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/02/2 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Überblick über die I/O-Funktionen der ANSI-C-Standardbibliothek (2) (Funktionsdeklarationen in <stdio.h>) • Funktionen zur Ein-und Ausgabe von/nach stdin und stdout int getchar (void); zeichenweise Eingabe von stdin char *gets (char *s); zeilenweise Eingabe von stdin int printf (char *ctrl, ...); formatierte Ausgabe nach stdout int putchar (int c); zeichenweise Ausgabe nach stdout int puts (char *s); zeilenweise Ausgabe nach stdout int scanf (char *ctrl, ...); formatierte Eingabe von stdin int vprintf (char *ctrl, va_list arg); formatierte Ausgabe nach stdout • Funktionen zur Ein-/Ausgabe beliebiger Dateien int fgetc (FILE *fp); zeichenweise Eingabe char *fgets (char *s, int n, FILE *fp); zeilenweise Eingabe int fprintf (FILE *fp,char *ctrl, ...); formatierte Ausgabe int fputc (int c, FILE *fp); zeichenweise Ausgabe int fputs (char *s, FILE *fp); stringweise Ausgabe size_t fread (void *ptr, size_t size, size_t count, FILE *fp); datenobjektorientierte Eingabe int fscanf (FILE *fp, char *ctrl, ...); formatierte Eingabe datenobjektorientierte Ausgabe size_t fwrite (void *ptr, size_t size, size_t count, FILE *fp); int getc (FILE *fp); zeichenweise Eingabe int putc (int c, FILE *fp); zeichenweise Ausgabe int ungetc (int c, FILE *fp); Rückgabe eines Zeichens in Eingabepuffer int vfprintf (FILE * fp, char *ctrl, va_list arg); formatierte Ausgabe • Funktionen zur Ein-/Ausgabe in/aus Strings int sprintf (char *s, char *ctrl, ...); format. Ausgabe in einen String int sscanf (char *s, char *ctrl, ...); format. Eingabe aus einem String int vsprintf (char *s, char *ctrl, va_list arg); format. Ausgabe in einen String HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/03/1 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der ANSI-C-Standardbibliothek zum Zeichenklassentest Bei Verwendung einer der folgenden Funktionen (die häufig als Makros realisiert sind) muß die zugehörige Standard-Header-Datei mittels #include <ctype.h> in das Quellmodul eingebunden werden. • Alle Funktionen haben einen int-Übergabeparameter. Der übergebene aktuelle int-Parameter muß in den Wertebereich von unsigned char fallen oder EOF sein. • Funktionen zur Überprüfung eines Zeichens auf eine bestimmte Klassenzugehörigkeit Funktionswert: • - != 0 (TRUE), wenn der übergebene aktuelle Parameter in die jeweils überprüfte Zeichenklasse fällt, - == 0 (FALSE), wenn der übergebene aktuelle Parameter nicht in die überprüfte Zeichenklasse fällt - einen undefinierten Wert, wenn der übergebene aktuelle Parameter nicht als unsigned char darstellbar und nicht gleich EOF ist. int isalnum (int c); Überprüfung auf Buchstabe (Groß- oder Klein-) oder (Dezimal-)Ziffer int isalpha (int c); Überprüfung auf Buchstabe (Groß- oder Klein-) int iscntrl (int c); Überprüfung auf Steuerzeichen int isdigit (int c); Überprüfung auf (Dezimal-)Ziffer int isgraph (int c); Überprüfung auf darstellbare Zeichen, ausgenommen BLANK (SPACE) int islower (int c); Überprüfung auf Klein-Buchstaben int isprint (int c); Überprüfung auf darstellbare Zeichen, einschließlich BLANK (SPACE) int ispunct (int c); Überprüfung auf darstellbare Zeichen, ausgenommen BLANK, Buchstaben und (Dezimal-)Ziffern int isspace (int c); Überprüfung auf BLANK (SPACE), FF, NEWLINE, CR, HT und VT int isupper (int c); Überprüfung auf Groß-Buchstaben int isxdigit (int c); Überprüfung auf Hexadezimal-Ziffer Funktionen zur Zeichenumwandlung Funktionswert: - das umgewandelte Zeichen, falls eine Umwandlung relevant ist, - den als aktuellen Parameter übergebenen Wert, falls eine Umwandlung nicht relevant ist. int tolower (int c); Umwandlung von Groß- in Klein-Buchstaben int toupper (int c); Umwandlung von Klein- in Groß-Buchstaben HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/04/1 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der ANSI-C-Standardbibliothek zur Stringbearbeitung Überblick (1) ● Funktions-Deklarationen (Function Prototypes) in : <string.h> ● Funktionen zum Kopieren von Strings und Zeichenarrays void *memcpy(void *s1, const void *s2, size_t n); Kopieren von n Zeichen vom Array s2 in das Array s1 void *memmove(void *s1, const void *s2, size_t n); Kopieren von n Zeichen vom Array s2 in das Array s1(Überlappung ist zulässig) char *strcpy(char *s1, const char *s2); Kopieren des Strings s2 in den String s1. char *strncpy(char *s1, const char *s2, size_t n); Kopieren von maximal n Zeichen vom String s2 in den String s1 (ggf. kein Abschluß mit '\0') ● Funktionen zum Aneinanderhängen von Strings char *strcat(char *s1, const char *s2); Anhängen von String s2 an den String s1 char *strncat(char *s1, const char *s2, size_t n); Anhängen von maximal n Zeichen des Strings s2 an den Str. s1 (Abschluß immer mit '\0') ● Funktionen zum Vergleich von Strings und Zeichenarrays int memcmp(const void *s1, const void *s2, size_t n); Vergleich von n Zeichen der Arrays s1 und s2 int strcmp(const char *s1, const char *s2); Vergleich der Strings s1 und s2 int strncmp(const char *s1, const char *s2, size_t n); Vergleich der Strings s1 und s2 (max. n Zeichen) int strcoll(const char *s1, const char *s2); Vergleich der Strings s1 und s2 gemäß dem mit setlocale() festgelegten länderspezifischen Zeichencode size_t strxfrm(char *s1, const char *s2, size_t n); Code-Transformation von maximal n Zeichen des Strings s2 in den String s1 Quellcode: länderspezifisch Zielcode : der von strcmp() verwendete Zeichencode HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/04/2 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen der ANSI-C-Standardbibliothek zur Stringbearbeitung Überblick (2) • • Funktionen zum Suchen in Strings und Arrays void *memchr(const void *s, int c, size_t n); Durchsuchen von max n Zeichen des Arrays s nach dem ersten Auftritt des Zeichens c char *strchr(const char *s, int c); Durchsuchen des Strings s nach dem ersten Auftreten des Zeichens c char *strrchr(const char *s, int c); Durchsuchen des Strings s nach dem letzten Auftreten des Zeichens c char *strpbrk( const char *s1, const char *s2); Durchsuchen des Strings s1 nach dem ersten Auftreten irgendeines der im String s2 enthaltenen Zeichen char *strstr( const char *s1, const char *s2); Durchsuchen des Strings s1 nach dem ersten Auftreten des Strings s2 size_t strspn( const char *s1, const char *s2); Ermittlung der Länge des Anfangs des Strings s1, der nur aus in s2 enthaltenen Zeichen besteht == Index des 1. Auftritts eines Zeichens in s1, das mit keinem Zeichen in s2 übereinstimmt size_t strcspn( const char *s1, const char *s2); Ermittlung der Länge des Anfangs des Strings s1, der nur aus in s2 nicht enthaltenen Zeichen besteht == Index des 1. Auftritts eines Zeichens in s1, das mit irgendeinem Zeichen in s2 übereinstimmt char *strtok(char *s1, const char *s2); Zerlegen des Strings s1 in die Teilstrings, die durch im String s2 enthaltene Zeichen getrennt werden. Aufeinanderfolgende Aufrufe liefern jeweils den Pointer auf den nächsten Teilstring bzw den NULL-Pointer (Ende!). Ab dem 2. Aufruf muss für s1 der NULLPointer übergeben werden. Funktion zum Füllen eines Arrays mit einem Zeichen void *memset(void *s, int c, size_t n); • Funktion zur Ermittlung der Länge eines Strings size_t strlen(const char *s); • Ablage des Zeichens c in n aufeinanderfolgende Elemente des (char-) Arrays s Ermittlung der Länge des Strings s Funktion zur Ermittlung einer Fehlermeldung char *strerror(int errnum); Ermittlung der Fehlermeldung, die zur Fehlernummer errnum gehört HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/04/3 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Ausgewählte Stringbearbeitungsfunktionen (1) strcpy - Kopieren eines Strings #include <string.h> char *strcpy(char *str1, const char *str2); Aktion: "strcpy()" kopiert den String "str2" in den String "str1" Übergabeparameter : str1 str2 Funktionswert : String, in den kopiert wird (Zielstring) String, der kopiert wird (Quellstring) Pointer auf den Zielstring, d.h. "str1" (== 1.Parameter) strcat - Konkatenation zweier Strings #include <string.h> char *strcat(char *str1, const char *str2); Aktion: "strcat()" hängt den String "str2" an den String "str1" an. (der resultierende String "str1" wird mit dem '\0'-Zeichen abgeschlossen) Übergabeparameter : str1 str2 Funktionswert: strcmp String, an den angehängt wird String, der angehängt wird Pointer auf den resultierenden String, d.h. "str1" (== 1.Parameter) - Vergleich zweier Strings #include <string.h> int strcmp(const char *str1, const char *str2); Aktion: "strcmp()" vergleicht den String "str1" lexikographisch mit dem String "str2" Übergabeparameter : str1 str2 erster zu vergleichender String zweiter zu vergleichender String Funktionswert : wenn "str1" kleiner "str2" ist wenn "str1" gleich "str2" ist wenn "str1" größer "str2" ist <0 ==0 >0 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/04/4 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Ausgewählte Stringbearbeitungsfunktionen (2) strlen - Ermittlung der Länge eines Strings #include <string.h> size_t strlen(const char *str); Aktion: "strlen()" ermittelt die Länge des Strings "str" in Anzahl Zeichen (ohne abschliessendes '\0'-Zeichen) Übergabeparameter : str String, dessen Länge ermittelt werden soll Funktionswert : Länge des Strings "str" in Anzahl Zeichen Anmerkung : Der Typ size_t ist der "natürliche" unsigned-Typ (definiert in <string.h>) strchr - Durchsuchen eines Strings nach einem Zeichen #include <string.h> char *strchr(const char *str, int c); Aktion: "strstr()" durchsucht den String "str" nach dem ersten Auftreten des Zeichens "c". Übergabeparameter : str C Funktionswert: zu durchsuchender String Suchzeichen Pointer auf die Stelle im String "str", an der das Zeichen "c" erstmals auftritt bzw NULL-Pointer, wenn das Zeichen "c" nicht im String "str" enhalten ist strstr - Durchsuchen eines Strings nach einem anderen String #include <string.h> char *strstr(const char *str1, const char *str2); Aktion: "strstr()" durchsucht den String "str1" nach dem ersten Auftreten des Strings "str2". Übergabeparameter : str1 str2 Funktionswert: zu durchsuchender String Suchstring Pointer auf die Stelle im String "str1", an der der String "str2" beginnt bzw NULL-Pointer, wenn der String "str2" nicht im String "str1" enhalten ist HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/04/5 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Ausgewählte Zeichenarray-Bearbeitungsfunktionen (1) memcpy - Kopieren einer Zeichenfolge bestimmter Länge #include <string.h> void *memcpy(void *s1, const void *s2, size_t n); Aktion: "memcpy()" kopiert "n" Zeichen aus dem durch "s2" referierten – als Zeichenfolge aufgefassten – Datenobjekt (Quell-Speicherbereich) in das duch "s1" referierte – als Zeichenfolge aufgefasste – Datenobjekt (Ziel-Speicherbereich). Übergabeparameter : s1 s2 n Pointer auf Ziel-Datenobjekt ( Zielzeichenfolge) (Ziel-Speicherbereich, Speicherbereich in der kopiert wird) Pointer auf Quell-Datenobjekt (Quellzeichenfolge) (Quell-Speicherbereich, Speicherbereich aus dem kopiert wird) Anzahl der zu kopierenden Zeichen Funktionswert : Pointer auf den Ziel-Speicherbereich, d.h. "s1" (== 1.Parameter) Anmerkungen: 1. Quell- und Ziel-Speicherbereich dürfen sich nicht überlappen 2. Der Typ size_t ist der "natürliche" unsigned-Typ (definiert in <string.h>) memcmp - Vergleich zweier Zeichenfolgen bestimmter Länge #include <string.h> int memcmp(const void *s1, const void *s2, size_t n); Aktion: "memcmp()" vergleicht die ersten "n" Zeichen des durch "s1" referierten – als Zeichenfolge aufgefassten –Datenobjekts lexikographisch mit den ersten "n" Zeichen des durch "s2" referierten – als Zeichenfolge aufgefassten – Datenobjekts Für den Vergleich werden die Zeichen als unsigned char interpretiert. Übergabeparameter : s1 s2 n Pointer auf das erste zu vergleichende Datenobjekt (erste Zeichenfolge) Pointer auf das zweite zu vergleichende Datenobjekt (zweite Zeichenf.) Anzahl der zu vergleichenden Zeichen Funktionswert : wenn das erste unterschiedliche Zeichen bei der durch "s1" referierten Zeichenfolge kleiner als bei der durch "s2" referierten Zeichenfolge ist. wenn die ersten "n" Zeichen in beiden Zeichnfolgen gleich sind wenn das erste unterschiedliche Zeichen bei der durch "s1" referierten Zeichenfolge größer als bei der durch "s2" referierten Zeichenfolge ist. <0 ==0 >0 Anmerkung : Der Typ size_t ist der "natürliche" unsigned-Typ (definiert in <string.h>) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/04/6 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Ausgewählte Zeichenarray-Bearbeitungsfunktionen (2) memchr - Suchen eines Zeichens in einer Zeichfolge bestimmter Länge #include <string.h> void *memchr(const void *s, int c, size_t n); Aktion: "memchr()" durchsucht die ersten "n" Zeichen des durch "s" referierten – als Zeichenfolge aufge fassten – Datenobjekts nach dem ersten Auftreten des – in unsigned char umgewandelten – Zeichens "c". Übergabeparameter : s c n Pointer auf das zu durchsuchende Datenobjekt (zu durchsuchende Zeichenfolge) (= Speicherbereich, der durchsucht wird) Zeichen, nach dem gesucht wird Anzahl der bei der Suche zu berücksichtigenden Zeichen (== Länge der zu durchsuchenden Zeichenfolge) Funktionswert : Pointer auf die Stelle, an der das Zeichen "c" erstmals auftritt bzw NULL-Pointer, wenn das Zeichen "c" nicht enhalten ist Anmerkung : Der Typ size_t ist der "natürliche" unsigned-Typ (definiert in <string.h>) memset - Füllen eines Speicherbereichs mit einem Zeichen #include <string.h> void *memset(void *s, int c, size_t n); Aktion: "memset()" setzt die ersten "n" Zeichen des durch "s" referierten Speicherbereichs auf den Wert des – in unsigned char umgewandelten – Zeichens "c" Übergabeparameter : s c n Pointer auf den zu füllenden Speicherbereich Zeichen, mit dem derSpeicherbereich gefüllt wird Länge des zu füllenden Speicherbereichs (in Bytes) Funktionswert : Pointer auf gefüllten Speicherbereich (== Parameter "s") Anmerkung : Der Typ size_t ist der "natürliche" unsigned-Typ (definiert in <string.h>) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/1 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Allgemeine Utility-Funktionen der ANSI-C-Standardbibliothek (1) ● In der Standard-Header-Datei <stdlib.h> sind die Deklarationen einer Reihe von Funktionen enthalten, die man zusammenfassend als Allgemeine Utility-Funktionen bezeichnen kann. Diese Funktionen lassen sich in die folgenden Gruppen einteilen : ▻ Funktionen zur Umwandlung von Strings in numerische Werte ▻ Funktionen zur Erzeugung von Pseudo-Zufallszahlen ▻ Funktionen zur dynamischen Speicherverwaltung ▻ Environment -Funktionen ▻ Such- und Sortierfunktionen ▻ Integer-Arithmetik -Funktionen ▻ Funktionen zur Verarbeitung erweiterter Zeichensätze (nicht in allen Implementierungen enthalten) ● Bei Verwendung dieser Funktionen ist die Standard-Header-Datei <stdlib.h> einzubinden : #include <stdlib.h> ● In dieser Header-Datei sind auch die folgenden Typen und Konstanten definiert: ▻ Typ div_t ein Structure-Typ für den Funktionswert von div(). typedef struct {int quot, rem;} div_t; ▻ Typ ldiv_t ein Structure-Typ für den Funktionswert von ldiv(). typedef struct {long quot, rem;} ldiv_t; ▻ Typ size_t der "natürliche" unsigned-Typ, d.h. der Typ, den der sizeofOperator liefert. ▻ Typ wchar_t ein ganzzahliger Typ, dessen Wertebereich den größten vom Compiler unterstützten Zeichensatz darstellen kann(nur definiert, wenn die Funktionen zur Verarbeitung erweiterter Zeichensätze implementiert sind). ▻ Konstante EXIT_FAILURE ein ganzzahliger Wert zur Kennzeichnung einer fehlerhaften Programmbeendigung (Rückgabewert von exit()). ▻ Konstante EXIT_SUCCESS ein ganzzahliger Wert zur Kennzeichnung einer erfolgreichen Programmbeendigung (Rückgabewert von exit()). ▻ Konstante MB_CUR_MAX ein positiver ganzzahliger Wert, der angibt aus wieviel Bytes ein MultiByte-Character maximal bestehen kann (nur definiert, wenn die Funktionen zur Verarbeitung erweiterter Zeichensätze implementiert sind). ▻ Konstante RAND_MAX die größte von rand() erzeugte Pseudo-Zufallszahl (ganzzahlig), dieser implementierungs-abhängige Wert muss mindestens 32767 sein. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/2 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Allgemeine Utility-Funktionen der ANSI-C-Standardbibliothek (2) • • • Funktionen zur Umwandlung von Strings in numerische Werte double atof(const char *s); Umwandlung des Strings s in einen doubleWert int atoi(const char *s); Umwandlung des Strings s in einen intWert long atol(const char *s); Umwandlung des Strings s in einen longWert double strtod(const char *s, char **endp); Umwandlung des Strings s in einen doubleWert, Speicherung eines Pointers auf das erste nicht umgewandelte Zeichen von s in *endp (nur wenn endp != NULL) long strtol(const char *s, char **endp, int base); Umwandlung des Strings s in einen longWert mit der Basis base, Speicherung eines Pointers auf das erste nicht umgewandelte Zeichen von s in *endp (nur wenn endp != NULL) unsigned long strtoul(char *s, char **endp, int base); Umwandlung des Strings s in einen unsigned long-Wert mit der Basis base, Speicherung eines Pointers auf das erste nicht umgewandelte Zeichen von s in *endp (nur wenn endp != NULL) Funktionen zur Erzeugung von Pseudo-Zufallszahlen int rand(void); Erzeugung einer ganzzahligen PseudoZufallszahl im Bereich 0 .. RAND_MAX void srand(unsigned int seed); Setzen des Initialisierungswertes für die durch rand() erzeugte Zufallszahlenfolge (default : 1) Funktionen zur dynamischen Speicherverwaltung void *calloc(size_t nobj, size_t size); Allokation eines Speicherblocks für nobj Objekte je der Länge size, Initialisierung aller Speicherplätze mit 0 void free(void *p); Freigabe des Speicherbereichs auf den p zeigt void *malloc(size_t size); Allokation eines Speicherblocks derLänge size (keine Initialisierung) void *realloc(void *p, size_t size); Änderung der Größe eines bereits vorher allokierten - durch p referierten Speicherblocks - auf den neuen Wert size HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/3 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Allgemeine Utility-Funktionen der ANSI-C-Standardbibliothek (3) • • • Environment-Funktionen void abort(void); Anormale Beendigung eines Programms int atexit(void (*fcn)(void)); Registrierung der Funktion (*fcn)() als eine Funktion, die bei Programmbeendigung mittels exit() aufgerufen wird void exit(int status); Normale Beendigung eines Programms char *getenv(const char *name); Ermittlung des zur Environment-Variablen name gehörenden Strings (Wert der Environment-Variablen) (implementierungsabhängig) int system(const char *s); Übergabe des - ein Kommando bedeutenden - Strings s an die Betriebssystem-Umgebung zur Ausführung Such- und Sortier-Funktionen void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*cmp)(const void *, const void *)); Durchsuchen eines in aufsteigender Ordnung sortierten - durch base referierten - Feldes von n Objekten der Länge size auf Übereinstimmung mit dem Suchschlüssel *key. Der Vergleich erfolgt mittels der Funktion (*cmp)(). void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *)); Sortieren des - durch base referierten Feldes von n Objekten der Länge size in aufsteigender Ordnung mittels der Vergleichsfunktion (*cmp)(). Integer-Arithmetik-Funktionen int abs(int i); Ermittlung des Absolutwertes von i (int-Wert) div_t div(int dend, int disor); Ermittlung des ganzzahligen Quotienten und des Restes der Division von dend durch disor (int-Werte) long labs(long n); Ermittlung des Absolutwertes von n (long-Wert) ldiv_t ldiv(long dend, long disor); Ermittlung des ganzzahligen Quotienten und des Restes der Division von dend durch disor (long-Werte) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/4 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Funktionen zur Erzeugung von Pseudo-Zufallszahlen rand - Erzeugung einer ganzzahligen Pseudo-Zufallszahl #include <stdlib.h> int rand(void); Aktion: "rand()" ermittelt die nächste Pseudo-Zufallszahl aus einer durch einen Initialisierungswert festgelegten ganzzahligen Pseudo-Zufallszahlenfolge. Die Pseudo-Zufallszahlen liegen im Bereich 0 .. RAND_MAX. Übergabeparameter : keiner Funktionswert : die erzeugte Pseudo-Zufallszahl Anmerkung : Sollen die Pseudo-Zufallszahlen in einem anderen Bereich als 0 .. RAND_MAX liegen, müssen sie durch eine geeignete Abbildungsfunktion in den gewünschten Bereich transformiert werden. Beispiel : Sollen die Pseudo-Zufallszahlen im Bereich 0 .. ZMAX liegen (ZMAX < RAND_MAX), ist auf die erzeugten Zahlen die Operation "Modulo (ZMAX+1)" anzuwenden. srand - Setzen des Initialisierungswertes einer Pseudo-Zufallszahlenfolge #include <stdlib.h> void srand(unsigned int seed); Aktion: "srand()" setzt den Initialisierungswert für eine durch den fortlaufenden Aufruf von "rand()" erzeugbare Pseudo-Zufallszahlen-Folge auf den Parameter "seed". Ein bestimmter Wert von "seed" führt immer zur Erzeugung der gleichen Zahlenfolge. Übergabeparameter : seed Initialisierungswert für eine Pseudo-Zufallszahlenfolge Funktionswert : keiner Anmerkung : Der Default-Initialisierungswert für eine Pseudo-Zufallszahlenfolge ist 1, d,h. der Aufruf von "rand()" ohne vorherigen Aufruf von "srand()" führt zu derselben Pseudo-Zufallszahlenfolge, wie sie der vorherige Aufruf von "rand()" mit dem Parameter 1 bewirken würde. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/5 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm zur Erzeugung von Pseudo-Zufallszahlen (1) /* --------------------------------------------------------------------Programm randdemo (C-Modul randdemo_m.c) --------------------------------------------------------------------Demonstrationsprogramm für die Verwendung von rand() und srand() Es werden MAX_ANZ_ZZ Pseudo-Zufallsszahlen zwischen 0 und einem angebbaren Maximalwert erzeugt,von diesen werden nur die ersten anz Zahlen (anz ebenfalls angebbar) ausgegeben. Über die Gesamtheit der erzeugten Zahlen wird die Wahrscheinlichkeitsverteilung ausgegeben. --------------------------------------------------------------------- */ #include <stdlib.h> #include <stdio.h> #include <string.h> #define MAX_ANZ_ZZ 10000000L int main(void) { int zz; int mzz; int mdis; unsigned int sd; long* ip; int i; long l; printf("\nErzeugung von %ld Pseudo-Zufallszahlen :\n", MAX_ANZ_ZZ); printf("\nZufallszahlenbereich : 0 .. ? "); scanf("%d", &mzz); printf("\nInitialisierungswert der Folge ? "); scanf("%u", &sd); printf("\nAnzahl der auszugebenden Zahlen ? "); scanf("%d", &mdis); ip = calloc(mzz+1,sizeof(long)); /* Alternative : ip = malloc((mzz+1)*sizeof(long)); memset(ip, '\0', (mzz+1)*sizeof(long)); */ putchar('\n'); srand(sd); for (l=0; l<MAX_ANZ_ZZ; l++) { zz=rand()%(mzz+1); if (l< mdis) printf("%d ", zz); ++ip[zz]; } putchar('\n'); printf("\nWahrscheinlichkeitsverteilung : \n"); for (i=0; i<= mzz; i++) printf("\n%4d : %.6f", i, (double)ip[i]/MAX_ANZ_ZZ); printf("\n\n"); return 0; } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/6 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm zur Erzeugung von Pseudo-Zufallszahlen (2) Beispiel für einen Programmlauf HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/7 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Ausgewählte Environment-Funktionen exit - Beendigung des Programms #include <stdlib.h> void exit(int status); Aktion: "exit()" beendet das laufende Programm (normale Beendigung). Vor der Rückkehr zum Aufrufer des Programms (z.B. der Kommandoprozessor des Betriebssystems) werden alle gegebenenfalls mit "atexit()" festgelegten Funktionen aufgerufen, sowie alle noch nicht herausgeschriebenen Datei-Schreib-Puffer herausgeschrieben und alle offenen Dateien geschlossen. Der Parameter "status" wird als Program Return Code dem Aufrufer des Programms übergeben (auf der BS-Ebene kann er mit geeigneten System Calls ermittelt werden). I.a. ist es üblich (aber nicht verpflichtend) eine fehlerfreie Programmbeendigung mit dem Return Code 0 zu kennzeichnen, Werte != 0 stehen i.a. für Fehlergründe, die zur Programmbeendigung geführt haben Übergabeparameter : status Rückgabewert des Programms (Program Return Code) Funktionswert : keiner, da die Funktion nicht zurückkehrt Anmerkung : Die Funktion "exit()" sollte i.a. – wenn überhaupt – nur aufgerufen werden, wenn das Programs aus einer anderen Funktion als "main()" heraus beendet werden soll (was aber einen – nur wenn wohl durchdacht auch tolerierbaren – Verstoß gegen Grundsätze einer strukturierten Programmierung darstellt). Normalerweise gibt es keinen Grund, "exit()" von "main()" aus aufzurufen. (eine Beendigung von "main()" bewirkt das gleiche wie der Aufruf von "exit()") system - Ausführung eines Programms durch das Betriebssystems #include <stdlib.h> int system(const char *s); Aktion: "system()" ruft den Kommandoprozessor des Betriebssystems auf und übergibt diesem die durch "s" referierte Kommandozeile zur Ausführung. Übergabeparameter : s Funktionswert : Pointer auf die Kommandozeile zum Aufruf des Programms (Programmpfad einschließlich Programmparameter) - falls für "s" der NULL-Pointer übergeben wird : ==0, wenn kein Kommandoprozessor verfügbar ist !=0, wenn ein Kommandoprozessor verfügbar ist - falls für "s" ein Wert != NULL übergeben wird : ein implementierungsabhängiger Wert HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/8 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Such- und Sortierfunktionen bsearch - Durchsuchen eines in aufsteigender Ordnung sortierten Arrays #include <stdlib.h> void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*cmp)(const void*, const void*)); Aktion: "bsearch()" durchsucht das durch base referierte Array nach dem Auftritt eines Elements, das mit dem durch key referierten Suchschlüssel übereinstimmt. Für den Vergleich des Suchschlüssels mit den einzelnen Array-Elementen ruft die Funktion die durch cmp referierte Vergleichsfunktion auf (Pointer auf Suchschlüssel wird dabei als erster Parameter übergeben). Die Vergleichsfunktion muss folgende Funktionswerte erzeugen : <0, wenn das erste Element kleiner als das zweite ist (Suchschlüssel < Array-Element) ==0, wenn beide Elemente gleich sind (Suchschlüssel == Array-Element) >0, wenn das erste Element größer als das zweite ist (Suchschlüssel > Array-Element) Übergabeparameter : key base n size cmp Funktionswert : qsort Pointer auf Suchschlüssel Pointer auf das zu durchsuchende Array Arraygröße (== Anzahl der Array-Elemente) Größe eines Array-Elementes in Bytes Pointer auf Vergleichsfunktion, die für das Suchen verwendet wird - Pointer auf das gefundene Element bei Erfolg - NULL-Pointer bei Misserfolg - Sortieren eines Arrays in aufsteigender Ordnung #include <stdlib.h> void qsort(void *base, size_t n, size_t size, int (*cmp)(const void*, const void*)); Aktion: "qsort()" sortiert das durch base referierte Array nach dem Quick-Sort-Verfahren. Für den Vergleich von jeweils zwei Elementen ruft die Funktion die durch cmp referierte Vergleichsfunktion auf. Dieser werden die Pointer auf die zu vergleichenden Elemente als Parameter übergeben. Sie muss folgende Funktionswerte erzeugen : <0, wenn das erste Element kleiner als das zweite ist ==0, wenn beide Elemente gleich sind >0, wenn das erste Element größer als das zweite ist Übergabeparameter : base n size cmp Funktionswert : keiner Pointer auf das zu sortierende Array Arraygröße (== Anzahl der Array-Elemente) Größe eines Array-Elementes in Bytes Pointer auf Vergleichsfunktion, die für das Sortieren verwendet wird HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/05/9 – TH/KR – 01 ----------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsbeispiel zur Bibliotheksfunktion qsort() /* ----------------------------------------------------------------------C-Modul qsortdemo1_m.c ----------------------------------------------------------------------Einfaches Demo-Programm zur Bibliotheksfunktion qsort() Sortieren von double-Werten, die von der Standard-Eingabe eingelesen werden, Ausgabe der sortierten Werte in die Standard-Ausgabe ----------------------------------------------------------------------- */ #include <stdlib.h> #include <stdio.h> #define MAXANZ 10 int lesenDoubleWerte(double*, int); void ausDoubleWerte(double*, int); int vglDouble(const void *, const void *); int main(void) { int anz; double darr[MAXANZ]; anz = lesenDoubleWerte(darr, MAXANZ); qsort(darr, anz, sizeof(double), vglDouble); ausDoubleWerte(darr, anz); return 0; } int vglDouble(const void *d1, const void *d2) { int iRet = 0; if (*(double*)d1<*(double*)d2) iRet = -1; else if (*(double*)d1>*(double*)d2) iRet = 1; return iRet; //return *(double*)d1 - *(double*)d2; // geht nicht !!! Warum ??? } int lesenDoubleWerte(double* dfeld, int iAnz) { unsigned int i = 0; int ende = 0; printf("Eingabe von double-Werten (maximal %d) :\n", iAnz); while (i<iAnz && !ende) { printf("Wert %2d : ",i); fflush(stdout); if (scanf ("%lf", dfeld++)<=0) ende = 1; else i++; } return i; } void ausDoubleWerte(double* dfeld, int iAnz) { unsigned int i; printf("\nAusgabe der sortierten Werte :\n"); for (i=0;i<iAnz;i++) { printf("Wert %2d : %7.3lf\n",i,*(dfeld++)); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/06/1 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Mathematische Funktionen der ANSI-C-Standardbibliothek (1) • Die ANSI-C-Standardbibliothek stellt auch eine Anzahl häufig benötigter mathematischer Funktonen zur Verfügung. Die entsprechenden Funktions-Deklarationen befinden sich in der Header-Datei <math.h>. • Alle mathematischen Funktionen erzeugen einen Funktionswert vom Typ double. Die Parameter sind i.a. ebenfalls vom Typ double. • Alle Funktionen erkennen Bereichsüberschreitungen sowohl für die übergebenen aktuellen Parameter als auch für den Funktionswert : - liegt ein aktueller Parameterwert außerhalb des zulässigen Bereichs ( domain error), so wird die globale Fehlervariable errno auf den Wert EDOM gesetzt. Der Funktionswert ist in diesem Fall implementierungsabhängig. - liegt der sich ergebende Funktionswert außerhalb des darstellbaren Wertebereichs des Typs double ( range error), so wird die globale Fehlervariable errno auf den Wert ERANGE gesetzt. - Als Funktionswert wird in einem derartigen Fall zurückgegeben : - der Wert 0, wenn ein Bereichs-Unterlauf auftritt, bzw. - der Wert HUGE_VAL (gegebenenfalls mit einem Vorzeichen versehen),wenn ein BereichsÜberlauf auftritt. • Die Deklaration der Fehlervariablen errno und die Definition der Konstanten EDOM und ERANGE ist in der Standard-Header-Datei <errno.h> enthalten. • In <math.h> ist außerdem der Makro HUGE_VAL definiert: double-Wert, der zur Kennzeichnung des Überschreitens des darstellbaren Wertebereichs dient (kann "unendlich" repräsentieren). Die Definition kann als Konstante oder als Variable erfolgen. • Exponential- und logarithmische Funktionen double exp(double x); Exponentialfunktion ex ("e hoch x") double frexp(double x, int *ex); Zerlegung von x in die normalisierte Darstellung x = m*2n mit 0.5 <= m < 1 m: Funktionswert n: Ablage in *ex Sonderfall: x=0 ? m=0 und n=0 double ldexp(double x, int n); Funktion x*2n double log(double x); natürlicher Logarithmus ln(x), x>0 double log10(double x); dekadischer Logarithmus lg(x), x>0 double modf(double x, int *ip); Zerlegung von x in einen ganzzahligen und einen gebrochenen Anteil; gebrochener Anteil: Funktionswert ganzzahliger Anteil: Ablage in *ip double pow(double x, double y); xy ("x hoch y") double sqrt(double x); Positive Qaudratwurzel von x, x>=0 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/06/2 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Mathematische Funktionen der ANSI-C-Standardbibliothek (2) • Trigonometrische und hyperbolische Funktionen Die trigonometrischen Funktionen arbeiten mit Parametern im Bogenmaß. Deren Wert sollte nicht zu weit ausserhalb des Intervalls [-2π ... 2π] liegen, da die Funktionen für Parameter mit großem Betrag häufig ungenaue Werte liefern. Gegebenenfalls sind die Parameter modulo π zu übergeben. • double acos(double x); Arcus-Cosinus von x (Hauptwert) -1 <= x <= 1 Funktionswert: [0 .. π] double asin(double x); Arcus-Sinus von x (Hauptwert) -1 <= x <= 1 Funktionswert : [-π/2 .. π/2] double atan(double x); Arcus-Tangens von x (Hauptwert) Funktionswert : [-π/2 .. π/2] double atan2(double y, double x); Arcus-Tangens von y/x (Hauptwert) Funktionswert : [-π .. π] Die Ermittlung des Quadranten, in dem der Funktionswert liegt, erfolgt unter Berücksichtigung des Vorzeichens beider Parameter. double cos(double x); Cosinus von x double cosh(double x); Cosinus Hyperbolicus von x double sin(double x); Sinus von x double sinh(double x); Sinus Hyperbolicus von x double tan(double x); Tangens von x double tanh(double x); Tangens Hyperbolicus von x Sonstige mathematische Funktionen double ceil(double x); kleinste ganze Zahl >= x double fabs(double x); Absolutwert von x double floor(double x); größte ganze Zahl <=x double fmod(double x, double y); Gleitpunkt-Rest der Division x/y Funktionswert hat das Vorzeichen von x Falls y=0 ? Funktionswert implementierungsabhängig HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/07/1 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Datums- und Zeitfunktionen (1) • Die ANSI-C-Standard-Bibliothek stellt Funktionen, die einen Zugriff zur Systemuhr ermöglichen, zur Verfügung : - Funktionen zur Ermittlung von Zeiten und Zeitdifferenzen - Funktionen zur Umwandlung von Zeitdarstellungen - Funktionen zur Darstellung von Zeiten als Strings • Es werden drei verschiedenen Zeitdarstellungen verwendet : - Prozessorzeit (processor time), ausgedrückt in Systemzeit-Perioden. - Kalenderzeit (calendar time), gemäß dem Gregorianischen Kalender, in einer implementierungsspezifischen unstrukturierten Darstellung. - Ortszeit (local time), die für eine spezifische Zeitzone geltende Kalenderzeit, gegebenenfalls modifiziert als Sommerzeit (daylight saving time), in einer strukturierten Darstellung. • Die entsprechenden Funktionsdefinitionen befinden sich in der Standard-Header-Datei <time.h>. Diese ist daher bei Verwendung der Funktionen mittels #include <time.h> einzubinden. In <time.h> sind außerdem definiert : Konstante Anzahl der Perioden (ticks) der Systemzeit pro Sekunde Typ CLOCKS_PER_SEC (== CLK_TCK) clock_t Typ time_t Arithmetischer Datentyp zur unstrukturiertenDarstellung der Kalendertzeit (meist als long definiert) Typ Arithmetischer Datentyp zur Darstellung der Prozessorzeit (meist als long definiert) Structure-Typ zur strukturierten Darstellung der Kalenderzeit (Ortszeit) struct tm Der Typ struct tm muss wenigstens die folgenden Komponenten enthalten : int int int int int int int int int tm_sec; tm_min; tm_hour; tm_mday; tm_mon; tm_year; tm_wday; tm_yday; tm_isdst; /* /* /* /* /* /* /* /* /* Sekunden nach der Minute Minuten nach der Stunde Stunden seit Mitternacht Tag des Monats Monate seit Januar Jahre seit 1900 Tage seit Sonntag Tage seit 1.Januar Sommerzeit-Flag [0 [0 [0 [1 [0 */ */ */ */ */ */ [0 .. 6] */ [0 ..365] */ *) */ *) Für den Wert des Sommerzeit-Flags tm_isdst gilt : - >0 - =0 - <0 wenn die Sommerzeit in Kraft ist wenn die Sommerzeit nicht in Kraft ist wenn die Information nicht verfügbar ist .. .. .. .. .. 59] 59] 23] 31] 11] HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/07/2 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Datums- und Zeitfunktionen (2) • Funktionen zur Ermittlung von Zeiten und Zeitdifferenzen clock_t clock(void); Ermittlung der Prozessorzeit, die seit Programmstart vergangen ist, in Systemzeit-Perioden (ticks). clock()/CLK_TCK ist die Zeit in Sekunden. Funktionswert = (clock_t)-1, wenn die Prozessorzeit nicht verfügbar ist. time_t time(time_t *timer); Ermittlung der Kalenderzeit in einer implementierungsspezifischen Darstellung. Falls timer!=NULL wird der Funktionswert auch *timer zugewiesen. Funktionswert = (time_t)-1, wenn Kalenderzeit nicht verfügbar ist. double difftime(time_t t2, time_t t1); Bildung der Zeitdifferenz t2 - t1 ausgedrückt in Sekunden (t2 und t1 enthalten Zeiten in implementierungsspezifischer Darstellung!) • Funktionen zur Umwandlung von Zeitdarstellungen struct tm *gmtime(const time_t *tp); Umwandlung der in implementierungsspezifischer Darstellung vorliegenden Kalenderzeit *tp in eine strukturierte Darstellung der Coordinated Universal Time (UTC). Falls die UTC nicht ermittelt werden kann, ist der Funktionswert = NULL struct tm *localtime(const time_t *tp); Umwandlung der in implementierungsspezifischer Darstellung vorliegenden Kalenderzeit *tp in eine strukturierte Darstellung der Ortszeit time_t mktime(struct tm *tptr); Umwandlung der in strukturierter Darstellung vorliegenden Ortszeit *tptr in die implementierungsspezifische Darstellung der Kalenderzeit. Die Werte der einzelnen Strukturkomponenten müssen nicht normiert sein. Die Funktion führt zusätzlich ihre Normierung durch und berechnet die Werte für die Komponenten tm_wday und tm_yday. Falls keine darstellbare Kalenderzeit ermittelt werden kann erzeugt die Funktion den Wert (time_t)-1. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/07/3 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Datums- und Zeitfunktionen (3) • Funktionen zur Darstellung von Zeiten als Strings char *asctime(const struct tm *tptr); Umwandlung der in strukturierter Darstellung vorliegenden Ortszeit *tptr in einen String der folgenden Form: "Sun Apr 14 11:23:22 1991\n\0" Der Funktionswert ist Pointer auf diesen String. char *ctime(const time_t *tp); Umwandlung der in implementierungsspezifischer Darstellung vorliegenden Kalenderzeit *tp in einen String der folgenden Form: "Sun Apr 14 11:23:22 1991\n\0" Der Funktionswert ist Pointer auf diesen String. ctime(tp) ist äquivalent zu asctime(localtime(tp)). size_t strftime( char *s, size_t smax, const char *fmt, const struct tm *tptr); Ablage von in *tptr enthaltenen Zeitund Datumsinformationen in den String s gemäß den im String fmt enthaltenen Formatspezifikationen. Jeder sonstiger Text in fmt wird nach s übernommen. Der String s darf maximal smax Zeichen lang werden. Funktionswert: - Anzahl der in s abgelegten Zeichen ohne abschließendes '\0'), wenn diese <=smax ist. - 0, wenn s länger als smax Zeichen werden würde. Der Inhalt von s ist in diesem Fall undefiniert. Format-Spezifier für strftime(): %a %A %b %B %c %d %H %I %j %m %M %p %S %U %w %W %x %X %y %Y %Z %% abgekürzter Name des Wochentags ausgeschriebener Name des Wochentags abgekürzter Name des Monats ausgeschriebener Name des Monats geeignete Darstellung der lokalen Zeit (Datum und Zeit) Tag des Monats als Dezimalzahl (01 .. 31) Stunde als Dezimalzahl (24-Std-Uhr) (00 .. 23) Stunde als Dezimalzahl (12-Std-Uhr) (01 .. 12) Tag des Jahres als Dezimalzahl (001 .. 386) Monat als Dezimalzahl (01 .. 12) Minute als Dezimalzahl (00 .. 59) PM oder AM (oder landesspezifisches Äquivalent) Sekunde als Dezimalzahl (00 .. 59) Woche des Jahres dezimal Sonntag ist der erste Tag der Woche) (00 .. 53) Wochentag als Dezimalzahl (Sonntag = 0) (0 .. 6) Woche des Jahres dezimal(Montag ist der erste Tag der Woche) (00 .. 53) geeignete Darstellung des lokalen Datums geeignete Darstellung der lokalen Zeit (Jahr - Jahrhundert) als Dezimalzahl (00 .. 99) Jahr als Dezimalzahl (einschließlich Jahrhundert) Name der Zeitzone, falls ein Name existiert Das Zeichen % HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/07/4 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Datums- und Zeitfunktionen (4) ● Zusammenhänge zwischen den Datums- und Zeitdarstellungen gmtime() time() implementierungsspezifische unstrukturierte Kalenderzeit (Typ : time_t) strukturierte Kalenderzeit (UTC) (Typ : struct tm) localtime() mktime() strukturierte Kalenderzeit (Ortszeit) (Typ : struct tm) asctime() ctime() asctime() Kalenderzeit in String-Darstellung HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/07/5 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsbeispiel zu den ANSI-C-Datums- und Zeitfunktionen (1) /*--------------------------------------------------------------------*/ /* Programm timedemo (C-Modul timedemo_m.c) */ /*--------------------------------------------------------------------*/ /* Demonstrationsprogramm zur Verwendung der Zeit- und Datumsfunk*/ /* tionen der ANSI-C-Standardbibliothek */ /*--------------------------------------------------------------------*/ #include <time.h> #include <stdio.h> #include <string.h> void proc_cal_time_out(clock_t, time_t); void warten(void); void loc_time_out(struct tm *); int main(void) { time_t zeit1,zeit2; clock_t clk1, clk2; double ctsec, ttdiff; struct tm *lzeit; char zeit_str[80]; clk1=clock(); zeit1=time(NULL); proc_cal_time_out(clk1, zeit1); warten(); clk2=clock(); zeit2=time(NULL); proc_cal_time_out(clk2, zeit2); ctsec=(double)(clk2-clk1)/CLK_TCK; ttdiff=difftime(zeit2, zeit1); printf("\nWartezeit : %f Sek. (ermittelt ueber Prozessorzeit)",ctsec); printf("\n %f Sek. (ermittelt ueber Kalenderzeit)\n",ttdiff); lzeit=localtime(&zeit2); loc_time_out(lzeit); strcpy(zeit_str,asctime(lzeit)); printf("\nZeit-String : %s\n",zeit_str); return 0; } void proc_cal_time_out(clock_t ct, time_t tt) { printf("\nProzessorzeit = %ld Ticks",ct); printf("\nKalenderzeit = %ld\n",tt); } void warten(void) { printf("\nWarten bis Eingabe CR "); getchar(); } void loc_time_out(struct tm *ltp) { printf("\nOrtszeit : Jahr = %d",ltp->tm_year); printf("\n Monat = %d",ltp->tm_mon); printf("\n Tag = %d",ltp->tm_mday); printf("\n Zeit = %d:%d:%d",ltp->tm_hour,ltp->tm_min, ltp->tm_sec); printf("\nSommerzeit-Flag : %d\n",ltp->tm_isdst); } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 08/07/6 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsbeispiel zu den ANSI-C-Datums- und Zeitfunktionen (2) Beispiel für einen Programmlauf HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/00/0 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 9. Ausgewählte Algorithmen 9.1. Sieb des Eratosthenes 9.2. Rekursion 9.3. Bestimmung von Nullstellen durch Iteration 9.4. Sortieren HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/01/1 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sieb des Eratosthenes (1) ● Algorithmus zur Ermittlung von Primzahlen ● Grundprinzip : ● Modifikation : Die Zahl 2 ist die einzige gerade Primzahl. Alle weiteren Primzahlen sind ungerade. Beschränkung auf die Untersuchung der ungeraden Zahlen >= 3, d.h. das Sieb enthält nur alle ungeraden Zahlen >= 3. Annahme : Die Zahlen sind linear ihrer Größe nach im Sieb angeordnet Sie besitzen eine Position Zahlen im Sieb : 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 ... Position im Sieb : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ... Zusammenhang zwischen einer Zahl z im Sieb und ihrer Position i : z = 2*i + 3 prim sei eine Primzahl. Da sich im Sieb nur ungerade Zahlen befinden, können auch nur ungerade Vielfache der Primzahl enthalten sein (jedes gerade Vielfache ist eine gerade Zahl). Damit beschränkt sich das Entfernen auf das Entfernen der ungeraden Vielfachen der Primzahl. Für die zu entfernenden Zahlen gilt somit : zahl = prim zahl neu = zahl alt + 2 * prim Für die Position der zu entfernenden Zahlen ergibt sich : 2 * ineu + 3 = 2 * ialt + 3 + 2 * prim ineu = ialt + prim Die Positionsdifferenz der zu entfernenden Zahlen zahl ist gleich der Primzahl prim HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/01/2 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sieb des Eratosthenes (2) ● Realisierung des Siebs durch ein Array von logischen Werten. Der Index eines Array-Elements entspricht der Position einer Zahl im Sieb und legt damit eindeutig die entsprechende Zahl fest. Der Wert eines Array-Elements (TRUE oder FALSE) gibt an, ob sich die zugeordnete Zahl im Sieb befindet (TRUE) oder ob sie aus dem Sieb entfernt wurde (FALSE). Struktogramm des modifizierten Algorithmus : HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/01/3 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sieb des Eratosthenes (3) ● Programmcode : /* /* /* /* /* -------------------------------------------------------------------Programm primzahl (C-Modul sieberathost_m.c) -------------------------------------------------------------------Primzahlenermittlung mittels Sieb des Eratosthenes -------------------------------------------------------------------- */ */ */ */ */ #include <stdio.h> #define #define #define #define #define SIZE TRUE FALSE ANZZEIL WRITELN 802 1 0 12 putchar('\n') int main(void) { int i,k; int iPrim, iAnzahl; char cFlags[SIZE+1]; /* Sieb */ printf("\nPrimzahlen zwischen 3 und %d\n\n",2*SIZE+3); iAnzahl=0; for (i=0; i<=SIZE; i++) cFlags[i]=TRUE; for (i=0; i<=SIZE; i++) if (cFlags[i]) /* zugehoerige Zahl im Sieb ? */ { iPrim=2*i+3; for (k=i; k<=SIZE; k=k+iPrim) cFlags[k]=FALSE; /* zugehoerige Zahl aus Sieb entfernen */ iAnzahl++; printf("%6d",iPrim); /* Ausgabe Primzahl */ if (iAnzahl%ANZZEIL == 0) WRITELN; } return 0; } ● Programmausgabe : HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/02/1 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Rekursion (1) • Rekursion ist ein grundlegendes Prinzip zur Formulierung von Algorithmen. Auch bei Datenstrukturen wird dieses Prinzip angewendet. • Rekursion liegt vor, wenn die Definition eines Objektes unter Verwendung des Objekts selbst erfolgt : Datenstrukturen : Die Definition enthält Referenzen auf Strukturen des eigenen Typs Algorithmen (Funktionen) : Ein (Teil-)Algorithmus (eine Funktion) ruft sich während seiner/ ihrer Ausführung selbst auf • Bei Algorithmen (Funktionen) unterscheidet man : ▻ direkte Rekursion : Der Funktionsaufruf ist direkt in der Funktion enthalten (-->rekursive Funktion) ▻ indirekte Rekursion : Der Funktionsaufruf erfolgt indirekt über wenigstens eine weitere Funktion. Z.B. f() ruft b() auf und b() ruft ihrerseits f() auf. (-->rekursiv verwendete Funktion) • Eine Alternative zur Rekursion ist die Iteration. Hierunter versteht man die wiederholte Ausführung einer Anweisungsfolge, wobei die Anzahl der Wiederholungen durch bestimmte Variable gesteuert wird. • Bei jedem erneuten Aufruf (Inkarnation) einer rekursiven bzw rekursiv verwendeten Funktion werden die lokalen auto- und register-Variablen und die formalen Parameter neu angelegt, d.h. auf dem Stack wird für sie neuer Speicherplatz allokiert (bzw werden die alten register-Variablen auf dem Stack zwischengespeichert). ▻ zu den verschiedenen Funktionsinkarnationen gehören verschiedene Variablen- und Parametersätze. (Problem : Gefahr des Stack-Überlaufs) ▻ Statisch-lokale Variable werden dagegen nicht jedesmal neu angelegt. Sie existieren nur einmal und sind in allen Inkarnationen zugänglich. • Eine rekursive bzw rekursiv verwendete Funktion wird erst beendet, wenn alle in ihr bzw. durch sie erfolgten erneuten Funktionsaufrufe ihrerseits beendet worden sind. Da jede sinnvolle Rekursion einmal enden muss (Zeit, Stack !) muss die Funktion eine Abbruchbedingung enthalten, die zu keinem erneuten Funktionsaufruf führt. Die Abbruchbedingung muss durch wenigstens eine Variable gesteuert werden, mit der Information von einer Funktionsinkarnation zur nächsten übergeben wird. Hierfür lässt sich eine lokale statische Variable oder ein Parameter einsetzen. • Forderungen an eine rekursive bzw. rekursiv verwendete Funktion: Die Abbruchbedingung muss bei jedem Funktionsaufruf überprüft werden. Wenigstens eine der Variablen, die die Abbruchbedingung steuern, muss bei jedem erneuten Funktionsaufruf einen neuen Wert, im Sinne einer Annäherung an die Abbruchbedingung, bekommen. Die Abbruchbedingung muss nach einer sinnvollen Anzahl von Funktionsaufrufen erfüllt sein. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/02/2 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Rekursion (2) • Beispiel für einen rekursiven Algorithmus : Ermittlung der Fakultät fac(n)=(n!) rekursive Definition : fac(0) = 1 fac(n) = n * fac(n-1) Formulierung als C-Funktion : unsigned fac (unsigned n) { if (n==0) /* Abbruchbedingung */ return(1); else return(n*fac (n-1)); } Untersuchung der Funktionsaufrufe und Funktionswerte für fac(4) : Funktionsaufrufe Funktionswerte fac(4) 24 <┐ │ │ └> 4*fac(3) 6 <┐ │ │ └> 3*fac(2) 2 <┐ │ │ └> 2*fac(1) 1 <┐ │ │ └> 1*fac(0) ---> 1 • Anwendung rekursiver Algorithmen ◇ Rekursive Algorithmen sind häufig leichter zu formulieren und klarer zu verstehen als entsprechende iterative Algorithmen. ◇ Trotzdem ist Rekursion nicht in jedem Fall vorteilhafter. Häufig führt sie weder zu einem merklich geringeren Codeumfang noch zu einer besseren Speicherausnutzung (Stack !). Vielfach ist sogar die Laufzeit wegen der vielfachen Funktionsaufrufe länger. ◇ Andererseits lassen sich manche Probleme mittels eines rekursiven Algorithmus wesentlich effektiver – auch bezüglich des Zeitaufwands – lösen, als mit einem iterativen Algorithmus. Beispiel : Potenz n einer reellen Zahl x iterativ : (n-1) Multiplikationen rekursiv : ld n Multiplikationen In einem solchen Fall ist i.a. Rekursion vorzuziehen. ◇ Weiterhin sollte Rekursion dann angewendet werden, ▻ wenn ein iterativer Algorithmus nur sehr schwierig und umständlich zu formulieren ist, ein entsprechender rekursiver Algorithmus dagegen leicht zu finden und anschaulich zu verstehen ist. ▻ wenn die zugrundeliegenden Datenstrukturen ihrerseits rekursiv sind (z.B. lineare Listen, Bäume usw.). HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/02/3 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Dezimal-Dualwandlung mittels rekursiver Funktion /* /* /* /* /* /* /* /* /* /* -----------------------------------------------------------------Programm dezdualrek (C-Modul dezdualrek_m.c) -----------------------------------------------------------------Modifikation des Programms dezdual zur Dezimal-Dualwandlung positiver ganzer Zahlen */ */ */ */ */ */ Ausgabe einer positiven Integerzahl als Dualzahl ohne Zwischen*/ speicherung der ermittelten Dualziffern */ Einsatz einer rekursiven Funktion */ ------------------------------------------------------------------ */ #include <stdio.h> #define BASIS 2 void printdual(unsigned i) { unsigned zahl; if ((zahl=i/BASIS) != 0) printdual(zahl); putchar(i%BASIS + '0'); return; } int main(void) { unsigned zahl; while ((printf("\npositive ganze Dezimalzahl ? "), scanf("%d",&zahl)) != EOF) { printf("\numgewandelt in Dualzahl : "); printdual(zahl); putchar('\n'); } return 0; } Beispiel für einen Programmlauf : HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/1 – TH/KR – 02 --------------------------------------------------------------------------------------------------------------------------------------------------------- Nullstellen berechnen – Iterationsverfahren (1) Betrachtet wird das folgende elektrische Netzwerk: UBR IR f(UB) IB R UQ B UB UB Das Bauelement B ist nichtlinear und besitze folgende Kennlinie: IB = k * UB Gesucht ist die Spannung UB in Abhängigkeit von UQ der Spannungsquelle. Es gilt IR = IB und mit UR = UQ – UB erhält man also: UQ −U B R = k * UB Mathematisch betrachtet ist die Lösung dieser Gleichung die Bestimmung der Nullstelle der Funktion: f (U B ) = k * U B − U Q −U B R Für die numerische – und damit mittels Programm realisierbare – Bestimmung der Nullstelle einer Funktion f(x) bieten sich folgende drei Verfahren an: (1) Intervallhalbierung (2) Regula Falsi (Sekantenverfahren) (3) Newton-Verfahren Allgemein gilt: Gibt es für f(x) im Intervall a ≤ x0 ≤ x1 ≤ b zwei Funktionswerte mit entgegengesetztem Vorzeichen, gilt also f(x0) * f(x1) < 0, so exisitiert ein f(xn) = 0. Vor.: f(x) ist definiert und stetig innerhalb a ≤ xn ≤ b. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/2 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Nullstellen berechnen – Iterationsverfahren (2) (1) Intervallhalbierung: f(x) a a'=k x b Es sind zwei Anfangswerte a,b so vorzugeben, dass gilt f(a)*f(b)<0. Das Intervall wird halbiert k = ½ * (a+b) und wie folgt verkleinert: ist f(k)*f(a)<0, so wird als neues Intervall [a,k] gewählt, d.h. b durch k ersetzt, ist f(k)*f(b)<0, so wird als neues Intervall [k,b] gewählt, d.h. a durch k ersetzt. Dieses Verfahren wird fortgesetzt, bis das Abbruchkriterium erfüllt ist: |a-b| < ε (2) Regula Falsi (Sekantenverfahren): Der Schnittpunkt der Geraden durch die beiden Funktionswerte f(a) und f(b) mit der x-Achse ergibt den neuen Stützpunkt a'. f(x) f(b) a' a b f(a) Es gilt : f(b)/(b-a') = -f(a)/(a'-a) Umformung ergibt : a'=a–f(a)*(b-a)/(f(b)-f(a)) Abbruchkriterium: |f(a')| < ε x HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/3 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Nullstellen berechnen – Iterationsverfahren (3) (3) Das Newton-Verfahren: Beginnend bei einem Anfangswert x0 wird der Schnittpunkt x1 der Tangente an der Stelle f(x0) mit der x-Achse ermittelt. Die Tangente an der Stelle f(x1) liefert den nächsten Näherungswert. Abbruchkriterium: |xν+1-xν| < ε f(x) x1 x0 x Es gilt: Umformung ergibt : f'(xν) = f(xν)/(xν - xν+1) xν+1 = xν-f(xν)/f'(xν) Im interessierenden Intervall muß gelten: Für den Startwert x0 gilt also: |f(xν)*f''(xν)/(f'(xν))2| < 1 |f(x0)*f''(x0)| < (f'(x0))2 Ist die nötige Ableitung der Funktion f'(x0) nicht bekannt, so kann sie durch den Differenzenquotienten angenähert werden: f'(x0) ≈ [f(x0+dh)-f(x0)]/dh HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/4 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Nullstellenermittlung mit unterschiedlichen Verfahren (1) /* C-Headerdatei nullst.h /* Konstantendefinitionen und Funktionsdeklarationen /* für das Demo-Programm zur Nullstellenbestimmung */ */ */ #ifndef __NULLST_H #define __NULLST_H #define EPSILON 1.e-7 #define MAXITER 100 /* Genauigkeit der Nullstelle /* maximale Anzahl Iterationen */ */ /* Newton-Verfahren */ int dNewton(double, double *, double (*)(double)); /* Intervallhalbierung */ int dIntHalb (double ,double, double *, double (*)(double)); /* Regula Falsi – Trapezverfahren */ int dRegFals (double ,double, double *, double (*)(double)); #endif /* ---------------------------------------------------------------------/* C-Modul IntHalb.c /* Nullstellenbestimmung mittels Intervallhalbierung /*----------------------------------------------------------------------/* Übergabe: /* dA: Startwert der Nullstellenbestimmung /* dB: Startwert der Nullstellenbestimmung /* pRes: Zeiger auf x-Wert der Nullstelle, für die Rückgabe /* pFunc: Zeiger auf Funktion, deren Nullstelle bestimmt wird /* Rückgabe: /* Anzahl der Iterationen /* -1 im Fehlerfall /* Defines: /* EPSILON geforderte Genauigkeit /* MAXITER maximale Anzahl durchzuführender Iterationen /*----------------------------------------------------------------------#include <math.h> #include "nullst.h" int dIntHalb (double dA,double dB, double *pRes, double (*pFunc)(double)) { double dX; int iIter=0; if((*pFunc)(dA)*(*pFunc)(dB)>0) /* ungueltige Startwerte */ iIter = -1; else { do { dX = (dA + dB)/2.0; if ((*pFunc)(dX)*(*pFunc)(dB) < 0) dA = dX; else dB = dX; iIter++; } while ((fabs(dB-dA) > EPSILON) && (iIter < MAXITER)); if (iIter >= MAXITER) /* Begrenzung der Iterationen */ iIter = -1; else *pRes = dX; } return iIter; } */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/5 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Nullstellenermittlung mit unterschiedlichen Verfahren (2) /*---------------------------------------------------------------------/* C-Modul RegFals.c /* Nullstellenbestimmung mittels Regula Falsi /*---------------------------------------------------------------------/* Übergabe: /* dA: Startwert 1 der Nullstellenbestimmung /* dB: Startwert 2 der Nullstellenbestimmung /* pRes: Zeiger auf x-Wert der Nullstelle, für die Rückgabe /* pFunc: Zeiger auf Funktion, deren Nullstelle bestimmt wird /* Rückgabe: /* Anzahl der Iterationen /* -1 im Fehlerfall /* Global: /* Defines: /* EPSILON geforderte Genauigkeit /* MAXITER maximale Anzahl durchzuführender Iterationen /*---------------------------------------------------------------------#include <math.h> #include "nullst.h" int dRegFals (double dA,double dB, double *pRes, double (*pFunc)(double)) { double dFa,dFb,dFx; int iIter=0; if((*pFunc)(dA)*(*pFunc)(dB)>0) /* ungueltige Startwerte */ iIter = -1; else { dFb = (*pFunc)(dB); do { dFa = (*pFunc)(dA); dA = dA - dFa*(dB-dA)/(dFb-dFa); dFx = (*pFunc)(dA); iIter++; } while ((fabs(dFx) > EPSILON) && (iIter < MAXITER)); if (iIter >= MAXITER) iIter = -1; else *pRes = dA; } return iIter; } /* Begrenzung der Iterationen */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/6 – TH/KR – 07 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Nullstellenermittlung mit unterschiedlichen Verfahren (3) /*----------------------------------------------------------------------/* C-Modul Newt.c /* Nullstellenbestimmung nach Newton /*----------------------------------------------------------------------/* Übergabe: /* dX0 Startwert der Nullstellenbestimmung /* pRes Zeiger auf x-Wert der Nullstelle, für die Rückgabe /* pFunc: Zeiger auf Funktion, deren Nullstelle bestimmt wird /* Rückgabe: /* Anzahl der Iterationen /* -1 im Fehlerfall /* Global: /* Defines: /* EPSILON geforderte Genauigkeit /* MAXITER maximale Anzahl durchzuführender Iterationen /*----------------------------------------------------------------------#include <math.h> #include "nullst.h" /* Vorwaertsdeklaration */ double d_Func (double (* dFunc)(double),double dX); int dNewton (double dX0, double *pRes, double (*pFunc)(double)) { double dXi, dXialt; double dFi; double dFs; int iIt = 0; dXi = dXialt = dX0; do /* Berechnung des neuen Funktionswerts */ { dFi = (*pFunc)(dXi); dFs = d_Func(pFunc,dXi); dXialt = dXi; if(fabs(dFs) > 0) dXi = dXi - dFi/dFs; iIt++; } while ((iIt<=MAXITER) && (fabs(dFs) >= 1.e-10) && (fabs(dXi-dXialt) >= EPSILON)) ; if (fabs(dFs) < 1.e-10) iIter = -1; else if (iIt > MAXITER) iIter = -1; else *pRes=dXialt; return iIt; /* verschwindende Ableitung abgefangen */ /* Begrenzung der Iterationen */ } /* numerische Ableitung einer Funktion: Differenzenquotient */ double d_Func (double (* dFunc)(double),double dX) { /* dX: Stelle der Ableitung */ /* dFunc: abzuleitende Funktion */ double dFs; double dH = 1.e-6; dFs = ((*dFunc)(dX+dH) - (*dFunc)(dX))/dH; return dFs; } */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/7 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Nullstellenermittlung mit unterschiedlichen Verfahren (4) /* C-Modul funcs.c Funktionen, deren Nullstelle gesucht wird einige Beispiele: */ #include <math.h> double dFunc1 (double dX) { double dF; dF = dX*dX - 6*dX + 7; return dF; } // Polynom double dFunc2 (double dX) // trig. Funktion { double dF; dF = cos(dX)*cos(dX) - dX*dX; return dF; } double dFunc3 (double dUb) // Vergleiche Eingangsbeispiel { double dF; double dK = 0.005; double dUq = 2.5, dR = 1e2; dF = dK*sqrt(dUb) - (dUq-dUb)/dR; return dF; } double dFunc4 (double dUd) // B ist Diode (Shockley-Modell) { double dF; double dIss = 10e-9, dUt = 50e-3; double dUg = 1.5, dRg = 1e3; dF = dIss*(exp(dUd/dUt) - 1) - (dUg-dUd)/dRg; return dF; } /* C-Headerdatei funcs.h Funktionsdeklarationen einiger Funktionen, von denen die Nullstelle ermittelt werden soll */ #ifndef _FUNCS_H #define _FUNCS_H double double double double #endif dFunc1 dFunc2 dFunc3 dFunc4 (double (double (double (double dX); dX); dUb); dUd); // // // // Polynom trig. Funktion Vergleiche Eingangsbeispiel B ist Diode (Shockley-Modell) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/8 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Nullstellenermittlung mit unterschiedlichen Verfahren (5) /* C-Modul nullst_m.c - Modul mit main()-Funktion für das Programm nullst Programmbeispiel:Nullstellenermittlung mit unterschiedlichen Verfahren Modul enthält auch einige zu untersuchende Funktionen */ #include #include #include #include <stdio.h> <math.h> "nullst.h" "funcs.h" int main(void) { double (*pFunc)(double); /* Zeiger auf die Funktion, für die die Nullstelle zu ermitteln ist */ double dXa= 4.0; /* Startwert 1 */ double dXb= 10.0; /* Startwert 2 */ double dXn; /* Position der Nullstelle */ int iTer; /* Anzahl Iterationen */ char sel; int valid; pFunc = dFunc1; /* Vorbelegung Funktionszeiger */ do { printf("Welche Funktion: (1)..(4)? "); do sel = getchar(); while(sel == '\n'); valid = 1; switch (sel) { case '1': dXa=4.; dXb=10.0;/* Startwerte */ pFunc = dFunc1; /* Auswahl der Funktion */ break; case '2': dXa=-10.; dXb=0.0; pFunc = dFunc2; break; case '3': dXa=9.0; dXb=0.25; pFunc = dFunc3; break; case '4': dXa=0.2; dXb=0.8; pFunc = dFunc4; break; default : valid = 0; break; } if (valid && sel != EOF) { if((iTer = dNewton (dXa,&dXn,pFunc)) >= 0) printf ("N-Nullstelle: x=%-11.5lf y=%14.5le nach %3d Iterationen\n", dXn, (*pFunc)(dXn),iTer); else printf("Fehler - Newton!\n"); if((iTer = dIntHalb (dXa,dXb,&dXn,pFunc)) >= 0) printf ("H-Nullstelle: x=%-11.5lf y=%14.5le nach %3d Iterationen\n", dXn, (*pFunc)(dXn), iTer); else printf("Fehler - Int.-Halb.!\n"); if((iTer = dRegFals(dXa,dXb,&dXn,pFunc)) >= 0) printf ("R-Nullstelle: x=%-11.5lf y=%14.5le nach %3d Iterationen\n\n", dXn, (*pFunc)(dXn), iTer); else printf("Fehler - Reg.Fals.!\n\n"); } } while (sel != EOF); return 0; } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/03/9 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Nullstellenermittlung mit unterschiedlichen Verfahren (6) Probelauf HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/1 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sortieren durch direktes Austauschen – Bubble Sort ● Aufgabe: Sortierung eines Arrays a mit n Elementen in aufsteigender Reihenfolge. ● Verbale Beschreibung des Algorithmus: Beginnend am Arrayende werden Arrayelemente ausgetauscht, falls der Wert des Elementes mit dem niedrigeren Index größer ist als der des Elementes mit dem höheren Index. Damit wird an die Stelle des niedrigsten Index das kleinste Arrayelement plaziert. Mit dem restlichen Teilarray wird entsprechend verfahren. ● Struktogramm: ● Beispiel: n=5 j 1 2 3 4 i 4 3 2 1 4 3 2 4 3 4 a0 3 3 3 3 1 1 1 1 1 1 1 a1 2 2 2 1 3 3 3 2 2 2 2 a2 5 5 1 2 2 2 2 3 3 3 3 a3 1 1 5 5 5 4 4 4 4 4 4 a4 4 4 4 4 4 5 5 5 5 5 5 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/2 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sortieren durch direktes Einfügen ● Aufgabe: Sortierung eines Arrays a mit n Elementen in aufsteigender Reihenfolge. ● Verbale Beschreibung des Algorithmus: Beginnend bei einem Vergleichselement werden alle Elemente mit kleinerem Index um eine Position verschoben, sofern ihr Wert größer als der des Vergleichselementes ist. Ansonsten wird das Vergleichselement an der betrachteten Position direkt eingefügt. Der Vergleich startet an der 2. Arrayposition und wird für alle folgenden Positionen wiederholt. ● Struktogramm: ● Beispiel: n=5 j 1 2 3 4 i 0 -1 1 2 1 0 -1 3 2 a0 3 2 2 2 2 1 1 1 a1 2 3 3 3 3 2 2 2 2 a2 5 5 5 5 3 3 3 3 3 a3 1 1 1 1 5 5 5 5 4 a4 4 4 4 4 4 4 4 4 5 5 x 2 2 5 1 1 1 1 4 4 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/3 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sortieren durch direktes Auswählen ● Aufgabe: Sortierung eines Arrays a mit n Elementen in aufsteigender Reihenfolge. ● Verbale Beschreibung des Algorithmus: Beginnend beim ersten Element wird der Index jenes Arrayelementes gesucht, das den minimalen Wert besitzt. Mit diesem direkt ausgewählten Element wird das erste Element vertauscht. Dieses wird fortgesetzt mit dem Teilarray beginnend mit dem zweiten Arrayelement usw. ● Struktogramm: ● Beispiel: j 0 1 2 3 n=5 i 1 2 3 4 2 3 4 3 4 4 5 a0 3 3 3 3 1 1 1 1 1 1 1 1 a1 2 2 2 2 2 2 2 2 2 2 2 2 a2 5 5 5 5 5 5 5 5 5 3 3 3 a3 1 1 1 1 3 3 3 3 3 5 5 4 a4 4 4 4 4 4 4 4 4 4 4 4 5 aimin 3 2 2 1 2 2 2 5 3 5 4 imin 0 1 1 3 1 1 1 2 3 3 4 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/4 – TH/KR – 05 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sortieren durch austauschendes Suchen ● Aufgabe: Sortierung eines Arrays a mit n Elementen in aufsteigender Reihenfolge. ● Verbale Beschreibung des Algorithmus: Beginnend bei dem Element mit niedrigstem Index werden die Folgeelemente durchsucht und mit dem betrachteten Element ausgetauscht, falls das Folgeelement kleiner ist. Dies wird bis zum Arrayende fortgesetzt. ● Struktogramm: ● Beispiel: n=5 j i a0 a1 a2 a3 a4 0 1 3 2 5 1 4 2 2 3 5 1 4 3 2 3 5 1 4 4 1 3 5 2 4 2 1 3 5 2 4 3 1 3 5 2 4 4 1 2 5 3 4 3 1 2 5 3 4 4 1 2 3 5 4 4 1 2 3 5 4 1 2 3 4 5 1 2 3 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/5 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sortieren durch Zerlegen – Quick Sort (1) ● Aufgabe: Sortierung eines Arrays a mit n Elementen in aufsteigender Reihenfolge. ● Verbale Beschreibung des Algorithmus: Das Array wird in zwei Teile zerlegt, von denen der erste nur Elemente enthält, die kleiner als ein Vergleichswert (Pivot) sind, der zweite Teil enthält nur Elemente die größer oder gleich dem Vergleichswert sind. Das Verfahren wird auf die Teilarrays erneut angewendet, bis die resultierenden Teilarrays aus nur noch einem Element bestehen. ● Struktogramm: HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/6 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Sortieren durch Zerlegen – Quick Sort (2) ● Beispiel: n = 8; l: Index des ersten Elements, r: Index des letzten Elements l 0 r 7 0 4 1 4 1 3 5 7 i 0 1 2 3 5 0 0 1 1 2 3 4 1 1 2 3 5 6 7 j 7 7 6 5 4 4 2 0 4 4 3 3 3 3 2 1 7 6 5 a0 3 3 3 3 a1 6 2 2 2 a2 5 5 -7 -7 a3 4 4 4 0 a4 1 1 1 1 3 -7 2 2 -7 3 0 0 1 1 -7 -7 2 2 3 1 0 0 1 3 3 3 2 0 1 1 0 2 -7 0 1 2 a5 0 0 0 4 a6 -7 -7 5 5 a7 2 6 6 6 pivot 4 4 4 4 1 1 3 4 4 5 5 6 6 4 5 6 5 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/7 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm : Vergleich verschiedener Sortierverfahren (1) /* /* /* /* /* /* /* ------------------------------------------------------------------------------Programm sortdemo (C-Modul sortdemo_m.c - Modul mit main()-Funktion) ------------------------------------------------------------------------------Demoprogramm zur Untersuchung und zum Vergleich verschiedener Sortierverfahren Sortiert werden jeweils Zufalls-double-Werte, die mittels der Bibliotheksfunktion rand() erzeugt werden ------------------------------------------------------------------------------- #include #include #include #include <stdio.h> <ctype.h> <stdlib.h> <time.h> #define ANZ_VAL 20000 #define PROZEILE 7 void void void void void void BubbleSort(double*,int); DirectInsert(double *, int); DirectSelect(double *, int); ExchangingSeek(double *, int); QuickSort(double *, int); ANSI_qs( double *, int); void displSelection(); void fillArray(double*, int); void show(double *daF,int iAnz); int main (void) { static double daSortDat[ANZ_VAL] = {0}; unsigned char cSelect; clock_t start, finish; double diffzeit; int iLae; iLae = sizeof(daSortDat)/sizeof(daSortDat[0]); do { displSelection(); do cSelect = getchar(); while (cSelect == '\n'); if(toupper(cSelect) != 'E') { fillArray(daSortDat, iLae); start = clock(); switch (cSelect) { case '1': BubbleSort(daSortDat,iLae); break; case '2': DirectInsert(daSortDat,iLae); break; case '3': DirectSelect(daSortDat,iLae); break; case '4': ExchangingSeek(daSortDat,iLae); break; case '5': QuickSort(daSortDat,iLae); break; case '6': ANSI_qs(daSortDat,iLae); break; } finish = clock(); show(daSortDat,5); show(daSortDat+iLae-5,5); diffzeit = (double)(finish-start)/(CLOCKS_PER_SEC/1000.); printf ("Start: %d - Stop: %d - Diff(msec): %.2f\n\n", (int)start, (int)finish, diffzeit); fflush(stdout); } } while (toupper(cSelect) != 'E'); return 0; } */ */ */ */ */ */ */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/8 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm : Vergleich verschiedener Sortierverfahren (2) /* Fortsetzung des C-Moduls sortdemo_m.c von main() verwendete Hilfsfunktionen */ void displSelection() { printf("->Auswahl\n"); printf("(1) Bubble printf("(3) DirectSelect printf("(5) Quick Sort printf("(E) EXIT \n"); } (2) DirectInsert\n"); (4) Austauschendes Suchen\n"); (6) ANSI-qs\n"); fflush(stdout); void fillArray(double* daSortDat, int anz) { int i; srand(123); for (i=0; i<anz; i++) daSortDat[i] = rand(); } void show(double *daF,int iAnz) { int i; for (i=0; i<iAnz; i++) { printf("%-10.2f",daF[i]); if((i+1)%PROZEILE == 0) putchar('\n'); } putchar('\n'); } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/9 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm : Vergleich verschiedener Sortierverfahren (3) /* C-Modul sortfunc.c Definition von Funktionen, die verschiedene Sortierverfahren implementieren Sortiert werden "double"-Arrays in aufsteigender Reihenfolge */ #include <stdlib.h> /* Bubble-Sort - Sortieren durch direktes Austauschen */ void BubbleSort(double *daF, int iAnz) { int j,i; double dHlp; for (j=1;j<iAnz;j++) for (i=iAnz-1;i>=j;i--) if (daF[i-1] > daF[i]) { dHlp = daF[i-1]; daF[i-1] = daF[i]; daF[i] = dHlp; } } /* Sortieren durch direktes Einfuegen */ void DirectInsert(double *daF, int iAnz) { int i,j; double dHlp; for (j=1;j<iAnz;j++) { dHlp = daF[j]; i=j-1; while(dHlp<daF[i] && i>=0) { daF[i+1]=daF[i]; i--; } daF[i+1] = dHlp; } } /* Sortieren durch direktes Auswählen */ void DirectSelect(double *daF, int iAnz) { int i,j,imin; double dHlp; for (j=0;j<iAnz-1;j++) { imin=j; for (i=j+1;i<iAnz;i++) if(daF[i] < daF[imin]) imin=i; dHlp = daF[imin]; daF[imin] = daF[j]; daF[j] = dHlp; } } /* Sortieren durch austauschendes Suchen */ void ExchangingSeek(double *daF, int iAnz) { int i,j; double dHlp; for (j=0;j<iAnz-1;j++) { for (i=j+1;i<iAnz;i++) if(daF[i] < daF[j]) { dHlp = daF[i]; daF[i] = daF[j]; daF[j] = dHlp; } } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/10 – TH/KR – 04 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm : Vergleich verschiedener Sortierverfahren (4) /* Fortsetzung des C-Moduls sortfunc.c */ /* Sortieren durch Zerlegen - Quick Sort (Hoare) */ void quickSort(double *daF, int iStart, int iEnd) // Aufruf siehe unten { int i,j; double dPivot, dHlp; i=iStart; // Index des 1. Elements j=iEnd; // Index des letzten Elements dPivot = daF[(iStart+iEnd)/2]; do { while ( daF[i] < dPivot ) i++; while ( daF[j] > dPivot ) j--; if(i<j) { dHlp = daF[i]; daF[i] = daF[j]; daF[j] = dHlp; i++; j--; } else if (i==j) { i++; j--; } } while (i<=j); if(iStart<j) quickSort(daF,iStart,j); if(i<iEnd) quickSort(daF,i,iEnd); } /* Aufruffunktion fuer Quick Sort */ void QuickSort(double *daF, int iAnz) { quickSort(daF, 0, iAnz-1); } // Startindex und Endindex!! // von qsort() benoetigte Vergleichsfunktion int vgl(const double * dEins, const double *dZwei) { int iRet = 0; if(*dEins > *dZwei) iRet = 1; else if (*dEins < *dZwei) iRet = -1; return iRet; } */ // von qsort() benoetigte Vergleichsfunktion mit norm-kompatibler Parameterliste int vgl(const void * dEins, const void *dZwei) { int iRet = 0; if(*(double*)dEins > *(double*)dZwei) iRet = 1; else if (*(double*)dEins < *(double*)dZwei) iRet = -1; return iRet; } */ /* Quick Sort mittels Bibliotheksfunktion qsort() */ void ANSI_qs( double *daF, int iAnz) { qsort(daF,iAnz,sizeof(daF[0]),vgl); } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–C – 09/04/11 – TH/KR – 03 --------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm : Vergleich verschiedener Sortierverfahren (5) Beispiel für einen Probelauf HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – Inhalt – TH – 07 ------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Teil 2: Einführung in die OOP mit Java 10. Grundkonzepte der Objektorientierten Programmierung 10.0. 10.1. 10.2. 10.3. 10.4. 10.5 Einführung : Grundgedanke der OOP Objekte und Klassen Kapselung Vererbung Polymorphie Klassenbeziehungen 11. Grundlegende Eigenschaften von Java 11.1 11.2. 11.3. 11.4. 11.5. 11.6. Java und C Klassen und Kapselung Programmstruktur und Startklasse Erzeugung und Start von Java-Programmen Packages in Java Java-Standardbibliothek (Java API) 12. Daten und Objekte in Java 12.1. 12.2. 12.3. 12.4. 12.5. 12.6. 12.7. 12.8. Einfache Datentypen Referenztypen und Objekterzeugung Konstante in Java Wrapper-Klassen für die einfachen Datentypen Strings Arrays Die Klasse Object Beispiel : Klasse Ratio 13. Elementare Programmfunktionalitäten in Java 13.1. 13.2. 13.3. 13.4. Zugriff zu Programmparametern Standard-Ein- und Ausgabe Exceptions Dateibearbeitung 14. Vererbung und Polymorphie in Java 14.1. 14.2. 14.3. 14.4. 14.5. Umfassende Klassendefinition Klassenableitung Polymorphie Interfaces und ihre Implementierung Programmbeispiel : Simpson-Integration HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/00/0 – TH – 01 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Programmieren (4. Sem.) Kapitelüberblick 10. Grundkonzepte der Objektorientierten Programmierung (OOP) 10.0. Einführung : Grundgedanke der OOP 10.1. Objekte und Klassen 10.2. Kapselung 10.3. Vererbung 10.4. Polymorphie 10.5. Klassenbeziehungen HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/00/1 – TH – 02 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Einführung • Grundgedanke der OOP: OOP ist eine Softwareentwicklungsmethodik, deren Grundidee aus der Simulationstechnik stammt : In dieser werden die Objekte der realen Welt sowie deren Beziehungen durch entsprechende Strukturen im Rechner abgebildet. In der OOP wird dieses Prinzip auf alle Arten von Informationen und Abläufen – auch auf solche abstrakter Natur – angewendet. Der Aufgabenbereich eines zu lösenden Problems wird in Objekte und die zwischen ihnen bestehenden Beziehungen zerlegt. Diese werden in einem das entsprechende Problem lösenden Programm nachgebildet. Ein OOP-Programm besteht somit im wesentlichen aus einer Anzahl miteinander in Beziehung stehender Objekte. Diese Denk- und Vorgehensweise ermöglicht es, auch sehr umfangreiche und komplexe Aufgaben auf einem relativ hohem Abstraktionsniveau erfolgreich zu bearbeiten. Sie nutzt die intellektuellen Fähigkeiten aus, die der Mensch zur Bewältigung der ihn umgebenden Komplexität entwickelt hat. Dies sind im wesentlichen die Fähigkeiten des Abstrahierens, des Klassifizierens und des Generalisierens. Auf ihnen beruhen die Grundkonzepte der OOP : ◈ Bildung von Objekten ◈ Abstraktion der Objekte durch Klassen ◈ Kapselung ◈ Vererbung ( Klassifizieren) ◈ Polymorphie ( Generalisieren) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/01/1 – TH – 03 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Grundkonzepte der OOP : Objekte und Klassen (1) • Konkrete Objekte: ◇ Objekte sind in einem bestimmten Sinn abgeschlossene Einheiten, die durch zwei Aspekte gekennzeichnet sind : ▻ Sie besitzen einen (inneren) Zustand ▻ und sie verfügen über Fähigkeiten, d.h. sie können bestimmte Operationen – aktiv oder passiv – ausführen Diese Fähigkeiten und damit das dadurch bestimmte Verhalten können von außen angefordert, aktiviert werden. Die Aktivierung der Fähigkeiten kann eine Zustandsänderung bewirken. ◇ Beispiel : Willies Uhr Zustand : aktuelle Zeit Fähigkeiten : Uhr stellen Zeit fortschalten (ticken) Zeit darstellen • Objekte in der OOP ◇ In der OOP stehen Objekte im Vordergrund. Sie bilden die grundlegenden Strukturierungseinheiten eines OOP-Programmms Dabei kann ein Objekt sehr konkret aber auch beliebig abstrakt sein, es kann ein statisches Gebilde (z.B. ein Auto), oder einen dynamischen Ablauf (Vorgang) beschreiben (z.B. ein Tennisspiel). Der (innere) Zustand des Objekts wird durch Datenstrukturen (Datenkomponenten), seine Fähigkeiten – die von ihm ausführbaren Operationen – werden durch Funktionen (Prozeduren) beschrieben. Die Datenkomponenten werden auch als Attribute, die Funktionen (Memberfunktionen) als Methoden bezeichnet. ◇ Ein Objekt verbindet also Daten und den zu ihrer Bearbeitung dienenden Code zu einer Einheit. Die von einem Objekt ausführbaren Methoden (= Funktionen) sind Bestandteil des Objekts und nur als solche relevant. Dies steht im Gegensatz zur konventionellen (prozeduralen, imperativen) Programmierung, bei der Daten und Code getrennt sind, wobei der Code (Prozeduren, Funktionen) eigenständig ist und im Vordergrund steht : Code wird auf Daten angewendet. ◇ In der OOP wird eine Methode für ein Objekt aufgerufen, in dem an das Objekt – i.a. durch ein anderes Objekt – eine entsprechende Nachricht (Botschaft) geschickt wird : Das Objekt interpretiert die Nachricht und reagiert mit der Ausführung einer zugeordneten Operation (Methode). Zwischen den Objekten bestehen also Kommunikationsbeziehungen. Ein OOP-Programm besteht im wesentlichen aus einer Ansammlung miteinander kommunizierender und dadurch interagierender Objekte. ◇ Der OOP-Ansatz erfordert eine andere Vorgehensweise bei der Problemlösung : Statt einer Top-Down-Zerlegung des Problems ( hierarchische Modularisierung) müssen die relevanten Objekte (Aufbau und Verhalten) des Problems und die zwischen ihnen bestehenden Beziehungen ermittelt werden ( aufgaben- und kommunikationsorientierte Zerlegung) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/01/2 – TH – 03 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Grundkonzepte der OOP : Objekte und Klassen (2) • Klassen ◇ Der Aufbau, die Eigenschaften und Fähigkeiten (Verhaltensweisen) von Objekten werden durch Klassen beschrieben. ◇ Eine Klasse legt die Datenkomponenten (Datenstruktur) und die Methoden zur Bearbeitung der Daten (Memberfunktionen) für eine Menge gleichartiger Objekte – d.h. Objekte mit gemeinsamen Merkmalen und gleichen Fähigkeiten, die diese von anderen Objekten unterscheiden – fest. Abstraktion ◇ Ein spezielles Objekt der durch eine Klasse definierten Objektmenge wird auch Instanz genannt. Es unterscheidet sich von einem anderen Objekt (einer anderen Instanz) der gleichen Klasse nur durch seinen jeweiligen Zustand, d.h. den Werten seiner Datenkomponenten. ⇒ Die Klasse entspricht dem Datentyp prozeduraler Programmiersprachen, während eine Instanz (ein spezielles Objekt dieser Klasse) einer Variablen entspricht. ◇ Eine Klasse kann formal als Erweiterung einer Struktur (struct-Typ) in C aufgefasst werden. Gegenüber einer nur aus Datenkomponenten bestehenden Struktur besitzt eine Klasse zusätzlich Funktionskomponenten. ◇ Beispiel : Klasse Uhr Uhr Datenkomp : actTime Funktionskomp : setTime(...) tick() displayClock() ◇ Jedes Objekt (Instanz) einer Klasse hat einen eigenen (inneren) Zustand. Die Datenkomponenten existieren für jedes Objekt (Unterschied zu Modulen der prozeduralen Programmierung). Sie werden erst geschaffen, wenn das Objekt generiert wird ( "Variablendefinition"). ◇ Die Methoden (Funktionen) existieren dagegen nur einmal pro Klasse. Sie werden durch die Definition der Klasse ( "Typdefinition") geschaffen. Auch wenn es gar keine Objekte dieser Klasse gibt, existieren die Methoden. Jedes Objekt einer Klasse arbeitet mit demselben Code ( Code Sharing) Beim Aufruf einer Methode für ein spezielles Objekt, wird dieser eine Referenz auf das Objekt als verborgener Parameter übergeben. Dadurch wird die Methode an ein spezielles Objekt gekoppelt Sie wird damit quasi zu einem Bestandteil des Objekts Dies ermöglicht der Methode, zu sämtlichen Komponenten (insbesondere also auch zu den Datenkomponenten) dieses Objekts zuzugreifen. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/02/1 – TH – 01 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Grundkonzepte der OOP : Kapselung • Kapselung (Encapsulation): ◇ Der Benutzer eines Objekts (allg. : Anwenderprogramm, speziell : anderes Objekt) braucht seinen genauen Aufbau nicht zu kennen. Ihm müssen lediglich die Methoden, die er für eine Interaktion mit dem Objekt benötigt, d.h. über die er die Fähigkeiten des Objekts aktivieren und dessen Zustand verändern kann, bekannt zu sein. Von der internen Darstellung der den jeweiligen Objektzustand festlegenden Daten braucht er dagegen keinerlei Kenntnis zu haben. ◇ Nur die Funktionen (= Methoden) eines Objekts, die zu seiner Verwendung tatsächlich benötigt werden, werden allgemein zugänglich, d.h. öffentlich (public), gemacht. Sie bilden das Interface (Protokoll), über das zu dem Objekt kontrolliert zugegriffen werden kann, d.h. sie bilden seine Schnittstelle zur "Außenwelt". Die Daten (u. gegebenenfalls reine Hilfs- und Verwaltungsfunktionen) sind nur Komponenten des Objekts selbst zugänglich, d.h. privat (private). Der "Außenwelt" gegenüber bleiben sie verborgen Sie sind nach außen gekapselt. Hierdurch wird sichergestellt, dass zu einem Objekt nur über eine wohldefinierte Schnittstelle zugegriffen Werden kann. Klassen-Schnittstelle Zugriffe zu Interna, die nur zur internen Realisierung und Verwaltung des Objekts dienen, sind nicht möglich Vermeidung von Fehlern durch Verhinderung eines direkten und unkontrollierten Zugriffs ⇒ Trennung von Interface (Schnittstelle) und Implementierung. Methoden Daten Methoden ◇ Die Kapselung bewirkt außerdem eine Datenabstraktion (data abstraction) : Eine Datenstruktur ist - nach außen - nicht mehr an eine bestimmte Implementierung gebunden, sondern wird allein über die auf sie anwendbaren Operationen (Methoden, Funktionen) definiert. ( abstrakter Datentyp, ADT) Eine Änderung der Implementierung - bei gleichbleibendem Interface – hat keinen Einfluß auf den Anwendungscode. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/03/1 – TH – 04 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Grundkonzepte der OOP : Vererbung • Vererbung (Inheritance) : ◇ Weitergabe von Eigenschaften (Daten und Funktionen) eines Objekts an ein anderes Objekt. Zusätzlich zu den ererbten Eigenschaften kann ein Objekt neue spezifische Eigenschaften (Daten und Funktionen) besitzen bzw. bestimmte Eigenschaften modifizieren. Schaffung einer neuen Art von Objekten durch Erweiterung einer bestehenden Art von Objekten. ◇ Die Vererbung führt zum Aufbau von Klassenhierarchien : Eine neue Klasse wird aus einer - oder mehreren - bereits definierten Klasse(n) abgeleitet. vorhandenene Klasse : Basisklasse, Elternklasse, Oberklasse neue Klasse : abgeleitete Klasse, Kindklasse, Unterklasse, Subklasse Die abgeleitete Klasse erbt die Daten und Methoden der Basisklasse(n). Dabei können geerbte Methoden abgeändert ("überschrieben") werden. Zusätzlich kann die abgeleitete Klasse neue Daten und Methoden besitzen. Abänderung und Ergänzung im Sinne einer weiteren Spezialisierung ( Klassifizierung) ◇ Beispiel : Ableitung der Klasse UhrMitDatum von der Klasse Uhr neue Datenkomponenten : neue Funktionskomponenten : geänderte Funktionskomponenten : actDate setDate(), setClock() tick(), displayClock() Uhr Datenkomp : actTime Funktionskomp : setTime(...) tick() displayClock() UhrMitDatum neue Datenkomp : actDate neue Funkt.komp : setDate(...) setClock(...) modifiz. Funkt.komp : tick() displayClock() ◇ Ein Objekt einer abgeleiteteten Klasse kann immer auch als – spezielles - Objekt der Basisklasse(n) betrachtet werden : z.B. ist jedes UhrMitDatum-Objekt auch ein Uhr-Objekt. ◇ Einfache Vererbung : Ableitung einer Klasse von nur einer Basisklasse. Mehrfachvererbung : Ableitung einer Klasse von mehreren Basisklassen (nicht von allen OOP-Sprachen unterstützt, u.a. auch nicht von Java) ◇ Durch Vererbung übertragene Methoden (Funktionen) existieren nur einmal ( Code Sharing). Dies erleichtert Änderungen und Erweiterungen an bestehenden Klassenhierarchien. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/04/1 – TH – 04 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Grundkonzepte der OOP : Polymorphie • Prinzip der Polymorphie (Polymorphism) : ◇ Verwendung des gleichen Namens für unterschiedliche - aber miteinander verwandte - Dinge. (griech. : Vielgestaltigkeit) ◇ Polymorphie in der OOP ermöglicht, dass verschiedenartige Objekte (unterschiedlicher aber durch Vererbung miteinander verwandter Klassen) unter einem gemeinsamen Oberbegriff (Basisklasse) betrachtet und bearbeitet werden können.( Generalisierung) Beispiel : Sowohl Objekte der Klasse Uhr als auch Objekte der Klasse UhrMitDatum lassen sich als Uhr-Objekte behandeln. ● Überschreiben und Überladen von Methoden ◇ In der Basisklasse definierte Funktionen können in abgeleiteten Klassen mit dem gleichen Namen und der gleichen Parameterliste (d.h. gleichem Interface) erneut definiert werden. Überschreiben von Funktionen (Function Overwriting), virtuelle Funktionen. . ◇ Dadurch wird das durch den Aufruf einer derartigen Funktion bewirkte Verhalten eines Objekts in der abgeleiteten Klasse abgeändert. Der gleiche (Funktions-)Name kann somit zur Spezifizierung einer Gruppe ähnlicher - aber doch unterschiedlicher – Operationen (Methoden) verwendet werden. Die gleiche durch den Funktions-Namen ausgedrückte Botschaft kann - an unterschiedliche Objekte gerichtet – zum Aufruf unterschiedlicher Methoden führen. Die in einem konkreten Fall ausgeführte Operation (Methode) hängt von der tatsächlichen Klasse des Objekts ab, an das die Botschaft gerichtet ist ( "Ein Interface - mehrere Methoden"). Also nicht die Botschaft, d.h. der Aufrufer, bestimmt, welche Methode (Funktion) ausgeführt wird, sondern der Empfänger der Botschaft. ◇ Beispiel : Die Botschaft "displayClock()" an ein UhrMitDatum-Objekt gerichtet, bewirkt die Ausgabe der Uhrzeit und des Datums, während sie bei einem Uhr-Objekt nur zur Ausgabe der Uhrzeit führt. ⇒ Polymorphie erlaubt durch die Bereitstellung eines gemeinsamen Interfaces die einheitliche Verarbeitung unterschiedlicher Objekte, die über gemeinsame Grundfähigkeiten verfügen. Dadurch wird die Beherrschung einer größeren Komplexität ermöglicht. ◇ Beim Aufruf virtueller (überschreibbarer) Methoden wird die Zuordnung der tatsächlich aufgerufenen Methode zur Botschaft (Methodenname) erst zur Laufzeit vorgenommen. Dies bezeichnet man als "späte Bindung" ("late binding"). Bei einer Zuordnung bereits zur Compilezeit spricht man von "früher Bindung" ("early binding"). ◇ In einer erweiterten Betrachtungsweise ermöglicht Polymorphie auch das – nicht nur in der OOP eingesetzte – Überladen von Funktionen (Methoden) und Operatoren : ▻ Überladen von Funktionen (Function Overloading) : Mehrere Funktionen können den gleichen Namen besitzen, sofern ihre Parameterliste (Signatur) unterschiedlich ist. Durch den gleichen Namen wird auch hier eine Art Standard-Interface (das sich aber nicht auf die Parameter erstreckt) bereitgestellt, über das sich mehrere unterschiedliche aber meist miteinander verwandte Methoden aufrufen lassen. Die speziell angewandte Methode hängt hier von den beim Aufruf übergebenen Daten (Parametern) ab. ▻ Überladen von Operatoren (Operator Overloading) : Operatoren können zur Anwendung auf unterschiedliche Datentypen umdefiniert werden. Definition spezieller Operatorfunktionen (in Java nicht möglich) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/05/1 – TH – 02 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Klassenbeziehungen (1) • Vererbungsbeziehung ◇ Beziehung zwischen Klassen, deren Komponenten sich teilweise überdecken ◇ Eine abgeleitete Klasse erbt die Eigenschaften und Fähigkeiten (Komponenten) der Basisklasse(n). "ist"-Beziehung ein Objekt der abgeleiteten Klasse ist auch ein Objekt der Basisklasse(n) ◇ Ordnungsprinzip bei der Spezifikation von Klassen. Generalisierung / Spezialisierung ◇ Ein Spezialfall der Vererbung ist die Implementierung (Basisklasse definiert nur ein Zugriffsinterface) • Nutzungsbeziehungen ◇ Unter einer (statischen) Nutzungsbeziehung versteht man eine in einem konkreten Anwendungsbereich geltende Beziehung zwischen Klassen, deren Instanzen voneinander Kenntnis haben und die dadurch miteinander kommunizieren können Nutzungsbeziehungen sind notwendig für die Interaktion von Objekten ◇ Assoziation - Spezielle Beziehung zwischen Klassen bzw Objekten, bei der die Objekte unabhängig voneinander existieren und lose miteinander gekoppelt sind Beispiel : einem Objekt wird die Referenz auf ein anderes Objekt in einer Methode als Parameter übergeben und nur in dieser Methode verwendet - Navigationsrichtung : legt die Kommunikationsrichtung und die Richtung, in der ein Objekt der einen Klasse ein Objekt der anderen Klasse referieren kann, fest. bidirektional (Kommunikation in beiden Richtungen möglich) oder unidirektional - Multiplizität (multiplicity) : bezeichnet die mögliche Anzahl der an der Assoziation beteiligten Instanzen einer Klasse. ◇ Aggregation - Spezielle Beziehung zwischen Klassen bzw Objekten, bei der die Objekte der einen Klasse Bestandteile (Komponenten) eines oder mehrerer Objekte der anderen Klasse sind. zwischen den Objekten besteht eine feste Kopplung - "hat"-Beziehung bzw "ist Teil von"-Beziehung - Das "umschließende" Objekt bildet einen Container für das bzw die enthaltene(n) Objekt(e) - Aggregation kann als Spezialfall der Assoziation aufgefaßt werden. - eine Aggregation ist i.a. eine unidirektionale Beziehung (Navigation vom umschließenden Objekt zu den Komponenten-Objekten) Je nach dem Grad der Kopplung unterscheidet man : ▻ einfache Aggregation Das umschließende Objekt und die Komponenten sind nicht existenzabhängig Eine Komponente kann zusätzlich noch weiteren umschließenden Objekten der gleichen oder einer anderen Klasse zugeordnet sein. Bei Löschung des umfassenden Objekts bleiben die Komponenten unabhängig davon erhalten. Beispiel : Objekt wird vom umfassenden Objekt erzeugt, Referenz darauf wird anderem Objekt übergeben ▻ echte Aggregation (Komposition, composite aggregation) Die Komponenten können nur einem umfassenden Objekt zugeordnet sein und nur innerhalb diesem existieren. Bei Löschung des Aggregats werden auch die Komponenten gelöscht. Beispiel : Objekt wird von umfassenden Objekt erzeugt und nur dort verwendet ◇ Anmerkung : In der Praxis kann es im Einzelfall sehr schwierig sein, zwischen Assoziation und Aggregation und den verschiedenen Formen der Aggregation zu unterscheiden. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/05/2 – TH – 02 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Klassenbeziehungen (2) • Klassendiagramm ◇ Die (statischen) Beziehungen (Vererbungsbeziehungen und Nutzungsbeziehungen) zwischen den in einem OOP-System zusammenwirkenden Klassen lassen sich durch Klassendiagramme beschreiben. ◇ Ein Klassendiagramm ist eines der in der UML (Unified Modelling Language) zusammengefassten graphischen Darstellungsmittel. Es ermöglicht die Erstellung eines detaillierten statisches Systemmodells ◇ Zur Erhöhung der Übersichtlichkeit kann die Gesamtheit der Klassen eines Systems auf mehrere Teildiagramme aufgeteilt sein • Anmerkung : ◇ Die dynamischen Beziehungen zwischen den Objekten eines OOP-Systems werden durch Sequenzdiagramme, einer anderen Diagrammart der UML, beschrieben. Sie werden hier nicht weiter betrachtet. • Elemente eines Klassendiagramms Klassenname KlasseA Datenkomponenten 1 * KlasseB Funktionskomponenten Klasse Assoziation (bidirektional) KlasseA KlasseA 1 n KlasseB Assoziation (unidirektional) KlasseB KlasseA Vererbung 1 1 KlasseB (einfache) Aggregation InterfaceA KlasseA KlasseB KlasseB Implementierung Komposition (echte Aggregation) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 10/05/3 – TH – 03 ------------------------------------------------------------------------------------------------------------------------------------------------------------ Klassenbeziehungen (3) • Beispiele für einfache Klassendiagramme Textdateifilterung Test reeller Zweipole Bestellverwaltung HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/00/0 – TH – 01 ----------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 11. Grundlegende Eigenschaften von Java 11.1. Java und C 11.2. Klassen und Kapselung 11.3. Programmstruktur und Startklasse 11.4. Erzeugung und Start von Java-Programmen 11.5. Packages in Java 11.6. Java-Standardbibliothek (Java API) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/01/1 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Java und C (1) • Java-Entwicklung ◇ Java wurde von der Fa. Sun entwickelt. Die Entwicklung begann im Jahr 1990. 1995 wurde Java erstmals der Öffentllichkeit vorgestellt (Sun World 95). Seit 1996 stellt die Fa. Sun (jetzt Oracle) "offizielle Java-Versionen" in Form von Java Development Kits kostenlos zum Download bereit. Die Java Development Kits enthalten alle für die Entwicklung von Java-Programmen benötigten Tools (Compiler, Interpreter (Java Virtual Machine, JVM), weitere Dienstprogramme und die Standardbibliothek) ▻ 1996 (Jan.) Freigabe des Java Development Kits 1.0 (JDK 1.0) ▻ 1997 JDK 1.1 ▻ 1998 JDK 1.2, kurze Zeit später umbenannt in Java 2 Platform ▻ 2000 JDK 1.3 (Offizieller Name : Java2 System Development Kit 1.3, J2SDK 1.3) ▻ 2002 JDK 1.4 (J2SDK 1.4) (06.02.2002) ▻ 2004 JDK 1.5 (JDK 5.0) (30.09.2004) ▻ 2006 JDK 1.6 (JDK 6.0) (11.12.2006) ▻ 2011 JDK 1.7 (JDK 7.0) (28.07.2011) ▻ 2014 JDK 1.8 (JDK 8.0) (18.03.2014) • Java2-Plattformen ► Java2 Standard Edition (J2SE) ► Java2 Enterprise Edition (J2EE), zusätzliche Komponenten zur Entwicklung unternehmensweiter verteilter Anwendungen (u.a. Enterprise Java Beans, EJB) ► Java2 MicroEdition (J2ME), eingeschränkter Sprachstandard für den Einsatz in Geräten wie Mobiltelefonen, PDAs usw ► Java Card Platform • Java – ein C-Derivat ◇ Java kann als Derivat der Sprache C betrachtet werden. Die Syntax und großenteils die Semantik von Java entsprechen weitgehend der Syntax und Semantik von C. Anweisungen und Variablenvereinbarungen werdem im wesentlichen wie in C formuliert. ◇ Im Unterschied zu C++, einer ebenfalls von C abgeleiteten hybriden (prozedural und objektorientiert verwendbaren) Programmiersprache, ist Java als rein objekt- (bzw klassen-) orientierte Sprache konzipiert worden. ◇ Außer von C ist Java auch stark von C++ und weiteren objektorientierten Programmiersprachen wie Oberon, Ada, Eiffel, Objective-C beeinflusst worden. ◇ Zahlreiche fehleranfällige oder redundante Bestandteile /Eigenschaften von C/C++ wurden nicht implementiert. Wo sinnvoll und erforderlich sind diese durch "bessere" Konzepte ersetzt. ◇ Ein weiterer elementarer Unterschied zwischen C und Java liegt in der Anwendbarkeit der erzeugten Programme : - Ein C-Compiler erzeugt Maschinen-Code, C-Programme laufen direkt, aber nur auf der jeweiligen Hardware, für die sie erzeugt wurden. - Ein Java-Compiler erzeugt einen maschinenunabhängigen Byte-Code. Dieser kann als Maschinencode einer virtuellen Maschine aufgefasst werden. Java-Programme müssen mittels eines Interpreters (Java Virtual Machine, JVM) ausgeführt werden. Ein einmal erzeugtes Java-Programm läuft damit aber auf jeder Hardware, für die eine JVM existiert. Motto von Sun : Write Once , Run Anywhere HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/01/2 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Java und C (2) • In Java nicht implementierte C-Eigenschaften Im wesentlichen fehlen in Java die folgenden C- Eigenschaften bzw Sprachelemente : ▻ Pointer und Pointerarithmetik ▻ Funktionspointer ▻ Speicherklassen (auto, register, extern) ▻ globale Variable ▻ freie Funktionen ▻ die strukturierten Typen struct und union ▻ Bitfelder ▻ Datentyp long double ▻ vorzeichenlose Datentypen (kein unsigned und damit auch kein signed) ▻ Nachbildung von logischen Werten durch int-Werte ▻ Einführung neuer Typnamen mittels typedef ▻ Sprunganweisung goto ▻ sizeof-Operator ▻ Komma-Operator ▻ ->-Operator ▻ Präprozessoranweisungen (Textersatz (Makros), bedingte Kompilierung) ▻ Header-Dateien ▻ Extern- und Vorwärtsreferenzen ▻ Dynamische Allokation von Variablen einfacher Datentypen ▻ Datentyp wchar_t (ein in C in Headerdateien mittels typedef definierter Typ) ▻ Datentypen long long, _Bool, sowie komplexe und imaginäre Datentypen (C99) ▻ inline-Funktionen (C99) • In Java geänderte Eigenschaften von C (C90) ▻ Die Trennung eines Blockes in Vereinbarungsteil (am Anfang) und Anweisungsteil existiert in Java nicht Vereinbarungen und Anweisungen können beliebig gemischt werden. ▻ Der Anfangsausdruck im Kopf einer for-Anweisung kann durch eine initialisierte Variablendefinition ersetzt werden keine vorherige Definition einer "Laufvariablen" notwendig Beispiel : for (int i=1; i<=end; i++) // ... ▻ In der Parameterliste einer parameterlosen Funktion darf das Schlüsselwort void nicht angegeben werden. Die Parameterliste muß leer bleiben. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/01/3 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Vergleich der Schlüsselworte und Identifier von Java und C • Schlüselworte (key words) reine CSchlüsselworte ++) C-99 Schlüsselworte, die in C und Java enthalten sind *) in Java reserviert, aber nicht verwendet neue Schlüsselworte von Java auto extern inline register restrict signed sizeof struct typedef union unsigned _Bool _Complex _Imaginary break case char const *) continue default do double else enum float for goto if int long return short static switch void volatile while abstract assert boolean byte catch class extends false **) final finally implements import instanceof interface native new null **) package private protected ++) ++) *) ++) ++) ++) public strictfp super synchronized this throw throws transient true **) try **) genau genommen handelt es sich hierbei in Java nicht um ein Schlüsselwort, sondern um eine Konstante (literal) • Identifier ◇ Identifier (Namen) in Java werden nach den gleichen Regeln wie in C gebildet : ▻ Sie bestehen aus Buchstaben, dem Underscore ('_') und Ziffern ▻ Sie müssen mit einem Buchstaben oder dem Underscore ('_') beginnen ▻ Groß- und Klein-Buchstaben sind unterschiedlich ▻ Sie dürfen nicht wie ein reserviertes Wort (einschliesslich false, true und null) lauten ◇ Zusätzlich dürfen Java-Identifier das Dollarzeichen ('$') enthalten. Dieses wird wie ein Buchstabe behandelt (Beginn mit '$' ist zulässig) ◇ Als Buchstaben sind alle Unicode-Buchstaben (also auch andere als im lateinischen Alphabet) zulässig ◇ Java-Identifier dürfen aus beliebig vielen Zeichen bestehen. ◇ Konventionen (nicht verpflichtend) bezüglich der Schreibweise : ▻ Variable : nur Kleinbuchstaben, z. Tl. aber auch Unterteilung in Folge-Abschnitte (Beginn mit Groß-Bu) ▻ Konstante : nur Großbuchstaben ▻ Funktionen : Beginn mit Klein-Bu, häufig Unterteilung in Abschnitte, die jeweils mit Groß-Bu beginnen ▻ Typen (Klassen) : Beginn mit Groß-Bu, Unterteilung in Abschnitte, die jeweils mit Groß-Bu beginnen HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/01/4 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Operatoren in Java • Operator-Hierarchie von Java Priorität Operator Operation 1 [] () . ++ (Postfix) -- (Postfix) Indizierung Funktionsaufruf Komponentenzugriff Increment Decrement l 2 ++ (Prefix) -- (Prefix) + ! ~ Increment Decrement Identität Negation (arithmetisch) Negation (logisch) bitweises Komplement lr 3 new (type) Objekterzeugung Typkonvertierung (Cast) lr 4 * / % Multiplikation / Division / Modulus l r 5 + - Addition bzw Konkatenation (Strings) Subtraktion l r 6 << >> >>> Linkssschieben Rechtsschieben, Nachschieben des Vorzeichens Rechtsschieben, Nachschieben von 0 l r *) *) Assoziativität r 7 < <= > >= instanceof *) Vergleich (kleiner / kleiner gleich) Vergleich (größer / größer gleich) Typprüfung l r 8 == != Vergleich (gleich / ungleich) l r 9 & bitweises UND l r 10 ^ bitweises EXOR l r 11 | bitweises ODER l r 12 && logisches UND l r 13 || logisches ODER l r 14 ?: bedingte Auswertung lr 15 = *= /= %= += -= <<= >>= >>>= *) &= ^= |= Zuweisung Zuweisung mit Verknüpfungsoperation lr *) neue Operatoren in Java HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/02/1 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassen und Kapselung (1) • Klassen – Grundbausteine eines Java-Programms ◇ Im vorigen Kapitel wurde ein OOP-Programm im wesentlichen als eine Ansammlung miteinander kommunizierender Objekte definiert. Objekte werden durch Klassen beschrieben. Eine Klasse definiert die jeweiligen Eigenschaften (den Aufbau) und das Verhalten (die Funktionalität) der von ihr instanzierbaren Objekte. ◇ Klassen sind die elementaren Bestandteile jedes Java-Programms. Sämtlicher Code eines Java-Programms befindet sich innerhalb von Klassen. Ein Programm kann aus beliebig vielen Klassen bestehen. • Aufbau von Klassen ◇ Eine Klasse ist aus Klassenkomponenten aufgebaut. Diese werden in einer Klassendefinition festgelegt. Klassenkomponenten können sein : - Datenkomponenten (Membervariable) Variablendefinitionen Die Gesamtheit der Datenkomponenten beschreibt den Aufbau und – mit ihren jeweiligen konkreten Werten in einem Objekt – den Zustand der Objekte ( Objektvariable). - Funktionskomponenten (Memberfunktionen) Funktionsdefinitionen Diese legen das Verhalten, die Fähigkeiten der Objekte fest ( Objektfunktionen). ◇ Klassen können formal als eine Erweiterung der Structure-Typen von C aufgefasst werden. ◇ Eine Klasse kann auch Komponenten (sowohl Daten- als auch Funktionskomponenten) enthalten, die nicht objekt-spezifisch sind, sondern den Zustand und das Verhalten der Klasse selbst beschreiben. Sie sind durch den Modifizierer static gekennzeichnet. statische Datenkomponenten (Klassenvariable) bzw statische Funktionskomponenten (Klassenfunktionen) ◇ Zusätzlich kann eine Klassendefinition - Konstruktoren enthalten Konstruktoren sind spezielle klassenspezifische Funktionen, die bei der Objekterzeugung automatisch aufgerufen werden. Sie tragen immer den Klassennamen und besitzen keinen Rückgabetyp (auch nicht void). Sie werden nicht durch static gekennzeichnet. Sie werden in Java nicht zu den Memberfunktionen und nicht zu den Klassenkomponenten gerechnet. Ihre primäre Aufgabe besteht in der Initialisierung der Datenkomponenten des erzeugten Objekts. Konstruktoren können Parameter besitzen ( Initialisierungswerte). Ein parameterloser Konstruktor wird auch als no-arg-Konstruktor bezeichnet.In einer Klasse können mehrere Konstruktoren – mit jeweils unterschiedlicher Parameterliste – definiert sein. Falls eine Klassendefinition keinen Konstruktor enthält, wird vom Compiler implizit ein Default-Konstruktor (parameterlos) bereitgestellt. ◇ Eine Klassendefinition kann noch weitere Bestandteile enthalten (s. später) • Kapselung ◇ Das Konzept der Kapselung wird in Java durch die Festlegung von Zugriffsberechtigungen zur Klasse selbst und zu den Klassenkomponenten realisiert. ◇ Dies erfolgt durch die Angabe von Zugriffs-Modifizierern. Hierfür existieren folgende Möglichkeiten : - kein Zugriffs-Modifizierer : Zugriffsberechtigung "package". Ein Zugriff besteht nur von innerhalb des Packages (eine Art Namensraum), in dem die Klasse definiert ist. - Modifizierer public : Ein Zugriff ist von überall her möglich (öffentlicher Zugriff). - Modifizierer private (nur für Klassenkomponenten) : Zugriff nur von innerhalb der Klasse, in dem die jeweilige Komponente definiert ist. - Modifizierer protected (nur für Klassenkomponenten) : Zugriff beschränkt auf die Klasse selbst, auf abgeleitete Klassen und auf das Package HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/02/2 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassen und Kapselung (2) • Vereinfachte Syntax der Klassendefinition (vollständiges Syntax-Diagramm s. später) class Klassen-Name { Komponentendefinition public } Konstruktordefinition ; ◇ Komponentendefinition : Variablen- oder Funktionsdefinition Beide können beliebig gemischt werden ▻ Variablendefinition (Definition von Datenkomponenten) Typangabe Variablenname Feld-Modifizierer Initialisierer = ; , Datenkomponenten können bei der Definition initialisiert werden (mit einem typkonformen Ausdruck) Feld-Initialisierer ▻ Funktionsdefinition (Definition von Memberfunktionen), vereinfacht Rückgabetyp MethodenModifizierer Methoden-Name ( Methoden-Kopf { Formal-Parameterliste ) throws-Klausel Anweisung } MethodenRumpf ▻ Die wichtigsten Feld- bzw Methoden-Modifizierer sind (es gibt noch weitere) : - die Zugriffs-Modifizierer public, private und protected - static : Die Komponente ist eine statische Klassenkomponente (klassenspezifische Komponente) - final : Bei Datenkomponenten : Komponente ist eine Konstante Bei Memberfunktionen : Methode kann in abgeleiteten Klassen nicht überschrieben werden ▻ Wenn eine Methode keine Parameter besitzt, ist die Formal-Parameterliste leer (kein void !!!) ▻ Die throws-Klausel deklariert Exceptions, die von der Funktion geworfen werden können (s. später) ◇ Konstruktordefinition : analog zur Funktionsdefinition, mit folgenden Unterschieden : ▻ als (Konstruktor-) Modifizierer sind nur die Zugriffs-Modifizierer zulässig ▻ kein Rückgabetyp ▻ Methodenname == Klassenname HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/02/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassen und Kapselung (3) • Beispiel einer einfachen Klassendefinition : public class Uhr { private long acttime; public Uhr() { acttime = 0; } // Datenkomponente // Konstruktor public void setTime(long time) { acttime = time; } public long tick() { ++actTime; return acttime; } // Funktionskomponenten public void displayClock() { /* ... */ } } Anmerkung : Der Klassenname allein dient als Typ-Bezeichnung für Variablendefinitionen • Zugriff zu Klassenkomponenten ◇ Ein Zugriff von ausserhalb der Klasse ist nur entsprechend der durch den jeweiligen Zugriffs-Modifizierer festgelegten Berechtigung möglich. Zu private-Komponenten ist ein Zugriff von außen grundsätzlich nicht zulässig ◇ Klassenkomponenten (sowohl Daten- als auch Funktionskomponenten) werden mittels des – aus C für den Struktur-Komponenten-Zugriff bekannten – Komponentenzugriff-Operators . ausgewählt : ▻ objektspezifische Komponenten über eine Objekt-Referenz Beispiel : myClck sei eine Referenz auf ein Uhr-Objekt myClck.displayClock(); // Methodenaufruf Interpretation : An das durch myClock referierte Objekt wird die Botschaft displayClock geschickt. Das Objekt reagiert daraufhin mit der Ausführung der zugeordneten Methode. ▻ klassenspezifische Komponenten (static !!!) über den Klassennamen. Beispiel : System ist eine Bibliotheksklasse, die u.a. die statische Methode getProperties() besitzt. System.getProperties() // Methodenaufruf ◇ Von innerhalb eines Objekts einer Klasse (Memberfunktionen, Konstruktoren) ist der Zugriff zu allen Komponenten (auch private) allein mit deren Namen möglich. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/03/1 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Struktur von Java-Programmen (1) • Modulkonzept ◇ Ein Java-Programm besteht nur aus Klassen. ◇ Jede Klasse muss in einer eigenen Quelldatei vollständig definiert und implementiert werden. Ausnahme von der eigenen Quelldatei : eingebettete Klassen (nested classes).(hier nicht betrachtet) Der Quelldatei-Hauptname muss wie der (Haupt-)Klassenname lauten. Die Quelldatei-Extension ist .java. Beispiel : Klasse Welcome Quelldateiname : Welcome.java ◇ Eine Quelldatei ist die Übersetzungseinheit (Übersetzungs-Modul). Für jede enthaltene Klasse wird vom Compiler eine eigene Byte-Code-Datei erzeugt. Die Byte-Code-Datei für die (Haupt-)Klasse bekommt den Hauptnamen der Quellcode-Datei und die Extension .class Beispiel : Aus Welcome.java erzeugt der Compiler Welcome.class Nur bei eingebetteten Klassen : Die Hauptnamen der übrigen Byte-Code-Dateien sind aus den Namen der Haupt-Klasse und der eingebetteten Klassen zusammengesetzt. • Programmstruktur ◇ Ein Linken der getrennt übersetzten Module (Klassen-Dateien) zu einer – ausführbaren – Programm-Datei findet in Java nicht statt. ◇ Ein Java-Programm ist damit konzeptionell nicht in einer einzigen Datei zusammengefasst. Vielmehr besteht es – entsprechend den im Programm eingesetzten Klassen – aus einer oder mehreren Dateien. Anmerkung : Es gibt Möglichkeiten der Zusammenfassung zu einer Archiv-Datei (hier nicht betrachtet) ◇ Eine dieser Dateien muss die Start-Klasse des Programms enthalten. ◇ Ausgehend von der Start-Klasse werden die übrigen Klassen des Programms referiert. • Programmarten ◇ Es werden zwei Java-Programmarten unterschieden : ▻ Applikations-Programme ▻ Applets ◇ Applikationsprogramme "Normale" Java-Programme. Sie werden durch direkten Start der Virtuellen Maschine (JVM) ausgeführt. Hierfür ist dieser beim Start die Start-Klasse des Programms als Parameter zu übergeben ◇ Applets Diese Programme werden innerhalb eines Java-fähigen Web-Browsers (oder im Kontext eines anderen "applet viewers") ausgeführt. Ein derartiger Browser (bzw "applet viewer") enthält eine – gegebenenfalls über ein Plug-In eingebundene – JVM. Der Applet-Code befindet sich üblicherweise auf einem Server und wird durch ein <Applet>-Tag im HTML-Code einer Web-Seite referiert. Stößt der Browser beim Interpretieren des HTML-Codes auf ein derartiges <Applet>-Tag, startet er seine JVM. Diese lädt von der angegebenen URL den Byte-Code der Start-Klasse des Applets und führt ihn aus. Applets sind damit – i.a. kleine – Programme, die in einfacher Art und Weise über das Internet verteilt werden können. Da Applets prinzipiell unzuverlässigen Code enthalten können, unterliegt ihre Ausführung strengen Sicherheitsrestriktionen. Der Start und die Ausführung eines Applets unterscheidet sich aber erheblich von Start und Ausführung eines Applikations-Programms. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/03/2 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Struktur von Java-Programmen (2) • Start-Klasse eines Java-Applikations-Programms ◇ Die der JVM beim Start zu übergebene Klasse muss – neben möglicherweise beliebig vielen anderen Methoden – eine statische main()-Methode besitzen. Diese main()-Methode muss öffentlich zugänglich (public) und vom Typ void sein. Als formalen Parameter muß sie ein String-Array besitzen. Sie stellt den Startpunkt der Programmausführung dar. ◇ Beispiel für ein minimales Java-Programm : // Welcome.java public class Welcome { public static void main(String[] args) { System.out.println("Welcome to the world of Java"); } } ◇ I.a. ist ein Java-Programm nicht klassenorientiert sondern objektorientiert. D.h. im Programm werden Objekte erzeugt, die miteinander kommunizieren. Die Erzeugung des "ersten" Objekts erfolgt dann in der main()-Methode der Start-Klasse : ▻ Entweder durch Instantiierung einer anderen Klasse (Start-Klasse dient nur zum Programmstart) ▻ oder durch Instantiierung der eigenen Klasse (Start-Klasse enthält auch spezifische Programmfunktionalitäten) ◇ Ausgehend vom ersten Objekt werden dann die weiteren Objekte erzeugt. ◇ Beispiel für ein minimales Java-Programm mit Objekterzeugung : // Willkommen.java public class Willkommen { public Willkommen() { // nur zur Demonstration } public void begruessung() { System.out.println("Willkommen bei Java"); } public static void main(String[] args) { Willkommen gruss = new Willkommen(); gruss.begruessung(); } // Konstruktor // Memberfunktion // Erzeugung eines Objekts } ◇ Anmerkung : Häufig wird auch bei Klassen, die in der späteren Verwendung nicht als Start-Klassen dienen sollen, eine statische main()-Methode vorgesehen. Diese Methode enthält dann i.a. Code, der das – weitgehend isolierte – Testen der Klasse ermöglicht. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/04/1 – TH – 01 -------------------------------------------------------------------------------------------------------------------------------------------------------- Erzeugung und Start von Java-Programmen • Vom Quellprogramm zur Programmausführung Quell-Datei (z.B.Welcome.java) Java-Compiler weitere Byte-Code-Dateien javac Byte-Code-Datei (z.B.Welcome.class) Java-Interpreter (Java Virtual Machine) java • Aufruf des Java-Compilers (Kommandozeilen-Tool javac des JDK) ◇ Der Compiler sucht die zu übersetzende Quelldatei ausgehend vom aktuellen Arbeitsdirectory. Befindet sich die Datei in einem anderen Directory, ist ein entsprechender Zugriffspfad anzugeben. ◇ Der Name der zu übersetzenden Quelldatei ist einschließlich der Extension .java anzugeben. ◇ Beispiel :Quelldatei Welcome.java im Directory E:\Java\Vorl E:\Java\Vorl>javac Welcome.java ◇ Der Compiler erzeugt – bei Fehlerfreiheit – eine Byte-Code-Datei (hier : Welcome.class) im Verzeichnis der Quelldatei. ◇ Fehler werden zusammen mit der Zeilennummer und dem Quelldateinamen gemeldet. ◇ Werden in einer Quelldatei weitere Klassen referiert und findet der Compiler keine entsprechenden .class-Dateien, versucht er diese durch zusätzliches Übersetzen der entsprechenden .java-Dateien zu erzeugen. (Ausnahme : Klassen der Standardbibliothek) • Aufruf des Java-Interpreters (Kommandozeilen-Tool java des JDK) ◇ Dem Java-Interpreter ist der Klassenname (nicht der Dateiname, d.h. keine Extension !) der Start-Klasse als Programm-(Kommandozeilen-)Parameter zu übergeben. ◇ Der Java-Interpreter sucht nach der Byte-Code-Datei der Start-Klasse im aktuellen Directory und – falls dort nicht vorhanden – in den Directories, die in der Environment-Variablen CLASSPATH enthalten sind. ◇ Beispiel : Byte-Code-Datei Welcome.class (mit Klasse Welcome) im Verzeichnis E:\Java\Vorl CLASSPATH-Variable ist nicht gesetzt. E:\Java\Vorl>java Welcome ◇ Werden von der Start-Klasse weitere Klassen benötigt, sucht der Interpreter die entsprechenden Byte-CodeDateien im aktuellen Directory bzw in den Directories der CLASSPATH-Variablen (Ausnahme : Klassen der Standardbibliothek) ◇ Wird eine benötigte Byte-Code-Datei nicht gefunden, wird die Exception NoClassDefFoundError erzeugt. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/05/1 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Packages in Java (1) • Packages als Namensräume ◇ Klassen lassen sich in Java in Pakete (Packages) gruppieren. Packages können als Namensräume (name spaces) aufgefasst werden, in denen irgendwie zusammengehörige Klassen zusammengefasst sind (z.B. zu einem bestimmten Projekt gehörend, von einem bestimmten Entwickler erzeugt, zu einer bestimmten Bibliothek gehörend usw). ◇ Packages haben einen Namen und können hierarchisch strukturiert (Package Unter-Package) sein. Package-Namen können aus mehreren Bestandteilen bestehen, die durch einen Punkt ('.') vonein ander getrennt sind. ◇ Damit lässt sich eine Klasse über einen vollständigen Klassennamen ansprechen. Dieser besteht aus dem eigentlichen Klassennamen, dem der durch einen Punkt (.) abgetrennte Package-Name vorangestellt ist. voll-qualifizierter Klassenname Package-Name Beispiel : . Klassenname Klasse Date im Package java.util voll-qualifizierter Klassenname : java.util.Date ◇ Mittels Packages lassen sich Namenskonflikte, insbesondere zwischen Klassen unterschiedlicher Hersteller (Bibliotheken), weitgehend vermeiden. ◇ Empfohlene Konvention zur Vergabe von Package-Namen : Beginn des Package-Namens mit dem invertierten Internet-Domain-Namen des Herstellers. Beispiel : Fa. SESA, Internet-Domain-Name : sesa.com Package-Name : com.sesa.wmf Es ist auch üblich, aus Gründen der Übersichtlichkeit den Top-Level-Domain-Namen wegzulassen. ◇ Klassen, die explizit keinem Package zugeordnet sind, befinden sich im namenlosen Package (unnamed package). Für die Verwendung in Bibliotheken sind derartige Klassen nicht geeignet. • Packages als Directory-Struktur ◇ Ein strukturierter Package-Name wird in eine entsprechende Directory-Struktur abgebildet. ◇ Beispiel : Klassen-Dateien des Packages com.sesa.wmf sind im Verzeichnis com\sesa\wmf ◇ Das Start-Verzeichnis einer derartigen Directory-Struktur muss sich im aktuellen Verzeichnis oder in einem über die CLASSPATH-Variable festgelegten Verzeichnis befinden. ◇ Package-Namen ermöglichen damit eine strukturierte und dadurch übersichtliche Ablage von KlassenDateien. ◇ Klassen-Dateien des namenlosen Package müssen direkt im aktuellen Directory bzw in einem durch die CLASSPATH-Variable festgelegten Verzeichnis liegen. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/05/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Packages in Java (2) • Package-Zuordnung einer Klasse ◇ Hierzu dient die package-Vereinbarung package Beispiel : Package-Name ; package hm.ee.vorl; ◇ Die package-Vereinbarung muss am Beginn einer Quelldatei (erste Vereinbarung) stehen. Sie legt fest, dass die nachfolgend definierte Klasse zu dem angegebenen Package gehört. • Verwendung von Klassen des eigenen Packages ◇ Eine Klasse kann eine andere Klasse ihres eigenen Packages allein mit dem einfachen Klassennamen verwenden. ◇ Jede Klasse eines Packages hat Zugriff zu den anderen Klassen desselben Packages, auch wenn sie nicht explizit public deklariert sind. (Zugriffsberechtigung "package") • Verwendung von Klassen aus anderen Packages ◇ Eine Klasse kann zu Klassen aus einem anderen Package nur zugreifen, wenn diese explizit public deklariert sind. ◇ Für die Verwendung bestehen prinzipiell zwei unterschiedliche Möglichkeiten : ▻ Verwendung des voll-qualifizierten Namens. ▻ Importieren der Klassen und Verwendung der einfachen Klassennamen ◇ Verwendung des voll-qualifizierten Namens : Beispiel : Verwendung der Klasse Date aus dem Package java.util // Datum1.java public class Datum1 { public static void main(String[] args) { java.util.Date now = new java.util.Date(); System.out.println(now); } } Nachteil : Klassennamen können sehr lang und damit unhandlich werden. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/05/3 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Packages in Java (3) • Verwendung von Klassen aus anderen Packages, Forts. ◇ Importieren von Klassen aus anderen Packages : mittels der import-Deklaration Diese existiert in zwei Formen : import import voll-qualifizierter Klassenname Package-Name . Importieren einer einzelnen Klasse ; * ; Importieren aller Klassen eines Packages (type import on demand), aber nicht von dessen Unter-Packages ◇ Beispiel zum Importieren einer Klasse : Verwendung der Klasse Date aus dem Package java.util // Datum2.java import java.util.Date; // oder : import java.util.*; public class Datum2 { public static void main(String[] args) { Date now = new Date(); System.out.println(now); } } ◇ Anmerkung : Importieren bedeutet kein Einbinden von Code anderer Klassen. • Die Zugriffsberechtigung "package" ◇ In Java haben alle Klassenkomponenten einer Klasse, die nicht explizit als public, protected oder private gekennzeichnet sind, die Zugriffsberechtigung "package". Default-Zugriffsberechtigung ◇ Diese Zugriffsberechtigung erlaubt den Zugriff durch alle Klassen desselben Packages. ◇ Die Zugriffsberechtigung zu einem Package erstreckt sich nicht auf dessen eventuelle Unter-Packages. Klassen eines Packages haben keinen Zugriff zu "package"-Komponenten von Klassen eines zugehörigen Unter-Packages (u. umgekehrt). ◇ Die Zugriffsberechtigung protected umfasst in Java auch die Zugriffsberechtigung "package" Zu Klassenkomponenten mit der Zugriffsberechtigung protected können neben den abgeleiteten Klassen auch alle Klassen desselben Packages zugreifen. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/05/4 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Packages in Java (4) • Beispiel zur Zugriffsberechtigung "package" ◇ Definition einer Klasse in einem Unter-Package : package vorl.pack1; public class PackAccDemo { private int val = 123; void showValPack() { System.out.println ("package-Zugriff } // Zugriffsberechtigung package : " + val); protected void showValProt() { System.out.println ("protected-Zugriff : " + val); } public void showValPub() { System.out.println ("public-Zugriff } : " + val); } ◇ Definition einer verwendenden Klasse in demselben Package Zugriff zu allen Komponenten der verwendeten Klasse mit der Zugriffsberechtigung "package", protected oder public package vorl.pack1; class PackAccDemoUse1 { public static void main(String[] args) { PackAccDemo demo = new PackAccDemo(); demo.showValPack(); demo.showValProt(); demo.showValPub(); } } ◇ Definition einer verwendenden Klasse in einem übergeordneten Package Zugriff nur zu Komponenten der verwendeten Klasse mit der Zugriffsberechtigung public package vorl; import vorl.pack1.*; class PackAccDemoUse3 { public static void main(String[] args) { PackAccDemo demo = new PackAccDemo(); //demo.showValPack(); // Zugriff zu package nicht zulässig //demo.showValProt(); // Zugriff zu protected nicht zulässig demo.showValPub(); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/06/1 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Java-Standardbibliothek (1) • Allgemeines ◇ Die Java-Plattform stellt auch eine Laufzeit-Bibliothek (API) zur Verfügung. ◇ Diese sehr umfangreiche Java Standard-Bibliothek enthält zahlreiche nützliche Klassen für diverse Anwendungsbereiche. Eine Reihe dieser Klassen implementieren wesentliche Spracheigenschaften und sind damit sehr eng mit der Sprache selbst verknüpft. ◇ Die Klassen der Standardbibibliothek sind auf verschiedene APIs aufgeteilt und in Paketen (Packages) zusammengefasst. Dabei ist die Zuordnung der einzelnen Klassen zu den verschiedenen Paketen nicht immer ganz stimmig, insbesondere stimmen die API-Grenzen nicht immer mit den Paketgrenzen überein. ◇ Zu vielen der Haupt-Paketen existieren Unter-Pakete. ◇ Die Pakete der im JDK enthaltenen Java Standard-Bibliothek lassen sich in drei Gruppen einteilen : ▻ Standard-Pakete. Sie gehören zu jeder Java-Plattform ▻ Standard-Erweiterungs-Pakete. Sie stellen erweiterte und ergänzende Funktionalitäten zur Verfügung und müssen nicht unbedingt auf jeder Java-Plattform zur Verfügung stehen ▻ Ergänzende Pakete von Dritt-Herstellern. Sie ergänzen und erweitern ebenfalls die Funktionalität der Bibliothek. ◇ Die Java Standard-Bibliothek ist ständig erweitert worden. (JDK 1.0 8 Pakete, JDK 1.4 über 130 Pakete, JDK 5.0 über 170 Pakete, JDK 6.0 203 Pakete, JDK 7.0 209 Pakete, JDK 8.0 217 Pakete) ◇ Sun stellt in der das JDK begleitenden Dokumentation eine ausführliche Beschreibung der Klassen zur Verfügung (Java Platform API Specification) • Überblick über die Standard-Pakete ◇ Die Namen der Standard-Pakete beginnen mit java. ◇ Die folgende Tabelle gibt nur einen Überblick über die Haupt-Pakete dieser Gruppe java.applet Applets java.awt Abstract Windowing Toolkit : Erzeugung von GUIs, 11 Unter-Pakete java.beans Java Beans (Komponenten-Architektur von Java), ein Unter-Paket java.io Ein-/Ausgabe mittels Streams, Dateien und Serialisierung java.lang Elementare Sprachunterstützung, 6 Unter-Pakete java.math Unterstützung von Ganzzahl- und Gleitpunkt-Arithmetik java.net Unterstützung von Netzwerkanwendungen java.nio New I/O Package, verbesserte I/O-Unterstützung, 7 Unter-Pakete, ab JDK 1.4 java.rmi Remote Method Invocation, Entfernte Objekt-Kommunikation, 4 Unter-Pakete java.security Sicherheits-Framework, 4 Unter-Pakete java.sql Datenbankzugriff java.text Erweiterte Textdarstellung und –bearbeitung, Internationalisierung, ein Unter-Paket java.time Div. zeit- und datum-bezogene Klassen, 4 Unterpakete, ab JDK 8.0 java.util Utility-Klassen, Collection Framework, spezielle Datenstrukturen, 11 Unter-Pakete HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 11/06/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Java-Standardbibliothek (2) • Überblick über die Standard-Erweiterungs-Pakete ◇ Die Namen der Standard-Erweiterungs-Pakete beginnt mit javax. ◇ Die folgende Tabelle gibt nur einen Überblick über die Haupt-Pakete dieser Gruppe (hier : Standard Edition) javax.accessibility Unterstützung spezieller I/O-Geräte (z.B. für Braille-Zeichen) javax.activation Unterstützung der Objekt-Aktivierung javax.activity spezielle Exception-Klassen im Zusammenhang mit der CORBA-Serialisierung javax.annotation Unterstützung von Annotations, ein Unter-Paket javax.crypto Unterstützung kryptographischer Operationen, zwei Unter-Pakete javax.imageio I/O von Bilddateien, 6 Unter-Pakete javax.jws Unterstützung von Web Services, ein Unter-Paket javax.lang.model Unterstützung der Modellierung der Sprache Java, drei Unter-Pakete javax.management Java Management Erweiterungen, 8 Unter-Pakete javax.naming Zugriff zu Namensdiensten, vier Unter-Pakete javax.net Ergänzung zur Netzwerkunterstützung, ein Unter-Paket javax.print Print Service API, Zugriff zu Print Services, drei Unter-Pakete javax.rmi Ergänzung zum RMI-API, zwei Unter-Pakete javax.script Scripting API (Definition von Java Scripting Engines) javax.security.... Ergänzung zum Sicherheits-Framework, besteht nur aus 8 Unter-Paketen javax.sound.... Sound API, besteht nur aus 4 Unter-Paketen javax.sql Ergänzung zum Datenbankzugriffs-API (serverseitig), drei Unter-Pakete javax.swing Swing Toolkit, Erweiterungen zur GUI-Erzeugung, 17 Unter-Pakete javax.tools Interfaces und Klassen für Tools zum Starten aus Programmen heraus javax.transaction Unterstützung von Transaktionen, ein Unter-Paket javax.xml Zugriff zu XML-Dateien, 34 Unter-Pakete • Ergänzende Pakete von Drittherstellern ◇ Hierzu gehören zahlreiche Pakete unterhalb der org.omg.-Hierarchie. Sie stellen im wesentlichen CORBA-Unterstützung zur Verfügung ◇ Weitere Pakete befinden sich unter den Hierachien org.w3c. und org.xml. Sie bieten Unterstützung für den Zugriff zu XML-Dateien. ◇ Das Paket org.ietf.jgss stellt ein Framework zur Nutzung von Sicherheitsdiensten (Authentifizierung, Daten-Sicherheit, Daten-Integrität usw) unterschiedlichster Mechanismen (wie z.B. Kerberos) zur Verfügung • Verwendung der Java Standard-Bibliothek ◇ Zur Verwendung von Komponenten der Standard-Bibliothek in eigenen Programmen werden entsprechende import-Vereinbarungen benötigt, wenn nicht voll-qualifizierte Namen verwendet werden sollen. Ausnahme : Das Paket java.lang ist so eng mit der Sprache verknüpft, dass es automatisch durch den Compiler importiert wird. Eine explizite import-Anweisung ist daher für Komponenten aus diesem Paket nicht erforderlich. ◇ Der Java-Compiler und der Java-Interpreter finden den Zugriffspfad zu den Bibliotheks-Dateien automatisch. Er muss daher nicht in die CLASSPATH-Variable aufgenommen werden (ab JDK 1.2). HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/00/0 – TH – 03 ----------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 12. Daten und Objekte in Java 12.1. Einfache Datentypen 12.2. Referenztypen und Objekterzeugung 12.3. Konstante in Java 12.4. Wrapperklassen für die einfachen Datentypen 12.5. Strings 12.6. Arrays 12.7. Die Klasse Object 12.8. Beispiel : Klasse Ratio HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/01/1 – TH – 03 ------------------------------------------------------------------------------------------------------------------------------------------------------- Einfache Datentypen in Java (1) • Allgemeines zu Datentypen ◇ Java ist eine streng typisierte Sprache. Das bedeutet, dass jede Variable und jeder Ausdruck einen Typ hat, der zur Compilezeit bekannt sein muss. ◇ In Java muß jede Variable immer einen definierten Wert haben: Membervariable werden automatisch mit einem Default-Wert initialisiert, sofern ihnen nicht explizit ein Wert zugewiesen wird. Bei lokalen Variablen verhindert der Compiler, dass sie ohne explizite Initialisierung bzw Wertzuweisung verwendet werden (Definite Assignment). ◇ In Java gibt es zwei Kategorien von Datentypen : ▻ einfache (primitive) Datentypen (primitive types) ▻ Referenz-Typen (reference types) • Überblick über die einfachen Datentypen ◇ Variable eines einfachen Datentyps enthalten einen Wert ihres jeweiligen Typs. ◇ Java kennt acht einfache Datentypen ◇ Für jeden Datentyp ist der Wertebereich und damit die Größe des belegten Speicherplatzes eindeutig – unabhängig von der jeweiligen Plattform – festgelegt. ◇ Zu jedem primitiven Datentyp ist in der Standard-Bibliothek eine Wrapper-Klasse definiert (s. später). ◇ Überblick : Typname Größe Art des Typs Wertebereich Default-Wert WrapperKlasse byte 1 Byte vorzeichenbeh. ganze Zahl -128 ... +127 0 Byte short 2 Bytes vorzeichenbeh. ganze Zahl -215 0 Short 31 ... +2 -1 0 Integer ... +263-1 0 Long '\u0000' Character 0.0 Float 0.0 Double false Boolean ... +215-1 31 int 4 Bytes vorzeichenbeh. ganze Zahl -2 long 8 Bytes vorzeichenbeh. ganze Zahl -263 char 2 Bytes Unicode-Zeichen alle Unicode-Zeichen float 4 Bytes Gleitpunktzahl (reelle Zahl) +/-3.4028...*1038 double 8 Bytes Gleitpunktzahl (reelle Zahl) +/-1.7976...*10 boolean 1 Bit *) logischer Wert false, true 308 *) Die belegte Speichergröße ergibt sich durch die kleinste adressierbare Speichereinheit (meist 1 Byte) ◇ Der Datentyp float entspricht dem 32-Bit-IEEE-Format (single precision) Der Datentyp double entspricht dem 64-Bit-IEEE-Format (double precision) ◇ Der Datentyp char zählt (mit byte, short, int und long) zu den ganzzahligen Datentypen (integral types). ◇ Die ganzzahligen Datentypen bilden zusammen mit den Gleitpunkttypen (floating point types ) (float und double) die numerischen Typen (numeric types). ◇ Der logische Datentyp boolean muß überall dort verwendet werden, wo ein logischer Operand erforderlich ist (logische Operatoren, Steuerausdrücke in den Steueranweisungen, erster Ausdruck im bedingten Auswerte-Operator). Ein Ersatz durch ganzzahlige Typen (Werte 0 und !=0) ist nicht zulässig. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/01/2 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Einfache Datentypen in Java (2) • Darstellung der Werte der einfachen Datentypen im Quelltext (Konstante, literals) ◇ Die Wertedarstellung entspricht im wesentlichen der von C ◇ Datentypen byte, short, int und long (ganzzahlige Datentypen ohne char) : ▻ Darstellung als Dezimalzahl (Ziffern 0 bis 9, Beginn nicht mit 0) ▻ Darstellung als Oktalzahl : Beginn mit 0 (nur Ziffern 0 bis 7) ▻ Darstellung als Sedezimalzahl : Beginn mit 0x oder 0X (Ziffern 0 bis 9, a bis f, A bis F) ▻ ab JDK 1.7 : Darstellung als Binärzahl : Beginn mit 0b oder 0B (Ziffern 0 und 1) ▻ Ziffernfolgen sind grundsätzlich (ohne Suffix) vom Typ int. Allerdings muss der dargestellte Wert innerhalb des für int zulässigen Bereichs liegen. ▻ Ziffernfolgen mit dem Suffix l oder L sind vom Typ long ▻ Jeder ganzzahlige Wert, kann mit dem Vorzeichen – oder + versehen werden. ◇ Datentypen float und double : ▻ Exponential- oder Dezimalbruchdarstellung ▻ ohne Suffix oder mit Suffix d oder D : Datentyp double ▻ mit Suffix f oder F : Datentyp float ▻ Zusätzlich kann eine Gleitpunktzahl mit dem Vorzeichen – oder + versehen werden. ◇ Datentyp char : ▻ Darstellung eines Einzelzeichens in einfachen Hochkommata (single quotes), z.B.: 'A', 'Π', ':' ▻ Darstellung durch eine C-kompatible Escape-Sequenz in einfachen Hochkommata: '\b' '\t' '\n' '\r' '\f' '\'' '\"' '\\' '\ooo' Backspace (BS) Horizontaler Tabulator (Horizontal Tab, HT) Zeilenwechsel (Newline, Linefeed, LF) Carriage Return (CR) Seitenwechsel (Form Feed, FF) Ersatzdarstellung für einfaches Hochkomma (single quote) Ersatzdarstellung für doppeltes Hochkomma (double quote) Ersatzdarstellung für Fluchtsysmbol \ Oktal-Escape-Sequenz, 1 bis 3 Oktalziffern, maximaler Wert : 0377 Anmerkung : Die C-Escape-Sequenzen '\v', '\a', '\?' und '\xhh' sind in Java nicht implementiert. ▻ Darstellung durch eine Unicode-Escape-Sequenz in einfachen Hochkommata : '\uhhhh' (h Sedezimalziffer), z.B. : '\u0041' (== 'A'), '\u00dc' (== 'Ü'), '\uffff' ◇ Für einige Grenzwerte (z.B. größter und kleinster darstellbarer positiver Wert) der numerischen Datentypen sind in den entsprechenden Wrapper-Klassen (Package java.lang) symbolische Konstanten (als public static final) definiert. s. später ◇ Datentyp boolean ▻ Es existieren nur die beiden Konstanten true und false HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/01/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Einfache Datentypen in Java (3) • Typkonvertierungen bei einfachen Datentypen ◇ Typkonvertierungen (Casts) zwischen allen ganzzahligen Typen (einschließlich char) und den Gleitpunkt-Typen sind grundsätzlich zulässig. ◇ Implizite Typkonvertierungen bei typgemischten zweistelligen arithmetischen Operationen vor Ausführung der Operation (numeric promotion) : ▻ Nur ganzzahlige Operanden (Integer-Operation), kein Operand vom Typ long : Alle Nicht–int-Operanden werden in den Typ int umgewandelt. Die Operation wird als int-Operation ausgeführt und liefert ein int-Ergebnis ▻ Nur ganzzahlige Operanden (Integer-Operation), ein Operand vom Typ long : Der andere Operand wird in den Typ long umgewandelt. Die Operation wird als long-Operation ausgeführt und liefert ein long-Ergebnis ▻ Ein Operand ist vom Typ double. Der andere Operand wird in den Typ double umgewandelt. Die Operation wird als double-Operation ausgeführt und liefert ein double-Ergebnis ▻ Ein Operand ist vom Typ float und der andere nicht vom Typ double. Der andere Operand wird in den Typ float umgewandelt. Die Operation wird als float-Operation ausgeführt und liefert ein float-Ergebnis ◇ Implizite Typkonvertierungen zwischen numerischen Typen bei Wertzuweisungen (u. Initialisierungen) Nur zulässig, wenn sichergestellt ist, dass keine Größen-Information verloren gehen kann (Genauigkeit darf verloren gehen) : ▻ Ganzzahl-Werte an Ganzzahl-Variable eines größeren Typs. ▻ Ganzzahl-Werte an Gleitpunkt-Variable ▻ float-Werte an double-Variable ▻ int-Konstante an byte-, short- oder char-Variable, wenn die int-Konstante sich auch als Wert des kleineren Datentyps darstellen lässt (für char : Zeichencode zwischen 0 und 65535). ◇ Alle anderen Umwandlungen zwischen numerischen Typen, wenn also Information verloren gehen könnte, sind nur explizit möglich (Cast-Operator !). ◇ Zwischen dem Typ boolean und den numerischen Datentypen sind keinerlei Typkonvertierungen zulässig (auch nicht explizit). ◇ Werte eines numerischen Datentyps lassen sich mit einem String-Operanden (Bibliotheks-Klasse String) mittels des Konkatenations-Operators (+) vernüpfen. In einem derartigen Fall wird der numerische Wert in seine dezimale Text-Darstellung umgewandelt und mit dem String-Operanden zu einem neuen String zusammengefasst. Beispiel : double dv = 74.25; System.out.println("Wert von dv : " + dv); Ausgabe : Wert von dv : 74.25 ◇ Analog lassen sich Werte des Typs boolean mit einem String-Operanden mittels des Konkatenations-Operators (+) verknüpfen. In einem derartigen Fall wird der logische Wert in seine Textdarstellung (entweder "true" oder "false") umgewandelt und mit dem StringOperanden zu einem neuen String zusammengefasst. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/01/4 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Java-Utility-Klasse Math • Allgemeines zur Klasse Math ◇ Diese nicht instanzierbare und nicht ableitbare Klasse definiert zwei Konstante und eine Reihe statischer Methoden zur Durchführung grundlegender numerischer Berechnungen. ◇ Die Klasse befindet sich im Package java.lang. • Mathematische Konstante ◇ public static final double E // Eulersche Zahl ◇ public static final double PI // Kreiskonstante • Statische Memberfunktionen Klasse Math (Auswahl) public static type abs(type x) ▻ Ermittlung des Betrags (Absolutwerts) des Arguments Funktionswert ▻ mehrfach überladen für verschiedene Typen type : double, float, int und long public static double atan2(double y, double x) ▻ Ermittlung des arctan(y/x) im Bogenmaß (= Phasenwinkel der Polarkoordinaten des Punktes mit den kartesichen Koordinaten (x,y)) Funktionswert public static double cos(double x) ▻ Ermittlung des Cosinus des Winkels x (x im Bogenmaß) Funktionswert public static double sin(double x) ▻ Ermittlung des Sinus des Winkels x (x im Bogenmaß) Funktionswert public static double tan(double x) ▻ Ermittlung des Tangens des Winkels x (x im Bogenmaß) Funktionswert public static double exp(double x) ▻ Ermittlung von ex Funktionswert public static double log(double x) ▻ Ermittlung des natürlischen Logarithmus von x Funktionswert public static double log10(double x) ▻ Ermittlung des dekadischen Logarithmus von x Funktionswert public static double sqrt(double x) ▻ Ermittlung der positiven Quadratwurzel von x Funktionswert ▻ Für x<0 wird NaN als Funktionswert zurückgegeben public static double pow(double a, double b) ▻ Ermittlung von ab Funktionswert public static double random() ▻ Ermittlung einer Pseudo-Zufallszahl r im Bereich (0.0 <= r < 1.0) Funktionswert Hinweis : Die Konstanten und Methoden müssen mit ihrem vollqualifizierten Namen verwendet werden (z.b: Math.PI) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/02/1 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Referenz-Typen und Objekterzeugung in Java (1) • Eigenschaften von Variablen eines Referenz-Typs ("Variablen eines Klassentyps") ◇ Beispiel : Uhr Uhr myClk; sei der Name einer Klasse (Klassentyp) Definition einer "Variablen dieses Klassentyps" ◇ Die Definition einer "Variablen eines Klassentyps" bewirkt lediglich die Bereitstellung des Speicherplatzes für die Aufnahme der Adresse eines Objekts dieser Klasse, nicht jedoch für das Objekt selbst. Dieses muss vielmehr gesondert erzeugt werden. Variable mit dieser Eigenschaft bezeichnet man als Referenz-Variable (= Variable eines Referenz-Typs) ◇ Referenz-Variable in Java referieren Objekte, d.h. Instanzen einer Klasse. Objekte können in Java nicht direkt als Werte verwendet werden (z.B. als Funktions-Parameter), sondern nur über eine Referenz. ◇ Variable eines Referenz-Typs (Referenz-Variable) werden zwar häufig als "Objekt-Variable" bezeichnet, tatsächlich sind sie aber Pointer-Variable, die auf ein Objekt verweisen Ihr Wert ist eine Speicheradresse. Bei ihrer Verwendung werden sie automatisch dereferenziert. (Anwendung des Punkt- (.) Operators zum Komponentenzugriff). ◇ Eine Referenz-Variable kann immer nur auf Objekte eines bestimmten Typs bzw bestimmter Typen (Polymorphie !) zeigen. Formal werden drei Arten von Referenz-Variablen unterschieden : ▻ Klassen-Typen (sie verweisen auf Objekte der entsprechenden Klasse oder auf Objekte davon abgeleiteter Klassen) ▻ Interface-Typen (sie verweisen auf Objekte, die das jeweilige Interface implementieren) ▻ Array-Typen (sie verweisen auf Arrays, Arrays sind in Java ebenfalls – spezielle – Objekte) ◇ Bezüglich ihres Anwendungs-Zwecks kann man zwei Klassen-Kategorien unterscheiden : ▻ Value-Typen : Der Inhalt der Objekte (= Wert der Datenkomponenten) ist das Wesentliche. Er repräsentiert den Wert eines Objekts und der ist entscheidend für dessen Verwendung (analog zu den einfachen Datentypen) ▻ Entity-Typen : Der Inhalt eines Objekts ist nicht das Wesentliche. Objekte werden nicht als Werte betrachtet. Sie bieten im wesentlichen Dienste an, gegebenenfalls durch die Benutzung referierter anderer Objekte. Diese Dienste sind entscheidend für ihre Verwendung. ◇ Die für Referenz-Variable definierten Vergleichs-Operatoren == und != beziehen sich auf die Referenzen (Adressen) und nicht auf den Inhalt der referierten Objekte. => Vergleich auf Identität der Objekte und nicht auf deren inhaltliche Gleichheit Gleiche Referenzen bedeuten natürlich auch Gleichheit der referierten - identischen - Objekte. Ungleiche Referenzen bedeuten aber nicht, dass auch die referierten - nicht identischen - Objekte ungleich sein müssen. ◇ Als spezielle Referenz-Konstante ist der Wert null definiert. Eine Referenz-Variable mit diesem Wert zeigt auf nichts (also auf kein Objekt). Einer lokalen Referenz-Variablen, die auf nichts zeigen soll, muss dieser Wert explizit zugewiesen werden. Beispiel : String sa = null; Für Referenz-Variable, die Member-Variable sind, ist null der ohne explizite Initialisierung zugewiesene Default-Wert. ◇ Mit dem auf Referenz-Variable anwendbaren Operator instanceof lässt sich überprüfen, ob das referierte Objekt von einem bestimmten Typ ist (s. später). HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/02/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Referenz-Typen und Objekterzeugung in Java (2) • Erzeugung von Objekten ◇ Objekte werden grundsätzlich nur namenlos dynamisch auf dem Heap alloziert. ◇ Hierzu dient ein new-Ausdruck : new-Operator mit nachgestellten Konstruktor-Aufruf. new Konstruktor-Aufruf ◇ Konstruktoren sind spezielle klassenspezifische Funktionen, die zur Erzeugung eines Objekts benötigt werden. Sie tragen i.a. den Namen der Klasse des zu erzeugenden Objekts (Ausnahme s. Arrays). Konstruktoren können Parameter besitzen. ◇ Der new-Ausdruck liefert die Adresse des erzeugten Objekts zurück, die dann einer Referenz-Variablen als Wert zugewiesen werden kann. Beispiel : String sv = new String("Hallo !"); ◇ In Java gibt ein in der JVM im Hintergrund laufender automatischer Garbage Collector den allozierten Speicher für ein Objekt wieder frei, wenn keinerlei Referenz mehr auf das Objekt existiert Eine explizite Freigabe des dynamisch allozierten Speichers ist daher weder notwendig noch überhaupt möglich In Java existiert daher kein Speicherfreigabe-Operator (wie z.B. delete in C++). • Die this-Referenz ◇ In jeder objektspezifischen Methode eines Objekts steht eine Referenz auf das Objekt selbst zur Verfügung Schlüsselwort this this-Referenz ◇ Verwendung ▻ als Rückgabewert einer Methode. ▻ als Parameter in aufgerufenen Methoden anderer Objekte, die eine Referenz auf das aufrufende Objekt benötigen ▻ Verwendung zum Ansprechen der Datenkomponenten des eigenen Objekts Nur sinnvoll, bei gleichnamigen Funktions-/Konstruktor-Parametern ▻ innerhalb eines Konstruktors Aufruf eines anderen Konstruktors der eigenen Klasse ◇ Beispiel : public class Punkt { private double x; private double y; public Punkt() { this(0,0); } public Punkt(double x, double y) { this.x = x; this.y = y; } public Punkt move (double dx, double dy) { x+=dx; y+=dy; return this; } public void printPunkt() { System.out.print(this); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/03/1 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Konstante in Java • Der Modifizierer final für Daten ◇ Er legt fest, dass der entsprechende Datenwert nicht geändert werden kann ◇ Anwendbar – sowohl für einfache Datentypen als auch für Referenztypen – auf ▻ statische Datenkomponenten ▻ nichtstatische Datenkomponenten ▻ lokale Variable (in Funktionen) ◇ Für Referenztypen legt final lediglich fest, dass die Referenz nicht änderbar ist, d.h. das referierte Objekt kann nicht gewechselt werden. Ob der Zustand dieses referierten Objekts änderbar ist, wird dadurch nicht bestimmt. • Symbolische Konstante ◇ Sind objektunabhängig. Definition als nicht-änderbare statische Datenkomponenten (Klassen-Konstante). Modifizierer static und final. Wenn sie allgemein verwendbar sein sollen zusätzlicher Modifizierer public. ◇ Initialisierung mit dem durch sie repräsentierten Wert bei der Definition. ◇ Es ist üblich – aber nicht verpflichtend – , Konstanten-Namen mit Großbuchstaben zu bilden. ◇ Beispiele aus der Java-Standard-Bibliothek public final class Integer { public static final int MAX_VALUE = 0x7fffffff; public static final int MIN_VALUE = 0x80000000; // ... } public final class Math { public static final double PI = 3.14159265358979323846; // ... } ◇ Verwendung ausserhalb ihrer definierenden Klasse mit ihrem vollqualifizierten Namen: klassenname.KONSTANTENNAME • Objektspezifische und lokale Konstante ◇ Objektspezifische Konstante : Definition als nichtstatische nichtänderbare Datenkomponenten Modifizierer final (und meist private) Initialisierung bei der Definition oder durch den Konstruktor, danach nicht mehr änderbar ( Fehler !) Alle nichtstatischen Datenkomponenten final : Objektzustand nicht änderbar (konstantes Objekt) ◇ Lokale Konstante in Funktionen: Modifizierer final vor der Typangabe Beispiel : final int ANZ = 10; Sofern diese nicht bei ihrer Definition initialisiert werden (blank final), kann ihnen später einmal ein Wert zugewiesen werden Jeder Versuch einer weiteren Wertzuweisung führt zu einem Compilerfehler. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/04/1 – TH – 06 -------------------------------------------------------------------------------------------------------------------------------------------------------- Wrapper-Klassen für die einfachen Datentypen (1) • Verwendung von Werten einfacher Datentypen wie Objekte ◇ Werte einfacher Datentypen und Objekte werden prinzipiell unterschiedlich dargestellt und referiert. Dadurch lassen sich Werte einfacher Datentypen nicht direkt in Situationen, in denen Objekte benötigt werden, verwenden (z.B. als Argument in vielen Methoden, aktueller Typ bei generischen Datentypen) ◇ Um eine derartige Verwendung trotzdem zu ermöglichen, existieren Wrapper-Klassen für alle einfachen Datentypen. Ein Objekt einer Wrapper-Klasse kapselt einen Wert des korrespondierenden einfachen Datentyps. Dieser Wert wird bei der Objekterzeugung festgelegt (Konstruktor-Parameter) und kann später nicht mehr geändert werden ( objektspezifische Konstante). • Die Wrapper-Klassen im Überblick ◇ Die Wrapper-Klassen befinden sich im Package java.lang ◇ Alle Wrapper-Klassen – außer Character – verfügen über mindestens 2 Konstruktoren : ▻ Ein Konstruktor besitzt einen Parameter vom zu kapselnden einfachen Typ. Er dient zur Umwandlung : Wert Objekt ▻ Der zweite Konstruktor besitzt einen String-Parameter. Der String muss die textuelle Repräsentation des zu kapselnden Wertes sein. ◇ Zur Ermittlung des von einem Wrapper-Klassen-Objekt gekapselten Wertes stellen die Wrapper-Klassen sogenannte "Getter"-Methoden zur Verfügung (Umwandlung Objekt Wert): primitivtyp primitivtypValue() ◇ einfacher Datentyp Wrapper-Klasse "Getter"-Methode char Character public char charValue() byte Byte public byte byteValue() short Short public short shortValue() int Integer public int intValue() long Long public long longValue() float Float public float floatValue() double Double public double doubleValue() boolean Boolean public boolean booleanValue() ◇ Beispiele für die Umwandlung Wert ⇔ Objekt Integer iobj Double dobj long lval1 Long lobj = = = = new Integer(27); new Double("3.14"); -34567L; new Long(lval1); int ival double dval long lval2 = iobj.intValue(); = dobj.doubleValue(); = lobj.LongValue(); Wert Objekt Objekt Wert HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/04/2 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Wrapper-Klassen für die einfachen Datentypen (2) • Umwandlung von Strings in dargestellte Werte ◇ Die Wrapper-Klassen stellen auch statische Methoden zur Verfügung, die einen String, der die textuelle Repräsentation eines Werts eines einfachen Datentyps ist, in den dargestellten Wert umwandeln. ("Parser"-Methoden) Falls der String nicht umwandelbar ist, wird von den Methoden eine NumberFormatException geworfen. ◇ Überblick über die "Parser"-Methoden : ▻ Klasse Byte : public static byte ▻ Klasse Short : public static short parseShort(String str) ▻ Klasse Integer : public static int parseInt(String str) ▻ Klasse Long : public static long parseLong(String str) ▻ Klasse Float : public static float parseFloat(String str) ▻ Klasse Double : public static double parseDouble(String str) ▻ Klasse Boolean : public static boolean parseBoolean(String str) Der String "true" (unabhängig von Groß-/Kleinschreibung) liefert true, alle anderen Strings liefern false (Methode existiert erst ab dem JDK 5.0) ◇ Beispiele : double dv String is int iv boolean bv = = = = parseByte(String str) Double.parseDouble("55.4321"); new String("98765"); Integer.parseInt(is); Boolean.parseBoolean("richtig"); • Grenzwerte der numerischen Datentypen ◇ Für alle numerischen Datentypen sind in den entsprechenden Wrapper-Klassen (Package java.lang) die folgenden symbolischen Konstanten (als public static final) definiert : größter darstellbarer Wert kleinster darstellbarer Wert (ist negativ) bei Ganzzahl-Typen bzw kleinster darstellbarer positiver Wert bei Gleitpunkt-Typen MAX_VALUE MIN_VALUE ◇ Für die Datentypen float und double sind in den Klassen Float und Double u.a. zusätzlich definiert : NaN NEGATIVE_INFINITY POSITIVE_INFINITY Not-a-Number (Repräsentation eines ungültigen Werts, z.B. 0/0) negativ unendlich positiv unendlich ◇ Verwendung dieser Konstanten immer nur zusammen mit dem jeweiligen Klassennamen (voll-qualifizierter Name) : z.B. : double dv = Double.MIN_VALUE; HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/04/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Wrapper-Klassen für die einfachen Datentypen (3) • Autoboxing/-unboxing (Implizite Umwandlung zwischen Werten und Objekten) ◇ Bis zum JDK 1.4 mußten Umwandlungen zwischen den Werten einfacher Datentypen und Wrapper-KlassenObjekten explizit mittels Konstruktoren und "Getter"-Methoden durchgeführt werden (s. oben). ◇ Mit dem JDK 5.0 wurde das Konzept des Autoboxing/-unboxing eingeführt. Dieses ermöglicht eine implizite (automatische) Umwandlung zwischen Werten und Objekten. Werte einfacher Datentypen und Wrapper-Objekte können völlig kompatibel zueinander verwendet werden. ◇ Wert Objekt (Autoboxing) : Beispiel 1 : Beispiel 2 : ◇ Objekt int iVal1 = 12; Integer iObj; iObj = iVal1; // implizite Erzeugung eines neuen Integer-Objekts Double dObj = 0.75; // implizite Erzeugung eines neuen Double-Objekts Wert (Autounboxing) : Beispiel 1 : int iVal2 = iObj; // implizites De-Wrapping Beispiel 2 : double dVal = dObj; // implizites De-Wrapping ◇ Autoboxing/-unboxing findet überall statt, wo Werte einfacher Datentypen bzw Wrapper-Objekte benötigt werden. Damit können z.B. mit Wrapper-Objekten auch "direkt" arithmetische Operationen vorgenommen werden. Beispiele : Integer iObj1 = 10; Integer iObj2 = iObj1 + 5; ++iObj1; ◇ Autoboxing findet allerdings nur dann statt, wenn keine andere implizit mögliche Typkonvertierung anwendbar ist. Derartige Situationen können z.B. bei überladenen Funktionen auftreten. Beispiel : void func(double dv); void func(Integer io); . . . func(5) führt zum Aufruf der ersten Methode (func(5.0)) (int double) • Interpretation des Bitmusters von Gleitpunktwertdarstellungen als ganze Zahl ◇ Hierfür stehen in der Klasse Float bzw Double entsprechende statische Methoden zur Verfügung. ◇ Klasse Float : public static int floatToIntBits(float value) Interpretation des Bitmusters des float-Wertes value als int-Wert ◇ Klasse Double : public static long doubleToLongBits(double value) Interpretation des Bitmusters des double-Wertes value als long-Wert HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/05/1 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Strings in Java - Allgemeines • Darstellung von Strings ◇ Strings in Java bestehen aus Unicode-Zeichen. Wie in C besteht eine String-Konstante (string literal) aus einer Folge von 0 oder mehr Zeichen, die in doppelte Hochkommata (double qoutes) eingeschlossen sein müssen. Die Zeichen können auch durch Escape-Sequenzen dargestellt werden. Beispiele : "\nHallo" "\u00ea\u0256" ◇ Strings sind in Java Objekte. String-Konstante werden durch Objekte der Bibliotheks-Klasse String dargestellt. Der Inhalt derartiger Objekte ist unveränderlich. Zur Darstellung von veränderlichen Strings dienen die Bibliotheks-Klassen StringBuffer (threadsicher) und StringBuilder (ab JDK 5.0, nicht thread-sicher, aber schneller) ◇ Eine String-Variable ist – wie alle "Objekt-Variablen" in Java – eine Referenz-Variable. Sie enthält eine Referenz auf ein String-Objekt. Eine Zuweisung eines neuen Strings an eine String-Variable ("Variable vom Typ String") bedeutet nicht, dass das referierte String-Objekt geändert wird, sondern dass die String-Variable ein anderes String-Objekt referiert. Beispiel : String str = "Hausboot"; // ... str = "Segelyacht"; // str zeigt auf ein anderes Objekt ! • Direkte Sprachunterstützung für Strings ◇ In der Sprache Java selbst sind einige Mechanismen zur Erzeugung und Verwendung von Strings implementiert, die im Zusammenhang mit der Klasse String zur Anwendung kommen ◇ Beim Auftritt einer String-Konstanten im Quelltext erzeugt der Compiler ein String-Objekt, das mit der String-Konstanten initialisiert wird. Beispiele : System.out.println("Schoene Gruesse !"); Der Compiler erzeugt ein String-Objekt mit dem Inhalt "Schoene Gruesse !" und übergibt eine Referenz (Adresse) auf dieses Objekt der Methode println(...) String s1 = "Das alte Europa"; Der Compiler erzeugt ein String-Objekt mit dem Inhalt "Das alte Europa" und weist der Variablen s1 eine Referenz (Adresse) auf dieses Objekt zu ◇ Für String-Objekte ist der Operator + überladen als Konkatenations-Operator. Er erzeugt ein neues String-Objekt, das aus den beiden Operanden zusammengesetzt ist. Beispiel : s1 = s1 + " hatte Recht !"; ◇ Der + -Operator wirkt auch dann als Konkatenations-Operator, wenn nur ein Operand vom Typ String ist. In einem derartigen Fall wird der andere Operand implizit in ein String-Objekt umgewandelt und mit dem String-Operanden zu einem neuen String-Objekt konkateniert. Die implizite Konvertierung in ein String-Objekt (string conversion) findet für jeden einfachen Typ und für jeden Referenz-Typ (Klasse !) statt. Ist die in einer Objekt-Variablen gespeicherte Referenz gleich null, wird der String "null" erzeugt. Beispiel : System.out.println("Heute ist : " + new java.util.Date()); ◇ Eine String-Konkatenation kann auch mit dem += -Operator realisiert werden. Wenn hier der rechte Operand kein String-Objekt ist, wird er implizit in ein solches konvertiert. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/05/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Strings in Java - Beispiele zur direkten Sprachunterstützung • Klasse StringDemo1 als Demonstrationsbeispiel public class StringDemo1 { public void demo() { System.out.println("\n\"Hallo\""); System.out.println("\u00ea\u0256\u00df\u00fc"); String s2 = "Hausboot"; // ... s2 = "Segelyacht"; System.out.println(s2); System.out.println("Schoene Gruesse !"); String s1 = "Das alte Europa"; s1 = s1 + " hatte Recht !"; s1 += 25; int i3 = 248; s1+=i3; System.out.println(s1); String s3 = "Ein Test "; int i1 = 1; int i2 = 2; System.out.println(s3+i1+i2); System.out.println("Heute ist : " + new java.util.Date()); String neu = null; System.out.println("Der String neu lautet : " + neu); } public static void main(String[] args) { StringDemo1 strdem1 = new StringDemo1(); strdem1.demo(); } } Ausgabe des Programms : "Hallo" ê?ßü Segelyacht Schoene Gruesse ! Das alte Europa hatte Recht !25248 Ein Test 12 Heute ist : Tue Jul 23 17:17:19 CEST 2013 Der String neu lautet : null HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/05/3 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse String in Java (1) • Allgemeines zur Klasse String ◇ Standard-Klasse zur Darstellung von nicht veränderlichen Strings. ◇ Bestandteil des Package java.lang ◇ Von der Klasse String können keine anderen Klassen abgeleitet werden (Die Klasse ist final) ◇ Die Klasse String implementiert das Interface CharSequence ◇ Die Klasse String enthält zahlreiche Konstruktoren und Methoden zur Verwendung von Strings, u.a. - zur expliziten Erzeugung von String-Objekten (Konstruktoren), - zur Ermittlung der Stringlänge - zum lesenden Zugriff zu String-Elementen, - zum String-Vergleich - zum Suchen in Strings - zur Extraktion von Teilstrings, - zum Kopieren von Strings mit Umwandlung aller Buchstaben in Großbuchstaben bzw Kleinbuchstaben. • Konstruktoren der Klasse String (Auswahl) public String() Erzeugt ein String-Objekt, das den Leerstring enthält public String(String str) Erzeugt ein String-Objekt, das eine Kopie des Inhalts von str enthält (Copy-Konstruktor) public String(char[] acs) Erzeugt ein String-Objekt, das die Zeichenfolge aus acs enthält. Die Zeichen werden in das neu erzeugte Objekt kopiert. public String(byte[] abs) Erzeugt ein String-Objekt, das mit der Zeichenfolge, die durch Decodierung der byte-Werte von abs entsteht, initialisiert ist. Die Decodierung erfolgt für den Default-Zeichensatz der Implementierung public String(StringBuffer buf) Erzeugt ein String-Objekt, das mit der in buf bzw public String(StringBuilder bld) bld enthaltenen Zeichenfolge initialisiert ist. Die Zeichen werden in das neu erzeugte Objekt kopiert. ◇ Hinweis : Die explizite Erzeugung eines String-Objekts bei Initialisierung mit einer String-Konstanten ist ineffizient : String str = new String("Wer rastet, der rostet"); Dies führt zunächst zur impliziten Erzeugung eines String-Objektes das mit der String-Konstanten "Wer rastet, der rostet" initialisiert ist. Anschliessend wird zur expliziten Erzeugung eines weiteren String-Objekts der Copy-Konstruktor aufgerufen, dem eine Referenz auf das zuerst erzeugte Objekt übergeben wird. Eine Referenz auf das explizit erzeugte zweite Objekt wird dann der String-Variablen str zugewiesen. Vorzuziehen ist daher die direkte "Initialisierung" einer String-Variablen mit einer String-Konstanten. In diesem Fall wird nur ein String-Objekt – implizit – erzeugt String str = "Wer rastet, der rostet"; Entsprechendes gilt für die Wertzuweisung an String-Variable. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/05/4 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse String in Java (2) • Memberfunktionen Klasse String (Auswahl Teil 1) public int length() ▻ Gibt die Länge eines String-Objekts (= die Anzahl der enthaltenen Unicode-Zeichen) als Funktionswert zurück public char charAt(int index) ▻ Liefert das Zeichen mit dem Index index als Funktionswert zurück. ▻ Der zulässige Indexbereich reicht von 0 (erstes Zeichen) bis length()-1 (letztes Zeichen). ▻ Erzeugung einer IndexOutOfBoundsException, wenn ein unzulässiger Index übergeben wird. public boolean contains(CharSequence s) // ab dem JDK 5.0 vorhanden ▻ Überprüfung, ob die Zeichenfolge s (z.B. Inhalt eines String-Objekts) im aktuellen Objekt enthalten ist ▻ Falls ja, wird true als Funktionswert zurückgegeben, andernfalls false public int indexOf(int ch) ▻ Rückgabe des Index des ersten Auftritts des – als int-Wert übergebenen – Zeichens ch als Funktionswert. ▻ Falls das Zeichen ch nicht enthalten ist, wird der Wert –1 zurückgegeben. ▻ Beispiel : "hamburger".indexOf('r') liefert 5 public int indexOf(int ch, int from) ▻ Funktionalität wie int indexOf(int ch), allerdings beginnt die Suche am Index from ▻ Beispiel : "hamburger".indexOf('r', 6) liefert 8 public int indexOf(String str) ▻ Rückgabe des Index des ersten Auftritts des Teilstrings str. ▻ Der zurückgegebene Funktionswert ist der Index des ersten Zeichens des gefundenen Teilstrings. ▻ Falls der Teilstring str nicht enthalten ist, wird der Wert –1 zurückgegeben. public int indexOf(String str, int from) ▻ Funktionalität wie int indexOf(String str), allerdings beginnt die Suche am Index from public public public public int int int int lastIndexOf(int ch) lastIndexOf(int ch, int from) lastIndexOf(String str) lastIndexOf(String str, int from) ▻ Ähnlich wie die Funktionen indexOf(...). ▻ Nur Ermittlung des Index des letzten Auftritts des Zeichens ch bzw des Teilsstrings str. ▻ Beispiel : "hamburger".lastIndexOf("rindfleisch") liefert -1 public boolean isEmpty() // ab dem JDK 6.0 vorhanden ▻ Gibt true zurück, wenn das aktuelle String-Objekt leer (d.h. seine Länge==0) ist, andernfalls false HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/05/5 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse String in Java (3) • Memberfunktionen Klasse String (Auswahl Teil 2) public boolean endsWith(String suffix) ▻ Überprüfung, ob das aktuelle Objekt mit dem String suffix endet. ▻ Falls ja, wird true als Funktionswert zurückgegeben, andernfalls false public boolean startsWith(String prefix) ▻ Überprüfung, ob das aktuelle Objekt mit dem String prefix beginnt. ▻ Falls ja, wird true als Funktionswert zurückgegeben, andernfalls false public boolean equals(Object obj) ▻ Überprüfung, ob das übergebene Objekt ein String-Objekt ist und gleiche Länge und gleichen Inhalt wie das aktuelle Objekt hat. ▻ Falls ja, wird true als Funktionswert zurückgegeben, andernfalls false public int compareTo(String str) ▻ Durchführung eines lexikographischen Vergleichs zwischen dem aktuellen String-Objekt und dem String-Objekt str ▻ Der Vergleich wird mit den Codewerten der einzelnen Zeichen bzw der Stringlänge ausgeführt ▻ Funktionswert : - 0, wenn beide String-Objekte gleich sind - <0, wenn das aktuelle Objekt "kleiner" als str ist - >0, wenn das aktuelle Objekt "größer" als str ist ▻ Beispiel : "haben".compareTo("hat") liefert –18 public String substring(int beg, int end) ▻ Rückgabe des Teilstrings, der am Index beg beginnt und am Index end-1 endet, als neues StringObjekt (genauer : als Referenz auf ein neu erzeugtes String-Objekt mit dem entsprechenden Inhalt des Teilstrings). ▻ Erzeugung einer IndexOutOfBoundsException, falls wenigstens einer der übergebenen Indizees unzulässig ist oder beg>end ist. ▻ Beispiel : "hamburger".substring(4,8) liefert "urge" public String toUpperCase() ▻ Rückgabe eines neuen String-Objekts, in dem alle Buchstaben in Großbuchstaben konvertiert sind public String replace(char alt, char neu) ▻ Rückgabe eines neuen String-Objekts, das aus dem aktuellen Objekt durch Ersatz aller Auftritte des Zeichens alt durch das Zeichen neu und der Übernahme alle übrigen Zeichen entsteht. ▻ Ist das Zeichen alt überhaupt nicht im aktuellen Objekt enthalten, wird kein neues Objekt erzeugt, sondern das aktuelle Objekt zurückgegeben. public String trim() ▻ Rückgabe einer neu erzeugten Kopie des aktuellen Objekts, bei der alle führenden und endenden Blanks sowie ASCII-Steuerzeichen (Zeichencode <= '\u0020') entfernt sind ▻ Falls sowohl das erste als auch das letzte Zeichen des aktuellen String-Objekts einen Zeichencode > '\u0020' besitzen, wird keine Kopie erzeugt, sondern das aktuelle Objekt zurückgegeben HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/05/6 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse String in Java (4) • Explizite Umwandlung in eine String-Darstellung ◇ In der Klasse String existiert eine mehrfach überladene statische Memberfunktion zur Umwandlung von Werten der einfachen Datentypen sowie von Objekten beliebiger Klassen in eine String-Darstellung Beispiel für die Umwandlung von double-Werten : public static String valueOf(double d) ◇ Für jede Klasse ist in Java die folgende Memberfunktion definiert, mit der ein Objekt der jeweiligen Klasse in ein String-Objekt (textuelle Repräsentation des Objekts !) umgewandelt wird : public String toString() Inhalt und Aufbau des Strings kann für jede Klasse geeignet festgelegt werden. Falls das nicht der Fall ist, wird eine Default-Darstellung verwendet (s. später, Klasse Object) ◇ Erzeugung formatierter Strings (z.B. für die formatierte Darstellung von Zahlenwerten) mittels der statischen Memberfunktion der Klasse String (s.a. Kap. 13.2, Standard-Ein- und Ausgabe) : public static String format(String form, Object... args) ▻ Parameter : form Formatierungs-String, Aufbau im wesentlichen wie bei printf(...) in C. args auszugebende Objekte/Werte (Autoboxing!) entsprechend den Formatangaben im Formatierungs-String ▻ Funktionswert : Erzeugter formatierter String HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/05/7 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse String in Java (5) • Demonstrationsbeispiel zur Klasse String public class StringDemo2 { public void demo() { String str = "Aller Anfang"; str = str + " ist"; str += " schwer"; System.out.println(str); int len = str.length(); System.out.println("Laenge des Strings : " + len); int idx = 6; System.out.println(idx+1 + ". Zeichen : " + str.charAt(idx)); System.out.println("Index von \"ist\" : " + str.indexOf("ist")); char ch = 'r'; System.out.println("letzter Index von " + ch + " : " + str.lastIndexOf(ch)); int ie = idx+6; System.out.print("Teilstring (" + idx + ',' + ie + ") : "); System.out.println(str.substring(idx, ie)); System.out.println(str.toUpperCase()); int diff = "haben".compareTo("hat"); System.out.println("Vergleich von \"haben\" und \"hat\" liefert : "+diff); java.util.Date now = new java.util.Date(); String s10 = now.toString(); String s11 = String.valueOf(now); System.out.println(s10); System.out.println(s11); if (s10.equals(s11)) System.out.println("Strings sind gleich !"); else System.out.println("Strings sind ungleich !"); String pi_4st = String.format("%.4f", Math.PI); System.out.println("PI : " + pi_4st); } public static void main(String[] args) { StringDemo2 strdem2 = new StringDemo2(); strdem2.demo(); } } Ausgabe des Programms : Aller Anfang ist schwer Laenge des Strings : 23 7. Zeichen : A Index von "ist" : 13 letzter Index von r : 22 Teilstring (6,12) : Anfang ALLER ANFANG IST SCHWER Vergleich von "haben" und "hat" liefert : -18 Tue Jul 23 18:19:30 CEST 2013 Tue Jul 23 18:19:30 CEST 2013 Strings sind gleich ! PI : 3,1416 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/06/1 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Arrays in Java (1) • Allgemeine Eigenschaften ◇ Arrays sind in Java Objekte. Array-Typen sind – namenlose – Klassen. Von einer Array-Klasse können keine weiteren Klassen abgeleitet werden. ◇ Als Element-Typ ist jeder beliebige Typ (einfacher Datentyp oder Referenz-Typ) zulässig. ◇ Die Auswahl eines Array-Elements erfolgt wie in C : Ein Array-Element wird über einen Index, der vom Typ int sein muss, ausgewählt. Der Index des ersten Elements hat den Wert 0. ◇ Array-Variable sind Referenz-Variable. Sie verweisen auf Array-Objekte. Die Definition einer Array-Variablen erzeugt noch kein Array-Objekt, d.h. sie alloziert keinen Speicherplatz für die Array-Elemente. ◇ Eine Array-Variable legt nur den Element-Typ , nicht aber die Länge eines Arrays (d.h. die Anzahl seiner Elemente) fest. Sie kann daher auf Arrays beliebiger Länge zeigen. ◇ Die Länge eines Arrays wird dynamisch bei seiner Erzeugung (z.B. mittels eines new-Ausdrucks) festgelegt. Anschließend ist sie nicht mehr veränderbar. ◇ Ein Array kann auch die Länge 0 besitzen. Ein derartiges Array ist ein echtes Array-Objekt. Sinnvoll können derartige Arrays beispielsweise als Rückgabewert von Funktionen (genauer : Referenz darauf) auftreten. ◇ Jedes Array besitzt die öffentliche Datenkomponente public final int length; Sie enthält die Länge des Arrays. ◇ Ein Array-Zugriff wird zur Laufzeit auf Zulässigkeit überprüft. Der Versuch des Zugriffs zu einer nicht existierenden Array-Komponente (ungültiger Index) führt zum Werfen der Exception ArrayIndexOutOfBoundsException. • Vereinbarung von Array-Variablen ◇ Angabe eines Array-Typs : Element-Typ [] ◇ Diese Typangabe ist bei der Vereinbarung von Array-Variablen zu verwenden. Beispiele : int[] ia; // ia kann auf Array-Objekte zeigen, deren Elemente int-Werte sind String[] names; // names kann auf Array-Objekte zeigen, deren Elemente // Referenzen auf String-Objekte sind ◇ Element-Typ eines Arrays kann wiederum ein Array sein mehrdimensionale Arrays. Die Vereinbarung mehrdimensionaler Arrays erfolgt analog zu eindimensionalen Arrays. Beispiele : double[][] kmat; // kmat kann auf Array-Objekte zeigen, deren Elemente Refe// renzen auf Array-Objekte sind, die double-Werte enthalten String[][] seite; // seite kann auf Array-Objekte zeigen, deren Elemente // Referenzen auf Array-Objekte sind, die Referenzen auf // String-Objekte enthalten. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/06/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Arrays in Java (2) • Definition von Array-Objekten mittels new-Ausdruck ◇ Andere Syntax als bei Nicht-Array-Objekten : kein Konstruktoraufruf ! Stattdessen ist der Elementtyp zusammem mit der Länge des Arrays anzugeben. Die einzelnen Elemente werden mit ihrem Defaultwert initialisiert. Beispiele : ia = new int[10]; // Speicherallokation für ein Array-Objekt, das 10 int// Elemente hat. // Alle Elemente sind mit 0 initialisiert. names = new String[3]; // // // // Speicherallokation für ein Array-Objekt, das 3 Elemente hat, die Referenzen auf String-Objekte sind. Alle Elemente sind mit null initialisiert. ◇ Die Definition eines Array-Objekts mittels eines new-Ausdrucks kann natürlich auch zusammen mit der Vereinbarung einer Array-Variablen erfolgen. Beispiel : float[] pol = new float[6]; // pol zeigt auf ein alloziertes float-Array // mit 6 Elementen (alle mit 0.0 initialisiert) ◇ Bei mehrdimensionalen Arrays kann im new-Ausdruck die Länge aller Dimensionen angegeben werden. Es reicht aber aus, nur die Länge der ersten (am weitesten links stehenden) Dimension festzulegen. Die Längen der übrigen Dimensionen müssen dann später festgelegt werden. Beispiel : kmat = new double[5][4]; // // // // // // Speicherallokation für ein Array-Objekt, das 5 Elemente hat, die auf gleichzeitig allozierte double-Arrays, die alle 4 Elemente haben, zeigen. Die Elemente der double-Arrays sind mit 0.0 initialisiert Das obige Beispiel ist eine abkürzende Schreibweise für die folgende ausführlichere Formulierung : kmat = new double[5][]; // // // // Speicherallokation für ein Array-Objekt, das 5 Elemente hat, die auf double-Arrays zeigen können, aber alle mit null initialisiert sind. for (int i=0; i<kmat.length; i++) kmat[i] = new double[4]; // Speicherallokation für 5 double-Arrays der // Länge 4. // Alle Elemente dieser Arrays sind mit 0.0 // initialisiert. ◇ Als Vorteil der Java-Implementierung von mehrdimensionalen Arrays ergibt sich, dass die Element-Arrays (Arrays in zweiter (und gegebenenfalls höherer) Dimension eine unterschiedliche Länge besitzen können. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/06/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Arrays in Java (3) • Definition von Array-Objekten mittels Array-Initialisierer (array initializer) ◇ Alternative zum new-Ausdruck : Angabe eines Array-Initialisierers bei der Variablenvereinbarung ◇ Syntax für Array-Initialisierer : { Initialisierungs-Ausdruck } , ◇ Ein Array-Initialisierer bewirkt die Allokation von Speicherplatz für ein Array-Objekt und initialisiert die einzelnen Elemente mit den angegebenen Initialisierungs-Ausdrücken. ◇ Die Länge des Arrays wird nicht angegeben .Sie ergibt sich aus der Anzahl der Initialisierungsausdrücke Beispiel : String[] farben = { "rot", "gruen", "blau", "gelb", "weiss" }; // Vereinbarung der Array-Variablen farben. Diese zeigt auf ein gleichzeitig alloziertes // Array-Objekt, dessen 5 Elemente mit den Referenzen auf die angegebenen Farb-Strings, // für die ebenfalls Speicher alloziert wird, initialisiert werden. ◇ Bei mehrdimensionalen Arrays können Array-Initialisierer geschachtelt werden (InitialisierungsAusdrücke sind wiederum Array-Initialisierer) ◇ Array-Initialisierer können auch zusammen mit einem new-Ausdruck verwendet werden. Sie sind dem new-Ausdruck nachzustellen, eine Array-Länge darf nicht angegeben werden. (sinnvoll zur Erzeugung anonymer initialisierter Arrays) • Unterstützende Klassen der Standard-Bibliothek ◇ In der Java-Standardbibliothek sind zwei Klassen definiert, die statische Memberfunktionen zur Unterstützung der Verwendung von Arrays zur Verfügung stellen Beide Klassen können nicht instanziiert werden. ◇ Klasse java.lang.reflect.Array Sie stellt statische Methoden zur dynamischen Erzeugung von und zum Zugriff zu Arrays zur Verfügung. ◇ Klasse java.util.Arrays Sie stellt statische Methoden zur Bearbeitung von Arrays zur Verfügung. Im wesentlichen handelt es sich um Methoden - zum Suchen in Arrays - zum Sortieren von Array-Komponenten - zum Vergleich von Arrays - zur Zuweisung an Array-Bereiche ◇ Die Klasse java.lang.System definiert zum Kopieren von Arraybereichen die statische Methode public static void arraycopy(Object src, int sPos, Object dest, int dPos, int len) Quell-Objekt (src) und Ziel-Objekt (dest) müssen Arrays sein HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/06/4 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm zu Arrays in Java • Quellcode des Programms (Klasse ArrayDemo) public class ArrayDemo { public void demo() { int[] ia; ia = new int[6]; System.out.println("int-Array : " + ia); for (int i=0; i<ia.length; i++) System.out.println(i + " : " + ia[i]); String[] names = /* new String[]*/ { "Walter", "Franz", "Ilka" }; System.out.println("String-Array :" + names); for (int i=0; i<names.length; i++) System.out.println(i + " : " + names[i]); double[][] kmat = new double[4][3]; System.out.println("2-dimensionales double-Array : " + kmat); for (int i=0; i<kmat.length; i++) System.out.println(i + " : " + kmat[i]); System.out.println("erstes Zeilen-Array :"); for (int i=0; i<kmat[0].length; i++) System.out.println(i + " : " + kmat[0][i]); System.out.println("weiteres 2-dimensionales double-Array :"); double[][] gmat = new double[4][]; for (int i=0; i<gmat.length; i++) gmat[i] = new double[i+1]; for (int i=0; i<gmat.length; i++) { System.out.print(i + " : "); for (int j=0; j<gmat[i].length; j++) System.out.print(gmat[i][j]+ " "); System.out.println(); } } public static void main(String[] args) { new ArrayDemo().demo(); } } • Ausgabe des Programms int-Array : [I@108786b 0 : 0 1 : 0 2 : 0 3 : 0 4 : 0 5 : 0 String-Array :[Ljava.lang.String;@119c082 0 : Walter 1 : Franz 2 : Ilka 2-dimensionales double-Array : [[D@1add2dd 0 : [D@eee36c 1 : [D@194df86 2 : [D@defa1a 3 : [D@f5da06 erstes Zeilen-Array : 0 : 0.0 1 : 0.0 2 : 0.0 weiteres 2-dimensionales double-Array : 0 : 0.0 1 : 0.0 0.0 2 : 0.0 0.0 0.0 3 : 0.0 0.0 0.0 0.0 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/06/5 – TH – 01 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die vereinfachte for-Anweisung in Java • Iteration über alle Elemente eines Arrays (und eines Containers) ◇ Zum sequentiellen Durchlaufen der Elemente eines Arrays wird üblicherweise die – auch aus C bekannte – (Standard-) for-Anweisung eingesetzt. Zum Elemente-Zugriff erfordert sie die explizite Definition und Verwendung einer "Laufvariablen". ◇ Beispiel : double[] da; // ... for (int i=0; i<da.length; i++) System.out.println(da[i]); ◇ Analoges gilt für das Durchlaufen eines Containers (in Java Collection genannt). Anstelle der Laufvariablen tritt hier ein Iterator-Objekt. • Vereinfachte for-Anweisung (Enhanced for Loop, For-Each-Schleife) ◇ Sie wurde mit dem JDK 5.0 zum sequentiellen Durchlaufen aller Elemente eines Arrays (bzw einer Collection) eingeführt ◇ Bei ihr wird auf die explizite Definition einer Laufvariablen (bzw eines Iterator-Objekts) verzichtet. ◇ Syntax : Array-Objekt for ( Element-Typ Var-Name : ) Anweisung Collection-Objekt ◇ Beispiel : double[] da; // ... for(double el : da) System.out.println(el); ◇ Wirkung : "Für jedes Element el in da " Der Element-Typ-Variablen el wird in jedem Schleifendurchlauf das jeweils nächste Element des Arrays da zugewiesen. ◇ Die Gültigkeit der Element-Typ-Variablen el ist auf den Schleifenrumpf begrenzt. ◇ Der vom Compiler erzeugte Code benötigt und verwaltet auch hier für den Elementzugriff eine Laufvariable (bzw ein Iterator-Objekt). Diese wird vom Compiler implizit erzeugt. • Anwendbarkeit der vereinfachten for-Anweisung ◇ Sie ist kein allgemeiner Ersatz für die (Standard-) for-Anweisung ◇ Sie kann nur zum sequentiellen Durchlaufen aller Elemente eines Arrays (bzw einer Collection) in Vorwärtsrichtung eingesetzt werden (vom ersten zum letzten Element, ohne Überspringen einzelner Elemente) ◇ Sie kann nicht eingesetzt werden, wenn ▻ ein Element aus dem Array (der Collection) entfernt oder modifiziert werden soll ▻ innerhalb einer Schleife mehrere Arrays (Collections) parallel bearbeitet werden sollen ◇ Ihr Einsatz in geschachtelten Schleifen ist jedoch möglich Beispiel : Durchlaufen eines zweidimensionalen Arrays double[][] gmat; // ... for (double[] row : gmat) for (double el : row) System.out.println(el); HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/06/6 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm zur vereinfachten for-Anweisung in Java • Quellcode des Programms (Klasse ForEachDemo) // ForEachDemo.java public class ForEachDemo { public void demo() { double[] da; da = new double[6]; for (int i=0; i<da.length; i++) da[i] = i; System.out.println("\ndouble-Array (eindimensional) : "); for(double el : da) System.out.println(el); String[] names = { "Walter", "Franz", "Ilka" }; System.out.println("\nString-Array : "); for (String str : names) System.out.println(str); System.out.println("\ndouble-Array (zweidimensional) :"); double[][] gmat = new double[4][]; for (int i=0; i<gmat.length; i++) gmat[i] = new double[i+1]; for (double[] row : gmat) { for (double el : row) System.out.print(el + " "); System.out.println(); } } public static void main(String[] args) { new ForEachDemo().demo(); } } • Ausgabe des Programms double-Array (eindimensional) : 0.0 1.0 2.0 3.0 4.0 5.0 String-Array : Walter Franz Ilka double-Array (zweidimensional) : 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/1 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse Object in Java (1) • "Mutter" aller Klassen ◇ In Java existiert nur eine Klassenhierarchie. Wurzel dieser Hierarchie ist die Klasse Object. Alle Klassen haben – direkt oder indirekt – diese Klasse als Basisklasse. Auch Klassen, die scheinbar nicht abgeleitet sind, d.h. die ohne Angabe einer Basisklasse definiert sind, sind tatsächlich implizit direkt von Object abgeleitet. ◇ Da eine Variable eines Klassen-Typs auch auf Objekte abgeleiteter Klassen zeigen kann (Polymorphie !), kann eine Variable vom Typ Object Objekte eines beliebigen Klassen-Typs (einschliesslich ArrayTyps) referieren. ◇ Die Klasse Object ist im Package java.lang enthalten. • Memberfunktionen der Klasse Object, Allgemeines ◇ Die Klasse Object definiert insgesamt elf Memberfunktionen, die wegen der Vererbung in allen Klassen zur Verfügung stehen. ◇ Sechs dieser Memberfunktionen sind nicht überschreibbar. Die übrigen lassen sich in abgeleiteten Klassen überschreiben. In Abhängigkeit von der jeweiligen Klasse kann für eine sinnvolle Funktionalität ein Überschreiben notwendig sein. ◇ Die Memberfunktionen von Object zerfallen in zwei Kategorien : - Allgemeine Utility-Funktionen (6) - Methoden zur Thread-Unterstützung (5) Hier werden nur 5 der 6 Utilility-Funktionen vorgestellt : ▻ Class<?> getClass() ▻ String toString() ▻ Object clone() ▻ boolean equals(Object obj) ▻ int hashCode() • Memberfunktionen der Klasse Object, Auswahl (1) public final Class<?> getClass() ▻ Diese – nicht überschreibbare – Funktion gibt eine Referenz auf das Objekt der Klasse Class zurück, das die tatsächliche Klasse des aktuellen Objekts repräsentiert ▻ Anmerkungen : 1. Jede Klasse (sowie jedes Interface, jeder einfacher Datentyp und void) wird durch ein Objekt der – generischen – Klasse Class repräsentiert. Ein Class-Objekt wird automatisch durch die JVM beim Laden der von ihm repräsentierten Klasse erzeugt. 2. Neben vielen anderen Informationen stellt ein Class-Objekt auch den – vollqualifizierten – Klassennamen der repräsentierten Klasse zur Verfügung. Dieser lässt sich mittels der folgenden Class-Memberfunktion ermitteln : public String getName() HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/2 – TH – 08 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse Object in Java (2) • Memberfunktionen der Klasse Object, Auswahl (2) public String toString() ▻ Diese Funktion ermöglicht es, dass jedes Objekt jeder beliebigen Klasse in einen String "umgewandelt" werden kann (z.B. bei der Konkatenation eines Objekts mit einem String) Sie liefert eine String-Repräsentation des aktuellen Objekts. ▻ Die Default-Implementierung in der Klasse Object erzeugt ein String-Objekt, dessen Inhalt aus dem – vollqualifizierten – Klassennamen des aktuellen Objekts, dem Zeichen '@' und der sedezimalen Darstellung des dem Objekt zugeordneten Hash-Codes (s. Methode hashCode()) zusammengesetzt ist. Bildung des Inhalts des erzeugten String-Objekts : getClass().getName() + '@' + Integer.toHexString(hashCode()) ▻ Soll eine andere das Objekt kennzeichnende String-Darstellung erzeugt werden, muss die Methode toString() geeignet überschrieben werden ▻ Die Methode toString() wird immer dann implizit aufgerufen, wenn eine Objekt-Referenz in einem String-Konkatenations-Ausdruck auftritt. protected Object clone() throws CloneNotSupportedException ▻ Die Funktion liefert ein neues Objekt, das ein Clone (eine Kopie) des aktuellen Objekts ist. ▻ Die tatsächliche Klasse des aktuellen Objekts muß das Interface Cloneable implementieren. ▻ Die Default-Implementierung in der Klasse Object prüft, ob für das aktuelle Objekt das Interface Cloneable implementiert ist. Ist das nicht der Fall, wird die Exception CloneNotSupportedException geworfen. Falls es implementiert ist, wird ein neues Objekt der Klasse erzeugt, dessen Datenkomponenten mit den Werten der entsprechenden Datenkomponenten des aktuellen Objekts initialisiert werden. ("flache" Kopie, shallow copy) ▻ Soll das neue Objekt als "tiefe" Kopie (deep copy) erzeugt werden, muss clone() geeignet überschrieben werden. clone() kann auch überschrieben werden, wenn nur eine "flache" Kopie erzeugt werden soll. Das ist insbesondere dann notwendig, wenn die Methode von überall her, also öffentlich (public), - und nicht nur aus dem gleichen Package oder aus abgeleiteten Klassen – aufgerufen werden soll. Die überschreibende Methode muss dann als public deklariert werden. ▻ Per Konvention sollte eine überschreibende Methode zunächst super.clone() aufrufen. Notwendige Änderungen an den Datenkomponenten des zurückgegebenen Objekts, die ObjektReferenzen sind (bei "tiefer" Kopie), sind anschließend durchzuführen. ▻ Wenn die überschreibende Methode super.clone() nicht aufruft, sondern das Kopieren der Datenkomponenten direkt vornimmt, muss weder die Klasse das Interface Cloneable implementieren noch muss die Exception CloneNotSupportedException beachtetet werden. ▻ Die Klasse Object selbst implementiert das Interface Cloneable nicht. Das bedeutet, dass der Aufruf von clone() für ein Objekt der Klasse Object zum Werfen der Exception CloneNotSupportedException führt. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/3 – TH – 07 -------------------------------------------------------------------------------------------------------------------------------------------------------- Die Klasse Object in Java (3) • Memberfunktionen der Klasse Object, Auswahl (3) public boolean equals(Object obj) ▻ Die Funktion vergleicht das aktuelle Objekt mit dem durch obj referierten Objekt. Sie liefert true als Funktionswert, wenn Gleichheit vorliegt, andernfalls false. ▻ Grundsätzlich ist die Funktion für eine Überprüfung auf Wert-Gleichheit der Objekte vorgesehen. ▻ Die Default-Implementierung in der Klasse Object geht davon aus, dass ein Objekt nur zu sich selbst gleich sein kann, d.h. sie setzt Wert-Gleichheit mit Referenz-Gleichheit (Identität) gleich. Sie liefert genau dann true, wenn obj das aktuelle Objekt referiert (obj==this). ▻ Für die Implementierung einer echten Wert-Gleichheit, die sich auf den Inhalt (Zustand) der referierten Objekte bezieht, muss die Methode in abgeleiteten Klassen geeignet überschrieben werden. Dies ist für zahlreiche Klassen der Standard-Bibliothek, u.a. auch für die Klasse String, erfolgt. Allgemein gilt : Ein Überschreiben ist für Value-Typen erforderlich, nicht jedoch für Entity-Typen public int hashCode() ▻ Die Funktion liefert einen Hash-Code für das aktuelle Objekt. Jedes Objekt besitzt einen derartigen Hash-Code. Er ermöglicht die Verwaltung von Objekten in hash-basierten Containern (z.B. java.util.Hashtable). ▻ Der Hash-Code eines Objekts darf sich während der Ausführung einer Java-Applikation nicht ändern. Bei unterschiedlichen Ausführungen derselben Applikation kann er dagegen unterschiedlich sein. ▻ Die Default-Implementierung in der Klasse Object liefert für unterschiedliche Objekte unterschiedliche Hash-Codes (Typischerweise ist dieser gleich der in einen int-Wert umgewandelten Speicheradresse des Objekts). ▻ Wenn für die zwei durch x und y referierten Objekte x.equals(y) den Wert true liefert, die beiden Objekte also inhaltlich gleich sind, muß der für die beiden Objekte erzeugte Hash-Code auch gleich sein (x.hashCode()==y.hashCode()) bei Überschreiben der Funktion equals() ist es daher i.a. auch notwendig die Funktion hashCode() entsprechend zu überschreiben. ▻ Wenn für die zwei durch x und y referierten Objekte x.equals(y) den Wert false liefert, die beiden Objekte also inhaltlich verschieden sind, darf der für die beiden Objekte erzeugte HashCode durchaus auch gleich sein. Dies bewirkt aber eine ineffizientere Verwaltung derartiger Objekte in hash-basierten Containern ▻ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/4 – TH – 06 -------------------------------------------------------------------------------------------------------------------------------------------------------- Überschreiben der Object-Methode equals() (1) • Allgemeines ◇ Die Methode equals() überprüft auf die inhaltliche Gleichheit (Wert-Gleichheit) zweier Objekte ◇ Ein Überschreiben ist nur für für Value-Typen erforderlich, für Entity-Typen macht eine Überprüfung auf Wert-Gleichheit i.a. keinen Sinn. • Kontrakt der Methode equals() Die Methode equals() sollte die folgenden Bedingungen erfüllen : ▻ Reflexivität : Der Vergleich eines Objekts mit sich selbst liefert immer true (Identität) ▻ Symmetrie : x.equals(y) liefert immer dasselbe Ergebnis wie y.equals(x) ▻ Transitivität : Wenn x.equals(y) true liefert und y.equals(z) true liefert, muss auch x.equals(z) true liefern ▻ Konsistenz : Sofern sich weder x noch y inhaltlich ändern, liefern mehrmalige Aufrufe von x.equals(y) immer dasselbe Ergebnis ▻ null-Verschiedenheit : Für jedes x!=null liefert x.equals(null) immer false • Implementierung von equals() ◇ Grundsätzlich ist nur ein Vergleich von Objekten derselben Klasse sinnnvoll (Typgleichheit). Zu beachten ist, dass das Objekt, mit dem das aktuelle Objekt verglichen werden soll, über eine ObjectReferenz übergeben wird. ◇ Prinzipiell müssen die Datenkomponenten der beiden Objekte auf Gleichheit überprüft werden. Dabei kann ein Objekt i.a. nur zu den Datenkomponenten, die in seiner Klasse direkt definiert sind, zugreifen. ◇ Ein Vergleich der von der Basisklasse geerbten Komponenten muss an diese delegiert werden ( Aufruf von super.equals()) ◇ Ein Aufruf von super.equals() muss in allen abgeleiteten Klassen erfolgen, außer in einer direkten Subklasse von Object. Unter einer direkten Subklasse von Object wird hier die erste Klasse in einer Ableitungs-Hierarchie verstanden, die equals() überschreibt. I.a. wird es eine direkt von Object abgeleitete Klasse sein. ◇ In einer direkten Subklasse von Object wird eine Überprüfung auf die null-Referenz als Parameter sowie auf Typgleichheit vorgenommen. ◇ Prinzipielle Darstellung der Implementierung : Überprüfung auf Identität (optional) direkte Subklasse indirekte Subklasse Überprüfung auf null-Referenz Delegierung an Basisklasse Überprüfung auf Typgleichheit Vergleich der Datenkomponenten HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/5 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Überschreiben der Object-Methode equals() (2) • Sourcecode einer Beispiel-Implementierung ◇ direkte Subklasse von Object public class MyClass { private String s1; private int i; // ... public boolean equals(Object other) { boolean bRet = false; if (this == other) bRet = true; else if (other != null) if (other.getClass() == getClass()) if (s1.equals(((MyClass)other).s1)) if (i == ((MyClass)other).i) bRet = true; return bRet; } } // Überprüfung auf Identität // // // // Überprüfung auf null-Referenz Überprüfung auf Typgleichheit Vergleich der Datenkomponenten ◇ indirekte Subklasse von Object public class MySubClass extends MyClass { private String s2; // ... public boolean equals(Object other) { boolean bRet = false; if (this == other) // Überprüfung auf Identität bRet = true; else if (super.equals(other)) // Delegierung an Basisklasse if (s2.equals(((MySubClass)other).s2)) // Vergleich der Datenkomponente bRet = true; return bRet; } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/6 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Überschreiben der Object-Methode hashCode() (1) • Allgemeines ◇ Ein Hashcode ist ein einem Objekt zugeordneter ganzzahliger Wert, der eine Speicherung und Wiederfinden von Objekten in hash-basierten Containern (z.B. Hash-Tabellen) ermöglicht. ◇ Ein hash-basierter Container besteht aus mehreren Sektionen ("Buckets"), in denen die Objekte sequentiell abgelegt werden (Für Java gilt : Es werden Objekt-Referenzen statt Objekte abgelegt) Der Hashcode legt einen Index fest, der zur Auswahl des Buckets dient, in dem das Objekt abgelegt wird. Die Auswahl des Buckets erfolgt für alle Objekte gleich schnell. Der Zugriff zu einem Objekt innerhalb des Buckets erfolgt dagegen sequentiell. Damit hängt die Zugriffszeit von der Position des Objekts innerhalb des Buckets ab. Daraus folgt, dass ein effizienter hash-basierter Container aus möglichst vielen, möglichst kleinen Buckets bestehen sollte. ◇ Die Methode hashCode() ermittelt den Hashcode eines Objekts und damit "dessen" Bucket. Beim Durchsuchen der sequentiell organisierten Buckets müssen Objekte miteinander inhaltlich verglichen werden. Hierfür wird equals() aufgerufen. Es wird immer die Kombination von hashCode() und equals() verwendet, um zu Objekten in hash-basierten Containern zuzugreifen. Die beiden Methoden müssen konsistent zueinander implementiert sein. Wenn equals() überschreiben wird, muss auch hashCode() passend hierzu überschrieben werden • Kontrakt der Methode hashCode() Die Methode hashCode() sollte die folgenden Bedingungen erfüllen : ▻ Während eines Programmlaufs muss der für ein Objekt ermittelte Hashcode immer derselbe sein, vorausgesetzt, der Inhalt des Objekts ändert sich nicht. Bei einem anderen Programmlauf darf der Hashcode ein anderer sein. ▻ Für zwei Objekte, die inhaltlich gleich sind (d.h. für die equals() true liefert), muss hashCode() denselben Wert erzeugen ▻ Für zwei Objekte, die inhaltlich verschieden sind (d.h. für die equals() false liefert), muss hashCode() nicht unbedingt verschiedene Werte liefern. (bei gleichen Werten : die Objekte kommen in denselben Bucket). Für die Performance wären aber unterschiedliche Hashcodes günstiger) • Implementierung von hashCode() ◇ Zwei wichtige Aspekte : ▻ Konsistenz zu equals() ▻ Performanz : Die Zugriffe zu hash-basierten Containern sollen möglichst effizient erfolgen Die Hashcode-Berechnung muss schnell und einfach erfolgen schneller Zugriff zum Bucket Die berechneten Hashcodes sollten möglichst gleichmäßig verteilt sein viele kleine Buckets ◇ Die Konsistenz zwischen hashCode() und equals() kann erreicht werden, indem zur Berechnung des Hashcodes nur die Informationen verwendet werden, die auch in der Implementierung von equals() berücksichtigt sind. Typischerweise werden in die Berechnung des Hashcodes nur die Datenkomponenten eingehen, die in equals() miteinander verglichen werden. Die Komponenten, die in equals() nicht vorkommen, dürfen auch nicht zur Berechnung des Hashcodes herangezogen werden. Allerdings müssen nicht alle in equals() verwendete Datenkomponenten auch tatsächlich in hashCode() berücksichtigt werden. Dies wird gegebenenfalls dazu führen, dass ungleiche Objekte den selben Hashcode haben werden, was ja nach dem Kontrakt zulässig ist. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/7 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Überschreiben der Object-Methode hashCode() (2) • Implementierung von hashCode(), Forts. ◇ Die Berechnung des Hashcodes darf nicht zu aufwendig sein, sonst dauert die Auswahl eines Buckets zu lange Der Geschwindigkeitsvorteil durch einen schnellen Bucket-Zugriff geht verloren. Daher kann es aus Performanz-Gründen durchaus sinnvoll sein, einige von equals() berücksichtigte Datenkomponenten bei der Hashcode-Berechnung wegzulassen. Z.B. werden bei einem Array als Datenkomponente zwar alle Array-Elemente zur Ermittlung der ObjektGleichheit beitragen, ihre Berücksichtigung in der Hashcode-Berechnung wäre aber bei größeren Arrays zu aufwendig. Hier bietet es sich an, nur jedes n-te Array-Element zu berücksichtigen. Weiterhin kann man Datenelemente weglassen, die bei den meisten Objekten den gleichen Wert haben werden. ◇ Implementierungsvorschlag : ▻ Identifikation der Datenkomponenten, die in die Hashcode-Berechnung eingehen sollen ▻ Zuordnung eines int-Wertes an jede Datenkomponente ( feldwertN) (s. nachfolgende Tabelle) ▻ Auswahl einer – als Multiplikator verwendeten – nicht zu großen Primzahl ( ▻ Festlegung eines von 0 verschiedenen Ausgangswertes für den Hashcode ( hashmult) hashcode0) ▻ Ausgehend von dem festgelegten Anfangswert Ermittlung des Hashcodes (hashcodeN) durch iterative Aufaddition gemäß folgender Beziehung : hashcodeN = hashcodeN-1 * hashmult + feldwertN ▻ Tabelle Zuordnung Datenkomponente (komp) – int-Wert Typ von komp boolean zugeordneter int-Wert (komp ? 0 : 1) byte, char, short, (int)komp int long (int)(komp^(komp>>>32)) float (komp==0.0F) ? 0 : Float.floatToIntBits(komp) double Zuordnung eines long-Wertes : (komp==0.0) ? 0L : Double.doubleToLongBits(komp) anschließend Behandlung des long-Wertes wie oben Objekt-Referenz (komp==null) ? 0 : komp.hashCode() Arrays Behandlung der zu berücksichtigen Elemente entsprechend ihres Typs Gesamtheit der Basisklassenkomponenten super.hashCode() ◇ hashCode() in Klassenhierarchien : ▻ in indirekten Subklassen : Berücksichtigung der Basisklassenkomponenten durch super.hashCode() ▻ in direkten Subklassen : kein Aufruf von super.hashCode() (ein Aufruf wäre ein grober Fehler) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/8 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Überschreiben der Object-Methode hashCode() (3) • Sourcecode einer Beispiel-Implementierung ◇ direkte Subklasse von Object public class MyClass { private String s1; private int i; // ... public int hashCode() { int hc = 13; // willkürlicher Anfangswert int hashMult = 37; // Primzahl als Multiplikator hc = hc*hashMult + i; hc = hc*hashMult + s1.hashCode(); return hc; } } ◇ indirekte Subklasse von Object public class MySubClass extends MyClass { private String s2; // ... public int hashCode() { int hc = 13; // willkürlicher Anfangswert int hashMult = 37; // Primzahl als Multiplikator hc = hc*hashMult + s2.hashCode() hc = hc*hashMult + super.hashCode(); return hc; } } ◇ Klasse mit Array als Datenkomponente public class MyArrClass { private Object arrField[]; // ... public int hashCode() { int hc = 13; // willkürlicher Anfangswert int hashMult = 37; // Primzahl als Multiplikator // ... hc = hc*hashMult + arrField.length; // Beruecksichtigung der Array-Laenge for (int i=0; i<arrField.length; i*=4) // Beruecksichtigung nur hc = hc*hashMult + arrField[i].hashCode() // jedes 4. Array-Elements return hc; } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/9 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm zu Memberfunktionen der Klasse Object (1) • Quellcode der Klasse ObjDemo public class ObjDemo implements Cloneable { private String name; public ObjDemo(String str) { name=str; } public String toString() { return super.toString()+" ("+name+") "+ super.hashCode(); } public boolean equals(Object other) { boolean bRet = false; if (this == other) bRet = true; else if (other != null) if (other.getClass() == getClass()) if (name.equals(((ObjDemo)other).name)) bRet = true; return bRet; } public int hashCode() { int hc = 13; int hashMult = 37; hc = hc*hashMult + name.hashCode(); return hc; } // Überprüfung auf Identität // Überprüfung auf null-Referenz // Überprüfung auf Typgleichheit // Vergleich der Datenkomponente // willkürlicher Anfangswert // Primzahl als Multiplikator public static void main(String[] args) throws Exception { ObjDemo o1 = new ObjDemo("Test1"); Object o2 = new ObjDemo("Test2"); Object o3 = new Object(); System.out.println("Klassenname von o1 : " + o1.getClass().getName()); System.out.println("Klassenname von o2 : " + o2.getClass().getName()); System.out.println("Klassenname von o3 : " + o3.getClass().getName()); System.out.println("o1.toString() : " + o1.toString()); System.out.println("o2 : " + o2); System.out.println("o2.equals(o1) : " + o2.equals(o1)); System.out.println("o1.hashCode() : " + o1.hashCode()); System.out.println("o2.hashCode() : " + o2.hashCode()); o2=o1; System.out.println("\nnach o2=o1 :"); System.out.println("o1 : " + o1); System.out.println("o2 : " + o2); System.out.println("o2.equals(o1) : " + o2.equals(o1)); System.out.println("o1.hashCode() : " + o1.hashCode()); System.out.println("o2.hashCode() : " + o2.hashCode()); o2=o1.clone(); System.out.println("\nnach o2=o1.clone() :"); System.out.println("o1 : " + o1); System.out.println("o2 : " + o2); System.out.println("o2.equals(o1) : " + o2.equals(o1)); System.out.println("o1.hashCode() : " + o1.hashCode()); System.out.println("o2.hashCode() : " + o2.hashCode()); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/07/10 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Demonstrationsprogramm zu Memberfunktionen der Klasse Object (2) • Ausgabe des Programms ohne überschreibende Methoden equals() und hashCode() (beide Methoden sind im Quellcode der Klasse auskommentiert) Klassenname von o1 : ObjDemo Klassenname von o2 : ObjDemo Klassenname von o3 : java.lang.Object o1.toString() o2 o2.equals(o1) o1.hashCode() o2.hashCode() : : : : : ObjDemo@173f7175 (Test1) 390033781 ObjDemo@4631c43f (Test2) 1177666623 false 390033781 1177666623 nach o2=o1 : o1 o2 o2.equals(o1) o1.hashCode() o2.hashCode() : : : : : ObjDemo@173f7175 (Test1) 390033781 ObjDemo@173f7175 (Test1) 390033781 true 390033781 390033781 nach o2=o1.clone() : o1 : ObjDemo@173f7175 (Test1) 390033781 o2 : ObjDemo@6d4b2819 (Test1) 1833642009 o2.equals(o1) : false o1.hashCode() : 390033781 o2.hashCode() : 1833642009 /* 390033781 == 0x173f7175 */ /* 1177666623 == 0x4631c43f */ /* Hashcode == Adresse */ /* Adressen sind gleich */ /* 1833642009 == 0x6d4b2819 */ /* Inhalte sind gleich, aber */ /* Adressen sind ungleich */ • Ausgabe des Programms mit überschreibenden Methoden equals() und hashCode() Klassenname von o1 : ObjDemo Klassenname von o2 : ObjDemo Klassenname von o3 : java.lang.Object o1.toString() o2 o2.equals(o1) o1.hashCode() o2.hashCode() : : : : : ObjDemo@4cf5fa0 (Test1) 390033781 ObjDemo@4cf5fa1 (Test2) 1177666623 false 80699296 80699297 nach o2=o1 : o1 o2 o2.equals(o1) o1.hashCode() o2.hashCode() : : : : : ObjDemo@4cf5fa0 (Test1) 390033781 ObjDemo@4cf5fa0 (Test1) 390033781 true 80699296 80699296 nach o2=o1.clone() : o1 : ObjDemo@4cf5fa0 (Test1) 390033781 o2 : ObjDemo@4cf5fa0 (Test1) 1833642009 o2.equals(o1) : true o1.hashCode() : 80699296 o2.hashCode() : 80699296 /* /* /* /* 4cf5fa0 ist der Hashcode 390033781 ist Adresse Hashcode inhaltsabhängig 80699296 == 0x4cf5fa0 */ */ */ */ /* Adressen sind gleich */ /* Inhalte sind gleich, aber */ /* Adressen sind ungleich */ HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/08/1 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel : Klasse Ratio (1) • Klasse zur Darstellung rationaler Zahlen (hier nur Implementierung einer Teilfunktionalität) • Definition der Klasse Ratio : // Ratio.java public class Ratio implements Cloneable { final private int iZaehl; final private int iNenn; public Ratio(int iZ, int iN) { if(iN>0) { iZaehl = iZ; iNenn = iN; } else if (iN<0) { iZaehl = -iZ; iNenn = -iN; } else { iNenn = 1; iZaehl = 0; } } // Konstruktor 1 public Ratio(int iZ) { this(iZ,1); } // Konstruktor 2 - ganze Zahl public Ratio() { this(0); } // Konstruktor 3 - ohne Parameter // Vorzeichen nur im Zaehler // Throw exception !! spaeter public Ratio add(Ratio sP) { int iZ = sP.iZaehl*iNenn + sP.iNenn*iZaehl; int iN = sP.iNenn*iNenn; return new Ratio(iZ, iN); } private int iGgTeil() { int iP,iQ,iS; int iGgt; int locZ; if(iZaehl >= 0) locZ = iZaehl; else locZ = -iZaehl; if(locZ > iNenn) { iP = locZ; iQ = iNenn; } else { iP = iNenn; iQ = locZ; } do { iS = iP%iQ; iP = iQ; iQ = iS; } while(iS>0); iGgt = iP; return iGgt; } // // // int iKgViel() { return ( iZaehl * } // groesster gemeinsamer Teiler // Zaehler und Nenner positiv - Vorzeichen ! // groessere der beiden Zahlen ermitteln // Fortgesetzte Division bis Rest gleich 0 // ergibt ggT // kleinstes gem Vielfaches - nicht benoetigt iNenn / iGgTeil()); HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/08/2 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel : Klasse Ratio (2) • Definition der Klasse Ratio, Forts.: public Ratio kuerze() { int iZw,iZ,iN; iZw = iGgTeil(); iZ = iZaehl / iZw; iN = iNenn / iZw; return new Ratio(iZ,iN); } public String toString() { return new String(iZaehl + "/" + iNenn); } public String toMixedString() { int iGanz = iZaehl/iNenn; int iBruch = Math.abs(iZaehl)%iNenn; Ratio lRat = new Ratio(iBruch,iNenn); return new String (iGanz + " " + lRat); } public boolean equals(Object other) { boolean bRet = false; if (this == other) bRet = true; else if (other != null) if (other.getClass() == getClass()) if ((iZaehl==((Ratio)other).iZaehl) && (iNenn == ((Ratio)other).iNenn)) bRet = true; return bRet; } public int hashCode() { int hc = 13; int hashMult = 37; hc = hc*hashMult + iZaehl; hc = hc*hashMult + iNenn; return hc; } //Achtung Vorzeichen // Überprüfung auf Identität // // // // Überprüfung auf null-Referenz Überprüfung auf Typgleichheit Vergleich der Datenkomponenten // willkürlicher Anfangswert // Primzahl als Multiplikator public Object clone() // hier : Erweiterung der Zugriffsberechtigung !!! { try {return super.clone();} catch (CloneNotSupportedException e) {return null;} } /* public Object clone() // oder : public Ratio clone(); { return new Ratio(iZaehl, iNenn); } */ } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 12/08/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Beispiel : Klasse Ratio (3) • Definition der Demonstrations- und Test-Klasse RatioDemo : // RatioDemo.java // Test der Klasse Ratio public class RatioDemo { public static void main(String[] args) { Ratio rFirst = new Ratio(150,60); Ratio rSecnd = new Ratio(28,84); Ratio rThird; System.out.println("First: "+rFirst); System.out.println("Secnd: "+rSecnd); rFirst = rFirst.kuerze(); rSecnd = rSecnd.kuerze(); System.out.println("First: "+rFirst); System.out.println("Secnd: "+rSecnd); rThird = rFirst.add(rSecnd); System.out.print(rFirst+" + "+rSecnd+" = "+rThird); System.out.println(" = " + rThird.toMixedString()); rFirst = new Ratio(27, 99); rSecnd = (Ratio)rFirst.clone(); // Typecast nicht notwendig, wenn System.out.println(); if (rFirst.equals(rSecnd)) System.out.println(rFirst + " ist gleich " + rSecnd); else System.out.println(rFirst + " ist ungleich " + rSecnd); System.out.println("Hashcode von " + rFirst + " : " + rFirst.hashCode()); System.out.println("Hashcode von " + rSecnd + " : " + rSecnd.hashCode()); rSecnd = rFirst.kuerze(); System.out.println(); if (rFirst.equals(rSecnd)) System.out.println(rFirst + " ist gleich " + rSecnd); else System.out.println(rFirst + " ist ungleich " + rSecnd); System.out.println("Hashcode von " + rFirst + " : " + rFirst.hashCode()); System.out.println("Hashcode von " + rSecnd + " : " + rSecnd.hashCode()); } } Programmausgabe : First: 150/60 Secnd: 28/84 First: 5/2 Secnd: 1/3 5/2 + 1/3 = 17/6 = 2 5/6 27/99 ist gleich 27/99 Hashcode von 27/99 : 18895 Hashcode von 27/99 : 18895 27/99 ist ungleich 3/11 Hashcode von 27/99 : 18895 Hashcode von 3/11 : 17919 public Ratio clone() HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/00/0 – TH – 02 ------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 13. Elementare Programmfunktionalitäten in Java 13.1. Zugriff zu Programmparametern 13.2. Standard-Ein-und-Ausgabe (Konsolen-E/A) 13.3. Exceptions 13.4. Dateibearbeitung HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/01/1 – TH – 01 -------------------------------------------------------------------------------------------------------------------------------------------------------- Zugriff zu Programmparametern in Java • Programparameter (Kommandozeilenparameter) ◇ Einem Programm können i.a. beim Aufruf Parameter übergeben werden. Programmparameter ◇ Diese werden beim Aufruf aus der Kommandozeile durch Blanks getrennt an den eigentlichen Programmaufruf angehängt. Kommandozeilenparameter Bei Java-Programmen werden sie nach dem Namen der Start-Klasse angegeben. ◇ Beispiel : java Echo Sie tanzte nur einen Sommer Kommandozeilenparameter sind : Sie tanzte nur einen Sommer • Zugriff im Programm ◇ Die Programmparameter (ohne Namen der Startklasse !) werden in einem String-Array zusammengefasst. Eine Referenz auf dieses Array wird der main()-Methode der Startklasse als Parameter übergeben. ◇ Die main()-Methode der Startklasse muß daher mit einem Parameter (Argument) vom Typ String[] definiert werden : public static void main(String[] args) { // ... } ◇ Innerhalb der main()-Methode stehen damit die Programmparameter als Komponenten des StringArrays args zur Verfügung. ◇ Die Anzahl der Programmparameter (= Länge des String-Arrays) ist ermittelbar mittels args.length. • Beispiel : // Echo.java class Echo { public static void main(String[] args) { for (int i=0; i<args.length; i++) System.out.println("Parameter "+ i + " : " + args[i]); } } Beispiel eines Programm-Aufrufs : E:\java\fhm\ee\vorl>java Echo Sie tanzte nur einen Sommer Parameter Parameter Parameter Parameter Parameter 0 1 2 3 4 : : : : : Sie tanzte nur einen Sommer E:\java\fhm\ee\vorl> HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/1 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (1) • Grundsätzliches zum I/O-Model von Java ◇ Eine Programm-Ein- und Ausgabe findet üblicherweise über Dateien und/oder Geräte statt. ◇ Java betrachtet sowohl Geräte als auch Dateien grundsätzlich als sequentielle Folge von Bytes (Byte-Stream). Da Java Zeichen und Strings im Unicode darstellt, bietet die Sprache auch die Möglichkeit Textdateien/ Text-Geräte als sequentielle Folge von Unicode-Zeichen (Zeichen-Stream) zu interpretieren. ◇ Programmintern wird eine Datei bzw ein Gerät durch ein Objekt einer Stream-Klasse repräsentiert. ◇ Es gibt zahlreiche verschiedene Stream-Klassen, die jeweils unterschiedliche Eigenschaften modellieren. Alle Stream-Klassen sind im Package java.io enthalten. • Standard-Ein- und Ausgabe-Objekte ◇ In den Programmen der meisten Programmiersprachen (z.B. auch in C-Programmen) werden automatisch Zugriffsmöglichkeiten zu den Standard-Ein- und Ausgabegeräten (üblicherweise Tastatur und Bildschirm des Konsolengeräts) bereitgestellt. Diese können sofort nach Programmstart ohne weitere Maßnahmen eingesetzt werden. ◇ Auch in Java ist dies der Fall : In jedem Java-Programm werden automatisch Stream-Objekte angelegt, die einen Zugriff zur StandardEingabe, Standard-Ausgabe und Standard-Fehlerausgabe ermöglichen. Die entsprechenden Streams sind implizit geöffnet. Aus historischen Gründen handelt es sich bei diesen Stream-Objekten um Byte-Stream-Objekte. ◇ Referenzen auf diese Stream-Objekte stehen als öffentliche statische Datenkomponenten der – nicht instanzierbaren – Klasse System (im Package java.lang) zur Verfügung . Es handelt sich um Referenz-Konstante. Sie müssen zusammen mit dem Klassennamen System verwendet werden. ▻ public static final InputStream in Standard-Eingabe-Objekt Referierung mittels System.in ▻ public static final PrintStream out Standard-Ausgabe-Objekt Referierung mittels System.out ▻ public static final PrintStream err Standard-Fehlerausgabe-Objekt Referierung mittels System.err ◇ Ab den JDK 6.0 existiert eine – allerdings nicht immer verfügbare – alternative Möglichkeit zum Zugriff zur Standard-Ein- und Ausgabe zur Verfügung : Wenn ein Java-Programm aus der Kommandozeile (ohne Ein-/Ausgabe-Umleitung) gestartet wird, wird implizit durch die JVM ein Objekt der Klasse Console erzeugt. (nicht jedoch beim Start aus einer IDE). Eine Referenz auf dieses Objekt kann mit einer statischen Methode der Klasse System ermittelt werden : public static Console console() Die Klasse Console (Package java.io) stellt u.a. Methoden zum Schreiben in das sowie Lesen aus dem Konsolengerät (== Standard-Eingabe + Standard-Ausgabe) zur Verfügung. Genaueres s. API-Dokumentation. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (2) • Schreiben in die Standard-Ausgabe und Standard-Fehlerausgabe ◇ Die durch system.out (Standard-Ausgabe) und system.err (Standard-Fehlerausgabe) referierten Stream-Objekte sind Instanzen der Klasse PrintStream (Package java.io). Diese Klasse stellt die folgenden Methoden zum Schreiben zur Verfügung : public void print(...) public void println(...) ▻ Beide Methoden sind mehrfach überladen und haben jeweils einen Parameter, der dem jeweiligen Typ des auszugebenden Werts bzw Objekts entspricht. Sie ermöglichen die – nicht explizit formatierbare – Ausgabe von - von boolean-, char-, double-, float-, int- und long-Werten - von char-Arrays - von Strings (Objekte der Klasse String) - sowie der String-Repräsentation von Objekten beliebiger Klassen (impliziter Aufruf der Objekt-Methode toString()) ▻ Die Anwendung der String-Konkatenation in Verbindung mit der automatischen Umwandlung von beliebigen Datenwerten und Objekten in eine String-Repräsentation bei ihrem Auftritt in KonkatenationsAusdrücken (Methode toString()) erlaubt die Ausgabe mehrerer Werte/Objekte mit einem Methodenaufruf. ▻ Die Methode println() ergänzt die Ausgabe um ein Zeilenendezeichen. Sie lässt sich auch ohne Parameter aufrufen. In diesem Fall bewirkt sie lediglich einen Übergang in eine neue Zeile. public PrintStream printf(String form, Object... args) (ab dem JDK 5.0) ▻ Diese Methode ermöglicht eine – C-ähnliche – formatierte Ausgabe (s. gesonderte Beschreibung) ▻ Ihr Rückgabewert ist eine Referenz auf das aktuelle PrintStream-Objekt, für das sie aufgerufen wird. ◇ Beispiel zur Anwendung von print(...) und println(...) : class StdOutDemo1 { public void demo() { boolean b = true; char c = 'Z'; int i = 399127; long l = 124L; float f = 2.25E-2f; double d = 0.0/0.0; String s = "Hallo !"; System.out.print(b); System.out.println(); System.out.println("Zeichen : " +c); System.out.println(i); System.out.println(i + " " + l); System.out.println(f); System.out.println(d); System.out.println(s); System.out.println(this); } public static void main(String[] args) { new StdOutDemo().demo(); } } Ausgabe : true Zeichen : Z 399127 399127 124 0.0225 NaN Hallo ! StdOutDemo1@3e25a5 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (3) • Formatierte Ausgabe (ab dem JDK 5.0) ◇ Mittels der PrintStream-Methode public PrintStream printf(String form, Object... args); ◇ Die Methode ermöglicht eine C-ähnliche formatierte Ausgabe ◇ Der erste Parameter form ist der Format-String. Er enthält – analog zum Format-String der C-Funktion printf() – die einzelnen Formatangaben (format specifier) für die auszugebenden Werte/Objekte. Durch die Formatangaben wird auch der Typ bzw die Darstellungsart der Ausgabe-Werte festgelegt Zusätzlich kann der Format-String weiteren Text enthalten, der direkt ausgegeben wird. ◇ Die zweite Parameter-Angabe Object... args bedeutet, dass eine beliebige Anzahl (auch keine) weiterer Parameter beliebigen Referenz-Typs folgen darf. Diese Parameter (Argumente) legen die Werte fest, die entsprechend des jeweils spezifizierten Formats auszugeben sind. Für jede Formatangabe muss ein passendes Argument übergeben werden. Die Typen dieser Argumente müssen zu den entsprechenden Angaben im Format-String passen. Überflüssige Argumente werden ignoriert Als Parameter können auch Werte einfacher Datentypen auftreten, da sie mittels Autoboxing automatisch in Objekte der zugehörigen Wrapper-Klassen umgewandelt werden. ◇ Syntax der Formatangaben (vereinfacht) : % [flags][width][.precision]conversion ▻ conversion (Konvertierungszeichen) legt die Formatierungsart und/oder den Typ der Ausgabe fest und schließt eine Formatangabe ab Die wichtigsten Konvertierungszeichen sind : b d o x c e f g oder B oder X oder C oder E oder G s oder S n logischer Wert ganzzahliger Wert in Dezimaldarstellung ganzzahliger Wert in Oktaldarstellung ganzzahliger Wert in Sedezimaldarstellung Unicode-Zeichen Gleitpunkt-Wert in Exponentialdarstellung Gleitpunkt-Wert in Dezimalbruchdarstellung Gleitpunkt-Wert in Dezimalbruchdarstellung oder Exponentialdarstellung (abhängig von Wert und Genauigkeit) String (der String, der durch die jeweilige Methode toString() erzeugt wird) implementierungsabhängige Darstellung des Zeilenendes Bei einem Großbuchstaben als Konvertierungszeichen : Ausgabe aller Buchstaben als Großbuchstaben ▻ flags (Steuerflags) modifizieren das Ausgabeformat, sie können gegebenenfalls miteinander kombiniert werden Die wichtigsten Steuerflags sind : + 0 ' ' (Blank) linksbündige Ausgabe auch positive Werte werden mit Vorzeichen ausgegeben (nur für Zahlen anwendbar) Ausgabe führender Nullen (nur für Zahlen anwendbar) Ausgabe positiver Werte mit führendem Blank (nur für Zahlen anwendbar) ▻ width (Ausgabefeldbreite) legt die minimale Ausgabefeldbreite fest ▻ precision (Genauigkeit) legt i.a. die maximale Ausgabefeldbreite fest, bei Gleitpunktzahlen jedoch die Anzahl der Nachpunktstellen (Ausnahme : beim Konvertierungszeichen g bzw G wird die Gesamtzahl der Ziffern festgelegt), bei Strings : Anzahl der auszugebenden Zeichen des Strings HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/4 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (4) • Formatierte Ausgabe (ab dem JDK 5.0), Forts. ◇ Nichtzulässige Zeichen in einer Formatangabe sowie fehlende oder zu einer Formatangabe nichtkompatible Argumente führen zum Werfen einer IllegalFormatException. • Demonstrationsprogramm zur formatierten Ausgabe : // FormOutpDemo.java // Demonstrationsprogramm zur formatierten Ausgabe public class FormOutpDemo { void show() { int anz = 10; double summe = 99.98765; float anteil = 0.00005432f; String type = "Airbus A-380"; System.out.println("0123456789012345678901234567890123456789"); System.out.printf("Anzahl : %5d Summe : %8.3e\n", anz, summe); System.out.printf("Anzahl : %-5d Summe : %8.3f\n", anz, summe); System.out.printf("Anzahl : %0+5d Summe : %08.3f\n", anz, summe); System.out.printf("Anteil : % .4g\n", anteil); System.out.printf("Anteil : %.4E\n", anteil); System.out.printf("Anteil : %.4f\n", anteil); System.out.printf("Typ : %s\n", type); System.out.printf("Typ : %15s\n", type); System.out.printf("Typ : %5s\n", type); System.out.printf("Typ : %5.6S\n", type); System.out.printf("Typ : %8.6s\n", type); System.out.printf("%B\n", true); } public static void main(String[] args) { FormOutpDemo1 demo = new FormOutpDemo(); demo.show(); } Ausgabe des Programms : 0123456789012345678901234567890123456789 Anzahl : 10 Summe : 9.999e+01 Anzahl : 10 Summe : 99,988 Anzahl : +0010 Summe : 0099,988 Anteil : 5.432e-05 Anteil : 5.4320E-05 Anteil : 0,0001 Typ : Airbus A-380 Typ : Airbus A-380 Typ : Airbus A-380 Typ : AIRBUS Typ : Airbus TRUE HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/5 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (5) • Alternativen zur formatierten Ausgabe ◇ 1. Erzeugung eines formatierten Strings mittels einer statischen Memberfunktion der Klasse String (ab JDK 5.0) : public static String format(String form, Object... args) ▻ Bedeutung der Parameter wie bei der PrintStream-Methode printf(...) 2. Ausgabe dieses Strings mittels der PrintStream-Methoden print(...) bzw println(...) Beispiel : int anz = 10; double summe = 99.98765; String s; s = String.format("Anzahl : %5d System.out.println(s); Summe : %8.3e\n", anz, summe); ◇ Verwendung der Klasse Formatter (Package java.util). ● Verwendung der Klasse Formatter zur formatierten Ausgabe ◇ Objekte der Klasse Formatter ermöglichen die Ausgabe formatierter Strings in ein mit Ihnen verbundenes Ausgabe-(Ziel-)Objekt. Das Ziel-Objekt eines Formatter-Objekts ist dessen Konstruktor als Parameter zu übergeben. ◇ Ziel-Objekt eines Formatter-Objekts kann u.a. auch das Standard-Ausgabe-Objekt sein. Erzeugung eines Formatter-Objekts für die Standard-Ausgabe : Formatter stdoutform = new Formatter(System.out); ◇ Mittels der Formatter-Objekt-Methode public Formatter format(String form, Object... args); lässt sich ein formatierter String erzeugen und in das Ziel-Objekt, hier also in das Standard-Ausgabe-Objekt, ausgeben. ▻ Bedeutung der Parameter wie bei der PrintStream-Methode printf(...) ◇ Beispiel : int anz = 10; double summe = 99.98765; Formatter stdoutform = new Formatter(System.out); stdoutform.format("Anzahl : %5d Summe : %8.3e\n", anz, summe); HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/6 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (6) • Lesen aus der Standard-Eingabe ◇ Das durch system.in referierte Stream-Objekt für die Standard-Eingabe steht als Instanz der Klasse InputStream (Package java.io) zur Verfügung. Diese Klasse besitzt zum Einlesen lediglich die mehrfach überladene Methode read(), mit der nur einzelne Bytes oder eine Gruppe von Bytes (Byte-Array) eingelesen werden können. public int read() throws IOEception ▻ Einlesen eines Bytes ▻ Rückgabewert : gelesenes Byte zu int ergänzt, bzw -1 falls Eingabeende erreicht ist public int read(byte[] b) throws IOEception ▻ Einlesen einer Bytefolge in das als Parameter übergebene Array b Es werden maximal b.length Bytes gelesen ▻ Rückgabewert : Anzahl der gelesenen Bytes, bzw -1 falls Eingabeende erreicht ist ◇ Sofern die über die Standard-Eingabe (Tastatur) eingegebenen Zeichen jeweils nur aus einem Byte bestehen – was bei dem bei uns üblichen Zeichensatz i.a. der Fall ist – können mit diesen Methoden einzelne Zeichen eingelesen werden. ◇ Üblicherweise soll von der Standard-Eingabe aber aus mehreren Zeichen bestehender Text eingelesen werden, der vom Programm entweder als Wert eines einfachen Datentyps oder direkt als String interpretiert und verwendet werden soll. D.h. also, es müssen Zeichenfolgen (Datentyp char) und nicht Bytefolgen (Datentyp Byte) eingelesen und in interne Wertdarstellungen umgewandelt werden. ◇ Um dies effektiv zu realisieren, muß das Objekt System.in mit Objekten anderer Klassen zusammenarbeiten. Hierfür existieren mehrere Möglichkeiten. Die einfachste – allerdings erst ab dem JDK 5.0 vorhandene – Möglichkeit besteht im Einsatz eines Objekts der Klasse Scanner (Package java.util). • Allgemeines zur Klasse Scanner ◇ Objekte der Klasse Scanner dienen zum Zergliedern und Interpretieren von Zeichenfolgen. ◇ Die von Scanner-Objekten bearbeitbaren Zeichenfolgen können aus unterschiedlichen Eingabe-Quellen – auch aus Bytestreams – stammen. (Dateien, sonstige Eingabe-Streams wie z.B. die Standard-Eingabe, Strings). Das jeweilige Quell-Objekt muss beim Erzeugen eines Scanner-Objekts festgelegt werden ( Konstruktor-Parameter). ◇ Scanner-Objekte zerlegen ihre Eingabe-Zeichenfolge in Teil-Abschnitte (token). Die Zeichen(-muster), die als Trennzeichen zwischen den Abschnitten interpretiert werden (delimiter pattern), lassen sich konfigurieren. Defaultmässig werden Whitespace-Character verwendet. ◇ Die einzelnen Teil-Abschnitte stehen als Strings zur Verfügung und können als Werte eines einfachen Datentyps interpretiert und in die dem jeweiligen Typ entsprechende interne Darstellung umgewandelt werden. Damit lassen sich Scanner-Objekte sehr elegant zum Einlesen von Werten der einfachen Datentypen aus der Standard-Eingabe einsetzen, insbesondere auch dann, wenn in einer Eingabezeile mehrere einzulesende Werte enthalten sind. ◇ Scanner-Objekte ermöglichen auch das zeilenweise Lesen. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/7 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (7) • Konstruktoren der Klasse Scanner (Auswahl) public Scanner(InputStream source) Erzeugung eines Scanner-Objekts, dessen Eingabe-Quelle das durch source spezifizierte InputStream-Objekt ist public Scanner(File source) Erzeugung eines Scanner-Objekts, dessen Eingabe-Quelle die durch source spezifizierte Datei ist public Scanner(String source) Erzeugung eines Scanner-Objekts, dessen Eingabe-Quelle der durch source spezifizierte String ist. • Erzeugung eines Scanner-Objekts für die Standard-Eingabe Scanner stdinscan = new Scanner(System.in); • Memberfunktionen der Klasse Scanner (Auswahl) public String nextLine() Rückgabe des Rests der aktuellen Eingabe-Zeile Wenn die Bearbeitungsposition sich unmittelbar vor dem Anfang der nächsten Zeile befindet, wird die gesamte nächste Zeile zurückgegeben public boolean hasNextLine() Überprüfung, ob eine weitere Eingabe-Zeile vorhanden ist Rückgabe von true wenn ja, sonst von false public String next() Rückgabe des nächsten Teil-Abschnitts (token) der Eingabe-Zeichenfolge public boolean hasNext() Überprüfung ob ein weiterer Teil-Abschnitt vorhanden ist Rückgabe von true wenn ja, sonst von false public boolean hasNextByte() public boolean hasNextShort() public boolean hasNextInt() public boolean hasNextLong() public boolean hasNextFloat() public boolean hasNextDouble() Überprüfung, ob der nächste Teilabschnitt sich als Wert des jeweiligen Typs interpretieren lässt. (boolean-Werten können mit Groß- und/oder Kleinbuchstaben dargestellt werden) Rückgabe von true wenn ja, sonst von false Die Methoden geben auch false zurück, wenn kein weiterer Teil-Abschnitt mehr vorhanden ist (d.h. das Eingabe-Ende erreicht ist) Achtung : Der Dezimal-Punkt bei Float- und Double-Werten muss für das in Deutschland standardmäßig verwendete Default-Locale-Objekt bei der Eingabe durch ein Dezimal-Komma ersetzt werden, änderbar durch Setzen eines anderen Locale-Objekts (s. unten) public boolean hasNextBoolean() public byte nextByte() public short nextShort() public int nextInt() public long nextLong() public float nextFloat() public double nextDouble() Interpretation des nächsten Teil-Abschnitts der Eingabe-Zeichenfolge als Wert des jeweiligen Typs und Rückgabe der internen Darstellung dieses Wertes Jede der Funktionen wirft eine Exception vom Typ - InputMismatchException, wenn der Teil-Abschnitt sich nicht als Wert des jeweiligen Typs interpretieren lässt (boolean-Werte können mit Groß- und/oder Kleinbuchstaben dargestellt werden) - NoSuchElementException, wenn das Ende der Zeichenfolge, d.h. bei Dateien und Geräten das StreamEnde, erreicht ist (z.B. Eingabe des Dateiendezeichens) public boolean nextBoolean() public Scanner useLocale (Locale loc) Setzen des vom Scanner-Objekt verwendeten Locale-Objekts (Locale-Objekte repräsentieren regionale Besonderheiten/Darstellungen) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/02/8 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Standard-Ein- und Ausgabe in Java (8) • Demonstrationsbeispiele zur Klasse Scanner ◇ Programm EchoLinesScanDemo // EchoLinesScanDemo.java // Echo der von der Standard-Eingabe eingelesenen Zeilen in die Standard-Ausgabe // Verwendung der Klase Scanner import java.util.*; public class EchoLinesScanDemo { public static void main(String[] args) { Scanner stdinscan = new Scanner(System.in); while (stdinscan.hasNextLine()) System.out.println(stdinscan.nextLine()); } } ◇ Programm StdInpScanDemo // StdInpScanDemo.java // Demonstrationsprogramm zum Einlesen von der Standard-Eingabe // unter Verwendung der Klasse Scanner import java.util.*; public class StdInpScanDemo { public void demo() { int a, b; Scanner stdinscan = new Scanner(System.in); stdinscan.useLocale(Locale.ROOT); System.out.print("Zwei Integer-Werte a und b ? "); a = stdinscan.nextInt(); b = stdinscan.nextInt(); System.out.println("a+b = " + (a + b)); System.out.print("Boolean-Wert ? "); boolean bw = stdinscan.nextBoolean(); System.out.println("Eingabe war : " + bw); System.out.print("Double-Wert ? "); if (stdinscan.hasNextDouble()) { double d = stdinscan.nextDouble(); System.out.println("Double-Wert : " + d); } else System.out.printf("keine oder falsche Eingabe"); } public static void main(String[] args) { new StdInpScanDemo().demo(); } } ◇ Beispiel für die Ein- und Ausgabe des Programms StdInpScanDemo Zwei Integer-Werte a und b ? 12 25 a+b = 37 Boolean-Wert ? FalsE Eingabe war : false Double-Wert ? 4.5 Double-Wert : 4.5 HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/03/1 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Exceptions in Java (1) • Prinzip des Exception Handlings ◇ Java stellt – wie auch z.B. C++ – einen speziellen Mechanismus zur Behandlung von Ausnahmesituationen (Fehlerfällen, Exceptions), die während der Programmausführung auftreten können, zur Verfügung ⇒ Exception Handling ◇ Ausnahmesituationen in diesem Sinne sind Fehler oder sonstige unerwünschte Sonderfälle (z.B. Dateizugriffsfehler, Verletzung von Array-Grenzen, Datenformatfehler usw), die im normalen Programmablauf nicht auftreten sollten, aber auftreten können. Exception Handling ist kein Mechanismus zur Behandlung von externen oder internen Interrupts. ◇ Exception Handling trennt den Code, der Fehler verursachen kann, von dem Code, der einen aufgetretenen Fehler behandelt. Dadurch kann der eigentliche den normalen Programmablauf steuernde (produktive) Code frei gehalten werden von einer ständigen Überprüfung auf den Auftritt von Fehlern und Sonderfällen sowie der Reaktion auf diese. Dies trägt erheblich zur Klarheit und Übersichtlichkeit des Codes bei. ◇ Code (z.B. eine Methode einer Bibliotheksklasse), der eine Ausnahme- (Fehler-)situation entdeckt, wirft eine Exception, die dann von einem anderen Code-Teil, dem Exception-Handler gefangen wird. Im Exception-Handler findet die Reaktion auf die (Bearbeitung der) Ausnahmesituation statt. Diese kann der Fehlerart sowie der jeweiligen spezifischen Programmsituation angepasst werden. Z.B. kann eine Fehlermeldung ausgegeben werden und anschliessend das Programm fortgesetzt oder beendet werden. Oder es kann versucht werden, die Fehlerursache zu beseitigen und das Programm dann fortzusetzen. ◇ Exceptions werden als Objekte behandelt. Sie enthalten Informationen über die Fehlerursache und stellen Methoden zum Zugriff zu diesen zur Verfügung. Werfen einer Exception (throwing) bedeutet das Erzeugen eines Objekts einer Exception-Klasse und die anschliessende Suche eines passenden Exception-Handlers. Wird ein derartiger Handler gefunden, wird ihm das Exception-Objekt (genauer eine Referenz darauf) übergeben ( Fangen der Exception, catching). Dem Handler stehen damit die im Exception-Objekt enthaltenen Informationen zur Auswertung zur Verfügung. produktiver Code Werfen einer Exception Fehlersituation ExceptionObjekt Fangen der Exception Exception-Handler (Fehlerbehandlung) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/03/2 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Exceptions in Java (2) ● Fangen einer Exception ◇ Es gibt verschiedene Exception-Klassen. Ein Exeption-Handler kann immer nur Exception-Objekte ganz bestimmter Klassen fangen. ◇ Nach dem Werfen einer Exception muss ein passender Exception-Handler gesucht werden, der diese fangen kann. Der Exception-Handler kann sich befinden : ▻ unmittelbar nach dem produktiven (überwachten) Code-Block ( try-Anweisung) ▻ in einem übergeordneten Block ▻ in einer aufrufenden Funktion ▻ in der JVM Die Suche erfolgt von "innen nach außen". ◇ Exception-Objekte enthalten i.a. Informationen über die Ursache bzw eine Kurzbeschreibung der Exception, die in ihrem Konstruktor durch einen String festgelegt werden (direkt codiert oder als Parameter). Der Exception-Handler kann zu diesen Informationen mittels entsprechende Objekt-Methoden zugreifen. ◇ Nach der Ausführung des Exception-Handlers wird das Programm mit der dem Handler nachfolgenden Anweisung fortgesetzt. Keine Rückkehr zum Ort des Fehlerauftritts • Sehr umfangreiche und strikte Anwendung des Exception Handlings in Java : ◇ In der Standard-Bibliothek sind zahlreiche Exception-Klassen definiert. Sie bilden eine über mehrere Pakete verteilte Klassenhierarchie, die die Klasse Throwable (von Object abgeleitet, Paket java.lang) als Wurzelklasse besitzt. Die Exception-Ursache bzw eine Kurzbeschreibung der Exception wird i.a. im Konstruktor der jeweiligen Klasse durch einen String (fest codiert oder als Parameter übergeben) festgelegt. ◇ Methoden vieler Bibliotheksklassen werfen Exceptions. ◇ Java erzwingt, dass geworfene Exceptions gefangen werden müssen. Existiert in einem Benutzerprogramm kein geeigneter Exception-Handler, so werden sie von einem in der JVM angesiedelten Standard-Handler behandelt, der Informationen über die Exception in die Standard-Fehler-Ausgabe ausgibt und anschliessend das Programm (genauer den aktuellen Thread) beendet. ◇ Ein Benutzer kann jederzeit eigene Exception-Klassen definieren. Dies kann sinnvoll sein, wenn spezielle Fehlerfälle gesondert von anderen behandelt werden sollen (z.B. "Division durch Null" als Spezialfall einer ArithmeticException). Eine beutzerdefinierte Exception-Klasse muß von einer der Standard-Bibliotheks-Exception-Klassen abgeleitet sein. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/03/3 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Exceptions in Java (3) • Hierarchie der Bibliotheks-Exception-Klassen ◇ Alle Exception-Klassen sind – direkt oder indirekt – von der Klasse Throwable abgeleitet. ◇ Die Klasse Throwable (Paket java.lang) ist eine direkte Unterklasse von Object. ◇ Überblick über den Anfang der Hierarchie : Throwable Error Exception ungeprüfte Exceptions VirtualMachineError weitere Error-Klassen ArithmeticException geprüfte Exceptions RuntimeException IOException weitere RuntimeExceptionKlassen FileNotFoundException weitere ExceptionKlassen weitere IOExceptionKlassen ◇ Exceptions der Klasse Error sowie der davon abgeleiteten Klassen kennzeichnen ernsthafte Systemfehler, die normalerweise nicht auftreten sollten, aber prinzipiell jederzeit auftreten können. Ein normales Benutzerprogramm kann auf derartige Exceptions i.a. nicht sinnvoll reagieren und sollte sie deswegen auch nicht fangen. Vielmehr sollten sie immer vom Default-Exception-Handler der JVM behandelt werden. ◇ Exceptions der Klasse RuntimeException sowie der davon abgeleiteten Klassen können ebenfalls prinzipiell an beliebiger Stelle während des Programmablaufs auftreten. I.a. liegt ihre Ursache in einem logischen Programmierfehler (z.B. Division durch 0 oder unzulässiger Array-Index). Eine vernünftige Reaktion zur Laufzeit ist i.a. nicht möglich. Derartige Fehler sollten i.a. im Programm-Code korrigiert werden. Ein Benutzerprogramm kann diese Exceptions behandeln (d.h. geeignete Exception-Handler bereitstellen), muß es aber nicht. Im letzteren Fall erfolgt wiederum eine Behandlung durch den Default-Handler der JVM. ◇ Exceptions aller übrigen Klassen (z.B. IOException) repräsentieren Fehler, mit denen man prinzipiell rechnen muß (z.B. Datei ist nicht vorhanden) und die deswegen auch in irgendeiner Weise behandelt werden müssen. Für diese Exceptions schreibt Java eine "catch-or-declare"-Erfordernis vor. Sie müssen von der Funktion, in der sie geworfen werden, entweder gefangen oder von ihr als weiter-werfbar deklariert werden. Andererseits darf eine Funktion nur solche Exceptions dieser Klassen werfen, die sie auch deklariert hat. Das Einhalten dieser Erfordernis kann vom Compiler überprüft werden. Sie werden daher als geprüfte (checked) Exceptions bezeichnet. ◇ Exceptions der Klassen Error und RuntimeException sowie der davon abgeleiteten Klassen sind dagegen ungeprüfte (unchecked) Exceptions. Sie können jederzeit geworfen werden, ohne dass sie von der entsprechenden Funktion deklariert werden müssen. Für sie gilt die "catch-or-declare"-Erfordernis nicht, sie dürfen aber deklariert werden HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/03/4 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Exceptions in Java (4) • Die Klasse Throwable ◇ Die direkt von der Klasse Object abgeleitete Klasse Throwable (Paket java.lang) ist Basisklasse aller Exception-Klassen. ◇ Sie definiert ein allgemeines Interface für den Zugriff zu den Exception-Objekten aller Exception-Klassen Die vier wichtigsten Methoden dieses Interfaces sind : public String getMessage() ▻ Diese Methode gibt den bei der Exception-Objekt-Erzeugung gespeicherten Ursachen- bzw KurzbeschreibungsString als Funktionswert zurück, bzw null, falls kein derartiger String gespeichert wurde. public String toString() ▻ Diese Methode erzeugt einen String, der aus dem vollqualifizierten Namen der tatsächlichen Klasse des ExceptionObjekts besteht, gegebenenfalls (falls getMessage() einen Wert !=null zurückgibt) gefolgt von ": " und dem Rückgabewert von getMessage(). public void printStackTrace() ▻ Diese Methode gibt den Funktionswert von toString() gefolgt von dem Aufruf-Stack der Funktion, in der die Exception ursprünglich geworfen wurde, in die Standard-Fehler-Ausgabe (System.err) aus. public void printStackTrace(PrintStream s) ▻ Diese Methode gibt den Funktionswert von toString() gefolgt von dem Aufruf-Stack der Funktion, in der die Exception ursprünglich geworfen wurde, in den durch s referierten Print-Stream aus. • Deklaration werfbarer Exceptions (throws-Klausel) ◇ Die von einer Funktion werfbaren geprüften Exceptions müssen von dieser deklariert werden. Werfbare ungeprüfte Exceptions können, müssen aber nicht deklariert werden. ◇ Die Deklaration erfolgt durch eine im Funktionskopf nach der Parameterliste anzugebende throwsKlausel. Die throws-Klausel besteht aus dem Schlüsselwort throws, gefolgt von einer durch Kommata getrennten Auflistung von Exception-Klassen ◇ Syntax : throws Exception-Klasse , ◇ Beispiel : public String liesEtwas(String dname) throws FileNotFoundException, EOFException { // ... } • Werfen einer Exception – throw-Anweisung throw Exception-Objekt-Referenz ; ◇ Nach dem Schlüsselwort throw ist eine Referenz auf das zu werfende Exception-Objekt anzugeben. Diese Referenz kann ▻ der Wert eines new-Ausdrucks sein (neu erzeugtes Exception-Objekt) ▻ die einem Exception-Handler übergebene Objekt-Referenz sein ("Weiterwerfen" der Exception) ◇ Beispiel : throw new ArithmeticException("Nenner ist Null"); HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/03/5 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Exceptions in Java (5) • Die try-Anweisung ◇ Sie ermöglicht das Überwachen und das Fangen von Exceptions. ◇ Eine try-Anweisung besteht i.a. aus drei Teilen : ▻ dem try-Block ▻ einem oder mehreren catch-Blöcken ▻ einem finally-Block Entweder die catch-Blöcke oder der finally-Block dürfen auch fehlen, aber nicht beides. ◇ Syntax : try { catch ( finally Anweisung Exception-Klasse } Name { Anweisung } { Anweisung } ) ◇ Der try-Block enthält den eigentlichen produktiven Code, der auf den Auftritt von Exceptions überwacht wird. Er wird solange ausgeführt, bis entweder durch eine der Anweisungen eine Exception (direkt durch eine throw-Anweisung oder indirekt durch eine aufgerufene Funktion) geworfen wird oder alle Anweisungen erfolgreich abgearbeitet worden sind. Beim Auftritt einer Exception wird der try-Block sofort verlassen und nach einem Exception-Handler gesucht, der die Exception fangen kann. ◇ Die catch-Blöcke bilden die Exception-Handler. Jeder catch-Block ist zuständig für das Fangen von Exceptions einer bestimmten Klasse und deren Unterklassen. Die von einem catch-Block fangbare Exception-Klasse ist in seinem Kopf – analog zur Parameter-Deklaration von Funktionen – angegeben. Da Objekte (genauer Objekt-Referenzen) einer Klasse zuweisungskompatibel zu Variablen einer Basisklasse sind, können auch Exceptions aller Klassen, die von der deklarierten Klasse abgeleitet sind, gefangen werden. Die Suche nach einem "passenden" Exception-Handler erfolgt in der Reihenfolge seiner Angabe im Quellcode. Wird ein Exception-Handler gefunden, der die geworfene Exception fangen kann, wird das Programm mit dessen Abarbeitung fortgesetzt. Dabei wird die Exception wie ein formaler Funktionsparameter an den Handler übergeben. Wird kein "passender" Exception-Handler gefunden, wird die Suche in der jeweils nächsten umfassenden try-Anweisung (gegebenenfalls in der aufrufenden Funktion) fortgesetzt. Bleibt auch das erfolglos, wird der Default-Exception-Handler der JVM ausgeführt und das Programm beendet. ◇ Der finally-Block enthält Code, der immer ausgeführt wird, unabhängig davon, wie die try-Anweisung verlassen wird, ob durch reguläre Beendigung des try-Blocks oder durch Werfen einer gefangenen oder nicht gefangenen Exception. Die Ausführung des finally-Blocks erfolgt unmittelbar vor Verlassen des try-Blocks bzw – beim Fangen einer Exception – vor Verlassen des entsprechenden catch-Blocks. Die Ausführung erfolgt auch dann, wenn try- bzw catch-Block mit einer return-Anweisung verlassen werden. Der finally-Block wird insbesondere verwendet, um Aufräumarbeiten durchzuführen und sicherzustellen, dass belegte System-Resourcen (z.B. geöffnete Dateien) freigegeben werden HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/03/6 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Exceptions in Java (6) • Demonstrationsprogramm zu Exceptions // ExceptDemo.java import java.io.*; class ExceptDemo { public void func1() { try { System.out.println("in func1() : Aufruf von func2()"); func2(); System.out.println("normales Ende von try in func1()!"); return; } finally { System.out.println("func1() verlassen !"); } } public void func2() { try { System.out.println("in func2() : Aufruf von func3()"); func3(); System.out.println("normales Ende von try in func2()!"); return; } catch (RuntimeException e) { System.out.print("Exception-Handler in func2() : "); System.out.println(e.getMessage()); return; } finally { System.out.println("func2() verlassen !"); } } public void func3() { try { System.out.println("in func3() : Exception wird geworfen"); throw new RuntimeException("Demo-Exception !"); } catch (ArithmeticException e) { System.out.print("Exception-Handler in func3() : "); System.out.println(e.getMessage()); } finally { System.out.println("func3() verlassen !"); } } public static void main(String[] args) { new ExceptDemo().func1(); } } in func1() : Aufruf von func2() in func2() : Aufruf von func3() in func3() : Exception wird geworfen func3() verlassen ! Exception-Handler in func2() : Demo-Exception ! func2() verlassen ! normales Ende von try in func1()! func1() verlassen ! HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/03/7 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Exceptions in Java (7) • Demonstrationsprogramm ExeptDemo : Veranschaulichung des Programmablaufs func1() try func2() func2() return finally try func3() func3() return try catch (RuntimeException) throw RuntimeException return finally ? catch (ArithmeticException) finally HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/1 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java - Überblick • Überblick ◇ Java behandelt Dateien als sequentielle Folge von Bytes (Byte-Streams). ◇ In der IO-Bibliothek (Package java.io) sind zahlreiche Klassen definiert, die das Arbeiten mit Dateien unterstützen. Die wichtigsten dieser Klassen sind : ▻ die File-Stream-Klassen zum sequentiellen Zugriff • FileInputStream (byteweises Lesen) • FileOutputStream (byteweises Schreiben) • FileReader (zeichenweises Lesen) • FileWriter (zeichenweises Schreiben) ▻ eine Klasse zum wahlfreien Zugriff (Lesen und Schreiben) • RandomAccessFile ▻ Klassen für den Zugriff zum Dateisystem • FileDescriptor (Handle zur Datei-Verwaltungsstruktur des Betriebssystems, hier nicht betrachtet) • File ▻ Zahlreiche weitere Stream-Klassen (meist Filter-Klassen) ermöglichen einen spezialisierten Dateizugriff ◇ Auszugsweises Klassendiagramm : Anmerkungen : ▻ Das Diagramm beschränkt sich auf die Datei-Eingabe-Stream-Klassen (FileInputStream, FileReader). ▻ Für die Datei-Ausgabe-Stream-Klassen (FileOutputStream, FileWriter) bestehen analoge Ableitungs-und Nutzungsbeziehungen. Deren – direkte bzw indirekte – Basisklassen sind OutputStream bzw Writer. ▻ Die IO-Bibliothek enthält eine Reihe weiterer – im Diagramm nicht aufgeführter – Stream-Klassen, die zumeist von InputStream oder Reader (bzw OutputStream oder Writer) direkt oder indirekt abgeleitet sind. InputStream Reader FilterInputStream BufferedInputStream DataInputStream InputStreamReader FileInputStream FileReader BufferedReader RandomAccessFile FileDescriptor File ◇ Eine Bearbeitung von Textdateien ist auch möglich mit den Klassen Formatter und Scanner (Package java.util) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse File (1) • Die Klasse File ◇ Objekte dieser Klasse repräsentieren Zugriffspfade (zu Dateien bzw Directories) im Dateisystem. ◇ Wesentliche Funktionalitäten : ▻ Speicherung eines Datei- bzw Directory-Zugriffspfades in einer abstrakten system-unabhängigen Darstellung, sowie Bereitstellung von Informationen über denselben ▻ Ermittlung diverser Eigenschaften einer repräsentierten Datei (bzw eines repräsentierten Directories) ▻ Manipulation von Dateisystem-Einträgen (Erzeugen, Löschen, Änderung bestimmter Eigenschaften) ◇ Der durch ein File-Objekt repräsentierte Zugriffspfad muss nicht einen tatsächlich existierenden Dateisystem-Eintrag referieren. ◇ Konstruktoren der Klasse File (Auswahl) : public File(String path) ▻ Erzeugung eines neuen File-Objekts, das den Zugriffspfad path repräsentiert. ▻ Falls path==null ist, wird eine NullPointerException geworfen. public File(String dirName, String name) ▻ Erzeugung eines neuen File-Objekts, das den aus dirName und name gebildeten Zugriffspfad repräsentiert. (Eintrag name im Directory dirName). ▻ Äquivalent zu : File(dirName + File.separator + name) ▻ Falls name==null ist, wird eine NullPointerException geworfen. public File(File fileDir, String name) ▻ Erzeugung eines neuen File-Objekts, das den Zugriffspfad repräsentiert, der aus dem Eintrag name in dem durch fileDir repräsentierten Directory gebildet wird, ▻ Äquivalent zu : File(fileDir.getPath(), name) ▻ Falls name==null ist, wird eine NullPointerException geworfen. ◇ Methoden zum Erzeugen neuer Dateisystem-Einträge (Auswahl) public boolean createNewFile() throws IOException ▻ Diese Methode erzeugt eine neue leere Datei mit dem vom aktuellen File-Objekt repräsentierten Zugriffspfad, falls noch keine Datei mit diesem Zugriffspfad existiert. ▻ Funktionswert : true, falls die Datei erzeugt werden konnte false,falls eine Datei mit dem Zugriffspfad bereits existiert hat. ▻ Falls ein I/O-Fehler auftritt, wird eine IOException geworfen. public boolean mkdir() ▻ Diese Methode erzeugt ein neues Directory mit dem vom aktuellen File-Objekt repräsentierten Zugriffspfad ▻ Funktionswert : true, falls das Directory erzeugt werden konnte, andernfalls false HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/3 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse File (2) • Die Klasse File, Forts ◇ Methoden zur Veränderung von Dateisystem-Einträgen public boolean renameTo(File newPath) ▻ Umbenennen / Verschieben des aktuell repräsentierten Dateisystem-Eintrags. ▻ Der Dateisystem-Eintrag wird anschliessend durch das File-Objekt newPath repräsentiert. Der vom aktuellen Objekt bisher repräsentierte Eintrag existiert anschliessend nicht mehr. ▻ Funktionswert : true, falls Umbenennen / Verschieben erfolgreich war, andernfalls false ▻ Falls newPath==null ist, wird eine NullPointerException geworfen. public boolean delete() ▻ Löschen des aktuell repräsentierten Dateisystem-Eintrags. Ein zu löschendes Directory muss leer sein. ▻ Funktionswert : true, falls erfolgreich gelöscht werden konnte, andernfalls false ◇ Methoden zur Ermittlung von Informationen über Dateisystem-Einträge (Auswahl) public boolean exists() ▻ Überprüfung, ob durch das File-Objekt ein tatsächlich existierender Eintrag repräsentiert wird ▻ Funktionswert : true, wenn der Eintrag existiert, sonst false public boolean isDirectory() ▻ Funktionswert : true, wenn der Eintrag existiert und ein Directory ist, sonst false public boolean isFile() ▻ Funktionswert : true, wenn der Eintrag existiert und eine normale Datei ist, sonst false public boolean canWrite() ▻ Funktionswert : true, wenn Eintrag existiert und geändert werden kann, sonst false public boolean canRead() ▻ Funktionswert : true, wenn Eintrag existiert und gelesen werden kann, sonst false public String[] list() ▻ Funktionswert : Wenn Eintrag ein Directory ist : Rückgabe einer Auflistung aller in ihm enthaltenen Einträge als String-Array (ausser "." und "..") Wenn Eintrag kein Directory ist : null public long length() ▻ Funktionswert : Dateilänge, wenn Eintrag Datei ist, bzw. undefiniert, wenn Eintrag Directory ist public String getPath() public String getAbsolutePath() ▻ Funktionswert : Zugriffspfad des repräsentierten Dateisystem-Eintrags (wie im Konstruktor angegeben) bzw absoluter Zugriffspfad (gegebenenfalls mittels aktuellem Arbeitsdir. ermittelt) HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/4 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse File (3) • Demoprogramm zur Klasse File // ListDir.java import java.io.*; public class ListDir { public static void main(String[] args) { try { if (args.length<=0) throw new IOException("Programmparameter (Directory-Pfad) fehlt !"); File dir = new File(args[0]); if (!dir.exists()) throw new IOException("\"" + args[0] + "\" existiert nicht !"); if (!dir.isDirectory()) throw new IOException("\"" + dir.getPath() + "\" ist kein Directory !"); String[] entries = dir.list(); System.out.println("Inhalt des Directories \"" + dir.getAbsolutePath() + "\" :"); for (int i=0; i<entries.length; i++) System.out.println(entries[i]); } catch (IOException ex) { System.out.println(ex.getMessage()); } } } Beispiele für Programmaufruf und -ausgabe E:\Java\fhm\ee\vorl\fileacc>java ListDir Programmparameter (Directory-Pfad) fehlt ! E:\Java\fhm\ee\vorl\fileacc>java ListDir meindir "meindir" existiert nicht ! E:\Java\fhm\ee\vorl\fileacc>java ListDir ftestdir "ftestdir" ist kein Directory ! Inhalt des Directories "E:\Java\Vorl_Prog\elemprogfunc\." : FormOutpDemo.java Echo.java StdOutDemo1.java EchoLinesScanDemo.java StdInpScanDemo.java package cache StdOutDemo1.class FormOutpDemo.class Echo.class formoutpdemo.txt EchoLinesScanDemo.class StdInpScanDemo.class ExceptDemo.java ExceptDemo.class StdInpCharDemo.class ListDir.java ftestdir ListDir.class HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/5 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse File (4) • Verwendung von File-Objekten für den Dateizugriff ◇ File-Objekte können Konstruktor-Parameter der File-Stream-Klassen sein. Dateizugriff mittels der damit erzeugten File-Stream-Objekte ◇ File-Objekte können auch Konstruktor-Parameter der Klasse RandomAccessFile sein. Dateizugriff mittels der damit erzeugten RandomAccessFile-Objekte (wahlfreier Dateizugriff) ◇ File-Objekte können auch Konstruktor-Parameter der Klassen ▻ Scanner und ▻ Formatter sein formatiertes Schreiben (Klasse Formatter) sowie Lesen (Klasse Scanner) in/aus Textdateien • Formatiertes Schreiben in Textdateien mittels der Klasse Formatter (Package java.util) Öffnen der Datei : beim Erzeugen eines Formatter-Objekts durch den Konstruktor Formatter(File fil) throws FileNotFoundException z.B. : Formatter fdat = new Formatter(new File("dat.txt"); formatiertes Schreiben in die Datei : mit Objekt-Methode format() public Formatter format(String form, Object... args); Bedeutung der Parameter : wie bei der PrintStream-Methode printf() z.B. : fdat.format("%4d", 127); Schließen der Datei : mit Objekt-Methode close() public void close() z.B. fdat.close(); • Formatiertes Lesen aus Textdateien mittels der Klasse Scanner (Package java.util) Öffnen der Datei : beim Erzeugen eines Scanner-Objekts durch den Konstruktor Scanner(File fil) throws FileNotFoundException z.B. : Scanner sdat = new Scanner(new File("dat.txt"); formatiertes Lesen aus der Datei : mit entsprechenden Objekt-Methoden der Klasse Scanner (s. Standard-Eingabe) z.B. public int nextInt(); public double nextDouble(); usw. z.B. : int zahl = sdat.nextInt(); Schließen der Datei : mit Objekt-Methode close() public void close() z.B. sdat.close(); HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/6 – TH – 10 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Formatiertes Schreiben und Lesen • Demoprogramm zur Verwendung der Klassen Formatter und Scanner // FormTextFileDemo1.java import java.io.*; import java.util.*; public class FormTextFileDemo1 { private String dpfad; public FormTextFileDemo1(String str) { dpfad = str; } public void formOutput() throws IOException { Formatter fdat = new Formatter(new File(dpfad)); Random rand = new Random(); for (int i=1; i<=8; ++i) { int anz = 1 + rand.nextInt(6); fdat.format("%d : ", anz); double d ; for (int j=1; j<=anz; ++j) { d = rand.nextDouble(); fdat.format("%.5f ", d); } fdat.format("%n"); } fdat.close(); } public void formInput() throws IOException { Scanner sdat = new Scanner(new File(dpfad)); try { while (sdat.hasNext()) { int anz = sdat.nextInt(); System.out.printf("%d : ", anz); sdat.next(); // Ueberlesen des ':' double d; for (int i = 1; i <= anz; ++i) { d = sdat.nextDouble(); System.out.printf("%.5f ", d); } System.out.printf("%n"); } } catch (InputMismatchException ex) { System.err.println("Type Mismatch beim Lesen."); } sdat.close(); } public static void main(String[] args) { Locale.setDefault(Locale.ROOT); try { if (args.length <= 0) throw new IOException("Programmparameter (Datei-Pfad) fehlt !"); FormTextFileDemo1 demo = new FormTextFileDemo1(args[0]); demo.formOutput(); demo.formInput(); } catch (IOException ex) { System.err.println(ex.getMessage()); } } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/7 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – File-Stream-Klassen (1) • Sequentieller Dateizugriff mittels der File-Stream-Klassen ◇ Klassen für den byteweisen sequentiellen Dateizugriff : ▻ FileInputStream (byteweises Lesen) ▻ FileOutputStream (byteweises Schreiben) ◇ Klassen für den zeichenweisen sequentiellen Dateizugriff : ▻ FileReader (zeichenweises Lesen) ▻ FileWriter (zeichenweises Schreiben) ◇ Überblick über die Konstruktoren (Auswahl) : 1.Parameter 2. Parameter String path Dateizugriffspfad ------File fil repräsentiert Dateizugriffspfad String path Dateizugriffspfad File fil repräsentiert Dateizugriffspfad boolean app wenn true : Anhängen an Datei (append) wenn false : Schreiben am Dateianfang existiert für Klasse FileInputStream FileOutputStream FileReader FileWriter FileOutputStream FileWriter Beispiele : public FileInputStream(File fil) throws FileNotFoundException public FileWriter(String path, boolean app) throws IOException ● Bei der Erzeugung eines File-Stream-Objekts wird die referierte Datei – falls sie vorhanden und die jeweilige Zugriffsart zulässig ist – geöffnet ● Die Konstruktoren der Datei-Ausgabe-Stream-Klassen versuchen die referierte Datei neu anzulegen, falls sie nicht existiert. ● Kann eine Datei nicht geöffnet werden (z.B. weil der Zugriffspfad ein Directory referiert), wird eine FileNotFoundException (bzw bei der Klasse FileWriter eine IOException) geworfen. ◇ Schliessen eines File-Streams mittels der für alle File-Stream-Klassen existierenden Methode : public void close() throws IOException ▻ Die Methode schliesst den File-Stream und damit die referierte Datei und gibt alle sonstigen mit dem Stream assoziierten System-Resourcen frei. ▻ Beim Auftritt eines I/O-Fehlers wird eine IOException geworfen. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/8 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – File-Stream-Klassen (2) • Sequentieller Dateizugriff mittels der File-Stream-Klassen, Forts. ◇ Methoden zum Dateizugriff (Auswahl) ● Alle folgenden Methoden werfen eine IOException falls ein I/O-Fehler auftritt ● Schreiben in Dateien (Klassen FileOutputStream / FileWriter) public void write(int bc) throws IOException ▻ Schreiben des Bytes bc (FileOutputStream) bzw des Zeichens bc (FileWriter) public void write(byte[] buff, int pos, int len) throws IOException public void write(char[] buff, int pos, int len) throws IOException ▻ Schreiben von len Bytes (FileOutputStream) bzw von len Zeichen (FileWriter) aus buff ab Position pos public void write(String str) throws IOException // nur FileWriter ▻ Schreiben des Strings str public void flush() throws IOException ▻ Herausschreiben aller Schreib-Buffer (Übergabe an das Betriebssystem) ● Lesen aus Dateien (Klassen FileInputStream / FileReader) public int read() throws IOException ▻ Lesen eines Bytes (FileInputStream) bzw eines Zeichens (FileReader) ▻ Funktionswert : gelesenes Byte bzw gelesenes Zeichen bzw -1 falls das Dateiende erreicht ist public int read(byte[] buff) throws IOException public int read(char[] buff) throws IOException ▻ Lesen einer Byte-Folge (FileInputStream) bzw einer Zeichen-Folge (FileReader) ▻ Ablage der gelesenen Byte- bzw Zeichen-Folge im Buffer buff ▻ Funktionswert : Anzahl gelesener Bytes bzw Anzahl gelesener Zeichen bzw -1 falls das Dateiende erreicht ist public long skip(long n) throws IOException ▻ Überlesen der nächsten n Bytes (FileInputStream) bzw der nächsten n Zeichen (FileReader) ● Es existieren noch weitere Methoden, mit denen auch Teile eines Byte- bzw Zeichen-Arrays gelesen bzw Teile eines Strings geschrieben werden können. ◇ Durch die Zusammenarbeit mit geeigneten Filterklassen lassen sich spezialisiertere Formen der DateiEin-/Ausgabe realisieren. Beispiel : Die Zusammenarbeit von FileInputStream mit DataInputStream sowie FileOutputStream mit DataOutputStream ermöglicht das Lesen und Schreiben von Daten in Binärdarstellung. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/9 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – File-Stream-Klassen (3) • Demoprogramm zum sequentiellen Dateizugriff (Kopieren einer Datei) // FileCopy.java import java.io.*; public class FileCopy { public void copy(String inpath, String outpath) { try { File ifile = new File(inpath); FileInputStream in = new FileInputStream(ifile); FileOutputStream out = new FileOutputStream(outpath); long fsize = ifile.length(); System.out.print("Kopiert wird \"" + inpath+ "\" (Laenge : "); System.out.println(fsize + " Bytes) nach \"" + outpath+ "\""); byte[] buff = new byte[512]; int len; while ((len=in.read(buff))>=0) { out.write(buff, 0, len); } out.close(); in.close(); System.out.println("Kopieren der Dateien erfolgreich"); } catch(IOException ex) { System.out.println("Exception : " + ex.getMessage()); } } public static void main(String[] args) { if (args.length <2) System.out.println("Aufruf : \"java FileCopy quelldatei zieldatei\""); else new FileCopy().copy(args[0],args[1]); } } Beispiel für Programmaufruf und -ausgabe E:\Java\fhm\ee\vorl\fileacc>java FileCopy ListDir.class ListDir.bin Kopiert wird "ListDir.class" (Laenge : 1100 Bytes) nach "ListDir.bin" Kopieren der Dateien erfolgreich HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/10 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse RandomAccessFile (1) • Wahlfreier Zugriff zu Dateien – Klasse RandomAccessFile ◇ Den wahlfreien Zugriff zu Dateien ermöglicht die Klasse RandomAccessFile. Diese Klasse ist von keiner der File-Stream-Klassen sondern direkt von Object abgeleitet. ◇ Objekte dieser Klasse ermöglichen einen wahlfreien Lese- bzw Schreibzugriff zu Dateien. Der wahlfreie Zugriff wird durch die Möglichkeit zur expliziten Veränderung der aktuellen Bearbeitungsposition (File-Pointer, nicht zu verwechseln mit dem File-Pointer in C, der referiert eine Datei) realisiert. ◇ Öffnen der Datei : durch den Konstruktor bei der Erzeugung eines RandomAccessFile-Objekts Dateibearbeitung : Aufruf von Memberfunktionen für das RandomAccessFile-Objekt. ◇ Hierfür stellt die Klasse zahlreiche Methoden – auch für spezialisierte Dateizugriffe – bereit : ▻ Lesen und Schreiben von Bytes und Bytefolgen (Byte-Arrays) ▻ Schreiben von Strings als Byte-Folge, Zeichenfolge und in UTF-8-Codierung ▻ zeilenweises Lesen (Textdateien !) ▻ Lesen von Strings in modifizierter UTF-8-Codierung ▻ Lesen und Schreiben von Werten der einfachen Datentypen in interner Binärdarstellung (Binärdateien !) ◇ Anmerkung zur modifizierten UTF-8-Codierung : ▻ UTF = Universal Transfer Format : effiziente Codierung von Unicode-Zeichen durch 1, 2 und 3 Bytes ▻ Zeichen zwischen '\u0001' und '\u007F' durch ein Byte : 01 ... 7F Zeichen zwischen '\u0080' und '\u07FF' durch zwei Bytes : C2 80 ... DF BF Zeichen zwischen '\u0800' und '\uFFFF' durch drei Bytes : E0 A0 80 ... EF BF BF der NUL-Character (das Zeichen '\u0000') durch zwei Bytes : C0 80 ▻ Vor der eigentlichen den String codierenden Bytefolge wird deren Länge als short-Wert (2 Bytes) in den Stream geschrieben. ◇ Konstruktoren der Klasse RandomAccessFile public RandomAccessFile(String path, String mode) throws FileNotFoundException public RandomAccessFile(File fil, String mode) throws FileNotFoundException ▻ Der erste Parameter referiert die zu bearbeitende Datei (über den Zugriffspfad bzw ein FileObjekt, das diesen repräsentiert) ▻ Der zweite Parameter legt die Zugriffsart fest : - "r" nur Lesen, - "rw" Lesen und Schreiben. ("rws" und "rwd" sind auch zulässig) ▻ Die Konstruktoren versuchen die referierte Datei für die angegebene Zugriffsart zu öffnen. ▻ Falls die referierte Datei nicht existiert wird bei der Zugriffsart "rw" versucht, sie zu erzeugen. ▻ Die Konstruktoren können die folgenden Exceptions werfen : - FileNotFoundException, falls die referierte Datei nicht existiert (Zugriffsart "r") oder ein Directory ist oder nicht erzeugt werden kann oder aus einem anderen Grund nicht geöffnet werden kann. - SecurityException, falls für die vorgesehene Zugriffsart keine Berechtigung besteht - IllegalArgumentException, falls ein nicht zulässiger String für die Zugriffsart angegeben wird. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/11 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse RandomAccessFile (2) • Wahlfreier Zugriff zu Dateien – Klasse RandomAccessFile, Forts ◇ Methoden der Klasse RandomAccessFile zum Lesen u. Schreiben (Auswahl) : Schreiben void write(int b) Lesen Datentyp int read() byte Rückgabewert : gelesenes Byte bzw -1 bei Dateiende void write(byte[] ba) void write(byte[] ba, int off, int len) int read(byte[] ba) int read(byte[] ba, int off, int len) byte-Array byte-Array (Länge len ab Offset off) Rückgabewert : Anzahl gelesener Bytes bzw -1 bei Dateiende void writeBytes(String str) String als Byte-Folge --- pro Zeichen ein Byte, das höherwertige Byte jedes Zeichens geht verloren void writeChars(String str) String als Char-Folge --- pro Zeichen zwei Bytes, höherwertiges Byte zuerst (big endian) void writeUTF(String str) String readUTF() *) String readLine() String (UTF-8-Cod.) String (Zeile) Rückgabewert : nächste Zeile (Bytes zu char ergänzt, höherwertiges Byte == NUL-Byte) bzw null bei Dateiende void void void void void void void void writeBoolean(boolean b) writeByte(int b) writeChar(int c) writeShort(int s) writeInt(int i) writeLong(long l) writeFloat(float f) writeDouble(double d) boolean readBoolean() byte readByte() char readChar() short readShort() int readInt() long readLong() float readFloat() double readDouble() *) *) *) *) *) *) *) *) boolean byte char short int long float double Ablage in Datei in big endian *) diese Lese-Methoden werfen beim vorzeitigen Erreichen des Dateiendes eine EOFException Alle Methoden zum Lesen/Schreiben werfen eine IOException, wenn ein I/O-Fehler auftritt. alle Methoden sind mit der throws-Klausel throws IOException definiert. ◇ Die I/O-Methoden der Klasse RandomAccessFile ermöglichen sowohl eine Bearbeitung von ▻ Binärdateien ▪ byteweises Lesen und Schreiben ▪ datenobjektorientiertes Lesen und Schreiben ▻ als auch von Textdateien. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/12 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse RandomAccessFile (3) • Wahlfreier Zugriff zu Dateien – Klasse RandomAccessFile, Forts ◇ Bearbeitung von Textdateien mittels RandomAccessFile-Objekten ▻ Mit der Methode writeBytes() kann Text in der üblichen Darstellung (pro Zeichen ein Byte) in Dateien geschrieben werden. ▻ Eine direkte Methode zum Schreiben eines Zeilenendes in eine Textdatei existiert allerdings nicht. Die im jeweiligen System verwendete – implementierungsabhängige - Zeilenendedarstellung lässt sich jedoch u.a. mittels der statischen String-Methode format(...) ermitteln : String.format("%n") erzeugt die Zeilenendedarstellung als String-Objekt raf.writeBytes(String.format("%n")) schreibt ein Zeilenende in die vom Objekt raf referierte Datei. ▻ Zum Lesen von Textdateien kann die Methode readLine() eingesetzt werden ▻ Formatiertes Schreiben : Erzeugung eines String-Objektes, das den entsprechend formatierten String enthält - mittels der statischen Methode format(...) der Klasse String - bzw mittels eines Formatter-Objektes anschliessende Ausgabe dieses Strings mittels der Methode writeBytes() ▻ Formatiertes Lesen : Lesen des formatierten Textes mittels der Methode readLine(), anschliessende Interpretation des erhaltenen String-Objektes mittels eines Scanner-Objektes ◇ Weitere Methoden der Klasse RandomAccessFile (Auswahl) void seek(long pos) throws IOException ▻ Setzen der Bearbeitungsposition auf pos Bytes nach dem Dateianfang long getFilePointer() throws IOException ▻ Rückgabe der aktuellen Bearbeitungsposition (in Bytes bezogen auf den Dateianfang) int skipBytes(int cnt) throws IOException ▻ Versuch, die Bearbeitungsposition um cnt Bytes weiterzusetzen ▻ Rückgabe der Anzahl Bytes, um die tatsächlich weitergesetzt wurde long length() throws IOException ▻ Rückgabe der Dateilänge void setLength(long newLength) throws IOException ▻ Explizites Verändern der Dateilänge. Die neue Länge kann größer oder kleiner der aktuellen Dateilänge sein. newLength < akt. Dateilänge : die Datei wird auf newlength verkürzt, wenn die akt. Position des File Pointers > newLength ist, wird sie auf newLength gesetzt newLength > akt. Dateilänge : die Datei wird auf newlength verlängert, der Inhalt des verlängerten Dateiteils ist undefiniert void close()throws IOException ▻ Schliessen der Datei und Freigabe aller belegten Resourcen HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/13 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse RandomAccessFile (4) • Demoprogramm zum Kopieren von Dateien (mittels RandomAccessFile) // FileCopy2.java import java.io.*; public class FileCopy2 { public void copy(String inpath, String outpath) { try { RandomAccessFile in = new RandomAccessFile(inpath, "r"); RandomAccessFile out = new RandomAccessFile(outpath, "rw"); out.setLength(0); long fsize = in.length(); System.out.print("Kopiert wird \"" + inpath+ "\" (Laenge : "); System.out.println(fsize + " Bytes) nach \"" + outpath+ "\""); byte[] buff = new byte[512]; int len; while ((len=in.read(buff))>=0) out.write(buff, 0, len); out.close(); in.close(); System.out.println("Kopieren der Dateien erfolgreich"); } catch(IOException ex) { System.out.println("Exception : " + ex.getMessage()); } } public static void main(String[] args) { if (args.length <2) System.out.println("Aufruf : \"java FileCopy quelldatei zieldatei\""); else new FileCopy2().copy(args[0],args[1]); } } Beispiel für Programmaufruf und -ausgabe E:\Java\fhm\ee\vorl\fileacc>java FileCopy2 Echo.class Echo.bin Kopiert wird "Echo.class" (Laenge : 808 Bytes) nach "Echo.bin" Kopieren der Dateien erfolgreich HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/14 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse RandomAccessFile (5) • Demoprogramm zum wahlfreien Dateizugriff // RandAccDemo.java // Zufallsgesteuerte Auswahl eines Spruches aus einer Textdatei mit Spruechen. // Die einzelnen Sprueche sind jeweils durch eine Zeile, // die nur das Zeichen '*' enthaelt, getrennt. import java.io.*; public class RandAccDemo { public void demo(String dname) { try { RandomAccessFile raf = new RandomAccessFile(dname, "r"); long pos = (long)(raf.length()*Math.random()); raf.seek(pos); while((pos!=0) && ((char)raf.read()!='*')) raf.seek(--pos); int ch; if (pos==0) System.out.println(); while (((ch=raf.read())!=-1) && ((char)ch!='*')) System.out.print((char)ch); System.out.println(); raf.close(); } catch (IOException ex) { System.out.println(ex); } } public static void main(String[] args) { String datname ="sprueche.txt"; if (args.length >0) datname = args[0]; new RandAccDemo.demo(datname); } } Beispiele für Programmaufruf und -ausgabe E:\Java\fhm\ee\vorl\fileacc>java RandAccDemo Gut gehaengt ist besser als schlecht verheiratet. E:\Java\fhm\ee\vorl\fileacc>java RandAccDemo Es ist nicht genug, dass man redet, man muss auch richtig reden. E:\Java\fhm\ee\vorl\fileacc>java RandAccDemo Ein Tor nur schliesst aus aeusserem Gehaben getrost auf eines Menschen innere Gaben. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/15 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Dateibearbeitung in Java – Klasse RandomAccessFile (6) • Demoprogramm zum Schreiben von Daten in Binärdarstellung (mittels RandomAccessFile) // BinDataDemo.java // Schreiben von Werten einfacher Datentypen und Strings in interner Binaerdarstellung // Lesen als Bytefolgen import java.io.*; public class BinDataDemo { static final String NL = String.format(%n"); public String byteToHex(byte b) { String str = Integer.toHexString(b & 0x00FF); if (str.length() == 1) str = '0' + str; return str; } public void demo(String dpfad) { try { RandomAccessFile raf = new RandomAccessFile(dpfad, "rw"); raf.setLength(0); raf.writeBoolean(true); raf.writeInt(124); raf.writeDouble(3.14); raf.writeUTF("Hallo Java-Freunde"); raf.writeUTF("\u007F\u0080\u07FF\u0800\uFFFF\u0000"); raf.writeChars("Hallo"); raf.writeBytes("Hallo\n"); raf.writeBytes(NL); raf.seek(0); System.out.println("Inhalt der Datei \"" + dpfad + "\" :"); int b; int cnt = 0; while((b=raf.read())!=-1) { cnt++; String hex = byteToHex((byte)b); System.out.print(hex + ' '); if (cnt%25 == 0) System.out.println(); } System.out.println(); raf.close(); } catch(IOException ex) { System.out.println("Exception : " + ex.getMessage()); } } public static void main(String[] args) { if (args.length <1) System.out.println("Aufruf : \"java BinDataDemo dateipfad\""); else { new BinDataDemo().demo(args[0]); } } } Programmaufruf und -ausgabe E:\Java\fhm\ee\vorl\fileacc>java Inhalt der Datei "demo.dat" : 01 00 00 00 7c 40 09 1e b8 51 eb 2d 46 72 65 75 6e 64 65 00 0d 7f 00 61 00 6c 00 6c 00 6f 48 61 6c BinDataDemo demo.dat 85 1f 00 12 48 61 6c 6c 6f 20 4a 61 76 61 c2 80 df bf e0 a0 80 ef bf bf c0 80 00 48 6c 6f 0a 0d 0a HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 13/04/16 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Zusammenfassende Gegenüberstellung der Dateibearbeitung in Java und C • Vorbemerkung In der Java-Standardbibliothek sind zahlreiche – teilweise alternative – Möglichkeiten zur Dateibearbeitung implementiert. Nachfolgend werden nur einige – nach Meinung des Verfassers die wichtigsten und einfachsten – dieser Möglichkeiten aufgeführt Aktion C Java Dateireferenz FILE-Pointer (FILE*) Objekt einer geeigneten Klasse Öffnen einer Datei fopen(...) Beim Erzeugen des für den Dateizugriff verwendeten Objekts durch den jeweiligen Konstruktor Schließen einer Datei fclose(...) Methode close() (existiert für jede für den Dateizugriff eingesetzte Klasse) byteweises Schreiben in Datei fputc(...) oder putc(...) Klasse FileOutputStream oder Klasse RandomAccessFile Methode write(...) byteweises Lesen aus Datei fgetc(...) oder getc(...) Klasse FileInputStream oder Klasse RandomAccessFile Methode read() formatiertes Schreiben in Textdatei fprintf(...) Klasse Formatter Methode format(...) formatiertes Lesen aus Textdatei fscanf(...) Klasse Scanner zahlreiche Methoden zum Lesen des Werts eines bestimmten Datentyps z.B. nextDouble() stringweises Schreiben in Textdatei (ein Byte pro Zeichen) fputs(...) Klasse RandomAccessFile Methode writeBytes(...) Schreiben eines Zeilenendes in Textdatei fputc('\n') writeBytes(String.format("%n")) zeilenweises Lesen aus Textdatei fgets(...) Klasse RandomAccessFile Methode readLine() oder : Klasse Scanner Methode nextLine() Schreiben von Datenwerten in Binärdatei (maschineninterne Darstellung) fwrite(...) Klasse RandomAccessFile datentypspezifische Schreib-Methoden z.B. writeDouble(...) Lesen von Datenwerten aus Binärdatei (maschineninterne Darstellung) fread(...) Klasse RandomAccessFile datentypspezifische Lese-Methoden z.B. readDouble() Verändern der aktuellen Bearbeitungsposition fseek(...) Klasse RandomAccessFile Methode seek(...) Ermitteln der aktuellen Bearbeitungsposition ftell(...) Klasse RandomAccessFile Methode getFilePointer() HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/00/0 – TH – 04 ------------------------------------------------------------------------------------------------------------------------------------------------------- Programmieren (4. Sem.) Kapitelüberblick 14. Vererbung und Polymorphie 14.1. Umfassende Klassendefinition 14.2. Klassenableitung 14.3. Polymorphie 14.4. Interfaces und ihre Implementierung 14.5. Programmbeispiel : Simpson-Integration HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/01/1 – TH – 01 -------------------------------------------------------------------------------------------------------------------------------------------------------- Umfassende Klassendefinition (1) • Wiederholung ◇ Klassen sind die grundlegenden Programmiereinheiten in Java. Jedes Java-Programm besteht aus einer oder mehreren Klassen. Es existiert kein Code ausserhalb einer Klasse. ◇ I.a. ist jede Klasse in einer eigenen Quelldatei definiert (Ausnahme : eingebettete Klassen). Diese Klassen-Quelldateien bilden die Übersetzungseinheiten (Übersetzungs-Module). Pro Klasse (auch für jede eingebettete Klasse) wird vom Compiler eine eigene Byte-Code-Datei erzeugt. ◇ Klassen definieren den Aufbau und die Funktionalität (das Verhalten) von Objekten. Objekte sind Instanzen von Klassen. Sie bestehen aus Datenkomponenten (Felder, fields), die ihren Zustand beschreiben und besitzen Funktionen (Memberfunktionen, Methoden, methods), die mit diesen Datenkomponenten arbeiten und ihr Verhalten festlegen. Die Datenkomponenten und die (Member-)Funktionen werden in einer Klassendefinition festgelegt und als Klassenkomponenten bezeichnet. ◇ Neben den instanzspezifischen Datenkomponenten (Instanz-Variable) und Memberfunktionen (InstanzMethoden) kann eine Klasse auch klassenspezifische Datenkomponenten (Klassen-Variable) und Memberfunktionen (Klassen-Methoden) besitzen. Derartige statische Klassenkomponenten beschreiben den Zustand und das Verhalten der Klasse. • Klassenkomponenten ◇ Klassenkomponenten (class members) können sein : ▻ Datenkomponenten (Membervariable, Felder, fields) ▻ Funktionskomponenten (Memberfunktionen, Methoden, methods) ▻ Eingebettete Klassen und Interfaces (nested classes and interfaces) (hier nicht weiter betrachtet) ◇ Zusätzlich kann eine Klassendefinition enthalten : ▻ Konstruktoren (Sie werden in Java nicht zu den Memberfunktionen gerechnet) ▻ Initialisierungsblöcke (Code zur Initialisierung von Datenkomponenten) (hier nicht weiter betrachtet) • Syntax der Klassendefinition (vollständig) : class Klassen-Name KlassenModifizierer Typ-ParamDeklaration { KlassenAbleitung Komponentendefinition Konstruktordefinition Initialisierungsblock ; InterfaceImplementierung } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/01/2 – TH – 01 -------------------------------------------------------------------------------------------------------------------------------------------------------- Umfassende Klassendefinition (2) • Klassen-Modifizierer ◇ Sie legen bestimmte Eigenschaften der Klasse fest ◇ Die wichtigsten Modifizierer sind : ▻ public Die Klasse ist öffentlich zugänglich – sie kann überall verwendet werden. Ohne diesen Modifizierer ist sie nur innerhalb des Packages, in dem sie enthalten ist, verwendbar. ▻ abstract Die Klasse ist unvollständig definiert oder sie wird als unvollständig definiert betrachtet abstrakte Klasse. Eine abstrakte Klasse ist nicht instanzierbar. I.a. besitzt sie eine oder mehrere abstrakte Methoden. Eine abstrakte Methode besitzt keine vollständige Definition. Häufig ist sie lediglich deklariert. Ihre – vollständige – Definition bleibt einer abgeleiteten Klasse überlassen. ▻ final Von der Klasse können keine weiteren Klassen abgeleitet werden. Damit können ihre Methoden niemals überschrieben werden. ◇ Die gleichzeitige Verwendung der Modifizierer public und abstract bzw public und final ist zulässig. Die Modifizierer abstract und final schließen sich gegenseitig aus. • Typ-Param-Deklaration Deklaration von formalen Typ-Parametern (Typ-Variable). Falls vorhanden, wird dadurch eine generische Klasse definiert. Generische Klassen werden hier nicht weiter betrachtet. • Klassen-Ableitung Festlegung der Basisklasse, falls es sich um eine abgeleitete Klasse handelt. Eine direkte Ableitung von der Klasse Object wird nicht explizit angegeben. • Interface-Implementierung Festlegung der von der Klasse direkt implementierten Interfaces • Eingebettete Klassen und Interfaces Als Klassenkomponenten lassen sich auch Klassen und Interfaces definieren. eingebettete Typen. Dies wird gegebenenfalls angewendet, um den logischen Zusammenhang eng zusammenarbeitender Klassen auszudrücken. Es gibt verschiedene Arten eingebetteter Typen Sie werden hier nicht weiter betrachtet • Initialisierungsblöcke Hierbei handelt es sich um Codeblöcke, die ausserhalb jeder Funktion definiert sind. Dieser Code dient – als Alternative und/oder Ergänzung zu Konstruktoren und Feld-Initialisierern – zur Initialisierung von Datenkomponenten. Damit lassen sich – insbesonder auch für statische Datenkomponenten – komplexere Initialisierungsalgorithmen formulieren. Auf Initialisierungsblöcke wird hier nicht weiter eingegangen. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/01/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Umfassende Klassendefinition (3) • Ergänzungen zur Definition von Memberfunktionen : ◇ Methoden-Modifizierer : Neben den schon bekannten Modifizierern public, private, protected, static und final existieren noch weitere Methoden-Modifizierer. U.a. sind dies : ▻ abstract Es handelt sich um eine abstrakte Methode. Für eine derartige Methode wird nur die Signatur (Name und Parameter), der Rückgabetyp und gegebenenfalls die throws-Klausel nicht jedoch die Implementierung festgelegt. (Methode wird nur deklariert) Der Methoden-Rumpf wird durch ein ; ersetzt. Abstrakte Methoden sind nur in abstrakten Klassen zulässig. Jede von einer abstrakten Klasse abgeleitete Klasse, die selbst nicht wieder abstrakt ist, muß eine Definition aller abstrakten Methoden bereitstellen. ▻ native Dieser Modifizierer erlaubt das Einbinden von Methoden, die in nativen Machinencode vorliegen. Üblicherweise werden derartige Methoden in einer anderen Programmiersprache (z.B. C, C++, FORTRAN, Assembler) formuliert und dann in Maschinencode übersetzt. Eine native-Methode wird in der Klassendefinition nur deklariert (statt Methoden-Rumpf nur ; ). ◇ Überladene Funktionen : In einer Klasse können zwei oder mehr Funktionen mit gleichem Namen, aber unterschiedlicher Parameterliste ( unerschiedliche Signatur) definiert werden. Derartige Funktionen werden als überladen bezeichnet. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/02/1 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassenableitung (1) • Prinzip und Eigenschaften der Klassenableitung ◇ Eine Klasse kann durch Vererbung erweitert (extended, subclassed) werden abgeleiteten Klasse. Definition einer ▻ Die abgeleitete Klasse (subclass, extended class) erbt alle Datenkomponenten und Memberfunktionen ihrer Basisklasse (superclass), d.h. diese Komponenten stehen auch in der abgeleiteten Klasse zur Verfügung. ▻ Sie kann neue Datenkomponenten und Methoden hinzufügen. Ergänzung / Änderung der Eigenschaften und/oder des Verhaltens der Klasse bzw ihrer instanzierter Objekte. ◇ Von einer als final vereinbarten Klasse können keine weiteren Klassen abgeleitet werden. ◇ In Java hat jede Klasse, die nicht explizit von einer anderen Klasse abgeleitet ist, die Klasse Object als direkte Basisklasse. ◇ Da in Java nur einfache Vererbung möglich ist, besitzt jede Klasse – außer der Klasse Object – genau eine Basisklasse. ◇ In jedem Objekt einer abgeleiteten Klasse ist ein Teilobjekt seiner Basisklasse enthalten. ◇ Abgeleitete Klassen sind zuweisungs-kompatibel zu ihren Basisklassen. Eine Basisklassen-Referenzvariable kann auch auf Objekte abgeleiteter Klassen zeigen. Über diese Referenz können aber nur die Komponenten der Basisklasse (d.h. des jeweiligen BasisklassenTeilobjekts) angesprochen werden, nicht jedoch die in der abgeleiteten Klasse zusätzlich definierten Komponenten. (Ausnahme : Zugriff zu überschreibenden Methoden, s. Polymorphie) ◇ In jedem Objekt einer abgeleiteten Klasse steht mit dem Schlüsselwort super eine Referenz auf das in ihm enthaltene Basisklassen-Teilobjekt zur Verfügung. Mittels der super-Referenz können nur Komponenten der Basisklasse angesprochen sowie BasisklassenKonstruktoren aufgerufen werden. Sie kann nicht – wie this – allein benutzt werden, um das Basisklassen-Teilobjekt insgesamt anzusprechen. • Syntax zur Angabe der Basisklasse in der Klassendefinition (extends-Deklaration) Klassen-Ableitung : extends Basisklassenname • Konstruktoren von abgeleiteten Klassen ◇ Ein Konstruktor kann nur die Datenkomponenten der eigenen Klasse initialisieren. Die Initialisierung der geerbten Datenkomponenten der Basisklasse muß durch einen Konstruktor der Basisklasse erfolgen. ◇ Jeder Aufruf eines Konstruktors muß daher als erstes den Aufruf eines Konstruktors der Basisklasse bewirken. Dies kann u.a. erfolgen durch : ▻ den expliziten Aufruf eines Basisklassen-Konstruktors mittels super(...). Dieser Aufruf muß die erste Anweisung im Konstruktor der abgeleiteten Klasse sein ▻ den impliziten Aufruf des no-arg-Konstruktors der Basisklasse, wenn die erste Anweisung im Konstruktor der abgeleiteten Klasse weder super(...) noch this(...) ist. ◇ Anmerkung : In den Parameter-Ausdrücken eines expliziten Konstruktorsaufrufs (mittels super(...)) darf keine Komponente des aktuellen Objekts referiert werden. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/02/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassenableitung (2) • Demonstrationsbeispiel zur Vererbung ◇ Definition einer Basisklasse Punkt // Punkt.java public class Punkt { private double x; private double y; public Punkt() { this(0.0, 0.0); } // no-arg-Konstruktor // nur zur Demo, ist nicht notwendig public Punkt(double wx, double wy) { x = wx; y = wy; } // Konstruktor public Punkt move(double dx, double dy) { x += dx; y += dy; return this; } // Die this-Referenz referiert das aktuelle Objekt // innerhalb einer Memberfunktion public void printPunkt() { System.out.print(this); } public String toString() { return "(" + x + " , " +y + ")"; } public void printPos() { System.out.print("x/y : " + x + " / " + y); } } ◇ Definition einer abgeleiteten Klasse FarbPunkt // Farbpunkt.java public class Farbpunkt extends Punkt { private String col; public Farbpunkt() { col = "black"; } // no-arg-Konstruktor // impliziter Aufruf von super() public Farbpunkt(String wcol) { col = wcol; } // Konstruktor // impliziter Aufruf von super() public Farbpunkt(double wx, double wy, String wcol) // Konstruktor { super(wx, wy); // expliziter Aufruf von super() col = wcol; } public void printFarbe() { System.out.print(" Farbe : " + col); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/02/3 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassenableitung (3) • Demonstrationsbeispiel zur Vererbung, Forts. ◇ Definition einer Testklasse APunktTest // APunktTest.java // Testklasse fuer die Klassen Punkt und Farbpunkt public class APunktTest { public static void main(String[] args) { Punkt p1 = new Punkt(4.2, 6.3); Farbpunkt p2 = new Farbpunkt("rot"); Punkt p3 = new Farbpunkt(2.5, 3.7, "blau"); System.out.print("p1 : System.out.println("p1 System.out.print("p2 : System.out.println("p2 System.out.print("p2 : System.out.println("p3 //System.out.print("p3 System.out.print("p3 : "); p1.printPunkt(); System.out.println(); : " + p1); "); p2.printPunkt(); System.out.println(); : " + p2); "); p2.printPos();p2.printFarbe(); System.out.println(); : " + p3); : "); p3.printPos(); p3.printFarbe(); System.out.println(); "); p3.printPos(); ((Farbpunkt)p3).printFarbe(); System.out.println(); System.out.println("nach \"move(1,1)\": "); p1.move(1.0, 1.0); p2.move(1.0, 1.0); p3.move(1.0, 1.0); System.out.println("p1 : " + p1); System.out.println("p2 : " + p2); System.out.println("p3 : " + p3); } } ◇ Ausgabe des Demonstrations-Programms p1 : p1 : p2 : p2 : p2 : p3 : p3 : nach p1 : p2 : p3 : (4.2 , 6.3) (4.2 , 6.3) (0.0 , 0.0) (0.0 , 0.0) x/y : 0.0 / 0.0 (2.5 , 3.7) x/y : 2.5 / 3.7 "move(1,1)": (5.2 , 7.3) (1.0 , 1.0) (3.5 , 4.7) Farbe : rot Farbe : blau HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/02/4 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassenableitung (4) • Namensgleichheit bei Komponenten Die in der abgeleiteten Klasse neu definierten Komponenten können den gleichen Namen wie Komponenten der Basisklasse tragen. In einem derartigen Fall werden die gleichnamigen Komponenten der Basisklasse entweder überschrieben oder überdeckt oder überladen. • Überschreiben von Methoden (Overriding) ◇ Eine in der abgeleiteten Klasse definierte Methode mit gleichem Namen und gleicher Parameterliste ( == gleicher Signatur) sowie gleichem Rückgabetyp wie in der Basisklasse, überschreibt die Methode der Basisklasse. Hierdurch wird die Basisklassen-Implementierung der Methode für die abgeleitete Klasse durch eine neue Implementierung ersetzt. ◇ Eine abstrakte Methode der Basisklasse (Deklaration als abstract), wird durch die überschreibende Methode in der abgeleiteten Klasse überhaupt erst implementiert. ◇ Die Definition einer Methode in der abgeleiteten Klasse, die sich nur im Rückgabetyp von einer Methode gleichen Namens in der Basisklasse unterscheidet, ist ein Fehler. Ausnahme : Wenn der Rückgabetyp ein Referenztyp ist, darf die überschreibende Methode eine Referenz auf eine davon abgeleitete Klasse zurückgeben ◇ Es können nur Methoden überschrieben werden, die weder als static noch als private noch als final vereinbart sind. Methoden, die überschrieben werden können, werden als virtuell bezeichnet. In Java ist jede nichtstatische Methode der Basisklasse, die weder private noch final ist, grundsätzlich virtuell. ◇ Eine in der abgeleiteten Klasse definierte überschreibende Funktion darf die in der Basisklasse festgelegte Zugriffsberechtigung nicht einschränken. Eine Erweiterung der Zugriffsberechtigung ist zulässig. Beispiel : Wenn in der Basisklasse für eine Methode protected festgelegt ist, darf eine überschreibende Methode in der abgeleiteten Klasse protected oder public sein, jedoch nicht private oder package. ◇ Bezüglich der Exception-Deklaration (throws-Klausel) gelten folgende Regeln : ▻ Die Exception-Deklarationen von überschreibender und überschriebener Methode können unterschiedlich sein. ▻ Eine überschreibende Methode darf in ihrer Exception-Deklaration nur Exception-Typen enthalten, die zu einem in der Exception-Deklaration der überschriebenen Methode festgelegten Typen polymorph kompatibel sind (d.h. von der gleichen Klasse oder einer von ihr abgeleiteten Klasse sind). ▻ Eine überschreibende Methode muss nicht alle von der überschriebenen Methode deklarierten ExceptionTypen ebenfalls deklarieren. Auch wenn die überschriebene Methode eine Exception-Deklaration hat, darf diese bei der überschreibenden Methode fehlen. ▻ Eine überschreibende Methode darf nichtgeprüfte Exceptions werfen, die in der überschriebenen Methode nicht auftreten können. Diese dürfen aber nicht in die Exception-Deklaration aufgenommen werden. ◇ Zu der überschriebenen Methode kann – innerhalb der überschreibenden Methode oder in einer anderen Instanz-Methode der gleichen Klasse – über die Basisklassen-Teilobjekt-Referenz super zugegriffen werden (nicht jedoch über ihren vollqualifizierten Namen). super.method(...) ruft immer die Implementierung der Methode method(...) auf, die in der direkten Basisklasse verwendet wird. Es kann sich dabei durchaus um eine Methode handeln, die in der Ableitungshierarchie "weiter oben" definiert und in der direkten Basisklasse selbst nicht neu definiert worden ist. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/02/5 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassenableitung (5) • Überladen von Methoden (Overloading) ◇ Eine in der abgeleiteten Klasse definierte Methode mit gleichem Namen aber anderer Parameterliste, überlädt die Methode der Basisklasse. Der Rückgabetyp bleibt unberücksichtigt, er kann gleich oder unterschiedlich sein. Das gleiche gilt für die Exception-Deklaration (throws-Klausel). ◇ In der abgeleiteten Klasse kann sowohl zu der geerbten Methode der Basisklasse als auch zu der neu definierten Methode allein über ihren Namen zugegriffen werden. in Java ist ein Überladen von Methoden auch über Klassengrenzen hinweg möglich. ◇ Auch statische Methoden können im obigen Sinn überladen werden. ◇ Ferner können statische Methoden nicht-statische Methoden und umgekehrt überladen. • Demonstrationsbeispiel zum Überschreiben und Überladen : siehe nächste Seite • Überdecken von Klassenkomponenten (Hiding) ◇ Statische Methoden können überdeckt statt überschrieben werden : Eine in der abgeleiteten Klasse definierte statische Methode mit gleicher Signatur sowie gleichen Rückgabetyps wie eine statische Methode der Basisklasse, überdeckt die Methode der Basisklasse. ◇ Eine statische Methode darf keine Instanz-Methode überdecken ( Compiler-Fehler) ◇ Überdecken von Datenkomponenten ist zulässig ▻ zwischen Instanz-Variablen (nicht-statischen Datenkomponenten) ▻ zwischen Klassen-Variablen (statischen Datenkomponenten) ▻ gemischt zwischen Instanz- und Klassen-Variablen. ◇ Das Überdecken zwischen Datenkomponenten unterschiedlichen Typs ist zulässig. ◇ Überdeckt werden genaugenommen Namen. Überdecken eines Namens bedeutet, dass in der abgeleiteten Klasse der Name allein nur in der in dieser Klasse definierten Bedeutung verwendet werden kann. Um den Namen in der Bedeutung, die in der Basisklasse definiert ist, zu verwenden, muss - bei statischen Komponenten der entsprechende vollqualifizierte Name und - bei nicht-statischen (objektspezifischen) Komponenten der Name zusammen mit einer BasisklassenReferenz (in Instanzmethoden z.B. super.kompo) angegeben werden. ◇ Beim Zugriff zu überdeckten Komponenten wird die tatsächlich ausgewählte Komponente durch die deklarierte Klasse der verwendeten Referenz (und nicht durch die Klasse des referierten Objekts) festgelegt. (Festlegung zur Compile-Zeit, statisches Binden, early binding) ◇ Zwischen einer Variablen und einer Funktion gleichen Namens findet kein Überdecken statt. Beide können gleichzeitig allein mit ihrem Namen verwendet werden. Das gilt auch, wenn beide in derselben Klasse definiert sind. ◇ Eine als private deklarierte Komponente kann weder überschrieben noch überdeckt werden (Sie ist ja in der abgeleiteten Klasse überhaupt nicht zugänglich). Ihr Name kann deshalb in der abgeleiteten Klasse für beliebige Definitionen verwendet werden. Beispielsweise ist es bei einer Memberfunktion zulässig, in der abgeleiteten Klasse eine Methode mit gleicher Signatur aber unterschiedlichem Rückgabetyp zudefinieren. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/02/6 – TH – 05 -------------------------------------------------------------------------------------------------------------------------------------------------------- Klassenableitung (6) • Demonstrationsbeispiel zum Überschreiben und Überladen von Methoden ◇ Definition einer Basisklasse Punkt (ergänzt) public class Punkt { private double x; private double y; public Punkt() { this(0.0, 0.0); } // no-arg-Konstruktor // nur zur Demo, ist nicht notwendig public Punkt(double wx, double wy) { x = wx; y = wy; } // Konstruktor public Punkt move(double dx, double dy) { x += dx; y += dy; return this; } // Die this-Referenz referiert das aktuelle Objekt // innerhalb einer Memberfunktion public void printPunkt() { System.out.print(this); } public String toString() { return "(" + x + " , " +y + ")"; } public void printPos() { System.out.print("x/y : " + x + " / " + y); } } // ------------------------------ Ergänzung ------------------------------------public Punkt change(Punkt p) { x = p.x; y = p.y; return this;} } ◇ Definition einer abgeleiteten Klasse Farbpunkt (ergänzt) public class Farbpunkt extends Punkt { private String col; public Farbpunkt() { col = "black"; } // impliziter Aufruf von super() public Farbpunkt(String wcol) { col = wcol; } // impliziter Aufruf von super() public Farbpunkt(double wx, double wy, String wcol) { super(wx, wy); // expliziter Aufruf von super() col = wcol; } public void printFarbe() { System.out.print(" Farbe : " + col); } // ------------------------------ Ergänzung ------------------------------------public String toString() { return super.toString() + " & " + } } col; // ueberschreibt Basisklassenmethode // Aufruf Basisklassenmethode public Farbpunkt change(String ncol) { col = ncol; return this; } // ueberlaedt Basisklassenmethode public Farbpunkt change(Farbpunkt fp) { change((Punkt)fp); change(fp.col); return this; } // ueberlaedt Basisklassenmethode HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/03/1 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Polymorphie (1) • Wesen der Polymorphie ◇ Poymorphie (griech. : Vielgestaltigkeit) bedeutet, dass mit dem gleichen Namen unterschiedliche Dinge bezeichnet werden können. D.h. der gleiche Name/Begriff kann unterschiedliche Bedeutungen haben. • Realisierungsarten der Polymorphie in Java ◇ Eine simple Form der Polymorphie liegt bei – auch innerhalb einer Klasse möglichen – überladenen Funktionen vor. Funktionen mit unterschiedlicher Funktionalität werden über den gleichen Namen aufgerufen. Die Unterscheidung, welche Funktion aufzurufen ist, wird durch die Parameterliste festgelegt. Dies kann der Compiler vornehmen Compilezeit-Polymorphie, statisches Binden, early binding. ◇ Compilezeit-Poymorphie ist auch bei der Auswahl von überdeckten Komponenten zwischen Basisklassen und abgeleiteten Klassen gegeben. ◇ Wesentlich interessanter und mächtiger sind die Referenz-Polymorphie und die darauf aufbauende Laufzeit-Polymorphie durch virtuelle Methoden. Beide haben das Konzept der Vererbung (Ableitung) als Voraussetzung. • Referenz-Polymorphie (polymorphic references) ◇ Abgeleitete Klassen sind zuweisungs-kompatibel zu ihren Basisklassen. D.h. einer Basisklassen-Referenzvariablen können auch Referenzen auf abgeleitete Objekte zugewiesen werden. Eine Referenz-Variable kann somit auf Objekte der eigenen Klasse sowie auf Objekte von ihr abgeleiteter Klassen zeigen Über den Namen einer Referenz-Variablen können somit Objekte unterschiedlicher – wenn auch in Ableitungsbeziehungen zueinander stehender – Klassen angesprochen werden. ◇ Andererseits kann ein Objekt einer abgeleiteten Klasse immer auch als Objekt ihrer Basisklasse betrachtet werden. Ein Objekt einer abgeleiteten Klasse kann überall verwendet werden, wo ein Objekt der Basisklasse benötigt wird. • Laufzeit-Polymorphie durch virtuelle Methoden ◇ Folgt aus der Referenz-Polymorphie : Virtuelle Methode == (in Java jede) überschriebene/überschreibende Methode Virtuelle Methoden bewirken, dass Objekte einer abgeleiteten Klasse und Objekte der jeweiligen Basisklasse für den gleichen Methodenaufruf unterschiedliches Verhalten zeigen. ◇ Beim Aufruf einer virtuellen Methode über eine Basisklassen-Referenz wird die in der tatsächlichen Klasse des aktuell referierten Objekts definierte Methode ausgeführt. Über eine Basisklassenreferenz aufgerufene virtuelle Methoden können also – je nach der tatsächlichen Klasse des referierten Objekts – zur Abarbeitung unterschiedlicher Funktionen führen. Da eine Basisklassen-Referenz während der Laufzeit i.a. auf unterschiedliche Objekte zeigen kann, kann der Compiler einem Methodenaufruf keine eindeutige Funktion zuordnen. Dies kann erst die JVM. Dynamisches Binden, late binding, Laufzeit-Polymorphie ! ◇ Ein Cast einer Referenz auf ein Objekt der abgeleiteten Klasse in die Basisklassen-Referenz ändert nicht den tatsächlichen Typ des referierten Objekts. Über einen derartigen Cast kann daher nicht zur überschriebenen sondern nur zur überschreibenden Methode zugegriffen werden. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/03/2 – TH – 04 -------------------------------------------------------------------------------------------------------------------------------------------------------- Polymorphie (2) • Demonstrationsbeispiel zur Polymorphie ◇ Verwendung der Klassen Punkt und Farbpunkt ◇ Definition einer weiteren Test-Klasse ErwPunktTest // ErwPunktTest.java // Testklasse fuer die erweiterten Versionen der Klassen Punkt und Farbpunkt public class ErwPunktTest { public static void main(String[] args) { Punkt p1 = new Punkt(4.2, 6.3); Punkt p2 = new Farbpunkt(); Farbpunkt p3 = new Farbpunkt("magenta"); Farbpunkt p4 = new Farbpunkt(2.5, 3.7, "blue"); Punkt pa[] = {p1, p2, p3, p4}; for (int i=0; i< pa.length; i++) { System.out.printf("p%d : ", i+1); pa[i].printPunkt(); System.out.println(); } System.out.println(); for (int i=0; i< pa.length; i++) System.out.printf("p%d : %s\n", i+1, pa[i].toString()); System.out.println("\nnach einigen Aenderungen : "); p4.move(1.0, 1.0); p4.change("yellow"); p3.change(p4); p1.change(p4); p2.change(p1); //p2.change("red"); // Fehler : p2 ist Punkt-Referenz !!! ((Farbpunkt)p2).change("red"); for (int i=0; i< pa.length; i++) System.out.printf("p%d : %s\n", i+1, pa[i]); System.out.println("\nCast der Farbpunkt-Ref. p4 in Punkt-Ref. :"); System.out.println("p4 : " + ((Punkt)p4).toString()); } } ◇ Ausgabe des Demonstrations-Programms p1 p2 p3 p4 : : : : (4.2 (0.0 (0.0 (2.5 , , , , 6.3) 0.0) & black 0.0) & magenta 3.7) & blue p1 p2 p3 p4 : : : : (4.2 (0.0 (0.0 (2.5 , , , , 6.3) 0.0) & black 0.0) & magenta 3.7) & blue nach p1 : p2 : p3 : p4 : einigen Aenderungen : (3.5 , 4.7) (3.5 , 4.7) & red (3.5 , 4.7) & yellow (3.5 , 4.7) & yellow Cast der Farbpunkt-Ref. p4 in Punkt-Ref. : p4 : (3.5 , 4.7) & yellow HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/03/3 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Polymorphie (3) • Typkompatibilität und -umwandlung ◇ Einer Basisklassen-Variablen kann jederzeit eine Referenz auf ein Objekt einer von der Basisklasse abgeleiteten Klasse zugewiesen werden. Das durch eine Basisklassen-Variable referierte Objekt einer abgeleiteten Klasse wird dadurch als Objekt der Basisklasse behandelbar. Implizite Typumwandlung. Da von einem spezialisierteren Typ in einen allgemeineren Typ umgewandelt wird, spricht man auch von einer erweiternden Typumwandlung (widening conversion). Weil diese Umwandlung in der KlassenHierarchie aufwärts erfolgt nennt man sie auch Aufwärts-Cast (upcast). ◇ Die Zuweisung einer Basisklassen-Referenz, die aber tatsächlich auf ein Objekt einer abgeleiteten Klasse zeigt, an eine Variable des tatsächlich referierten Typs ist implizit nicht möglich. Sie erfordert eine explizite Typumwandlung mittels des Cast-Operators. Hierbei findet eine Umwandlung von einem allgemeineren Typ in einen spezialisierteren Typ statt. Man nennt dies eine einengende Typumwandlung (narrowing conversion) oder einen Abwärts-Cast (down cast). ◇ Ein Abwärts-Cast ist prinzipiell unsicher. Er darf nur dann durchgeführt werden, wenn durch die Basisklassen-Referenz tatsächlich ein Objekt des angegebenen Zieltyps referiert wird. Der Versuch einer Typumwandlung, bei der dies nicht erfüllt ist, führt zum Werfen einer Exception vom Typ ClassCastException. • Der Operator instanceof ◇ Dieser binäre Operator ermöglicht die Überprüfung, ob ein Ausdruck zuweisungskompatibel zu einem bestimmten Typ ist. ◇ Syntax : Ausdruck instanceof Typangabe ◇ Wert eines instanceof-Ausdrucks : ▻ true, wenn der Ausdruck der linken Seite zuweisungs-kompatibel zum Typ der rechten Seite ist ▻ false, wenn der Ausdruck der linken Seite nicht zuweisungs-kompatibel zum Typ der rechten Seite ist ◇ Der instanceof-Operator kann eingesetzt werden, um einen sicheren Abwärts-Cast zu ermöglichen. ◇ Beispiel : class That { // ... } class More extends That { // ... } { // ... That sref = new More(); More mref; // ... if (sref instanceof More) mref=(More)sref; // ... } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/04/1 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Interfaces und ihre Implementierung (1) • Prinzip des Interfaces ◇ Interfaces definieren – ähnlich wie Klassen – Typen. Allerdings erfolgt die Definition in einer abstrakten Form, die keinerlei Implementierung bereitstellt. Damit lassen sich Interfaces nicht instanziieren. Sie legen – wie der Name bereits ausdrückt – lediglich eine Schnittstelle (Kontrakt) fest, die dann von Klassen implementiert werden kann. ◇ Ein Interface besteht – ähnlich wie eine Klasse – aus Komponenten. Diese Komponenten können aber lediglich sein : ▻ abstrakte Methoden ▻ Konstante ▻ eingebettete Klassen und Interfaces (hier nicht betrachtet) ◇ Im Prinzip ähnelt ein Interface einer abstrakten Klasse. Im Unterschied zu einem Interface kann eine abstrakte Klasse aber eine (Teil-) Implementierung enthalten. (Instanz- und Klassen-Variable, Klassen-Methoden, definierte Instanz-Methoden). Ein Interface kann als abstrakte Klasse, die nur Konstante und abstrakte Methoden (und gegebenenfalls eingebettete Typen) vereinbart, aufgefasst werden ("rein abstrakte" Klasse). ◇ Interfaces müssen wie Klassen in jeweils einer eigenen Quelldatei (Ausnahme eingebettete Interfaces) definiert werden, deren Hauptname gleich dem Interface-Namen sein muß. Interfaces werden vom Compiler auch getrennt übersetzt. • Interface-Definition ◇ Syntax : Interface-Name interface InterfaceModifizierer Typ-ParamDeklaration { Konstantendefinition InterfaceAbleitung } Abstrakte-Methoden-Deklaration Klassen-/Interface-Definition ◇ Beispiel : public interface Fahrbar { int MOTOR_UND_REIFEN = 3; // int MOTOR_UND_SCHRAUBE = 5; // int MUSKEL_UND_REIFEN = 2; int MUSKEL_UND_SCHRAUBE = 4; int LEHRLAUF = 0; void vorwFahren(double dist); void rueckwFahren(double dist); int getAntrieb(); } ; implizit geltende Modifizierer static und final (Konstante) sowie public können weggelassen werden // implizit geltende Modifizierer abstract und public // können weggelassen werden HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/04/2 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Interfaces und ihre Implementierung (2) • Interface-Modifizierer ◇ Zulässig sind u.a. : ▻ public Ohne diesen Zugriffs-Spezifizierer besitzt jedes Interface die Zugrifssberechtigung package. ▻ abstract Jedes Interface ist implizit abstract. Eine explizite Angabe dieses Modifizierers ist daher überflüssig. • Definition von Memberkonstanten (Interface-Konstante) ◇ Ein Interface darf als Datenkomponenten nur Konstante enthalten. ◇ Jede Datenkomponente ist daher implizit static und final. Zusätzlich ist sie implizit public. Eine explizite Angabe dieser Feld-Modifizierer ist zwar zulässig, aber überflüssig. ◇ Jede Datenkomponente muß durch einen Initialisierer initialisiert werden. blank finals sind nicht zulässig. Der Initialisierer muß ein konstanter Ausdruck sein. • Deklaration abstrakter Memberfunktionen (Interface-Methoden) ◇ Jede in einer Interface-Definition enthaltene Memberfunktion ist implizit public und abstract. Ihre Vereinbarung muß an Stelle eines Funktionsrumpfes ein ; enthalten. Nichtöffentliche Memberfunktionen machen keinen Sinn. ◇ Eine explizite Verwendung der Modifizierer abstract und public ist zwar zulässig, aber überflüssig ◇ Andere Modifizierer sind unzulässig. • Abgeleitete Interfaces ◇ Interfaces können auch voneinander abgeleitet werden ◇ Es gibt aber kein allgemeines Wurzel-Basis-Interface (etwa wie die Wurzel-Basisklasse Object) ◇ Im Unterschied zu Klassen ist bei Interfaces Mehrfachvererbung zulässig. Ein Interface kann von mehreren anderen Interfaces abgeleitet sein. ◇ Syntax : Interface-Ableitung : extends Basis-Interface-Name , ◇ Beispiel : Bibliotheks-Interfaces Serializable (Package java.io) Runnable (Package java.lang) public Interface SerializeAndRunnable extends java.io.Serializable, Runnable { // ... } Das Interface SerializeAndRunnable enthält – durch Vererbung – alle Komponenten der Interfaces Serializable und Runnable. Zusätzlich kann es weitere Komponenten definieren. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/04/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Interfaces und ihre Implementierung (3) • Implementierung von Interfaces ◇ Der Sinn von Interfaces liegt in der Definition von Schnittstellen, die zur Realisierung einer Funktionalität durch Klassen implementiert werden müssen. ◇ Eine Klasse, die ein Interface implementiert, muß sämtliche Methoden des Interfaces (einschliesslich der von Basis-Interfaces geerbten) definieren (d.h. für sie eine Implementierung bereitstellen) oder selbst als abstract deklariert sein. Im letzteren Fall können – müssen aber nicht – die nicht implementierten Methoden des Interfaces als abstrakt deklariert werden Prinzipiell entspricht die Implementierung eines Interfaces damit der Ableitung von einer (rein) abstrakten Klasse. ◇ Eine Klasse kann – gegebenenfalls zusätzlich zur Ableitung von einer Klasse – mehrere Interfaces implementieren. Realisierung einer Art eingeschränkter Mehrfachableitung in Java. ◇ Syntax : Interface-Implementierung : implements Interface-Name , Die implements-Klausel muss nach einer – gegebenenfalls vorhandenen – extends-Klausel aufgeführt werden. ◇ Beispiel : public class Auto implements Fahrbar { private double kmZaehler; private double kraftstoff; private double verbrauch; public Auto(double verbr, double km) { kmZaehler = km; verbrauch = verbr; kraftstoff = 0.0; } public int getAntrieb() { return MOTOR_UND_REIFEN; } public void vorwFahren(double dist) { // Funktionsrumpf } // Definition der im Interface // deklarierten Methoden public void rueckwFahren(double dist) { // Funktionsrumpf } // weitere Memberfunktionen } ◇ Die Implementierung eines Interfaces wird auch an abgeleitete Klassen vererbt. In einem derartigen Fall ist die Angabe einer implements-Klausel bei der abgeleiteten Klasse nicht explizit notwendig, aber zulässig. Dem Kopf einer Klassendefinition können nicht in jedem Fall alle implementierten Interfaces entnommen werden. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/04/4 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Interfaces und ihre Implementierung (4) • Demonstationsbeispiel zur Implementierung eines Interfaces ▻ Definition der das Interface Fahrbar implementierenden Klasse Auto // Auto.java // Definition der Klasse Auto // Klasse implementiert das Interface Fahrbar import java.util.*; public class Auto implements Fahrbar { public static String TANKLEER = "Tanken notwendig !"; private private private private final double verbrauch; double kmZaehler; double kraftstoff; double restkm; // liter/(100 km) public Auto(double verbr, double km) { kmZaehler = km; verbrauch = verbr; kraftstoff = 0.0; restkm = 0.0; } public int getAntrieb() { return MOTOR_UND_REIFEN; } public void vorwFahren(double dist) { double maxEntf = kraftstoff/verbrauch*100; if (dist>maxEntf) { restkm = dist-maxEntf; dist=maxEntf; } else restkm = 0.0; kmZaehler+=dist; kraftstoff-=dist/100*verbrauch; if (dist==maxEntf) throw new RuntimeException(TANKLEER); } public void rueckwFahren(double dist) { } public void tanken(double lit) { kraftstoff += lit; } public double getKmStand() { return kmZaehler; } public double getKraftstoff() { return kraftstoff; } public double getRestKm() { return restkm; } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/04/5 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Interfaces und ihre Implementierung (5) • Demonstationsbeispiel zur Implementierung eines Interfaces, Forts. ▻ Definition einer Start- und Testklasse AutoTest // AutoTest.java // Klasse AutoTest zum Testen der Klasse Auto import java.util.Scanner; public class AutoTest { public static void main(String[] args) { Locale.setDefault(Locale.ROOT); Auto myCar = new Auto(6.0, 500); Scanner stdinp = new Scanner(System.in); double entf; double liter; double altkm = myCar.getKmStand(); double neukm; boolean mussTanken = false; boolean endeFahrt = false; do { try { altkm = myCar.getKmStand(); entf = myCar.getRestKm(); if (entf == 0.0) { System.out.print("Fahrstrecke ? "); entf=stdinp.nextDouble(); } if (entf==0.0) endeFahrt = true; else myCar.vorwFahren(entf); } catch (RuntimeException ex) { if (ex.getMessage().equals(Auto.TANKLEER)); { mussTanken = true; } } finally { neukm = myCar.getKmStand(); if (neukm > altkm) { System.out.println("gefahrene km : " + (neukm-altkm)); System.out.println("km-Stand : " + neukm); altkm = neukm; } if (mussTanken) { System.out.print("Tanken notwendig, wieviel Liter ? "); liter = stdinp.nextDouble(); myCar.tanken(liter); if (myCar.getKraftstoff() > 0) mussTanken = false; else endeFahrt = true; } } } while (!endeFahrt); System.out.println("Ende der Fahrt !!!"); } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/04/6 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Interfaces und ihre Implementierung (6) • Demonstationsbeispiel zur Implementierung eines Interfaces, Forts. (2) ▻ Beispielhafte Programmdialoge HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/04/7 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Interfaces und ihre Implementierung (6) • Interfaces als Objekt-Referenzen ◇ Objekte einer Klasse, die ein Interface implementiert, lassen sich auch als Instanzen des implementierten Interfaces behandeln. Explizite oder implizite Typkonvertierung (cast) eines Objekts in den Typ eines implementierten Interfaces. ◇ Dies ermöglicht es auch, mit dem instanceof-Operator zu überprüfen, ob die Klasse eines Objekts ein bestimmtes Interface implementiert. Beispiel : Auto car = new Auto(7.0, 10000); boolean isfb = car instanceof Fahrbar; // true ◇ Man kann Variable eines Interface-Typs definieren und ihnen Referenzen auf Objekte einer implementierenden Klasse zuweisen. Natürlich lassen sich über eine Interface-Variable auch nur die Komponenten eines Objekts ansprechen, die in dem implementierten Interface definiert sind. Beispiel : // ... Fahrbar fb = new Auto(10, 150000); // ... fb.getAntrieb(); // ok fb.tanken(50); // unzulässig ! // ... ◇ Jede Objekt-Referenz eines Interface-Typs lässt sich allerdings als Referenz auf die Klasse Object verwenden. Damit lassen sich über eine Interface-Variable auch die in der Klasse Object definierten Methoden aufrufen. Dies ist deswegen möglich, da ja das referierte Objekt von irgendeiner Klasse sein muss und jede Klasse von Object abgeleitet ist. Beispiel : // ... Fahrbar fb = new Auto(5, 1500); // ... String str = fb.toString(); // keine Methode von Auto, // ... // aber von Object • Marker Interfaces ◇ Es kann auch sinnvoll sein, ein Interface leer, d.h. ohne jegliche Komponenten, zu definieren. ◇ Ein derartige Interface dient dazu, anzuzeigen, dass eine implementierende Klasse über eine bestimmte Eigenschaft verfügt. Es markiert die Klasse hinsichtlich des Besitzes dieser Eigenschaft Marker Interface ◇ In der Java-Standardbibliothek sind mehrere derartige Marker Interfaces enthalten. Beispiel : ▻ Cloneable (Package java.lang) Es legt fest, dass Objekte einer implementierenden Klasse mit der clone()-Methode geclont werden können. Die clone()-Methode ist selbst nicht Komponente des Interfaces, sondern der Klasse Object. HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/05/1 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Simpson-Integration(1) ● Aufgabenstellung ◇ Im C-Teil der Vorlesung ist als Beispiel für die Verwendung von Funktions-Pointern ein Programm zur Anwendung der Simpson-Integration auf verschiedene zu integrierende Funktionen y=f(x) vorgestellt worden. In dem Beispiel wird eine allgemeine Simpson-Integrations-Funktion formuliert, der die jeweils zu integrierende Funktion mittels eines Funktions-Pointers als Parameter übergeben werden muss. ◇ Dieses Beispiel soll nun sinnvoll nach Java portiert werden. Die Implementierung soll dabei so erfolgen, dass auch eine einfache Erweiterung auf andere numerische Integrationsverfahren ermöglicht wird. ● Lösung : ◇ Definition einer abstrakten Basisklasse Integrator. Sie ist Basisklasse für konkrete Klassen, die jeweils ein spezielles numerisches Integrationsverfahren implementieren. Für die Simpson-Integration soll das die Klasse SimpsonIntegrator sein. Die Klasse Integrator definiert eine Instanzvariable zur Speicherung der gewünschten Genauigkeit (die ihr im Konstruktor zu übergeben ist) und deklariert eine abstrakte Methode integrate(...). Diese ist von den abgeleiteten Klassen zur Realisierung des jeweiligen Integrations-Algorithmus geeignet zu implementieren. ◇ Jede zu integrierende Funktion wird durch eine Klasse beschrieben, die das Interface FunctionYvonX implementieren muss ( Funktionsklassen). Dieses Interface deklariert die von den Funktionsklassen zu definierenden Methode double funcVal(double x), die die jeweilige zu integrierende Funktion realisiert. Beispielhaft werden hier die Funktionsklassen Halbkreis, Dreieck und Polynom2 (Polynom 2. Ord.) definiert. ◇ Jedes Objekt einer Funktionsklasse lässt sich als Instanz des Interfaces FunctionYvonX behandeln. Als solche wird es (mittels einer entsprechenden Referenz) der Integrator-Funktion integrate(...) als Parameter übergeben. Innerhalb dieser Funktion ruft das Integrator-Objekt die Methode funcVal(...) für dieses Objekt auf. ( "Das Integrator-Objekt schickt die Botschaft funcVal an das FunctionYvonX-Objekt") ◇ Als Startklasse dient eine Klasse SimpIntDemo. In ihrer Methode main() werden ein SimpsonIntegrator-Objekt sowie die verschiedenen Funktionsklassen-Objekte erzeugt. ● Klassendiagramm der Lösung (ohne Startklasse) Integrator FunctionYvonX eps integrate() funcVal() SimpsonIntegrator Halbkreis Dreieck Polynom2 integrate() funcVal() funcVal() funcVal() HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/05/2 – TH – 02 -------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Simpson-Integration(2) ● Definition der abstrakten Klasse Integrator // Integrator.java // abstrakte Basisklasse fuer Integrator-Klassen package integration; public abstract class Integrator { protected double eps; protected Integrator(double gen) { eps = gen; } public abstract double integrate(FunctionYvonX func, double x1, double x2); } ● Definition der Klasse SimpsonIntegrator // SimpsonIntegrator.java // Integrator-Klasse, implementiert die Simpson-Integration package integration; public class SimpsonIntegrator extends Integrator { public SimpsonIntegrator(double gen) { super(gen); } public double integrate(FunctionYvonX func, double dXu, double dXo) { long i; double dFl = 0.; /* letzter Integralwert */ double dFa = 0.; /* aktueller Integralwert */ double dH; /* Schrittweite */ double dX; /* X-Wert */ double dY; /* Funktionswert */ double dg; /* Gewicht */ double dErr; /* Fehler */ long n = 2; /* Intervallanzahl */ do /* Schleife ueber jeweils verdoppelte Intervallanzahl */ { dH = (dXo - dXu)/n; /* Schrittweite bestimmen */ dFa = func.funcVal(dXu) + func.funcVal(dXo); for (i=1; i < n; i++) /* Flaeche der akt. Intervallanzahl */ { dX = dXu + dH*i; dY = func.funcVal(dX); if (i%2 == 1) dg = 4.; else dg = 2.; dFa += dg*dY; } dFa *= dH/3; dErr = Math.abs((dFa -dFl)/dFa); n*=2; /* Verdoppeln der Intervallanzahl */ dFl = dFa; } while ( dErr > eps); return dFa; } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/05/3 – TH – 03 -------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Simpson-Integration(3) ● Definition des Interfaces FunctionYvonX // FunctionYvonX.java // Interface fuer Funktions-Klassen, Funktionen der Form y = f(x) package integration; public interface FunctionYvonX { double funcVal(double x); } ● Definition der Klasse Halbkreis // Halbkreis.java // Funktions-Klasse, die einen Halbkreis implementiert // Der Radius wird dem Konstruktor uebergeben package integration; public class Halbkreis implements FunctionYvonX { private double radius; public Halbkreis(double r) { radius = r; } public double funcVal(double dX) { double dY = 0; if (dX <= radius && dX >= -radius) dY = Math.sqrt (radius*radius - dX*dX); return dY; } } ● Definition der Klasse Polynom2 // // // // Polynom2.java Funktions-Klasse, die ein Polynom 2.Grades implementiert y = a*x*x + b*x +c Die Polynom-Koeffizienten werden dem Konstruktor uebergeben package integration; public class Polynom2 implements FunctionYvonX { private double a, b, c; public Polynom2(double ak, double bk, double ck) { a = ak; b = bk; c = ck; } public double funcVal(double dX) { return a*dX*dX + b*dX + c; } } HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – PR–JV – 14/05/4 – TH – 01 -------------------------------------------------------------------------------------------------------------------------------------------------------- Programmbeispiel : Simpson-Integration(4) ● Definition der Klasse Dreieck // // // // // Dreieck.java Funktions-Klasse, die ein Dreieck implementiert Koordinaten der Eckpunkte : (xl,0), (xr,0), (0,yh), mit xl < 0 und xr > 0 xl, xr und yh werden dem Konstruktor uebergeben Die Flaeche des Dreiecks betraegt (xr-xl)*yh/2 package integration; public class Dreieck implements FunctionYvonX { private double xl; private double xr; private double h; public Dreieck(double xlw, double xrw, double yhw) { xl=xlw; xr=xrw; h=yhw; } public double funcVal(double dX) { double dRet; if (dX > xr || dX < xl) dRet = 0.; else if(dX >= 0) dRet = (h-h/xr*dX); else dRet = (h-h/xl*dX); return dRet; } } ● Definition der Start-Klasse SimpIntDemo // SimpIntDemo.java // Startklasse fuer ein Demo-Programm zur Simpson-Integration package integration; import java.io.*; import java.util.*; public class SimpIntDemo { public static void main(String[] args) { double dXo = 1.; double dXu = -1.; double dF; FunctionYvonX funcArr[] ={ new Halbkreis(1.0), new Dreieck(-1, 1, 1), int i; boolean bBed; Scanner scan = new Scanner(System.in); Integrator sig = new SimpsonIntegrator(1.e-8); new Polynom2(1, -6, 1), new Dreieck(-0.5, 1, 3) }; do { System.out.printf(" Auswahl: "); i=scan.nextInt(); bBed = i>0 && i <= funcArr.length; /* Abbruchbedingung formulieren */ if(bBed) { dF = sig.integrate(funcArr[i-1],dXu,dXo); System.out.printf (" Xu: %8.4f, Xo: %8.4f, F: %12.8f \n", dXu,dXo,dF); } } while(bBed); } }