SE Software Engineering Inhaltsverzeichnis 1. Vom Problem zum Programm.............................................................................................4 1.1. Einführung Software Engineering................................................................................4 1.2. Phasen der Software Entwicklung...............................................................................6 1.2.1. Analysephase........................................................................................................7 1.2.2. Designphase.........................................................................................................7 1.2.3. Implementierungsphase........................................................................................9 2. Die Programmiersprache C...............................................................................................10 2.1. Globale Deklarationen und Funktionen......................................................................10 2.2. Ablauf der Programmerstellung..................................................................................11 2.3. „Hello World“...............................................................................................................12 3. Elementare Datentypen – Initialisierung, Deklaration und Verknüpfung...........................13 3.1. Variablen....................................................................................................................13 3.2. Identifier (Bezeichner)................................................................................................13 3.3. Syntax ........................................................................................................................13 3.4. Die 5 atomaren Typen................................................................................................14 3.4.1. Modifizierung der atomaren Typen.....................................................................14 3.5. enum und typedef.......................................................................................................15 3.6. Variablen Definition, Zuweisung und Verknüpfung....................................................16 3.7. char in ASCII = int .....................................................................................................17 3.8. Ausführen eines Programms .....................................................................................19 3.9. Die Ausgabefunktion printf.........................................................................................19 3.9.1. Format Elemente.................................................................................................20 3.10. Die Einlesefunktion scanf.........................................................................................21 3.11. die Darstellung von Umlauten..................................................................................21 3.12. Die Modulo-Operation..............................................................................................21 4. Operatoren, Ausdrücke & Funktionsaufrufe......................................................................22 4.1. Operanden & Operatoren...........................................................................................22 4.1.1. Klassifikation der Operatoren nach Anzahl der Operanden...............................22 4.1.2. Inhaltliche Klassifikation von Operatoren............................................................22 4.2. Ausdrücke...................................................................................................................26 4.2.1. Prioritäten von Ausdrücken.................................................................................27 4.2.2. Typen von Ausdrücken.......................................................................................27 4.3. math.h.........................................................................................................................29 4.3.1. Trigonometrische und hyperbolische Funktionen...............................................29 4.3.2. Exp, Log und andere...........................................................................................30 5. Kontrollstrukturen in C.......................................................................................................30 5.1. Sequenz ....................................................................................................................31 5.1.1.Deklarations-Liste: Variablen und Konstanten.....................................................31 5.1.2. Verschachtelte Sequenzen.................................................................................32 5.2. Bedingte Verzweigung – if-Anweisung.......................................................................32 5.2.1. logische Operatoren............................................................................................33 5.2.2. Verschachtelte If-Anweisung..............................................................................33 5.3. Bedingte Verzweigung – Mehrfachauswahl mit switch..............................................34 5.4. Bedingte Iteration.......................................................................................................34 5.4.1. Prüfen am Anfang – while...................................................................................34 5.4.2. Prüfen am Ende – do ... while.............................................................................36 1 SE 5.5. gesteuerte Iteration – for ...........................................................................................38 5.5.1. geschachtelte Schleifen mit for ..........................................................................39 5.6. Kontrollstrukturen im Nassi-Schneidermann-Diagramm............................................40 6. Arrays, Strings und Pointer................................................................................................41 6.1. Arrays.........................................................................................................................41 6.1.1. Grenzüberschreitung...........................................................................................42 6.1.2. Sortieralgorithmus...............................................................................................43 6.1.3. Zählen der Elemente eines Arrays......................................................................44 6.1.4. Vergleichen von Arrays.......................................................................................44 6.2. Strings.........................................................................................................................45 6.2.1. Zeichenzahl eines Arrays auslesen....................................................................45 6.2.2. Beispiel suchen und Ersetzten eines Worts in einem String..............................46 6.2.3. Beispiel Grenzüberschreitung.............................................................................47 6.2.4. Einlesen von Strings durch gets.........................................................................47 6.2.5. Einlesen von Strings durch fgets........................................................................47 6.3. String.h.......................................................................................................................48 6.4. Mehrdimensionale Felder...........................................................................................57 6.4.1. Beispiel Matrix Quadratwerte..............................................................................58 7. Funktionen.........................................................................................................................59 7.1. Definition von Funktionen...............................................................................................59 7.2. Funktionsaufruf/-deklaration/-definiton.......................................................................60 7.3. Lokale Variablen.........................................................................................................61 7.4 Globale Variablen........................................................................................................62 7.5. Statische Variablen ....................................................................................................63 7.6. Datenaustausch zwischen Funktionen.......................................................................64 7.6.1 Funktion mit Wertübergabe – call by value..........................................................64 7.6.2. Funktion mit Wertrückgabe.................................................................................65 7.7. Die Hauptfunktion main()............................................................................................65 7.8. Rekursive Funktionen.................................................................................................66 7.9. Übergabe von Arrays an Funktionen.........................................................................70 7.10. Arrays aus Funktionen zurückgeben........................................................................70 8. Präprozessoranweisungen................................................................................................75 8.1. Einkopieren von Dateien mittels #include..................................................................75 8.2. Markos und Konstanten.............................................................................................76 8.2.1. symbolische Konstanten.....................................................................................76 8.2.2 Makros......................................................................................................................77 8. Zeiger.................................................................................................................................78 8.1. Zeigerarithmetik.....................................................................................................81 8.2. Zeiger auf Arrays........................................................................................................83 8.2.1. Zugriff auf einen Array mittels Zeiger..................................................................83 8.2.2. Funktionsaufrufe von Arraynamen......................................................................85 8.2.3 Gemeinsamkeinten Array und Zeiger..................................................................86 8.3. Zeiger auf Strings.......................................................................................................87 8.3.1. Zeiger auf konstante Objekte - Read-only-Zeiger...............................................88 8.3.4 Zeiger auf Zeiger und Stringtabellen....................................................................89 8.3.5. Zeiger erhöhen....................................................................................................90 8.3.6. Beispiel für Zeiger...............................................................................................91 9. Kommandozeilenargumente..............................................................................................92 9.1. Optionen/Schalter.......................................................................................................94 10. Dynamische Speicherverwaltung....................................................................................96 10.1. Speicherallokation mit malloc()................................................................................96 2 SE 10.2. Speicherbereich wieder freigeben - free()................................................................98 10.3. dynamische Arrays.................................................................................................100 10.4. realloc und calloc....................................................................................................100 11. Strukturen......................................................................................................................105 11.1. Strukturen deklarieren............................................................................................105 11.2. Initialisierung und Zugriff auf Strukturen................................................................106 11.3. Strukturen als Werteübergabe an Funktionen.......................................................108 11.4. Strukturen als Rückgabewerte einer Funktion ......................................................109 11.5. Strukturen vergleichen...........................................................................................110 11.6. Arrays von Strukturen.............................................................................................111 11.7. Verschachtelte Strukturen......................................................................................115 11.8. Aufzählungstyp enum.............................................................................................124 11.9. Typendefinition mit typdef......................................................................................125 12. Dynamische Datenstrukturen........................................................................................128 12.1. Lineare Listen – einfache verkettete Listen...........................................................128 12.1.1. Erstes Element in der Liste löschen...............................................................132 12.1.2. beliebiges Element der Liste löschen.............................................................132 12.1.3. Elemente der Liste ausgeben.........................................................................133 12.1.4. Alle Elemente löschen....................................................................................134 12.1.5. Elemente in die Liste einfügen........................................................................134 13. Dateien...........................................................................................................................137 13.1 Schema für Dateioperationen..................................................................................138 13.2. fopen()....................................................................................................................138 13.2.1. Modus der Dateiöffnung..................................................................................138 14. Headerfiles.....................................................................................................................141 14.1. One-Definition-Rule....................................................................................................141 14.2 Inhalt Header ..........................................................................................................142 14.2. Inhalt Implementierungsdatei.................................................................................142 14.3. Einbinden von Headern..........................................................................................143 14.4. Objekt Files.............................................................................................................143 15. Objektorientierung.........................................................................................................144 15.1. Stärken der Strukturierten Programmierung..........................................................144 15.2. Schwächen der Strukturierten Programmierung ...................................................144 15.3. Imperativ / prozedurales Programmierparadigma..................................................144 15.4. objektorientiertes Programmierparadigma.............................................................145 15.3. Objekte...................................................................................................................145 15.4. Information Hidding................................................................................................145 15.5. Zugriffsmechanismen.............................................................................................145 15.6. Klassen...................................................................................................................146 15.7. Beziehung zwischen Klassen.................................................................................148 15.7.1. Vererbung.......................................................................................................148 15.7.2. Aggregation.....................................................................................................149 15.2.3. Assoziation......................................................................................................150 15.2.4. Unified Modeling Language - UML.................................................................150 3 SE 1. Vom Problem zum Programm 1.1. Einführung Software Engineering ●Verwendung im Maschinenbau In Produkten – Steuerung von Sensoren und Aktoren in Produktionsanlagen – CNC-Steuerung in der Entwicklung – CAD, Officeanwendungen ●Fakten Fünf von sechs Software-Projekten sind erfolgreich ein drittel wird abgebrochen restlichen oft finanziell und zeitlich stark verkalkuliert Softwarekriese ist ein immer aktuelles Thema ●Fehler Rate 1977: 7 – 20 Fehler pro 1000 LOC Rate 1994: 0,05 – 0,2 Fehler pro 1000 LOC Steigerung um den Faktor 100 in 13 Jahren Komplexitätssteigerung Faktor 10 in 5 Jahren 0,1 Fehler bedeuten •300 versagende Herzschrittmacher pro Jahr •18 Flugzeugabstürze am Tag •22.000 falsch gebuchte Schecks pro Stunde ●Definition Software Engineering nach IEEE •The application of a systematic, disciplined, quantifiable approach to the development, operation, and maintenance of software; that is, the application of engineering to software •The study of approaches as in (1) 4 SE ●Teilgebiete Software Requirements Software Design Software Construction Software Testing Software Maintenance Software Configuration Management Software Engineering Management Software Engineering Process Software Engineering Tolls & Methods Software Quality Knowledge Areas of Related Disciplines 5 SE 1.2. Phasen der Software Entwicklung ●Analysephase oder Spezifikation Abstrahieren des Problems auf das wesentliche beschränkte, präzise und eindeutige Form Ergebnis ist die Problemspezifikation (Pflichtenheft) ●Designphase Überführen des Problems in Rechenverfahren kann vom Computer in einzelnen Schritten ausgeführt werden Ergebnis ist ein Algorithmus ●Implementierung Algorithmus wird in Form eines Programms oder mehrerer Programmmodule in einer Programmiersprache formuliert das ist die sogenannte Codierung ●Integration, Test & Inbetriebnahme Programmmodule werden zusammengeführt, in Betrieb genommen und auf Erfüllung des Pflichtenhefts geprüft Ergebnis ist fertiges Softwaresystem (Installiert, Abgenommen durch Auftraggeber, inklusive Dokumentation) ●tritt ein Fehler in einer der letzten beiden Phasen auf, muss in der linken Phase auf gleicher Höher der Fehler gesucht werden 6 SE 1.2.1. Analysephase ●Ergebnis ist Problembeschreibung/-spezifikation mit Anforderungen ●vollständig, eindeutig, lösungsneutral und widerspruchsfrei ●Spezifikation in umgangssprachlicher, mathematischer oder formaler Form Formale Spezifikation beschreibt •Menge aller Eingabegrößen •Menge aller Ausgabegrößen mit allen für die Lösung wichtigen Eigenschaften •funktionalen Zusammenhang zwischen ihnen •z.B. mathematisch Semi-formale Spezifikation •Anwendungsfälle umgangssprachlich •deren Verknüpfung mit formalen Mitteln •z.B. UseCase-Diagramm Informale Spezifikation •textuelle Beschreibung •meist nur für den Autor eindeutig und widerspruchsfrei •z.B. umgangssprachlicher Text 1.2.2. Designphase ●Spezifikation des Lösungswegs ●Architekturbeschreibung ●Kreativphase ●Algorithmus in Beschreibung und Ausführung endlich, deterministisch und effektive Vorschrift zur Lösung eines Problems, die effizient sein soll •Endlich: Der Algorithmus endet nach einer endlichen Zeit •Deterministisch: Eindeutige Beschreibung des nächsten Schritts •Effektiv: Eindeutige Ausführbarkeit der Einzelschritte •Effizient: Möglichst geringer Ressourcenverbrauch ●es gibt viele unterschiedliche Lösungswege für das gleiche Problem ●sinnvoll vorhandene Programmmodule einzubinden ●bei Komplexen Problemstellungen müssen Programmmodule entwickelt werden ●allgemeiner Weg, noch nicht an eine Programmiersprache gebunden 7 SE ●Darstellung durch ein Flussdiagramm ●sehr anschaulich ●werden bei komplexeren Problemstellungen schnell unübersichtlich ●viel Querverweise, sogenannte Gotos ●Darstellung durch Struktogramm oder Nassi-ShneidermannDiagramm ●Schleife mit Prüfung vor dem Rumpf Test der Schleifenbedingung falsch → überspringen der Schleife wahr → durchlaufen der Schleife ●Schleife mit Prüfung nach dem Rumpf Rumpf wird erst durchlaufen Test der Schleifenbedingung falsch → Fortsetzung bei nächstem Schritt wahr → erneutes Durchlaufen der Schleife ●allgemein gilt für Schleifen: durch den Rumpf muss irgendwann die Bedingung erfüllt werden können ansonsten kann sich ein Programm aufhängen – Endlosschleife 8 SE ●Beispiel: 1: Gehe zur ersten Person. 2: Erfrage das Alter. 3: Merke dieses Alter. 4: Solange noch nicht alle Personen gefragt wiederhole Schritte 4.1 – 4.3: •4.1: Gehe zur nächsten Person. •4.2: Erfrage das Alter. •4.3: Wenn das Alter kleiner als das gemerkte Alter, dann merke das neue Alter. 5 Jüngste Person ist ‘gemerktes Alter‘ Jahre alt. ●Alternatives Programm mit zusätzlichen nicht gefragten Funktionen 1.2.3. Implementierungsphase ● Umsetzung in einer Programmiersprache 9 SE 2. Die Programmiersprache C Objektorientiert weiterentwickelt zu C++ und C# UNIX basiert auf C hohe Portabilität weil auf alle Plattformen verfügbar wird häufig im technischen Bereich eingesetzt kleiner,überschaubarer Sprachumfang ermöglicht kompakte, effiziente Codes Sehr mächtig verlangt hohe Disziplin schon vorhandene Bausteine werden oft eingebunden – Standartelemente heißen Header, Dateiendung .h ● case sensitive!!! ● ● ● ● ● ● ● ● ● ● 2.1. Globale Deklarationen und Funktionen ● ganz oben sind globale Deklarationen #include <stdio.h> - als Präpozessoranweißung zum einbinded des Standard Ein-/Ausgabe Headers ● besteht aus einer beliebigen Menge an Funktionen ● zwingend ist die Main-Funktion ist Hauptfunktion kann als Alleinige Funktion vorhanden sein wird nach Programmstart als erstes Aufgerufen Übergabe von Parametern in runden Klammern 10 SE 2.2. Ablauf der Programmerstellung ● editieren, also tippen, des Programms in einem Editor ● das ist Quelltext oder source, wird mit der Endung .c gespeichert ● geschrieben in höherer Programmiersprache ● übersetzten des Programms durch Compiler in die Prozessorsprache, heißt auch Maschinensprache ● Compiler überprüft auf Syntaxfehler ● Linker oder Binder bindet Bibliotheken/Header eindeutige ● Laden des Programms vom Loader oder Lader -> Programmstart ● Debugger hilft bei Fehlersuche 11 SE 2.3. „Hello World“ ● Einstiegsprogramm 1. #include <stdio.h> 2. 3. int main (void) 4. { 5. /*Hello World Programm*/ 6. printf („\n Hello World!! \n“); 7. retung 0; 8. } 1. Anweisung an Präprozessor Standartbibliothek stdio.h einzufügen Standard Ein-/Ausgabefunktion Kopfzeile heißt „Globaler Deklarationsblock“ Quellcode wird vor dem Compilieren vom Präprozessor bearbeitet 3. main ist Hauptfunktion - Beginn eigentliches C-Programm wird immer sofort nach Programmstart aufgerufen 4. { Beginnn des ersten Blocks Blockklammer 5. Kommentarblock mit /* */ - Kommentar für eine Zeile // 6. printf ist Ausgabefunktion aus stdio.h Text in () \n neue Zeile „ zeigen, dass es sich um reinen Text handelt und nicht um Programmanweisungen Zeile Endet mit ; 7. return 0 gibt OS den Integer-Wert 0 zurück -> OS weiß, dass Programm wurde erfolgreich beendet 8. } Ende des ersten Blocks 12 SE 3. Elementare Datentypen – Initialisierung, Deklaration und Verknüpfung 3.1. Variablen ● verarbeitende Daten und Befehle werden im Hauptspeicher abgelegt ● Speicherbereich muss auf der untersten Maschinenebene reserviert werden ● geschieht durch Label ● Varaible ist Platzhalter für solchen Speicherbereich ● wird in höherer Programmiersprache komfortabel definiert und beliebig benannt ● Durch Datentyp wird angegeben wieviel Speicherplatz die Variable brauchen wird ● Format: Datentyp, Name ● jetzt kann der Variablen beliebige Werte mit dem gleichen Datentyp zugeordnet werden 3.2. Identifier (Bezeichner) ● ● ● ● ● ● Name für Vriable Name besteht aus Buchstaben, Ziffern und Unterstrich keine Ziffer am Anfang case sensitive Standard-Identifier wie printf dürfen nicht überschrieben werden in KamelSchreibWeise 3.3. Syntax ● Folge von Basiselementen ● Leerzeichen, Tabs und Zeilenumbrüche werden überlesen ● Syntax ist korrekte Aneinanderreihung von Befehlen, also prinzipiell ausführbar ● Samentisch korrekt heißt, dass das Programm sein Pflichtenheft erfüllt, also keine Endlosschleifen, Erfüllung aller Voraussetzungen 13 SE 3.4. Die 5 atomaren Typen ● ● ● ● ● Buchstaben character char Ganze Zahlen integer int Gleitkommazahlen floating-point float doppelte genaue float double float.-p. double ohne Wert valueless void ● void ist eigentlich kein eigener Datentyp ● char sind einzelne Buchstaben, Ziffern oder Sonderzeichen – ASCII-Code 3.4.1. Modifizierung der atomaren Typen ● alle ausschließlich void ● signed, unsigned, long, short ● Platzsparen für Computer bei kleinen Programmen sinnlos ● bei Steuerungen essentiell ● signed ist standardwert für alle Datentypen ● unsigned schränkt Wertebereich auf positive Zahlen ein 14 SE ● long Ganzzahlvaraible wie int auf einem 32-Bit Rechner gleicher Datenbereich wie int nicht aber auf einem 16-Bit Rechner long double • 80 Bit Gelitkommazahl ● short zum Platzsparen verkleinert int um die Hälfte 3.5. enum und typedef ● ● ● ● ● ● ● ● definieren eigener Datentypen durch typedef Aufzählung einer konstanten Reihe festlegen durch enum (enumeration) Elemente werden von 0 an durchnummeriert Konstanten und neu eingeführte Datentypen in GROSSBUCHSTABEN in geschweiften Klammern und mit Komma getrennt nach der geschlossenen Klammer den Namen des Datentyps erhöht Lesbarkeit des Programms es kann dem bestimmten Datentyp kein Wert zugewiesen werden, der nicht der Aufzählung entspricht ● Beispiel typedef enum {ROT, GRUEN, GELD, GELB-ROT, GEL_BLINK} AMPEL; AMPEL AmpelSafranberg, AmpelAlbeckerSteige; AmpelSafranberg = GRUEN; AmpelAlbeckerSteige = GELB; vorheriges ist das gleiche wie: AmpelAlbeckerSteige = 3; 15 SE 3.6. Variablen Definition, Zuweisung und Verknüpfung ● Definition kann beliebige Werte des Datentyps annehmen Identifier mit Präfix in Abhängigkeit von Datentyp Definition mehrerer Variablen des gleichen Typs mit Komma getrennt hintereinander • i int • c char • f float • d double ● Initialisierung erfolgt durch Gleichheitszeichen nicht mathematische Überprüfung auf Gleichheit nur Werte, die dem Datentyp entsprechen es können schon mathematische Oprationen angewendet werden nicht initialisierte Variablen verwenden den zufälligen Wert, der an dem Bereich im Hauptspeicher im Moment gespeichert ist auf den die Varaible abgebildet wird sicherheitshalber alle Variablen vor erstem gebraucht zu initialisieren am besten direkt nach Definition ● Verknüpfung ausführen von Funktionen 16 SE ● Beispiel Definition int iSummand1; int iSummand2; int iSummand3 = 3, iSummand4 = 4; float fPi, fExpo_Test; double dPiQuadrat; char cZeichen1, cZeichen2; ● Beispiel Initialisierung iSummand1 = 38; iSummand2 = iSummand1; fPi = 3,141; fExpo_Test = 7,1E-5; cZiffer1 = 'c'; • einfacher Buchstabe in ' ' längerer Ausdruck in „ „ cZiffer2 = cZiffer1; Definition und Initialisierung • char cZeichen = 0; • int iIndex = 0' • float fTemp = 0.0f; • double dFaktor = 0.0; ● Beispiel Zuweisung int iSummand3, iFaktor; iFaktor = 5; iSummand3 = (iSummand1 + iSummand2) * iFaktor; • Punkt vor Strich! dPiQuadrat = fPi * fPi 3.7. char in ASCII = int Zeichen werden im ASCII-Code codiert und als Zahl gespeichert char können mit und ohne Vorzeichen gespeichert werden macht kein Sinn für Buchstaben spart aber Speicherplatz, wenn sie als Zahlen betrachtet werden ein char braucht 1 Byte Speicherplatz, ein short int schon 2 Byte Zeichen als char werden internt als Zahlen nach der ASCII-Tabelle gespeichert ● bei int kann auch mit Buchstaben operiert werden ● Beispiele char cZeichen1; cZeichen1 = 'A' ist das gleiche wie cZeichen1 = 65 int iZahl; iZahl = 'A' + 'B'; Ergebnis wäre 131 (65 + 66) iZahl = 'A' + 32 Ergebnis wäre 'a' oder 97 ● ● ● ● ● ● 17 SE 18 SE 3.8. Ausführen eines Programms ● Definition und Initialisierung im Speicher werden die Speicherplätze für die Varaiblen belegt int iX = 1, iY = 2, iZ = 3, iSum=0; ● Zuweisung iSum = iX + iY – iZ; ● Im Speicher itemp1 = iX; itemp 1 += iY; itemp1 -= iZ i += a ist i = i + a 3.9. Die Ausgabefunktion printf ● printf („Das Ergebnis von %d + %d - %d = %d \n“, iX, iY, iZ, iSum) ● ● ● ● %d ist Format-Element/Format-String wird durch Argumente hinter Komma ersetzt Zeichenkette heißt String \n für neue Zeile, \t für Tab 19 SE 3.9.1. Format Elemente ● %[Flags][Feldbreite][.Genauigkeit]Konvertierungszeichen ● Flags + '' - mit Vorzeichen ausgeben mit führendem Leerzeichen ausgeben im Feld linksbündig ausgeben ● Feldbreite Länge des Ausgabefeldes ● Genauigkeit Anzahl der Nachkommastellen mit Punkt davor! ● Konvertierungszeichen d,i Dezimalzahl u Dezimalzahl ohne Vorzeichen o Oktalzahl x,X Hexadezimalzahl f Gleitkommazahl e,E Exponentialzahl c einzelnes Zeichen s Ausgabe eines Strings bis '\0' p Adresse in Hexadezimalform (pointer) ● Elemente in [] sind optional ● im Bereich der Argumentenliste kann auch gerechnet werden 20 SE 3.10. Die Einlesefunktion scanf ● scan formatted ● Bedienschnittstelle ist Interaktion zwischen Benutzer und Programm durch Ein- und Ausgabe ● Vor der Eingabe erfolgt eine Eingabeaufforderung mit printf scanf („Format-String“ [, Argumentenliste]); scanf („ %d“, &iX); scanf („%f %1f, &fFloat1, &dDouble); Das Leehrzeichen vor %d bewirkt, dass scanf Leerzeichen, Tabs und Returns (sogenannte White-space-Zeichen) überliest ● & ist Adressoperatort ● 1 vor f gibt spezielle Größenangabe an ● ● ● ● 3.11. die Darstellung von Umlauten ● ● ● ● ● ● ● ● ● Umlaute werden nicht richtig dargestellt codierung durch Hex-Code ä \204 Ä \216 ö \224 Ö \231 ü \201 Ü \232 ß \341 3.12. Die Modulo-Operation ● ● ● ● „Rechnen mit Rest“ es wird bei einer Division als Ergebnis der Rest ausgegeben Operator ist das %-Zeichen 5%2=1 21 SE 4. Operatoren, Ausdrücke & Funktionsaufrufe 4.1. Operanden & Operatoren ● Operanden: Konstanten Variabeln Funktionsaufrufe andere Ausdrücke ● Operatoren: +, -, *, / % = +=, -=, *=, /=, %= ++, - &&, || !, &, |, ~ <, >, <=, >0 *, ? uvm 4.1.1. Klassifikation der Operatoren nach Anzahl der Operanden ● unäre Operatoren z.B. ++ erhöht Wert um 1 Inkrementfunktion ● binäre Operatoren z.B. a + b ● drei Operanden (a>b) ? c=a : c=-a; Ordne a c zu, wenn a >0, sonst -a Betragsfunktion 4.1.2. Inhaltliche Klassifikation von Operatoren ● Stärke von C ist die Vielfalt an Operatoren ● Arithmetische Operatoren unär Minus postfix Inkrement ++ prefix Inkrement postfix Dekrement -- -x x negiert x++ eval. dann um 1 erhöhen ++ ++x um 1 erhöhen, dann eval x-eval. dann um 1 reduzieren 22 SE prefix Dekrement ---x um 1 reduzieren, dann eval (Evaluation bedeutet ausführen eines Befehls, z.B. printf) binär Addition + x+y x plus y Subtraktion x–y x minus y Multiplikation * x*y x mal y Division / x/y x durch y Modulo-Fkt. % x%y x modulo y Stellung des Operators wichtig, z.B. Minus für Negation und Subtraktion In-/Dekrement nur für Zähler-Variable, meist int -> indexgesteuerte Schleifen Beispiel mit Ausgabe im Kommentar int iZahl1 = 2, iZahl2 = 23; printf(„iZahl1:%d\t iZahl2:%d\n“, iZahl1, iZahl2 ); /*iZahl1:2 iZahl2:23*/ printf(„iZahl1:%d\t iZahl2:%d\n“, iZahl1++, iZahl2-- ); /*iZahl1:2 iZahl2:23*/ printf(„iZahl1:%d\t iZahl2:%d\n“, iZahl1, iZahl2 ); /*iZahl1:3 iZahl2:22*/ printf(„iZahl1:%d\t iZahl2:%d\n“, ++iZahl1, --iZahl2 ); /*iZahl1:4 iZahl2:21*/ printf(„iZahl1:%d\t iZahl2:%d\n“, iZahl1, iZahl2 ); /*iZahl1:4 iZahl2:21*/ 23 SE Seiteneffekt wenn nicht klar, ob In-/Dekrement-Befehl zuerst evaluiert wird von Compiler zu Compiler verschieden trennen in einzelnen Ausdrücken sicherer Beispiel: int iZahl = 5, iErgebnis; ... iErgebnis = iZahl * iZahl++; entweder: oder iErgebnis = 5 * 5; also 25 iErgebnis = 6 * 5; also 30 sicherheitshalber: iErgebnis = iZahl * iZahl; iZahl++; iZahl++; iErgebnis = iZahl * iZahl; oder: ● Zuweisungs-Operatoren Zuweisung Additive Zuw. Subtrak. Zuw. Multik. Zuw. Divid.. Zuw. Modulo Zuw. = += -= *= /= %= Standard a=b Wert von b nach a arithmetisch a += b Wert von a + b nach a a -= b Wert von a – b nach a a *= b Wert von a * b nach a a /= b Wert von a / b nach a a %= b Wert von a % b nach a Verkürzt Gesamtfunktion a = a + b schneller und kompakter weil nur einmal Operatoren aufgerufen werden müssen ● Logische und Vergleichs-Operatoren (auch relationale) Vergleich größer > a>b 1 wenn a größer b, sonst 0 kleiner < a<b 1 wenn a kleiner b, sonst 0 größer gleich >= a >= b 1 wenn a größer oder gleich b, sonst 0 kleiner gleich <= a <= b 1 wenn a kleiner oder gleich b, sonst 0 gleich == a == b 1 wenn a gleich b, sonst 0 ungleich != a != b 1 wenn a ungleich b, sonst 0 24 SE logisches Und && logisches Oder logische Negat. Logisch a && b 1 wenn a und b nicht 0, sonst 0 || a || b 1 wenn a oder b nicht 0, sonst 0 ! !a 1 wenn a 0, sonst 0 Vergleichsoperatoren liefern 1 wenn Vergleichsbedinung erfüllt ist, sonst 0 weitere logische Operatoren müssen aus den 3 eifnachen Operatoren zusammengesetzt werden Beispiel: #include<stdio.h> int main(void) { int iNote = 1, iPunkte = 100; printf („Bitte Punkte eingeben: „) scanf („%i“, &iPunkte); iNote = 1 + (iPunkte < 90) + (iPunkte < 80) + (iPunkte < 65) + (iPunkte < 50) + (iPunkte < 26); printf )“\n Die Note ist: %i“, iNote); return0; } jeder Vergleich leifert entweder 0 oder 1, die Summe ist dann die Entnote nach dem Notenschlüssel ● Bitweise Operatoren wichtig für hardwarenahe Programmierung & bsp.: x = 7 & 20 7: 00111 z.B. Kontrollfunktion bei Sicherheits20: 10100 Netzwerktechnik 12: 01100 also x = 12 |, ~, <<, >> wird nicht weiter drauf eingeangen 25 SE ● Sonstige Operatoren Adressoperator Dereferenzierungsoperator Pfeil-Operator -> Punkt-Operator Konditionale Operator Kommaoperator & * . ?: , bsp.: (a>b0) ? c=a : c=-a; bsp.: int iZahl1 = 1, iZahl2 = 2; 4.2. Ausdrücke ● Ausdruck ist Verknüpfung von Operatoren mit Operanden ● jeder Ausdruck hat einen Typ und endet in einem Wert ● Reihenfolge der Auswertung nicht immer definiert Bsp.: x = fkt_1() + fkt_2();unklar ob fkt_1 oder fkt_2 zuerst ausgeführt wird wichtig bei der Abarbeitung von Steuerbefehlen ● Typ eines Ausdrucks ist von den Typen seiner Operanden abhängig ● Anweisung ist Aneinanderreihung von Ausdrücken, die mit einem Strichpunkt abgeschlossen ist 26 SE 4.2.1. Prioritäten von Ausdrücken ● Festlegung welcher Oprator welchen Operanden wann zu einem Zwischenergebnis verknüpft ● d.i. Reihenfolge der Evaluation ● Assoziativität gibt an wie Operatoren innerhalb einer Klassenebene ausgewertet werden ● Vorrangregel gibt an welcher Operator innerhalb eines Ausdrucks einer höheren Ebene zugeordnet wird Klasse primär unär multiplikativ Operator () [] -> . ! ~ ++ -- + - (type) * & Assoziativität links nach rechts links nach rechts +- links nach rechts << >> links nach rechts < <= > >= links nach rechts == != links nach rechts bitweises UND & links nach rechts bitw. exklus. ODER ^ links nach rechts bitw. inkls. ODER | links nach rechts && links nach rechts logisches ODER || links nach rechts Kondition ?: rechts nach links = += -= *= /= %= &= ^= |= <<= rechts nach links shift relational Geleichheit logisches UND Zuweisung Komma , hoch rechts nach links */% additiv Vorran g links nach rechts niedrig ● Punkt vor Strich wird automatisch eingehalten ● boolsches UND vor boolschem ODER -> (A&&B) || (C&&D) auch ohne Klammern das gleiche ● A – B % C entspricht A – (B % C) ● !A || !B entspricht (!A) || (!B) 4.2.2. Typen von Ausdrücken ● Ausdrücke representieren konstante, ganzzahligen oder gebrochen rationale Werte ● bestimmter Typ wird durch Cast-Operator erwirkt ● egal, ob gecasteter Wert von höherem oder niedererem Typ ist 27 SE ● meisten Operanden geben Ergebnis mit dem selben Typ wie ihre Operanden zurück double + double ergibt double float * float ergibt float int / int ergibt int (!!!) 1.0 / 2.0 ergibt 0.5 1 / 2 ergibt 0 bei downcast werden Kommastellen einfach abgeschnitten Ergebnis bei / und % unklar, wenn Operand negativ ist mögliche Lösung: einen der beiden Operanden als float deklarieren Implizite Typkonversion bei Typen-Mix ● werden mehrere unterschiedliche Typen an Operanden in einem Ausdruck verwendet, wird höchster Typ für Ergebnis ausgewählt ● hochcasten erfolgt nur für die Zeit der Berechnung, deklarierter Datentyp bleibt erhalten ● in Absteigender Reihenfolge: long double, double, floar, unsigned long, long unsigned int ● Beispiel: char c; int i; float f; double d; (c / i) + (f * d) - (f + i); ergibt ein double als Ergebnis int iZahl1 = 2, iZahl2 = 3; float fZahl3 = 1.0; fZahl3 = iZahl2 / iZahl1; fZahl3 ist 1.0 weil Ergebnis ein int ist 28 SE Explizite Typenkonversion – der Cast-Operator ● gesteuerte Konvertierung eines Ausdrucks ● Cast-Operator besteht aus: (Typ) Ausdruck ● Beispiel: int iZahl1 = 1, iZahl2 = 2; float fZahl3; fZahl3 = (float) iZahl2 / iZahl1; fZahl3 erhält den Wert 0.5 als float 4.3. math.h ● enthält mathematische Funktionen ● #include <math.h> 4.3.1. Trigonometrische und hyperbolische Funktionen ● Typenangabe links neben Funktionsnamen sagt aus von welchem Typ das Ergebnis der Funktion sein wird ● in den runden Klammern steht welches Argument die Funktion zur Bearbeitung erwartet ● double sin (double x); ● double cos (double x); ● double tan (double x); ● ● ● ● double double double double sin (x) Argumen im Bogenmaß cos (x) Argumen im Bogenmaß tan (x) Argumen im Bogenmaß asin (double x); arcsin (x) acos (double x); arccos (x) atan (double x); arctan (x) atan2 (double y, double x); arctan (y/x) ● double cosh (double x); ● double sinh (double x); ● double tanh (double x); Hyperbelfunktion Hyperbelfunktion Hyperbelfunktion 29 SE 4.3.2. Exp, Log und andere ● double exp (double x); ● double log (double x); ● double log10 (double x); x>0 ● double sqrt (double x); ● double ldexp (double x, int n); ● double pow (double x, double y); Exponentialfunktion e x natürlicher Logarithmus ln (x), x > 0 dekadischer Logarithmus lg (x) , ● double ceil (double x); Ceiling-Funktion, nächst höherer Ganzzahlwert Floor-Fkt., nächst niederer ● double floor (double x); Ganzzahlwert ● double fabs (double x); Quadratwurzel aus x, x >= 0 n x ∗2 x y x>0, y ganzzahlig Absolutbetrag |a| 5. Kontrollstrukturen in C ● Programmteile abhängig von Bedingungen oder aktuellen Zuständen zu durchlaufen ● Anweisungen wiederholt ausführen ● wesentliche Fähigkeit höherer Programmiersprachen ● 3 Klassen: Sequenz • einfache sequenzielle Aneinanderreihung • einmaliges Durchlaufen des ganzen Programms bedingte Verzweigung • abhängig von Bedingung • wird Programmteil durchlaufen oder nicht • Sprünge im Programm Iteration • gesteuerte Wiederholung • Schleifen im Programm 30 SE 5.1. Sequenz ● wird mit öffnender geschweifter Klammer begonnen und mit schließender abgeschlossen ● Im Block kann vor Programmanweisung eine eine Deklarationsliste folgen Syntax: { [Delarations-Liste] [Anweisungs-Liste] } 5.1.1.Deklarations-Liste: Variablen und Konstanten ● Benannte Konstanten const double dPi = 3,1415; ● Literal-Konstanten int iIndex = 0; double dTest = 0.0; iIndex = iIndex + 1; ● Kostante Ausdrücke dTest = 3,1415 * 3,1415; dTest = dPi * dPi; ● Symbolische Konstanten #define PI 3,14 int main() { double dFlaeche = 0.0, dRadius = 1.0; dFlaeche = dRadius * dRadius * PI; return0; } 31 SE ● #define ist Präprozessoranweisung ● eignet sich gut um am Anfang eines Programms Parameter zu deklarieren ● können einfach geändert oder angepasst werden, ohne dass im Programm was geändert werden muss ● ● ● ● Präprozessor wird vom Compiler gestartet erster Arbeitsgang beim compilieren durchsucht Quelltext auf # Präprozessoranweisungen enden nicht mit ; 5.1.2. Verschachtelte Sequenzen ● Variablendefinitionen gelten nur innerhalb eines Struckturblocks -> logale Variablen ● und im Struckturblock enthaltenen Struckturblöcken ● Beispiel zur Kreisberechnung #include <stdio.h> #define PI 3,14 int main() { //Deklarationsliste double dRadius = 1.0; //Anweisungsliste { //Deklarationsliste double dUmfang = 1.0; //Anweisungsliste dUmfang = 2 * dRadius * PI; printf (\n Der Umfang betraegt %1f“, dUmfang); } { //Deklarationsliste – dUmfang ist hier nicht mehr Verfügbar double dFlaeche = 1.0; //Anweisungsliste dFlaeche = dRadius * dRadius * PI; printf /“\n Die Fläche betraegt %1f“, dFlacehe); } return 0; } 5.2. Bedingte Verzweigung – if-Anweisung ● einfachster Fall der bedingten Verzweigung ● Syntax: if (Ausdruck) Strukturblock if else Struckturblock else 32 SE ● Beispiel: if (...) // Einzelanweisung printf („test); else printf (“something else“); if (...) //Anweisungsliste { //Anweisungen } ● Wahrheitswert 0 für falsche, 1 und alle anderen Werte für wahr ● Überprüfen auf Gleichheit z.B. eines Indexes mit doppeltem Gleichheitszeichen == (einfaches ist Variablenzuweisung) ● wenn auf else zwei Zeilen folgen, aber diese nicht mit {} in einem Block zusammengefasst sind, wird die zweite Zeile immer ausgeführt 5.2.1. logische Operatoren ● ● ● ● ● ● == <= >= < > != gleich kleiner gleich größer gleich kleiner größer ungleich iIndex == 2 'B' > 'A' 5.2.2. Verschachtelte If-Anweisung ● ● ● ● überprüfen weiterer Bedingungen innerhalb einer Bedingung jedes else wird vom Compiler dem nächstgelegenen if zugeordnet andere Zuordnung durch Blockklammern übersichtilcher wenn if immer auf einer Ebene ● Beispiel: Suchen kleinster Wert aus drei Werten { if (a < b) if (a < c) printf („\n der kleineste Wert ist a \n“) else printf („\n der kleineste Wert ist c \n“) else if (b < c) printf („\n der kleineste Wert ist b \n“) else 33 SE printf („\n der kleineste Wert ist c \n“) } 5.3. Bedingte Verzweigung – Mehrfachauswahl mit switch ● Überprüfen eines Ausdrucks auf mehrere Fälle ● entsprechende unterschiedliche Anweisungen ● trifft keine der Bedingungen ein wird default-Zweig durchlaufen ● zu überprüfender Ausdruck in runden Klammern nach switch ● Block mit geschweiften Klammern ● jeder Fall begint mit case, dann in die Bedingung und nach Doppelpunkt die Anweisung ● am Ende breake, so dass Switch-Block nach erfolgreichem durchlaufen einer Anweisung verlassen wird ● nach default geschlossenen geschweifte Klammer ● Beispiel: einfacher Taschenrechner Variablen schon eigegeben char in einfachen Gänsefüßchen! char cRechenzeiche; ... swtich (cRechenzeiche) { case '+' : fErgebnis = fOperand1 + fOperand2; break; case '-' : fErgebnis = fOperand1 - fOperand2; break; case '*' : fErgebnis = fOperand1 * fOperand2; break; case '/' : fErgebnis = fOperand1 / fOperand2; break; default : //unzulässiger Operator printf („falsche Operation!“); break; 5.4. Bedingte Iteration 5.4.1. Prüfen am Anfang – while ● auch Wiederholungsanweisung oder Schleife genannt ● wird so lange ausgeführt bis Bedingung nicht mehr erfüllt ist 34 SE ● wenn Bedingung von Anfang an nicht erfüllt ist, wird Schleife übersprungen ● Einzelanweisung oder Anweisungsliste in {} ● Kriterium, das bei der Bedingung geprüft wird, muss im Rumpf irgendwie verändert werden, sonst Endlosschleife 35 SE ● Syntax: while (Bedingung) Strukturblock ● Beispiel Älteste Person im Raum: #include <stdio.h> int main(void) { //Variablendeklaration int iAlter = 0, iMaxAlter = 0, iAnzahlPersonen = 0, iIndex = 1; //Anweisungsliste printf („\n Geben Sie die Anzahl der Personen an:“); scanf (%d“, &iAnzahlPersonen); while (iIndex <= iAnzahlPersonen) { printf („Alter der %d-ten Person eingeben:“, iIndex); scanf („%d“, %iAlter); if (iAlter > iMaxAlter) iMaxAlter = iAlter; iIndex++; //Änderung des Bedingungskriteriums } printf („\n Die aelteste Person ist %d Jahre alt!“, iMaxAlter); return 0; } 5.4.2. Prüfen am Ende – do ... while ● zuerst Rumpf ausführen ● dann überprüfen ● und evt. nochmal durchlaufen wenn Bedingung wahr ● Schleife wird unabhängig von Bedingung einmal ausgeführt 36 SE ● Syntax: do Strukturblock while (Bedingung) ● Beispiel Älteste Person im Raum: //siehe oben do { printf („Alter der %d-ten Person eingeben:“, iIndex); scanf („%d“, %iAlter); if (iAlter > iMaxAlter) iMaxAlter = iAlter; iIndex++; //Änderung des Bedingungskriteriums } while (iIndex <= iAnzahlPersonen); Wenn der Index von Anfang an größer ist als die Anzahl der Personen wird ein falsches Ergebnis ausgegeben Schleifenart muss abhängig vom Problem ausgesucht werden 37 SE 5.5. gesteuerte Iteration – for ● Sonderform der Iteration mit Test vor Schleifeneintritt ● werden verwendet wenn einen Schleife durch einen Zählindex gesteuert wird ● Einzelanweisung oder Anweisungsblock in {} ● kann durch while-Schleife dargestellt werden ● Vorteil ist zentrale Festlegung der Initialisierung, der Bedingung und der Weiterschaltung ● nach for folgt dreiteilige Schleifensteuerung Initialisierung Setzen des Schleifenzählers auf Startwert Bedingung muss erfüllt sein, dass Schleife durchlaufen wird Veränderungsanweisung wird nach jedem Schleifendurchlauf durchgeführt durch Semikolon ; getrennt ● Syntax: for ([Initialisierung]; [Bedingung]; [Veränderung]) Struckturblock ● Beispiel Älteste Person im Raum: //siehe oben for (iIndex = 1; iIdnex <= iAnzahlPersonen; iIndex++) { printf („Alter der %d-ten Person eingeben:“, iIdnex); scanf („%d“, &iAlter); if (iAlter > iMaxAlter) iMaxAlter = iAlter; } 38 SE 5.5.1. geschachtelte Schleifen mit for ● weitere Schleifen in einer Schleife ● können auch gemischte Formen der Iteration sein ● Beispiel Tabelle aller Produkten aus 1 – 10 x 1 – 10 #include <stdio.h> int main(void) { int j = 0, k = 0; //Indexvariabeln printf (" 1 2 3 4 5 6 7 8 9 10\n"); printf ("---------------------------------\n"); } for (j=1; j<=10; j++) //Zeilenindex j { printf ("%2d|",j); //aktuelle Zeile for (k=1; k<=10; k++) printf ("%3d", j*k); //Produkte printf("\n"); } printf("\n"); return 0; 39 SE 5.6. Kontrollstrukturen im Nassi-Schneidermann-Diagramm ● Sequenz / Folge: Variablendeklaration: index := 0, index ∈ ℤ Anweisung: index := index + 1 Einzelanweisungen und Anweisungsblöcke in quadratischen Kästchen ● Symbole == Vergleich := Zuweisung <a> Variable a ≠ / != Ungleich >/< Größer / Kleiner 40 SE 6. Arrays, Strings und Pointer 6.1. Arrays ● ● ● ● ● Reihung, Felder oder Vektor vereinfacht Verwendung vieler Varaiblen/Bezeichner ansprechbar über Indexwert → Schleifen einfacherer Programmänderung durch Änderung der Länge des Arrays Länge oft als #define Länge ● Syntax: Datentyp Bezeichner [Länge] = {Initialisierungsliste}; Datentyp gilt für alle Element Bezeichner mit A für Array, dann Kleinbuchstabe für Datentyp, dann mit Großbuchstabe beginnend Bezeichner Länge ganze Zahl größer 0 in eckigen Klammern – hier können auch Ausrücke stehen Initialisierungsliste nicht notwendig direkt nach Deklaration – Werte durch Komma getrennt und in geschweiften Klammern werden in der Initialisierungsliste nicht alle Elemente angegeben, werden diese mit 0 belegt Länge muss bei Deklaration entweder angegeben werden oder durch Initialisierung festgelegt werden → Klammer kann leer gelassen werden die Elemente von mit static deklarierte Arrays werden auf 0 gesetzt globale Arrays werden ebenfalls auf 0 gesetzt #include <stdio.h> int AiArray1[5]; int main() { static int AiArray2[5]; int AiArray3[5]={1}; int AiArray4[5]; int i=0; printf("Index \tArray 1\tArray 2\tArray 3\tArray 4\n"); for (i=0; i<5; i++) printf ("%d\t%d\t%d\t%d\t%d\n", i, AiArray1[i], AiArray2[i], AiArray3[i], AiArray4[i]); return 0; } Index 0 1 2 3 4 Array 1 0 0 0 0 0 Array 2 0 0 0 0 0 Array 3 1 0 0 0 0 Array 4 0 -1074865512 -1209377266 -1208661639 134520820 ● Länge wird von 0 an gezählt, das ist Basisadresse, dann je nach Datentyp 41 SE Anzahl an Speicherplätzen weiter gezählt → Index ● n-Dimensionaler Array läuft von 0 bis n-1 ● Länge kann auch durch #define Varaible... am Programmanfang definiert werden ● Feldlänge nicht überschreiten -> sonst Absturz., überschreiben anderer Daten, Endlosschleife... ● Feldüberschreitung wird vom Compiler nicht überprüft ● Operationen können nur für jedes Element einzeln durchgeführt werden 6.1.1. Grenzüberschreitung #include <stdio.h> int main() { int AiTest[10]; int i; for(i=0; i<=10; i++) AiTest[i]=i; } /* !!Bereichsüberschreitung!! */ for(i=0; i<=10; i++) printf("%d, ",AiTest[i]); printf("\n"); return 0; ● es wird ein Array mit 10 Feldern deklariert ● in der for-Schleife wird von 0 bis 10 hochgezählt, sprich in 11 Schritten ● for(i=0; i<10; i++) // ohne '='-Zeichen richtig 42 SE 6.1.2. Sortieralgorithmus #include<stdio.h> #define DIM 6 int main(void) { float AfZahl[DIM], fTemp=0; int i=0, j=0; for (i=0; i<DIM; i++) { printf ("\n Bitte Zahl eingeben: "); scanf ("%f", &AfZahl[i]); } //DIM -1 weil unten zu j eins dazugezählt wird [j+1] //Zähle von DIM-1 runter auf 0 for (i=DIM-1; i>0; i--) { //zähle von 0 bis i hoch, 2. Schleife verlgeiche die Zahl j und die darauf folgende Zahl for (j=0; j<i; j++) { //wenn j+1 größer als j, tausche die beiden Zahlen Zwischenspeichern in fTemp if (AfZahl[j+1] < AfZahl[j]) { fTemp = AfZahl[j]; AfZahl [j] = AfZahl [j+1]; AfZahl [j+1] = fTemp; } } } printf ("\n\n Ausgabe Zahlen sortiert: \n"); for (i=0; i<DIM; i++) printf ("%.2f ", AfZahl[i]); return 0; } ● sortiert 6 eingegebene Zahlen nach der Größe und gibt diese aus ● Bubble-Sort-Algorithmus, weil die Zahlen sortiert werden, wie eine Blase im Wasser nach oben schwimmen ● Anzahl der Zahlen kann durch DIM am Anfang festgelegt werden ● typisches EVA-Prinzip – Eingabe, Verarbeitung, Ausgabe 43 SE ● Nassi Schneridermann Diagamm für Buble-Sort-Algorithmus 6.1.3. Zählen der Elemente eines Arrays #include <stdio.h> #define SIZE 10 int main() { int AiZahlen[SIZE] = { 0 }; } printf("Anz. Elemente : %d\n",sizeof(AiZahlen)/sizeof(int)); return 0; 6.1.4. Vergleichen von Arrays for(i=0; i<10; i++) { if( AiArray1[i] == AiArray2[i] ) continue; else { printf("Unterschied an Position %d\n",i); break; } } 44 SE 6.2. Strings ● Zeichenketten ● wird mit \0 abgeschlossen ● nutzbare Bereich um ein Zeichen kleiner ( wegen \0) ● Syntax: char szBezeichner [Länge] = „String“; Länge definiert Anzahl der Elemente Länge kann bei direkter Initialisierung offen gelassen werden ● \0 wird automatisch angehängt → immer ein Zeichen mehr Deklarieren, als für Buchstaben benötigt wird ● Operationen und Zuweisungen nur für einzelne Elemente ● Einhaltung der Grenzen! ● scanf und printf mit %s scanf interpretiert Leerzeichen, Tabs und Return als Trennzeichen zwischen Strings scanf fügt automatisch \0 ein Adressoperator bei scanf nicht notwendig (scanf(„%s“, szString);) char hallo[]={'H','a','l','l','o', ' ', 'W','e','l','t','\n','\0'}; char hallo[] = {"Hallo Welt\n"}; beide Deklarationen sind identisch 6.2.1. Zeichenzahl eines Arrays auslesen for(i=0; hello1[i] != '\0'; i++); printf("Länge von '%s' = %d Zeichen\n",hello1,i); 45 SE 6.2.2. Beispiel suchen und Ersetzten eines Worts in einem String #include <stdio.h> char undbig[] = { "Hund und Katze sind nicht ohne " "Grund des Menschens bester Freund\n" }; int main() { int i; for(i=0; undbig[i] != '\0'; i++) { if(undbig[i-1]==' '&& (undbig[i]=='u'||undbig[i]=='U')) { if(undbig[i+1]=='n'&& undbig[i+2]=='d' && undbig[i+3]==' ') { undbig[i]='U'; /* n zu Grossbuchstabe konvertieren (N) */ undbig[i+1]-=32; /* d zu Grossbuchstabe konvertieren (D) */ undbig[i+2]-=32; } } } printf("%s",undbig); return 0; } ● Stringkonstante über zwei Zeilen, wenn mit „ am Ende der einen und „ am Anfang der nächsten ● oder \ am Ende der ersten char array[] = {"Eine Zeichenkette über" "2 Zeilen\n"}; /* Alternative */ char array[] = {"Eine Zeichenkette über \ 2 Zeilen"}; 46 SE 6.2.3. Beispiel Grenzüberschreitung ● Source: char szString[]=“Affe“; printf(„Kontrollausgabe: %s \n“,szString); szString[4] = 'x'; //-> \0 überschrieben printf („Kontrollausgabe. %s \n“, szString); ● Ausgabe: Kontrollausgabe: Affe Kontrollausgabe. Affex� ԩ�8թ�P�����8թ�P�� Speicherschrott!!! ● Verhinderung der Grenzüberschreitung bei scanf durch Angabe der Feldbreite ● scanf(„%20s“, szString); ● Feldbreite auf 20 festgelegt, muss direkt dezimal angegeben werden, keine Variabeln möglich 6.2.4. Einlesen von Strings durch gets ● Syntax: gets (Bezeichner_String); gets (szText); ● keine Feldbreite einstellbar -> Gefahr des Überschreitens groß ● deutet Leerzeichen als Leerzeichen... -> einlesen von ganzen Sätzen 6.2.5. Einlesen von Strings durch fgets char fgets(char string,int anzahl_zeichen,FILE stream); fgets(str, 100, stdin); ● in der 2. Zeile werden bis zu 100 Zeichen aus dem Tastaturbuffer in den String str geschrieben ● hängt am Ende eines Strings immer ein \n an 47 SE 6.3. String.h strcat char *strcat(char *s1, const char *s2); ● ● ● ● Pointer! Hängt s2 an das Ende von s1, wenn s1 genug Platz hat die Größe des Zielstrings kann nicht überprüft werden!!! die \0 am Ende des Zielstrings wird überschrieben strchr char *strchr(const char *s, int ch); ● suchen nach einem Zeichen im String ● gibt die Position des Zeichens ch zurück, wenn es nicht vorhanden ist gibt sie 0 zurück #include <stdio.h> #include <string.h> int main() { char str[] = "Ein String mit Worten"; printf("%s\n",strchr(str, (int)'W')); return 0; } Worten ● gibt alle Zeichen ab W aus 48 SE strcmp int strcmp(const char *s1, const char *s2); ● ● ● ● Strings vergleichen gibt 0 zurück für gleichgroße Strings einen Wert kleiner als 0 wenn s1 kleiner als s2 einen Wert größer als 0 wenn s1 größer als s2 #include <stdio.h> #include <string.h> void String_Vergleich(char s1[], char s2[]) { int ret; ret = strcmp (s1, s2); if(ret == 0) printf("%s == %s\n",s1, s2); else printf("%s %c %s\n",s1,( (ret < 0)?'<':'>'), s2); } int main() { char str1[] = "aaa"; char str2[] = "aab"; char str3[] = "abb"; } aaa aaa abb aaa String_Vergleich(str1, String_Vergleich(str1, String_Vergleich(str3, String_Vergleich(str1, return 0; str2); str3); str2); str1); < aab < abb > aab == aaa 49 SE strcpy char *strcpy(char *s1, const char *s2); ● Kopieren eines Strings in einen adressierten Char-Vektor ● Speicherbereich wird nicht auf Größe überprüft!!! #include <stdio.h> #include <string.h> int main() { char ziel_str[50]; char str1[] = "Das ist "; char str2[] = "ein "; char str3[] = "Teststring"; strcpy(ziel_str, str1); /* Ein umständliches Negativbeispiel */ strcpy(&ziel_str[8], str2); /* So ist es einfacher und sicherer */ strcat(ziel_str, str3); printf("%s\n",ziel_str); return 0; } ● mit strcpy(&ziel_str[8], str2); können auch Strings aneinander gehangen werden, ist aber wesentlich umständlicher als mit strcat strcspn int strcspn(const char *s1, const char *s2); ● einen Teilstring ermitteln #include <stdio.h> #include <string.h> int main() { char string[] = "Das ist ein Teststring"; int pos; pos = strcspn( string, "Ttg" ); printf("Erstes Auftreten von T, t oder g an Pos.: %d\n",pos); return 0; } Erstes Auftreten von T, t oder g an Pos.: 6 50 SE strlen size_t strlen(const char *s1); ● Länge eines Strings ermitteln ● gibt die Länge des Strings ohne das \0 zurück #include <stdio.h> #include <string.h> int main() { char string[] = "Das ist ein Teststring"; size_t laenge; laenge = strlen(string); printf("Der String \"%s\" hat %d Zeichen\n",string, laenge); return 0; } strncat char *strncat(char *s1, const char *s2, size_t n); ● hängt s2 an das Ende von s1 ● übergibt einen Integer, der die Anzahl der anzuhängenden Zeichen festlegt #include <stdio.h> #include <string.h> #define MAX 15 int main() { char string[MAX] = "Hallo "; char puffer[20]; /* Vorhandenen Platz in string ermitteln*/ size_t len = MAX - strlen(string)+1; printf("Ihr Name: "); fgets(puffer, 20, stdin); strncat(string, puffer, len); printf("%s",string); return 0; } 51 SE strncmp int strncmp(const char *s1, const char *s2, size_t n); ● vergleichen der ersten n Zeichen der beiden Strings ● Rückgabewert wie bei strcmp #include <stdio.h> #include <string.h> int main() { char str1[] = "aaaa"; char str2[] = "aabb"; int i; for(i = strlen(str1); i > 0; i--) { if(strncmp( str1, str2, i) != 0) printf("Die ersten %d Zeichen der Strings "\ "sind nicht gleich\n",i); else { printf("Ab Zeichen %d sind "\ "beide Strings gleich\n",i); /* Weiter vergleich sind nicht mehr nötig */ break; } } return 0; } Die ersten 4 Zeichen der Strings sind nicht gleich Die ersten 3 Zeichen der Strings sind nicht gleich Ab Zeichen 2 sind beide Strings gleich 52 SE strncpy char *strncpy(char *s1, const char *s2, size_t n); ● Kopiert n Zeichen aus s2 nach s1 #include <stdio.h> #include <string.h> #define MAX 20 int main() { char str1[MAX]; char str2[] = "Ein Teststring welcher laenger"\ " als 20 Zeichen ist"; /* MAX-Zeichen in str1 kopieren */ strncpy(str1, str2, MAX); /* Wichtig, String am Ende terminieren !! */ str1[MAX] = '\0'; printf("%s\n",str1); return 0; } Ein Teststring welch strpbrk char *strpbrk( const char *s1, const char *s2); ● gibt alle Zeichen aus s1 wieder, die hinter einem der Zeichen folgen, die in s2 angegeben sind #include <stdio.h> #include <string.h> int main() { char str1[]="Das ist ein Teststring"; char str2[]="ie"; } printf("%s\n",strpbrk(str1, str2)); return 0; ist ein Teststring 53 SE strrchr char *strrchr(const char *s, int ch); ● ermitteln des letzten Zeichens, das in ch angegeben wird und in s gesucht wird #include <stdio.h> #include <string.h> int main() { char string[20]; char *ptr; } printf("Eingabe machen: "); fgets(string, 20 , stdin); /* Zeiger auf die Adresse des Zeichens \n */ ptr = strrchr(string, '\n'); /* Zeichen mit \0 überschreiben */ *ptr = '\0'; printf("%s",string); return 0; ● Überschreiben des \n, das fgets automatisch am Ende eines Strings setzt mit \0 strspn int strspn(const char *s1, const char *s2); ● gibt erste Position an, an der ein Zeichen aus s1 nicht in s1 zu finden ist #include <stdio.h> #include <string.h> int main() { char string[] = "75301234-2123"; int pos = strspn(string, "0123456789"); printf("Position, welche keine Ziffer ist:"); printf(" %d\n",pos); /* 8 */ return 0; } Position, welche keine Ziffer ist: 8 54 SE strstr char *strstr(const char *s1, const char *s2); ● durchsuchen von s1 nach dem Teilstring, der in s2 angegeben ist #include <stdio.h> #include <string.h> int main() { char string[] = "Das ist ein Teststring"; char suchstring[] = "ein"; if( strstr(string, suchstring) != NULL) printf("Suchstring \"%s\" gefunden\n",suchstring); return 0; } Suchstring "ein" gefunden 55 SE strtok char *strtok(char *s1, const char *s2); ● Zertrennen von Strings ● s1 wird von Token aus s2 getrennt ● ein Token ist ein String, der keine Zeichen von s2 enthält #include <stdio.h> #include <string.h> int main() { char string[] = "Ein Teststring mit mehreren Worten\n" "und mehreren Zeilen.\t Ende\n"; int i=1; char *ptr; ptr = strtok(string, "\n\t "); while(ptr != NULL) { printf("% d. Wort: %s\n",i++,ptr); ptr = strtok(NULL, "\n\t "); } return 0; } 1. 2. 3. 4. 5. 6. 7. 8. 9. Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Wort: Ein Teststring mit mehreren Worten und mehreren Zeilen. Ende 56 SE 6.4. Mehrdimensionale Felder ● definiert als Feld von Feld von Feld... Datentyp iMatrix [Zeilen][Spalten] = { double dMatrix [3][4]; {Initialisierungsliste 1}, {Initialisierungsliste 2}, {Initialisierungsliste 3} }; Feld mit 3 Elementen, wobei jedem der 3 Elemente 4 weitere untergeordnet sind Zeilen kann bei direkter Initialisierung offen gelassen werden int Matrix[4][4] = { {0}, {1}, {0,1}, {0,0,1} }; ● hier wird eine 4x4 Matrix initialisiert und gleich deklariert ● alle Werte, die nicht auf 1 gesetzt sind 0 ● für jede weitere Dimension müssen neue geschweifte Klammern eingefügt werden int dreid[][][]= {1.Feldindex{2.Feldindex{3.Feldindex}}}; 57 SE 6.4.1. Beispiel Matrix Quadratwerte #include<stdio.h> int main(void) { float AdMatrix [] [2] = { {1.6, 2.43}, {5.634, 6.2345}, {4.123, 7.735} }; float AdQMatrix [3] [2]; int i=0, j=0; //AdMatrix ausgeben printf ("Ausgangsmatrix:\n"); for (i=0; i<3; i++) { for (j=0; j<2; j++) printf("%8.2f", AdMatrix[i][j]); printf("\n"); } //Quadrtrat der Matrix for (i=0; i<3; i++) { for (j=0; j<2; j++) AdQMatrix [i][j]= AdMatrix[i][j] * AdMatrix[i][j]; } //AdQMatrix ausgeben printf ("Quadrierte Matrix:\n"); for (i=0; i<3; i++) { for (j=0; j<2; j++) printf("%8.2f", AdQMatrix[i][j]); printf("\n"); } return 0; } %8.2f bei printf ist Feldbreite.Genauigkeit ● Ausgabe: Ausgangsmatrix: 1.60 2.43 5.63 6.23 4.12 7.74 Quadrierte Matrix: 2.56 5.90 31.74 38.87 17.00 59.83 58 SE 7. Funktionen ● ● ● ● Ablauf eines Programms beginnt immer mit der main Funktione!!! Unterprogramme lösen von Teilprobleme keine parallele Ausführung von Funktionen ● Vorteile: Übersichtlicherer Code wiederverwenden durch Erstellen eines Headers Zusammenfassen von sich wiederholenden Aufgaben einfacheres Verändern des Codes 7.1. Definition von Funktionen ● Syntax: Rückgabetyp Funktionsname (Parameter) { /*Anweisungsblock*/ } ● Hautpfunktion int main (void) Rückgabetyp ● Datentyp des Rückgabewertes ● ohne Rückgabewert → void Funktionsname ● eindeutiger Name ● gleiche Benennung wie Variablen ● zum Aufruf innerhalb einer Funktion Parameter ● optionale ● durch Datentyp und Name spezifiziert ● durch Komma getrennt ● kein Parameter → void oder garnichts Anweisungsblock ● wie bei normaler Funktion 59 SE 7.2. Funktionsaufruf/-deklaration/-definiton ● In Anweisungsblock Funktionsname und in Klammern Parameter #include <stdio.h> void hilfe(void) { printf("Ich bin die Hilfsfunktion\n"); } int main() { hilfe(); return 0; } ● Deklaration vor main-Funktion → Vorwärtsdeklaration ● Deklaration mit ; abschließen! ● Definition kann an beliebiger Stelle stattfinden ● nach Deklaration ● kein ; wegen Anweisungsblock #include <stdio.h> void func1(); void func2(); void func3(); void func1() { printf("Ich bin func1 \n"); func3(); } void func2(void) { printf("Ich bin func2 \n"); } void func3() { printf("Ich bin func3 \n"); func2(); } int main() { func1(); return 0; } ● Vorwärtsdeklaration hier unbedingt notwendig ● auch notwendig für die Verwendung von Funktionen über Dateigrenzen hinweg 60 SE 7.3. Lokale Variablen ● Lokalste Variable is die im Anweisungsblock 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main() { int i=333; if(i == 333) { int i = 111; printf("%d\n",i); /* 111 */ } printf("%d\n",i); /* 333 */ return 0; } ● gibt 333 \n 111 aus ● weil das i im ersten Anweisungsblock eine andere Variable ist, wie das i, das im zweiten Anweisungsblock deklariert wird ● eine Variable ist nur für den Anweisungsblock in dem sie steht und für Anweisungsblöcke, die in dem Anweisungsblocke enthalten sind in der sie deklariert wurde, gültig ● wenn in Zeile 9 nur i=111 steht ist die Ausgabe 111 \n 111 ● bei gleichnamigen Variablen ist immer die lokalste Variable gültig ● also die die dem Anweisungsblock am nächsten steht ● lokale Variablen in Funktionen verhalten sich genauso wie in Anweisungsblöcke #include <stdio.h> void aendern() { int i = 111; printf("In der Funktion aendern: %d\n",i); } int main() { int i=333; printf("%d\n",i); aendern(); printf("%d\n",i); return 0; } ● gibt 333 \n In der Funktion aendern: 111 \n 333 aus ● Varialbe muss aber in der neuen Funktion definiert werden 61 SE 7.4 Globale Variablen ● Sind in allen Funktionen gültig ● müssen im Voraus deklariert werden ● werden vor der ersten Funktion deklariert #include <stdio.h> int i=333; /* Globale Variable */ void aendern() { i = 111; printf("In der Funktion aendern: %d\n",i); /* 111 */ } int main() { printf("%d\n",i); aendern(); printf("%d\n",i); return 0; } /* 333 */ /* 111 */ ● gibt 333 \n In der Funktion aendern: 111 \n 111 aus #include <stdio.h> int i=333; /* Globale Variable i */ void aendern() { i = 111; /* Ändert die globale Variable */ printf("In der Funktion aendern: %d\n",i); /* 111 */ } int main() { int i = 444; printf("%d\n",i); aendern(); printf("%d\n",i); return 0; } /* 444 */ /* 444 */ ● hier gilt aber das gleiche für lokale Variablen ● gibt 444 \n In der Funktion aendern:111 \n 444 aus ● Varaiblen so lokal wir möglich und so global wie nötig 62 SE 7.5. Statische Variablen #include <stdio.h> void inkrement() { int i = 1; printf("Wert von i : %d\n",i); i++; } int main() { inkrement(); inkrement(); inkrement(); return 0; } ● gibt 1 \n 1 \n 1 aus ● Variable wird bei jedem Durchlauf der Fkt erneut mit 1 initialisiert #include <stdio.h> void inkrement() { static int i = 1; printf("Wert von i : %d\n",i); i++; } int main() { inkrement(); inkrement(); inkrement(); return 0; } ● gibt 1 \n 2 \n 3 aus ● statische Varialben verlieren nach Beendigung der Funktion nicht ihren Wert bzw. ihre Speicheradresse ● werden wo anders in der Hardware gespeichert ● statische Varaiblen müssen bei Deklaration initialisiert werden 63 SE 7.6. Datenaustausch zwischen Funktionen ● Funktion als Schnittstelle ● dadurch können globale Variablen gespart werden durch Werteübergabe Übertragen von Daten in die Funktion mittels Parameter durch Werterückgabe Daten aus der Funktion 7.6.1 Funktion mit Wertübergabe – call by value ● ● ● ● Werte in Klammern werden kopiert hier von z nach zahl können auch mehrere Werte mit Komma getrennt übergeben werden #include <stdio.h> void verdoppeln(int); void halbieren(int); ind dann void halbieren(int zahl) { zahl/=2; printf("Halbiert: %d\n",zahl); } void verdoppeln(int zahl) { zahl*=2; printf("Verdoppelt: %d\n",zahl); } int main() { int wahl, z; printf("Bitte geben Sie eine Zahl ein : "); scanf("%d",&z); printf("Wollen Sie diese Zahl\n"); printf("\t1.)verdoppeln\n\t2.)halbieren\n\nIhre Wahl : "); scanf("%d",&wahl); } switch(wahl) { case 1 : verdoppeln(z); break; case 2 : halbieren(z); break; default: printf("Unbekannte Eingabe\n"); } return 0; 64 SE 7.6.2. Funktion mit Wertrückgabe ● Ende der Funktion mit return ● Wert wird an aufrufende Funktion zurückgeliefert ● #include <stdio.h> int bignum(int a, int b) { if(a > b) return a; else if(a < b) return b; else return 0; /* beide Zahlen sind gleich groß */ } int main() { int wert1, wert2, big; printf("Bitte einen Wert eingeben: "); scanf("%d",&wert1); printf("Bitte noch einen Wert eingeben: "); scanf("%d",&wert2); } big = bignum(wert1, wert2); if(big != 0) printf("%d ist die größere der beiden Zahlen\n",big); else printf("Beide Zahlen haben denselben Wert\n"); return 0; 7.7. Die Hauptfunktion main() ● ● ● ● ● Funktion des Typs int Startup-Code wird beim Beginn des Prozesses erzeugt damit wird auch der Prozess wieder beendet zurückspringen zum Startup-Code mit return 0 er führt dann die Funktion exit aus und räumt auf, z.B. Freigabe der Speicherplätze... int main() { return 0; } ● Prozess ist Programm während seiner Ausführung 65 SE 7.8. Rekursive Funktionen ● Rufen sich selbst immer wieder auf ● ohne Abbruchbedingung gibt es einen Stack-Overflow int divide(int x, int y) { if(x>=y) return (1 + divide(x-y, y)); if(x) printf("Zahl nicht teilbar -> Rest: %d -> ", x); return 0; } ● z.B. Teilen mit Rest ohne / oder % zu verwenden Beispiele Fakultät #include <stdio.h> long fakul(long n) { if(n) return n * fakul(n-1); return 1; } int main() { printf("Fakultät von 5 = %ld\n",fakul(5)); printf("Fakultät von 9 = %ld\n",fakul(9)); return 0; } Beispiele Text invertieren #include <stdio.h> void reverse(char *s) { if(*s) reverse(s+1); putchar(*s); /* putchar gibt ein Zeichen aus */ } int main() { reverse("\nSome men interpret nine memos"); reverse("\nHALLO"); return 0; } 66 SE Beispiel Fibonacci #include <stdio.h> long fibo(long n) { if(n) return (n<=2) ?n:fibo(n-2)+fibo(n-1); } int main() { long f; long i=0; printf("Wie viele Fibonacci-Zahlen wollen Sie ausgeben:"); scanf("%ld",&f); while(i++ < f) printf("F(%ld) = %ld\n", i, fibo(i)); return 0; } Beispiele Größter gemeinsamer Teiler #include <stdio.h> unsigned long ggt(unsigned long a, unsigned long b) { if(a==b) return a; else if(a<b) return ggt(a,b-a); else return ggt(a-b,b); } int main() { unsigned long a,b; printf("ggt = größter gemeinsamer Teiler\n"); printf("Zahl 1: "); scanf("%lu",&a); printf("Zahl 2: "); scanf("%lu",&b); printf("Der ggT von %lu und %lu ist %lu\n", a, b, ggt(a,b)); return 0; } 67 SE Alternative GGT #include <stdio.h> unsigned long ggt(unsigned long a, unsigned long b) { unsigned long count; if(a==b) return a; else if((a%b)==0) return b; else for(count=b; count>0; count--) { if(((a%count) + (b%count)) == 0) return count; } } int main() { unsigned long a,b,c; printf("ggt = größter gemeinsamer Teiler\n"); printf("Zahl 1: "); scanf("%lu",&a); printf("Zahl 2: "); scanf("%lu",&b); if(a<b) { /*a und b vertauschen*/ c=a; a=b; b=c; } printf("Der ggT von %lu und %lu ist %lu\n", a, b, ggt(a,b)); return 0; } 68 SE Beispiel GGT für beliebig viele Zahlen #include <stdio.h> unsigned long ggt(unsigned long a, unsigned long b) { if(b==0) return a; return ggt(b, a%b); } int main() { unsigned long a,b; printf("ggt = größter gemeinsamer Teiler(mit 0 beenden)\n"); printf("Zahl> "); scanf("%lu", &a); printf("Zahl> "); scanf("%lu", &b); a=ggt(a, b); while(1) { printf("Zahl> "); scanf("%lu", &b); if(b==0) break; a=ggt(a, b); } printf("-------->ggt = %lu\n", a); return 0; } Beispiel Dezimalzahl zu Dualzahl #include <stdio.h> void dez2bin(unsigned long dez) { if(dez) { dez2bin(dez/2); printf("%lu", dez%2); } } int main() { unsigned long dezimal; printf("Dezimalzahl in Dualzahl konvertieren \n"); printf("Welche Zahl soll konvertiert werden: "); scanf("%lu", &dezimal); printf("Dezimal = %lu \nDual = ",dezimal); dez2bin(dezimal); printf("\n"); return 0; } 69 SE 7.9. Übergabe von Arrays an Funktionen void function(int feld[], int MAX) ● Indexwert für Größe wird nicht angegeben, weil der Funktion nicht bekannt ist, wieviele Werte der Array hat ● empfehlenswert die Angabe als Argument mitzugeben – oben MAX ● Arrays werden über Zeiger weitergegeben → keine Kopie der ganzen Werte, sondern lediglich der Anfangsadresse ● nicht call-by-value, sondern call-by-reference ● function(&feld[0], MAX); ist identisch zu dem oben #include <stdio.h> struct array{ int wert[3]; }; void output_array(struct array z) { int i; for(i=0; i < sizeof(struct array)/sizeof(int); i++) printf("%d\t",z.wert[i]); printf("\n"); } int main() { struct array new_array; new_array.wert[0] = 10; new_array.wert[1] = 20; new_array.wert[2] = 30; /* call-by-value */ output_array(new_array); return 0; } 7.10. Arrays aus Funktionen zurückgeben ● Können nicht als Rückgabetyp von Funktionen definiert werden #include <stdio.h> struct array{ int wert[3]; }; struct array init_array() { int i; struct array z; for(i=0; i < sizeof(struct array)/sizeof(int); i++) { printf("Wert %d eingeben: ",i); scanf("%d",&z.wert[i]); } return z; } 70 SE void output_array(struct array z) { int i; for(i=0; i < sizeof(struct array)/sizeof(int); i++) printf("%d\t",z.wert[i]); printf("\n"); } int main() { struct array new_array; /* Array als Rückgabewert in einer Struktur verschachtelt */ new_array=init_array(); /* call-by-value */ output_array(new_array); return 0; } 71 SE 72 SE 73 SE ● 74 SE 8. Präprozessoranweisungen ● Teil dds Compilers ● nimmt Änderungen am Quelltext vor dessen Übersetzung vor ● Aufgaben entfernen Zeilenumbrüche mit Backslash am Anfang entfernen von Kommentaren entfernen von Whitespacecharakters Header- und Quelldateien kopieren Konstanten einbinden Bedingte Kompilierung 8.1. Einkopieren von Dateien mittels #include ● Meinstens Einfügen von Headern ● Direktiv wird einfach durch Quellcode ersetzt ● Dateipfad in Anführungszeichen ● im implementierungsdefinierten Pfad dann in Dreiecksklammern ● im aktuellen Arbeitsverzeichniss dann in Anführungszeichen ● Standardheader assert.h Fehlersuche und Debugging ctype.h Zeichentest und Konvertierung errno.h Fehlercodes float.h Limits/Eigenschaften für Gleitpunkttypen limits.h Implementierungskonstanten locale.h Länderspezifische Eigenschaften math.h Mathematische Funktionen setjmp.h Unbedingte Sprünge signal.h Signale stdarg.h Variable Parameterübergabe stddef.h Standard-Datentyp stdio.h Standard-I/O stdlib.h Nützliche Funktionen string.h Zeichenkettenoperationen time.h Datum und Uhrzeit 75 SE 8.2. Markos und Konstanten 8.2.1. symbolische Konstanten #include <stdio.h> #define EINS 1 int main() { printf("%d\n",EINS); return 0; } ● Präprozessor ersetzt jedesmal die Konstante bei ihrem Aufruf mit dem Wert, der am Anfang des Programms definiert wurde ● Konstanten können im Verlauf des Programms nicht verändert werden ● es können auch Strings definiert werden, z.B. #define SCHREIB printf( ● einfacher zu Ändern ● weniger Tippaufwand für längere Konstanten, die öfter verwendet werden ● wenn mit define eine Konstante festgelegt wird, die aus einer Berechnung hervorgeht, wird diese jedesmal erneut berechnet, z.B. #define PI atan(1)*4 ● besser ist es eine konstante Variable zu deklarieren, z.B. const double PI = atan(1)*4; 76 SE 8.2.2 Makros #include <stdio.h> #define KLEINER_100(x) ((x) < 100) void klHundert(int zahl) { if(KLEINER_100(zahl)) printf("Yep! Die Zahl ist kleiner als 100!\n"); else printf("Die Zahl ist größer als 100!\n"); } int main() { int b=100; klHundert(b); return 0; } ● parametrisierte Makros mit (*Parameter*) am Ende ● mehrere Parameter mit Komma getrennt ● bei Definition über mehrere Zeilen muss jede Zeile mit \ abgeschlossen werden ● lange Makros sind nicht sinnvoll → Funktionen ● Parameter muss auf beiden Seiten in Klammern stehen ● kein ; am Ende der Zeile ● durch #undef kann ein Makro oder eine Konstante aufgehoben werden ● ansonsten gilt sie bis zum Dateiende #define MAX(x,y) ( (x)<=(y) ?(y) :(x) ) #define TAUSCHE(x,y) int j; \ j=x; x=y; y=j; \ } { \ ● häufige Verwendung 77 SE 8. Zeiger ● ● ● ● ● ● ● ● Stärkstes und gefährlichstes Feature von C Syntaxcrash bei unsachgemäßer Verwendung Art Link entscheidend ist die Variable, die in der angegebenen Adresse gespeichert ist mehrere Zeiger können auf eine Adresse zeigen Arrays werden über Zeiger realisiert Stern für jeden Zeiger extra, z.B. bei Aufzählung mit Komma... kann im Verlauf des Programms erneut zugewiesen werden ● * ist Dereferenzierungsoperator oder Indirektionsoperator ohne * steht für die Adresse mit * steht für den Wert, der in der Adresse steht Verwendung: ● Speicherbereiche können dynamisch reserviert, verwaltet und wieder gelöscht werden. ● Mit Zeigern können Sie Datenobjekte direkt (call-by-reference) an Funktionen übergeben. ● Es lassen sich Funktionen als Argumente an andere Funktionen übergeben. ● Rekursive Datenstrukturen wie Listen und Bäume lassen sich fast nur mit Zeigern bewerkstelligen. ● Es lässt sich ein typenloser Zeiger (void *) definieren, womit Datenobjekte beliebigen Typs verarbeitet werden können. ● Rückgabe eines Wertes aus einer Funktion, auf den z.B: eine Schleife reagieren könnte Deklaration: Datentyp *zeigervariable; ● Datentyp des Zeigers muss mit dem Datentyp übereinstimmen auf den er zeigt ● 78 SE Initialisierung: ● ohne Initialisierung zeigt der Zeiger auf irgendeine Adresse ● kann zu Fehlern, Überschreibung... führen ● Speicherort einer Variablen wird beim kompilieren festgelegt ● kann durch printf("%p", &VariabLe); ausgegeben werden Zeiger=&Variable; ● Zeiger ohne * und Variable mit & stehen für Adressen ● Zeiger mit * und Variable ohne & stehen für gespeicherten Wert ● Zeiger mit & davor steht für die Adresse in der der Zeiger gespeichert ist Beispiel Menu #include <stdio.h> int main() { int abfrage; int Kapitel1 = 5; int Kapitel2 = 60; int Kapitel3 = 166; int Nachtrag = 233; int *Verzeichnis; /* Zeiger */ do{ printf("\tINDEXREGISTER VOM BUCH\n"); printf("\t*******************************\n\n"); printf("\t-1- Kapitel 1\n"); printf("\t-2- Kapitel 2\n");1 printf("\t-3- Kapitel 3\n"); printf("\t-4- Nachtrag\n"); printf("\t-5- Ende\n"); printf("\n"); printf("\tAuswahl : "); scanf("%d",&abfrage); printf("\tKapitel %d finden Sie auf ",abfrage); switch(abfrage) { case 1 : Verzeichnis=&Kapitel1; printf("Seite %d\n",*Verzeichnis); break; case 2 : Verzeichnis=&Kapitel2; printf("Seite %d\n",*Verzeichnis); break; case 3 : Verzeichnis=&Kapitel3; printf("Seite %d\n",*Verzeichnis); break; case 4 : Verzeichnis=&Nachtrag; printf("Seite %d\n",*Verzeichnis); break; default: printf("Seite ???\n"); break; } }while(abfrage<5); return 0; } 79 SE Dereferenzieren: ● schreiben auf Variable mit Zeigern *Zeiger=Wert ● Dereferenzierung eines Zeigers, der auf eine zufällige Adresse Zeigt, weil er nicht Initialisiert wurde kann zu schweren Fehlern führen ● Wenn noch keine Adresse für Zeiger feststeht mit NULL initialisieren int *Zeiger=NULL; ● Überprüfung ob der Zeiger auf eine Adresse zeigt if(Zeiger == NULL) ● Wert eines Zeigers kann auch in einer Variablen gespeichert werden Variable = *Zeiger; ● gleichsetzten zweier Zeiger – ohne *!!! piA = piB ● Scanf ohne *, weil Zeiger schon auf Adresse zeigt scanf („%f“, pfZeiger); ● printf mit *, damit der Wert aus der Adresse ausgegeben wird printf („%f“, *pfZeiger); ● einen String ausgeben – ohne*!!! ● mit + und %c wird nur der erste Buchstabe ausgegeben printf("%s",ptr1); Beispiel schreiben auf Zeiger #include <stdio.h> int main() { int x=5; int *y; y=&x; printf("%d==%d\n", x, *y); printf("%p==%p\n", &x, y); *y=10; printf("%d==%d!=5\n", x, *y); printf("%p==%p\n", &x, y); return 0; } 80 SE 8.1. Zeigerarithmetik Operationen mit Pointern ● Ganzzahlwerte erhöhen ● Ganzzahlwerte verringern ● Inkrementieren ● Dekrementieren ● Vergleichsoperatoren ● immer um ein Vielfaches der Größe des Datentyps ● Zeiger+=10 ist 40 Bytes weiter wie Anfangsadresse Verweis auf andere Zeiger ● können auch auf andere Zeiger verweisen ● kopieren der Adresse Zeiger1=Zeiger2 8.2. Zeiger als Funktionsparameter - call-by-reference Zeiger zur Wertübergabe ● Nachteil von call-by-value ist kopieren der zu übergebenden Werte #include <stdio.h> #define PI 3.141592f float kreisflaeche(float wert) { return (wert = wert * wert * PI); } int main() { float radius, flaeche; printf("Berechnung einer Kreisfläche!!\n\n"); printf("Bitte den Radius eingeben : "); scanf("%f",&radius); flaeche = kreisflaeche(radius); printf("\nDie Kreisfläche beträgt : %f\n",flaeche); return 0; } 81 SE #include <stdio.h> #define PI 3.141592f void kreisflaeche(float *wert) { *wert = ( (*wert) * (*wert) * PI ); } int main() { float radius; printf("Berechnung einer Kreisfläche!!\n\n"); printf("Bitte den Radius eingeben : "); scanf("%f",&radius); /*Adresse von radius als Argument an kreisflaeche() */ kreisflaeche(&radius); printf("\nDie Kreisfläche beträgt : %f\n",radius); return 0; } ● keine Rückgabe mit return ● Funktionstyp void ● keine Rückgabe notwendig, da der Wert, der in der Übergebenden Adresse geändert wird und die Adresse für das ganz Programm gleich bleibt ● die Varaible Wert wird nur für die Funktion Kreisflaeche verwendet ● das Ergebnis wird in radius geschrieben Zeiger zur Wertrückgabe ● geben Anfangsadresse des Rückgabewerts zurück ● geben nur Werte mit dem gleichen Datentyp zurück ● Rückgabe bei Strings oder Strukturen #include <stdio.h> #include <string.h> #define MAX 255 char *eingabe(char *str) { char input[MAX]; printf("Bitte \"%s\" eingeben: ",str); fgets(input, MAX, stdin); return strtok(input, "\n"); } int main() { char *ptr; ptr = eingabe("Vorname"); printf("Hallo %s\n",ptr); ptr = eingabe("Nachname"); printf("%s, interssanter Nachname\n",ptr); return 0; } ● strtok untberbricht string bei \n und gibt Anfangsadresse zurück 82 SE 8.2. Zeiger auf Arrays ● Arrays werden über Zeiger realisiert int AiFeld[3] Adresse ist 4 Byte lang wegen int erster Wert steht in Adresse, mit der Array beginnt → 0x4+Basisadresse zweiter Wert in Basisadresse+ 1x4 0, 1... ist Offset 8.2.1. Zugriff auf einen Array mittels Zeiger #include <stdio.h> int main() { int element[8]= {1,2,4,8,16,32,64,128}; int *ptr; int i; ptr=element; printf("Der Wert auf den *ptr zeigt ist %d\n",*ptr); printf("Durch *ptr+1 zeigt er jetzt auf %d\n",*(ptr+1)); printf("*(ptr+3) = %d\n",*(ptr+3)); printf("\nJetzt alle zusammen : \n"); for(i=0; i<8; i++) printf("element[%d]=%d \n",i,*(ptr+i)); return 0; } Der Wert auf den *ptr zeigt ist 1 Durch *ptr+1 zeigt er jetzt auf 2 *(ptr+3) = 8 Jetzt alle zusammen : element[0]=1 element[1]=2 element[2]=4 element[3]=8 element[4]=16 element[5]=32 element[6]=64 element[7]=128 ● Zuweisen der Basisadresse des Arrays auf den Zeiger erfolgt ohne eckige Klammern und ohne Adressoperator ● der Arrayname ohne Adressoperator ist die Basisadresse ● identisch zu ptr=element wäre ptr=&element[0] ● Array ist Zeiger sehr ähnlich in der Adresse des Arrays steht der erste Werte, d.i. die Basisadresse in der Adresse des Zeiger steht eine weitere Adresse 83 SE #include <stdio.h> int main() { int element[8] = {1,2,4,8,16,32,64,128}; int i; printf("*element = %d\n",*element); printf("*(element+1) = %d\n",*(element+1)); printf("*(element+3) = %d\n",*(element+3)); } printf("\nJetzt alle zusammen : \n"); for(i=0; i<8; i++) printf("*(element+%d) = %d \n",i,*(element+i)); return 0; *element = 1 *(element+1) = 2 *(element+3) = 8 Jetzt alle zusammen : *(element+0) = 1 *(element+1) = 2 *(element+2) = 4 *(element+3) = 8 *(element+4) = 16 *(element+5) = 32 *(element+6) = 64 *(element+7) = 128 ● mit Arrays kann so umgegangen werden wie mit Zeigern ● vgl. die letzten beiden Beispiele int array[10]; /* Deklaration */ int *pointer1, *pointer2; pointer1=array; /*pointer1 auf Anfangsadresse von array */ pointer2=array+3; /* pointer2 auf 4.Element von array */ array[0] *(array+1) pointer1[1] *(pointer1+2) *pointer2 = = = = = 99; 99; 88; 77; 66; /* /* /* /* /* array[0] array[1] array[1] array[2] array[3] */ */ */ */ */ ● Dereferenzierungsoperator wenn der Offset dazuaddiert wird 84 SE 8.2.2. Funktionsaufrufe von Arraynamen int funktion(int elemente[]) /* Gleichwertig mit ... */ int funktion(int *elemente) Möglichkeiten eine Funktion aufzurufen int werte[] = { 1,2,3,5,8 }; int *pointer; pointer = werte; funktion(werte); funktion(&werte[0]); funktion(pointer); /* 1. Möglichkeit */ /* 2. Möglichkeit */ /* 3. Möglichkeit */ Übergabe der Anzahl der Elemente an eine Funktion #include <stdio.h> void funktion(int *array, int n_array) { int i; for(i=0; i < n_array; i++) printf("%d ",array[i]); printf("\n"); } int main() { int werte[] = { 1,2,3,5,8,13,21 }; funktion(werte, sizeof(werte)/sizeof(int)); return 0; } 85 SE 8.2.3 Gemeinsamkeinten Array und Zeiger #include <stdio.h> int main() { int n=3; /* eindim. Array mit Platz für 5 Werte*/ int array[5]={ 1,2,3,4,5 }; /* int-Zeiger verweist jetzt auf array[0] */ int *ptr = array; /* 4 Möglichkeiten, um auf das erste Element zuzugreifen */ printf("%d " ,*ptr); printf("%d ",ptr[0]); printf("%d ",*array); printf("%d\n",array[0]); /* 4 Möglichkeiten, um auf das n-te Element zuzugreifen */ printf("%d " ,*(ptr+n)); printf("%d ",ptr[n]); printf("%d ",*(array+n)); printf("%d\n",array[n]); /* 4 Möglichkeiten, um auf die Anfangsadresse zuzugreifen */ printf("%p " ,ptr); printf("%p ",&ptr[0]); printf("%p ",array); printf("%p\n",&array[0]); /* 4 Möglichkeiten, um auf die Adresse des n-ten Elements zuzugreifen */ printf("%p " ,ptr+n); printf("%p ",&ptr[n]); printf("%p ",array+n); printf("%p\n",&array[n]); return 0; } 86 SE 8.3. Zeiger auf Strings ● Für Strings gilt das gleiche wie für Arrays #include <stdio.h> void funktion(char *str) { printf("%s\n",str); } int main() { char *string = "Hallo Welt"; funktion(string); printf("Anfangsadresse auf die *string zeigt = %p\n",*string); printf("Der Inhalt dieser Anfangsadresse = %c\n",*string); return 0; } Hallo Welt Anfangsadresse auf die *string zeigt = 0x48 Der Inhalt dieser Anfangsadresse = H ● str beinhaltet die Adresse in der das H gespeichert wird 87 SE 8.3.1. Zeiger auf konstante Objekte - Read-only-Zeiger ● Variablen muss const vorangestellt sein ● kann nicht verändert, nur gelesen werden #include <stdio.h> void funktion1(char *str) { char *ptr; ptr = str+5; *ptr = '-'; } int main() { char string1[] = "Hallo Welt\n"; funktion1(string1); printf("%s\n",string1); return 0; } Hallo-Welt ● die Basisadresse von ptr entspricht der Adresse des 6. Elements von str ● welches im Verlauf von funktion1 verändert wird ● Überschreiben wird verhindert durch: void funktion1(const char *str) 88 SE 8.3.4 Zeiger auf Zeiger und Stringtabellen ● Syntax datentyp **bezeichner; ● d.i. ein Zeiger, der auf einen Zeiger zeigt, der wiederum auf eine Variable zeigt ● mehrfache Indirektion ● für gewöhnlich nur zweidimensional ● Verwendbar zur dynamischen Erzegung von mehrdimensionalen Arrays, z.B. Matrizenberechnung ● #include <stdio.h> int main() { int wert = 10; /* ptr ist ein Zeiger auf int wert */ int *ptr=&wert; /* ptr_ptr ist ein Zeiger auf den Zeiger int *ptr */ int **ptr_ptr=&ptr; printf("*ptr = %d\n",*ptr); printf("**ptr_ptr = %d\n", **ptr_ptr); /* Verändert den Wert, auf den int *ptr zeigt */ **ptr_ptr = 100; printf("*ptr = %d\n",*ptr); printf("**ptr_ptr = %d\n", **ptr_ptr); /* Verändert nochmals den Wert */ *ptr = 200; printf("*ptr = %d\n",*ptr); printf("**ptr_ptr = %d\n", **ptr_ptr); return 0; } *ptr **ptr_ptr *ptr **ptr_ptr *ptr **ptr_ptr = = = = = = 10 10 100 100 200 200 ● *ptr und *ptr_ptr zeigen letztlich auf die gleiche Adresse ● *ptr_ptr=100 würde auf die Speicheradresse 100 verweisen → **!!! 89 SE 8.3.5. Zeiger erhöhen ● Zeiger können wie Indizes erhöht werden #include <stdio.h> #define GROESSE 3 int main() { int Matrix [GROESSE]= { 3, 2, 1 }; int *Zeiger; Zeiger=Matrix; printf("Wert X=%d\n", *(Zeiger+1)); Zeiger++; printf("Wert Y=%d\n", (*Zeiger+1)); return 0; } Wert X=2 Wert Y=3 90 SE 8.3.6. Beispiel für Zeiger ● ● ● ● Sucht Leerzeichen ersetzt Leerzeichen mit \0 gibt String bis \0 aus gibt den Rest des Strings aus #include <stdio.h> int main() { int Ausgabe = 0; char Satz[] = "ARRAYS STRINGS UND ZEIGER"; char *Temp_z; for (Temp_z = Satz; Temp_z <= Satz + 24; Temp_z++) { if (*Temp_z == 32) /* ASCII 32 ist ein Leerzeichen */ { *Temp_z = 0; /* ASCII 0 ist ein '\0' */ printf("\n Ausgabe Nr. %d", ++Ausgabe); printf("\n Satz: %s", Satz); printf("\n Temp_z: %s", Temp_z+1); printf("\n"); } } return 0; } Ausgabe Nr. 1 Satz: ARRAYS Temp_z: STRINGS UND ZEIGER Ausgabe Nr. 2 Satz: ARRAYS Temp_z: UND ZEIGER Ausgabe Nr. 3 Satz: ARRAYS Temp_z: ZEIGER 91 SE 9. Kommandozeilenargumente ● Übergabe von Argumenten an die Main #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int i; for (i=0; i < argc; i++) { printf("argv[%d] = %s ", i, argv[i]); printf("\n"); } return EXIT_SUCCESS; } argv[0] = /home/weltmachtsubzentrum/workspace/Uebung/Debug/Uebung ● ● ● ● ● ● Übergabe 2 Argumente an die Main von der Konsole ein Integer, eine Stringtabelle Ausgabe ist Stringtabelle stdlib enthält EXIT_SUCCESS ist definiert als 1 int argc zählt automatisch Anzahl der übergebenen Argumente **argv ist ein Array aus Zeigern, die auf Adressen zeigen, die Variablen des gleichen Datentyps enthalten weltmachtsubzentrum% /home/weltmachtsubzentrum/workspace/Uebung/Debug/Uebung Hallo Welt argv[0] = /home/weltmachtsubzentrum/workspace/Uebung/Debug/Uebung argv[1] = Hallo argv[2] = Welt ● in argv[0] steht für gewöhnlich der Programmname ● Argumente müssen mit Leerzeichen getrennt werden #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { printf("Insgesamt %d Argumente\n", argc-1); printf("Letztes Argument: %s\n", argv[argc-1]); return EXIT_SUCCESS; } weltmachtsubzentrum% /home/weltmachtsubzentrum/workspace/Uebung/Debug/Uebung Hallo Welt Insgesamt 2 Argumente Letztes Argument: Welt 92 SE Beispiel Taschenrechner #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char **argv) { int i, j; long y, erg; if (argc < 4) { printf("Benötige mindestens 4 Argumente!\n"); printf("Aufruf: %s <zahl> <op> <zahl> ...\n", *argv); return EXIT_FAILURE; } /* 1.Zahl in einen Integer konvertieren*/ erg = strtol(argv[1], NULL, 10); if (erg == 0) { printf("Keine gültige Ganzzahl ... \n"); return EXIT_FAILURE; } for (i = 1; i < argc-1; i += 2) { for (j=i+1; j < i+2; j++) { y = strtol(argv[i+2], NULL, 10); if (y == 0) { printf("Keine gültig Ganzzahl ... \n"); printf("argc: %d (%s)?!\n", i+2, argv[i+2]); return EXIT_FAILURE; } if (strcmp(argv[j], "+") == 0) erg += y; else if (strcmp(argv[j], "-") == 0) erg -= y; else if (strcmp(argv[j], "x") == 0) erg *= y; else if (strcmp(argv[j], "/") == 0) erg/=y; else { printf("Ungültiger Operand: %s\n", argv[j]); return EXIT_FAILURE; } } } printf("Ergebnis: %d\n", erg); return EXIT_SUCCESS; } weltmachtsubzentrum% /home/weltmachtsubzentrum/workspace/Uebung/Debug/Uebung 2 + 7 5/2 Ergebnis: 2 93 SE ● ● ● ● ● ● ● ● ● ● Argumenten unbedingt mit Leerzeichen trennen kein Punkt vor Stricht nur Integer als Ergebnis Übergabe von 7 Argumenten jedes Argument hat ein \0 am Ende müssen mindestens 4 Argumente eingegeben werden, sonst Fehlermeldung Prog.name, Zahl, Operand, Zahl strtol um String in float umzuwandeln 1. For-Schlaufe durchläuft die ungeraden Feldindizes, die Integer enthält 2. For-Schlaufe durchläuft die geraden Feldindizes, die Operanden strcmp vergleicht string 9.1. Optionen/Schalter ● Schalter mit -- davor Beispiel Verändert Eingegebenen Text in Abhängigkeit des Schlaters #include <stdio.h> #include <string.h> #include <ctype.h> /* tolower(), toupper(), isalpha() */ #include <stdlib.h> #define FALSE 0 #define TRUE 1 #define BUF 4096 void show_help(void) { printf("\nProgrammaufruf: myecho [OPTION] STRING\n"\ "Programm gibt den Text in gewünschter Form auf"\ "dem Bildschirm aus\n\nFolgende Optionen stehen"\ "Ihnen zur Verfügung:\n\n"\ "\t-r Text wird spiegelverkehrt ausgegeben\n"\ "\t-g Text wird in Grossbuchstaben ausgegeben\n"\ "\t-s Text wird in Kleinbuchstaben ausgegeben\n "\ "\t-h Dieser Text\n" "\t-v Versionsnummer\n\n"); } int getopt(char *argument, char *option) { if( argument[0]=='-' && argument[1]==option[0] ) return TRUE; return FALSE; } void spiegeln(char *string) { char *reverse = string; while(*reverse++); while(reverse-- != string) printf("%c",*reverse); printf("\n"); } void larger(char *string) { char *large=string; while(*large) printf("%c",(isalpha(*large))?toupper(*large++):*large++); printf("\n"); } 94 SE void smaller(char *string) { char *small=string; while(*small) printf("%c",(isalpha(*small))?tolower(*small++):*small++); printf("\n"); } int main(int argc, char **argv) { int counter=3; char buffer[BUF]; size_t len=0; if(argc == 1 || getopt(argv[1],"h") == TRUE ) { show_help(); return EXIT_FAILURE; } else if(getopt(argv[1],"v") == TRUE) { printf("Version 1.0\n"); return EXIT_SUCCESS; } else if(argc < 3) { show_help(); return EXIT_FAILURE; } len=strlen(argv[2])+1; /* Ab argv[2] bis argv[n] alle Elemente in buffer */ if(len > BUF) { printf("Der String enthält zu viele Zeichen\n"); return EXIT_FAILURE; } strcpy(buffer,argv[2]); while(argv[counter] != NULL) { len += strlen(argv[counter])+2; if(len > BUF) { printf("Der Puffer ist bereits voll\n"); break; } strcat(buffer, " "); strcat(buffer, argv[counter++]); } if(getopt(argv[1],"r") == TRUE) spiegeln(buffer); else if(getopt(argv[1],"g") == TRUE) larger(buffer); else if(getopt(argv[1],"s") == TRUE) smaller(buffer); else show_help(); return EXIT_SUCCESS; } 95 SE weltmachtsubzentrum% /home/weltmachtsubzentrum/workspace/Uebung/Debug/Uebung Programmaufruf: myecho [OPTION] STRING Programm gibt den Text in gewünschter Form aufdem Bildschirm aus Folgende Optionen stehenIhnen zur Verfügung: -r Text wird spiegelverkehrt ausgegeben -g Text wird in Grossbuchstaben ausgegeben -s Text wird in Kleinbuchstaben ausgegeben -h Dieser Text -v Versionsnummer weltmachtsubzentrum% /home/weltmachtsubzentrum/workspace/Uebung/Debug/Uebung -r abc cba 10. Dynamische Speicherverwaltung 10.1. Speicherallokation mit malloc() #include <stdlib.h> void *malloc(size_t size); ● gibt die Anfangsadresse eines Speicherbereichs in der angeforderten Größe zurück ● die Größe des zu reservierenden Bereichs muss in Byte angegeben werden ● Problem ist unterschiedliche Größe der Datentypen auf verschiedenen Systemen, besser der Compiler berechnet die Größe selbst ● gibt bei Fehler Null zurück – wenn nicht mehr genügend zusammenhängender Speicher gefunden werden kann p=(int *)malloc(sizeof(int)); ● weißt p die Anfangsadresse eines Bereichs zu, in dem ein int gespeichert werden kann ● (int *) ist Typenkonfertierung der voidfunktion 96 SE Beispiel #include <stdio.h> #include <stdlib.h> int main() { int *p; p=(int *)malloc(2*sizeof(int)); if(p != NULL) { *p=99; /* Alternativ auch p[0] = 99 */ *(p+1) = 100; /* Alternativ auch p[1] = 100 */ printf("Allozierung erfolgreich ... \n"); } else printf("Kein Speicherplatz vorhanden!!!\n"); } printf("%d %d\n",p[0],p[1]); /* Sie können die Werte auch so ausgeben lassen */ printf("%d %d\n",*p, *(p+1)); return 0; ● reserviert Speicher für 2 int ● Zugriff durch p[0],p[1] oder *p, *(p+1) 3 Möglichkeiten zur Übergabe der Größe: ● Als numerische Konstante: p=(int *)malloc(sizeof(2)); Problem der verschiendenen Größen der Datentypen auf verschiedenen Systeme ● Die Angabe des Datentyps mithilfe des sizeof-Operators: p=(int *)malloc(sizeof(int)); Problem, dass wenn plötzklich statt int double benötigt werden ● Den dereferenzierten Zeiger selbst für den sizeof-Operator verwenden: p=(double *)malloc(sizeof(*p)); wichtig ist Dereferenzierungsoperator ohne wird Größe des Zeigers übergeben, nicht des zugewisenen Datentyps → Überlappung des Speicherbereichs Fehler bei Reservierung ● #define check4error(x) if(NULL == x) { printf(„Es ist ein Fehler in der Funktion malloc aufgetreten, das Programm wird beendet.“): exit(0);} 97 SE 10.2. Speicherbereich wieder freigeben - free() #include <stdlib.h> void free (void *p); Beispiel #include <stdio.h> #include <stdlib.h> int main() { int *p; p=(int *)malloc(sizeof(int)); if(p != NULL) { *p=99; printf("Allozierung erfolgreich ... \n"); } else printf("Kein Speicherplatz vorhanden!!!\n"); } if(p != NULL) free(p); return 0; ● Speicherbereich wird nur freigegeben, wenn p auf eine Adresse verweist ● p zeigt nach der Freigabe immer noch auf den Bereich, auch wenn er zum überschreiben wieder freigegeben ist entweder nach freigabe auf NULL setzten oder Makro definieren, z.B. #define myfree (x) free(x); *x=NULL 98 SE Beispiel reserviert dynamischen String, Länge abhängig von Eingabe #include <stdio.h> #include <string.h> #include <stdlib.h> #define BUF 80 int main() { char puffer[BUF]; char *dyn_string; printf("Ein Text mit max. 80 Zeichen: "); fgets(puffer, BUF, stdin); dyn_string = (char *)malloc(strlen(puffer)+1); if(dyn_string != NULL) strncpy(dyn_string, puffer, strlen(puffer)+1); else printf("Konnte keinen Speicherplatz reservieren\n"); printf("%s",dyn_string); free(dyn_string); return 0; } 99 SE 10.3. dynamische Arrays #include <stdio.h> #include <stdib.h> #include <string.h> int main() { int *value; int size; int i=0; printf("Wie viele Werte benötigen Sie : "); scanf("%d",&size); value=(int *)malloc(size*sizeof(int)); if( NULL == value ) { printf("Fehler bei malloc....\n"); exit(0); } while( i<size ) { printf("Wert für value[%d] eingeben : ",i); scanf("%d",&value[i]); i++; } printf("Hier Ihre Werte\n"); for(i=0; i<size; i++) printf("value[%d] = %d\n",i,value[i]); return 0; } ● liest vom Benutzer die Anzahl der zu speichernden Werte ein ● reserviert entsprechend die notwendige Menge an Speicherplatz 10.4. realloc und calloc void *calloc(size_t anzahl, size_t groesse); void *realloc(void *zgr, size_t neuegroesse); int *zahlen; zahlen=(int *)calloc(100,sizeof(int)); ● reserviert Speicher für 100 Werte des Typs int ● werden auf 0 initialisiert, wohingegen malloc die werte nicht intialisieren würde 100 SE #include <stdio.h> #include <stdlib.h> int main() { int n=0, max=2, z,i, *zahlen=NULL; /*Wir reservieren Speicher für 10 int-Werte mit calloc*/ zahlen = (int *)calloc(max, sizeof(int)); if(NULL == zahlen) { printf(".....Speicherplatzmangel!!!!!\n"); exit(0); } printf("Zahlen eingeben --- Beenden mit 0\n"); /* Endlossschleife */ while(1) { printf("Zahl (%d) eingeben : ",n+1); scanf("%d",&z); if(z==0) break; /*Reservierung von Speicher während der Laufzeit des Programms mit realloc*/ if(n>=max) { max+=max; zahlen = (int *)realloc(zahlen,max*sizeof(int)); if(NULL == zahlen) { printf("Speicherplatzmangel!!!\n"); exit(1); } printf("Speicherplatz reserviert " " (%d Bytes)\n",sizeof(int)*max); } zahlen[n++]=z; } printf("Folgende Zahlen wurden eingegeben ->\n\n"); for(i=0;i<n;i++) printf("%d ",zahlen[i]); printf("\n"); free(zahlen); return 0; } ● der Speicherbereich kann mit realloc während des laufenden Programms verändert werden gespeicherter werte werden zwischengespeichert neuer Speicherbereich wird gesucht Werte werden wieder zurückkopiert 101 SE Beispiel dynamischer String #include <stdio.h> #include <string.h> #include <stdlib.h> #define BUF 5 int main() { size_t len; char *str=NULL; char puffer[BUF]; } printf("Ein dynamisches char-Array für Strings\n"); printf("Eingabe machen : "); fgets(puffer, BUF, stdin); str = (char *)malloc(strlen(puffer)+1); if(NULL == str) { printf("Konnte keinen Speicher bereitstellen...\n"); exit(0); } strcpy(str, puffer); printf("Weitere Eingabe oder beenden mit \"END\"\n>"); /* Endlossschleife */ while(1) { fgets(puffer, BUF, stdin); /* Abbruchbedingung */ if(strcmp(puffer,"end\n")==0||strcmp(puffer,"END\n")==0) break; /* Aktuelle Länge von str zählen für realloc */ len = strlen(str); /* Neuen Speicher für str anfordern */ str = (char *)realloc(str,strlen(puffer)+len+1); if(NULL == str) { printf("Konnte keinen Speicher bereitstellen...\n"); exit(0); } /* Hinten Anhängen */ strcat(str, puffer); } printf("Ihre Eingabe lautete: \n"); printf("%s",str); free(str); return 0; ● liest Buchstaben aus Tastaturbuffer ein bis end oder END eingegeben wird ● reserviert neuen Speicherbereich, je nach dem wieviele Buchstaben mehr eingegeben werden ● Speicherbereich muss für String immer um ein Zeichen größer sein als der Text! 102 SE 10.5. Mehrdimensionale dynamische Arrays #include <stdio.h> #include <stdlib.h> #define BUF 255 int main() { int i, j, zeile, spalte; /* Matrix ist Zeiger auf int-Zeiger */ int ** matrix; printf("Wie viele Zeilen : "); scanf("%d",&zeile); printf("Wie viele Spalten: "); scanf("%d",&spalte); /* Speicher reservieren für die int-Zeiger (=zeile) */ matrix = (int **)malloc(zeile*sizeof(int *)); if(NULL == matrix) { printf("Kein Speicher fuer die Zeilen...\n"); exit(0); } /* Jetzt noch Speicher reservieren für die einzelnen Spalten der i-ten Zeile */ for(i=0; i < zeile; i++) { matrix[i] = (int *)malloc(spalte*sizeof(int)); if(NULL == matrix[i]) { printf("Kein Speicher fuer Zeile %d\n",i); exit(0); } } /* Mit beliebigen Werten initialisieren */ for (i = 0; i < zeile; i++) for (j = 0; j < spalte; j++) matrix[i][j] = i+j; /* matrix[zeile][spalte] */ /* Inhalt der Matrix entsprechend ausgeben */ for (i = 0; i < zeile; i++) { for (j = 0; j < spalte; j++) printf("%d ",matrix[i][j]); printf("\n"); } } /* Speicherplatz wieder freigeben * Wichtig! In umgekehrter Reihenfolge */ /* Spalten der i-ten Zeile freigeben */ for(i=0; i< zeile; i++) free(matrix[i]); /* Jetzt können die leeren Zeilen freigegeben werden */ free(matrix); return 0; 103 SE ● Zeiger auf Zeiger ergibt mehrdimensionale Arrays ● matrix = (int **)malloc(zeile*sizeof(int *)); reserviert Speicherbereich für die Zeilen, in denen int-Zeiger gespeichert werden sollen ● matrix[i] = (int *)malloc(spalte*sizeof(int)); in einer for-Schleife, die die einzelnen Zeilen durchzählt wird der Bereich für die tatsächlich Werte reserviert ● Zeilen müssen wieder in einer for-Schleife freigegeben werden Zugriff auf … Möglichkeit 1 Möglichkeit 2 Möglichkeit 3 1.Zeile, 1.Spalte **matrix *matrix[0] matrix[0][0] i.Zeile, 1.Spalte **(matrix+i) *matrix[i] matrix[i][0] 1.Zeile, i.Spalte *(*matrix+i) *(matrix[0]+i) matrix[0][i] i.Zeile, j.Spalte *(*(matrix+i)+j) *(matrix[i]+j) matrix[i][j] 104 SE 11. Strukturen ● Zusammenfassen von verschiedenen Variablen verschiedener Datentypen zu einer Struktur 11.1. Strukturen deklarieren struct adres { char vname[20]; char nname[20]; long PLZ; char ort[20]; int geburtsjahr; }adressen; ● ● ● ● deklariert eine Struktur zum speichern von Adressen mit 5 Variablen adres ist der Name der Struktur mit adresse kann auf die Struktur zugegriffen werden, das ist der Variablen-Bezeichner Allgemeiner Syntax struct typNAME { Datentyp1; Datentyp2; ......... /* Liste der Strukturelemente */ Datentyp_n; }Variablen_Bezeichner; struct index { int seite; char titel[30]; }; ● bräuchte 4 Byte für int und 30 Byte für char, also 34 Byte ● wird vom Betriebssystem häufig in 36 Byte gespeichert wegen dem sogenannten Vier-Byte-Alignment 105 SE 11.2. Initialisierung und Zugriff auf Strukturen #include <stdio.h> #include <string.h> struct index { }; int seite; char titel[30]; int main() { struct index lib; lib.seite = 23; strcpy(lib.titel, "C-Programmieren"); } printf("%d, %s\n",lib.seite, lib.titel); return 0; ● Zugriff durch den Punktoperator ● Deklaration wie Variable durch struct index lib; ● auch umständlicher möglich struct index { int seite; char titel[30]; }lib; ● es können auch mehrere Strukturen auf einmal deklariert werden struct index { int seite; char titel[30]; }lib1, lib2, lib3; ● können auch gleich initialisiert werden struct index { int seite; char titel[30]; }lib = { 308, "Strukturen" }; ● oder bei der Deklaration in der Mainfunktion Initialisieren struct index lib = { 55, "Einführung in C" }; ● Zugriff auf Elemente durch lib.seite = 23; oder strcpy(lib.titel, "CProgrammieren"); ● Kopieren einer Struktur in einer andere memcpy(&werte2,&wert1, sizeof(werte1)); ● auch komponentenweise möglich als sogenannte flache Kopie ● eine Änderung des Inhaltes des Bezeichners1 würde automatisch auch den Inhalt des Bezeichners2 ändern, da beide auf die gleiche Adresse verweisen Bezeichner1 = Bezeichner2; 106 SE Beispiel: #include <stdio.h> #define MAX 30 struct adres { char vname[MAX]; char nname[MAX]; long PLZ; char ort[MAX]; int geburtsjahr; }adressen; /*Funktion zur Ausgabe des Satzes*/ void ausgabe(struct adres x) { printf("\n\nSie gaben ein:\n\n"); printf("Vorname.........:%s", x.vname); printf("Nachname........:%s", x.nname); printf("Postleitzahl....:%ld\n",x.PLZ); printf("Ort.............:%s", x.ort); printf("Geburtsjahr.....:%d\n", x.geburtsjahr); } int main() { printf("Vorname : "); fgets(adressen.vname, MAX, stdin); printf("Nachname : "); fgets(adressen.nname, MAX, stdin); printf("Postleitzahl : "); do {scanf("%5ld",&adressen.PLZ); } while(getchar()!= '\n'); printf("Wohnort : "); fgets(adressen.ort, MAX, stdin); printf("Geburtsjahr : "); do {scanf("%4ld",&adressen.geburtsjahr); }while(getchar()!='\n' ); } ausgabe(adressen); return 0; ● Übergabe einer Struktur an einen Funktion durch ausgabe(adressen); ● einlesen einer Zahl ● do {scanf("%5ld",&adressen.PLZ); } while(getchar()!= '\n'); Parameter für Funktion sind void ausgabe(struct adres x) Bezeichner ● Zugriff mit x printf("Vorname.........:%s", Typ und x.vname); 107 SE 11.3. Strukturen als Werteübergabe an Funktionen ● Struktur wird komplett kopiert ausgabe(adressen); und printf("Vorname.........:%s",struct_ptr.vname); ● sehr zeitaufwendig bei größeren Strukturen ● besser mit Zeigern → call-by-reference ausgabe(&adressen); und printf("Vorname.........:%s",(*struct_ptr).vname); bzw. struct_ptr->vname #include <stdio.h> #define MAX 30 struct adres { char vname[MAX]; char nname[MAX]; long PLZ; char ort[MAX]; int geburtsjahr; }adressen; /*Funktion zur Ausgabe des Satzes*/ void ausgabe(struct adres *struct_ptr) { printf("\n\nSie gaben ein:\n\n"); printf("Vorname.........:%s",(*struct_ptr).vname); printf("Nachname........:%s",(*struct_ptr).nname); printf("Postleitzahl....:%ld\n",(*struct_ptr).PLZ); printf("Ort.............:%s",(*struct_ptr).ort); printf("Geburtsjahr.....:%d\n",(*struct_ptr).geburtsjahr); } int main() { printf("Vorname : "); fgets(adressen.vname, MAX, stdin); printf("Nachname : "); fgets(adressen.nname, MAX, stdin); printf("Postleitzahl : "); do {scanf("%5ld",&adressen.PLZ); } while(getchar()!= '\n'); printf("Wohnort : "); fgets(adressen.ort, MAX, stdin); printf("Geburtsjahr : "); do {scanf("%4ld",&adressen.geburtsjahr); }while(getchar()!='\n' ); ausgabe(&adressen); return 0; } ● Übergabe der Adresse ausgabe(&adressen); ● Parameter ist Typ und Adresse des Bezeichners void ausgabe(struct adres *struct_ptr) ● Zugriff über Zeiger printf("Vorname.........:%s",(*struct_ptr).vname); ● Alternativ mit dem Elementkennzeichnungsoperator -> printf("Vorname.........:%s",(struct_ptr->vname); ● -> nimmt automatisch eine Dereferenzierung vor 108 SE 11.4. Strukturen als Rückgabewerte einer Funktion #include <stdio.h> #include <stdlib.h> #define MAX 30 struct adres { char vname[MAX]; char nname[MAX]; long PLZ; char ort[MAX]; int geburtsjahr; }; /*Funktion zur Ausgabe des Satzes*/ void ausgabe(struct adres *struct_ptr) { printf("\n\nSie gaben ein:\n\n"); printf("Vorname.........:%s",struct_ptr->vname); printf("Nachname........:%s",struct_ptr->nname); printf("Postleitzahl....:%ld\n",struct_ptr->PLZ); printf("Ort.............:%s",struct_ptr->ort); printf("Geburtsjahr.....:%d\n",struct_ptr->geburtsjahr); } struct adres *eingabe(void) { struct adres *adressen; adressen=(struct adres *)malloc(sizeof(struct adres)); printf("Vorname : "); fgets(adressen->vname, MAX, stdin); printf("Nachname : "); fgets(adressen->nname, MAX, stdin); printf("Postleitzahl : "); do {scanf("%ld",&adressen->PLZ);} while(getchar()!= '\n'); printf("Wohnort : "); fgets(adressen->ort, MAX, stdin); printf("Geburtsjahr : "); do { scanf("%ld",&adressen->geburtsjahr); }while(getchar()!='\n' ); return adressen; } int main() { struct adres *adresse1, *adresse2; adresse1=eingabe(); adresse2=eingabe(); ausgabe(adresse1); ausgabe(adresse2); return 0; } 109 SE ● ● ● ● Übergabe der Adresse eines Zeigers durch adresse1=eingabe(); Deklaration der Funktion zur Eingabe durch struct adres *eingabe(void) Deklarieren einer neuen Struktur struct adres *adressen; zuweisen von Speicher mit der notwendigen Größe adressen=(struct adres *)malloc(sizeof(struct adres)); ● Schreiben der einzelnen Elemente fgets(adressen->vname, MAX, stdin); oder do {scanf("%ld",&adressen->PLZ);} while(getchar()!= '\n'); ● Rückgabe der Adresse return adressen;, die in adresse1 gespeichert wird 11.5. Strukturen vergleichen ● Gibt keine Funktion, die das übernimmt Beispiel: #include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX 30 struct adres { char vname[MAX]; char nname[MAX]; long PLZ; char ort[MAX]; int geburtsjahr; }; int cmp_structs(struct adres *str1, struct adres *str2) { /* Vorname gleich und */ if(strcmp(str1->vname, str2->vname) == 0 && /* Nachname gleich und */ strcmp(str1->nname, str2->nname) == 0 && /* Postleitzahl gleich und */ (str1->PLZ-str2->PLZ) == 0 && /* Wohnort gleich und */ strcmp(str1->ort, str2->ort) == 0 && /* geburtsjahr gleich */ (str1->geburtsjahr-str2->geburtsjahr) == 0) return 0; /* Beide Strukturen gleich */ else return 1; /* Strukturen nicht gleich */ } int main() { struct adres adresse1={"John","Leroy",1234,"New York",1980 }; struct adres adresse2={"John","Leroy",1234,"New York",1980 }; if(cmp_structs(&adresse1, &adresse2) == 0) printf("Beide Strukturen sind gleich?!?!\n"); else printf("Die Strukturen weisen Unterschiede auf\n"); return 0; } 110 SE 11.6. Arrays von Strukturen #include <stdio.h> #include <string.h> struct index { }; int seite; char titel[30]; int main() { int i; struct index lib[3]; lib[0].seite=312; strcpy(lib[0].titel, "Arrays von Strukturen"); lib[1].seite=320; strcpy(lib[1].titel, "Strukturen in Strukturen"); lib[2].seite=900; strcpy(lib[2].titel, "Anhang"); for(i=0; i<3; i++) printf("Seite %3d\t %-30s\n",lib[i].seite, lib[i].titel); return 0; } ● Deklarieren des Arrays durch struct index lib[3]; ● Zugriff über lib[0].seite=312; bzw strcpy(lib[1].titel, "Strukturen in Strukturen"); 111 SE Beispiel Adressbuch #include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX 30 static int x=0; struct adres { char vname[MAX]; char nname[MAX]; long PLZ; char ort[MAX]; int geburtsjahr; }adressen[100]; void Eingabe(int nr, struct adres neu[]) { printf("Vorname : "); fgets(neu[nr].vname, MAX, stdin); printf("Nachname : "); fgets(neu[nr].nname, MAX, stdin); printf("Postleitzahl: "); do {scanf("%5ld",&neu[nr].PLZ);} while(getchar()!= '\n'); printf("Wohnort : "); fgets(neu[nr].ort, MAX, stdin); printf("Geburtsjahr : "); do { scanf("%4d",&neu[nr].geburtsjahr); }while(getchar()!= '\n'); } void Suche(struct adres search[],char buchstabe,int nr) { int i; for(i=0; i<=nr; i++) { if(search[i].nname[0] == buchstabe) { printf("\n\nGefunden unter Buchstabe " ":\"%c\"\n\n",buchstabe); printf("Vorname......:%s",search[i].vname); printf("Nachname.....:%s",search[i].nname); printf("Postleitzahl.:%ld\n",search[i].PLZ); printf("Ort..........:%s",search[i].ort); printf("Geburtsjahr..:%d\n", search[i].geburtsjahr); printf("\n\tWeiter mit <ENTER>\n"); getchar(); } } } 112 SE void Ausgabe(struct adres all[],int nr) { int i; for(i=0; i<nr; i++) { printf("Vorname.........:%s",all[i].vname); printf("Nachname........:%s",all[i].nname); printf("Postleitzahl....:%ld\n",all[i].PLZ); printf("Ort.............:%s",all[i].ort); printf("Geburtsjahr.....:%d\n\n",all[i].geburtsjahr); } if((!(i%2))&& i!=0) { printf("\n\tWeiter mit <Enter>\n\n"); getchar(); } } void Sort(struct adres sort[],int nr) { int i,j; struct adres *temp; temp=(struct adres *)malloc(sizeof(struct adres *)); if(NULL == temp) { printf("Konnte keinen Speicher reservieren...\n"); return; } for(i=0; i<nr; i++) { for(j=i+1;j<nr;j++) { if(strcmp(sort[i].nname, sort[j].nname)>0) { *temp=sort[j]; sort[j]=sort[i]; sort[i]=*temp; } } } printf(".....Sortiert!!\n\n"); } 113 SE int main() { int auswahl; char c; do { printf("-1- Neue Adresse eingeben\n"); printf("-2- Bestimmte Adresse ausgeben\n"); printf("-3- Alle Adressen ausgeben\n"); printf("-4- Adressen sortieren\n"); printf("-5- Programm beenden\n"); printf("\nIhre Auswahl : "); scanf("%d",&auswahl); /* fflush(stdin); */ getchar(); switch(auswahl) { case 1 : Eingabe(x++,adressen); break; case 2 : printf("Anfangsbuchstabe d. Nachnamen :"); do { scanf("%c",&c); } while(getchar()!= '\n'); Suche(adressen,c,x); break; case 3 : Ausgabe(adressen,x); break; case 4 : Sort(adressen,x); break; case 5 : printf("Ende....\n"); break; default: printf("Falsche Eingabe\n"); } } }while(auswahl <5); return 0; ● liest Adressen ein ● Sortiert sie ● Duchrsucht alle Adressen nach einem Nachname mit gleichen Buchstaben wie Eingabe ● Gibt alle Adressen aus ● Eingabe(x++,adressen); zählt mit wieviele Adressen engegeben wurden ● Zum sortieren wird ein Buffer benötitg struct adres *temp; temp=(struct adres *)malloc(sizeof(struct adres *)); 114 SE 11.7. Verschachtelte Strukturen ● Verwendung von Strukturen in Strukturen struct uhrzeit { unsigned int stunde; unsigned int minute; unsigned int sekunde; }; struct datum { unsigned int tag; unsigned int monat; int jahr; }; struct termin { struct datum d; struct uhrzeit z; }t; ● struct uhrzeit und struct datum werden in struct termin verwendet ● Zugriff auf einzelne Strukturen wird komplizierter #include <stdio.h> struct uhrzeit { unsigned int stunde; unsigned int minute; unsigned int sekunde; }; struct datum { unsigned int tag; unsigned int monat; int jahr; }; struct termin { struct datum d; struct uhrzeit z; }t; 115 SE int main() { struct termin t = {{19,8,2003},{20,15,0}}; printf("Termin am "); printf("%u.%u.%d um ",t.d.tag,t.d.monat,t.d.jahr); printf("%u.%u.%u0 Uhr \n\n",t.z.stunde, t.z.minute,t.z.sekunde); } printf("Neuen Termin eingeben !!\n\n"); printf("Tag.............: "); scanf("%u",&t.d.tag); printf("Monat...........: "); scanf("%u",&t.d.monat); printf("Jahr............: "); scanf("%d",&t.d.jahr); printf("\n"); printf("Stunde..........: "); scanf("%u",&t.z.stunde); printf("Minuten.........: "); scanf("%u",&t.z.minute); printf("Sekunden........: "); scanf("%u",&t.z.sekunde); printf("\n"); printf("Neuer Termin am "); printf("%02u.%02u.%04d um ",t.d.tag,t.d.monat,t.d.jahr); printf("%02u.%02u.%02u Uhr \n",t.z.stunde, t.z.minute,t.z.sekunde); return 0; ● Deklaration und Initialisierung struct termin t = {{19,8,2003},{20,15,0}}; ● zugriff über zwei Strukturen durch z.B. scanf("%u",&t.d.tag); struct termin { struct datum d; struct uhrzeit z; struct adressen a; }t[20]; ● würde eine Struktur mit 20 Feldern definieren, in der Datum, Uhrzeit und Adresse gespeichert werden können 116 SE Beispielprogramm zu Termin mit Datum, Uhrzeit und Adresse #include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX 30 static int dates=0; static int nr=0; struct uhrzeit { unsigned int stunde; unsigned int minute; unsigned int sekunde; }; struct datum { unsigned int tag; unsigned int monat; int jahr; }; struct adressen { char vname[MAX]; char nname[MAX]; long PLZ; char ort[MAX]; int geburtsjahr; }xyz[100]; struct termin { struct datum d; struct uhrzeit z; struct adressen a; }t[20]; void newdate(struct termin *); int suche(char *); void listdate(struct termin *,int); void replacedate(struct termin *,int); void sortdate(struct termin *,int); void Eingabe(struct adressen *); void Ausgabe(struct adressen *); void Sortadress(struct adressen *); 117 SE void newdate(struct termin t[]) { int auswahl,ret; char such_name[MAX]; printf("Tag.......: "); scanf("%u",&t[dates].d.tag); printf("Monat.....: "); scanf("%u",&t[dates].d.monat); printf("Jahr......: "); scanf("%d",&t[dates].d.jahr); printf("---------------------\n"); printf("Stunde....: "); scanf("%u",&t[dates].z.stunde); printf("Minute(n).: "); scanf("%u",&t[dates].z.minute); printf("---------------------\n"); printf("\nTermin mit :\n -1- Neuer Adresse\n"); printf(" -2- Vorhandener Adresse\n"); printf("Ihre Auswahl : "); do { scanf("%d",&auswahl); } while(getchar()!= '\n'); if(auswahl==1) { printf("Vorname.....: "); fgets(t[dates].a.vname, MAX, stdin); printf("Nachname....: "); fgets(t[dates].a.nname, MAX, stdin); printf("Postleitzahl: "); do { scanf("%ld",&t[dates].a.PLZ); } while(getchar()!= '\n'); printf("ORT.........: "); fgets(t[dates].a.ort, MAX, stdin); printf("Geburtsjahr..: "); do { scanf("%ld",&t[dates].a.geburtsjahr); } while(getchar()!= '\n'); /* Neue Adresse kommt auch zum neuen Adresssatz */ strcpy(xyz[nr].vname, strtok(t[dates].a.vname, "\n")); strcpy(xyz[nr].nname, strtok(t[dates].a.nname, "\n")); xyz[nr].PLZ = t[dates].a.PLZ; strcpy(xyz[nr].ort, t[dates].a.ort); xyz[nr].geburtsjahr=t[dates].a.geburtsjahr; dates++; nr++; } 118 SE else { } printf("Bitte geben Sie den Nachnamen ein : "); fgets(such_name, MAX, stdin); ret=suche(strtok(such_name,"\n")); strcpy(t[dates].a.vname,xyz[ret].vname); strcpy(t[dates].a.nname,xyz[ret].nname); t[dates].a.PLZ=xyz[ret].PLZ; strcpy(t[dates].a.ort,xyz[ret].ort); t[dates].a.geburtsjahr=xyz[ret].geburtsjahr; dates++; } int suche(char suchname[]) { int n,found=0; for(n=0; n<=nr; n++) { if(strcmp(xyz[n].nname,suchname)==0) { found=1; break; } } if(found==1) return n; else { printf("Keine Eintrage mit dem Namen %s " "gefunden\n",suchname); return 0; } } void listdate(struct termin list[],int dates) { int i; for(i=0;i<dates;i++) { printf("Termin am %02u.%02u.%04d ", list[i].d.tag,list[i].d.monat,list[i].d.jahr); printf("um %02u.%02u Uhr\n", list[i].z.stunde,list[i].z.minute); printf("mit %s %s\n\n",list[i].a.vname,list[i].a.nname); } } 119 SE void replacedate(struct termin aendern[],int nt) { if(nt < 20) { printf("Bitte neue Terminzeit eingeben!!\n"); printf("Tag..........: "); scanf("%u",&aendern[nt].d.tag); printf("Monat........: "); scanf("%u",&aendern[nt].d.monat); printf("Jahr.........: "); scanf("%d",&aendern[nt].d.jahr); printf("------------------------\n"); printf("Stunden......: "); scanf("%u",&aendern[nt].z.stunde); printf("Minuten......: "); scanf("%u",&aendern[nt].z.minute); } else printf("Falsche Eingabe\n"); } void sortdate(struct termin sort[],int dates) { struct termin *temp; int i,j; temp=(struct termin *)malloc(sizeof(struct termin *)); if(NULL == temp) { printf("Konnte keinen Speicher reservieren...\n"); return; } for(i=0; i<dates; i++) { for(j=i+1;j<dates;j++) { if(sort[i].d.jahr>sort[j].d.jahr) { *temp=sort[j]; sort[j]=sort[i]; sort[i]=*temp; } } } printf(".....Sortiert!!\n"); } 120 SE void Eingabe(struct adressen neu[]) { unsigned int size; printf("Vorname : "); fgets(neu[nr].vname, MAX, stdin); /* newline-Zeichen entfernen */ size = strlen(neu[nr].vname); neu[nr].vname[size-1] = '\0'; printf("Nachname : "); fgets(neu[nr].nname, MAX, stdin); /* newline-Zeichen entfernen */ size = strlen(neu[nr].nname); neu[nr].nname[size-1] = '\0'; printf("Postleitzahl: "); do { scanf("%ld",&neu[nr].PLZ); } while(getchar()!= '\n'); printf("Wohnort : "); fgets(neu[nr].ort, MAX, stdin); printf("Geburtsjahr : "); do { scanf("%d",&neu[nr].geburtsjahr); } while(getchar()!= '\n'); nr++; } void Ausgabe(struct adressen all[]) { int i; for(i=0; i<nr; i++) { printf("Vorname.........:%s\n",all[i].vname); printf("Nachname........:%s\n",all[i].nname); printf("Postleitzahl....:%ld\n",all[i].PLZ); printf("Ort.............:%s",all[i].ort); printf("Geburtsjahr.....:%d\n\n",all[i].geburtsjahr); if((!(i%2))&& i!=0) { // fflush(stdin); printf("\n\tWeiter mit <Enter>\n\n"); getchar(); } } } 121 SE void Sortadress(struct adressen sort[]) { struct adressen *temp; int i,j; temp=(struct adressen *)malloc(sizeof(struct adressen *)); if(NULL == temp) { printf("Konnte keinen Speicher reservieren...\n"); return; } for(i=0; i<nr; i++) { for(j=i+1;j<nr;j++) { if(strcmp(sort[i].nname, sort[j].nname)>0) { *temp=sort[j]; sort[j]=sort[i]; sort[i]=*temp; } } } printf(".....Sortiert!!\n"); } 122 SE int main() { int eingabe,aendern; do { } printf("\tTerminverwaltung\n"); printf("\t----------------\n\n"); printf("\t-1- Neuer Termin\n"); printf("\t-2- Termine auflisten\n"); printf("\t-3- Termin ändern\n"); printf("\t-4- Termine sortieren\n"); printf("\t-5- Neue Adresse eingeben\n"); printf("\t-6- Adressen ausgeben\n"); printf("\t-7- Adressen sortieren\n"); printf("\t-8- Programm beenden\n"); printf("\n\tIhre Auswahl : "); scanf("%d",&eingabe); /* fflush(stdin); */ getchar(); switch(eingabe) { case 1 : newdate(t); break; case 2 : listdate(t,dates); break; case 3 : listdate(t,dates); printf("Welchen Termin ändern(Nr.?):"); scanf("%d",&aendern); replacedate(t,--aendern); break; case 4 : sortdate(t,dates); break; case 5 : Eingabe(xyz); break; case 6 : Ausgabe(xyz); break; case 7 : Sortadress(xyz); break; default : break; } }while(eingabe<8); printf("Bye\n"); return 0; 123 SE 11.8. Aufzählungstyp enum #include <stdio.h> enum zahl {NU_LL,EINS,ZWEI,DREI,VIER}; int main() { enum zahl x; x=NU_LL; printf("%d\n",x); x=EINS; printf("%d\n",x); x=ZWEI; printf("%d\n",x); x=DREI; printf("%d\n",x); } x=VIER; printf("%d\n",x); return 0; ● Festlegen von Konstanten ● gibt die Zahlen 0-4 aus ● entspricht dem Index der Aufzählung enum farben {rot, gelb=6, blau, gruen}; ● würde 0, 6, 7, 8 ausgeben 124 SE 11.9. Typendefinition mit typdef #include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX 30 static int x; struct adres { char vname[MAX]; char nname[MAX]; long PLZ; char ort[MAX]; int geburtsjahr; }adressen[100]; typedef struct adres ADRESSE; void Eingabe(int nr, ADRESSE *neu) { printf("Vorname : "); fgets(neu[nr].vname, MAX, stdin); printf("Nachname : "); fgets(neu[nr].nname, MAX, stdin); printf("Postleitzahl: "); do { scanf("%5ld",&neu[nr].PLZ); } while(getchar()!= '\n'); printf("Wohnort : "); fgets(neu[nr].ort, MAX, stdin); printf("Geburtsjahr : "); do { scanf("%4d",&neu[nr].geburtsjahr); } while(getchar()!= '\n'); } void Suche(ADRESSE *search,char buchstabe,int nr) { int i; for(i=0; i<=nr; i++) { if(search[i].nname[0] == buchstabe) { printf("\n\nGefunden unter Buchstabe :\"%c\"\n\n", buchstabe); printf("Vorname.......:%s",search[i].vname); printf("Nachname......:%s",search[i].nname); printf("Postleitzahl..:%ld\n",search[i].PLZ); printf("Ort...........:%s",search[i].ort); printf("Geburtsjahr...:%d\n",search[i].geburtsjahr); printf("\n\tWeiter mit <ENTER>\n"); getchar(); } } } 125 SE void Ausgabe(ADRESSE *all,int nr) { int i; for(i=0; i<nr; i++) { printf("Vorname.........:%s",all[i].vname); printf("Nachname........:%s",all[i].nname); printf("Postleitzahl....:%ld\n",all[i].PLZ); printf("Ort.............:%s",all[i].ort); printf("Geburtsjahr.....:%d\n\n",all[i].geburtsjahr); if((!(i%2))&& i!=0) { //fflush(stdin); printf("\n\tWeiter mit <Enter>\n\n"); getchar(); } } } void Sort(ADRESSE *sort,int nr) { ADRESSE *temp; int i,j; temp = (ADRESSE *)malloc(sizeof(ADRESSE *)); if(NULL == temp) { printf("Konnte keinen Speicher reservieren...\n"); return; } } for(i=0; i<nr; i++) { for(j=i+1;j<nr;j++) { if(strcmp(sort[i].nname, sort[j].nname)>0) { *temp=sort[j]; sort[j]=sort[i]; sort[i]=*temp; } } } printf(".....Sortiert!!\n"); 126 SE int main() { int auswahl; char c; do { } printf("-1- Neue Adresse eingeben\n"); printf("-2- Bestimmte Adresse ausgeben\n"); printf("-3- Alle Adressen ausgeben\n"); printf("-4- Adressen sortieren\n"); printf("-5- Programm beenden\n"); printf("\nIhre Auswahl : "); scanf("%d",&auswahl); /* fflush(stdin); */ getchar(); switch(auswahl) { case 1 : Eingabe(x++,adressen); break; case 2 : printf("Anfangsbuchstabe Nachnamen :"); do { scanf("%c",&c); } while(getchar()!= '\n'); Suche(adressen,c,x); break; case 3 : Ausgabe(adressen,x); break; case 4 : Sort(adressen,x); break; default: break; } }while(auswahl <5); return 0; ● typedef struct adres ADRESSE; neuen Typendefinition ● auf die Struktur kann jetzt mit z.B. ADRESSE *temp; zugegriffen werden typedef Typendefinition Bezeichner; ● sinnvoll, wenn mehrere Strukturen mit ähnliche Aufbau verwendet werden typedef struct adres { char vname[20]; char nname[20]; long PLZ; char ort[20]; int geburtsjahr; }ADRESSE; ADRESSE adressen[100]; ● andere Möglichkeit der Definition des neuen Typs 127 SE ● andere Anwendung typedef typedef typedef typedef typedef typedef ● unsigned unsigned unsigned unsigned unsigned unsigned char BYTE; int WORD; long DWORD; double QWORD; int uint; char uchar; uint wert1, wert2; /*1 /*1 /*1 /*1 Byte = 8 BIT*/ WORD = 16 BIT*/ DOUBLE WORD = 32 BIT*/ QUAD WORD = 64 BIT */ wäre dann äquivaltent zu unsigned int wert1,wert2; 12. Dynamische Datenstrukturen ● Vermischung von Zeigern, dynamischer Speicherverwaltung und Strukturen 12.1. Lineare Listen – einfache verkettete Listen ● Es wird eine Struktur mit einem Zeiger vom Typ der Struktur selbst definiert struct datum { int tag; int monat; int jahr; }; struct angestellt { char name[20]; char vorname[20]; struct datum alter; struct datum eingest; long gehalt; struct angestellt *next; }; Zeiger *next mit dem selben Typ, wie die Struktur selbst → Verkettung ● *next verweist auf die nächste Struktur, die wiederum einen Zeiger beinhaltet ● eine Art Array von Strukturen, jedoch ohne Index, sondern mit Zeiger ● Ende durch den Nullzeiger festlegen struct angestellt *next = NULL; ● struct angestellt *next; ● struct angestellt *structzeiger; Deklarieren einer Strucktur über Zeiger ● (*structzeiger).name oder structzeiger->name Zugriff auf einzelne Elemente ● es wird ein Anfang benötigt struct angestellt *anfang=NULL; 128 SE struct datum { int tag; int monat; int jahr; }; struct angestellt { }; char name[20]; char vorname[20]; struct datum alter; struct datum eingest; long gehalt; struct angestellt *next; struct angestellt *next=NULL; struct angestellt *anfang=NULL; Beispiel Verwaltung Mitarbeiter #include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX 20 struct datum { int tag; int monat; int jahr; }; struct angestellt { char name[MAX]; char vorname[MAX]; struct datum alter; struct datum eingest; long gehalt; struct angestellt *next; }; struct angestellt *next = NULL; struct angestellt *anfang=NULL; /*Wir hängen einen Datensatz an oder geben einen neuen ein n=name,v=vornam,at=alter.tage,am=alter.monat,aj=alter.jahr eint=eigestellt tag,einm=eingestellt monat,einj=eingest. Jahr g=gehalt*/ 129 SE void anhaengen(char n[],char v[],int at,int am,int aj, int eint,int einm,int einj,long g) { /*Zeiger zum Zugriff auf die einzelnen Elemente der Struktur*/ struct angestellt *zeiger; /* Wir fragen ab, ob es schon ein Element in der Liste gibt. Wir suchen das Element, auf das unser Zeiger *anfang zeigt. Falls *anfang immer noch auf NULL zeigt, bekommt *anfang die Adresse unseres 1. Elements und ist somit der Kopf (Anfang) unserer Liste*/ if(anfang == NULL) { /* Wir reservieren Speicherplatz für unsere Struktur für das erste Element der Liste*/ if((anfang =(struct angestellt *) malloc(sizeof(struct angestellt))) == NULL) fprintf(stderr,"Kein Speicherplatz vorhanden " "für anfang\n"); strcpy(anfang->name,n); strcpy(anfang->vorname,v); anfang->alter.tag=at; anfang->alter.monat=am; anfang->alter.jahr=aj; anfang->eingest.tag=eint; anfang->eingest.monat=einm; anfang->eingest.jahr=einj; anfang->gehalt=g; /* Somit haben wir unseren Anfang der Liste. Von nun an zeigt der Zeiger anfang immer auf das Element vor ihm. Da dies aber jetzt das 1. Element der Liste war, zeigt der Zeiger anfang auf den Zeiger next. next zeigt am Ende immer wieder NULL*/ anfang->next=NULL; } /* Es scheint schon mindestens ein Element in der Liste vorhanden zu sein, da der Anfang nicht == NULL ist. Jetzt suchen wir so lange nach dem nächsten Element, bis der *next-Zeiger auf NULL zeigt. Somit haben wir das Ende der Liste gefunden und können einen neuen Datensatz anhängen*/ 130 SE else { zeiger=anfang; /* Wir zeigen auf das 1. Element */ while(zeiger->next != NULL) zeiger=zeiger->next; /* Wir reservieren einen Speicherplatz für das letzte Element der Liste und hängen es an.*/ if((zeiger->next =(struct angestellt *) malloc(sizeof(struct angestellt))) == NULL) fprintf(stderr,"Kein Speicherplatz für das " "letzte Element\n"); zeiger=zeiger->next; /*zeiger auf neuen Speicherplatz*/ strcpy(zeiger->name,n); strcpy(zeiger->vorname,v); zeiger->alter.tag=at; zeiger->alter.monat=am; zeiger->alter.jahr=aj; zeiger->eingest.tag=eint; zeiger->eingest.monat=einm; zeiger->eingest.jahr=einj; /*Wir terminieren wieder unsere Datenstruktur*/ zeiger->gehalt=g; zeiger->next=NULL; } } /*Funktion zur Eingabe der Daten*/ void eingabe() { char nam[MAX],vorn[MAX]; int atag,amon,ajahr,eintag,einmon,einjahr; long gehalt; printf("Name........................: "); fgets(nam, MAX, stdin); printf("Vorname.....................: "); fgets(vorn, MAX, stdin); printf("Alter...........(tt.mm.jjjj): "); scanf("%2d.%2d.%4d",&atag,&amon,&ajahr); printf("Eingestellt am..(tt.mm.jjjj): "); scanf("%2d.%2d.%4d",&eintag,&einmon,&einjahr); printf("Monatsgehalt................: "); scanf("%ld",&gehalt); getchar(); /* Eingegebenen Datensatz hinten anhängen */ anhaengen(nam,vorn,atag,amon,ajahr,eintag, einmon,einjahr,gehalt); } int main() { while(1) eingabe(); return 0; } ● keine Abbruchbedingung!!! 131 SE 12.1.1. Erstes Element in der Liste löschen ● Anfang muss auf 2. Element zeigen ● über temporären Zeiger und Freigabe des Speicherbereichs des ersten Eintrags /*Funktion zum Löschen */ void loesche(char wen[]) { struct angestellt *zeiger ,*zeiger1; /*Ist überhaupt ein Element vorhanden?*/ if(anfang != NULL) { /*Ist unser 1. Element das von uns gesuchte (wen[])?*/ if(strcmp(anfang->name,wen) == 0) { zeiger=anfang->next; free(anfang); anfang=zeiger; } 12.1.2. beliebiges Element der Liste löschen ● Zuerst Überprüfen, ob das gesuchte Element nicht das erste ist ● wenn nicht, löschen des beliebigen Elements /*Funktion zum Löschen einer Datei*/ void loesche(char wen[]) { struct angestellt *zeiger ,*zeiger1; /*Ist überhaupt ein Element vorhanden?*/ if(anfang != NULL) { /*Ist unser 1. Element das von uns gesuchte (wen[])?*/ if(strcmp(anfang->name,wen) == 0) { zeiger=anfang->next; free(anfang); anfang=zeiger; } else { /*Es ist nicht das 1. Element zu löschen. Wir suchen in der weiteren Kette, ob das zu löschende Element vorhanden ist*/ zeiger=anfang; while(zeiger->next != NULL) { zeiger1=zeiger->next; /*Ist die Adresse von zeiger1 132 SE der gesuchte Name?*/ if(strcmp(zeiger1->name,wen) == 0) { /*Falls ja dann.....*/ zeiger->next=zeiger1->next; free(zeiger1); break; } zeiger=zeiger1; }/*Ende while*/ } /*Ende else*/ /*Ende if(anfang != NULL)*/ } else printf("Es sind keine Daten zum Löschen vorhanden!!!\n"); } 12.1.3. Elemente der Liste ausgeben ● Liste wird durchlaufen, bis der letzte Zeiger auf Null verweist void ausgabe() { struct angestellt *zeiger; zeiger=anfang; printf("||=====================================" "==================||\n"); printf("|%10cName%10c |Geburtsdatum|" "Eingestellt|Gehalt|\n",' ',' '); printf("||=====================================" "==================||\n"); while(zeiger != NULL) { printf("|%12s,%-12s| %02d.%02d.%04d|" "%02d.%02d.%04d|%06ld|\n", zeiger->name,zeiger->vorname,zeiger->alter.tag, zeiger->alter.monat,zeiger->alter.jahr, zeiger->eingest.tag,zeiger->eingest.monat, zeiger->eingest.jahr,zeiger->gehalt); printf("|-----------------------------------" "----------------------|\n"); zeiger=zeiger->next; } } 133 SE 12.1.4. Alle Elemente löschen void loesche_alles() { struct angestellt *zeiger, *zeiger1; /*Ist überhaupt eine Liste zum Löschen vorhanden*/ if(anfang != NULL) { /*Es ist eine vorhanden....*/ zeiger=anfang->next; while(zeiger != NULL) { zeiger1=anfang->next->next; anfang->next=zeiger1; free(zeiger->next); free(zeiger); zeiger=zeiger1; } /*Jetzt löschen wir erst den Anfang der Liste*/ free(anfang->next); free(anfang); anfang=NULL; printf("Liste erfolgreich gelöscht!!\n"); } else fprintf(stderr,"Keine Liste zum Löschen vorhanden!!\n"); } 12.1.5. Elemente in die Liste einfügen ● Sortiertes einfügen ● Liste soll nach Nachnamen geordnet werden ● 4 Möglichkeiten Es ist noch kein Element in der Liste vorhanden, und das eingegebene ist das erste Element. Das eingegebene Element ist das größte und wird somit hinten angehängt. Das eingegebene Element ist das kleinste und wird ganz an den Anfang eingefügt. Die letzte Möglichkeit ist gleichzeitig auch die schwierigste. Das Element muss irgendwo in der Mitte eingefügt werden. 134 SE void sortiert_eingeben(char n[],char v[],int at,int am,int aj, int et,int em,int ej,long geh) { struct angestellt *zeiger, *zeiger1; /*Ist es das 1. Element der Liste? */ if(anfang==NULL) anhaengen(n,v,at,am,aj,et,em,ej,geh); /*Es ist nicht das 1. Element. Wir suchen so lange, bis das gesuchte Element gefunden wird oder wir auf NULL stoßen*/ else { zeiger=anfang; while(zeiger != NULL && (strcmp(zeiger->name,n)<0)) zeiger=zeiger->next; /*Falls der Zeiger auf NULL zeigt, können wir unser Element hinten anhängen, da unser neues Element das "grösste" zu sein scheint */ if(zeiger==NULL) anhaengen(n,v,at,am,aj,et,em,ej,geh); /*Ist unser neues Element das kleinste und somit kleiner als das 1. Element, so müssen wir es an den Anfang hängen */ else if(zeiger==anfang) { anfang=(struct angestellt *) malloc(sizeof(struct angestellt)); if(NULL == anfang) { fprintf(stderr, "Kein Speicher\n"); return; } strcpy(anfang->name,strtok(n, "\n")); strcpy(anfang->vorname,strtok(v, "\n")); anfang->alter.tag=at; anfang->alter.monat=am; anfang->alter.jahr=aj; anfang->eingest.tag=et; anfang->eingest.monat=em; anfang->eingest.jahr=ej; anfang->gehalt=geh; anfang->next=zeiger; } /*Die letzte Möglichkeit ist, dass wir das Element irgendwo in der Mitte einfügen müssen*/ 135 SE else { zeiger1=anfang; /*Wir suchen das Element, das vor dem Zeiger zeiger steht*/ while(zeiger1->next != zeiger) zeiger1=zeiger1->next; zeiger=(struct angestellt *) malloc(sizeof(struct angestellt)); if(NULL == zeiger) { fprintf(stderr, "Kein Speicher"); return; } strcpy(zeiger->name,strtok(n, "\n")); strcpy(zeiger->vorname,strtok(v, "\n")); zeiger->alter.tag=at; zeiger->alter.monat=am; zeiger->alter.jahr=aj; zeiger->eingest.tag=et; zeiger->eingest.monat=em; zeiger->eingest.jahr=ej; zeiger->gehalt=geh; /*Wir fügen das neue Element ein*/ zeiger->next=zeiger1->next; zeiger1->next=zeiger; }//Ende else }//Ende else } 136 SE 13. Dateien Hauptspeicher ist flüchtig speichern von Daten auf Festplatte nach Programmende → nicht flüchtig Zugriff auf Daten bei Programmstart Organisationseinheit ist Datei Betriebssystem beitet Schnittstelle zum Zugriff auf verschiedene Speichermedien → Stream – Datenstrom ● Bibliotheksfunktionen zur Kommunikation mit dem Betriebssystem ● ● ● ● ● ● Ende einer Datei durch EOF – End Of File markiert ● Zugriff auf Elemente einer Datei durch File Position Pointer ● Funktion feof gibt einen Wert ungleich 0 zurück, wenn man sich am Dateiende befindet – while(!feof(pDatei)) ● Standardstreams: (File*) stdin → Tastatur (File*) stdout → Bildschirm (File*) stderr → Fehlerausgabe auf dem Bildschirm Das sind Pointer aus speziellen Datentyp namens File – in stdio.h definiert ● Binärdateien werden „im Block“ geschrieben und gelesen ähnlich der Laufzeitdaten eines Prozesses gut zur Speicherung von Programmdaten Äquivalente Programme #include <stdio.h> #include <stdio.h> int main() int main() { { int iZahl=0; int iZahl=0; printf("Zahl eingeben: "); FILE *pAusgabe=0, *pEingabe=0; scanf(" %d", &iZahl); pAusgabe = stdout; printf("Zahl: %d", iZahl); pEingabe = stdin; return 0; fprintf(pAusgabe, "Zahl eingeben:"); } fscanf(pEingabe, "%d", &iZahl); fprintf(pAusgabe, "Zahl: %d", iZahl); return 0; } 137 SE 13.1 Schema für Dateioperationen 1. Schritt: Definition eines Zeigers des Typs FILE 2. Schritt: Öffnen einer Datei und gleichzeitige Initialisierung des File-Zeigers mit Hilfe der Funktion fopen() 3. Schritt: Dateioperationen mittels spezieller Funktionen Zeichenweise Ein-/Ausgabe: fputc(), fgetc() Zeilenweise Ein-/Ausgabe: fputs(), fgets() Formatierte Ein-/Ausgabe: fprintf(), fscanf() Blockweise Ein-/Ausgabe: fwrite(), fread() - Binär 4. Schritt: Schließen der Datei mit fclose() 13.2. fopen() ● ● ● ● ● ● Definiert in stdio.h initialisiert Pointer FILE* fopen(const char *szFilename, const char *szMode) Liefert Zeiger auf geöffnete Datei oder NULL zurück szFilename zeigt auf String szMode zeigt auf String, der die Art des Zugriffs enthällt ● Fehlerursachen: Pfadangabe nicht korrekt Schreiben ohne Schreibrechte Schreiben auf volle Festplatte Lesen einer nicht existenten Datei Lesen ohne Leseberechtigung 13.2.1. Modus der Dateiöffnung r Lesen – Zugriff startet am Anfang r+ Lesen und schreiben – Zugriff startet am Anfang w Legt eine neue Datei an oder schrumpft w+ Legt eine neue Datei an oder schrumpft eine existierende auf 0 um sie zu eine existierende auf 0 um sie zu lesen bearbeiten – Zugriff startet am Anfang und zu beschreiben – Zugriff startet am Anfang a Schreiben – Zugriff am Ende der Datei - a+ Öffnet eine existierende Datei oder append erzeugt eine neue – Lesezugriff uneingeschränkt, Schreibzugriff nur am Dateiende 138 SE ● Zusatz t (default) für translate mode → rt, at+ Konvertierung für ASCII-Daten - \n umwandeln praktisch um die Datei danach mit einem Texteditor zu bearbeiten oder zu öffnen ● Zusatz b für binary mode → rb, ab+ keinerlei Konvertierung – Daten werden so geschrieben, wie sie vorliegen praktisch für interne Darstellung → relativ einfaches speichern und lesen von großen Datenmengen Beispiel: Zeichenweise Ein- und Ausgabe #include <stdio.h> int main() { FILE *pDatei = fopen("Textdatei.txt", "wt"); char cZeichen; printf("Testdatei bereit, bitte Eingabe, Abbruch mit '$' \n"); do { cZeichen = getc(stdin); putc (cZeichen, pDatei); } while (cZeichen!='$'); fclose(pDatei); pDatei=fopen("Textdatei.txt", "rt"); printf("\nDie Eingabe war:"); do { printf("%c", cZeichen = getc(pDatei)); } while (cZeichen!='$'); fclose(pDatei); return 0; } ● überprüfung ob fopen nicht NULL zurückgibt notwendig!!! ● fputs schreibt Text auf einen Rutsch in eine Datei fputs("So kann Text auf einen Rutsch \nin die Datei nach dem Komma geschrieben werden", pDatei); ● fgets gibt eine Zeile, jedoch max 5 Zeichen aus einer Datei aus ● zweite Zeile wird ausgegeben, wenn gleicher Syntax nocheinmal verwendet wird fgets(szString, 5, pDatei); 139 SE ● formatiertes schreiben von Text in eine Datei mit fprintf #include <stdio.h> int main() { FILE *pDatei = fopen("Textdatei.txt", "wt"); int iFaktor1=0, iFaktor2=0; fprintf(pDatei, "\nMultiplikationstabelle"); fprintf(pDatei, "\n----------------------"); for(iFaktor1=1; iFaktor1<=10; iFaktor1++) for(iFaktor2=1; iFaktor2<=10; iFaktor2++) fprintf(pDatei, "\n %3i * %3i = %4i", iFaktor1, iFaktor2, iFaktor1*iFaktor2); fclose(pDatei); } return 0; ● blockweise Ein- & Ausgabe mit fwrite und fread #include <stdio.h> typedef struct { int iX, iY; } KOORDINATEN; int main() { KOORDINATEN APunkte[5]= { { 0, 0 }, { -1, -1 }, { 1, 1 }, { -1, 1 }, { 1, -1 } }; KOORDINATEN AKontrolle[5]; int i=0; FILE *pDatei = fopen("Textdatei.bin", "wb"); fwrite(APunkte, sizeof(KOORDINATEN), 5, pDatei); fclose(pDatei); pDatei=fopen("Textdatei.bin", "rb"); fread(AKontrolle, sizeof(KOORDINATEN), 5, pDatei); fclose(pDatei); for (i=0; i<5; i++) printf("x: %i\t y: %i\n", AKontrolle[i].iX, AKontrolle[i].iY); return 0; } 140 SE ● ● ● ● nur binäres schreiben und lesen unformatiert aber mit fester Satzstruktur Plattformabhängig Übergebene Parameter Zeiger auf Datenquelle Größe des Elements Anzahl der Elemente zeigt auf den Strom, der benutzt werden soll 14. Headerfiles ● ● ● ● ● ● Aufteilung des Programmcodes → abgeschlossene Einheit Komplexitätsbeherrschung getrenntes Kompilieren und Debuggen von Programmteilen Schnittstellten zwischen Programmteilen Vereinfacht Wiederverwendung von Funktionen Vereinfachte Wartung und Änderunge ● Strukturierung in der Implementierung Aufteilen in mehrere Funktionen Aufteilen in Funktionspakete und mehrere Dateien Seperates kompilierne von Teilsystemen Kapselung der Implementierung ● Einbinden von Quellcode #include "Funktion.c" wird von Präprozessor reinkopiert ● Probleme Namenskonflikte – globale Variablen, Datentypen, Funktionen Mehrfachdefinition, wenn eine Datei mehrfach eingebunden wird – verschiedene Funktionen benötigen eine untergeordnete Funktion und werden von einer übergeordneten Funktion eingebunden bei Ände rung muss das komplette Programm neu kompiliert werden 14.1. One-Definition-Rule ● Deklarationen dürfen mehrfach erfolgen, Definitionen nur einmal bei Deklaration nur einführung von Namen, keine Speicherreservierung Deklaration ist Beschreibung der Schnittstellen Definition ist Implementierung //Deklaration int addieren(int); /*Einführung des Funktionsnamens mit Schnittstellenbeschreibung, keine Speicherreservierung*/ extern const float Pi; /* Schlüsselwort extern, Einführung des Namens, keine Speicherreservierung*/ //Definition int i=5; /* Name und gleichzeitige Speicherreservierung*/ 141 SE 14.2 Inhalt Header ● ● ● ● Funktionsprototypen reine Deklaration von externen globalen Variablen und Konstanten Definition von nach außen sichtbaren Konstanten und Typendefinitionen nach außen sichtbare includes anderer Header //Funktionsprototypen int addieren (int, int); void zeig(int a); ELEMENT eingelsen void; //includes /*Enthält Definition von LISTE, ELEMENT*/ #include "Liste.h" void insert (LISTE*, ELEMENT*); //Deklaration Variablen und Konstanten extern int iMax; extern const float Pi; //Definition von Konstanen und Typendefinitionen #define MAX 81 typedef struct { char szVorname [MAX]; char szNachname [MAX]; } NAME; 14.2. Inhalt Implementierungsdatei ● ● ● ● Implementierung der Funktionen includes eigener Header-Files – nach außen nicht sichtbar Definition globler Variablen und Konstanten Definition von nach außen nicht sichtbare Konstanten und Typendefinitionen //Funktionsimplementierung int addieren (int i, int j) { return i+j; } //Einbinden von Headern - nicht nach aussen sichtbar #include "xxx.h" #include <stdio.h> void zeig(int i) { printf("%i", a); } //Definition globale Konstanten und Variablen int iMax=100; const float fPi=3,14; //Definition nach aussen nicht sichtbare Konstanten und Typendefinitionen #define MAX 81 typedef struct { char szVorname[MAX]; char szNachname[MAX]; }NAME; 142 SE 14.3. Einbinden von Headern ● Problem, wenn ein Header mehrfach eingebunden wird → bedingtes Einbinden #ifndef XXX_H #define XXX_H XXX_H #endif ● ● ● ● ifndef überprüft ob ein Header schoneinmal eingebunden wurde wenn nicht wird die Strinkonstante definiert Konvention: erstezten des Punktes durch einen Unterstrich Klammer durch endif beenden //Liste.h #ifndef LISTE_H #define LISTE_H LISTE_H typedef struct { ...}DATEN; typedef struct LISTENELEMENT { ...}ELEMENTE; typedef struct { ...}LISTE; void deletList(LISTE*); void insert(LISTE*, DATEN*); ... #endif //Liste.c #include "Liste.h" void deletList(LISTE *pEineListe) { if (pEineLIste->pFirst==0) return; else { ... } } void insert (LISTE* pEineListe, DATEN *pEinmalDaten) { ... } //main.c #include "Liste.h" int main (void) { LISTE ListeGeschaeftsdaten; ... } 14.4. Objekt Files Kompilieren eines Teils eines Programms speicher in .obj Files können vom Linker zusammengefügt werden einzelne .obj-Files konnen neu kompiliert werden, wenn deren Programmcode erzeugt wurde ohne alle Teile eines Programms kompilieren zu müssen → erneutes linken ● Schnittstellen müssen unbedingt erhalten bleiben oder angepasst werden ● ● ● ● 143 SE ● dynamisches Binden ermöglicht updates Windows: ddl – dynamic link Libraries Unix: schared libraries mehrere Prozesse können gleiche dlls verwenden → nur einmal im Speicher, leicht austauschbar 15. Objektorientierung 15.1. Stärken der Strukturierten Programmierung Zielorientiertes Vorgehen Gute Umsetzbarkeit in Programmcode Schlanker effizienter Code Wiederverwendung und Komplexitätsbeherrschung durch Funktionen und mehrere Dateien ● höheres Abstraktionsniveau wie Assembler ● ● ● ● 15.2. Schwächen der Strukturierten Programmierung ● Starke Auxsrichtung an von Neumann Architektur → große semantische Lücke zwischen Realität und Programm ● Geringe Abstraktionstiefe ● relativ geringer Wiederverwendungswert ● schlechte Möglichkeiten für Veränderungen und Updates ● Schwierigkeit zur Bewälltigung der Komplexität bei großen Programmen 15.3. Imperativ / prozedurales Programmierparadigma ● ● ● ● ● Zerlegung des Anwendungsproblems im Mittelpunkt stehen Algorithmen und Daten prodzedural ist Aufteilung in Untereinheiten imerpativ ist Art der Befehlsform Trennung in unabhängige Programmdaten, die im Speicher liegen und darauf warten von Programmschirtten verarbeitet zu werden ● a=2+3 ● der Variablen a wird die Summe aus 2 und 3 zugewiesen ● Zuweisungs-, +-Operator und Ergebnisvariable notwendig 144 SE 15.4. objektorientiertes Programmierparadigma ● ● ● ● ● Zusammenspeil vieler selbstständiger Software-Objekte erledigen gemeinsam die Gesammtaufgabe jedes Objekt stellt Dienstleistungen zur Verfügung Dienstleistungen können von anderne Objekten genutzt werden Software-Objekte repräsenteiren reale Objekte ● Obejekt 2 bekommt Objekt 3 übergeben und die Aufgabe beide durch die Additionsfunktion zu addieren ● Summenobjekt 5 wird neu geschaffen und enthällt die gleichen Funktionen wie das Objekt 2 15.3. Objekte ● ● ● ● ● ● ● SW-Einheiten besitzen und verwalten eigenen Daten Daten sind Attribute, deren Werte bestimmten den aktuellen Zustand des Objekts bieten Dienstleistungen an, stellen dazu Methoden bereit aktive Funktionsträger einers Programms Kooperaieren mit einander tragen Verantwortung für die eigene Funktionalität und die eigenen Attribute 15.4. Information Hidding ● Attribute können nur vom Objekt selbst geändert werden ● Objekte kapseln ihre Daten 15.5. Zugriffsmechanismen ● Privat Zugriff nur durch Objekt selbst kein Zugriff von außen Information Hidding ● protected Zugriff für alle Objekte abgeleiteter Klassen möglich Vererbung ● package Zugriff freigegeben für alle Objekte des nächsten umschließenden Packetes ● public Uneingeschränkter Zugriff 145 SE 15.6. Klassen ● Art Schablone für die Erzeugung von Objekten ● definiert Attribute, Methoden und Zugriffsmechanismen ● Objekte entstehen durch Instanzierung einer Klasse Header //Telefonnummer.h #ifndef TELEFONNUMMER_H #define TELEFONNUMMER_H class CTel { public: //Konstruktor-Methode CTel(); //Ãœbrige Methoden void stzteNummer(int, int, int); void zeigeNummer(); private: int m_laenderkennziffer; int m_Ortsvorwahl; int m_Durchwahl; }; #endif ● Klassenname Ctel – Konvention: C für Class ● Zugriffsmechanismen privat und public ● Methodendeklaration Methoden, die wie die Klassen heißen sind Konstruktoren und werden bei der Instanziierung automatisch aufgerufen ● Attributdeklaration 146 SE Implementierungsfile //Telefonnummer.cpp #include "Telefonnummer.h" #include <iostream> using namespace std; CTel::CTel() { m_iLaenderkennziffer=49; m_iOrtsvorwahl=0; m_iDurchwahl=0; } void CTel::setzteNummer(int L, int O, int D) { m_iLaenderkennziffer=L; m_iOrtsvorwahl=O; m_iDurchwahl=D; } void CTel::zeigeNummer() { cout << "+" << m_iLaenderkennziffer << "(" << m_iOrtsvorwahl << ")" << m_iDurchwahl << end1; } ● :: ist Bereichsoperator – legt die Zugehörigkeit einer Methode zu einer Klasse fest ● cout gibt Inhalt auf Standardausgabe aus ● Zuweisung an die Klassenvariablen durch Methoden Main //main.cpp #include "Telefonnummer.h" int main(void) { //Objektinstanziierung und automatischer Aufruf der Konstruktoren CTel eineTel; eineTel.zeigeNummer(); //Wertzuweisung ueber Methode eineTel.setzteNummer (49, 89, 2345435); eineTel.zeigeNummer(); } //Zugriff auf privaten Bereich wäre: //eineTel.m_iLaenderkennziffer=43; //kein direkter Zugriff auf die Variablen! return 0; ● definierte Klasse wird bei Instanziierung wie ein Typ benutzt ● Zugriff auf öffentliche Methoden durch den Punktoperator ● Zugriffsverletzung bei direktem Zugriff auf die Variablen 147 SE 15.7. Beziehung zwischen Klassen 15.7.1. Vererbung ● ● ● ● ● ● ● ● Verschiedene Arten, wie Klassen unterinander in Beziehung stehen grafische Beschreibung durch UML – Unified Modeling Language bekanntestes Prinzip der Codewiederverwendung Unterklassen – Teilklassen – ableitete Klasse – Subklassen erben Attribute und Methoden ihrer Elternklasse Attribute und Methoden können ergänzt und geändert werden → Spezalisierung der generalisierten Elternklasse Problem bei Mehrfachvererbung → Namenskonflikte Polymorphismus ist verschiedene Art und Weise der Ausführung einer von einer Elternklasse vererbten Methode an eine Unterklasse Zugriff der Unterklassen auf die Attribute und Methoden der Oberklasse nur dann gewährt, wenn Zugriffsmechanismus als protected definiert ist – nicht private Attribute können nur über öffentliche Schnittstelle verändert werden //FestTel.h #ifndef FESTTEL_H #define FESTTEL_H FESTTEL_H #include "Telefonnummer.h" class CFestTEl:public CTel { }; ● Klasse CFestTel wird von der Klasse CTel abgeleitet – Doppelpkt. und public ● public vererbt alle Attribute und Methoden mit den gleichen Zugriffsmechanismen, wie die Elternklasse #endif //FestTel.cpp #include "FestTel.h" //main.cpp #include "FestTel.h" int main(void) { //Die gesammte Funktionalität wurde von CTel vererbt CFestTel eineTel; eineTel.zeigeNummer(); eineTel.setzteNummer(49, 93, 234455); eineTel.zeigeNummer(); } return 0; 148 SE //HandyTel.h #ifndef HANDYTEL_H #define HANDYTEL_H #include "Telefonnummer.h" class CHandyTel : public CTel { public: //Konstruktro-Methode CHandyTel(); //Uebrige Methoden void setzteNetz(char*); char* gibNetz(); void zeigNummer(); private: char m_szNetzbetreiber[10]; }; #endif ● Hinzufügen von Attributen und Methoden ChandyTel();, void ● setzteNetz(char*); und char* gibNetz(); Überschreiben der Methode void zeigNummer(); → Ausgabe des Netzbetreibers 15.7.2. Aggregation ● Klassen gehören anderen Klassen als Datenelement an ● aggredierende Klasse enthällt aggregierte Klassen ● eine Telefonbuch kann Fax-, Handy- und Festnetznummern einer Person enthalten, müssen aber nicht ● Zugriff auf privat und protected Elemente der aggregierten Klasse innerhalb der aggregierenden Klasse verwehrt – nur über public //TelBuch.h #ifndef TELBUCH_H #define TELBUCH_H #include "FestTel.h" ● jedes Objekt der Klasse CtelBuch aggregiert ein Array von Festnetznummern als interne Attribute - CFestTel m_ATels[MAX]; class CTelBuch { public: CTelBuch(); void insertNummer(CFestTel); ... private: CFestTel m_ATels[MAX]; ... }; #endif 149 SE 15.2.3. Assoziation ● Lose Beziehung von Klassen unterinander ● eine Person kann eine oder mehrere Telefonnummber haben, muss aber nicht ● Verbindung von Klasse Telefonnummer und Klasse Person 15.2.4. Unified Modeling Language - UML ● Grafische Beschreibung und Darstellung ● zur Spezifikation, Visualisierung, Konstruktion und Dokumentation ● statische Beziehungen zwischen Klassen werden im Klassendiagramm dargestellt ● implementierungsunabhängig Grundelemente ● + für public, - für private ● Verbindung der Klassen durch Pfeile → Vererbungsbeziehungen ● Raute für Aggregationsbeziehung ● hat ein über Pfeil für Assoziationsbeziehung 150