Programmieren in C 1 2 3 4 5 6 7 Wirtschaftsgymnasium Eisenberg LK IV Einführung ................................................................................................................................... 3 Grundlegende Sprachelemente .................................................................................................... 5 2.1 Kommentare ......................................................................................................................... 5 2.2 Datentypen und Variablen ................................................................................................... 5 2.2.1 Variablennamen ........................................................................................................... 5 2.3 Datentypen ........................................................................................................................... 6 2.3.1 Ganzzahlige Variablen ................................................................................................. 6 2.3.2 Fließkommazahlen ....................................................................................................... 6 2.3.3 Zeichen ......................................................................................................................... 6 2.4 Konstanten ........................................................................................................................... 6 2.5 Anweisungen und Blöcke .................................................................................................... 8 Kontrollstrukturen ........................................................................................................................ 9 3.1 Selektion............................................................................................................................... 9 3.1.1 Einfache Abfragen (if – else) ....................................................................................... 9 3.1.2 Verschachtelte if- Anweisung ...................................................................................... 9 3.1.3 Die switch - case – Anweisung ................................................................................. 16 3.2 Iteration .............................................................................................................................. 18 3.2.1 Abweisende Schleife mit while (kopfgesteuerte Schleife) ........................................ 18 3.2.2 For-Schleife ................................................................................................................ 21 3.2.3 Do-while Schleife ...................................................................................................... 23 Unterprogramme ........................................................................................................................ 26 4.1 Hinführung ......................................................................................................................... 26 4.2 Funktionen ......................................................................................................................... 26 4.3 Vordefinierte Funktionen (Standard-Funktionen) ............................................................. 27 4.4 Benutzerdefinierte Funktionen ........................................................................................... 27 4.4.1 Ort der Definition von Funktionen............................................................................. 27 4.5 Rekursive Funktionen ........................................................................................................ 32 Blöcke und Funktionen .............................................................................................................. 36 5.1 Stuktur eines Blockes ......................................................................................................... 36 5.1.1 Schachtelung von Blöcken ......................................................................................... 36 5.2 Gültigkeit, Sichtbarkeit und Lebensdauer .......................................................................... 37 Felder (Arrays) und Zeichenketten ............................................................................................ 42 6.1 Arrays verwenden .............................................................................................................. 42 6.2 Das eindimensionale Feld .................................................................................................. 42 6.3 Arrays initialisieren und darauf zugreifen ......................................................................... 42 6.4 Initialisierung mit einer Initialisierungsliste ...................................................................... 45 6.5 Strings (Zeichenketten) ...................................................................................................... 46 6.5.1 Strings initialisieren ................................................................................................... 46 6.5.2 Einlesen von Strings................................................................................................... 47 6.6 Die Standard- Bibliothek <string.h> .................................................................................. 47 6.6.1 strcat() – Strings aneinanderhängen ........................................................................... 48 6.6.2 strchr() – ein Zeichen im String suchen ..................................................................... 48 6.6.3 strcmp() – Strings vergleichen ................................................................................... 49 6.6.4 strcpy() – einen String kopieren ................................................................................. 50 6.6.5 strlen() - Länge eines Strings ermitteln ..................................................................... 51 Zeiger (Pointer) .......................................................................................................................... 53 7.1 Pointertypen und Pointervariablen ..................................................................................... 53 7.2 Wertzuweisung an einen Pointer, Adressoperator ............................................................. 53 7.2.1 Dereferenzierung: Zugriff auf ein Objekt über einen Pointer .................................... 54 7.2.2 Speichergröße von Zeigern ........................................................................................ 56 7.2.3 Wertebereich von Pointern, NULL-Pointer ............................................................... 57 Seite 1 von 69 Einführung Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 7.3 Arrays und Pointer ............................................................................................................. 57 7.3.1 Äquivalenz von Array- und Pointernotation .............................................................. 57 7.3.2 Zeigerarithmetik ......................................................................................................... 58 7.4 Übergabe von Arrays und Zeichenketten........................................................................... 59 7.4.1 Das Schlüsselwort const bei Pointern und Arrays ..................................................... 60 7.4.2 Zeiger als Funktionsparameter (call – by – reference) .............................................. 61 8 Sortierverfahren ......................................................................................................................... 64 Seite 2 von 69 Einführung Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg 1 Einführung http://www.c-howto.de/tutorial-einfuehrung-compiler-windows.html http://www.tutorials.at/c/c-oder-cplusplus.html 1. Erstes Programm unter Ubuntu Texteditor öffnen unter Anwendungen → Zubehör → Texteditor. Dort folgendes Programm eingeben Programmbestandteile: Programmteil #include <stdio.h> main() {} Seite 3 von 69 Beschreibung Befehl zur Einbindung von Bibliothen Standard Input/Output Ermöglicht die Verwendung von Ein- Ausgabebefehlen z.B. pintf() und scanf() Funktion, die jedes C-Programm aufweisen muss. Wird auch als Hauptprogramm bezeichnet. int gibt den Rückgabewert der Funktion an. Weißt das Programm im Ablauf keinen Fehler auf ist der Rückgabewert 0. (void) steht dafür, dass keine Werte übergeben werden. In den Klammern stehen die Anweisungen, welche zum Hauptprogramm gehören. Einführung Nicole Rottmann LK IV Programmieren in C printf() return 0 ; Wirtschaftsgymnasium Eisenberg LK IV Ausgabe auf den Bildschirm Beendet das Hauptprogramm Semikolon schließt jede! Anweisung ab. Programm abspeichern unter ErstesProgramm.c Um das Programm compilieren (übersetzen) zu können öffnen wir das Teminal Anwendung → Zubehör → Terminal Dort befinden wir uns in unserem Home Verzeichnis: Der C-Copiler wird mit dem Befehl cc gestartet. Wir geben deshalb folgende Zeile ein cc ErstesProgramm.c -o ErstesProgramm Damit wird das Programm ErstesProgramm.c übersetzt und mit -o als ErstesProgramm gespeichert. Mit ./ErstesProgramm wird das Programm gestartet. Als Ausgabe erscheint Hello World 1. Übung Ergänze die Zeichenkette um einen Zeilenumbruch \n „Hello Word \n“. Compiliere das Programm und führe es aus. Seite 4 von 69 Einführung Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 2 Grundlegende Sprachelemente 2.1 Kommentare Kommentare sind für die Lesbarkeit und Verständlichkeit von Programmen von großer Bedeutung. Einzeilige Kommentare können mit // erstellt werden. Ein größerer Kommentarblock beginnt mit /* und endet mit */ 2.2 Datentypen und Variablen Jedes Programm verarbeitet während seiner Ausführung Daten, die in geeigneter Form im Programm abgelegt sein müssen. Zur eindeutigen Identifizierung wird jedem einzelnen Datum ein Name zugeordnet. Dieser ermöglicht es dem Programmierer auf den Inhalt dieses Datums zuzugreifen, bzw. Veränderungen daran vorzunehmen. Durch die Zuordnung eines Namens zu einem bestimmten Datum wird eine Variable erzeugt. Variablen sind reservierte mit Namen versehene Platzhalter für gewisse Informationen während des Programmablaufs. Variablen müssen vor ihrem ersten Gebrauch deklariert werden. Dazu gibt der Programmierer einer Speicherzelle einen Namen und ordnet diesem Namen, und damit auch der Speicherzelle einen Datentyp zu. Jede Variable besitzt in C einen Datentyp. Durch die reine Deklaration ist der Variablen aber noch kein Startwert zugewiesen. Dies muss vor dem ersten Gebrauch durch den Programmierer gewährleistet werden. 2.2.1 Variablennamen Bei der Vergabe von Variablennamen ist auf die Aussagefähigkeit des Namens zu achten. Z.B. ist zaehler statt z deutlicher. Folgende Regeln sind zu beachten: Zulässig sind die Zeichen A-Z, a-z, 0-9 und der Unterstrich_ Groß- und Kleinschreibung sind zu beachten. Zahl, zahl und ZAHL sind drei unterschiedliche Variablen. Dieses Verhalten wird als case-sensitiv bezeichnet. Die Schlüsselwörter von C wie z.B. switch, float, printf,... sind als Variablennamen nicht erlaubt. Variablennamen dürfen nicht mit einer Ziffer beginnen. Variablennamen sollten mit einem Kleinbuchstaben beginnen, also besser zahl1 als Zahl1. Das ist für die Unterscheidung von später einzuführenden sog. Klassen von großer Bedeutung und erhöht die Lesbarkeit von Programmen. Für die Deklaration von Variablen stehen unterschiedliche Datentypen für die verschiedensten Aufgabenbereiche der Variablen zur Verfügung. Seite 5 von 69 Grundlegende Sprachelemente Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 2.3 Datentypen 2.3.1 Ganzzahlige Variablen In c gibt es drei Datentypen zur Aufnahme und Darstellung von Ganzzahlen (also Zahlen ohne Nachkommastellen): char int short int bzw. long int Der Datentyp char ist der kleinste verfügbare Ganzzahldatentyp. Er kann maximal 8 Bit breite Werte aufnehmen und ist dazu geeignet jeden beliebigen ASCII-Wert einer Variablen darzustellen, Der darstellbare Zahlenbereich liebt also zwischen -128 und +127, d.h., dass es sich hier um eine vorzeichenbehaftete Darstellung handelt. Der Ganzzahldatentyp int erfasst 32 Bit kann somit Zahlen im Bereich von -2147483648 bis 2147483647 darstellen. Long int besitzt die Möglichkeit 64 Bit große Zahlen darzustellen. An welcher Stelle die Deklaration der Variablen vorzunehmen ist lässt sich nicht für alle Fälle eindeutig beantworten. Grundsätzlich muss die Deklaration einer Variablen vor ihrem ersten Gebrauch passieren. Allerdings hat die exakte Position der Deklaration Einfluss auf den Gültigkeitsbereich der Variablen. 2.3.2 Fließkommazahlen Fließkommazahlen dienen zur Aufnahme von Zahlen mit einem möglichen Nachkommanteil, also klassische Dezimalzahlen. In c gibt es zwei Möglichkeiten, Fließkommazahlen zu deklarieren. float double Als Dezimalkomma in der Eingabe dient bei Fließkommazahlen der Punkt. bei der Eingabe von Zahlenwerten in Programme. 2.3.3 Zeichen Um einzelne Zeichen darstellen und verarbeiten zu können, wird der Datentyp char verwendet. Zugewiesen werden die Werte zu solchen char Variablen durch Verwendung der einfachen Anführungszeichen: char zeichen = ' ? ' char buchstabe = ' a ' Dabei gilt immer ' a ' < ' b ' < ' c ' <.........< ' z ' 2.4 Konstanten Für Werte, die im Programm nicht veränderbar sein sollen, werden in einem Programm Konstanten verwendet. Ihnen wird bereits bei der Deklaration ein fester Wert zugewiesen, der sich während des Programmlaufs nicht mehr ändern kann. Eingeleitet werden solche Deklarationen durch das Schlüsselwort const. Konstanten können für jeden Datentyp deklariert werden. const float PI = 3.14159265; Seite 6 von 69 Grundlegende Sprachelemente Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV const int WELTWUNDER = 7; Für die Benennung von Konstanten gelten im Wesentlichen die gleichen Regeln, wie für Variablennamen. Um die Lesbarkeit des Quelltextes zu erhöhen, sollte man die Namen von Konstanten konsequent komplett mir Großbuchstaben darstellen. 2. Übung Schreibe ein Programm, das die Kreisfläche eines Kreises mit dem Radius 4 cm berechnet. Die Ausgabe soll lauten: Die Kreisfläche beträgt : 50.24 cm ² Als Datentyp für die Variable kreisflaeche muss float gewählt werden, da das Ergebnis eine Kommazahl ergibt. Formatierte Ausgabe mit printf(): Die Funktion printf() dient zur Ausgabe. Es können Texte und Platzhalter für Variablenwerte angegeben werden. Platzhalter für Variablenwerte beginnen mit %, die darauf folgenden Zeichen geben an, von welchem Datentyp der auszugebende Variablenwert ist und wie die Ausgabe formatiert werden soll, d.h. Die Anzahl der gewünschten Stellen, ob die Ausgabe rechts- oder linksbündig erfolgen soll, ob führende Nullen angegeben werden sollen etc. %c für Daten vom Typ char. Die Funktion gibt das Zeichen aus, das dem ASCII Code des Variablenwertes entspricht. %d für Daten vom Typ int. %f für Daten vom Typ float und double Die Angabe %m.nf sorgt dafür, dass die auszugebende Zahl insgesamt mit m Stellen ausgegeben wird. Davon sind dann n Nachkommastellen. Für die Anzahl der Vorkommastellen ist der Dezimalpunkt und das Vorzeichen abzuziehen. Bsp. Es sollen beliebige Zahlen aufsummiert werden und die gebildete Summe mit dem Variablenname summe ausgegeben werden. Die Ausgabe soll folgendermaßen aussehen: Die Summe ergibt: 200.66 Dazu ist folgende Programmzeile notwendig (nur für die Ausgabe, dies ist keine Berechnung!) printf(„Die Summe ergibt: %5..2f „ , summe) Dabei sollen die Anführungszeichen oben sein. Es wird also der Text und die Formatierungsvorschrift in hochgestellte Anführungszeichen gesetzt und mit einem Komma getrennt der Variablenname der auszugebenden Variable. Soll nach der ausgegebenen Variablen noch ein Text stehen so muss dieser in einer erneuten printf() Anweisung stehen. Ergänze noch die Konstante PI und verwende sie zur Berechnung der Kreisfläche. 3. Übung Schreibe ein Programm, welches 60 Grad Fahrenheit in Grad Celsius umrechnet. Verwende dazu folgende Formel: Grad Celsius = 0,5556*(Grad Fahrenheit – 32). Die Ausgabe soll folgendermaßen erfolgen: Fahrenheit 60 Seite 7 von 69 Celsius 15 Grundlegende Sprachelemente Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 2.5 Anweisungen und Blöcke In C wird jede Programmzeile mit einem Semikolon abgeschlossen. Folglich stellt jede Programmzeile eine Anweisung dar. Einzelne Anweisungen werden durch die Verwendung von geschweiften Klammern {} zu Blöcken zusammen gefasst. Diese Blöcke spielen eine wesentliche Rolle bei den Abfragen (Alternationen) und Wiederholungen (Iterationen). Darüber hinaus haben solche Blöcke die Wirkung, dass in ihnen deklarierte Variablen nur innerhalb des Blockes definiert sind. Seite 8 von 69 Grundlegende Sprachelemente Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 3 Kontrollstrukturen 3.1 Selektion 3.1.1 Einfache Abfragen (if – else) Mit Hilfe von if und else werden Entscheidungen formuliert. Diese Anweisung besitzt folgende Syntax. if (Ausdruck) { Anweisung1; } else { Anweisung2; } Zunächst wird Ausdruck auf seinen Wahrheitsgehalt überprüft. Ist Ausdruck wahr, so wird Anweisung1 (oder ein entsprechender Anweisungsblock in geschweiften Klammern) ausgeführt. Ist Ausdruck jedoch falsch, so wird Anweisung2 (oder entsprechender Block in geschweiften Klammern) ausgeführt. Der else-Teil ist optional. Soll keine spezielle Anweisung abgearbeitet werden, kann dieser Block komplett entfallen. Beispiel: Programm: Struktogramm: if ( b == 0) { ergebnis = 4; } else { ergebnis = 4 – b; } b=0 ja ergebnis = 4 nein ergebnis = 4-b Achtung !! Der Bedingungsoperator ist gleich (=) wird im Ausdruck der if Abfrage mit zwei Gleichheitszeichen dargestellt (==). 3.1.2 Verschachtelte if- Anweisung Seite 9 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Folgt innerhalb einer Alternation eine weitere Bedingung in einem der Anweisungsblöcke so erhält man eine Verschachtelung. Die Verschachtelung kann sowohl im ja- Zweig als auch im nein-Zweig oder in beiden Zweigen erfolgen. Ebenso auch in allen tiefer liegenden Fällen. Auf diese Art und Weise kann schnell eine komplexe Struktur entstehen. Programm: if (b == 0) { ergebnis = 4; } else if (b == 4) { ergebnis = 8; } else { ergebnis = 4 – b; } b=0 ja nein ergebnis = 4 b=4 ja ergebnis =8 nein ergebnis =4-b 4. Übung Programme\Zahlenvergleich.c Schreibe ein Programm, das nachdem zwei Zahlen eingegeben wurden folgendes ausgibt: „beide gleich“, „erste Zahl größer als zweite Zahl“, erste Zahl kleiner als zweite Zahl“. Beispiel wenn zahl1 =7 und zahl2 = 4 dann Ausgabe: „erste Zahl größer zweite Zahl“. Hinweis: Es gibt folgende Vergleichsoperatoren: < > == != <= >= kleiner größer gleich ungleich kleiner gleich größer gleich Logische Operatoren Mit logischen Operatoren kann man mehrere Bedingungen miteinander verknüpfen. Logisches UND Syntax if (Bedingung1) && (Bedingung2) { } Beim logischen UND müssen beide Bedingungen wahr sein, damit der Gesamtausdruck erfüllt ist. Seite 10 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 5. Übung Programme\logischUnd.c Schreibe ein Programm das nach Eingabe eines Alters für Jugendliche über 6 und unter 16 Jahren folgendes ausgibt: „Du bekommst ein Jugendticket“. Logisches ODER Syntax if (Bedingung1) || (Bedingung2) { } Beim logischen ODER muss eine Bedingung wahr sein, damit der Gesamtausdruck wahr wird. 6. Übung Programme\Logisch Oder.c Man erhält einen ermäßigten Preis, wenn man jünger als 14 und älter als 60 Jahre ist. Ausgabe: „Du erhältst eine Ermäßigung.“ Logisches NICHT Syntax if (! Bedingung) { } 7. Übung Programme\Logisch Nicht.c Wenn jemand nicht älter als 18 Jahre ist. Ausgabe „Zu jung.“ 8. Übung Programme\kartenpreis.c Eingabe : Alter und Kartenpreis unter 5 frei; 5-12 Jahre halber Preis; 13 – 55 Jahre voller Preis; über 55 frei. Zeichne das Struktogramm dazu. Einschub: char- Variable In einer char – Variable kann nur ein Buchstabe gespeichert werden. Beispiel: In einer Disco zahlen Jungs den vollen Preis, Mädchen sind frei. Seite 11 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Achtung!! Zur Abfrage eines Buchstabens ist dieser in Hochkomma zu setzten. 9. Übung Programme\Konzertkarte.c Schreibe ein Programm, das den Preis von Konzertkarten ausgibt. Eingabe: Grundpreis in € Altersgruppe: (J)ugendlicher: -30 %; (E)rwachsener, (P)ensionist: -20% Kategorie 1 (Stehplatz) oder 2 (Sitzplatz) +50% Ausgabe: Grundpreis, Altersrabatt, Kategoriezuschlag, Endpreis. 3.1.2.1 Postfix- und Präfixoperatoren Postfix- Operatoren sind einstellige (unäre) Operatoren, die hinter (post) ihrem Operanden stehen. Präfix- Operatoren sind einstellige Operatoren, die vor (prä) ihrem Operanden stehen. Der Inkrement Ausdruck ++ erhöht den Wert einer Variablen um 1. i++ stellt die Anwendung des Postfix-Operators ++ auf seinen Operanden i dar. Beispiel int i = 3: printf (″%d″, i++); //Es wird 3 ausgegeben und anschließend wird i auf 4 erhöht. printf(″%d″, i); // Es wird 4 ausgegeben. Seite 12 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV ++ i stellt die Anwendung des Präfix-Operators ++ auf seinen Operanden i dar. Beispiel int i =3: printf(″%d″, ++i); printf(″%d″, i); // Es wird 4 ausgegeben, da i zuerst um 1 erhöht wird. // Wiederum wird 4 ausgegeben. Der Dekrement Ausdruck -- vermindert den Wert einer Variablen um 1. i-- : i wird erst nach Ausführung einer Anweisung vermindert. -- i : erst wird i vermindert und dann die Anweisung ausgeführt. 3.1.2.2 Auswertungsreihenfolge Wie in der Mathematik spielt es bei C eine wichtige Rolle, in welcher Reihenfolge ein Ausdruck berechnet wird. Genau wie in der Mathematik gilt auch in C die Regel ″Punkt vor Strich″, weshalb 5+2*3 gleich 11 und nicht 21 ist. Allerdings gibt es in C sehr viele Operatoren. Daher muss für alle Operatoren festgelegt werden, welcher im Zweifelsfall Priorität hat. Seite 13 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 10. Übung Verändere das Programm so, dass nur noch eine if- else Anweisung verwendet wird. Die Funktionalität des Programms soll dabei erhalten bleiben. Seite 14 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 11. Übung Lösung Gebrauch verschiedener Operatoren Gegeben sei: int a = 2, b = 1; Finde ohne C-Compiler heraus, welcher Wert der Variablen a in den einzelnen Anweisungen zugewiesen wird. Beachte dabei genau die Priorität der entsprechenden Operatoren. a) b) c) d) e) f) g) h) i) j) a = b = 2; a = 5 * 3 + 2; a = 5 * (3+2); a = !(--b == 0); (Bedenke an dieser Stelle, dass für einen falschen Ausdruck der Zahlenwert 0 zurückgegeben wird, für einen wahren Ausdruck der Zahlenwert 1.) a = 0 && 0 + 2; a = - 5 – 5; a = - (+ b++); a = 5 == 5 && 0 || 1; (Auch hier wird wieder für jeden wahren Ausdruck der Zahlenwert 1 gesetzt, für jeden falschen Ausdruck der Zahlenwert 0) a = ((((((b + b) *2) + b) && b || b)) == b); (Jeder Zahlenwert ungleich 0 wird als wahr gewertet) a = b + (++b); Seite 15 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 12. Übung c-Programme\Staubsauger.c Schreibe ein Programm, das Verkäuferprovisionen berechnet. Verkäufer erhalten pro Monat ein Grundgehalt von 500€. Er verkauft Staubsauger zu je 750€. Für jeden Staubsauger erhält er 3% Provision; ab 20 Stk. 5%, ab 100 Stk. 7%. Eingabe: Staubsauger Ausgabe: Grundgehalt, Provision, Endgehalt 3.1.3 Die switch - case – Anweisung Eine weitere Möglichkeit mehrere Alternativen zu einem Ausdruck zu bearbeiten, stellt die caseAnweisung dar. Ihre Syntax lautet: switch (Ausdruck) { case (Ausdruck1) : case (Ausdruck2): case (Ausdruck3): ….. default: Anweisung1; break; Anweisung2; break; Anweisung3; break; Anweisung4; } Beispiel: Programm Seite 16 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 13. Übung Welche der folgenden Zuweisungen sind falsch? a) summe = summe + 10; b) x*x = x*5; c) 5a = anfang + ende; d) wert = eingabe + alter_wert * 2 e) g_betrag = betrag * 1.14; f) gesamt-wert = wert1 + wert2 + wert3 ; 14. Übung Programme\rechentrainer.c Rechentrainer Welche Rechenoperationen möchtest du üben? (+ , - , * , /) Beispiel für + Operation Berechne 3 + 5 = ? Bei Eingabe 8 soll „richtig“ ansonsten „falsch“ ausgegeben werden Lösungsweg: Ausgabe: „Welche Rechenoperation…..“ Eingabe rechenop Computer erzeugt zwei zufällige Zahlen: zahl1, zahl2 Auswahl erfolgt über case Anweisung: Für +: Ausgabe: Zahl1 + Zahl2 = Eingabe: ergebnisBenutzer ergebnisComputer berechnen Wenn ergebnisBenutzer = ergebnisComputer übereinstimmen, dann Ausgabe „richtig“, sonst Ausgabe „falsch“: Für -: …. Welche Variablen brauchen wir? rechenop, zahl1, zahl2, ergebnisComputer, ergebnisBenutzer Zufallszahlen erzeugen 3 und 5 sind zufällige Werte zwischen 1 und 10, die vom Computer erzeugt werden. Folgende Befehle sind dazu notwendig srand( time(0)); //wird benötigt, damit jedes mal eine neue Zufallszahl erzeugt wird. zahl1 = rand() % 10 +1; // rand() lautet die Funktion, die eine Zufallszahl zwischen 0 und 32767 erzeugt. Damit eine Zahl zwischen 1 und 10 entsteht wird die gewonnene Zahl durch 10 geteilt und der Rest davon genommen % 10. 512 / 10 = 51 Rest 2; 390/10 = 39 Rest 0 da wir eine Zahl zwischen 1 und 10 bekommen wollen müssen wir noch 1 addieren. Seite 17 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Einschub Modulo- Operation Für den Datentyp int sind, außer den Grundrechenarten Addition (+), Subtraktion (-), Multiplikation (*) und Division (/), der Divisionsrest (%) definiert. Bei der Division von Zahlen des Datentyps int ist zu beachten, dass das Ergebnis der Division immer vom Datentyp int ist. Beispiel: 9 / 4 = 2 , die Kommastellen werden abgeschnitten. 9 % 4 = 1 , 2 * 4 ergibt 8, somit ist 1 der ganzzahlige Rest der Division. Übung zu den Operationen / und %: 7/ 2= -11 / 4 = -11 / -4 = 11 / -4 = 7% 2= -11 % 4 = -11 % -4 = 11 % -4 = 3.2 Iteration 3.2.1 Abweisende Schleife mit while (kopfgesteuerte Schleife) Stell dir vor, wir wollen die Zahlen von 1 bis 5 untereinander auf dem Bildschirm ausgeben. Das kann man einfach folgendermaßen umsetzen. printf( ″ 1 \n 2\n 3\n 4\n 5\n ″); Sollen jedoch deutlich mehr Zahlen ausgegeben werden, bzw. größere Anweisungsblöcke mehrmals ausgeführt werden, verwenden wir die while-Schleife. Die Syntax der while- Schleife lautet: while (Ausdruck) Anweisung Solange Ausdruck Anweisung In einer while-Schleife kann eine Anweisung in Abhängigkeit von der Auswertung des Ausdrucks wiederholt ausgeführt werden. Da der Ausdruck vor der Ausführung der Anweisung bewertet wird, spricht man von einer abweisenden Schleife. Der Ausdruck wird berechnet und die Anweisung nur dann ausgeführt, wenn der Ausdruck wahr ist. Um keine Endlos-Schleife zu erzeugen, muss daher der Wert des Ausdrucks im Schleifenrumpf, d.h. im Anweisungsteil verändert werden. Seite 18 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Beispiel: i=0 Solange (i < 10) Gib den Wert von i aus. Erhöhe i um 1. i++ ist ausführlich geschrieben i = i +1 Die Ausgabe auf dem Bildschirm ist: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15. Übung Schreibe ein Programm, das 10 aufeinander folgende Zahlen untereinander auf dem Bildschirm ausgibt, wobei die erste Zahl bei jedem Programmdurchlauf frei wählbar ist. 16. Übung Schreibe ein Programm, das alle 5er Zahlen zwischen 10 und 50 addiert und sowohl den jeweiligen Zwischenwert als auch die Endsumme auf dem Bildschirm ausgibt. 17. Übung Schreibe ein Programm, das von 1 * 1 beginnend die Quadratzahlen bis zu einer eingebbaren Zahl k, also k * k berechnet und untereinander auf dem Bildschirm ausgibt. 18. Übung Programme\Funktion.c Erstelle mit einer while- Schleife eine formatierte Wertetabelle der Funktionsgleichung y=0,1x³+2x²+5 . Dabei sollen die Anfangs- und Endwerte von x sowie eine beliebige Schrittweite vom Benutzer eingegeben werden. z.B. -10 <= x >= 10, Schrittweite 1 Die Ausgabe soll folgendermaßen erscheinen: Seite 19 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 19. Übung Programme\Kapital.c Schreibe ein Programm zur Kapitalberechnung. Einzugebende Werte sind das Anfangskapital, die Laufzeit und der Zinssatz. Die Ausgabe soll folgendermaßen aussehen: 20. Übung Versuche folgendes Programm auf dem Papier nachzuvollziehen und schreibe auf die vorgegebenen Linien die jeweilichen Ausgaben. Seite 20 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 3.2.2 For-Schleife Die for-Schleife verwenden wir, wenn die Anzahl der Schleifendurchläufe bekannt ist. Wir benötigen also immer eine Variable, die zum Zählen der Schleifendurchläufe verwendet wird. Üblicherweise bezeichnet man diese Zählvariablen mit i, j, k, usw. Beispiel Wir wollen wieder die Zahlen von 1 bis 5 untereinander auf dem Bildschirm ausgegeben. Die Schleife leiten wir mit dem Schlüsselwort for ein. Dann folgen in der Klammer drei Bereiche, welche durch einen Strichpunkt; voneinander getrennt werden: Bereich 1: Startwert der Zählvariablen setzten z.B. i = 1 Bereich 2: Durchlaufbedingung z.B. i <= 5 Bereich 3: Operation auf Zählvariable durchführen z.B. i++ Beispiel: Der Wert der Laufvariable wird bei jedem Schleifendurchlauf um 1 verringert: Es werden die Zahlen von 5 bis 0 ausgegeben. Seite 21 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 21. Übung Programme\Teiler.c Lasse alle Teiler einer einzulesenden ganzen Zahl z ausgeben. Prüfe dazu bei jeder Zahl i zwischen 1 und z, ob der Divisionsrest von z/i gleich 0 ist. Die Ausgabe soll folgendermaßen aussehen: 22. Übung Schreibe alle Übungen zur while-Schleife mit der for-Schleife. 23. Übung Schreibe ein Programm, das folgende Ausgabe erzeugt: 12, 10, 8, 6, 4, 2, 0, 24. Übung Schreibe ein Programm, das alle durch fünf teilbaren Zahlen zwischen 104 und 31 untereinander auf dem Bildschirm ausgibt. Verschachtelte Schleifen 25. Übung Schreibe ein Programm, das 10 Zeilen mit Sternchen ausgibt, wobei mit 1 Sternchen in der 1.Zeile begonnen wird und in jeder Zeile ein Sternchen hinzukommt. In der letzten Zeile müssen dann 10 Sternchen zu sehen sein. 26. Übung Was gibt folgendes Programm aus? Überlege dir die Ausgabe zuerst und überprüfe anschließend dein Ergebnis durch einen Programmlauf. Seite 22 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 3.2.3 Do-while Schleife Die do-while Schleife ist der while Schleife sehr ähnlich. Der einzige Unterschied besteht darin, dass die Anweisung einmal ausgeführt wird und erst dann die Bedingung zum ersten Mal geprüft wird. Die Schleife wird also mindestens einmal ausgeführt. Da die Bedingung am Ende geprüft wird, bezeichnet man die do-while Schleife als Fußgesteuerte Schleife. Die Syntax der do-while Schleife lautet: do { Anweisung } while (Ausdruck); Achtung! Hier wird ein Semikolon nach der Schleifenbedingung verlangt, weil hier das Ende der Anweisung erreicht ist. Anweisung Solange Ausdruck Beispiel: Bis eine 0 eingegeben wird sollen ganze Zahlen eingelesen und aufaddiert werden. Da eine ganze Zahl erst eingelesen werden muss, bevor sie geprüft werden kann, bietet sich hier die Fußgesteuerte Schleife an. Seite 23 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV summe = summe + zahl Solange zahl nicht 0 27. Übung Schreibe ein Programm, das eine Zahlenreihe von 0 bis 9 ausgibt. 28. Übung Teste folgendes Programm. Ersetze die while Schleife durch eine do while-Schleife. 29. Übung (für Fortgeschrittene!) Programme\Reihenentwicklung.c Die Exponentialfunktion ez soll mit Hilfe einer Reihenentwicklung berechnet werden. Schreibe ein Programm in C, welches die ersten n Glieder der Reihenentwicklung aufsummiert. z und n sollen ganzzahlig sein und eingelesen werden. Die Terme der Reihe sind in einer Schleife aufzusummieren. Dabei soll die Fakultät in der Schleife selbst berechnet werden. ez = 1 + Seite 24 von 69 z/1! + z2/2! + …… + z n-1/(n-1)! Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Fragen zu Schleifen: 1. Welche Schleifen stehen zur Verfügung? 2. Welche Schleife solltest du verwenden, wenn mehrere Anweisungen mindestens einmal ausgeführt werden sollen? 3. Was gibt diese Schleife aus, und welcher Fehler wurde hier gemacht? 4. Warum läuft folgende Schleife in einer Endlosschleife? Was kann man dagegen tun? Lösungen Seite 25 von 69 Kontrollstrukturen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 4 Unterprogramme In der Praxis werden komplette Programmausführungen nicht in einem einzigen Anweisungsblock zusammen gefasst, sondern eine Problemlösung in viele kleine Teilprobleme zerlegt und mithilfe von Unterprogrammen, auch Funktionen genannt, gelöst. Anweisungen eines Programms werden grundsätzlich in Funktionen zusammengefasst. Die Funktion, die immer als erstes angesprochen wird, ist die main- Funktion. Die main- Funktion bildet die Hauptfunktion, von dort aus werden alle Unterprogramme aufgerufen und gestartet. Ein Programm ohne main- Funktion kann nicht funktionieren. 4.1 Hinführung Schreibe ein Programm, das nach Eingabe von zweimal zwei ganzen Zahlen die jeweils größte der beiden Zahlen auf den Bildschirm ausgibt. Wie du leicht feststellen kannst taucht in deinem Programm zweimal ein beinahe gleicher Programmteil, die if..else.. Anweisung, auf. Es besteht durch eine geringe Änderung des Programms die Möglichkeit die gleiche if…elseAnweisung zweimal aufzurufen und damit das Programm übersichtlicher zu gestalten durch die mehrfache Verwendung von Programmteilen(Unterprogrammen) die Schreibarbeit und damit die Fehlerquote zu verringern. auch in mehreren Programmen verwendbar sind und in einer Bibliothek(library) zur Verfügung gestellt werden können Unterprogramme sind ein Mittel zur Strukturierung eines Programms. Ziel darf es nicht sein, ein einziges riesengroßes Programm zu schreiben, da dies schwer zu überblicken wäre. Gefordert ist hingegen eine Modularität. Das Hauptprogramm soll so weit wie möglich nur Unterprogramme aufrufen, damit es leicht verständlich bleibt. Mit dem Hauptprogramm beginnt ein Programm seine Ausführung. Ein Hauptprogramm kann Unterprogramme aufrufen. Ein Unterprogramm kann ebenfalls Unterprogramme aufrufen. Ein Unterprogramm ist eine Programmeinheit, die einen Namen trägt und über ihren Namen aufgerufen werden kann. Ein Unterprogramm ist eine Folge von Anweisungen, die in einem Programm über den Unterprogramm-Namen aufgerufen werden kann. Unterprogramme werden auch als Funktionen bezeichnet. 4.2 Funktionen Funktionen haben folgende Vorteile: Verschiedene Programmteile sind relativ unabhängig voneinander und können deshalb einzeln geschrieben und getestet werden. Ein Programmteil braucht nur einmal geschrieben, kann jedoch mehrfach ausgeführt werden. Seite 26 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Umfangreiche Programme lassen sich aus bereits vorhandenen Programmen als Bausteine zusammensetzten (Library). 4.3 Vordefinierte Funktionen (Standard-Funktionen) Zum Lieferumfang des Compilers gehören fertige Funktionen für alle möglichen Anwendungsfälle wie z.B printf(…) und scanf(…) Diese werden durch unterschiedliche Bibliotheken wie z.B. stdio.h eingebunden. Es entfällt die Vereinbarung der Funktionen, da sie vordefiniert sind, jedoch benötigen die meisten Funktionen Parameter, welche in Klammern anzugeben sind. Beim Aufruf der printf Funktion muss beispielsweise angegeben werden, was auf dem Bildschirm ausgegeben werden soll. 4.4 Benutzerdefinierte Funktionen Im Gegensatz zu den Standard Funktionen können die Namen und die jeweiligen Anweisungsfolgen vom Benutzer frei gewählt werden. 4.4.1 Ort der Definition von Funktionen Eine Funktion stellt eine Anweisungsfolge dar, die unter einem Namen aufgerufen werden kann. Eine Funktion, die unter ihrem Namen aufgerufen werden soll, muss definiert sein. Die Definition einer Funktion besteht in C aus dem Funktionskopf und dem Funktionsrumpf. Der Funktionskopf legt die Aufruf-Schnittstelle der Funktion fest. Der Funktionsrumpf enthält lokale Vereinbarungen und die Anweisungen der Funktion. Die Aufgabe einer Funktion ist es, aus Eingabedaten Ausgabedaten zu erzeugen. Syntax der Definition einer Funktion rueckgabetyp funktionsname (typ_1 formaler_parameter_1, typ_2 formaler_parameter_2,…..) { ….. } Funktions rumpf In C gibt es parameterlose Funktionen und Funktionen mit Parametern 4.4.1.1 Parameterlose Funktionen Beispiel void printGestrichelteLinie (void) { Seite 27 von 69 Unterprogramme Nicole Rottmann Funktions kopf Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV printf (″-------------------------------------------------------------″); } Parameterlose Funktionen werden definiert mit einem Paar von runden Klammern hinter dem Funktionsnamen, die anstelle von Übergabeparametern und ihren Datentypen den Datentyp void enthalten. Der Aufruf der Funktion erfolgt durch Anschreiben des Funktionsnamens, gefolgt von einem Paar runder Klammern, printGestrichelteLinie(); 4.4.1.2 Funktionen mit Parametern (call by value) Hat eine Funktion formale Parameter- das sind die Parameter in den runden Klammern der Definition der Funktion- so muss der Aufruf mit aktuellen Parametern erfolgen. Als Beispiel für eine Funktion mit Parametern wird die Funktion ausgebenPLZ() betrachtet, die die ihr übergebene Postleitzahl auf dem Bildschirm ausgeben soll: void ausgebenPLZ( int plz) { printf (″Die Postleitzahl ist: ″); printf(″%d\n″, plz); } Der Aufruf für dieses Beispiel erfolgt mit: ausgebenPLZ (aktPLZ); Hier ist aktPLZ der aktuelle Parameter. Er ist die Postleitzahl, die auf dem Bildschirm ausgegeben werden soll. Beispiel Maximum Programm ohne Funktionen: Seite 28 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg Programm mit der Funktion max() Die Parameter x und y werden innerhalb der Funktion als Platzhalter verarbeitet, sie sind formale Parameter. Beim Funktionsaufruf werden an Stelle der formalen Parameter x und y die aktuellen Parameter a und b bzw. c und d eingesetzt. Seite 29 von 69 Unterprogramme Nicole Rottmann LK IV Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Die Funktion arbeitet nur mit den lokalen Parametern x und y. Beim Aufruf der Funktion max(a,b) wird eine lokale Kopie von a und b in den formalen Parametern x und y angelegt. Der aktuelle Parameter kann von der aufgerufenen Funktion aus nicht verändert werden. Soll der aktuelle Parameter verändert werden, so muss stattdessen mit Pointern als Übergabeparameter gearbeitet werden. (späteres Kapitel) Implizite Datentypumwandlung Wird nicht der Datentyp als Argument an die Funktion übergeben, welcher als formaler Parameter definiert wurde, findet eine implizite Typumwandlung statt. Beispiel: In diesem Beispiel wird zwar ein Gleitpunkttyp an die Funktion circle als Argument übergeben, aber der formale Parameter von circle() ist ein Integer- Wert. Daher wird eine Umwandlung von float nach int durchgeführt und die Nachkommastellen abgeschnitten. 4.4.1.3 Funktionen mit Rückgabetyp - die return Anweisung In der Funktion max() ist eine Bildschirmausgabe enthalten. Programmteile sollen aber möglichst universell sein. Daher ist es günstiger eine Funktion max() zu schreiben, die den Maximalwert an das aufrufende Programm zurückgibt, anstatt ihn sofort auf den Bildschirm auszugeben. Das aufrufende Programm kann dann mit dem in der Funktion ermittelten Wert beliebig weiter verfahren. Z.B in arithmetischen Ausdrücken weiter verrechnen oder mit einem speziellen Format selbst auf dem Bildschirm auszugeben. Sehr oft hat eine Funktion den Zweck, genau einen einzigen, ganz bestimmten Wert zu berechnen. In diesem Fall gibt man der Funktion einen Rückgabetyp der es erlaubt, den berechneten Wert im Funktionsnamen an das aufrufende Programm zurückzugeben. Die return- Anweisung beendet den Funktionsaufruf. Das Programm kehrt zu der Anweisung, in der die Funktion aufgerufen wurde, zurück und beendet diese Anweisung. Anschließend wird die nächste Anweisung nach dem Funktionsaufruf abgearbeitet. Eine Funktion muss keinen Resultatwert liefert. Sich hat dann den Rückgabetyp void. Soll ein Resultatwert geliefert werden, so erfolgt dies mit Hilfe der return- Anweisung. Hat eine Funktion einen von void verschiedenen Rückgabetyp, so muss sie mit return einen Wert zurückgeben. Seite 30 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Nach return kann ein beliebiger Ausdruck stehen. Mit Hilfe der return – Anweisung ist es möglich, den Wert eines Ausdrucks, der in der Funktion berechnet wird, an den Aufrufer der Funktion zurückzugeben (Funktionsergebnis): Beispiel Maximum: Fragen zu Funktionen 1. Beschreibe den Begriff call-by-value. 2. Was muss bei der Verwendung eines Rückgabewertes beachtet werden und mit welcher Anweisung kann ein Wert aus einer Funktion zurückgegeben werden? 3. Warum lässt sich das folgende Programm nicht übersetzen? Seite 31 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 4. Was wurde bei folgendem Beispiel falsch gemacht? Lösungen 4.5 Rekursive Funktionen Eine Rekursion ist eine Funktion, die sich selbst aufruft und sich selbst immer wieder neu definiert. Damit sich eine Rekursion nicht unendlich oft selbst aufruft, sondern irgendwann auch zu einem Ergebnis kommt, benötigen sie unbedingt eine sogenannte Abbruchbedingung. Wenn eine Funktion aufgerufen wird, erweitert der Compiler den dafür vorgesehen Speicher (Stack) um einen Datenblock. In diesem Datenblock werden die Parameter, die lokalen Variablen und die Rücksprungadresse zur aufrufenden Funktion angelegt. Der Datenblock (Stackframe) bleibt so lange bestehen, bis diese Funktion wieder endet. Wird in ihm eine weitere Funktion aufgerufen, wird ein weiterer Datenblock auf den aktuellen gepackt. 30. Übung Eine Funktion soll zwei Zahlen dividieren. Der ganzzahlige Rest der Division soll angegeben werden. Zum Beispiel: 10/2 = 5 oder 10/3 = 3 Rest 1. Das Programm darf aber nicht die Operatoren / und % verwenden. So sieht die Lösung in Form einer rekursiven Funktion aus: Programme\dividiere recursiv.c Seite 32 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Im Beispiel wird der Funktion die Werte x = 9 und y = 2 übergeben. Innerhalb der Funktion wird zunächst die Abbruchbedingung geprüft: if (x <= y) Da die Bedingung für x = 9 und y = 2 wahr ist, wird die nächste Anweisung ausgeführt: return 1 + divide (x – y, y); Die Funktion gibt mittels return die Summe 1 + divide (x – y, y) zurück. Damit wird, bevor das Ergebnis endgültig zurückgegeben wird die Funktion divide erneut aufgerufen. Die Funktion ruft sich also selbst auf. Hiermit beginnt die Rekursion. Was passiert jetzt mit dem Rückgabewert 1 ? Auf den Stack wurde zuerst die main() Funktion gelegt, da diese zuerst die Funktion divide() aufgerufen hat. Hier ist gespeichert, wie das Programm wieder zur main()- Funktion zurückkommt. Bei jedem Funktionsaufruf in einem Programm wird der aktuelle Zustand der main() Funktion eingefroren und auf dem Stack abgelegt. Damit das Programm weiß, wo die Adresse der main()Funktion ist, wird auf dem Stack eine Rücksprungadresse mit abgelegt. int main() divide(9,2) return 1+ divide(8-2,2) Stack Erster rekursiver Aufruf Die Funktion ruft sich selbst mit der Anweisung return 1 + divide (x-y,y); auf. Es wird erneut überprüft : if (x >= y) da x = 6 und y = 2 ist die Abfrage wieder wahr und es erfolgt ein erneuter Funktionsaufruf return 1 + divide (x-y , y); Das Ganze wiederholt sich noch zweimal, bis es auf dem Stack folgendermaßen aussieht: Seite 33 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg int main() divide(9,2) return 1+ divide(8-2,2) return 1+ divide(6-2,2) return 1+ divide(4-2,2) return 1+ divide(2-2,2) LK IV Stack Jetzt wird die Abbruchbedingung aktiv, denn jetzt ist x = 0 und y = 2 . Die nächste Abfrage if (x) dient dazu den Rest auszugeben, falls x ungleich 0 sein sollte. Da die Funktion beendet wurde wird nun der Wert 0 (return 0) zurückgegeben. Das Programm geht zur nächsten Rücksprungadresse. Der Rückgabewert 0 wurde von dem Funktionsaufruf divide(2-2,2) erzeugt. Dorthin führt auch die Rücksprungadresse, also return 0+1. int main() divide(9,2) return 1+ divide(8-2,2) return 1+ divide(6-2,2) return 1+ divide(4-2,2) return 1+ divide(2-2,2) Stack return 0 Seite 34 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Die nächste Rücksprungadresse wurde von divide(4-2,2) erzeugt , also erfolgt return 0+1+1; anschließend erfolgt return 0+1+1+1 und zuletzt return 0+1+1+1+1. Die main Funktion bekommt dann den Rückgabewert 0+1+1+1+1, also 4. Programme\n-fakultaet.c Beispiel Fakultät: 31. Übung Wie sieht der Stack für die Berechnung der Fakultät 5! aus? Seite 35 von 69 Unterprogramme Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 5 Blöcke und Funktionen 5.1 Stuktur eines Blockes Anweisungen eines Blockes werden durch Blockbegrenzer (geschweifte Klammern {}) dargestellt. Einen Block benötigt man aus folgenden Gründen: der Rumpf einer Funktion ist ein Block ein Block gilt syntaktisch als nur eine Anweisung, z.b im if- oder else Zweig. Anweisungsfolgen werden logisch gegliedert. Variablen können an verschiedenen Stellen deklariert und definiert werden: außerhalb von Funktionen (globale Variablen) innerhalb von Funktionen und Blöcken vor der ersten Anweisung (lokale Variablen) 5.1.1 Schachtelung von Blöcken Da eine Anweisung eines Blocks selbst wieder ein Block sein kann, können Blöcke geschachtelt werden. Vereinbarungen, die innerhalb eines Blockes festgelegt werden, gelten nur lokal für diesen Block. Lokal definierte Variablen und deklarierte Namen sind nur innerhalb diese Blockes sichtbar, in einem umfassenden Block sind sie unsichtbar. In C können in jedem Block Vereinbarungen durchgeführt werden. Beispiel Seite 36 von 69 Blöcke und Funktionen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 5.2 Gültigkeit, Sichtbarkeit und Lebensdauer In der üblichen Programmierung werden viele Variable in Funktionen oder in Anweisungsblöcken deklariert und definiert. (Deklarieren: Der Variable wird ein Name gegeben und ein Typ zugewiesen. Definieren: Während dem Compilieren wird der Variablen ein Speicherplatz zugewiesen.) Grundlegend unterscheidet man zwischen lokalen und globalen Variablen. Die lokalste Variable ist immer die Variable im Anweisungsblock. Globale Variablen können in allen Funktionen verwendet werden. Existiert eine lokale Variable mit gleichem Namen wie die globale Variable bekommt immer die lokale Variable den Zuschlag. Vorsicht ! Dies kann zu unerwarteten Ergebnissen führen. Eine lokale Variable muss immer initialisiert, d.h. mit einem Wert versehen werden. Globale Variablen werden vom Compiler automatisch initialisiert. Besser, da übersichtlicher ist es aber die Variable selbst zu initialisieren. Wenn möglich globale Variablen vermeiden. Beispiel: Seite 37 von 69 Blöcke und Funktionen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Ausgabe: Die Lebensdauer ist die Zeitspanne, in der das Laufzeitsystem des Compilers der Variablen einen Platz im Speicher zur Verfügung stellt. Mit anderen Worten, während ihrer Lebensdauer besitzt eine Variable einen Speicherplatz. Die Gültigkeit einer Variablen bedeutet, dass an einer Programmstelle der Namen einer Variablen dem Compiler durch eine Vereinbarung bekannt ist. Die Sichtbarkeit einer Variablen bedeutet, dass man von einer Programmstelle aus die Variable sieht, das heißt, dass man auf sie über ihren Namen zugreifen kann. Eine Variable kann gültig sein und von einer Variablen desselben Namens verdeckt werden und deshalb nicht sichtbar sein. Auf eine verdeckte Variable kann nur über ihre Adresse zugegriffen werden, nicht über ihren Namen. Generell gilt für die Sichtbarkeit von Variablen: Variablen in inneren Blöcken sind nach außen nicht sichtbar. Globale Variablen und Variablen in äußeren Blöcken sind in inneren Blöcken sichtbar. Wird in einem Block eine lokale Variable definiert mit demselben Namen wie eine globale Variable oder wie eine Variable in einem umfassenden Block, so ist innerhalb des Blocks nur die Seite 38 von 69 Blöcke und Funktionen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV lokale Variable sichtbar. Die globale Variable bzw. die Variable in dem umfassenden Block wird durch die Namensgleichheit verdeckt. Schlüsselwörter für Variablen static: Variable behält ihren Wert bei, auch außerhalb ihres Bezugsrahmens. Beispiel: Programme\static_1.c Der Wert i wird innerhalb der Funktion inkrement() zwar um eins erhöht, bei jedem erneuten Aufrufen der Funktion jedoch wieder auf 1 gesetzt. Dem kann mit dem Schlüsselwort static entgegengewirkt werden. Programme\static_2.c Seite 39 von 69 Blöcke und Funktionen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV volatile: Modifiziert eine Variable so, dass der Wert dieser Variable vor jedem Zugriff neu aus dem Hauptspeicher eingelesen werden muss. Andernfalls besteht oft die Gefahr in c, dass der Compiler Programmteile wegoptimiert. Mit volatile versehene Variablen müssen genauso verarbeitet werden, wie der Programmier dies vorgibt. Fragen: 1. Das folgende Programm soll 10 Mal die Textfolge Hallo 0, Hallo 1, Hallo 2, ….Hallo 9 ausgeben. Die Ausgabe ist allerdings folgende: Wo liegt der Fehler? Was muss verändert werden? 2. Folgendes Programm lässt sich nicht übersetzen. Wo liegt der Fehler? Seite 40 von 69 Blöcke und Funktionen Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg Lebensdauer Seite 41 von 69 Blöcke und Funktionen Nicole Rottmann LK IV Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 6 Felder (Arrays) und Zeichenketten Bisher haben wir uns auf einfache Datentypen beschränkt. (char, short, int, float, double) In diesem Kapitel werden zusammengesetzte Datenstrukturen besprochen, sogenannte Arrays. 6.1 Arrays verwenden Mit Arrays werden einzelne Elemente als Folge von Werten eines bestimmten Typs abgespeichert und bearbeitet. 6.2 Das eindimensionale Feld Ein eindimensionales Feld bezeichnet man auch als Liste. Die Syntax zur Definition eines Arrays sieht wie folgt aus: Datentyp Arrayname [Anzahl_der_Elemente]; Beispiel: Ein Feld zur Speicherung von Temperaturwerten vom Datentyp float mit 100 Einträgen wird folgendermaßen definiert: float temperatur[100]; //Feld aus 100 float-Zahlen. Für den Feldname gelten die gleichen Regeln wie für die Vergabe der Variablenbezeichnungen. In den eckigen Klammern steht die Anzahl der Elemente als ganzzahlige Konstante, die größer als 0 sein muss. Ein Array, das aus Elementen verschiedener Datentypen besteht gibt es in C nicht. Zugreifen kann man auf das gesamte Array mit allen Komponenten über den Array Namen. Einzelne Elemente eines Arrays werden über den Namen und einen Indexwert in eckigen Klammern [n] angesprochen. Der Indexwert selbst wird über eine Ganzzahl angegeben und fängt bei 0 zu zählen an. 6.3 Arrays initialisieren und darauf zugreifen Um einzelnen Array-Elementen einen Wert zu übergeben oder Werte daraus zu lesen, wird der Indizierungsoperator [] verwendet. Beispiel: Seite 42 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Ausgabe: Vorsicht ! Das Array wird mit der Ganzzahl [3] initialisiert. Der erste Arraywert wird aber mit der Indexnummer 0 angesprochen. Der letzte Arraywert hat den Indexwert [n-1]. (n ist die Arraygröße). Fügen wir im Programm noch folgende Zeile hinzu: iArray[3] = 6666; würden wir auf einen nicht geschützen und reservierten Speicherbereich zugreifen. Bestenfalls stürzt das Programm mit einer Schutzverletzung (Segmentation fault) oder Zugriffsverletzung (Access violation) ab. Schlimmer ist es, wenn das Programm weiterläuft und irgendwann eine andere Variable den nicht geschützten Speicherbereich verwendet. Wir erhalten dann unerwünschte Ergebnisse. Häufig werden Werte von Arrays in Schleifen übergeben: Hierbei kann es leicht zu einem Überlauf kommen. Beispiel: Seite 43 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Vorsicht!! In der for Schleife muss es i < MAX heißen, da es bei i<=MAX zu einer Indizierung des Elements [10] kommt. Somit ein Feld zu viel initialisiert wird. Frage: In folgendem Programm wurden zwei Fehler gemacht: Anwort Seite 44 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 6.4 Initialisierung mit einer Initialisierungsliste Arrays können bereits bei der Definition mit einer Initialisierungsliste explizit initialisiert werden. Hierbei wird eine Liste von Werten, getrennt durch Kommata, in geschweiften Klammern bei der Definition an das Array zugewiesen. Beispiel: float fArray[3] = {0.75, 1.0, 0.5} In der Definition eines Arrays mit Initialisierungsliste kann die Längenangabe fehlen. Folgende Definition ist gleichwertig zur obigen: float fArray[] = {0.75, 1.0, 0.5} Wird hingegen bei der Längenangabe ein größerer Wert gewählt, werden die restlichen Elemente in der Liste automatisch mit dem Wert 0 initialisiert. Beispiel float fArray[5] = {12.3, 15.8} Nur die ersten beiden Werte enthalten Zahlen, die verbleibenden drei Werte haben den Wert 0. Bestimmte Elemente direkt initialisieren int iArray[5] = { 123, 456, [4] = 789} Die ersten beiden Elemente wurden direkt initialisiert und der letzte Element Wert wurde über den Index angesprochen. Arrays mit Schreibschutz Wenn ein Array benötigt wird, bei dem die Werte schreibgeschützt sind und nicht veränderbar sein sollen, so muss das Schlüsselwort const vor der Arraydefinition stehen. Beispiel const float fArray[3] = {0.75, 1.0, 0.5} fArray[0] = 0.8 erzeugt eine Fehlermeldung. 1. Frage Wo ist der Unterschied bei folgenden Initialisierungslisten? const int MAX = 5; int val[MAX] = {1, 2}; int val[MAX] = { 1, 2, 0, 0 }; int val[] = { 1, 2, 0, 0, 0}; int val[MAX] = {1, 2, 0, 0, 0, 1, 2}; 2. Frage Mit welchen Werten sind folgende Array-Definitionen anschließend initialisiert? const int MAX = 10; int a[MAX] = { 1, [MAX/2] = 123, 678, [MAX-1] = -1}; long val[]= {[19] = 123}; float fval[100] ={0.0}; Lösung Seite 45 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 32. Übung Schreibe ein Programm, das die Inhalte zweier Arrays miteinander vergleicht. Deklariere dazu zwei Arrays mit 10 Feldern. Initialisiere beide Felder mit einer for- Schleife, so dass sie identisch sind. Verändere von einem Feld die Pos 5 und vergleiche nun beide Felder mit einer weiteren forSchleife. Die veränderte Position soll angegeben werden. Programme\arrayvergleich.c 33. Übung Schreibe ein Programm, das die Anzahl der Elemente eines Arrays ermittelt. Deklariere und initialisiere dazu ein Array mit 17 beliebigen Zahlen. Die Größe eines Datentyps in Byte lässt sich mit dem sizeof- Operator ermitteln. Beispiel sizeof(int) liefert als Ergebnis entweder 16 oder 32, je nachdem um welches System es sich handelt. Da der sizeof Operator also für jedes Feldelement die Bit-Anzahl ausgibt, muss das Ergebnis noch durch sizeof(int) geteilt werden. Sofern das Feld vom Typ Integer ist. Für jeden anderen Typ entsprechend. Ergänze das Programm so, dass die Anzahl der Zahlenwerte und die Zahlenwerte vom Benutzer eingegeben werden können. Programme\anzahlelementearray.c 6.5 Strings (Zeichenketten) Was passiert, wenn ein Array vom Typ char verwendet wird? Mit einer Folge von char-Zeichen kann ein kompletter Text gespeichert, verarbeitet und ausgegeben werden. Für Arrays vom Typ char gelten nicht nur die Einschränkungen herkömmlicher Arrays, sondern es muss darauf geachtet werden, dass die zusammenhängende Folge von Zeichen mit dem NullZeichen ´\0´ abgeschlossen wird. Genaugenommen heißt das, dass die Länge eines char- Arrays immer um ein Zeichen größer sein muss als die Anzahl der relevanten Zeichen. Für Arbeiten mit Stings bietet die Standard Bibliothek außerdem viele Funktionen in der Headerdatei string.h an. 6.5.1 Strings initialisieren Zur Initialisierung von char- Arrays können Sting-Literale in Anführungszeichen gesetzt werden, anstatt ein Array Zeichen für Zeichen zu initialisieren. Somit sind die beiden folgenden char-Array Definitionen gleichwertig: char string1[20] = “String“; char string2[20] = {‘S’ , ‘t’ , ‘r’ , ‘i’ , ‘g’ , ‘\0’}; Beide Initialisierungen sind äquivalent. Es wird jeweils ein char-Array definiert, das 19 Zeichen enthalten kann. Die restlichen Zeichen werden auch hier, mit 0 vorbelegt. Ausgegeben wird der String mit printf(“%s \n“, sting1); Seite 46 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Achtung! Das Stringende – Zeichen ’0’ Ein char-Array, das einen String speichert, muss immer um mindestens ein Element länger sein als die Anzahl der relevanten (lesbaren) Zeichen. Nur dann kann es noch das Stringende- Zeichen (oder auch NULL- Zeichen) ’\0’ aufnehmen. Soll also ein Text mit exakt 10 Zeichen abgespeichert werden, braucht man dafür ein char-Array mit 11 Zeichen. Fehlt das Stringende Zeichen und wird eine Funktion so lange ausgeführt bis das Stringende Zeichen erreicht ist, kann das Programm hinter dem eigentlichen Ende der Zeichenkette fortgeführt werden, bis sich irgendwo im Speicher ein Null- Wert befindet. Bei einem schreibenden Zugriff können hierbei natürlich Daten zerstört werden. 6.5.2 Einlesen von Strings Es ist möglich ein char-Array formatiert mit scanf einzulesen. Die scanf- Funktion liest allerdings nur bis zum ersten Leerzeichen ein. Alle restlichen Zeichen dahinter werden ignoriert. Aus diesem Grund wird die Standardfunktion fgets() verwendet. Die Syntax von fgets(): char *fgets(char *str, int n_chars, FILE *stream); Der String wird mit dem ersten Parameter str angegeben. Im zweiten Parameter wird angegeben, wie viele Zeichen eingelesen werden. Von wo etwas eingelesen wird, legt der dritte Parameter stream fest. In unserem Fall soll es die Standarteingabe sein, die mit dem Stream stdin angegeben wird. Beispiel: Programme\String_einlesen.c Mit vorliegendem Programm können maximal 19 Zeichen eingelesen werden. Das 20 Zeichen wird automatisch mit ’\0’ belegt. Ist noch ein weiteres Zeichen frei, wird ein Zeilenumbruch \n eingefügt. 6.6 Die Standard- Bibliothek <string.h> Funktionen, mit denen Strings kopiert, zusammengefügt oder verglichen werden können sind in der Standard- Headerdatei <string.h> definiert. Seite 47 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Alle String- Verarbeitungsfunktionen verwenden char-Zeiger, die auf den Anfang des Strings, also auf das erste Zeichen verweisen. Deshalb sind alle Syntax Beschreibungen mit dem Zeigeroperator* versehen. Im Programm selber erscheint vorerst aber die schon bekannte Array Schreibweise. 6.6.1 strcat() – Strings aneinanderhängen Syntax: char *strcat(char *s1, char *s2); Damit wird s2 an s1 angehängt, wobei das Stingendezeichen /0 am Ende von String 1 überschrieben wird. Voraussetzung ist, dass s2 Platz in s1 hat. Beispiel: 34. Übung Ergänze das Programm, so dass zusätzlich „Strasse: Meine_Strasse“ ausgegeben wird. Programme\stringcat.c 6.6.2 strchr() – ein Zeichen im String suchen Syntax: char *strchr (const char *s, int ch); Diese Funktion gibt die Position im String s beim ersten Auftreten von ch zurück. Tritt das Zeichen ch nicht auf, wird NULL zurückgegeben. Beispiel: Programme\stringchr.c Seite 48 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Dabei wird ab dem Buchstabe W der komplette String ausgegeben. 6.6.3 strcmp() – Strings vergleichen Für das Vergleichen zweier Strings kann die Funktion strcmp() mir folgender Syntax verwendet werden. int strcmp(const char *s1, const char *s2); Sind beide Strings identisch, gibt diese Funktion 0 zurück. Ist der String s1 kleiner als s2 ist der Rückgabewert kleiner als 0 ist s1 größer als s2, dann ist der Rückgabewert größer als 0. Beispiel : Programme\string_vergleichen.c Seite 49 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 6.6.4 strcpy() – einen String kopieren Soll ein String in einen adressierten char- Vektor kopiert werden, kann die Funktion strcpy() benutzt werden. Syntax: char *strcpy (char *s1, const char *s2); Beispiel Programme\string_kopieren.c Seite 50 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV In diesem Beispiel kann man sehen, dass es auch möglich ist, mit strcpy() Strings aneinander zu hängen. 6.6.5 strlen() - Länge eines Strings ermitteln Um die Länge eines Strings zu ermitteln, kann die Funktion strlen() eingesetzt werden. Syntax: size_t strlen(const char *s1); Damit wird die Länge des adressierten Strings s1 ohne das abschließende Stringende- Zeichen zurückgegeben. Beispiel: Programme\stringlaenge.c Seite 51 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 35. Übung Schreibe ein Programm, das die Größe in Bytes und die Anzahl der Elemente eines Arrays bzw. Strings ermittelt und ausgibt. Verwende dazu den sizeof – Operator. Bei den Strings kann die strlen() verwendet werden. Folgende Arrays und Strings sind „auszumessen“. int iarr[] = {2,4,6,4,2,4,5,6,7}; double darr[] = {3.3,4.4,2.3,5.8,7.7}; char str[] = {„Hallo Welt“}; Programme\Aufgabe1_Stringlaenge.c 36. Übung Schreibe eine Funktion, die zwei int-Arrays auf Gleichheit überprüft. Die Funktion soll -1 zurückgeben, wenn beide Arrays gleich sind, oder die Position, an der ein Unterschied gefunden wurde. -2 soll zurückgegeben werden, wenn beide Arrays unterschiedlich lang sind. Programme\Aufgabe2_Stringvergleich.c 37. Übung Schreibe eine Funktion, die in einem String ein bestimmtes Zeichen durch ein anderes Zeichen ersetzt. Programme\Aufgabe3_zeichenersetzen.c Seite 52 von 69 Arrays und Strings Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 7 Zeiger (Pointer) 7.1 Pointertypen und Pointervariablen Der Arbeitsspeicher eines Rechners ist in Speicherzellen eingeteilt. Jede Speicherzelle trägt eine Nummer. Die Nummer einer Speicherzelle wird als Adresse bezeichnet. Ein Pointer (Zeiger) ist eine Variable, welche die Adresse einer im Speicher befindlichen Variablen oder Funktion aufnehmen kann. Damit verweist eine Pointervariable mit ihrem Variablenwert auf die jeweilige Adresse. Arbeitsspeicher Adresse 0012ff7c float-Variable mit Namen alpha . Hat als Wert eine float-Zahl, z.B. 1.41 1.41 0012ff7c Pointervariable mit Namen pointer vom Typ „Pointer auf float“. Sie hat als Wert die Adresse von alpha Adresse 0 Pointer und Speichervariablen sind vom Typ her gekoppelt. Es ist nicht erlaubt, dass ein Pointer vom Typ int auf eine double-Variable zeigt. Daher ist es nötig, den Datentyp anzugeben, der den Pointer referenzieren soll. Definition von Pointervariablen. Ein Pointer wird formal wie eine Variable definiert – dem Pointername lediglich ein Stern vorangestellt int *pointer1; float *pointer2; Gelesen wird von rechts nach links; also pointer1 ist ein Pointer auf int; pointer2 ist ein Pointer auf float. 7.2 Wertzuweisung an einen Pointer, Adressoperator Durch den Ausdruck pointer2 = pointer1 wird einem Pointer pointer2 der Wert des Pointers pointer1 zugewiesen. Nach der Zuweisung haben beide Pointer denselben Inhalt und zeigen damit beide auf das Objekt, auf das zunächst von pointer1 verwiesen wurde. Objekt alpha Objekt alpha pointer1 pointer1 Vor der Zuweisung pointer2 = pointer1 enthält pointer2 irgendeine Adresse pointer2 pointer2 ? Seite 53 von 69 Pointer Nicole Rottmann Nach der Zuweisung pointer2 = pointer1 zeigen beide Pointer auf dasselbe Objekt alpha Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Die einfachste Möglichkeit, einen Pointer auf ein Objekt zeigen zu lassen, besteht darin, auf der rechten Seite des Zuweisungsoperators den Adressoperator & auf eine Variable anzusenden, denn es gilt: Ist x eine Variable vom Typ Typname, so liefert der Ausdruck &x einen Pointer auf das Ovjekt x vom Typ Pointer auf Typname. Im folgenden Beispiel wird eine int-Varible alpha definiert und ein Pointer pointer auf eine intVariable. alpha pointer int alpha ; ? ? int * pointer; Durch die Zuweisung alpha = 1 wird der int-Variablen alpha der Wert 1 zugewiesen, d.h. an der Stelle des Adressraums des Rechners, an der alpha gespeichert wird, befindet sich nun der Wert 1. Zu diesem Zeitpunkt ist der Wert des Pointers noch undefiniert, denn ihm wurde noch nichts zugewiesen. alpha pointer alpha = 1; ? 1 Erst durch die Zuweisung pointer = &alpha erhält der Pointer pointer einen definierten Wert, nämlich die Adresse der Variablen alpha. pointer = &alpha; alpha pointer 1 7.2.1 Dereferenzierung: Zugriff auf ein Objekt über einen Pointer Wurde einem Pointer ein Wert zugewiesen, so will man natürlich auch auf das referenzierte Objekt, d.h. auf das Objekt auf das der Pointer zeigt zugreifen können. Dazu gibt es in C den Inhaltsoperator * den man oft auch Dereferenzierungsoperator* nennt. Mit Hilfe des Dereferenzierungsoperators erhält man aus einem Pointer, das Objekt selbst. Ist int alpha = 1; und int *pointer = &alpha; dann wird mit *pointer = 2; der Variablen alpha der Wert 2 zugewiesen. Referenzieren heißt, dass man mit einer Adresse auf ein Speicherobjekt zeigt. Eine vorhandene Variable referenziert man mit Hilfe des Adressoperators. So liefert z.B. &x die Adresse der Variablen x. Möchte man über einen Pointer auf ein Objekt zugreifen, so muss man den Pointer dereferenzieren. Seite 54 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Objekt Referenzieren mit & Referenzieren Dereferenzieren mit * Adresse des Objekts int-Variable x pointer = &x pointer enthält die Adresse von x Dereferenzieren *pointer = 5; heißt: das Objekt, auf das der Pointer pointer zeigt, erhält den Wert 5. Gilt beispielsweise pointer = &alpha, so ist *pointer = *pointer +1; dasselbe wie alpha = alpha + 1 Beispiel: Programme\pointer1.c Was gibt das Programm aus? 38. Übung Schreibe ein einfaches Programm, das die folgenden Definitionen von Variablen und die geforderten Anweisungen enthält: Definition einer Variablen i vom Typ int Definition eines Pointers ptr vom Typ int Zuweisung der Adresse von i an den Pointer ptr Zuweisung des Wertes 1 an die Variable i Ausgabe des Wertes des Pointers ptr Seite 55 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Ausgabe des Wertes von i Ausgabe des Wertes des Objekts, auf das der Pointer ptr zeigt, mit Hilfe des Dereferenzierungsoperators Zuweisung des Wertes 2 an das Objekt, auf das der Pointer ptr zeigt, mit Hilfe des Dereferenzierungsoperators und Ausgabe des Wertes von i Programme\Aufgabe1_pointer.c Zusammenfassung: Deklaration, Adressierung und Dereferenzierung von Zeigern Ausgabe: 7.2.2 Speichergröße von Zeigern Die Größe eines Zeigers hängt nicht vom Datentyp ab, auf den der Zeiger verweist, da ein Zeiger keine Werte sondern Adressen speichert. Zur Speicherung von Adressen werden auf 32-bit Rechnern vier Bytes benötigt. Seite 56 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 7.2.3 Wertebereich von Pointern, NULL-Pointer Der Wertebereich einer Pointervariablen vom Typ „Pointer auf Typname“ ist die Menge aus allen Pointern, die auf Speicherobjekte vom Typ Typname zeigen können und dem NULL-Pointer. Der Pointer NULL ist ein vordefinierter Pointer, dessen Wert sich von allen regulären Pointern unterscheidet. Der NULL-Pointer zeigt auf die Adresse 0 und damit auf kein gültiges Speicheropjekt.( In C ist festgelegt, dass Variablen und Funktionen immer an Speicherplätzen abgelegt werden, deren Adressen von 0 verschieden sind). int * pointer = NULL; Der NULL – Pointer wird dazu verwendet, Zeigervariablen direkt bei der Definition zu initialisieren. Ein Pointer, der mit NULL initialisiert ist, zeigt an, dass er noch auf keine gültige Speicherstelle zeigt. Durch die Deklaration der Pointervariablen wird noch kein Speicherplatz für ein Objekt vom Typ Typname reserviert. Wenn ein Pointer noch nicht initialisiert ist, zeigt er auf eine beliebige Adresse im Speicherraum, was zu schwerwiegenden Fehlern führen kann. 7.3 Arrays und Pointer Ein eindimensionales Feld wird folgendermaßen definiert: int alpha [5]; //Definition des Arrays alpha mit Platz für 5 Integer Zahlen Eine einfache Möglichkeit, einen Pointer auf ein Arrayelement zeigen zu lassen: int * pointer; pointer = &alpha[i-1]; //Definition des Pointers pointer //pointer zeigt auf das i-te Arrayelement. 7.3.1 Äquivalenz von Array- und Pointernotation Der Name des Arrays kann als konstanter Zeiger auf das erste Element des Arrays verwendet werden. alpha[0] alpha alpha[1] alpha[2] alpha[3] alpha[4] &alpha[2] Pointer auf ein Array Für das erste Element gibt es zwei gleichwertige Schreibweisen: alpha[0] und mit Verwendung des Dereferenzierungsoperators auch *alpha. Zugriffe auf das i-te Element kann man mit alpha[i-1] bewerkstelligen, möglich ist aber auch *(alpha + i-1). Wird nämlich ein Pointer um 1 erhöht, so zeigt er um ein Element des Arrays weiter. alpha sei ein Array. Dann gilt alpha[i] = * (alpha+i). Seite 57 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV alpha *alpha *(alpha+1) *(alpha+2) *(alpha+3) *(alpha+4) Arrayelemente in Pointernotation Diese Äquivalenz gilt auch in der anderen Richtung. So wie ein Array durch Pointer dargestellt werden kann, gilt umgekehrt die Aussage, dass die Pointernotation gleichbedeutend ist zur Arraynotation. Vergleich von Arrays Es ist nicht möglich mit dem Vergleichsoperator zwei Felder auf identischen Inhalt zu überprüfen, wie z.B. durch arr1 == arr2. Der Grund dafür ist, dass die Arraynamen äquivalent sind zur Speicheradresse des ersten Vektorelements. Es wird mit arr1 == arr2 also nur verglichen, ob arr1 und arr2 auf dieselbe Adresse zeigen. Es gibt nur die Möglichkeit die einzelnen Feldelemente in einer Schleife zu vergleichen. Nur der sizeof Operator und der Adressoberator& kann als Operation auf ein Array als Ganzes ausgeführt werden. Sowohl der Arrayname als auch der Adressoperator angewandt auf einen Arraynamen stellen einen Pointer auf das erste Element des Arrays dar. Ausdrücke wie alpha ++ und alpha – sind nicht erlaubt, da der Arrayname hier in einen Pointer umgewandelt wird. 7.3.2 Zeigerarithmetik Neben der Zuweisung von Adressen anderer Objekte sind noch folgende Operationen erlaubt: Vergleiche zweier Zeiger mit folgenden Operatoren: == , != , < , <= , > und >=. Die Verwendung von Vergleichsoperatoren ist allerdings nur dann sinnvoll, wenn die Zeiger auf Array- Elemente zeigen. So liefert bspw. ein Vergleich von zeiger1 < zeiger2 wahr zurück, wenn die Speicheradresse von zeiger1 höher ist als die Adresse von zeiger2. Subtraktion zweier Zeiger liefert als Ergebnis die Anzahl der Elemente zwischen den Zeigern. Addition und Subtraktion des Zeigers mit einer Ganzzahl. Zeigt bspw. der Zeiger zeiger1 auf das Array[i], bedeutet eine Zuweisung des Zeigers mit zeiger2 = zeiger1 + 2, dass zeiger2 auf das Array Element array[i+2] zeigt. 7.3.2.1 Addition und Subtraktion Wenn ein Pointer vom Typ int * um 1 erhöht wird, so zeigt er um ein int - Objekt weiter. Wird ein Pointer vom Typ float * um 1 erhöht, so zeigt er um ein float – Objekt weiter. Genauso können Pointer erniedrigt werden. Ein Pointer, der auf ein Element in einem Array zeigt und ein ganzzahliger Wert dürfen addiert oder subtrahiert werden. Zeigt das Ergebnis dann nicht mehr in das Feld ist das Resultat undefiniert. 7.3.2.2 Vergleiche Wenn zwei Pointer auf dasselbe Speicherobjekt zeigen so ergibt der Test auf Gleichheit den boolschen Wert wahr. Seite 58 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Für Pointer, die auf Elemente des gleichen Arrays zeigen, kann aus dem Ergebnis der Vergleiche größer oder kleiner geschlossen werden, dass das eine Element, weiter vorne im Array liegt als das andere. 7.4 Übergabe von Arrays und Zeichenketten Bei der Übergabe eines Arrays an eine Funktion wird als aktueller Parameter der Arrayname übergeben. Der Arrayname stellt dabei einen Pointer auf das erste Element des Arrays dar. Der formale Parameter für die Übergabe eines Arrays kann ein offenes Array sein - oder wegen der Pointereigenschaft des Arraynamens – auch ein Pointer auf den Komponententyp des Arrays. Beispiel: Programme\arrayuebergabe.c Vergleich von char – Arrays und Pointern auf Zeichenketten Zur Speicherung von konstanten Zeichenketten gibt es zwei Möglichkeiten. a. char buffer [] = “hello“; //char- Array mit konstanter Zeichenkette b. char * pointer =“hello“; //Pointer auf erstes Element der Zeichenkette, also das ‚h’ Bei einem char-Array buffer kann der in ihm gespeicherte String verändert werden. buffer ist ein konstanter Pointer auf das erste Element des Arrays und kann auf keine andere Adresse zeigen. Zeigt eine Stringvariable vom Typ char * auf eine konstante Zeichenkette, so führt der Compiler die Speicherung der Zeichenkette selbst durch. Die Zeigervariable vom Typ char * kann eine neue Adresse zugewiesen bekommen. Seite 59 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 7.4.1 Das Schlüsselwort const bei Pointern und Arrays Mit Hilfe des Schlüsselworts const können benannte Konstanten wie Variablen vereinbart werden, indem man einer normalen Definition mit Initialisierung das Wort const voranstellt. Die sofortige Initialisierung ist vorgeschrieben. Die mit const definierten Variablen besitzen einen Wert, einen Typ, einen Namen und auch eine Adresse. Sie liegen also im adressierbaren Speicherbereich und dürfen nicht auf der linken Seite von Zuweisungen auftreten. So bedeutet const int feld[] = {1, 2, 3}; dass alle Feldelemente feld[0], feld[1], feld[2] Konstanten sind. Aufpassen muss man bei der Anwendung des Schlüsselwortes const im Zusammenhang mit Pointern. . Angenommen, ein char- Array sei definiert durch char blick[] = „Er will es blicken.“; dann bedeutet const char * text = blick; nicht, dass der Pointer konstant ist, sondern dass der Pointer auf eine konstante Zeichenkette zeigt. Demnach ist text[1] = ’s’; nicht möglich, wohl aber kann der Pointer auf eine andere konstante Zeichenkette zeigen, beispielsweise: text = “Jetzt blicke ich auch durch“; Soll ein konstanter Pointer eingeführt werden, so muss const vor dem Pointernamen stehen wie im folgenden Beispiel: char lili[] = “Ich liebe Lili“; char * const hugo = lili; Man kann sich diese Notation besser merken, indem man char * const hugo von rechts nach links liest mit den Worten hugo ist ein konstanter Pointer auf char. Dann ist zwar hugo[13] = ’o’; möglich, wird allerdings vielleicht der Lili nicht gefallen. hugo = “Ich liebe Susi“; ist allerdings nicht mehr möglich, da zum einen der Pointer hugo konstant ist und auch die Zeichenkette als Konstante geschützt ist. Aufgabe Das folgende Programm hat die Aufgabe, einen über die Tastatur eingegebenen Zeichenstrom in ein char – Array zu speichern, solange bis entweder ein ’$’ eingegeben wird oder bis 60 Zeichen erreicht sind. Danach soll das Eingabe- Array in ein Ausgabe- Array kopiert werden, wobei jedes im Zeichenstrom eingegebene % Zeichen in * umgewandelt werden soll. Die Zahl der durchgeführten Konvertierungen ist zu berechnen. Am Bildschirm ist das eingegebene Array, das konvertierte Array, sowie die Zahl der Konvertierungen auszugeben. Zur Lösung der Aufgabe benötigst du eine Funktion getchar(). Die Funktion verwendest du im Programm in einer while Schleife um den Zeichenstrom einzulesen und in einem Array zu speichern.. Im Internet findest du Hinweise auf die Syntax und die Verwendung der Funktion. Bspw. auf folgender Seite: http://de.wikibooks.org/wiki/C-Programmierung:_Einfache_Ein-_und_Ausgabe Programme\Scannen.c Seite 60 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 7.5 Zeiger als Funktionsparameter (call – by – reference) Funtionen, die mit einem oder mehreren Paramteren definiert werden und mit return einen Rückgabewert zurückliefern, nennt man call – by – value. Der Nachteil dieser Methode ist, dass bei jedem Aufruf erst einmal alle Parameter kopiert werden müssen, sodass diese Variablen der Funktion anschließend als lokale Variablen zur Verfügung stehen. Als bessere Alternative bietet es sich hierfür an, die Adressen der entsprechenden Variablen anstatt einer Kopie an die Funktion zu übergeben. Beispiel: Programme\callbyreference.c Seite 61 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Mit reset(&ival) wird die Adresse der Variablen ival als Referenz an die Funktion reset() übergeben. Im Funktionskopf reset() muss natürlich als formaler Parameter ein Zeiger mit dem entsprechenden Typ definiert sein. Mit Hilfe des Indirektionsoperators * und der Variablen val kann jetzt auf den Wert der Variablen ival zugegriffen werden und diese der Wert 0 zugewiesen werden. Ein Rückgabewert ist nicht erforderlich, da der Variable mit der Übersetzung des Programms eine Adresse zugewiesen wird, welche sich während der Laufzeit des Programms nicht mehr ändern lässt. Fragen: 1. Erläutere die grundlegende Funktion von Zeigern. Woran erkannt man einen Zeiger? 2. Welche Gefahr entsteht beim Initialisieren von Zeigern? 3. Was versteht man unter einer Dereferenzierung 4. Wie unterscheiden sich die Speichergrößen der Zeiger bei den einzelnen Datentypen? 5. Was versteht man unter call-by-reference? Lösung Aufgabe 2 Welcher Fehler wurde hier gemacht? int *ptr; int ival; ptr = ival; *ptr = 255; Aufgabe 3 Welcher Wert wird mit den beiden printf- Anweisungen ausgegeben? int *ptr ; Seite 62 von 69 Pointer Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg int ival; ptr = &ival; ival = 98765432; *ptr = 12345679; printf(“%d\n”, *ptr); printf(“%d\n”, ival); Aufgabe 4 Was geben die printf- Anweisungen auf dem Bildschirm aus? int iarray[] = { 12, 34, 56, 78, 90, 23, 45 }; int *ptr1, *ptr2; ptr1 = iarray; ptr2 = &array[4]; ptr1+=2; ptr2++; printf(“%d\n, *ptr1); printf(“%d\n, *ptr2); printf(“%d\n, prt2 - ptr1); printf(“%d\n, (ptr1 < ptr2)); printf(“%d\n, ((*ptr1)<(*ptr2))); Seite 63 von 69 Pointer Nicole Rottmann LK IV Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 8 Sortier- und Suchverfahren Unter Sortieren versteht man die Realisierung einer Ordnungsrelation innerhalb einer gegebenen Menge von Objekten. Objekte werden sortiert, damit man später den Suchvorgang vereinfachen kann. Dabei kann das Sortierkriterium aus mehreren Schlüsseln bestehen. So kann beispielsweise eine Studentenliste zuerst nach Studiengängen sortiert werden und dann innerhalb der Studiengänge nach dem Namen der Studenten. Für das Sortieren gibt es verschiedene Verfahren, die sich nach Aufwand, Speicherbedarf etc. unterscheiden. Wenn es mehrere Verfahren zu Lösung des gleichen Problems gibt, stellt sich die Frage welches das bessere ist. In der Informatik wird dies unter den Aspekten der Komplexitätsbetrachtungen untersucht. Dabei wird ein Verfahren bzw. ein Algorithmus im Hinblick auf seinen Speicherbedarf oder die benötigte Rechenzeit analysiert. Grundsätzlich werden für jedes Sortierverfahren Vergleichs- und Austauschoperationen benötigt. Der Aufwand wird zunächst immer für die Anzahl der Vergleiche sowie die Anzahl der Bewegungen von Elementen berechnet, wobei von einer Anzahl n unsortierten Elementen ausgegangen wird. 8.1 Sortieren durch Auswählen (Selection Sort) Sortieren durch Auswählen auch als Selection Sort oder MinSort bzw. MaxSort bekannt arbeitet nach folgendem Prinzip 1) Teile das Array in folgender Weise a) eine linke, sortierte Teilliste, die zu Beginn leer ist, b) und eine rechte, unsortierte Teilliste 2) Suche in der rechten unsortierten Teilliste das Element mit dem kleinsten Wert. 3) Tausche das erste Element der rechten Teilliste mit dem gefundenen Element 4) Verlängere die linke Liste rechts um 1 Element und verkürze die rechte Liste links um ein Element. Weiter bei Schritt 2. Das Verfahren wird durch folgendes Bild veranschaulicht: vertauschen Feldindex: 3 7 8 6 9 4 0 1 2 3 4 5 linke sortierte Teilliste kleinstes Element der rechten Teilliste rechte unsortierte Teilliste Das Verfahren beginnt mit einer leeren linken Teilliste, während die rechte Teilliste zunächst das ganze Array enthält. Das Verfahren endet, wenn die rechte Teilliste nur noch 1 Element enthält. Programme\auswahl.c Seite 64 von 69 Sortierverfahren Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg Absteigend sortierte Reihe: Programme\auswahlrückwärts.c Seite 65 von 69 Sortierverfahren Nicole Rottmann LK IV Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV 8.2 Rekursives Sortieren mit dem Quicksort-Verfahren Das Quicksort – Verfahren arbeitet nach dem Teile und Herrsche Prinzip. Man teilt das Array in zwei Teilarrays auf. Dazu wird ein beliebiges Vergleichselement aus dem Array benötigt, wobei sich das mittlere Element anbietet. Bedingung an die Teilarrays ist es dann, dass das linke Teilarray nur solche Elemente hat, die entweder gleich groß oder kleiner wie das Vergleichselement sind. Das rechte Teilarray hat weiterhin nur solche Elemente, die größer oder gleich groß wie das Vergleichselement sind. Danach wird das gleiche Teilungsverfahren auf das jeweilige linke und rechte Teilarray rekursiv angewandt, bis ein Teilarray weniger als 2 Elemente hat. Das Verfahren hat folgende Schritte: 1) Auswahl eines beliebigen Vergleichselements, z.B. des mittleren Elements des Arrays 2) Linkes Teilarray mit kleineren oder gleichen und rechtes Teilarray mit größeren oder gleichen Elementen erzeugen. a) Absuchen des linken Teilarrays von links her, bis ein größeres Element als das Vergleichselement gefunden wird. b) Absuchen des rechten Teilarrays von rechts her, bis ein kleineres Element als das Vergleichselement gefunden wird. c) Vertauschen der beiden gefundenen Elemente. d) Schritte a bis c wiederholden, solange sich noch falsche Elemente in den Teilarrays befinden. 3) Rekursive Zerlegung des linken und rechten Teilarrays gemäß 1) und 2) solange, bis die Teilarrays weniger als 2 Elemente haben. 7 3 8 6 9 1 2 4 Vertauschen (2.3) Rekursiver Aufruf (3) mit linkem und rechtem Teilarray (3.1) 4 3 (3.4) 2 1 9 6 8 7 Vertauschen (2.3) Rekursive Aufrufe (3.2) (3.5) (3.3) 1 2 3 4 1 2 3 4 9 8 7 7 8 9 Vertauschen (2.3) Resultat, nachdem alle Rekursionen (3) zurückgekehrt sind. 6 Die schattierten Felder sind die jeweiligen Vergleichselemente. Die Rekursiven Schritte sind mit 3.x dargestellt. Seite 66 von 69 Sortierverfahren Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg Beispiel: Programme\quicksrt.c Seite 67 von 69 Sortierverfahren Nicole Rottmann LK IV Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Rückwärts Programme\quicksrtrückwärts.c 8.2.1 Bubble Sort Programme\bubble.c Bei Bubble Sort wird jeweils das vollständige Feld durchlaufen und jedes Mal – wenn notwendig – werden die benachbarten Elemente miteinander vertauscht. Nach jedem Durchlauf bekommt immer das letzte Element einen Festen Platz. Daher wird eine rückwärts zählende Schleife eingesetzt. Für das Test_Feld {5,2,7,9,1,4} ergibt sich folgende Ausgabe: Es werden immer von links nach rechts die beiden benachbarten Elemente miteinander verglichen und 8.3 Sequentielles Suchen Dabei sind die Elemente einer Menge in ungeordneter Form abgelegt. Der Algorithmus Sequentielles Suchen besteht aus dem Vergleich des Suchbegriffes mit den Werten der Menge. Das Verfahren ist für eine Menge mit mehr als 100 Elemente uneffizient. 8.4 Binäres Suchen Voraussetzung für das Halbierungssuchen ist eine sortierte Liste von Elementen. Zur Vereinfachung wird von ganzzahligen Werten ausgegangen. Dabei wird wie folgt vorgegangen: 1) Zu Beginn ist die gesamte Liste das Suchintervall 2) Das Suchintervall wird nun halbiert. Durch Vergleich mit dem Element an der Position, an der das Intervall geteilt wurde, kann festgestellt werden, ob dieses Element das gesuchte Element ist oder ob sich das gesuchte Element im oberen oder unteren Teilintervall befindet. Die Halbierung der Teilintervalle werden solange fortgesetzt, bis das Element gefunden wurde oder das Teilintervall leer ist. In diesem Fall ist das gesuchte Element nicht in der Liste. 39. Übung Schreibe ein Programm, das eine vom Benutzer einzugebende Liste von ganzzahligen Werten einliest. Diese Werte sortiert und als sortiertes Feld wieder ausgibt. Der Benutzer wird aufgefordert ein Element anzugeben, das gesucht werden soll. Wird das entsprechende Element gefunden, soll die Stelle ausgegeben werden an der sich das Element in der sortierten Liste befindet. Wird das Element nicht gefunden soll ausgegeben werden, dass sich das gesuchte Element nicht in der Liste befindet. Seite 68 von 69 Sortierverfahren Nicole Rottmann Programmieren in C Wirtschaftsgymnasium Eisenberg LK IV Verwendete Literatur: 1 2 3 4 Jürgen Wolf, Grundkurs in C Galileo Computing, Bonn 2010 Jürgen Wolf, C von A bis Z Galileo Computing, Bonn 2009 Norbert Heiderich, Wolfgang Meyer, Technische Probleme lösen mit C/ C++ Hanser Verlag, München 2010 Manfred Dausmann, Ulrich Bröckl, C als erste Programmiersprache Vieweg+Teubner, Wiesbaden 2011 Seite 69 von 69 Sortierverfahren Nicole Rottmann